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