 Chromium Code Reviews
 Chromium Code Reviews Issue 1376593007:
  SSL in EmbeddedTestServer  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master
    
  
    Issue 1376593007:
  SSL in EmbeddedTestServer  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master| 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 |