xref: /aosp_15_r20/external/libbrillo/brillo/http/http_form_data_test.cc (revision 1a96fba65179ea7d3f56207137718607415c5953)
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