// Copyright 2014 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <brillo/http/http_form_data.h> #include <set> #include <base/files/file_util.h> #include <base/files/scoped_temp_dir.h> #include <brillo/mime_utils.h> #include <brillo/streams/file_stream.h> #include <brillo/streams/input_stream_set.h> #include <gtest/gtest.h> namespace brillo { namespace http { namespace { std::string GetFormFieldData(FormField* field) { std::vector<StreamPtr> streams; CHECK(field->ExtractDataStreams(&streams)); StreamPtr stream = InputStreamSet::Create(std::move(streams), nullptr); std::vector<uint8_t> data(stream->GetSize()); EXPECT_TRUE(stream->ReadAllBlocking(data.data(), data.size(), nullptr)); return std::string{data.begin(), data.end()}; } } // anonymous namespace TEST(HttpFormData, TextFormField) { TextFormField form_field{"field1", "abcdefg", mime::text::kPlain, "7bit"}; const char expected_header[] = "Content-Disposition: form-data; name=\"field1\"\r\n" "Content-Type: text/plain\r\n" "Content-Transfer-Encoding: 7bit\r\n" "\r\n"; EXPECT_EQ(expected_header, form_field.GetContentHeader()); EXPECT_EQ("abcdefg", GetFormFieldData(&form_field)); } TEST(HttpFormData, FileFormField) { base::ScopedTempDir dir; ASSERT_TRUE(dir.CreateUniqueTempDir()); std::string file_content{"text line1\ntext line2\n"}; base::FilePath file_name = dir.path().Append("sample.txt"); ASSERT_EQ(file_content.size(), static_cast<size_t>(base::WriteFile( file_name, file_content.data(), file_content.size()))); StreamPtr stream = FileStream::Open(file_name, Stream::AccessMode::READ, FileStream::Disposition::OPEN_EXISTING, nullptr); ASSERT_NE(nullptr, stream); FileFormField form_field{"test_file", std::move(stream), "sample.txt", content_disposition::kFormData, mime::text::kPlain, ""}; const char expected_header[] = "Content-Disposition: form-data; name=\"test_file\";" " filename=\"sample.txt\"\r\n" "Content-Type: text/plain\r\n" "\r\n"; EXPECT_EQ(expected_header, form_field.GetContentHeader()); EXPECT_EQ(file_content, GetFormFieldData(&form_field)); } TEST(HttpFormData, MultiPartFormField) { base::ScopedTempDir dir; ASSERT_TRUE(dir.CreateUniqueTempDir()); std::string file1{"text line1\ntext line2\n"}; base::FilePath filename1 = dir.path().Append("sample.txt"); ASSERT_EQ(file1.size(), static_cast<size_t>( base::WriteFile(filename1, file1.data(), file1.size()))); std::string file2{"\x01\x02\x03\x04\x05"}; base::FilePath filename2 = dir.path().Append("test.bin"); ASSERT_EQ(file2.size(), static_cast<size_t>( base::WriteFile(filename2, file2.data(), file2.size()))); MultiPartFormField form_field{"foo", mime::multipart::kFormData, "Delimiter"}; form_field.AddTextField("name", "John Doe"); EXPECT_TRUE(form_field.AddFileField("file1", filename1, content_disposition::kFormData, mime::text::kPlain, nullptr)); EXPECT_TRUE(form_field.AddFileField("file2", filename2, content_disposition::kFormData, mime::application::kOctet_stream, nullptr)); const char expected_header[] = "Content-Disposition: form-data; name=\"foo\"\r\n" "Content-Type: multipart/form-data; boundary=\"Delimiter\"\r\n" "\r\n"; EXPECT_EQ(expected_header, form_field.GetContentHeader()); const char expected_data[] = "--Delimiter\r\n" "Content-Disposition: form-data; name=\"name\"\r\n" "\r\n" "John Doe\r\n" "--Delimiter\r\n" "Content-Disposition: form-data; name=\"file1\";" " filename=\"sample.txt\"\r\n" "Content-Type: text/plain\r\n" "Content-Transfer-Encoding: binary\r\n" "\r\n" "text line1\ntext line2\n\r\n" "--Delimiter\r\n" "Content-Disposition: form-data; name=\"file2\";" " filename=\"test.bin\"\r\n" "Content-Type: application/octet-stream\r\n" "Content-Transfer-Encoding: binary\r\n" "\r\n" "\x01\x02\x03\x04\x05\r\n" "--Delimiter--"; EXPECT_EQ(expected_data, GetFormFieldData(&form_field)); } TEST(HttpFormData, MultiPartBoundary) { const int count = 10; std::set<std::string> boundaries; for (int i = 0; i < count; i++) { MultiPartFormField field{""}; std::string boundary = field.GetBoundary(); boundaries.insert(boundary); // Our generated boundary must be 16 character long and contain lowercase // hexadecimal digits only. EXPECT_EQ(16u, boundary.size()); EXPECT_EQ(std::string::npos, boundary.find_first_not_of("0123456789abcdef")); } // Now make sure the boundary strings were generated at random, so we should // get |count| unique boundary strings. However since the strings are random, // there is a very slim change of generating the same string twice, so // expect at least 90% of unique strings. 90% is picked arbitrarily here. int expected_min_unique = count * 9 / 10; EXPECT_GE(boundaries.size(), expected_min_unique); } TEST(HttpFormData, FormData) { base::ScopedTempDir dir; ASSERT_TRUE(dir.CreateUniqueTempDir()); std::string file1{"text line1\ntext line2\n"}; base::FilePath filename1 = dir.path().Append("sample.txt"); ASSERT_EQ(file1.size(), static_cast<size_t>( base::WriteFile(filename1, file1.data(), file1.size()))); std::string file2{"\x01\x02\x03\x04\x05"}; base::FilePath filename2 = dir.path().Append("test.bin"); ASSERT_EQ(file2.size(), static_cast<size_t>( base::WriteFile(filename2, file2.data(), file2.size()))); FormData form_data{"boundary1"}; form_data.AddTextField("name", "John Doe"); std::unique_ptr<MultiPartFormField> files{ new MultiPartFormField{"files", "", "boundary2"}}; EXPECT_TRUE(files->AddFileField( "", filename1, content_disposition::kFile, mime::text::kPlain, nullptr)); EXPECT_TRUE(files->AddFileField("", filename2, content_disposition::kFile, mime::application::kOctet_stream, nullptr)); form_data.AddCustomField(std::move(files)); EXPECT_EQ("multipart/form-data; boundary=\"boundary1\"", form_data.GetContentType()); StreamPtr stream = form_data.ExtractDataStream(); std::vector<uint8_t> data(stream->GetSize()); EXPECT_TRUE(stream->ReadAllBlocking(data.data(), data.size(), nullptr)); const char expected_data[] = "--boundary1\r\n" "Content-Disposition: form-data; name=\"name\"\r\n" "\r\n" "John Doe\r\n" "--boundary1\r\n" "Content-Disposition: form-data; name=\"files\"\r\n" "Content-Type: multipart/mixed; boundary=\"boundary2\"\r\n" "\r\n" "--boundary2\r\n" "Content-Disposition: file; filename=\"sample.txt\"\r\n" "Content-Type: text/plain\r\n" "Content-Transfer-Encoding: binary\r\n" "\r\n" "text line1\ntext line2\n\r\n" "--boundary2\r\n" "Content-Disposition: file; filename=\"test.bin\"\r\n" "Content-Type: application/octet-stream\r\n" "Content-Transfer-Encoding: binary\r\n" "\r\n" "\x01\x02\x03\x04\x05\r\n" "--boundary2--\r\n" "--boundary1--"; EXPECT_EQ(expected_data, (std::string{data.begin(), data.end()})); } } // namespace http } // namespace brillo