Home | History | Annotate | Download | only in http
      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 {
     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 
     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 
     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 
     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 
    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 
    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