// 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/platform/api/quic_test.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 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_;
  base::test::ScopedTaskEnvironment scoped_task_environment;
  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:
  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