blob: b0e4372e588d718dc14020bb23be6d1b6196c644 [file] [log] [blame]
// Copyright 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/tools/quic/quic_http_proxy_backend_stream.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_task_environment.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "net/third_party/quic/core/quic_connection_id.h"
#include "net/third_party/quic/platform/api/quic_test.h"
#include "net/third_party/quic/test_tools/quic_test_utils.h"
#include "net/third_party/quic/tools/quic_backend_response.h"
#include "net/tools/quic/quic_http_proxy_backend.h"
namespace net {
namespace test {
// Test server path and response body for the default URL used by many of the
// tests.
const char kDefaultResponsePath[] = "/defaultresponse";
const char kDefaultResponseBody[] =
"Default response given for path: /defaultresponse";
std::string kLargeResponseBody(
"Default response given for path: /defaultresponselarge");
const char* const kHttp2StatusHeader = ":status";
// To test uploading the contents of a file
base::FilePath GetUploadFileTestPath() {
base::FilePath path;
base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
return path.Append(
FILE_PATH_LITERAL("net/data/url_request_unittest/BullRunSpeech.txt"));
}
// /defaultresponselarge
// Returns a valid 10 MB response.
std::unique_ptr<test_server::HttpResponse> HandleDefaultResponseLarge(
const test_server::HttpRequest& request) {
std::unique_ptr<test_server::BasicHttpResponse> http_response(
new test_server::BasicHttpResponse);
http_response->set_content_type("text/html");
// return 10 MB
for (int i = 0; i < 200000; ++i)
kLargeResponseBody += "01234567890123456789012345678901234567890123456789";
http_response->set_content(kLargeResponseBody);
return std::move(http_response);
}
int ParseHeaderStatusCode(const spdy::SpdyHeaderBlock& header) {
int status_code;
spdy::SpdyHeaderBlock::const_iterator it = header.find(kHttp2StatusHeader);
if (it == header.end()) {
return -1;
}
const base::StringPiece status(it->second);
if (status.size() != 3) {
return -1;
}
// First character must be an integer in range [1,5].
if (status[0] < '1' || status[0] > '5') {
return -1;
}
// The remaining two characters must be integers.
if (!isdigit(status[1]) || !isdigit(status[2])) {
return -1;
}
if (base::StringToInt(status, &status_code)) {
return status_code;
} else {
return -1;
}
}
class TestQuicServerStreamDelegate
: public quic::QuicSimpleServerBackend::RequestHandler {
public:
TestQuicServerStreamDelegate()
: send_success_(false),
did_complete_(false),
quic_backend_stream_(nullptr) {}
~TestQuicServerStreamDelegate() override {}
void CreateProxyBackendResponseStreamForTest(
QuicHttpProxyBackend* proxy_backend) {
quic_backend_stream_ =
std::make_unique<QuicHttpProxyBackendStream>(proxy_backend);
quic_backend_stream_->set_delegate(this);
quic_backend_stream_->Initialize(connection_id(), stream_id(), peer_host());
}
QuicHttpProxyBackendStream* get_proxy_backend_stream() const {
return quic_backend_stream_.get();
}
const net::HttpRequestHeaders& get_request_headers() const {
return quic_backend_stream_->request_headers();
}
void StartHttpRequestToBackendAndWait(
spdy::SpdyHeaderBlock* incoming_request_headers,
const std::string& incoming_body) {
send_success_ = quic_backend_stream_->SendRequestToBackend(
incoming_request_headers, incoming_body);
EXPECT_TRUE(send_success_);
WaitForComplete();
}
void WaitForComplete() {
EXPECT_TRUE(task_runner_->BelongsToCurrentThread());
run_loop_.Run();
}
quic::QuicConnectionId connection_id() const override {
return quic::test::TestConnectionId(123);
}
quic::QuicStreamId stream_id() const override { return 5; }
std::string peer_host() const override { return "127.0.0.1"; }
void OnResponseBackendComplete(
const quic::QuicBackendResponse* response,
std::list<quic::QuicBackendResponse::ServerPushInfo> resources) override {
EXPECT_TRUE(task_runner_->BelongsToCurrentThread());
EXPECT_FALSE(did_complete_);
EXPECT_TRUE(quic_backend_stream_);
did_complete_ = true;
task_runner_->PostTask(FROM_HERE, run_loop_.QuitClosure());
}
private:
bool send_success_;
bool did_complete_;
std::unique_ptr<QuicHttpProxyBackendStream> quic_backend_stream_;
const scoped_refptr<base::SingleThreadTaskRunner> task_runner_ =
base::ThreadTaskRunnerHandle::Get();
base::RunLoop run_loop_;
};
class QuicHttpProxyBackendStreamTest : public QuicTest {
public:
QuicHttpProxyBackendStreamTest() {}
~QuicHttpProxyBackendStreamTest() override {}
// testing::Test:
void SetUp() override {
SetUpServer();
ASSERT_TRUE(test_server_->Start());
backend_url_ = base::StringPrintf("http://127.0.0.1:%d",
test_server_->host_port_pair().port());
CreateTestBackendProxy();
CreateTestBackendProxyToTestFailure();
}
void CreateTestBackendProxy() {
ASSERT_TRUE(GURL(backend_url_).is_valid());
proxy_backend_ = std::make_unique<QuicHttpProxyBackend>();
proxy_backend_->InitializeBackend(backend_url_);
}
void CreateTestBackendProxyToTestFailure() {
// To test against a non-running backend http server
std::string backend_fail_url =
base::StringPrintf("http://127.0.0.1:%d", 52);
ASSERT_TRUE(GURL(backend_fail_url).is_valid());
proxy_backend_fail_ = std::make_unique<QuicHttpProxyBackend>();
proxy_backend_fail_->InitializeBackend(backend_fail_url);
}
// Initializes |test_server_| without starting it. Allows subclasses to use
// their own server configuration.
void SetUpServer() {
test_server_.reset(new EmbeddedTestServer);
test_server_->AddDefaultHandlers(base::FilePath());
test_server_->RegisterDefaultHandler(base::BindRepeating(
&net::test_server::HandlePrefixedRequest, "/defaultresponselarge",
base::BindRepeating(&HandleDefaultResponseLarge)));
}
protected:
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::string backend_url_;
std::unique_ptr<QuicHttpProxyBackend> proxy_backend_;
std::unique_ptr<QuicHttpProxyBackend> proxy_backend_fail_;
std::unique_ptr<EmbeddedTestServer> test_server_;
};
TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendGetDefault) {
spdy::SpdyHeaderBlock request_headers;
request_headers[":path"] = kDefaultResponsePath;
request_headers[":authority"] = "www.example.org";
request_headers[":version"] = "HTTP/1.1";
request_headers[":method"] = "GET";
TestQuicServerStreamDelegate delegate;
delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
delegate.StartHttpRequestToBackendAndWait(&request_headers, "");
quic::QuicBackendResponse* quic_response =
delegate.get_proxy_backend_stream()->GetBackendResponse();
EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
quic_response->response_type());
EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
EXPECT_EQ(kDefaultResponseBody, quic_response->body());
}
TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendGetLarge) {
spdy::SpdyHeaderBlock request_headers;
request_headers[":path"] = "/defaultresponselarge";
request_headers[":authority"] = "www.example.org";
request_headers[":version"] = "HTTP/1.1";
request_headers[":method"] = "GET";
TestQuicServerStreamDelegate delegate;
delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
delegate.StartHttpRequestToBackendAndWait(&request_headers, "");
quic::QuicBackendResponse* quic_response =
delegate.get_proxy_backend_stream()->GetBackendResponse();
EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
quic_response->response_type());
EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
// kLargeResponseBody should be populated with huge response
// already in HandleDefaultResponseLarge()
EXPECT_EQ(kLargeResponseBody, quic_response->body());
}
TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendPostBody) {
const char kUploadData[] = "bobsyeruncle";
spdy::SpdyHeaderBlock request_headers;
request_headers[":path"] = "/echo";
request_headers[":version"] = "HTTP/2.0";
request_headers[":version"] = "HTTP/1.1";
request_headers[":method"] = "POST";
request_headers["content-length"] = "12";
request_headers["content-type"] = "application/x-www-form-urlencoded";
TestQuicServerStreamDelegate delegate;
delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
delegate.StartHttpRequestToBackendAndWait(&request_headers, kUploadData);
quic::QuicBackendResponse* quic_response =
delegate.get_proxy_backend_stream()->GetBackendResponse();
EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
quic_response->response_type());
EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
EXPECT_EQ(kUploadData, quic_response->body());
}
TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendPostEmptyString) {
const char kUploadData[] = "";
spdy::SpdyHeaderBlock request_headers;
request_headers[":path"] = "/echo";
request_headers[":authority"] = "www.example.org";
request_headers[":version"] = "HTTP/2.0";
request_headers[":method"] = "POST";
request_headers["content-length"] = "0";
request_headers["content-type"] = "application/x-www-form-urlencoded";
TestQuicServerStreamDelegate delegate;
delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
delegate.StartHttpRequestToBackendAndWait(&request_headers, kUploadData);
quic::QuicBackendResponse* quic_response =
delegate.get_proxy_backend_stream()->GetBackendResponse();
EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
quic_response->response_type());
EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
EXPECT_EQ(kUploadData, quic_response->body());
}
TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendPostFile) {
std::string kUploadData;
base::FilePath upload_path = GetUploadFileTestPath();
ASSERT_TRUE(base::ReadFileToString(upload_path, &kUploadData));
spdy::SpdyHeaderBlock request_headers;
request_headers[":path"] = "/echo";
request_headers[":authority"] = "www.example.org";
request_headers[":version"] = "HTTP/2.0";
request_headers[":method"] = "POST";
request_headers["content-type"] = "application/x-www-form-urlencoded";
TestQuicServerStreamDelegate delegate;
delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
delegate.StartHttpRequestToBackendAndWait(&request_headers, kUploadData);
quic::QuicBackendResponse* quic_response =
delegate.get_proxy_backend_stream()->GetBackendResponse();
EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
quic_response->response_type());
EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
EXPECT_EQ(kUploadData, quic_response->body());
}
TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendResponse500) {
const char kUploadData[] = "bobsyeruncle";
spdy::SpdyHeaderBlock request_headers;
request_headers[":path"] = "/echo?status=500";
request_headers[":authority"] = "www.example.org";
request_headers[":version"] = "HTTP/2.0";
request_headers[":method"] = "POST";
TestQuicServerStreamDelegate delegate;
delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
delegate.StartHttpRequestToBackendAndWait(&request_headers, kUploadData);
quic::QuicBackendResponse* quic_response =
delegate.get_proxy_backend_stream()->GetBackendResponse();
EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
quic_response->response_type());
EXPECT_EQ(500, ParseHeaderStatusCode(quic_response->headers()));
}
TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendFail) {
const char kUploadData[] = "bobsyeruncle";
spdy::SpdyHeaderBlock request_headers;
request_headers[":path"] = "/echo";
request_headers[":authority"] = "www.example.org";
request_headers[":version"] = "HTTP/2.0";
request_headers[":method"] = "POST";
TestQuicServerStreamDelegate delegate;
delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_fail_.get());
delegate.StartHttpRequestToBackendAndWait(&request_headers, kUploadData);
quic::QuicBackendResponse* quic_response =
delegate.get_proxy_backend_stream()->GetBackendResponse();
EXPECT_EQ(quic::QuicBackendResponse::BACKEND_ERR_RESPONSE,
quic_response->response_type());
}
TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendOnRedirect) {
const std::string kRedirectTarget = backend_url_.append("/echo");
spdy::SpdyHeaderBlock request_headers;
request_headers[":path"] = std::string("/server-redirect?") + kRedirectTarget;
request_headers[":authority"] = "www.example.org";
request_headers[":version"] = "HTTP/2.0";
request_headers[":method"] = "GET";
TestQuicServerStreamDelegate delegate;
delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
delegate.StartHttpRequestToBackendAndWait(&request_headers, "");
quic::QuicBackendResponse* quic_response =
delegate.get_proxy_backend_stream()->GetBackendResponse();
EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
quic_response->response_type());
EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
}
// Ensure that the proxy rewrites the content-length when receiving a Gzipped
// response
TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendHandleGzip) {
const char kGzipData[] =
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!!";
uint64_t rawBodyLength = strlen(kGzipData);
spdy::SpdyHeaderBlock request_headers;
request_headers[":path"] = std::string("/gzip-body?") + kGzipData;
request_headers[":authority"] = "www.example.org";
request_headers[":version"] = "HTTP/2.0";
request_headers[":method"] = "GET";
TestQuicServerStreamDelegate delegate;
delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
delegate.StartHttpRequestToBackendAndWait(&request_headers, "");
quic::QuicBackendResponse* quic_response =
delegate.get_proxy_backend_stream()->GetBackendResponse();
EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
quic_response->response_type());
EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
EXPECT_EQ(kGzipData, quic_response->body());
spdy::SpdyHeaderBlock quic_response_headers =
quic_response->headers().Clone();
// Ensure that the content length is set to the raw body size (unencoded)
auto responseLength = quic_response_headers.find("content-length");
uint64_t response_header_content_length = 0;
if (responseLength != quic_response_headers.end()) {
base::StringToUint64(responseLength->second,
&response_header_content_length);
}
EXPECT_EQ(rawBodyLength, response_header_content_length);
// Ensure the content-encoding header is removed for the quic response
EXPECT_EQ(quic_response_headers.end(),
quic_response_headers.find("content-encoding"));
}
// Ensure cookies are not saved/updated at the proxy
TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendCookiesNotSaved) {
spdy::SpdyHeaderBlock request_headers;
request_headers[":authority"] = "www.example.org";
request_headers[":method"] = "GET";
{
TestQuicServerStreamDelegate delegate;
request_headers[":path"] =
"/set-cookie?CookieToNotSave=1&CookieToNotUpdate=1";
delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
delegate.StartHttpRequestToBackendAndWait(&request_headers, "");
quic::QuicBackendResponse* quic_response =
delegate.get_proxy_backend_stream()->GetBackendResponse();
EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
spdy::SpdyHeaderBlock quic_response_headers =
quic_response->headers().Clone();
EXPECT_TRUE(quic_response_headers.end() !=
quic_response_headers.find("set-cookie"));
auto cookie = quic_response_headers.find("set-cookie");
EXPECT_TRUE(cookie->second.find("CookieToNotSave=1") != std::string::npos);
EXPECT_TRUE(cookie->second.find("CookieToNotUpdate=1") !=
std::string::npos);
}
{
TestQuicServerStreamDelegate delegate;
request_headers[":path"] = "/echoheader?Cookie";
delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
delegate.StartHttpRequestToBackendAndWait(&request_headers, "");
quic::QuicBackendResponse* quic_response =
delegate.get_proxy_backend_stream()->GetBackendResponse();
EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
EXPECT_TRUE(quic_response->body().find("CookieToNotSave=1") ==
std::string::npos);
EXPECT_TRUE(quic_response->body().find("CookieToNotUpdate=1") ==
std::string::npos);
}
}
// Ensure hop-by-hop headers are removed from the request and response to the
// backend
TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendHopHeaders) {
spdy::SpdyHeaderBlock request_headers;
request_headers[":path"] = "/echoall";
request_headers[":authority"] = "www.example.org";
request_headers[":method"] = "GET";
std::set<std::string>::iterator it;
for (it = QuicHttpProxyBackendStream::kHopHeaders.begin();
it != QuicHttpProxyBackendStream::kHopHeaders.end(); ++it) {
request_headers[*it] = "SomeString";
}
TestQuicServerStreamDelegate delegate;
delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
delegate.StartHttpRequestToBackendAndWait(&request_headers, "");
const net::HttpRequestHeaders& actual_request_headers =
delegate.get_request_headers();
for (it = QuicHttpProxyBackendStream::kHopHeaders.begin();
it != QuicHttpProxyBackendStream::kHopHeaders.end(); ++it) {
EXPECT_FALSE(actual_request_headers.HasHeader(*it));
}
quic::QuicBackendResponse* quic_response =
delegate.get_proxy_backend_stream()->GetBackendResponse();
EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
spdy::SpdyHeaderBlock quic_response_headers =
quic_response->headers().Clone();
for (it = QuicHttpProxyBackendStream::kHopHeaders.begin();
it != QuicHttpProxyBackendStream::kHopHeaders.end(); ++it) {
EXPECT_EQ(quic_response_headers.end(), quic_response_headers.find(*it));
}
}
} // namespace test
} // namespace net