147e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko// Copyright 2014 The Chromium OS Authors. All rights reserved. 247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko// Use of this source code is governed by a BSD-style license that can be 347e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko// found in the LICENSE file. 447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko 59ed0cab99f18acb3570a35e9408f24355f6b8324Alex Vakulenko#include <brillo/http/http_form_data.h> 647e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko 747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko#include <set> 847e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko 947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko#include <base/files/file_util.h> 1047e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko#include <base/files/scoped_temp_dir.h> 119ed0cab99f18acb3570a35e9408f24355f6b8324Alex Vakulenko#include <brillo/mime_utils.h> 129ed0cab99f18acb3570a35e9408f24355f6b8324Alex Vakulenko#include <brillo/streams/file_stream.h> 139ed0cab99f18acb3570a35e9408f24355f6b8324Alex Vakulenko#include <brillo/streams/input_stream_set.h> 1447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko#include <gtest/gtest.h> 1547e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko 169ed0cab99f18acb3570a35e9408f24355f6b8324Alex Vakulenkonamespace brillo { 1747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenkonamespace http { 1880663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenkonamespace { 1980663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenkostd::string GetFormFieldData(FormField* field) { 2080663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko std::vector<StreamPtr> streams; 2180663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko CHECK(field->ExtractDataStreams(&streams)); 2280663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko StreamPtr stream = InputStreamSet::Create(std::move(streams), nullptr); 2380663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko 2480663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko std::vector<uint8_t> data(stream->GetSize()); 2580663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko EXPECT_TRUE(stream->ReadAllBlocking(data.data(), data.size(), nullptr)); 2680663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko return std::string{data.begin(), data.end()}; 2780663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko} 2880663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko} // anonymous namespace 2947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko 3047e9a9dd3dce9d197820ee4241135e6859f95360Alex VakulenkoTEST(HttpFormData, TextFormField) { 3147e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko TextFormField form_field{"field1", "abcdefg", mime::text::kPlain, "7bit"}; 3247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko const char expected_header[] = 3347e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Disposition: form-data; name=\"field1\"\r\n" 3447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Type: text/plain\r\n" 3547e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Transfer-Encoding: 7bit\r\n" 3647e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "\r\n"; 3747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko EXPECT_EQ(expected_header, form_field.GetContentHeader()); 3880663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko EXPECT_EQ("abcdefg", GetFormFieldData(&form_field)); 3947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko} 4047e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko 4147e9a9dd3dce9d197820ee4241135e6859f95360Alex VakulenkoTEST(HttpFormData, FileFormField) { 4247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko base::ScopedTempDir dir; 4347e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko ASSERT_TRUE(dir.CreateUniqueTempDir()); 4447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko std::string file_content{"text line1\ntext line2\n"}; 45324b9515118eca1ac9b579af6dd4d9ffc33a023cJay Civelli base::FilePath file_name = dir.GetPath().Append("sample.txt"); 4647e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko ASSERT_EQ(file_content.size(), 4705d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko static_cast<size_t>(base::WriteFile( 4805d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko file_name, file_content.data(), file_content.size()))); 4947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko 5080663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko StreamPtr stream = FileStream::Open(file_name, Stream::AccessMode::READ, 5180663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko FileStream::Disposition::OPEN_EXISTING, 5280663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko nullptr); 5380663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko ASSERT_NE(nullptr, stream); 5405d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko FileFormField form_field{"test_file", 5580663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko std::move(stream), 5605d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko "sample.txt", 5747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko content_disposition::kFormData, 5805d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko mime::text::kPlain, 5905d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko ""}; 6047e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko const char expected_header[] = 6147e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Disposition: form-data; name=\"test_file\";" 6247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko " filename=\"sample.txt\"\r\n" 6347e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Type: text/plain\r\n" 6447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "\r\n"; 6547e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko EXPECT_EQ(expected_header, form_field.GetContentHeader()); 6680663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko EXPECT_EQ(file_content, GetFormFieldData(&form_field)); 6747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko} 6847e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko 6947e9a9dd3dce9d197820ee4241135e6859f95360Alex VakulenkoTEST(HttpFormData, MultiPartFormField) { 7047e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko base::ScopedTempDir dir; 7147e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko ASSERT_TRUE(dir.CreateUniqueTempDir()); 7247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko std::string file1{"text line1\ntext line2\n"}; 73324b9515118eca1ac9b579af6dd4d9ffc33a023cJay Civelli base::FilePath filename1 = dir.GetPath().Append("sample.txt"); 7447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko ASSERT_EQ(file1.size(), 7505d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko static_cast<size_t>( 7605d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko base::WriteFile(filename1, file1.data(), file1.size()))); 7747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko std::string file2{"\x01\x02\x03\x04\x05"}; 78324b9515118eca1ac9b579af6dd4d9ffc33a023cJay Civelli base::FilePath filename2 = dir.GetPath().Append("test.bin"); 7947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko ASSERT_EQ(file2.size(), 8005d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko static_cast<size_t>( 8105d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko base::WriteFile(filename2, file2.data(), file2.size()))); 8247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko 8347e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko MultiPartFormField form_field{"foo", mime::multipart::kFormData, "Delimiter"}; 8447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko form_field.AddTextField("name", "John Doe"); 8505d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko EXPECT_TRUE(form_field.AddFileField("file1", 8605d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko filename1, 8747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko content_disposition::kFormData, 8805d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko mime::text::kPlain, 8905d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko nullptr)); 9005d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko EXPECT_TRUE(form_field.AddFileField("file2", 9105d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko filename2, 9247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko content_disposition::kFormData, 9347e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko mime::application::kOctet_stream, 9447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko nullptr)); 9547e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko const char expected_header[] = 9647e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Disposition: form-data; name=\"foo\"\r\n" 9747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Type: multipart/form-data; boundary=\"Delimiter\"\r\n" 9847e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "\r\n"; 9947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko EXPECT_EQ(expected_header, form_field.GetContentHeader()); 10047e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko const char expected_data[] = 10147e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "--Delimiter\r\n" 10247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Disposition: form-data; name=\"name\"\r\n" 10347e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "\r\n" 10447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "John Doe\r\n" 10547e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "--Delimiter\r\n" 10647e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Disposition: form-data; name=\"file1\";" 10747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko " filename=\"sample.txt\"\r\n" 10847e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Type: text/plain\r\n" 10947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Transfer-Encoding: binary\r\n" 11047e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "\r\n" 11147e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "text line1\ntext line2\n\r\n" 11247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "--Delimiter\r\n" 11347e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Disposition: form-data; name=\"file2\";" 11447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko " filename=\"test.bin\"\r\n" 11547e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Type: application/octet-stream\r\n" 11647e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Transfer-Encoding: binary\r\n" 11747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "\r\n" 11847e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "\x01\x02\x03\x04\x05\r\n" 11947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "--Delimiter--"; 12080663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko EXPECT_EQ(expected_data, GetFormFieldData(&form_field)); 12147e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko} 12247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko 12347e9a9dd3dce9d197820ee4241135e6859f95360Alex VakulenkoTEST(HttpFormData, MultiPartBoundary) { 12447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko const int count = 10; 12547e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko std::set<std::string> boundaries; 12647e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko for (int i = 0; i < count; i++) { 12747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko MultiPartFormField field{""}; 12847e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko std::string boundary = field.GetBoundary(); 12947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko boundaries.insert(boundary); 13047e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko // Our generated boundary must be 16 character long and contain lowercase 13147e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko // hexadecimal digits only. 13247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko EXPECT_EQ(16u, boundary.size()); 13347e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko EXPECT_EQ(std::string::npos, 13447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko boundary.find_first_not_of("0123456789abcdef")); 13547e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko } 13647e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko // Now make sure the boundary strings were generated at random, so we should 13747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko // get |count| unique boundary strings. However since the strings are random, 13847e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko // there is a very slim change of generating the same string twice, so 13947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko // expect at least 90% of unique strings. 90% is picked arbitrarily here. 14047e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko int expected_min_unique = count * 9 / 10; 14147e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko EXPECT_GE(boundaries.size(), expected_min_unique); 14247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko} 14347e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko 14447e9a9dd3dce9d197820ee4241135e6859f95360Alex VakulenkoTEST(HttpFormData, FormData) { 14547e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko base::ScopedTempDir dir; 14647e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko ASSERT_TRUE(dir.CreateUniqueTempDir()); 14747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko std::string file1{"text line1\ntext line2\n"}; 148324b9515118eca1ac9b579af6dd4d9ffc33a023cJay Civelli base::FilePath filename1 = dir.GetPath().Append("sample.txt"); 14947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko ASSERT_EQ(file1.size(), 15005d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko static_cast<size_t>( 15105d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko base::WriteFile(filename1, file1.data(), file1.size()))); 15247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko std::string file2{"\x01\x02\x03\x04\x05"}; 153324b9515118eca1ac9b579af6dd4d9ffc33a023cJay Civelli base::FilePath filename2 = dir.GetPath().Append("test.bin"); 15447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko ASSERT_EQ(file2.size(), 15505d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko static_cast<size_t>( 15605d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko base::WriteFile(filename2, file2.data(), file2.size()))); 15747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko 15847e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko FormData form_data{"boundary1"}; 15947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko form_data.AddTextField("name", "John Doe"); 16047e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko std::unique_ptr<MultiPartFormField> files{ 16147e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko new MultiPartFormField{"files", "", "boundary2"}}; 16205d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko EXPECT_TRUE(files->AddFileField( 16305d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko "", filename1, content_disposition::kFile, mime::text::kPlain, nullptr)); 16405d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko EXPECT_TRUE(files->AddFileField("", 16505d29044d14a60775ed6c51c75a414eb0cb50347Alex Vakulenko filename2, 16647e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko content_disposition::kFile, 16747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko mime::application::kOctet_stream, 16847e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko nullptr)); 16947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko form_data.AddCustomField(std::move(files)); 17047e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko EXPECT_EQ("multipart/form-data; boundary=\"boundary1\"", 17147e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko form_data.GetContentType()); 17280663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko 17380663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko StreamPtr stream = form_data.ExtractDataStream(); 17480663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko std::vector<uint8_t> data(stream->GetSize()); 17580663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko EXPECT_TRUE(stream->ReadAllBlocking(data.data(), data.size(), nullptr)); 17647e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko const char expected_data[] = 17747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "--boundary1\r\n" 17847e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Disposition: form-data; name=\"name\"\r\n" 17947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "\r\n" 18047e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "John Doe\r\n" 18147e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "--boundary1\r\n" 18247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Disposition: form-data; name=\"files\"\r\n" 18347e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Type: multipart/mixed; boundary=\"boundary2\"\r\n" 18447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "\r\n" 18547e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "--boundary2\r\n" 18647e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Disposition: file; filename=\"sample.txt\"\r\n" 18747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Type: text/plain\r\n" 18847e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Transfer-Encoding: binary\r\n" 18947e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "\r\n" 19047e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "text line1\ntext line2\n\r\n" 19147e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "--boundary2\r\n" 19247e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Disposition: file; filename=\"test.bin\"\r\n" 19347e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Type: application/octet-stream\r\n" 19447e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "Content-Transfer-Encoding: binary\r\n" 19547e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "\r\n" 19647e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "\x01\x02\x03\x04\x05\r\n" 19747e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "--boundary2--\r\n" 19847e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko "--boundary1--"; 19980663bf89f5ba2c0646f196371a1fa92123855c6Alex Vakulenko EXPECT_EQ(expected_data, (std::string{data.begin(), data.end()})); 20047e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko} 20147e9a9dd3dce9d197820ee4241135e6859f95360Alex Vakulenko} // namespace http 2029ed0cab99f18acb3570a35e9408f24355f6b8324Alex Vakulenko} // namespace brillo 203