Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(162)

Unified Diff: net/test/embedded_test_server/request_helpers.cc

Issue 1376593007: SSL in EmbeddedTestServer (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Cleaning up request handler. Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: net/test/embedded_test_server/request_helpers.cc
diff --git a/net/test/embedded_test_server/request_helpers.cc b/net/test/embedded_test_server/request_helpers.cc
new file mode 100644
index 0000000000000000000000000000000000000000..36da33195b955694afc4bf8a72dde035a0a49257
--- /dev/null
+++ b/net/test/embedded_test_server/request_helpers.cc
@@ -0,0 +1,924 @@
+// 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_helpers.h"
mmenke 2015/10/15 20:16:15 default_request_handlers?
svaldez 2015/10/15 21:04:32 Since the main purpose of this file when used by o
mmenke 2015/10/15 21:13:54 If you want shareable helper methods for consumers
+
+#include <stdlib.h>
+#include <ctime>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "base/base64.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/format_macros.h"
+#include "base/md5.h"
+#include "base/path_service.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "net/base/escape.h"
+#include "net/base/url_util.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"
+
+#define NOT_HANDLED nullptr
mmenke 2015/10/15 19:38:18 This seems kinda weird and not really needed.
svaldez 2015/10/15 21:04:33 Done.
+
+const net::UnescapeRule::Type kUnescapeAll =
+ net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS |
+ net::UnescapeRule::SPOOFING_AND_CONTROL_CHARS |
+ net::UnescapeRule::REPLACE_PLUS_WITH_SPACE;
mmenke 2015/10/15 19:38:17 Anonymous namespace, and move this down so you don
svaldez 2015/10/15 21:04:33 Done.
+
+const std::string kDefaultRealm = "testrealm";
+const std::string kDefaultPassword = "password";
mmenke 2015/10/15 19:38:17 const char[] (Only POD types can be globals, and S
svaldez 2015/10/15 21:04:34 Done.
+
+namespace net {
+namespace test_server {
+
+void CustomHttpResponse::SendResponse(SendBytesCallback send,
+ SendCompleteCallback done) {
+ std::string response;
+ if (headers_.size() != 0 || contents_.size() != 0)
+ response = headers_ + "\r\n" + contents_;
+ send.Run(response, done);
+}
+
+bool ShouldHandle(const HttpRequest& request, std::string url) {
mmenke 2015/10/15 19:38:17 May want to build this into EmbeddedTestServer, as
mmenke 2015/10/15 19:38:18 |url| -> |path_prefix|
svaldez 2015/10/15 21:04:33 Done.
svaldez 2015/10/15 21:04:33 Done.
+ if (request.relative_url.compare(0, url.size(), url) != 0)
+ return false;
+ std::string endings("/?;#");
mmenke 2015/10/15 19:38:17 Not a fan of manually parsing URLs like this. I s
svaldez 2015/10/15 21:04:33 Done.
+ return request.relative_url.size() == url.size() ||
+ endings.find(request.relative_url.at(url.size())) != std::string::npos;
+}
+
+RequestQuery ParseQuery(const GURL& url) {
mmenke 2015/10/15 19:38:18 All this stuff that isn't exposed in the header sh
svaldez 2015/10/15 21:04:34 Done.
+ RequestQuery queries;
+
+ for (QueryIterator it(url); !it.IsAtEnd(); it.Advance()) {
+ queries[net::UnescapeURLComponent(it.GetKey(), kUnescapeAll)].push_back(
+ it.GetUnescapedValue());
+ }
+ return queries;
+}
+
+void GetFilePathWithReplacements(const std::string& original_file_path,
mmenke 2015/10/15 19:38:18 This needs documentation.
svaldez 2015/10/15 21:04:33 In the header.
+ const base::StringPairs& text_to_replace,
+ std::string* replacement_path) {
mmenke 2015/10/15 19:38:17 Should just return a string (It's a little less ef
svaldez 2015/10/15 21:04:32 Maintaining compatibility with the STS version of
+ std::string new_file_path = original_file_path;
+ bool first_query_parameter = true;
+ for (base::StringPairs::const_iterator it = text_to_replace.begin();
+ it != text_to_replace.end(); ++it) {
mmenke 2015/10/15 19:38:18 Can you use a range loop here?
svaldez 2015/10/15 21:04:33 Done.
+ const std::string& old_text = it->first;
+ const std::string& new_text = it->second;
+ std::string base64_old;
+ std::string base64_new;
+ base::Base64Encode(old_text, &base64_old);
+ base::Base64Encode(new_text, &base64_new);
+ if (first_query_parameter) {
+ new_file_path += "?";
+ first_query_parameter = false;
+ } 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;
+}
+
+GURL ToGURL(const HttpRequest& request) {
+ return GURL("http://localhost" + request.relative_url);
mmenke 2015/10/15 19:38:17 Suggest making this a member of HttpRequest (And u
svaldez 2015/10/15 21:04:32 Done.
+}
+
+// Handles |request| by serving a file from under |server_root|.
+scoped_ptr<HttpResponse> HandleFileRequest(const base::FilePath& server_root,
+ const HttpRequest& request) {
+ // This is a test-only server. Ignore I/O thread restrictions.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
mmenke 2015/10/15 19:38:18 Is this needed? We're running on our private thre
svaldez 2015/10/15 21:04:33 Still required since the restriction is only on th
+
+ std::string relative_url(request.relative_url);
+
+ // A proxy request will have an absolute path. Simulate the proxy by stripping
+ // the scheme, host, and port.
+ GURL request_gurl = ToGURL(request);
+ relative_url = request_gurl.path();
+
+ std::string files_prefix("/files/");
mmenke 2015/10/15 19:38:17 I thought you said you weren't doing this?
svaldez 2015/10/15 21:04:33 Yeah, I was using it to find any remaining old-sty
+ if (relative_url.compare(0, files_prefix.size(), files_prefix) == 0) {
+ LOG(ERROR) << "Using old-style /files/ for: " << relative_url;
+ relative_url = relative_url.substr(files_prefix.size() - 1);
+ }
+
+ std::string post_prefix("/post/");
+ if (relative_url.compare(0, post_prefix.size(), post_prefix) == 0) {
+ relative_url = relative_url.substr(post_prefix.size() - 1);
+ if (request.method != METHOD_POST)
+ return NOT_HANDLED;
+ }
+
+ RequestQuery query = ParseQuery(request_gurl);
+
+ if (query.find("expected_body") != query.end()) {
+ if (request.content.find(query["expected_body"].front()) ==
mmenke 2015/10/15 19:38:18 base::StartsWithASCII? I don't think I've ever se
svaldez 2015/10/15 21:04:33 Is there a version for "containsASCII"?
mmenke 2015/10/15 21:13:54 Oops...That comment was meant to go below the "/po
+ std::string::npos) {
+ scoped_ptr<BasicHttpResponse> not_found_response(new BasicHttpResponse);
+ not_found_response->set_code(HTTP_NOT_FOUND);
+ return not_found_response.Pass();
+ }
+ }
+
+ if (query.find("expected_headers") != query.end()) {
+ std::vector<std::string> headers = query["expected_headers"];
+ for (std::vector<std::string>::iterator it = headers.begin();
+ it != headers.end(); ++it) {
mmenke 2015/10/15 19:38:17 Range loop? (And can then just inline query["expe
svaldez 2015/10/15 21:04:33 Done.
+ std::string key = it->substr(0, it->find(":"));
+ std::string value = it->substr(it->find(":") + 1);
mmenke 2015/10/15 19:38:17 Should probably just explicitly fail when there is
svaldez 2015/10/15 21:04:33 Done.
+ if (request.headers.find(key) == request.headers.end() ||
+ request.headers.at(key) != value) {
+ scoped_ptr<BasicHttpResponse> not_found_response(new BasicHttpResponse);
+ not_found_response->set_code(HTTP_NOT_FOUND);
+ return not_found_response.Pass();
+ }
+ }
+ }
+
+ // Trim the first byte ('/').
+ std::string request_path = relative_url.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 NOT_HANDLED;
mmenke 2015/10/15 19:38:17 Should we return an error if there was a files/ or
svaldez 2015/10/15 21:04:33 Could be that there is another File handler that m
mmenke 2015/10/15 21:13:54 But there isn't...files/ was removed anyways, now,
+ }
+
+ if (query.find("replace_text") != query.end()) {
+ std::vector<std::string> replacements = query["replace_text"];
+ for (std::vector<std::string>::iterator it = replacements.begin();
+ it != replacements.end(); ++it) {
mmenke 2015/10/15 19:38:17 Range loop
svaldez 2015/10/15 21:04:33 Done.
+ std::string find, with;
mmenke 2015/10/15 19:38:18 Only declare one variable per line.
svaldez 2015/10/15 21:04:33 Done.
+ base::Base64Decode(it->substr(0, it->find(":")), &find);
+ base::Base64Decode(it->substr(it->find(":") + 1), &with);
+ base::ReplaceSubstringsAfterOffset(&file_contents, 0, find, with);
+ }
+ }
+
+ base::FilePath headers_path(
+ file_path.AddExtension(FILE_PATH_LITERAL("mock-http-headers")));
+
+ if (base::PathExists(headers_path)) {
+ std::string headers_contents;
+
+ if (!base::ReadFileToString(headers_path, &headers_contents))
+ return NOT_HANDLED;
mmenke 2015/10/15 19:38:17 Should we return an error in this case?
svaldez 2015/10/15 21:04:32 The way its currently implemented in STS only retu
+
+ if (request.method == METHOD_HEAD)
+ file_contents = "";
mmenke 2015/10/15 19:38:17 Should we be doing this regardless of the presence
svaldez 2015/10/15 21:04:33 Done.
+
+ scoped_ptr<CustomHttpResponse> http_response(
+ new CustomHttpResponse(headers_contents, file_contents));
+ return http_response.Pass();
+ }
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_OK);
+
+ if (request.headers.find("Range") != request.headers.end()) {
+ std::string range_header = request.headers.at("Range");
+ if (range_header.compare(0, 6, "bytes=") == 0) {
mmenke 2015/10/15 19:38:18 HttpUtil::ParseRangeHeader?
svaldez 2015/10/15 21:04:33 Done.
+ range_header = range_header.substr(6);
+
+ size_t start = 0;
+ size_t end = file_contents.size();
+ size_t delim = range_header.find("-");
+
+ if (delim == std::string::npos) {
+ start = std::atoi(range_header.c_str());
+ } else if (delim == range_header.size() - 1) {
+ start = std::atoi(range_header.substr(0, delim).c_str());
+ } else if (delim == 0) {
+ start = end - std::atoi(range_header.substr(1).c_str());
+ } else {
+ start = std::atoi(range_header.substr(0, delim).c_str());
+ end = std::atoi(range_header.substr(delim + 1).c_str());
+ }
+
+ http_response->set_code(HTTP_PARTIAL_CONTENT);
+ http_response->AddCustomHeader(
+ "Content-Range",
+ base::StringPrintf("bytes %" PRIuS "-%" PRIuS "/%" PRIuS, start,
+ end - 1, file_contents.size()));
+
+ file_contents = file_contents.substr(start, end - start);
+ }
+ }
+
+ if (file_path.MatchesExtension(FILE_PATH_LITERAL(".crx")))
+ http_response->set_content_type("application/x-chrome-extension");
+ else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".exe")))
+ http_response->set_content_type("application/octet-stream");
+ else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".gif")))
+ http_response->set_content_type("image/gif");
+ else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".jpeg")) ||
+ file_path.MatchesExtension(FILE_PATH_LITERAL(".jpg")))
+ http_response->set_content_type("image/jpeg");
+ else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".js")))
+ http_response->set_content_type("application/javascript");
+ else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".json")))
+ http_response->set_content_type("application/json");
+ else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".pdf")))
+ http_response->set_content_type("application/pdf");
+ else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".txt")))
+ http_response->set_content_type("text/plain");
+ else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".wav")))
+ http_response->set_content_type("audio/wav");
+ else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".xml")))
+ http_response->set_content_type("text/xml");
+ else if (file_path.MatchesExtension(FILE_PATH_LITERAL(".html")) ||
+ file_path.MatchesExtension(FILE_PATH_LITERAL(".htm")))
+ http_response->set_content_type("text/html");
mmenke 2015/10/15 19:38:17 Think all this should be in a separate method.
svaldez 2015/10/15 21:04:33 Done.
+
+ http_response->AddCustomHeader("Accept-Ranges", "bytes");
+ http_response->AddCustomHeader("ETag", "'" + file_path.MaybeAsASCII() + "'");
+ http_response->set_content(file_contents);
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleRedirectConnect(const HttpRequest& request) {
+ if (request.headers.find("Host") == request.headers.end() ||
+ request.headers.at("Host") != "www.redirect.com" ||
+ request.method != METHOD_CONNECT)
+ return NOT_HANDLED;
mmenke 2015/10/15 19:38:18 nit: Use braces
svaldez 2015/10/15 21:04:33 Done.
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_FOUND);
+ http_response->AddCustomHeader("Location",
+ "http://www.destination.com/foo.js");
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleServerAuthConnect(const HttpRequest& request) {
+ if (request.headers.find("Host") == request.headers.end() ||
+ request.headers.at("Host") != "www.server-auth.com" ||
+ request.method != METHOD_CONNECT)
+ return NOT_HANDLED;
mmenke 2015/10/15 19:38:18 nit: Braces
svaldez 2015/10/15 21:04:33 Done.
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_UNAUTHORIZED);
+ http_response->AddCustomHeader("WWW-Authenticate",
+ "Basic realm=\"WallyWorld\"");
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleDefaultConnect(const HttpRequest& request) {
+ if (request.method != METHOD_CONNECT)
+ return NOT_HANDLED;
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ 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.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleCacheControl(std::string url,
mmenke 2015/10/15 19:38:17 All of these should be const std::string &
svaldez 2015/10/15 21:04:33 Acknowledged.
+ std::string value,
mmenke 2015/10/15 19:38:18 Should use a clearer name than "value"
svaldez 2015/10/15 21:04:33 Acknowledged.
+ const HttpRequest& request) {
+ if (!ShouldHandle(request, url))
+ return NOT_HANDLED;
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_OK);
+ std::stringstream time;
+ time << std::time(0);
mmenke 2015/10/15 19:38:17 Suggest using base::Time
svaldez 2015/10/15 21:04:34 Acknowledged.
+ http_response->set_content("<html><head><title>" + time.str() +
+ "</title></head></html>");
mmenke 2015/10/15 19:38:17 Hrm...I wonder about the utility of all of these -
svaldez 2015/10/15 21:04:33 Acknowledged.
+ http_response->set_content_type("text/html");
+ http_response->AddCustomHeader("Cache-Control", value);
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleCacheExpires(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/cache/expires"))
+ return NOT_HANDLED;
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_OK);
+ std::stringstream time;
+ time << std::time(0);
+ http_response->set_content("<html><head><title>" + time.str() +
+ "</title></head></html>");
+ http_response->set_content_type("text/html");
+ http_response->AddCustomHeader("Expires", "Thu, 1 Jan 2099 00:00:00 GMT");
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleEchoHeader(std::string url,
+ std::string value,
+ const HttpRequest& request) {
+ if (!ShouldHandle(request, url))
+ return NOT_HANDLED;
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_OK);
+
+ GURL request_gurl = ToGURL(request);
+ if (request_gurl.has_query()) {
+ std::string header_name = request_gurl.query();
+ http_response->AddCustomHeader("Vary", header_name);
+ if (request.headers.find(header_name) != request.headers.end())
+ http_response->set_content(request.headers.at(header_name));
+ else
+ http_response->set_content("None");
+ }
+
+ http_response->set_content_type("text/plain");
+ http_response->AddCustomHeader("Cache-Control", value);
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleEcho(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/echo"))
mmenke 2015/10/15 19:38:17 Think all of the ones based on path should use a w
svaldez 2015/10/15 21:04:33 Done.
+ return NOT_HANDLED;
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_OK);
+
+ GURL request_gurl = ToGURL(request);
+ if (request_gurl.has_query()) {
+ RequestQuery query = ParseQuery(request_gurl);
+ if (query.find("status") != query.end())
+ http_response->set_code(static_cast<HttpStatusCode>(
+ 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.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleEchoTitle(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/echotitle"))
+ return NOT_HANDLED;
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_OK);
+ http_response->set_content_type("text/html");
+ http_response->set_content("<html><head><title>" + request.content +
+ "</title></head></html>");
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleEchoAll(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/echoall"))
+ return NOT_HANDLED;
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_OK);
+
+ std::string body;
+ body +=
+ "<html><head><style>"
+ "pre { border: 1px solid black; margin: 5px; padding: 5px }"
+ "</style></head><body>"
+ "<div style=\"float: right\">"
+ "<a href=\"/echo\">back to referring page</a></div>"
+ "<h1>Request Body:</h1><pre>";
+
+ if (request.has_content) {
+ std::vector<std::string> qs = base::SplitString(
+ request.content, "&", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+ for (size_t i = 0; i < qs.size(); ++i) {
+ body += qs[i] + "\n";
+ }
+ }
+
+ body +=
+ "</pre>"
+ "<h1>Request Headers:</h1><pre>" +
+ request.all_headers +
+ "</pre>"
+ "</body></html>";
+
+ http_response->set_content_type("text/html");
+ http_response->set_content(body);
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleSetCookie(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/set-cookie"))
+ return NOT_HANDLED;
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_OK);
+ http_response->set_content_type("text/html");
+ std::string content;
+ GURL request_gurl = ToGURL(request);
+ if (request_gurl.has_query()) {
+ std::vector<std::string> cookies = base::SplitString(
+ request_gurl.query(), "&", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+ for (size_t i = 0; i < cookies.size(); ++i) {
+ http_response->AddCustomHeader("Set-Cookie", cookies[i]);
+ content += cookies[i];
+ }
+ }
+
+ http_response->set_content(content);
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleSetManyCookies(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/set-many-cookies"))
+ return NOT_HANDLED;
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_OK);
+ http_response->set_content_type("text/html");
+ std::string content;
+
+ GURL request_gurl = ToGURL(request);
+ size_t num = 0;
+ if (request_gurl.has_query()) {
+ num = std::atoi(request_gurl.query().c_str());
+ }
+
+ for (size_t i = 0; i < num; ++i) {
+ http_response->AddCustomHeader("Set-Cookie", "a=");
+ }
+
+ http_response->set_content(
+ base::StringPrintf("%" PRIuS " cookies were sent", num));
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleExpectAndSetCookie(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/expect-and-set-cookie"))
+ return NOT_HANDLED;
+
+ std::vector<std::string> sentCookies;
+ if (request.headers.find("Cookie") != request.headers.end()) {
+ std::vector<std::string> cookies =
+ base::SplitString(request.headers.at("Cookie"), ";",
+ base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+ for (size_t i = 0; i < cookies.size(); ++i) {
+ sentCookies.push_back(cookies[i]);
+ }
+ }
+ bool gotExpected = true;
+
+ GURL request_gurl = ToGURL(request);
+ RequestQuery qs = ParseQuery(request_gurl);
+ if (qs.find("expect") != qs.end()) {
+ std::vector<std::string> expected = qs.at("expect");
+ for (size_t i = 0; i < expected.size(); ++i) {
+ bool found = false;
+ for (size_t j = 0; j < sentCookies.size(); ++j) {
+ if (expected[i] == sentCookies[j])
+ found = true;
+ }
+ gotExpected &= found;
+ }
+ }
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_OK);
+ http_response->set_content_type("text/html");
+ if (gotExpected) {
+ std::vector<std::string> setCookies = qs.at("set");
+ for (size_t i = 0; i < setCookies.size(); ++i) {
+ http_response->AddCustomHeader(
+ "Set-Cookie", net::UnescapeURLComponent(setCookies[i], kUnescapeAll));
+ }
+ }
+
+ std::string content;
+ if (qs.find("data") != qs.end()) {
+ std::vector<std::string> data = qs.at("data");
+ for (size_t i = 0; i < data.size(); ++i) {
+ content += data[i];
+ }
+ }
+
+ http_response->set_content(content);
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleSetHeader(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/set-header"))
+ return NOT_HANDLED;
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_OK);
+ http_response->set_content_type("text/html");
+
+ std::string content;
+
+ GURL request_gurl = ToGURL(request);
+ if (request_gurl.has_query()) {
+ RequestQuery headers = ParseQuery(request_gurl);
+ for (RequestQuery::iterator it = headers.begin(); it != headers.end();
+ ++it) {
+ std::string header = it->first;
+ std::string key = header.substr(0, header.find(": "));
+ std::string value = header.substr(header.find(": ") + 2);
+ http_response->AddCustomHeader(key, value);
+ content += header;
+ }
+ }
+
+ http_response->set_content(content);
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleNoContent(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/nocontent"))
+ return NOT_HANDLED;
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_NO_CONTENT);
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleCloseSocket(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/close-socket"))
+ return NOT_HANDLED;
+
+ scoped_ptr<CustomHttpResponse> http_response(new CustomHttpResponse("", ""));
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleAuthBasic(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/auth-basic"))
+ return NOT_HANDLED;
+
+ GURL request_gurl = ToGURL(request);
+ RequestQuery query = ParseQuery(request_gurl);
+
+ std::string expectedPassword = "secret";
+ if (query.find(kDefaultPassword) != query.end())
+ expectedPassword = query.at(kDefaultPassword).front();
+ std::string realm = kDefaultRealm;
+ if (query.find("realm") != query.end())
+ realm = query.at("realm").front();
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ bool authed = false;
+ std::string error;
+ std::string auth;
+ std::string username;
+ std::string userpass;
+ std::string password;
+ std::string b64str;
+ if (request.headers.find("Authorization") == request.headers.end()) {
+ error = "Missing Authorization Header";
+ } else {
+ auth = request.headers.at("Authorization");
+ if (auth.find("Basic ") == std::string::npos) {
+ error = "Invalid Authorization Header";
+ } else {
+ b64str = auth.substr(std::string("Basic ").size());
+ base::Base64Decode(b64str, &userpass);
+ if (userpass.find(":") != std::string::npos) {
+ username = userpass.substr(0, userpass.find(":"));
+ password = userpass.substr(userpass.find(":") + 1);
+ if (password == expectedPassword)
+ authed = true;
+ else
+ error = "Invalid Credentials";
+ } else
+ error = "Invalid Credentials";
+ }
+ }
+
+ if (!authed) {
+ http_response->set_code(HTTP_UNAUTHORIZED);
+ http_response->set_content_type("text/html");
+ http_response->AddCustomHeader("WWW-Authenticate",
+ "Basic realm=\"" + realm + "\"");
+ if (query.find("set-cookie-if-challenged") != query.end())
+ http_response->AddCustomHeader("Set-Cookie", "got_challenged=true");
+ http_response->set_content(base::StringPrintf(
+ "<html><head><title>Denied: %s</title></head>"
+ "<body>auth=%s<p>b64str=%s<p>username: %s<p>userpass: %s<p>"
+ "password: %s<p>You sent:<br>%s<p></body></html>",
+ error.c_str(), auth.c_str(), b64str.c_str(), username.c_str(),
+ userpass.c_str(), password.c_str(), request.all_headers.c_str()));
+ return http_response.Pass();
+ }
+
+ if (request.headers.find("If-None-Match") != request.headers.end() &&
+ request.headers.at("If-None-Match") == "abc") {
+ http_response->set_code(HTTP_NOT_MODIFIED);
+ return http_response.Pass();
+ }
+
+ base::FilePath file_path =
+ base::FilePath().AppendASCII(request.relative_url.substr(1));
+ if (file_path.FinalExtension() == FILE_PATH_LITERAL("gif")) {
+ base::FilePath server_root;
+ PathService::Get(base::DIR_SOURCE_ROOT, &server_root);
+ base::FilePath gif_path =
+ server_root.AppendASCII("chrome/test/data/google/logo.gif");
+ std::string gif_data;
+ base::ReadFileToString(gif_path, &gif_data);
+ http_response->set_content_type("image/gif");
+ http_response->set_content(gif_data);
+ } else {
+ http_response->set_content_type("text/html");
+ http_response->set_content(
+ base::StringPrintf("<html><head><title>%s/%s</title></head>"
+ "<body>auth=%s<p>You sent:<br>%s<p></body></html>",
+ username.c_str(), password.c_str(), auth.c_str(),
+ request.all_headers.c_str()));
+ }
+
+ http_response->set_code(HTTP_OK);
+ http_response->AddCustomHeader("Cache-Control", "max-age=60000");
+ http_response->AddCustomHeader("Etag", "abc");
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleAuthDigest(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/auth-digest"))
+ return NOT_HANDLED;
+
+ std::string nonce = base::MD5String(
+ base::StringPrintf("privatekey%s", request.relative_url.c_str()));
+ std::string opaque = base::MD5String("opaque");
+ std::string password = "secret";
+ std::string realm = kDefaultRealm;
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ bool authed = false;
+ std::string error;
+ std::string auth;
+ std::string digestStr = "Digest";
+ std::string username;
+ if (request.headers.find("Authorization") == request.headers.end()) {
+ error = "no auth";
+ } else if (request.headers.at("Authorization").substr(0, digestStr.size()) !=
+ digestStr) {
+ error = "not digest";
+ } else {
+ auth = request.headers.at("Authorization");
+
+ std::map<std::string, std::string> authPairs;
+ std::vector<std::string> authVector = base::SplitString(
+ auth, ", ", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+ for (std::vector<std::string>::iterator it = authVector.begin();
+ it != authVector.end(); ++it) {
+ std::string key = it->substr(0, it->find("="));
+ std::string value = it->substr(it->find("=") + 1);
+ if (value.substr(0, 1) == "\"" && value.substr(value.size() - 1) == "\"")
+ value = value.substr(1, value.size() - 2);
+ authPairs[key] = value;
+ }
+
+ if (authPairs["nonce"] != nonce) {
+ error = "wrong nonce";
+ } else if (authPairs["opaque"] != opaque) {
+ error = "wrong opaque";
+ } else {
+ username = authPairs["username"];
+
+ std::string hash1 = base::MD5String(
+ base::StringPrintf("%s:%s:%s", authPairs["username"].c_str(),
+ realm.c_str(), password.c_str()));
+ std::string hash2 = base::MD5String(base::StringPrintf(
+ "%s:%s", request.method_string.c_str(), authPairs["uri"].c_str()));
+
+ std::string response;
+ if (authPairs.find("qop") != authPairs.end() &&
+ authPairs.find("nc") != authPairs.end() &&
+ authPairs.find("cnonce") != authPairs.end()) {
+ response = base::MD5String(base::StringPrintf(
+ "%s:%s:%s:%s:%s:%s", hash1.c_str(), nonce.c_str(),
+ authPairs["nc"].c_str(), authPairs["cnonce"].c_str(),
+ authPairs["qop"].c_str(), hash2.c_str()));
+ } else {
+ response = base::MD5String(base::StringPrintf(
+ "%s:%s:%s", hash1.c_str(), nonce.c_str(), hash2.c_str()));
+ }
+
+ if (authPairs["response"] == response)
+ authed = true;
+ else
+ error = "wrong password";
+ }
+ }
+
+ if (!authed) {
+ http_response->set_code(HTTP_UNAUTHORIZED);
+ http_response->set_content_type("text/html");
+ std::string authHeader = base::StringPrintf(
+ "Digest realm=\"%s\", "
+ "domain=\"/\", qop=\"auth\", algorithm=MD5, nonce=\"%s\", "
+ "opaque=\"%s\"",
+ realm.c_str(), nonce.c_str(), opaque.c_str());
+ http_response->AddCustomHeader("WWW-Authenticate", authHeader);
+ http_response->set_content(base::StringPrintf(
+ "<html><head><title>Denied: %s</title></head>"
+ "<body>auth=%s<p>"
+ "You sent:<br>%s<p>We are replying:<br>%s<p></body></html>",
+ error.c_str(), auth.c_str(), request.all_headers.c_str(),
+ authHeader.c_str()));
+ return http_response.Pass();
+ }
+
+ http_response->set_content_type("text/html");
+ http_response->set_content(
+ base::StringPrintf("<html><head><title>%s/%s</title></head>"
+ "<body>auth=%s<p></body></html>",
+ username.c_str(), password.c_str(), auth.c_str()));
+
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleServerRedirect(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/server-redirect"))
+ return NOT_HANDLED;
+
+ GURL request_gurl = ToGURL(request);
+ std::string dest =
+ net::UnescapeURLComponent(request_gurl.query(), kUnescapeAll);
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_MOVED_PERMANENTLY);
+ http_response->AddCustomHeader("Location", dest);
+ http_response->set_content_type("text/html");
+ http_response->set_content(base::StringPrintf(
+ "<html><head></head><body>Redirecting to %s</body></html>",
+ dest.c_str()));
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleCrossSiteRedirect(EmbeddedTestServer* server,
+ const HttpRequest& request) {
+ if (!ShouldHandle(request, "/cross-site"))
+ return NOT_HANDLED;
+
+ std::string destAll = net::UnescapeURLComponent(
+ request.relative_url.substr(std::string("/cross-site").size() + 1),
+ kUnescapeAll);
+
+ std::string dest = base::StringPrintf(
+ "//%s:%hu/%s", destAll.substr(0, destAll.find("/")).c_str(),
+ server->port(), destAll.substr(destAll.find("/") + 1).c_str());
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_MOVED_PERMANENTLY);
+ http_response->AddCustomHeader("Location", dest);
+ http_response->set_content_type("text/html");
+ http_response->set_content(base::StringPrintf(
+ "<html><head></head><body>Redirecting to %s</body></html>",
+ dest.c_str()));
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleClientRedirect(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/client-redirect"))
+ return NOT_HANDLED;
+
+ GURL request_gurl = ToGURL(request);
+ std::string dest =
+ net::UnescapeURLComponent(request_gurl.query(), kUnescapeAll);
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_OK);
+ http_response->set_content_type("text/html");
+ http_response->set_content(base::StringPrintf(
+ "<html><head><meta http-equiv=\"refresh\" content=\"0;url=%s\"></head>"
+ "<body>Redirecting to %s</body></html>",
+ dest.c_str(), dest.c_str()));
+ return http_response.Pass();
+}
+
+scoped_ptr<HttpResponse> HandleDefaultResponse(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/defaultresponse"))
+ return NOT_HANDLED;
+
+ scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
+ http_response->set_code(HTTP_OK);
+ http_response->set_content_type("text/html");
+ http_response->set_content("Default response given for path: " +
+ request.relative_url);
+ return http_response.Pass();
+}
+
+class DelayedHttpResponse : public BasicHttpResponse {
+ public:
+ DelayedHttpResponse(double delay) : delay_(delay) {}
+
+ void SendResponse(SendBytesCallback send,
+ SendCompleteCallback done) override {
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, base::Bind(send, ToResponseString(), done),
+ base::TimeDelta::FromSecondsD(delay_));
+ }
+
+ private:
+ double delay_;
+};
+
+scoped_ptr<HttpResponse> HandleSlowServer(const HttpRequest& request) {
+ if (!ShouldHandle(request, "/slow"))
+ return NOT_HANDLED;
+
+ double delay = 1.0f;
+
+ GURL request_gurl = ToGURL(request);
+ if (request_gurl.has_query()) {
+ delay = std::atof(request_gurl.query().c_str());
+ }
+
+ scoped_ptr<BasicHttpResponse> http_response(new DelayedHttpResponse(delay));
+ http_response->set_code(HTTP_OK);
+ http_response->set_content_type("text/plain");
+ http_response->set_content(base::StringPrintf("waited %.1f seconds", delay));
+ return http_response.Pass();
+}
+
+void RegisterDefaultHandlers(EmbeddedTestServer* server) {
+ server->RegisterDefaultHandler(base::Bind(&HandleRedirectConnect));
+ server->RegisterDefaultHandler(base::Bind(&HandleServerAuthConnect));
+ server->RegisterDefaultHandler(base::Bind(&HandleDefaultConnect));
+
+ server->RegisterDefaultHandler(
+ base::Bind(&HandleCacheControl, "/nocachetime/maxage", "max-age=0"));
mmenke 2015/10/15 19:38:17 Rather than make all of these take a path, suggest
svaldez 2015/10/15 21:04:33 Done.
+ server->RegisterDefaultHandler(
+ base::Bind(&HandleCacheControl, "/nocachetime", "no-cache"));
+ server->RegisterDefaultHandler(
+ base::Bind(&HandleCacheControl, "/cachetime", "max-age=60"));
+ server->RegisterDefaultHandler(base::Bind(&HandleCacheExpires));
+ server->RegisterDefaultHandler(base::Bind(&HandleCacheControl,
+ "/cache/proxy-revalidate",
+ "max-age=60, proxy-revalidate"));
+ server->RegisterDefaultHandler(
+ base::Bind(&HandleCacheControl, "/cache/private", "max-age=3, private"));
+ server->RegisterDefaultHandler(
+ base::Bind(&HandleCacheControl, "/cache/public", "max-age=3, public"));
+ server->RegisterDefaultHandler(base::Bind(&HandleCacheControl,
+ "/cache/s-maxage",
+ "public, s-maxage=60, max-age=0"));
+ server->RegisterDefaultHandler(base::Bind(
+ &HandleCacheControl, "/cache/must-revalidate", "must-revalidate"));
+ server->RegisterDefaultHandler(base::Bind(&HandleCacheControl,
+ "/cache/must-revalidate/max-age",
+ "max-age=60, must-revalidate"));
+ server->RegisterDefaultHandler(
+ base::Bind(&HandleCacheControl, "/cache/no-store", "no-store"));
+ server->RegisterDefaultHandler(base::Bind(
+ &HandleCacheControl, "/cache/no-store/max-age", "max-age=60, no-store"));
+ server->RegisterDefaultHandler(
+ base::Bind(&HandleCacheControl, "/cache/no-transform", "no-transform"));
+
+ server->RegisterDefaultHandler(
+ base::Bind(&HandleEchoHeader, "/echoheader", "no-cache"));
+ server->RegisterDefaultHandler(
+ base::Bind(&HandleEchoHeader, "/echoheadercache", "max-age=60000"));
+ server->RegisterDefaultHandler(base::Bind(&HandleEcho));
+ server->RegisterDefaultHandler(base::Bind(&HandleEchoTitle));
+ server->RegisterDefaultHandler(base::Bind(&HandleEchoAll));
+
+ server->RegisterDefaultHandler(base::Bind(&HandleSetCookie));
+ server->RegisterDefaultHandler(base::Bind(&HandleSetManyCookies));
+ server->RegisterDefaultHandler(base::Bind(&HandleExpectAndSetCookie));
+ server->RegisterDefaultHandler(base::Bind(&HandleSetHeader));
+ server->RegisterDefaultHandler(base::Bind(&HandleNoContent));
+ server->RegisterDefaultHandler(base::Bind(&HandleCloseSocket));
+ server->RegisterDefaultHandler(base::Bind(&HandleAuthBasic));
+ server->RegisterDefaultHandler(base::Bind(&HandleAuthDigest));
+ server->RegisterDefaultHandler(base::Bind(&HandleServerRedirect));
+ server->RegisterDefaultHandler(base::Bind(&HandleCrossSiteRedirect, server));
+ server->RegisterDefaultHandler(base::Bind(&HandleClientRedirect));
+ server->RegisterDefaultHandler(base::Bind(&HandleDefaultResponse));
+ server->RegisterDefaultHandler(base::Bind(&HandleSlowServer));
+
+ // TODO(svaldez): HandleDownload
+ // TODO(svaldez): HandleDownloadFinish
+ // TODO(svaldez): HandleZipFile
+ // TODO(svaldez): HandleRangeReset
+ // TODO(svaldez): HandleSSLManySmallRecords
+ // TODO(svaldez): HandleChunkedServer
+ // TODO(svaldez): HandleGetSSLSessionCache
+ // TODO(svaldez): HandleGetChannelID
+ // TODO(svaldez): HandleGetClientCert
+ // TODO(svaldez): HandleClientCipherList
+ // TODO(svaldez): HandleEchoMultipartPost
+}
+
+} // namespace test_server
+} // namespace net

Powered by Google App Engine
This is Rietveld 408576698