Commit f5e7879a authored by Yixin Wang's avatar Yixin Wang Committed by Commit Bot

Inline QuicUrlUtilImpl::GetPushPromiseUrl() into SpdyUtils::GetPromisedUrlFromHeaders()

Since google3's implementation will be identical, there's no need for the code to be in quic/platform/impl.

This CL along with https://chromium-review.googlesource.com/c/chromium/src/+/893625 together correspond with google3 CL https://critique.corp.google.com/#review/184066984

Change-Id: I335b43b82ae7b53f4de794e0ba465f03165fe4ae
Reviewed-on: https://chromium-review.googlesource.com/894178
Commit-Queue: Yixin Wang <wangyix@chromium.org>
Reviewed-by: default avatarRyan Hamilton <rch@chromium.org>
Cr-Commit-Position: refs/heads/master@{#533536}
parent 383325a1
...@@ -5169,7 +5169,6 @@ test("net_unittests") { ...@@ -5169,7 +5169,6 @@ test("net_unittests") {
"quic/platform/api/quic_test_random.h", "quic/platform/api/quic_test_random.h",
"quic/platform/api/quic_text_utils_test.cc", "quic/platform/api/quic_text_utils_test.cc",
"quic/platform/api/quic_url_test.cc", "quic/platform/api/quic_url_test.cc",
"quic/platform/api/quic_url_utils_test.cc",
"quic/platform/impl/quic_chromium_clock_test.cc", "quic/platform/impl/quic_chromium_clock_test.cc",
"quic/platform/impl/quic_test_random_impl.cc", "quic/platform/impl/quic_test_random_impl.cc",
"quic/platform/impl/quic_test_random_impl.h", "quic/platform/impl/quic_test_random_impl.h",
......
...@@ -339,7 +339,7 @@ class QuicHttpStreamTest ...@@ -339,7 +339,7 @@ class QuicHttpStreamTest
promised_response_[":version"] = "HTTP/1.1"; promised_response_[":version"] = "HTTP/1.1";
promised_response_["content-type"] = "text/plain"; promised_response_["content-type"] = "text/plain";
promise_url_ = SpdyUtils::GetPromisedUrlFromHeaderBlock(push_promise_); promise_url_ = SpdyUtils::GetPromisedUrlFromHeaders(push_promise_);
} }
void SetRequest(const string& method, void SetRequest(const string& method,
...@@ -1873,7 +1873,7 @@ TEST_P(QuicHttpStreamTest, ServerPushCrossOriginOK) { ...@@ -1873,7 +1873,7 @@ TEST_P(QuicHttpStreamTest, ServerPushCrossOriginOK) {
// packet, but does it matter? // packet, but does it matter?
push_promise_[":authority"] = "mail.example.org"; push_promise_[":authority"] = "mail.example.org";
promise_url_ = SpdyUtils::GetPromisedUrlFromHeaderBlock(push_promise_); promise_url_ = SpdyUtils::GetPromisedUrlFromHeaders(push_promise_);
ReceivePromise(promise_id_); ReceivePromise(promise_id_);
EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr); EXPECT_NE(session_->GetPromisedByUrl(promise_url_), nullptr);
...@@ -1942,7 +1942,7 @@ TEST_P(QuicHttpStreamTest, ServerPushCrossOriginFail) { ...@@ -1942,7 +1942,7 @@ TEST_P(QuicHttpStreamTest, ServerPushCrossOriginFail) {
// TODO(ckrasic) - could do this via constructing a PUSH_PROMISE // TODO(ckrasic) - could do this via constructing a PUSH_PROMISE
// packet, but does it matter? // packet, but does it matter?
push_promise_[":authority"] = "www.notexample.org"; push_promise_[":authority"] = "www.notexample.org";
promise_url_ = SpdyUtils::GetPromisedUrlFromHeaderBlock(push_promise_); promise_url_ = SpdyUtils::GetPromisedUrlFromHeaders(push_promise_);
ReceivePromise(promise_id_); ReceivePromise(promise_id_);
// The promise will have been rejected because the cert doesn't // The promise will have been rejected because the cert doesn't
......
...@@ -55,13 +55,14 @@ void QuicClientPromisedInfo::OnPromiseHeaders(const SpdyHeaderBlock& headers) { ...@@ -55,13 +55,14 @@ void QuicClientPromisedInfo::OnPromiseHeaders(const SpdyHeaderBlock& headers) {
Reset(QUIC_INVALID_PROMISE_METHOD); Reset(QUIC_INVALID_PROMISE_METHOD);
return; return;
} }
if (!SpdyUtils::UrlIsValid(headers)) { if (!SpdyUtils::PromisedUrlIsValid(headers)) {
QUIC_DVLOG(1) << "Promise for stream " << id_ << " has invalid URL " QUIC_DVLOG(1) << "Promise for stream " << id_ << " has invalid URL "
<< url_; << url_;
Reset(QUIC_INVALID_PROMISE_URL); Reset(QUIC_INVALID_PROMISE_URL);
return; return;
} }
if (!session_->IsAuthorized(SpdyUtils::GetHostNameFromHeaderBlock(headers))) { if (!session_->IsAuthorized(
SpdyUtils::GetPromisedHostNameFromHeaders(headers))) {
Reset(QUIC_UNAUTHORIZED_PROMISE_URL); Reset(QUIC_UNAUTHORIZED_PROMISE_URL);
return; return;
} }
......
...@@ -84,7 +84,7 @@ class QuicClientPromisedInfoTest : public QuicTest { ...@@ -84,7 +84,7 @@ class QuicClientPromisedInfoTest : public QuicTest {
push_promise_[":method"] = "GET"; push_promise_[":method"] = "GET";
push_promise_[":scheme"] = "https"; push_promise_[":scheme"] = "https";
promise_url_ = SpdyUtils::GetPromisedUrlFromHeaderBlock(push_promise_); promise_url_ = SpdyUtils::GetPromisedUrlFromHeaders(push_promise_);
client_request_ = push_promise_.Clone(); client_request_ = push_promise_.Clone();
promise_id_ = promise_id_ =
......
...@@ -33,7 +33,7 @@ QuicAsyncStatus QuicClientPushPromiseIndex::Try( ...@@ -33,7 +33,7 @@ QuicAsyncStatus QuicClientPushPromiseIndex::Try(
const SpdyHeaderBlock& request, const SpdyHeaderBlock& request,
QuicClientPushPromiseIndex::Delegate* delegate, QuicClientPushPromiseIndex::Delegate* delegate,
TryHandle** handle) { TryHandle** handle) {
string url(SpdyUtils::GetPromisedUrlFromHeaderBlock(request)); string url(SpdyUtils::GetPromisedUrlFromHeaders(request));
QuicPromisedByUrlMap::iterator it = promised_by_url_.find(url); QuicPromisedByUrlMap::iterator it = promised_by_url_.find(url);
if (it != promised_by_url_.end()) { if (it != promised_by_url_.end()) {
QuicClientPromisedInfo* promised = it->second; QuicClientPromisedInfo* promised = it->second;
......
...@@ -63,7 +63,7 @@ class QuicClientPushPromiseIndexTest : public QuicTest { ...@@ -63,7 +63,7 @@ class QuicClientPushPromiseIndexTest : public QuicTest {
request_[":version"] = "HTTP/1.1"; request_[":version"] = "HTTP/1.1";
request_[":method"] = "GET"; request_[":method"] = "GET";
request_[":scheme"] = "https"; request_[":scheme"] = "https";
url_ = SpdyUtils::GetPromisedUrlFromHeaderBlock(request_); url_ = SpdyUtils::GetPromisedUrlFromHeaders(request_);
} }
MockQuicConnectionHelper helper_; MockQuicConnectionHelper helper_;
......
...@@ -99,7 +99,7 @@ bool QuicSpdyClientSessionBase::HandlePromised(QuicStreamId /* associated_id */, ...@@ -99,7 +99,7 @@ bool QuicSpdyClientSessionBase::HandlePromised(QuicStreamId /* associated_id */,
return false; return false;
} }
const string url = SpdyUtils::GetPromisedUrlFromHeaderBlock(headers); const string url = SpdyUtils::GetPromisedUrlFromHeaders(headers);
QuicClientPromisedInfo* old_promised = GetPromisedByUrl(url); QuicClientPromisedInfo* old_promised = GetPromisedByUrl(url);
if (old_promised) { if (old_promised) {
QUIC_DVLOG(1) << "Promise for stream " << promised_id QUIC_DVLOG(1) << "Promise for stream " << promised_id
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include "net/spdy/core/spdy_frame_builder.h" #include "net/spdy/core/spdy_frame_builder.h"
#include "net/spdy/core/spdy_framer.h" #include "net/spdy/core/spdy_framer.h"
#include "net/spdy/core/spdy_protocol.h" #include "net/spdy/core/spdy_protocol.h"
#include "url/gurl.h"
using std::string; using std::string;
...@@ -128,12 +129,8 @@ bool SpdyUtils::CopyAndValidateTrailers(const QuicHeaderList& header_list, ...@@ -128,12 +129,8 @@ bool SpdyUtils::CopyAndValidateTrailers(const QuicHeaderList& header_list,
} }
// static // static
string SpdyUtils::GetPromisedUrlFromHeaderBlock( // static
const SpdyHeaderBlock& headers) { string SpdyUtils::GetPromisedUrlFromHeaders(const SpdyHeaderBlock& headers) {
// RFC 7540, Section 8.1.2.3: All HTTP/2 requests MUST include exactly
// one valid value for the ":method", ":scheme", and ":path" pseudo-header
// fields, unless it is a CONNECT request.
// RFC 7540, Section 8.2.1: The header fields in PUSH_PROMISE and any // RFC 7540, Section 8.2.1: The header fields in PUSH_PROMISE and any
// subsequent CONTINUATION frames MUST be a valid and complete set of request // subsequent CONTINUATION frames MUST be a valid and complete set of request
// header fields (Section 8.1.2.3). The server MUST include a method in the // header fields (Section 8.1.2.3). The server MUST include a method in the
...@@ -152,6 +149,15 @@ string SpdyUtils::GetPromisedUrlFromHeaderBlock( ...@@ -152,6 +149,15 @@ string SpdyUtils::GetPromisedUrlFromHeaderBlock(
return string(); return string();
} }
// RFC 7540, Section 8.1.2.3: The ":scheme" pseudo-header field includes the
// scheme portion of the target URI ([RFC3986], Section 3.1). ":scheme" is not
// restricted to "http" and "https" schemed URIs. A proxy or gateway can
// translate requests for non-HTTP schemes, enabling the use of HTTP to
// interact with non-HTTP services.
//
// Since neither the client nor server implementations are intended to be
// such a proxy or gateway, ":scheme" must be HTTP or HTTPS. That check is
// performed later after the scheme is canonicalized.
it = headers.find(":scheme"); it = headers.find(":scheme");
if (it == headers.end() || it->second.empty()) { if (it == headers.end() || it->second.empty()) {
return string(); return string();
...@@ -167,30 +173,140 @@ string SpdyUtils::GetPromisedUrlFromHeaderBlock( ...@@ -167,30 +173,140 @@ string SpdyUtils::GetPromisedUrlFromHeaderBlock(
} }
QuicStringPiece authority = it->second; QuicStringPiece authority = it->second;
// RFC 7540, Section 8.1.2.3 requires that the ":path" pseudo-header MUST // RFC 7540, Section 8.1.2.3: The ":path" pseudo-header field includes the
// NOT be empty for "http" or "https" URIs; // path and query parts of the target URI (the "path-absolute" production
// and optionally a '?' character followed by the "query" production (see
// Sections 3.3 and 3.4 of RFC3986). A request in asterisk form includes the
// value '*' for the ":path" pseudo-header field.
// //
// However, to ensure the scheme is consistently canonicalized, that check // This pseudo-header field MUST NOT be empty for "http" or "https" URIs;
// is deferred to implementations in QuicUrlUtils::GetPushPromiseUrl(). // "http" or "https" URIs that do not contain a path MUST include a value of
// '/'. The exception to this rule is an OPTIONS request for an "http" or
// "https" URI that does not include a path component; these MUST include a
// ":path" pseudo-header with a value of '*' (see RFC7230, Section 5.3.4).
//
// In addition to the above restriction from RFC 7540, note that RFC3986
// defines the "path-absolute" construction as starting with "/" but not "//".
//
// It's stated above that GET and HEAD are the only methods allowed in a
// PUSH_PROMISE. Therefore, the exception mentioned in RFC 7540, Section
// 8.1.2.3 about OPTIONS requests does not apply here (i.e. ":path" cannot be
// "*").
it = headers.find(":path"); it = headers.find(":path");
if (it == headers.end()) { if (it == headers.end()) {
return string(); return string();
} }
QuicStringPiece path = it->second; QuicStringPiece path = it->second;
if (path.empty() || path[0] != '/' || (path.size() >= 2 && path[1] == '/')) {
return std::string();
}
return QuicUrlUtils::GetPushPromiseUrl(scheme, authority, path); // Canonicalize the scheme; this is to ensure a scheme of "foo://bar" is not
// parsed as a URL of "foo://bar://baz" when combined with a host of "baz".
std::string canonical_scheme;
url::StdStringCanonOutput canon_output(&canonical_scheme);
url::Component canon_component;
url::Component scheme_component(0, scheme.size());
if (!url::CanonicalizeScheme(scheme.data(), scheme_component, &canon_output,
&canon_component) ||
!canon_component.is_nonempty() || canon_component.begin != 0) {
return std::string();
}
canonical_scheme.resize(canon_component.len + 1);
// Validate the authority; this is to ensure an authority such as
// "host/path" is not accepted, as when combined with a scheme like
// "http://", could result in a URL of "http://host/path".
url::Component auth_component(0, authority.size());
url::Component username_component;
url::Component password_component;
url::Component host_component;
url::Component port_component;
url::ParseAuthority(authority.data(), auth_component, &username_component,
&password_component, &host_component, &port_component);
// RFC 7540, Section 8.1.2.3: The authority MUST NOT include the deprecated
// "userinfo" subcomponent for "http" or "https" schemed URIs.
//
// Note: Although |canonical_scheme| has not yet been checked for that, as
// it is performed later in processing, only "http" and "https" schemed
// URIs are supported for PUSH.
if (username_component.is_valid() || password_component.is_valid()) {
return std::string();
}
// Failed parsing or no host present. ParseAuthority() will ensure that
// host_component + port_component cover the entire string, if
// username_component and password_component are not present.
if (!host_component.is_nonempty()) {
return std::string();
}
// Validate the port if present (it's optional).
int parsed_port_number = url::PORT_INVALID;
if (port_component.is_nonempty()) {
parsed_port_number = url::ParsePort(authority.data(), port_component);
if (parsed_port_number < 0 && parsed_port_number != url::PORT_UNSPECIFIED) {
return std::string();
}
}
// Validate the host by attempting to canoncalize it. Invalid characters
// will result in a canonicalization failure (e.g. '/')
std::string canon_host;
canon_output = url::StdStringCanonOutput(&canon_host);
canon_component.reset();
if (!url::CanonicalizeHost(authority.data(), host_component, &canon_output,
&canon_component) ||
!canon_component.is_nonempty() || canon_component.begin != 0) {
return std::string();
}
// At this point, "authority" has been validated to either be of the form
// 'host:port' or 'host', with 'host' being a valid domain or IP address,
// and 'port' (if present), being a valid port. Attempt to construct a
// URL of just the (scheme, host, port), which should be safe and will not
// result in ambiguous parsing.
//
// This also enforces that all PUSHed URLs are either HTTP or HTTPS-schemed
// URIs, consistent with the other restrictions enforced above.
//
// Note: url::CanonicalizeScheme() will have added the ':' to
// |canonical_scheme|.
GURL origin_url(canonical_scheme + "//" + std::string(authority));
if (!origin_url.is_valid() || !origin_url.SchemeIsHTTPOrHTTPS() ||
// The following checks are merely defense in depth.
origin_url.SchemeIsSuborigin() || origin_url.has_username() ||
origin_url.has_password() ||
(origin_url.has_path() && origin_url.path_piece() != "/") ||
origin_url.has_query() || origin_url.has_ref()) {
return std::string();
}
// Attempt to parse the full URL, with the path as well. Ensure there is no
// fragment to the query.
std::string spec = origin_url.GetWithEmptyPath().spec();
spec.pop_back(); // Remove the '/', as ":path" must contain it.
spec.append(std::string(path));
GURL full_url(spec);
if (!full_url.is_valid() || full_url.has_ref()) {
return std::string();
}
return full_url.spec();
} }
// static // static
string SpdyUtils::GetHostNameFromHeaderBlock(const SpdyHeaderBlock& headers) { string SpdyUtils::GetPromisedHostNameFromHeaders(
const SpdyHeaderBlock& headers) {
// TODO(fayang): Consider just checking out the value of the ":authority" key // TODO(fayang): Consider just checking out the value of the ":authority" key
// in headers. // in headers.
return QuicUrlUtils::HostName(GetPromisedUrlFromHeaderBlock(headers)); return QuicUrlUtils::HostName(GetPromisedUrlFromHeaders(headers));
} }
// static // static
bool SpdyUtils::UrlIsValid(const SpdyHeaderBlock& headers) { bool SpdyUtils::PromisedUrlIsValid(const SpdyHeaderBlock& headers) {
string url(GetPromisedUrlFromHeaderBlock(headers)); string url(GetPromisedUrlFromHeaders(headers));
return !url.empty() && QuicUrlUtils::IsValidUrl(url); return !url.empty() && QuicUrlUtils::IsValidUrl(url);
} }
......
...@@ -39,15 +39,15 @@ class QUIC_EXPORT_PRIVATE SpdyUtils { ...@@ -39,15 +39,15 @@ class QUIC_EXPORT_PRIVATE SpdyUtils {
// :path headers of a PUSH_PROMISE. Returns empty string if the headers do not // :path headers of a PUSH_PROMISE. Returns empty string if the headers do not
// conform to HTTP/2 spec or if the ":method" header contains a forbidden // conform to HTTP/2 spec or if the ":method" header contains a forbidden
// method for PUSH_PROMISE. // method for PUSH_PROMISE.
static std::string GetPromisedUrlFromHeaderBlock( static std::string GetPromisedUrlFromHeaders(const SpdyHeaderBlock& headers);
const SpdyHeaderBlock& headers);
// Returns hostname, or empty string if missing. // Returns hostname, or empty string if missing.
static std::string GetHostNameFromHeaderBlock(const SpdyHeaderBlock& headers); static std::string GetPromisedHostNameFromHeaders(
const SpdyHeaderBlock& headers);
// Returns true if result of |GetPromisedUrlFromHeaderBlock()| is non-empty // Returns true if result of |GetPromisedUrlFromHeaders()| is non-empty and is
// and is a well-formed URL. // a well-formed URL.
static bool UrlIsValid(const SpdyHeaderBlock& headers); static bool PromisedUrlIsValid(const SpdyHeaderBlock& headers);
// Populates the fields of |headers| to make a GET request of |url|, // Populates the fields of |headers| to make a GET request of |url|,
// which must be fully-qualified. // which must be fully-qualified.
......
This diff is collapsed.
...@@ -18,11 +18,4 @@ bool QuicUrlUtils::IsValidUrl(QuicStringPiece url) { ...@@ -18,11 +18,4 @@ bool QuicUrlUtils::IsValidUrl(QuicStringPiece url) {
return QuicUrlUtilsImpl::IsValidUrl(url); return QuicUrlUtilsImpl::IsValidUrl(url);
} }
// static
string QuicUrlUtils::GetPushPromiseUrl(QuicStringPiece scheme,
QuicStringPiece authority,
QuicStringPiece path) {
return QuicUrlUtilsImpl::GetPushPromiseUrl(scheme, authority, path);
}
} // namespace net } // namespace net
...@@ -22,13 +22,6 @@ class QUIC_EXPORT_PRIVATE QuicUrlUtils { ...@@ -22,13 +22,6 @@ class QUIC_EXPORT_PRIVATE QuicUrlUtils {
// (e.g. greater than 65535). // (e.g. greater than 65535).
static bool IsValidUrl(QuicStringPiece url); static bool IsValidUrl(QuicStringPiece url);
// Returns a canonical, valid URL for a PUSH_PROMISE with the specified
// ":scheme", ":authority", and ":path" header fields, or an empty
// string if the resulting URL is not valid or supported.
static std::string GetPushPromiseUrl(QuicStringPiece scheme,
QuicStringPiece authority,
QuicStringPiece path);
private: private:
DISALLOW_COPY_AND_ASSIGN(QuicUrlUtils); DISALLOW_COPY_AND_ASSIGN(QuicUrlUtils);
}; };
......
// Copyright (c) 2018 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 "net/quic/platform/api/quic_url_utils.h"
#include <cstdint>
#include "net/quic/platform/api/quic_arraysize.h"
#include "net/quic/platform/api/quic_test.h"
using std::string;
namespace net {
namespace test {
namespace {
class QuicUrlUtilsTest : public QuicTest {};
TEST_F(QuicUrlUtilsTest, GetPushPromiseUrl) {
// Test acception/rejection of various input combinations.
// |input_headers| is an array of pairs. The first value of each pair is a
// string that will be used as one of the inputs of GetPushPromiseUrl(). The
// second value of each pair is a bitfield where the lowest 3 bits indicate
// for which headers that string is valid (in a PUSH_PROMISE). For example,
// the string "http" would be valid for both the ":scheme" and ":authority"
// headers, so the bitfield paired with it is set to SCHEME | AUTH.
const unsigned char SCHEME = (1u << 0);
const unsigned char AUTH = (1u << 1);
const unsigned char PATH = (1u << 2);
const std::pair<const char*, unsigned char> input_headers[] = {
{"http", SCHEME | AUTH},
{"https", SCHEME | AUTH},
{"hTtP", SCHEME | AUTH},
{"HTTPS", SCHEME | AUTH},
{"www.google.com", AUTH},
{"90af90e0", AUTH},
{"12foo%20-bar:00001233", AUTH},
{"GOO\u200b\u2060\ufeffgoo", AUTH},
{"192.168.0.5", AUTH},
{"[::ffff:192.168.0.1.]", AUTH},
{"http:", AUTH},
{"bife l", AUTH},
{"/", PATH},
{"/foo/bar/baz", PATH},
{"/%20-2DVdkj.cie/foe_.iif/", PATH},
{"http://", 0},
{":443", 0},
{":80/eddd", 0},
{"google.com:-0", 0},
{"google.com:65536", 0},
{"http://google.com", 0},
{"http://google.com:39", 0},
{"//google.com/foo", 0},
{".com/", 0},
{"http://www.google.com/", 0},
{"http://foo:439", 0},
{"[::ffff:192.168", 0},
{"]/", 0},
{"//", 0}};
for (size_t i = 0; i < QUIC_ARRAYSIZE(input_headers); ++i) {
bool should_accept = (input_headers[i].second & SCHEME);
for (size_t j = 0; j < QUIC_ARRAYSIZE(input_headers); ++j) {
bool should_accept_2 = should_accept && (input_headers[j].second & AUTH);
for (size_t k = 0; k < QUIC_ARRAYSIZE(input_headers); ++k) {
// |should_accept_3| indicates whether or not GetPushPromiseUrl() is
// expected to accept this input combination.
bool should_accept_3 =
should_accept_2 && (input_headers[k].second & PATH);
std::string url = QuicUrlUtilsImpl::GetPushPromiseUrl(
input_headers[i].first, input_headers[j].first,
input_headers[k].first);
::testing::AssertionResult result = ::testing::AssertionSuccess();
if (url.empty() == should_accept_3) {
result = ::testing::AssertionFailure()
<< "GetPushPromiseUrl() accepted/rejected the inputs when "
"it shouldn't have."
<< std::endl
<< " scheme: " << input_headers[i].first << std::endl
<< " authority: " << input_headers[j].first << std::endl
<< " path: " << input_headers[k].first << std::endl
<< "Output: " << url << std::endl;
}
ASSERT_TRUE(result);
}
}
}
// Test canonicalization of various valid inputs.
EXPECT_EQ("http://www.google.com/",
QuicUrlUtilsImpl::GetPushPromiseUrl("http", "www.google.com", "/"));
EXPECT_EQ("https://www.goo-gle.com/fOOo/baRR",
QuicUrlUtilsImpl::GetPushPromiseUrl("hTtPs", "wWw.gOo-gLE.cOm",
"/fOOo/baRR"));
EXPECT_EQ(
"https://foo%20bar/foo/bar/baz",
QuicUrlUtilsImpl::GetPushPromiseUrl("https", "foo bar", "/foo/bar/baz"));
EXPECT_EQ("http://foo.com:70/e/", QuicUrlUtilsImpl::GetPushPromiseUrl(
"http", "foo.com:0000070", "/e/"));
EXPECT_EQ("http://192.168.0.1:70/e/",
QuicUrlUtilsImpl::GetPushPromiseUrl("http", "0300.0250.00.01:0070",
"/e/"));
EXPECT_EQ("http://192.168.0.1/e/",
QuicUrlUtilsImpl::GetPushPromiseUrl("http", "0xC0a80001", "/e/"));
EXPECT_EQ("http://[::c0a8:1]/", QuicUrlUtilsImpl::GetPushPromiseUrl(
"http", "[::192.168.0.1]", "/"));
EXPECT_EQ("https://[::ffff:c0a8:1]/",
QuicUrlUtilsImpl::GetPushPromiseUrl(
"https", "[::ffff:0xC0.0Xa8.0x0.0x1]", "/"));
}
}; // namespace
}; // namespace test
}; // namespace net
\ No newline at end of file
...@@ -18,138 +18,4 @@ bool QuicUrlUtilsImpl::IsValidUrl(QuicStringPiece url) { ...@@ -18,138 +18,4 @@ bool QuicUrlUtilsImpl::IsValidUrl(QuicStringPiece url) {
return GURL(url).is_valid(); return GURL(url).is_valid();
} }
// static
std::string QuicUrlUtilsImpl::GetPushPromiseUrl(QuicStringPiece scheme,
QuicStringPiece authority,
QuicStringPiece path) {
// RFC 7540, Section 8.1.2.3: The ":path" pseudo-header field includes the
// path and query parts of the target URI (the "path-absolute" production
// and optionally a '?' character followed by the "query" production (see
// Sections 3.3 and 3.4 of RFC3986). A request in asterisk form includes the
// value '*' for the ":path" pseudo-header field.
//
// This pseudo-header field MUST NOT be empty for "http" or "https" URIs;
// "http" or "https" URIs that do not contain a path MUST include a value of
// '/'. The exception to this rule is an OPTIONS request for an "http" or
// "https" URI that does not include a path component; these MUST include a
// ":path" pseudo-header with a value of '*' (see RFC7230, Section 5.3.4).
//
// In addition to the above restriction from RFC 7540, note that RFC3986
// defines the "path-absolute" construction as starting with "/" but not "//".
//
// RFC 7540, Section 8.2.1: The header fields in PUSH_PROMISE and any
// subsequent CONTINUATION frames MUST be a valid and complete set of request
// header fields (Section 8.1.2.3). The server MUST include a method in the
// ":method" pseudo-header field that is safe and cacheable.
//
// RFC 7231, Section 4.2.1:
// ... this specification defines GET, HEAD, and POST as cacheable, ...
//
// Since the OPTIONS method is not cacheable, it cannot be the method of a
// PUSH_PROMISE. Therefore, the exception mentioned in RFC 7540, Section
// 8.1.2.3 about OPTIONS requests does not apply here (i.e. ":path" cannot be
// "*").
if (path.empty() || path[0] != '/' || (path.size() >= 2 && path[1] == '/')) {
return std::string();
}
// Validate the scheme; this is to ensure a scheme of "foo://bar" is not
// parsed as a URL of "foo://bar://baz" when combined with a host of "baz".
std::string canonical_scheme;
url::StdStringCanonOutput canon_output(&canonical_scheme);
url::Component canon_component;
url::Component scheme_component(0, scheme.size());
if (!url::CanonicalizeScheme(scheme.data(), scheme_component, &canon_output,
&canon_component) ||
!canon_component.is_nonempty() || canon_component.begin != 0) {
return std::string();
}
canonical_scheme.resize(canon_component.len + 1);
// Validate the authority; this is to ensure an authority such as
// "host/path" is not accepted, as when combined with a scheme like
// "http://", could result in a URL of "http://host/path".
url::Component auth_component(0, authority.size());
url::Component username_component;
url::Component password_component;
url::Component host_component;
url::Component port_component;
url::ParseAuthority(authority.data(), auth_component, &username_component,
&password_component, &host_component, &port_component);
// RFC 7540, Section 8.1.2.3: The authority MUST NOT include the deprecated
// "userinfo" subcomponent for "http" or "https" schemed URIs.
//
// Note: Although |canonical_scheme| has not yet been checked for that, as
// it is performed later in processing, only "http" and "https" schemed
// URIs are supported for PUSH.
if (username_component.is_valid() || password_component.is_valid()) {
return std::string();
}
// Failed parsing or no host present. ParseAuthority() will ensure that
// host_component + port_component cover the entire string, if
// username_component and password_component are not present.
if (!host_component.is_nonempty()) {
return std::string();
}
// Validate the port (if present; it's optional).
int parsed_port_number = url::PORT_INVALID;
if (port_component.is_nonempty()) {
parsed_port_number = url::ParsePort(authority.data(), port_component);
if (parsed_port_number < 0 && parsed_port_number != url::PORT_UNSPECIFIED) {
return std::string();
}
}
// Validate the host by attempting to canoncalize it. Invalid characters
// will result in a canonicalization failure (e.g. '/')
std::string canon_host;
canon_output = url::StdStringCanonOutput(&canon_host);
canon_component.reset();
if (!url::CanonicalizeHost(authority.data(), host_component, &canon_output,
&canon_component) ||
!canon_component.is_nonempty() || canon_component.begin != 0) {
return std::string();
}
// At this point, "authority" has been validated to either be of the form
// 'host:port' or 'host', with 'host' being a valid domain or IP address,
// and 'port' (if present), being a valid port. Attempt to construct a
// URL of just the (scheme, host, port), which should be safe and will not
// result in ambiguous parsing.
//
// This also enforces that all PUSHed URLs are either HTTP or HTTPS-schemed
// URIs, consistent with the other restrictions enforced above.
//
// Note: url::CanonicalizeScheme() will have added the ':' to
// |canonical_scheme|.
GURL origin_url(canonical_scheme + "//" + std::string(authority));
if (!origin_url.is_valid() || !origin_url.SchemeIsHTTPOrHTTPS() ||
// The following checks are merely defense in depth.
origin_url.SchemeIsSuborigin() || origin_url.has_username() ||
origin_url.has_password() ||
(origin_url.has_path() && origin_url.path_piece() != "/") ||
origin_url.has_query() || origin_url.has_ref()) {
return std::string();
}
// Attempt to parse the path.
std::string spec = origin_url.GetWithEmptyPath().spec();
spec.pop_back(); // Remove the '/', as ":path" must contain it.
spec.append(std::string(path));
// Attempt to parse the full URL, with the path as well. Ensure there is no
// fragment to the query.
GURL full_url(spec);
if (!full_url.is_valid() || full_url.has_ref()) {
return std::string();
}
return full_url.spec();
}
} // namespace net } // namespace net
...@@ -22,13 +22,6 @@ class QUIC_EXPORT_PRIVATE QuicUrlUtilsImpl { ...@@ -22,13 +22,6 @@ class QUIC_EXPORT_PRIVATE QuicUrlUtilsImpl {
// (e.g. greater than 65535). // (e.g. greater than 65535).
static bool IsValidUrl(QuicStringPiece url); static bool IsValidUrl(QuicStringPiece url);
// Returns a canonical, valid URL for a PUSH_PROMISE with the specified
// ":scheme", ":authority", and ":path" header fields, or an empty
// string if the resulting URL is not valid or supported.
static std::string GetPushPromiseUrl(QuicStringPiece scheme,
QuicStringPiece authority,
QuicStringPiece path);
private: private:
DISALLOW_COPY_AND_ASSIGN(QuicUrlUtilsImpl); DISALLOW_COPY_AND_ASSIGN(QuicUrlUtilsImpl);
}; };
......
...@@ -96,7 +96,7 @@ class QuicSpdyClientSessionTest : public QuicTestWithParam<ParsedQuicVersion> { ...@@ -96,7 +96,7 @@ class QuicSpdyClientSessionTest : public QuicTestWithParam<ParsedQuicVersion> {
push_promise_[":version"] = "HTTP/1.1"; push_promise_[":version"] = "HTTP/1.1";
push_promise_[":method"] = "GET"; push_promise_[":method"] = "GET";
push_promise_[":scheme"] = "https"; push_promise_[":scheme"] = "https";
promise_url_ = SpdyUtils::GetPromisedUrlFromHeaderBlock(push_promise_); promise_url_ = SpdyUtils::GetPromisedUrlFromHeaders(push_promise_);
promised_stream_id_ = promised_stream_id_ =
QuicSpdySessionPeer::GetNthServerInitiatedStreamId(*session_, 0); QuicSpdySessionPeer::GetNthServerInitiatedStreamId(*session_, 0);
associated_stream_id_ = associated_stream_id_ =
...@@ -502,7 +502,7 @@ TEST_P(QuicSpdyClientSessionTest, ReceivingPromiseEnhanceYourCalm) { ...@@ -502,7 +502,7 @@ TEST_P(QuicSpdyClientSessionTest, ReceivingPromiseEnhanceYourCalm) {
session_->HandlePromised(associated_stream_id_, id, push_promise_); session_->HandlePromised(associated_stream_id_, id, push_promise_);
// Verify that the promise is in the unclaimed streams map. // Verify that the promise is in the unclaimed streams map.
string promise_url(SpdyUtils::GetPromisedUrlFromHeaderBlock(push_promise_)); string promise_url(SpdyUtils::GetPromisedUrlFromHeaders(push_promise_));
EXPECT_NE(session_->GetPromisedByUrl(promise_url), nullptr); EXPECT_NE(session_->GetPromisedByUrl(promise_url), nullptr);
EXPECT_NE(session_->GetPromisedById(id), nullptr); EXPECT_NE(session_->GetPromisedById(id), nullptr);
} }
...@@ -521,7 +521,7 @@ TEST_P(QuicSpdyClientSessionTest, ReceivingPromiseEnhanceYourCalm) { ...@@ -521,7 +521,7 @@ TEST_P(QuicSpdyClientSessionTest, ReceivingPromiseEnhanceYourCalm) {
session_->HandlePromised(associated_stream_id_, id, push_promise_); session_->HandlePromised(associated_stream_id_, id, push_promise_);
// Verify that the promise was not created. // Verify that the promise was not created.
string promise_url(SpdyUtils::GetPromisedUrlFromHeaderBlock(push_promise_)); string promise_url(SpdyUtils::GetPromisedUrlFromHeaders(push_promise_));
EXPECT_EQ(session_->GetPromisedById(id), nullptr); EXPECT_EQ(session_->GetPromisedById(id), nullptr);
EXPECT_EQ(session_->GetPromisedByUrl(promise_url), nullptr); EXPECT_EQ(session_->GetPromisedByUrl(promise_url), nullptr);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment