1// Copyright 2014 The Chromium OS Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <brillo/http/http_form_data.h> 6 7#include <set> 8 9#include <base/files/file_util.h> 10#include <base/files/scoped_temp_dir.h> 11#include <brillo/mime_utils.h> 12#include <brillo/streams/file_stream.h> 13#include <brillo/streams/input_stream_set.h> 14#include <gtest/gtest.h> 15 16namespace brillo { 17namespace http { 18namespace { 19std::string GetFormFieldData(FormField* field) { 20 std::vector<StreamPtr> streams; 21 CHECK(field->ExtractDataStreams(&streams)); 22 StreamPtr stream = InputStreamSet::Create(std::move(streams), nullptr); 23 24 std::vector<uint8_t> data(stream->GetSize()); 25 EXPECT_TRUE(stream->ReadAllBlocking(data.data(), data.size(), nullptr)); 26 return std::string{data.begin(), data.end()}; 27} 28} // anonymous namespace 29 30TEST(HttpFormData, TextFormField) { 31 TextFormField form_field{"field1", "abcdefg", mime::text::kPlain, "7bit"}; 32 const char expected_header[] = 33 "Content-Disposition: form-data; name=\"field1\"\r\n" 34 "Content-Type: text/plain\r\n" 35 "Content-Transfer-Encoding: 7bit\r\n" 36 "\r\n"; 37 EXPECT_EQ(expected_header, form_field.GetContentHeader()); 38 EXPECT_EQ("abcdefg", GetFormFieldData(&form_field)); 39} 40 41TEST(HttpFormData, FileFormField) { 42 base::ScopedTempDir dir; 43 ASSERT_TRUE(dir.CreateUniqueTempDir()); 44 std::string file_content{"text line1\ntext line2\n"}; 45 base::FilePath file_name = dir.path().Append("sample.txt"); 46 ASSERT_EQ(file_content.size(), 47 static_cast<size_t>(base::WriteFile( 48 file_name, file_content.data(), file_content.size()))); 49 50 StreamPtr stream = FileStream::Open(file_name, Stream::AccessMode::READ, 51 FileStream::Disposition::OPEN_EXISTING, 52 nullptr); 53 ASSERT_NE(nullptr, stream); 54 FileFormField form_field{"test_file", 55 std::move(stream), 56 "sample.txt", 57 content_disposition::kFormData, 58 mime::text::kPlain, 59 ""}; 60 const char expected_header[] = 61 "Content-Disposition: form-data; name=\"test_file\";" 62 " filename=\"sample.txt\"\r\n" 63 "Content-Type: text/plain\r\n" 64 "\r\n"; 65 EXPECT_EQ(expected_header, form_field.GetContentHeader()); 66 EXPECT_EQ(file_content, GetFormFieldData(&form_field)); 67} 68 69TEST(HttpFormData, MultiPartFormField) { 70 base::ScopedTempDir dir; 71 ASSERT_TRUE(dir.CreateUniqueTempDir()); 72 std::string file1{"text line1\ntext line2\n"}; 73 base::FilePath filename1 = dir.path().Append("sample.txt"); 74 ASSERT_EQ(file1.size(), 75 static_cast<size_t>( 76 base::WriteFile(filename1, file1.data(), file1.size()))); 77 std::string file2{"\x01\x02\x03\x04\x05"}; 78 base::FilePath filename2 = dir.path().Append("test.bin"); 79 ASSERT_EQ(file2.size(), 80 static_cast<size_t>( 81 base::WriteFile(filename2, file2.data(), file2.size()))); 82 83 MultiPartFormField form_field{"foo", mime::multipart::kFormData, "Delimiter"}; 84 form_field.AddTextField("name", "John Doe"); 85 EXPECT_TRUE(form_field.AddFileField("file1", 86 filename1, 87 content_disposition::kFormData, 88 mime::text::kPlain, 89 nullptr)); 90 EXPECT_TRUE(form_field.AddFileField("file2", 91 filename2, 92 content_disposition::kFormData, 93 mime::application::kOctet_stream, 94 nullptr)); 95 const char expected_header[] = 96 "Content-Disposition: form-data; name=\"foo\"\r\n" 97 "Content-Type: multipart/form-data; boundary=\"Delimiter\"\r\n" 98 "\r\n"; 99 EXPECT_EQ(expected_header, form_field.GetContentHeader()); 100 const char expected_data[] = 101 "--Delimiter\r\n" 102 "Content-Disposition: form-data; name=\"name\"\r\n" 103 "\r\n" 104 "John Doe\r\n" 105 "--Delimiter\r\n" 106 "Content-Disposition: form-data; name=\"file1\";" 107 " filename=\"sample.txt\"\r\n" 108 "Content-Type: text/plain\r\n" 109 "Content-Transfer-Encoding: binary\r\n" 110 "\r\n" 111 "text line1\ntext line2\n\r\n" 112 "--Delimiter\r\n" 113 "Content-Disposition: form-data; name=\"file2\";" 114 " filename=\"test.bin\"\r\n" 115 "Content-Type: application/octet-stream\r\n" 116 "Content-Transfer-Encoding: binary\r\n" 117 "\r\n" 118 "\x01\x02\x03\x04\x05\r\n" 119 "--Delimiter--"; 120 EXPECT_EQ(expected_data, GetFormFieldData(&form_field)); 121} 122 123TEST(HttpFormData, MultiPartBoundary) { 124 const int count = 10; 125 std::set<std::string> boundaries; 126 for (int i = 0; i < count; i++) { 127 MultiPartFormField field{""}; 128 std::string boundary = field.GetBoundary(); 129 boundaries.insert(boundary); 130 // Our generated boundary must be 16 character long and contain lowercase 131 // hexadecimal digits only. 132 EXPECT_EQ(16u, boundary.size()); 133 EXPECT_EQ(std::string::npos, 134 boundary.find_first_not_of("0123456789abcdef")); 135 } 136 // Now make sure the boundary strings were generated at random, so we should 137 // get |count| unique boundary strings. However since the strings are random, 138 // there is a very slim change of generating the same string twice, so 139 // expect at least 90% of unique strings. 90% is picked arbitrarily here. 140 int expected_min_unique = count * 9 / 10; 141 EXPECT_GE(boundaries.size(), expected_min_unique); 142} 143 144TEST(HttpFormData, FormData) { 145 base::ScopedTempDir dir; 146 ASSERT_TRUE(dir.CreateUniqueTempDir()); 147 std::string file1{"text line1\ntext line2\n"}; 148 base::FilePath filename1 = dir.path().Append("sample.txt"); 149 ASSERT_EQ(file1.size(), 150 static_cast<size_t>( 151 base::WriteFile(filename1, file1.data(), file1.size()))); 152 std::string file2{"\x01\x02\x03\x04\x05"}; 153 base::FilePath filename2 = dir.path().Append("test.bin"); 154 ASSERT_EQ(file2.size(), 155 static_cast<size_t>( 156 base::WriteFile(filename2, file2.data(), file2.size()))); 157 158 FormData form_data{"boundary1"}; 159 form_data.AddTextField("name", "John Doe"); 160 std::unique_ptr<MultiPartFormField> files{ 161 new MultiPartFormField{"files", "", "boundary2"}}; 162 EXPECT_TRUE(files->AddFileField( 163 "", filename1, content_disposition::kFile, mime::text::kPlain, nullptr)); 164 EXPECT_TRUE(files->AddFileField("", 165 filename2, 166 content_disposition::kFile, 167 mime::application::kOctet_stream, 168 nullptr)); 169 form_data.AddCustomField(std::move(files)); 170 EXPECT_EQ("multipart/form-data; boundary=\"boundary1\"", 171 form_data.GetContentType()); 172 173 StreamPtr stream = form_data.ExtractDataStream(); 174 std::vector<uint8_t> data(stream->GetSize()); 175 EXPECT_TRUE(stream->ReadAllBlocking(data.data(), data.size(), nullptr)); 176 const char expected_data[] = 177 "--boundary1\r\n" 178 "Content-Disposition: form-data; name=\"name\"\r\n" 179 "\r\n" 180 "John Doe\r\n" 181 "--boundary1\r\n" 182 "Content-Disposition: form-data; name=\"files\"\r\n" 183 "Content-Type: multipart/mixed; boundary=\"boundary2\"\r\n" 184 "\r\n" 185 "--boundary2\r\n" 186 "Content-Disposition: file; filename=\"sample.txt\"\r\n" 187 "Content-Type: text/plain\r\n" 188 "Content-Transfer-Encoding: binary\r\n" 189 "\r\n" 190 "text line1\ntext line2\n\r\n" 191 "--boundary2\r\n" 192 "Content-Disposition: file; filename=\"test.bin\"\r\n" 193 "Content-Type: application/octet-stream\r\n" 194 "Content-Transfer-Encoding: binary\r\n" 195 "\r\n" 196 "\x01\x02\x03\x04\x05\r\n" 197 "--boundary2--\r\n" 198 "--boundary1--"; 199 EXPECT_EQ(expected_data, (std::string{data.begin(), data.end()})); 200} 201} // namespace http 202} // namespace brillo 203