• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.GetPath().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.GetPath().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.GetPath().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.GetPath().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.GetPath().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