// Copyright 2015 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/test/embedded_test_server/default_handlers.h" #include #include #include #include #include #include #include #include "base/base64.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/functional/bind.h" #include "base/functional/callback_forward.h" #include "base/functional/callback_helpers.h" #include "base/hash/md5.h" #include "base/logging.h" #include "base/memory/weak_ptr.h" #include "base/path_service.h" #include "base/strings/escape.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/task/sequenced_task_runner.h" #include "base/task/single_thread_task_runner.h" #include "base/time/time.h" #include "base/unguessable_token.h" #include "net/base/host_port_pair.h" #include "net/base/url_util.h" #include "net/filter/filter_source_stream_test_util.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" namespace net::test_server { namespace { const char kDefaultRealm[] = "testrealm"; const char kDefaultPassword[] = "secret"; const char kEtag[] = "abc"; const char kLogoPath[] = "chrome/test/data/google/logo.gif"; // method: CONNECT // Responses with a BAD_REQUEST to any CONNECT requests. std::unique_ptr HandleDefaultConnect(const HttpRequest& request) { if (request.method != METHOD_CONNECT) return nullptr; auto http_response = std::make_unique(); http_response->set_code(HTTP_BAD_REQUEST); http_response->set_content( "Your client has issued a malformed or illegal request."); http_response->set_content_type("text/html"); return http_response; } // /cachetime // Returns a cacheable response. std::unique_ptr HandleCacheTime(const HttpRequest& request) { auto http_response = std::make_unique(); http_response->set_content("Cache: max-age=60"); http_response->set_content_type("text/html"); http_response->AddCustomHeader("Cache-Control", "max-age=60"); return http_response; } // /echoheader?HEADERS | /echoheadercache?HEADERS // Responds with the headers echoed in the message body. // echoheader does not cache the results, while echoheadercache does. std::unique_ptr HandleEchoHeader(const std::string& url, const std::string& cache_control, const HttpRequest& request) { if (!ShouldHandle(request, url)) return nullptr; auto http_response = std::make_unique(); GURL request_url = request.GetURL(); std::string vary; std::string content; RequestQuery headers = ParseQuery(request_url); for (const auto& header : headers) { std::string header_name = header.first; std::string header_value = "None"; if (request.headers.find(header_name) != request.headers.end()) header_value = request.headers.at(header_name); if (!vary.empty()) vary += ","; vary += header_name; if (!content.empty()) content += "\n"; content += header_value; } http_response->AddCustomHeader("Vary", vary); http_response->set_content(content); http_response->set_content_type("text/plain"); http_response->AddCustomHeader("Access-Control-Allow-Origin", "*"); http_response->AddCustomHeader("Cache-Control", cache_control); return http_response; } // /echo-cookie-with-status?status=### // Responds with the given status code and echos the cookies sent in the request std::unique_ptr HandleEchoCookieWithStatus( const std::string& url, const HttpRequest& request) { if (!ShouldHandle(request, url)) return nullptr; auto http_response = std::make_unique(); GURL request_url = request.GetURL(); RequestQuery query = ParseQuery(request_url); int status_code = 400; const auto given_status = query.find("status"); if (given_status != query.end() && !given_status->second.empty() && !base::StringToInt(given_status->second.front(), &status_code)) { status_code = 400; } http_response->set_code(static_cast(status_code)); const auto given_cookie = request.headers.find("Cookie"); std::string content = (given_cookie == request.headers.end()) ? "None" : given_cookie->second; http_response->set_content(content); http_response->set_content_type("text/plain"); return http_response; } // TODO(https://crbug.com/1138913): Remove when request handlers are // implementable in Android's embedded test server implementation std::unique_ptr HandleEchoCriticalHeader( const HttpRequest& request) { auto http_response = std::make_unique(); http_response->set_content_type("text/plain"); http_response->AddCustomHeader("Access-Control-Allow-Origin", "*"); http_response->AddCustomHeader("Accept-CH", "Sec-CH-UA-Platform"); http_response->AddCustomHeader("Critical-CH", "Sec-CH-UA-Platform"); http_response->set_content( request.headers.find("Sec-CH-UA-Mobile")->second + request.headers.find("Sec-CH-UA-Platform")->second); return http_response; } // /echo?status=STATUS // Responds with the request body as the response body and // a status code of STATUS. std::unique_ptr HandleEcho(const HttpRequest& request) { auto http_response = std::make_unique(); GURL request_url = request.GetURL(); if (request_url.has_query()) { RequestQuery query = ParseQuery(request_url); if (query.find("status") != query.end()) http_response->set_code(static_cast( std::atoi(query["status"].front().c_str()))); } http_response->set_content_type("text/html"); if (request.method != METHOD_POST && request.method != METHOD_PUT) http_response->set_content("Echo"); else http_response->set_content(request.content); return http_response; } // /echotitle // Responds with the request body as the title. std::unique_ptr HandleEchoTitle(const HttpRequest& request) { auto http_response = std::make_unique(); http_response->set_content_type("text/html"); http_response->set_content("" + request.content + ""); return http_response; } // /echoall?QUERY // Responds with the list of QUERY and the request headers. // // Alternative form: // /echoall/nocache?QUERY prevents caching of the response. std::unique_ptr HandleEchoAll(const HttpRequest& request) { auto http_response = std::make_unique(); std::string body = "EmbeddedTestServer - EchoAll" "" "

Request Body:

";

  if (request.has_content) {
    std::vector query_list = base::SplitString(
        request.content, "&", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
    for (const auto& query : query_list)
      body += query + "\n";
  }

  body +=
      "
" "

Request Headers:

" +
      request.all_headers + "
" + "

Response nonce:

" +
      base::UnguessableToken::Create().ToString() + "
"; http_response->set_content_type("text/html"); http_response->set_content(body); if (base::EndsWith(request.GetURL().path_piece(), "/nocache", base::CompareCase::SENSITIVE)) { http_response->AddCustomHeader("Cache-Control", "no-cache, no-store, must-revalidate"); } return http_response; } // /echo-raw // Returns the query string as the raw response (no HTTP headers). std::unique_ptr HandleEchoRaw(const HttpRequest& request) { return std::make_unique("", request.GetURL().query()); } // /set-cookie?COOKIES // Sets response cookies to be COOKIES. std::unique_ptr HandleSetCookie(const HttpRequest& request) { auto http_response = std::make_unique(); http_response->set_content_type("text/html"); std::string content; GURL request_url = request.GetURL(); if (request_url.has_query()) { std::vector cookies = base::SplitString( request_url.query(), "&", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); for (const auto& cookie : cookies) { http_response->AddCustomHeader("Set-Cookie", cookie); content += cookie; } } http_response->set_content(content); return http_response; } // /set-invalid-cookie // Sets invalid response cookies "\x01" (chosen via fuzzer to not be a parsable // cookie). std::unique_ptr HandleSetInvalidCookie( const HttpRequest& request) { auto http_response = std::make_unique(); http_response->set_content_type("text/html"); std::string content; GURL request_url = request.GetURL(); http_response->AddCustomHeader("Set-Cookie", "\x01"); http_response->set_content("TEST"); return http_response; } // /expect-and-set-cookie?expect=EXPECTED&set=SET&data=DATA // Verifies that the request cookies match EXPECTED and then returns cookies // that match SET and a content that matches DATA. std::unique_ptr HandleExpectAndSetCookie( const HttpRequest& request) { std::vector received_cookies; if (request.headers.find("Cookie") != request.headers.end()) { received_cookies = base::SplitString(request.headers.at("Cookie"), ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); } bool got_all_expected = true; GURL request_url = request.GetURL(); RequestQuery query_list = ParseQuery(request_url); if (query_list.find("expect") != query_list.end()) { for (const auto& expected_cookie : query_list.at("expect")) { bool found = false; for (const auto& received_cookie : received_cookies) { if (expected_cookie == received_cookie) found = true; } got_all_expected &= found; } } auto http_response = std::make_unique(); http_response->set_content_type("text/html"); if (got_all_expected) { for (const auto& cookie : query_list.at("set")) { http_response->AddCustomHeader( "Set-Cookie", base::UnescapeBinaryURLComponent( cookie, base::UnescapeRule::REPLACE_PLUS_WITH_SPACE)); } } std::string content; if (query_list.find("data") != query_list.end()) { for (const auto& item : query_list.at("data")) content += item; } http_response->set_content(content); return http_response; } // An internal utility to extract HTTP Headers from a URL in the format of // "/url&KEY1: VALUE&KEY2: VALUE2". Returns a header key to header value map. std::map ExtractHeadersFromQuery(const GURL& url) { std::map key_to_value; if (url.has_query()) { RequestQuery headers = ParseQuery(url); for (const auto& header : headers) { size_t delimiter = header.first.find(": "); if (delimiter == std::string::npos) { continue; } std::string key = header.first.substr(0, delimiter); std::string value = header.first.substr(delimiter + 2); key_to_value.emplace(key, value); } } return key_to_value; } // /set-header?HEADERS // Returns a response with HEADERS set as the response headers, and also set as // the response content. // // Example: // /set-header?Content-Security-Policy: sandbox&Referer-Policy: origin std::unique_ptr HandleSetHeader(const HttpRequest& request) { std::string content; GURL request_url = request.GetURL(); auto http_response = std::make_unique(); http_response->set_content_type("text/html"); auto headers = ExtractHeadersFromQuery(request_url); for (const auto& [key, value] : headers) { http_response->AddCustomHeader(key, value); content += key + ": " + value; } http_response->set_content(content); return http_response; } // /set-header-with-file/FILE_PATH?HEADERS // Returns a response with context read from FILE_PATH as the response content, // and HEADERS as the response header. Unlike /set-header?HEADERS, which only // serves a response with HEADERS as response header and also HEADERS as its // content. // // FILE_PATH points to the static test file. For example, a query like // /set-header-with-file/content/test/data/title1.html will returns the content // of the file at content/test/data/title1.html. // HEADERS is composed of a list of "key: value" pairs. Note that unlike how a // file is normally served by `HandleFileRequest()`, its static mock headers // from the other file FILE_PATH.mock-http-headers will NOT be used here. // // Example: // /set-header-with-file/content/test/data/title1.html?Referer-Policy: origin std::unique_ptr HandleSetHeaderWithFile( const std::string& prefix, const HttpRequest& request) { if (!ShouldHandle(request, prefix)) { return nullptr; } GURL request_url = request.GetURL(); auto http_response = std::make_unique(); base::FilePath server_root; base::PathService::Get(base::DIR_SOURCE_ROOT, &server_root); base::FilePath file_path = server_root.AppendASCII(request_url.path().substr(prefix.size() + 1)); std::string file_content; CHECK(base::ReadFileToString(file_path, &file_content)); http_response->set_content(file_content); http_response->set_content_type(GetContentType(file_path)); auto headers = ExtractHeadersFromQuery(request_url); for (const auto& [key, value] : headers) { http_response->AddCustomHeader(key, value); } http_response->set_code(HTTP_OK); return http_response; } // /iframe?URL // Returns a page that iframes the specified URL. std::unique_ptr HandleIframe(const HttpRequest& request) { GURL request_url = request.GetURL(); auto http_response = std::make_unique(); http_response->set_content_type("text/html"); GURL iframe_url("about:blank"); if (request_url.has_query()) { iframe_url = GURL(base::UnescapeBinaryURLComponent(request_url.query())); } http_response->set_content(base::StringPrintf( "