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
16 namespace brillo {
17 namespace http {
18 namespace {
GetFormFieldData(FormField * field)19 std::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
TEST(HttpFormData,TextFormField)30 TEST(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
TEST(HttpFormData,FileFormField)41 TEST(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
TEST(HttpFormData,MultiPartFormField)69 TEST(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
TEST(HttpFormData,MultiPartBoundary)123 TEST(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
TEST(HttpFormData,FormData)144 TEST(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