| Index: third_party/WebKit/Source/modules/fetch/MultipartParserTest.cpp
|
| diff --git a/third_party/WebKit/Source/modules/fetch/MultipartParserTest.cpp b/third_party/WebKit/Source/modules/fetch/MultipartParserTest.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..ddc890fe727af4e061d654ed2f0640698fbc14de
|
| --- /dev/null
|
| +++ b/third_party/WebKit/Source/modules/fetch/MultipartParserTest.cpp
|
| @@ -0,0 +1,315 @@
|
| +// Copyright 2016 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 "modules/fetch/MultipartParser.h"
|
| +
|
| +#include "platform/HTTPNames.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +#include <algorithm>
|
| +#include <string.h>
|
| +
|
| +namespace blink {
|
| +
|
| +namespace {
|
| +
|
| +String toString(const Vector<char>& data) {
|
| + if (data.isEmpty())
|
| + return String("");
|
| + return String(data.data(), data.size());
|
| +}
|
| +
|
| +class MockClient final : public GarbageCollectedFinalized<MockClient>,
|
| + public MultipartParser::Client {
|
| + USING_GARBAGE_COLLECTED_MIXIN(MockClient);
|
| +
|
| + public:
|
| + struct Part {
|
| + Part() = default;
|
| + explicit Part(const HTTPHeaderMap& headerFields)
|
| + : headerFields(headerFields), dataFullyReceived(false) {}
|
| + HTTPHeaderMap headerFields;
|
| + Vector<char> data;
|
| + bool dataFullyReceived;
|
| + };
|
| + void partHeaderFieldsInMultipartReceived(
|
| + const HTTPHeaderMap& headerFields) override {
|
| + m_parts.append(headerFields);
|
| + }
|
| + void partDataInMultipartReceived(const char* bytes, size_t size) override {
|
| + m_parts.back().data.append(bytes, size);
|
| + }
|
| + void partDataInMultipartFullyReceived() override {
|
| + m_parts.back().dataFullyReceived = true;
|
| + }
|
| + size_t numberOfParts() const { return m_parts.size(); }
|
| + const Part& part(size_t partIndex) const {
|
| + EXPECT_LT(partIndex, numberOfParts());
|
| + return partIndex < numberOfParts() ? m_parts[partIndex] : m_emptyPart;
|
| + }
|
| +
|
| + private:
|
| + Part m_emptyPart;
|
| + Vector<Part> m_parts;
|
| +};
|
| +
|
| +constexpr char kBytes[] =
|
| + "preamble"
|
| + "\r\n--boundary\r\n\r\n"
|
| + "\r\n--boundary\r\ncontent-type: application/xhtml+xml\r\n\r\n1"
|
| + "\r\n--boundary\t\r\ncontent-type: "
|
| + "text/html\r\n\r\n2\r\n--\r\n--bound--\r\n--\r\n2\r\n"
|
| + "\r\n--boundary \r\ncontent-type: text/plain; charset=iso-8859-1\r\n\r\n333"
|
| + "\r\n--boundary--\t \r\n"
|
| + "epilogue";
|
| +
|
| +TEST(MultipartParserTest, AppendDataInChunks) {
|
| + const size_t sizes[] = {1u, 2u, strlen(kBytes)};
|
| +
|
| + Vector<char> boundary;
|
| + boundary.append("boundary", 8u);
|
| + for (const size_t size : sizes) {
|
| + MockClient* client = new MockClient;
|
| + MultipartParser* parser = new MultipartParser(boundary, client);
|
| +
|
| + for (size_t i = 0u, length = strlen(kBytes); i < length; i += size)
|
| + EXPECT_TRUE(parser->appendData(kBytes + i, std::min(size, length - i)));
|
| + EXPECT_TRUE(parser->finish()) << " size=" << size;
|
| + EXPECT_EQ(4u, client->numberOfParts()) << " size=" << size;
|
| + EXPECT_EQ(0u, client->part(0).headerFields.size());
|
| + EXPECT_EQ(0u, client->part(0).data.size());
|
| + EXPECT_TRUE(client->part(0).dataFullyReceived);
|
| + EXPECT_EQ(1u, client->part(1).headerFields.size());
|
| + EXPECT_EQ("application/xhtml+xml",
|
| + client->part(1).headerFields.get(HTTPNames::Content_Type));
|
| + EXPECT_EQ("1", toString(client->part(1).data));
|
| + EXPECT_TRUE(client->part(1).dataFullyReceived);
|
| + EXPECT_EQ(1u, client->part(2).headerFields.size());
|
| + EXPECT_EQ("text/html",
|
| + client->part(2).headerFields.get(HTTPNames::Content_Type));
|
| + EXPECT_EQ("2\r\n--\r\n--bound--\r\n--\r\n2\r\n",
|
| + toString(client->part(2).data));
|
| + EXPECT_TRUE(client->part(2).dataFullyReceived);
|
| + EXPECT_EQ(1u, client->part(3).headerFields.size());
|
| + EXPECT_EQ("text/plain; charset=iso-8859-1",
|
| + client->part(3).headerFields.get(HTTPNames::Content_Type));
|
| + EXPECT_EQ("333", toString(client->part(3).data));
|
| + EXPECT_TRUE(client->part(3).dataFullyReceived);
|
| + }
|
| +}
|
| +
|
| +TEST(MultipartParserTest, ContentTransferEncoding) {
|
| + constexpr const char* kBase64 = "base64";
|
| +
|
| + constexpr char bytes1[] = "--boundary\r\ncontent-transfer-encoding: ";
|
| + constexpr char bytes2[] =
|
| + "\r\ncontent-type: application/xhtml+xml\r\n\r\nMQ==\r\n--boundary--\r\n";
|
| + constexpr const char* transferEncodings[] = {"binary", kBase64, "7bit",
|
| + "8bit"};
|
| +
|
| + Vector<char> boundary;
|
| + boundary.append("boundary", 8u);
|
| + for (const char* const transferEncoding : transferEncodings) {
|
| + MockClient* client = new MockClient;
|
| + MultipartParser* parser = new MultipartParser(boundary, client);
|
| +
|
| + EXPECT_TRUE(parser->appendData(bytes1, strlen(bytes1)));
|
| + EXPECT_TRUE(parser->appendData(transferEncoding, strlen(transferEncoding)));
|
| + if (transferEncoding == kBase64) {
|
| + EXPECT_FALSE(parser->appendData(
|
| + bytes2, strlen(bytes2))); // Unsupported transfer encoding.
|
| + EXPECT_EQ(0u, client->numberOfParts());
|
| + } else {
|
| + EXPECT_TRUE(parser->appendData(
|
| + bytes2, strlen(bytes2))); // No-op transfer encoding.
|
| + EXPECT_EQ(1u, client->numberOfParts());
|
| + EXPECT_EQ(2u, client->part(0).headerFields.size());
|
| + EXPECT_EQ(transferEncoding, client->part(0).headerFields.get(
|
| + HTTPNames::Content_Transfer_Encoding));
|
| + EXPECT_EQ("application/xhtml+xml",
|
| + client->part(0).headerFields.get(HTTPNames::Content_Type));
|
| + EXPECT_EQ("MQ==", toString(client->part(0).data));
|
| + EXPECT_TRUE(client->part(0).dataFullyReceived);
|
| + }
|
| + }
|
| +}
|
| +
|
| +TEST(MultipartParserTest, Epilogue) {
|
| + constexpr size_t ends[] = {
|
| + 0u, // Non-empty epilogue in the end.
|
| + 8u, // Empty epilogue in the end.
|
| + 9u, // Partial CRLF after close delimiter in the end.
|
| + 10u, // No CRLF after close delimiter in the end.
|
| + 12u, // No transport padding nor CRLF after close delimiter in the end.
|
| + 13u, // Partial close delimiter in the end.
|
| + 14u, // No close delimiter but a delimiter in the end.
|
| + 15u // Partial delimiter in the end.
|
| + };
|
| +
|
| + Vector<char> boundary;
|
| + boundary.append("boundary", 8u);
|
| + for (size_t end : ends) {
|
| + MockClient* client = new MockClient;
|
| + MultipartParser* parser = new MultipartParser(boundary, client);
|
| +
|
| + EXPECT_TRUE(parser->appendData(kBytes, strlen(kBytes) - end));
|
| + EXPECT_EQ(end <= 12u, parser->finish()) << " end=" << end;
|
| + EXPECT_EQ(4u, client->numberOfParts()) << " end=" << end;
|
| + EXPECT_EQ(0u, client->part(0).headerFields.size());
|
| + EXPECT_EQ(0u, client->part(0).data.size());
|
| + EXPECT_TRUE(client->part(0).dataFullyReceived);
|
| + EXPECT_EQ(1u, client->part(1).headerFields.size());
|
| + EXPECT_EQ("application/xhtml+xml",
|
| + client->part(1).headerFields.get(HTTPNames::Content_Type));
|
| + EXPECT_EQ("1", toString(client->part(1).data));
|
| + EXPECT_TRUE(client->part(1).dataFullyReceived);
|
| + EXPECT_EQ(1u, client->part(2).headerFields.size());
|
| + EXPECT_EQ("text/html",
|
| + client->part(2).headerFields.get(HTTPNames::Content_Type));
|
| + EXPECT_EQ("2\r\n--\r\n--bound--\r\n--\r\n2\r\n",
|
| + toString(client->part(2).data));
|
| + EXPECT_TRUE(client->part(2).dataFullyReceived);
|
| + EXPECT_EQ(1u, client->part(3).headerFields.size());
|
| + EXPECT_EQ("text/plain; charset=iso-8859-1",
|
| + client->part(3).headerFields.get(HTTPNames::Content_Type));
|
| + switch (end) {
|
| + case 15u:
|
| + EXPECT_EQ("333\r\n--boundar", toString(client->part(3).data));
|
| + EXPECT_FALSE(client->part(3).dataFullyReceived);
|
| + break;
|
| + default:
|
| + EXPECT_EQ("333", toString(client->part(3).data));
|
| + EXPECT_TRUE(client->part(3).dataFullyReceived);
|
| + break;
|
| + }
|
| + }
|
| +}
|
| +
|
| +TEST(MultipartParserTest, NoEndBoundary) {
|
| + constexpr char bytes[] =
|
| + "--boundary\r\ncontent-type: application/xhtml+xml\r\n\r\n1";
|
| +
|
| + Vector<char> boundary;
|
| + boundary.append("boundary", 8u);
|
| + MockClient* client = new MockClient;
|
| + MultipartParser* parser = new MultipartParser(boundary, client);
|
| +
|
| + EXPECT_TRUE(parser->appendData(bytes, strlen(bytes)));
|
| + EXPECT_FALSE(parser->finish()); // No close delimiter.
|
| + EXPECT_EQ(1u, client->numberOfParts());
|
| + EXPECT_EQ(1u, client->part(0).headerFields.size());
|
| + EXPECT_EQ("application/xhtml+xml",
|
| + client->part(0).headerFields.get(HTTPNames::Content_Type));
|
| + EXPECT_EQ("1", toString(client->part(0).data));
|
| + EXPECT_FALSE(client->part(0).dataFullyReceived);
|
| +}
|
| +
|
| +TEST(MultipartParserTest, NoStartBoundary) {
|
| + constexpr char bytes[] =
|
| + "content-type: application/xhtml+xml\r\n\r\n1\r\n--boundary--\r\n";
|
| +
|
| + Vector<char> boundary;
|
| + boundary.append("boundary", 8u);
|
| + MockClient* client = new MockClient;
|
| + MultipartParser* parser = new MultipartParser(boundary, client);
|
| +
|
| + EXPECT_FALSE(parser->appendData(
|
| + bytes, strlen(bytes))); // Close delimiter before delimiter.
|
| + EXPECT_EQ(0u, client->numberOfParts());
|
| +}
|
| +
|
| +TEST(MultipartParserTest, NoStartNorEndBoundary) {
|
| + constexpr char bytes[] = "content-type: application/xhtml+xml\r\n\r\n1";
|
| +
|
| + Vector<char> boundary;
|
| + boundary.append("boundary", 8u);
|
| + MockClient* client = new MockClient;
|
| + MultipartParser* parser = new MultipartParser(boundary, client);
|
| +
|
| + EXPECT_TRUE(parser->appendData(bytes, strlen(bytes))); // Valid preamble.
|
| + EXPECT_FALSE(parser->finish()); // No parts.
|
| + EXPECT_EQ(0u, client->numberOfParts());
|
| +}
|
| +
|
| +constexpr size_t kStarts[] = {
|
| + 0u, // Non-empty preamble in the beginning.
|
| + 8u, // Empty preamble in the beginning.
|
| + 9u, // Truncated delimiter in the beginning.
|
| + 10u, // No preamble in the beginning.
|
| + 11u // Truncated dash boundary in the beginning.
|
| +};
|
| +
|
| +TEST(MultipartParserTest, Preamble) {
|
| + Vector<char> boundary;
|
| + boundary.append("boundary", 8u);
|
| + for (const size_t start : kStarts) {
|
| + MockClient* client = new MockClient;
|
| + MultipartParser* parser = new MultipartParser(boundary, client);
|
| +
|
| + EXPECT_TRUE(parser->appendData(kBytes + start, strlen(kBytes + start)));
|
| + EXPECT_TRUE(parser->finish());
|
| + switch (start) {
|
| + case 9u:
|
| + case 11u:
|
| + EXPECT_EQ(3u, client->numberOfParts()) << " start=" << start;
|
| + EXPECT_EQ(1u, client->part(0).headerFields.size());
|
| + EXPECT_EQ("application/xhtml+xml",
|
| + client->part(0).headerFields.get(HTTPNames::Content_Type));
|
| + EXPECT_EQ("1", toString(client->part(0).data));
|
| + EXPECT_TRUE(client->part(0).dataFullyReceived);
|
| + EXPECT_EQ(1u, client->part(1).headerFields.size());
|
| + EXPECT_EQ("text/html",
|
| + client->part(1).headerFields.get(HTTPNames::Content_Type));
|
| + EXPECT_EQ("2\r\n--\r\n--bound--\r\n--\r\n2\r\n",
|
| + toString(client->part(1).data));
|
| + EXPECT_TRUE(client->part(1).dataFullyReceived);
|
| + EXPECT_EQ(1u, client->part(2).headerFields.size());
|
| + EXPECT_EQ("text/plain; charset=iso-8859-1",
|
| + client->part(2).headerFields.get(HTTPNames::Content_Type));
|
| + EXPECT_EQ("333", toString(client->part(2).data));
|
| + EXPECT_TRUE(client->part(2).dataFullyReceived);
|
| + break;
|
| + default:
|
| + EXPECT_EQ(4u, client->numberOfParts()) << " start=" << start;
|
| + EXPECT_EQ(0u, client->part(0).headerFields.size());
|
| + EXPECT_EQ(0u, client->part(0).data.size());
|
| + EXPECT_TRUE(client->part(0).dataFullyReceived);
|
| + EXPECT_EQ(1u, client->part(1).headerFields.size());
|
| + EXPECT_EQ("application/xhtml+xml",
|
| + client->part(1).headerFields.get(HTTPNames::Content_Type));
|
| + EXPECT_EQ("1", toString(client->part(1).data));
|
| + EXPECT_TRUE(client->part(1).dataFullyReceived);
|
| + EXPECT_EQ(1u, client->part(2).headerFields.size());
|
| + EXPECT_EQ("text/html",
|
| + client->part(2).headerFields.get(HTTPNames::Content_Type));
|
| + EXPECT_EQ("2\r\n--\r\n--bound--\r\n--\r\n2\r\n",
|
| + toString(client->part(2).data));
|
| + EXPECT_TRUE(client->part(2).dataFullyReceived);
|
| + EXPECT_EQ(1u, client->part(3).headerFields.size());
|
| + EXPECT_EQ("text/plain; charset=iso-8859-1",
|
| + client->part(3).headerFields.get(HTTPNames::Content_Type));
|
| + EXPECT_EQ("333", toString(client->part(3).data));
|
| + EXPECT_TRUE(client->part(3).dataFullyReceived);
|
| + break;
|
| + }
|
| + }
|
| +}
|
| +
|
| +TEST(MultipartParserTest, PreambleWithMalformedBoundary) {
|
| + Vector<char> boundary;
|
| + boundary.append("--boundary", 10u);
|
| + for (const size_t start : kStarts) {
|
| + MockClient* client = new MockClient;
|
| + MultipartParser* parser = new MultipartParser(boundary, client);
|
| +
|
| + EXPECT_TRUE(parser->appendData(kBytes + start,
|
| + strlen(kBytes + start))); // Valid preamble.
|
| + EXPECT_FALSE(parser->finish()); // No parts.
|
| + EXPECT_EQ(0u, client->numberOfParts());
|
| + }
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +} // namespace blink
|
|
|