blob: 85c5ceccd571c4bf69c9de5e68bcdcb946154128 [file] [log] [blame]
// 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 "quic/core/http/spdy_utils.h"
#include <memory>
#include <string>
#include "absl/base/macros.h"
#include "absl/strings/string_view.h"
#include "quic/core/quic_versions.h"
#include "quic/platform/api/quic_test.h"
using spdy::SpdyHeaderBlock;
using testing::Pair;
using testing::UnorderedElementsAre;
namespace quic {
namespace test {
namespace {
const bool kExpectFinalByteOffset = true;
const bool kDoNotExpectFinalByteOffset = false;
static std::unique_ptr<QuicHeaderList> FromList(
const QuicHeaderList::ListType& src) {
std::unique_ptr<QuicHeaderList> headers(new QuicHeaderList);
headers->OnHeaderBlockStart();
for (const auto& p : src) {
headers->OnHeader(p.first, p.second);
}
headers->OnHeaderBlockEnd(0, 0);
return headers;
}
static void ValidateDatagramFlowId(
const std::string& header_value,
absl::optional<QuicDatagramStreamId> expected_flow_id) {
SpdyHeaderBlock headers;
headers["datagram-flow-id"] = header_value;
ASSERT_EQ(SpdyUtils::ParseDatagramFlowIdHeader(headers), expected_flow_id);
}
} // anonymous namespace
using CopyAndValidateHeaders = QuicTest;
TEST_F(CopyAndValidateHeaders, NormalUsage) {
auto headers = FromList({// All cookie crumbs are joined.
{"cookie", " part 1"},
{"cookie", "part 2 "},
{"cookie", "part3"},
// Already-delimited headers are passed through.
{"passed-through", std::string("foo\0baz", 7)},
// Other headers are joined on \0.
{"joined", "value 1"},
{"joined", "value 2"},
// Empty headers remain empty.
{"empty", ""},
// Joined empty headers work as expected.
{"empty-joined", ""},
{"empty-joined", "foo"},
{"empty-joined", ""},
{"empty-joined", ""},
// Non-continguous cookie crumb.
{"cookie", " fin!"}});
int64_t content_length = -1;
SpdyHeaderBlock block;
ASSERT_TRUE(
SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
EXPECT_THAT(block,
UnorderedElementsAre(
Pair("cookie", " part 1; part 2 ; part3; fin!"),
Pair("passed-through", absl::string_view("foo\0baz", 7)),
Pair("joined", absl::string_view("value 1\0value 2", 15)),
Pair("empty", ""),
Pair("empty-joined", absl::string_view("\0foo\0\0", 6))));
EXPECT_EQ(-1, content_length);
}
TEST_F(CopyAndValidateHeaders, EmptyName) {
auto headers = FromList({{"foo", "foovalue"}, {"", "barvalue"}, {"baz", ""}});
int64_t content_length = -1;
SpdyHeaderBlock block;
ASSERT_FALSE(
SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
}
TEST_F(CopyAndValidateHeaders, UpperCaseName) {
auto headers =
FromList({{"foo", "foovalue"}, {"bar", "barvalue"}, {"bAz", ""}});
int64_t content_length = -1;
SpdyHeaderBlock block;
ASSERT_FALSE(
SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
}
TEST_F(CopyAndValidateHeaders, MultipleContentLengths) {
auto headers = FromList({{"content-length", "9"},
{"foo", "foovalue"},
{"content-length", "9"},
{"bar", "barvalue"},
{"baz", ""}});
int64_t content_length = -1;
SpdyHeaderBlock block;
ASSERT_TRUE(
SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
EXPECT_THAT(block, UnorderedElementsAre(
Pair("foo", "foovalue"), Pair("bar", "barvalue"),
Pair("content-length", absl::string_view("9\09", 3)),
Pair("baz", "")));
EXPECT_EQ(9, content_length);
}
TEST_F(CopyAndValidateHeaders, InconsistentContentLengths) {
auto headers = FromList({{"content-length", "9"},
{"foo", "foovalue"},
{"content-length", "8"},
{"bar", "barvalue"},
{"baz", ""}});
int64_t content_length = -1;
SpdyHeaderBlock block;
ASSERT_FALSE(
SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
}
TEST_F(CopyAndValidateHeaders, LargeContentLength) {
auto headers = FromList({{"content-length", "9000000000"},
{"foo", "foovalue"},
{"bar", "barvalue"},
{"baz", ""}});
int64_t content_length = -1;
SpdyHeaderBlock block;
ASSERT_TRUE(
SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
EXPECT_THAT(block,
UnorderedElementsAre(
Pair("foo", "foovalue"), Pair("bar", "barvalue"),
Pair("content-length", absl::string_view("9000000000")),
Pair("baz", "")));
EXPECT_EQ(9000000000, content_length);
}
TEST_F(CopyAndValidateHeaders, NonDigitContentLength) {
// Section 3.3.2 of RFC 7230 defines content-length as being only digits.
// Number parsers might accept symbols like a leading plus; test that this
// fails to parse.
auto headers = FromList({{"content-length", "+123"},
{"foo", "foovalue"},
{"bar", "barvalue"},
{"baz", ""}});
int64_t content_length = -1;
SpdyHeaderBlock block;
EXPECT_FALSE(
SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
}
TEST_F(CopyAndValidateHeaders, MultipleValues) {
auto headers = FromList({{"foo", "foovalue"},
{"bar", "barvalue"},
{"baz", ""},
{"foo", "boo"},
{"baz", "buzz"}});
int64_t content_length = -1;
SpdyHeaderBlock block;
ASSERT_TRUE(
SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
EXPECT_THAT(block, UnorderedElementsAre(
Pair("foo", absl::string_view("foovalue\0boo", 12)),
Pair("bar", "barvalue"),
Pair("baz", absl::string_view("\0buzz", 5))));
EXPECT_EQ(-1, content_length);
}
TEST_F(CopyAndValidateHeaders, MoreThanTwoValues) {
auto headers = FromList({{"set-cookie", "value1"},
{"set-cookie", "value2"},
{"set-cookie", "value3"}});
int64_t content_length = -1;
SpdyHeaderBlock block;
ASSERT_TRUE(
SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
EXPECT_THAT(block, UnorderedElementsAre(Pair(
"set-cookie",
absl::string_view("value1\0value2\0value3", 20))));
EXPECT_EQ(-1, content_length);
}
TEST_F(CopyAndValidateHeaders, Cookie) {
auto headers = FromList({{"foo", "foovalue"},
{"bar", "barvalue"},
{"cookie", "value1"},
{"baz", ""}});
int64_t content_length = -1;
SpdyHeaderBlock block;
ASSERT_TRUE(
SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
EXPECT_THAT(block, UnorderedElementsAre(
Pair("foo", "foovalue"), Pair("bar", "barvalue"),
Pair("cookie", "value1"), Pair("baz", "")));
EXPECT_EQ(-1, content_length);
}
TEST_F(CopyAndValidateHeaders, MultipleCookies) {
auto headers = FromList({{"foo", "foovalue"},
{"bar", "barvalue"},
{"cookie", "value1"},
{"baz", ""},
{"cookie", "value2"}});
int64_t content_length = -1;
SpdyHeaderBlock block;
ASSERT_TRUE(
SpdyUtils::CopyAndValidateHeaders(*headers, &content_length, &block));
EXPECT_THAT(block, UnorderedElementsAre(
Pair("foo", "foovalue"), Pair("bar", "barvalue"),
Pair("cookie", "value1; value2"), Pair("baz", "")));
EXPECT_EQ(-1, content_length);
}
using CopyAndValidateTrailers = QuicTest;
TEST_F(CopyAndValidateTrailers, SimplestValidList) {
// Verify that the simplest trailers are valid: just a final byte offset that
// gets parsed successfully.
auto trailers = FromList({{kFinalOffsetHeaderKey, "1234"}});
size_t final_byte_offset = 0;
SpdyHeaderBlock block;
EXPECT_TRUE(SpdyUtils::CopyAndValidateTrailers(
*trailers, kExpectFinalByteOffset, &final_byte_offset, &block));
EXPECT_EQ(1234u, final_byte_offset);
}
TEST_F(CopyAndValidateTrailers, EmptyTrailerListWithFinalByteOffsetExpected) {
// An empty trailer list will fail as expected key kFinalOffsetHeaderKey is
// not present.
QuicHeaderList trailers;
size_t final_byte_offset = 0;
SpdyHeaderBlock block;
EXPECT_FALSE(SpdyUtils::CopyAndValidateTrailers(
trailers, kExpectFinalByteOffset, &final_byte_offset, &block));
}
TEST_F(CopyAndValidateTrailers,
EmptyTrailerListWithFinalByteOffsetNotExpected) {
// An empty trailer list will pass successfully if kFinalOffsetHeaderKey is
// not expected.
QuicHeaderList trailers;
size_t final_byte_offset = 0;
SpdyHeaderBlock block;
EXPECT_TRUE(SpdyUtils::CopyAndValidateTrailers(
trailers, kDoNotExpectFinalByteOffset, &final_byte_offset, &block));
EXPECT_TRUE(block.empty());
}
TEST_F(CopyAndValidateTrailers, FinalByteOffsetExpectedButNotPresent) {
// Validation fails if expected kFinalOffsetHeaderKey is not present, even if
// the rest of the header block is valid.
auto trailers = FromList({{"key", "value"}});
size_t final_byte_offset = 0;
SpdyHeaderBlock block;
EXPECT_FALSE(SpdyUtils::CopyAndValidateTrailers(
*trailers, kExpectFinalByteOffset, &final_byte_offset, &block));
}
TEST_F(CopyAndValidateTrailers, FinalByteOffsetNotExpectedButPresent) {
// Validation fails if kFinalOffsetHeaderKey is present but should not be,
// even if the rest of the header block is valid.
auto trailers = FromList({{"key", "value"}, {kFinalOffsetHeaderKey, "1234"}});
size_t final_byte_offset = 0;
SpdyHeaderBlock block;
EXPECT_FALSE(SpdyUtils::CopyAndValidateTrailers(
*trailers, kDoNotExpectFinalByteOffset, &final_byte_offset, &block));
}
TEST_F(CopyAndValidateTrailers, FinalByteOffsetNotExpectedAndNotPresent) {
// Validation succeeds if kFinalOffsetHeaderKey is not expected and not
// present.
auto trailers = FromList({{"key", "value"}});
size_t final_byte_offset = 0;
SpdyHeaderBlock block;
EXPECT_TRUE(SpdyUtils::CopyAndValidateTrailers(
*trailers, kDoNotExpectFinalByteOffset, &final_byte_offset, &block));
EXPECT_THAT(block, UnorderedElementsAre(Pair("key", "value")));
}
TEST_F(CopyAndValidateTrailers, EmptyName) {
// Trailer validation will fail with an empty header key, in an otherwise
// valid block of trailers.
auto trailers = FromList({{"", "value"}, {kFinalOffsetHeaderKey, "1234"}});
size_t final_byte_offset = 0;
SpdyHeaderBlock block;
EXPECT_FALSE(SpdyUtils::CopyAndValidateTrailers(
*trailers, kExpectFinalByteOffset, &final_byte_offset, &block));
}
TEST_F(CopyAndValidateTrailers, PseudoHeaderInTrailers) {
// Pseudo headers are illegal in trailers.
auto trailers =
FromList({{":pseudo_key", "value"}, {kFinalOffsetHeaderKey, "1234"}});
size_t final_byte_offset = 0;
SpdyHeaderBlock block;
EXPECT_FALSE(SpdyUtils::CopyAndValidateTrailers(
*trailers, kExpectFinalByteOffset, &final_byte_offset, &block));
}
TEST_F(CopyAndValidateTrailers, DuplicateTrailers) {
// Duplicate trailers are allowed, and their values are concatenated into a
// single string delimted with '\0'. Some of the duplicate headers
// deliberately have an empty value.
auto trailers = FromList({{"key", "value0"},
{"key", "value1"},
{"key", ""},
{"key", ""},
{"key", "value2"},
{"key", ""},
{kFinalOffsetHeaderKey, "1234"},
{"other_key", "value"},
{"key", "non_contiguous_duplicate"}});
size_t final_byte_offset = 0;
SpdyHeaderBlock block;
EXPECT_TRUE(SpdyUtils::CopyAndValidateTrailers(
*trailers, kExpectFinalByteOffset, &final_byte_offset, &block));
EXPECT_THAT(
block,
UnorderedElementsAre(
Pair("key",
absl::string_view(
"value0\0value1\0\0\0value2\0\0non_contiguous_duplicate",
48)),
Pair("other_key", "value")));
}
TEST_F(CopyAndValidateTrailers, DuplicateCookies) {
// Duplicate cookie headers in trailers should be concatenated into a single
// "; " delimted string.
auto headers = FromList({{"cookie", " part 1"},
{"cookie", "part 2 "},
{"cookie", "part3"},
{"key", "value"},
{kFinalOffsetHeaderKey, "1234"},
{"cookie", " non_contiguous_cookie!"}});
size_t final_byte_offset = 0;
SpdyHeaderBlock block;
EXPECT_TRUE(SpdyUtils::CopyAndValidateTrailers(
*headers, kExpectFinalByteOffset, &final_byte_offset, &block));
EXPECT_THAT(
block,
UnorderedElementsAre(
Pair("cookie", " part 1; part 2 ; part3; non_contiguous_cookie!"),
Pair("key", "value")));
}
using PopulateHeaderBlockFromUrl = QuicTest;
TEST_F(PopulateHeaderBlockFromUrl, NormalUsage) {
std::string url = "https://www.google.com/index.html";
SpdyHeaderBlock headers;
EXPECT_TRUE(SpdyUtils::PopulateHeaderBlockFromUrl(url, &headers));
EXPECT_EQ("https", headers[":scheme"].as_string());
EXPECT_EQ("www.google.com", headers[":authority"].as_string());
EXPECT_EQ("/index.html", headers[":path"].as_string());
}
TEST_F(PopulateHeaderBlockFromUrl, UrlWithNoPath) {
std::string url = "https://www.google.com";
SpdyHeaderBlock headers;
EXPECT_TRUE(SpdyUtils::PopulateHeaderBlockFromUrl(url, &headers));
EXPECT_EQ("https", headers[":scheme"].as_string());
EXPECT_EQ("www.google.com", headers[":authority"].as_string());
EXPECT_EQ("/", headers[":path"].as_string());
}
TEST_F(PopulateHeaderBlockFromUrl, Failure) {
SpdyHeaderBlock headers;
EXPECT_FALSE(SpdyUtils::PopulateHeaderBlockFromUrl("/", &headers));
EXPECT_FALSE(SpdyUtils::PopulateHeaderBlockFromUrl("/index.html", &headers));
EXPECT_FALSE(
SpdyUtils::PopulateHeaderBlockFromUrl("www.google.com/", &headers));
}
using DatagramFlowIdTest = QuicTest;
TEST_F(DatagramFlowIdTest, DatagramFlowId) {
// Test missing header.
SpdyHeaderBlock headers;
EXPECT_EQ(SpdyUtils::ParseDatagramFlowIdHeader(headers), absl::nullopt);
// Add header and verify it parses.
QuicDatagramStreamId flow_id = 123;
SpdyUtils::AddDatagramFlowIdHeader(&headers, flow_id);
EXPECT_EQ(SpdyUtils::ParseDatagramFlowIdHeader(headers), flow_id);
// Test empty header.
ValidateDatagramFlowId("", absl::nullopt);
// Test invalid header.
ValidateDatagramFlowId("M4SQU3", absl::nullopt);
// Test simple header.
ValidateDatagramFlowId("42", 42);
// Test with parameter.
ValidateDatagramFlowId("42; abc=def", 42);
// Test list.
ValidateDatagramFlowId("42, 44; ecn-ect0, 46; ecn-ect1, 48; ecn-ce", 42);
// Test reordered list.
ValidateDatagramFlowId("44; ecn-ect0, 42, 48; ecn-ce, 46; ecn-ect1", 42);
}
using ExtractQuicVersionFromAltSvcEntry = QuicTest;
TEST_F(ExtractQuicVersionFromAltSvcEntry, SupportedVersion) {
ParsedQuicVersionVector supported_versions = AllSupportedVersions();
spdy::SpdyAltSvcWireFormat::AlternativeService entry;
for (const ParsedQuicVersion& version : supported_versions) {
entry.protocol_id = AlpnForVersion(version);
ParsedQuicVersion expected_version = version;
// Versions with share an ALPN with v1 are currently unable to be
// advertised with Alt-Svc.
if (entry.protocol_id == AlpnForVersion(ParsedQuicVersion::RFCv1()) &&
version != ParsedQuicVersion::RFCv1()) {
expected_version = ParsedQuicVersion::RFCv1();
}
EXPECT_EQ(expected_version, SpdyUtils::ExtractQuicVersionFromAltSvcEntry(
entry, supported_versions))
<< "version: " << version;
}
}
TEST_F(ExtractQuicVersionFromAltSvcEntry, UnsupportedVersion) {
spdy::SpdyAltSvcWireFormat::AlternativeService entry;
entry.protocol_id = "quic";
EXPECT_EQ(ParsedQuicVersion::Unsupported(),
SpdyUtils::ExtractQuicVersionFromAltSvcEntry(
entry, AllSupportedVersions()));
}
} // namespace test
} // namespace quic