blob: e37181cc1aa341824b38d3024777a0666684ecfe [file] [log] [blame]
// Copyright (c) 2012 The Chromium 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 <stddef.h>
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/string_piece.h"
#include "extensions/browser/api/web_request/form_data_parser.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
namespace {
// Attempts to create a parser corresponding to the |content_type_header|.
// On success, returns the parser.
std::unique_ptr<FormDataParser> InitParser(
const std::string& content_type_header) {
std::unique_ptr<FormDataParser> parser(
FormDataParser::CreateFromContentTypeHeader(&content_type_header));
if (parser.get() == NULL)
return std::unique_ptr<FormDataParser>();
return parser;
}
// Attempts to run the parser corresponding to the |content_type_header|
// on the source represented by the concatenation of blocks from |bytes|.
// On success, returns true and the parsed |output|, else false.
// Parsed |output| has names on even positions (0, 2, ...), values on odd ones.
bool RunParser(const std::string& content_type_header,
const std::vector<const base::StringPiece*>& bytes,
std::vector<std::string>* output) {
DCHECK(output);
output->clear();
std::unique_ptr<FormDataParser> parser(InitParser(content_type_header));
if (!parser.get())
return false;
FormDataParser::Result result;
for (size_t block = 0; block < bytes.size(); ++block) {
if (!parser->SetSource(*(bytes[block])))
return false;
while (parser->GetNextNameValue(&result)) {
output->push_back(result.name());
base::Value value = result.take_value();
if (value.is_string()) {
output->push_back(value.GetString());
} else {
const auto& blob = value.GetBlob();
output->push_back(std::string(blob.begin(), blob.end()));
}
}
}
return parser->AllDataReadOK();
}
// Attempts to run the parser corresponding to the |content_type_header|
// on the source represented by the concatenation of blocks from |bytes|.
// Checks that the parser fails parsing.
bool CheckParserFails(const std::string& content_type_header,
const std::vector<const base::StringPiece*>& bytes) {
std::vector<std::string> output;
std::unique_ptr<FormDataParser> parser(InitParser(content_type_header));
if (!parser.get())
return false;
FormDataParser::Result result;
for (size_t block = 0; block < bytes.size(); ++block) {
if (!parser->SetSource(*(bytes[block])))
break;
while (parser->GetNextNameValue(&result)) {
output.push_back(result.name());
base::Value value = result.take_value();
if (value.is_string()) {
output.push_back(value.GetString());
} else {
const auto& blob = value.GetBlob();
output.push_back(std::string(blob.begin(), blob.end()));
}
}
}
return !parser->AllDataReadOK();
}
} // namespace
TEST(WebRequestFormDataParserTest, Parsing) {
// We verify that POST data parsers cope with various formats of POST data.
// Construct the test data.
const std::string kBoundary = "THIS_IS_A_BOUNDARY";
const std::string kBlockStr1 =
std::string("--") + kBoundary +
"\r\n"
"Content-Disposition: form-data; name=\"text\"\r\n"
"\r\n"
"test\rtext\nwith non-CRLF line breaks\r\n"
"--" +
kBoundary +
"\r\n"
"Content-Disposition: form-data; name=\"file\"; filename=\"test\"\r\n"
"Content-Type: application/octet-stream\r\n"
"\r\n";
const std::string kBlockStr2 =
std::string("\r\n--") + kBoundary +
"\r\n"
"Content-Disposition: form-data; name=\"password\"\r\n"
"\r\n"
"test password\r\n"
"--" +
kBoundary +
"\r\n"
"Content-Disposition: form-data; name=\"radio\"\r\n"
"\r\n"
"Yes\r\n"
"--" +
kBoundary +
"\r\n"
"Content-Disposition: form-data; name=\"check\"\r\n"
"\r\n"
"option A\r\n"
"--" +
kBoundary +
"\r\n"
"Content-Disposition: form-data; name=\"check\"\r\n"
"\r\n"
"option B\r\n"
"--" +
kBoundary +
"\r\n"
"Content-Disposition: form-data; name=\"txtarea\"\r\n"
"\r\n"
"Some text.\r\n"
"Other.\r\n"
"\r\n"
"--" +
kBoundary +
"\r\n"
"Content-Disposition: form-data; name=\"select\"\r\n"
"\r\n"
"one\r\n"
"--" +
kBoundary + "\r\n";
const std::string kBlockStr3 =
"Content-Disposition: form-data; name=\"binary\"\r\n"
"Content-Type: application/octet-stream\r\n"
"\r\n"
"\u0420\u043e\u0434\u0436\u0435\u0440 "
"\u0416\u0435\u043b\u044f\u0437\u043d\u044b"
"\r\n"
"--" +
kBoundary + "--";
// POST data input.
const std::string kBigBlock = kBlockStr1 + kBlockStr2 + kBlockStr3;
const std::string kUrlEncodedBlock =
"text=test%0Dtext%0Awith+non-CRLF+line+breaks"
"&file=test&password=test+password&radio=Yes&check=option+A"
"&check=option+B&txtarea=Some+text.%0D%0AOther.%0D%0A&select=one"
"&binary=%D0%A0%D0%BE%D0%B4%D0%B6%D0%B5%D1%80%20%D0%96%D0%B5%D0%BB%D1%8F%"
"D0%B7%D0%BD%D1%8B";
const base::StringPiece kMultipartBytes(kBigBlock);
const base::StringPiece kMultipartBytesSplit1(kBlockStr1);
const base::StringPiece kMultipartBytesSplit2(kBlockStr2);
const base::StringPiece kMultipartBytesSplit3(kBlockStr3);
const base::StringPiece kUrlEncodedBytes(kUrlEncodedBlock);
const std::string kPlainBlock = "abc";
const base::StringPiece kTextPlainBytes(kPlainBlock);
// Headers.
const std::string kUrlEncoded = "application/x-www-form-urlencoded";
const std::string kTextPlain = "text/plain";
const std::string kMultipart =
std::string("multipart/form-data; boundary=") + kBoundary;
// Expected output.
const char* kPairs[] = {"text",
"test\rtext\nwith non-CRLF line breaks",
"file",
"test",
"password",
"test password",
"radio",
"Yes",
"check",
"option A",
"check",
"option B",
"txtarea",
"Some text.\r\nOther.\r\n",
"select",
"one",
"binary",
"\u0420\u043e\u0434\u0436\u0435\u0440 "
"\u0416\u0435\u043b\u044f\u0437\u043d\u044b"};
const std::vector<std::string> kExpected(kPairs, kPairs + base::size(kPairs));
std::vector<const base::StringPiece*> input;
std::vector<std::string> output;
// First test: multipart POST data in one lump.
input.push_back(&kMultipartBytes);
EXPECT_TRUE(RunParser(kMultipart, input, &output));
EXPECT_EQ(kExpected, output);
// Second test: multipart POST data in several lumps.
input.clear();
input.push_back(&kMultipartBytesSplit1);
input.push_back(&kMultipartBytesSplit2);
input.push_back(&kMultipartBytesSplit3);
EXPECT_TRUE(RunParser(kMultipart, input, &output));
EXPECT_EQ(kExpected, output);
// Third test: URL-encoded POST data.
input.clear();
input.push_back(&kUrlEncodedBytes);
EXPECT_TRUE(RunParser(kUrlEncoded, input, &output));
EXPECT_EQ(kExpected, output);
// Fourth test: text/plain POST data in one lump.
input.clear();
input.push_back(&kTextPlainBytes);
// This should fail, text/plain is ambiguous and thus unparseable.
EXPECT_FALSE(RunParser(kTextPlain, input, &output));
}
TEST(WebRequestFormDataParserTest, MalformedPayload) {
// We verify that POST data parsers reject malformed data.
// Construct the test data.
const std::string kBoundary = "THIS_IS_A_BOUNDARY";
const std::string kBlockStr =
std::string("--") + kBoundary +
"\r\n"
"Content-Disposition: form-data; name=\"text\"\r\n"
"\r\n"
"test\rtext\nwith non-CRLF line breaks\r\n"
"-" +
kBoundary +
"\r\n" /* Missing '-'. */
"Content-Disposition: form-data; name=\"file\"; filename=\"test\"\r\n"
"Content-Type: application/octet-stream\r\n"
/* Two CRLF missing. */
"--" +
kBoundary +
"\r\n"
"Content-Disposition: form-data; name=\"select\"\r\n"
"\r\n"
"one\r\n"
"--" +
kBoundary + "-" /* Missing '-' at the end. */;
// POST data input.
// The following block is corrupted -- contains a "==" substring.
const std::string kUrlEncodedBlock =
"text=test%0Dtext%0Awith+non-CRLF+line+breaks"
"&file==test&password=test+password&radio=Yes&check=option+A"
"&check=option+B&txtarea=Some+text.%0D%0AOther.%0D%0A&select=one";
const base::StringPiece kMultipartBytes(kBlockStr);
const base::StringPiece kMultipartBytesEmpty("");
const base::StringPiece kUrlEncodedBytes(kUrlEncodedBlock);
const base::StringPiece kUrlEncodedBytesEmpty("");
// Headers.
const std::string kUrlEncoded = "application/x-www-form-urlencoded";
const std::string kMultipart =
std::string("multipart/form-data; boundary=") + kBoundary;
std::vector<const base::StringPiece*> input;
// First test: malformed multipart POST data.
input.push_back(&kMultipartBytes);
EXPECT_TRUE(CheckParserFails(kMultipart, input));
// Second test: empty multipart POST data.
input.clear();
input.push_back(&kMultipartBytesEmpty);
EXPECT_TRUE(CheckParserFails(kMultipart, input));
// Third test: malformed URL-encoded POST data.
input.clear();
input.push_back(&kUrlEncodedBytes);
EXPECT_TRUE(CheckParserFails(kUrlEncoded, input));
// Fourth test: empty URL-encoded POST data. Note that an empty string is a
// valid url-encoded value, so this should parse correctly.
std::vector<std::string> output;
input.clear();
input.push_back(&kUrlEncodedBytesEmpty);
EXPECT_TRUE(RunParser(kUrlEncoded, input, &output));
EXPECT_EQ(0u, output.size());
}
} // namespace extensions