| // Copyright 2015 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/test/embedded_test_server/request_handler_util.h" | |
| #include <stdlib.h> | |
| #include <ctime> | |
| #include <sstream> | |
| #include <utility> | |
| #include "base/base64.h" | |
| #include "base/files/file_util.h" | |
| #include "base/format_macros.h" | |
| #include "base/strings/string_util.h" | |
| #include "base/strings/stringprintf.h" | |
| #include "base/strings/utf_string_conversions.h" | |
| #include "base/threading/thread_restrictions.h" | |
| #include "build/build_config.h" | |
| #include "net/base/escape.h" | |
| #include "net/base/url_util.h" | |
| #include "net/http/http_byte_range.h" | |
| #include "net/http/http_util.h" | |
| #include "net/test/embedded_test_server/http_request.h" | |
| #include "starboard/types.h" | |
| #include "url/gurl.h" | |
| namespace net { | |
| namespace test_server { | |
| const char kMockHttpHeadersExtension[] = "mock-http-headers"; | |
| std::string GetContentType(const base::FilePath& path) { | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".crx"))) | |
| return "application/x-chrome-extension"; | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".css"))) | |
| return "text/css"; | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".exe"))) | |
| return "application/octet-stream"; | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".gif"))) | |
| return "image/gif"; | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".gzip")) || | |
| path.MatchesExtension(FILE_PATH_LITERAL(".gz"))) { | |
| return "application/x-gzip"; | |
| } | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".jpeg")) || | |
| path.MatchesExtension(FILE_PATH_LITERAL(".jpg"))) { | |
| return "image/jpeg"; | |
| } | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".js"))) | |
| return "application/javascript"; | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".json"))) | |
| return "application/json"; | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".pdf"))) | |
| return "application/pdf"; | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".txt"))) | |
| return "text/plain"; | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".wav"))) | |
| return "audio/wav"; | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".xml"))) | |
| return "text/xml"; | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".mhtml"))) | |
| return "multipart/related"; | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".mht"))) | |
| return "message/rfc822"; | |
| if (path.MatchesExtension(FILE_PATH_LITERAL(".html")) || | |
| path.MatchesExtension(FILE_PATH_LITERAL(".htm"))) { | |
| return "text/html"; | |
| } | |
| return ""; | |
| } | |
| bool ShouldHandle(const HttpRequest& request, const std::string& path_prefix) { | |
| GURL url = request.GetURL(); | |
| return url.path() == path_prefix || | |
| base::StartsWith(url.path(), path_prefix + "/", | |
| base::CompareCase::SENSITIVE); | |
| } | |
| std::unique_ptr<HttpResponse> HandlePrefixedRequest( | |
| const std::string& prefix, | |
| const EmbeddedTestServer::HandleRequestCallback& handler, | |
| const HttpRequest& request) { | |
| if (ShouldHandle(request, prefix)) | |
| return handler.Run(request); | |
| return nullptr; | |
| } | |
| RequestQuery ParseQuery(const GURL& url) { | |
| RequestQuery queries; | |
| for (QueryIterator it(url); !it.IsAtEnd(); it.Advance()) { | |
| std::string unescaped_query; | |
| UnescapeBinaryURLComponent( | |
| it.GetKey(), UnescapeRule::REPLACE_PLUS_WITH_SPACE, &unescaped_query); | |
| queries[unescaped_query].push_back(it.GetUnescapedValue()); | |
| } | |
| return queries; | |
| } | |
| void GetFilePathWithReplacements(const std::string& original_file_path, | |
| const base::StringPairs& text_to_replace, | |
| std::string* replacement_path) { | |
| std::string new_file_path = original_file_path; | |
| for (const auto& replacement : text_to_replace) { | |
| const std::string& old_text = replacement.first; | |
| const std::string& new_text = replacement.second; | |
| std::string base64_old; | |
| std::string base64_new; | |
| base::Base64Encode(old_text, &base64_old); | |
| base::Base64Encode(new_text, &base64_new); | |
| if (new_file_path == original_file_path) | |
| new_file_path += "?"; | |
| else | |
| new_file_path += "&"; | |
| new_file_path += "replace_text="; | |
| new_file_path += base64_old; | |
| new_file_path += ":"; | |
| new_file_path += base64_new; | |
| } | |
| *replacement_path = new_file_path; | |
| } | |
| // Handles |request| by serving a file from under |server_root|. | |
| std::unique_ptr<HttpResponse> HandleFileRequest( | |
| const base::FilePath& server_root, | |
| const HttpRequest& request) { | |
| // This is a test-only server. Ignore I/O thread restrictions. | |
| // TODO(svaldez): Figure out why thread is I/O restricted in the first place. | |
| base::ScopedAllowBlockingForTesting allow_blocking; | |
| // A proxy request will have an absolute path. Simulate the proxy by stripping | |
| // the scheme, host, and port. | |
| GURL request_url = request.GetURL(); | |
| std::string relative_path(request_url.path()); | |
| std::string post_prefix("/post/"); | |
| if (base::StartsWith(relative_path, post_prefix, | |
| base::CompareCase::SENSITIVE)) { | |
| if (request.method != METHOD_POST) | |
| return nullptr; | |
| relative_path = relative_path.substr(post_prefix.size() - 1); | |
| } | |
| RequestQuery query = ParseQuery(request_url); | |
| std::unique_ptr<BasicHttpResponse> failed_response(new BasicHttpResponse); | |
| failed_response->set_code(HTTP_NOT_FOUND); | |
| if (query.find("expected_body") != query.end()) { | |
| if (request.content.find(query["expected_body"].front()) == | |
| std::string::npos) { | |
| return std::move(failed_response); | |
| } | |
| } | |
| if (query.find("expected_headers") != query.end()) { | |
| for (const auto& header : query["expected_headers"]) { | |
| if (header.find(":") == std::string::npos) | |
| return std::move(failed_response); | |
| std::string key = header.substr(0, header.find(":")); | |
| std::string value = header.substr(header.find(":") + 1); | |
| if (request.headers.find(key) == request.headers.end() || | |
| request.headers.at(key) != value) { | |
| return std::move(failed_response); | |
| } | |
| } | |
| } | |
| // Trim the first byte ('/'). | |
| DCHECK(base::StartsWith(relative_path, "/", base::CompareCase::SENSITIVE)); | |
| std::string request_path = relative_path.substr(1); | |
| base::FilePath file_path(server_root.AppendASCII(request_path)); | |
| std::string file_contents; | |
| if (!base::ReadFileToString(file_path, &file_contents)) { | |
| file_path = file_path.AppendASCII("index.html"); | |
| if (!base::ReadFileToString(file_path, &file_contents)) | |
| return nullptr; | |
| } | |
| if (request.method == METHOD_HEAD) | |
| file_contents = ""; | |
| if (query.find("replace_text") != query.end()) { | |
| for (const auto& replacement : query["replace_text"]) { | |
| if (replacement.find(":") == std::string::npos) | |
| return std::move(failed_response); | |
| std::string find; | |
| std::string with; | |
| base::Base64Decode(replacement.substr(0, replacement.find(":")), &find); | |
| base::Base64Decode(replacement.substr(replacement.find(":") + 1), &with); | |
| base::ReplaceSubstringsAfterOffset(&file_contents, 0, find, with); | |
| } | |
| } | |
| base::FilePath::StringPieceType mock_headers_extension; | |
| #if defined(OS_WIN) | |
| base::string16 temp = base::ASCIIToUTF16(kMockHttpHeadersExtension); | |
| mock_headers_extension = temp; | |
| #else | |
| mock_headers_extension = kMockHttpHeadersExtension; | |
| #endif | |
| base::FilePath headers_path(file_path.AddExtension(mock_headers_extension)); | |
| if (base::PathExists(headers_path)) { | |
| std::string headers_contents; | |
| if (!base::ReadFileToString(headers_path, &headers_contents)) | |
| return nullptr; | |
| return std::make_unique<RawHttpResponse>(headers_contents, file_contents); | |
| } | |
| std::unique_ptr<BasicHttpResponse> http_response(new BasicHttpResponse); | |
| http_response->set_code(HTTP_OK); | |
| if (request.headers.find("Range") != request.headers.end()) { | |
| std::vector<HttpByteRange> ranges; | |
| if (HttpUtil::ParseRangeHeader(request.headers.at("Range"), &ranges) && | |
| ranges.size() == 1) { | |
| ranges[0].ComputeBounds(file_contents.size()); | |
| size_t start = ranges[0].first_byte_position(); | |
| size_t end = ranges[0].last_byte_position(); | |
| http_response->set_code(HTTP_PARTIAL_CONTENT); | |
| http_response->AddCustomHeader( | |
| "Content-Range", | |
| base::StringPrintf("bytes %" PRIuS "-%" PRIuS "/%" PRIuS, start, end, | |
| file_contents.size())); | |
| file_contents = file_contents.substr(start, end - start + 1); | |
| } | |
| } | |
| http_response->set_content_type(GetContentType(file_path)); | |
| http_response->AddCustomHeader("Accept-Ranges", "bytes"); | |
| http_response->AddCustomHeader("ETag", "'" + file_path.MaybeAsASCII() + "'"); | |
| http_response->set_content(file_contents); | |
| return std::move(http_response); | |
| } | |
| } // namespace test_server | |
| } // namespace net |