summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorsvaldez <svaldez@chromium.org>2015-10-27 08:41:25 -0700
committerCommit bot <commit-bot@chromium.org>2015-10-27 15:42:17 +0000
commit5c265faa70626b7754e8b62300f65771dae2adbf (patch)
treeecebe2e86facbf81c834dcd79e111a4b8f7b2cf8 /net
parentbefca2c1f96ef13bc0a1530187731985d16cb9bb (diff)
downloadchromium_src-5c265faa70626b7754e8b62300f65771dae2adbf.zip
chromium_src-5c265faa70626b7754e8b62300f65771dae2adbf.tar.gz
chromium_src-5c265faa70626b7754e8b62300f65771dae2adbf.tar.bz2
SSL and add handlers in EmbeddedTestServer
As part of the migration of tests to EmbeddedTestServer, this CL modifies EmbeddedTestServer to support SSLServerSocket and to add shared default handlers that can be used by tests to mirror the SpawnedTestServer handlers. The major changes are: - SSL support through SSLServerSocket - Adding SSLServerConfig to configure SSLServerSockets. - Setting up default handlers for BrowserTestBase tests. - Asynchronous HttpResponse. - Moving EmbeddedTestServer to the net:: namespace. BUG=496825 NOPRESUBMIT=TRUE Review URL: https://codereview.chromium.org/1376593007 Cr-Commit-Position: refs/heads/master@{#356305}
Diffstat (limited to 'net')
-rw-r--r--net/BUILD.gn2
-rw-r--r--net/net.gyp2
-rw-r--r--net/net.gypi2
-rw-r--r--net/socket/ssl_server_socket.h4
-rw-r--r--net/socket/ssl_server_socket_nss.cc13
-rw-r--r--net/socket/ssl_server_socket_nss.h6
-rw-r--r--net/socket/ssl_server_socket_openssl.cc10
-rw-r--r--net/socket/ssl_server_socket_openssl.h6
-rw-r--r--net/socket/ssl_server_socket_unittest.cc4
-rw-r--r--net/ssl/ssl_server_config.cc20
-rw-r--r--net/ssl/ssl_server_config.h63
-rw-r--r--net/test/embedded_test_server/embedded_test_server.cc232
-rw-r--r--net/test/embedded_test_server/embedded_test_server.h106
-rw-r--r--net/test/embedded_test_server/embedded_test_server_unittest.cc257
-rw-r--r--net/test/embedded_test_server/http_connection.cc12
-rw-r--r--net/test/embedded_test_server/http_connection.h7
-rw-r--r--net/test/embedded_test_server/http_request.cc27
-rw-r--r--net/test/embedded_test_server/http_request.h6
-rw-r--r--net/test/embedded_test_server/http_response.cc32
-rw-r--r--net/test/embedded_test_server/http_response.h45
-rw-r--r--net/test/embedded_test_server/request_handler_util.cc228
-rw-r--r--net/test/embedded_test_server/request_handler_util.h63
22 files changed, 983 insertions, 164 deletions
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 689c8c5..ae63eb7 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -607,6 +607,8 @@ source_set("test_support") {
"test/embedded_test_server/http_request.h",
"test/embedded_test_server/http_response.cc",
"test/embedded_test_server/http_response.h",
+ "test/embedded_test_server/request_handler_util.cc",
+ "test/embedded_test_server/request_handler_util.h",
"test/event_waiter.h",
"test/net_test_suite.cc",
"test/net_test_suite.h",
diff --git a/net/net.gyp b/net/net.gyp
index f24751f..ed66c35 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -564,6 +564,8 @@
'test/embedded_test_server/http_request.h',
'test/embedded_test_server/http_response.cc',
'test/embedded_test_server/http_response.h',
+ 'test/embedded_test_server/request_handler_util.cc',
+ 'test/embedded_test_server/request_handler_util.h',
'test/event_waiter.h',
'test/net_test_suite.cc',
'test/net_test_suite.h',
diff --git a/net/net.gypi b/net/net.gypi
index 91fb19b..569555b 100644
--- a/net/net.gypi
+++ b/net/net.gypi
@@ -191,6 +191,8 @@
'ssl/ssl_info.cc',
'ssl/ssl_info.h',
'ssl/ssl_private_key.h',
+ 'ssl/ssl_server_config.cc',
+ 'ssl/ssl_server_config.h',
# Most files in net/quic are in net_nacl_common_sources, except for the
# files that have dependency on SPDY (net/spdy) or NSS.
diff --git a/net/socket/ssl_server_socket.h b/net/socket/ssl_server_socket.h
index 88f7f94..ceb9c0a 100644
--- a/net/socket/ssl_server_socket.h
+++ b/net/socket/ssl_server_socket.h
@@ -18,7 +18,7 @@ class RSAPrivateKey;
namespace net {
-struct SSLConfig;
+struct SSLServerConfig;
class X509Certificate;
class SSLServerSocket : public SSLSocket {
@@ -57,7 +57,7 @@ NET_EXPORT scoped_ptr<SSLServerSocket> CreateSSLServerSocket(
scoped_ptr<StreamSocket> socket,
X509Certificate* certificate,
crypto::RSAPrivateKey* key,
- const SSLConfig& ssl_config);
+ const SSLServerConfig& ssl_config);
} // namespace net
diff --git a/net/socket/ssl_server_socket_nss.cc b/net/socket/ssl_server_socket_nss.cc
index 9bcf8ee..3ed4da1 100644
--- a/net/socket/ssl_server_socket_nss.cc
+++ b/net/socket/ssl_server_socket_nss.cc
@@ -83,7 +83,7 @@ scoped_ptr<SSLServerSocket> CreateSSLServerSocket(
scoped_ptr<StreamSocket> socket,
X509Certificate* cert,
crypto::RSAPrivateKey* key,
- const SSLConfig& ssl_config) {
+ const SSLServerConfig& ssl_config) {
DCHECK(g_nss_server_sockets_init) << "EnableSSLServerSockets() has not been"
<< " called yet!";
@@ -95,7 +95,7 @@ SSLServerSocketNSS::SSLServerSocketNSS(
scoped_ptr<StreamSocket> transport_socket,
scoped_refptr<X509Certificate> cert,
crypto::RSAPrivateKey* key,
- const SSLConfig& ssl_config)
+ const SSLServerConfig& ssl_config)
: transport_send_busy_(false),
transport_recv_busy_(false),
user_read_buf_len_(0),
@@ -338,6 +338,15 @@ int SSLServerSocketNSS::InitializeSSLOptions() {
int rv;
+ if (ssl_config_.require_client_cert) {
+ rv = SSL_OptionSet(nss_fd_, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ if (rv != SECSuccess) {
+ LogFailedNSSFunction(net_log_, "SSL_OptionSet",
+ "SSL_REQUEST_CERTIFICATE");
+ return ERR_UNEXPECTED;
+ }
+ }
+
rv = SSL_OptionSet(nss_fd_, SSL_SECURITY, PR_TRUE);
if (rv != SECSuccess) {
LogFailedNSSFunction(net_log_, "SSL_OptionSet", "SSL_SECURITY");
diff --git a/net/socket/ssl_server_socket_nss.h b/net/socket/ssl_server_socket_nss.h
index e30811b..73a659e 100644
--- a/net/socket/ssl_server_socket_nss.h
+++ b/net/socket/ssl_server_socket_nss.h
@@ -17,7 +17,7 @@
#include "net/base/nss_memio.h"
#include "net/log/net_log.h"
#include "net/socket/ssl_server_socket.h"
-#include "net/ssl/ssl_config_service.h"
+#include "net/ssl/ssl_server_config.h"
namespace net {
@@ -28,7 +28,7 @@ class SSLServerSocketNSS : public SSLServerSocket {
SSLServerSocketNSS(scoped_ptr<StreamSocket> socket,
scoped_refptr<X509Certificate> certificate,
crypto::RSAPrivateKey* key,
- const SSLConfig& ssl_config);
+ const SSLServerConfig& ssl_config);
~SSLServerSocketNSS() override;
// SSLServerSocket interface.
@@ -138,7 +138,7 @@ class SSLServerSocketNSS : public SSLServerSocket {
scoped_ptr<StreamSocket> transport_socket_;
// Options for the SSL socket.
- SSLConfig ssl_config_;
+ SSLServerConfig ssl_config_;
// Certificate for the server.
scoped_refptr<X509Certificate> cert_;
diff --git a/net/socket/ssl_server_socket_openssl.cc b/net/socket/ssl_server_socket_openssl.cc
index 3fd749a..fe3a369 100644
--- a/net/socket/ssl_server_socket_openssl.cc
+++ b/net/socket/ssl_server_socket_openssl.cc
@@ -29,7 +29,7 @@ scoped_ptr<SSLServerSocket> CreateSSLServerSocket(
scoped_ptr<StreamSocket> socket,
X509Certificate* certificate,
crypto::RSAPrivateKey* key,
- const SSLConfig& ssl_config) {
+ const SSLServerConfig& ssl_config) {
crypto::EnsureOpenSSLInit();
return scoped_ptr<SSLServerSocket>(
new SSLServerSocketOpenSSL(socket.Pass(), certificate, key, ssl_config));
@@ -39,7 +39,7 @@ SSLServerSocketOpenSSL::SSLServerSocketOpenSSL(
scoped_ptr<StreamSocket> transport_socket,
scoped_refptr<X509Certificate> certificate,
crypto::RSAPrivateKey* key,
- const SSLConfig& ssl_config)
+ const SSLServerConfig& ssl_config)
: transport_send_busy_(false),
transport_recv_busy_(false),
transport_recv_eof_(false),
@@ -618,6 +618,10 @@ int SSLServerSocketOpenSSL::Init() {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
ScopedSSL_CTX ssl_ctx(SSL_CTX_new(SSLv23_server_method()));
+
+ if (ssl_config_.require_client_cert)
+ SSL_CTX_set_verify(ssl_ctx.get(), SSL_VERIFY_PEER, NULL);
+
ssl_ = SSL_new(ssl_ctx.get());
if (!ssl_)
return ERR_UNEXPECTED;
@@ -685,7 +689,7 @@ int SSLServerSocketOpenSSL::Init() {
SSL_set_mode(ssl_, mode.set_mask);
SSL_clear_mode(ssl_, mode.clear_mask);
- // See SSLConfig::disabled_cipher_suites for description of the suites
+ // See SSLServerConfig::disabled_cipher_suites for description of the suites
// disabled by default. Note that !SHA256 and !SHA384 only remove HMAC-SHA256
// and HMAC-SHA384 cipher suites, not GCM cipher suites with SHA256 or SHA384
// as the handshake hash.
diff --git a/net/socket/ssl_server_socket_openssl.h b/net/socket/ssl_server_socket_openssl.h
index a073e7e..c64f070 100644
--- a/net/socket/ssl_server_socket_openssl.h
+++ b/net/socket/ssl_server_socket_openssl.h
@@ -12,7 +12,7 @@
#include "net/base/io_buffer.h"
#include "net/log/net_log.h"
#include "net/socket/ssl_server_socket.h"
-#include "net/ssl/ssl_config_service.h"
+#include "net/ssl/ssl_server_config.h"
// Avoid including misc OpenSSL headers, i.e.:
// <openssl/bio.h>
@@ -31,7 +31,7 @@ class SSLServerSocketOpenSSL : public SSLServerSocket {
SSLServerSocketOpenSSL(scoped_ptr<StreamSocket> socket,
scoped_refptr<X509Certificate> certificate,
crypto::RSAPrivateKey* key,
- const SSLConfig& ssl_config);
+ const SSLServerConfig& ssl_config);
~SSLServerSocketOpenSSL() override;
// SSLServerSocket interface.
@@ -139,7 +139,7 @@ class SSLServerSocketOpenSSL : public SSLServerSocket {
scoped_ptr<StreamSocket> transport_socket_;
// Options for the SSL socket.
- SSLConfig ssl_config_;
+ SSLServerConfig ssl_config_;
// Certificate for the server.
scoped_refptr<X509Certificate> cert_;
diff --git a/net/socket/ssl_server_socket_unittest.cc b/net/socket/ssl_server_socket_unittest.cc
index 03ff361..548f7c6 100644
--- a/net/socket/ssl_server_socket_unittest.cc
+++ b/net/socket/ssl_server_socket_unittest.cc
@@ -47,9 +47,9 @@
#include "net/socket/ssl_client_socket.h"
#include "net/socket/stream_socket.h"
#include "net/ssl/ssl_cipher_suite_names.h"
-#include "net/ssl/ssl_config_service.h"
#include "net/ssl/ssl_connection_status_flags.h"
#include "net/ssl/ssl_info.h"
+#include "net/ssl/ssl_server_config.h"
#include "net/test/cert_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
@@ -356,7 +356,7 @@ class SSLServerSocketTest : public PlatformTest {
FakeDataChannel channel_1_;
FakeDataChannel channel_2_;
SSLConfig client_ssl_config_;
- SSLConfig server_ssl_config_;
+ SSLServerConfig server_ssl_config_;
scoped_ptr<SSLClientSocket> client_socket_;
scoped_ptr<SSLServerSocket> server_socket_;
ClientSocketFactory* socket_factory_;
diff --git a/net/ssl/ssl_server_config.cc b/net/ssl/ssl_server_config.cc
new file mode 100644
index 0000000..8b3e67f
--- /dev/null
+++ b/net/ssl/ssl_server_config.cc
@@ -0,0 +1,20 @@
+// 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/ssl/ssl_server_config.h"
+
+#include "net/socket/ssl_client_socket.h"
+#include "net/ssl/ssl_config.h"
+
+namespace net {
+
+SSLServerConfig::SSLServerConfig()
+ : version_min(kDefaultSSLVersionMin),
+ version_max(SSL_PROTOCOL_VERSION_TLS1_2),
+ require_ecdhe(false),
+ require_client_cert(false) {}
+
+SSLServerConfig::~SSLServerConfig() {}
+
+} // namespace net
diff --git a/net/ssl/ssl_server_config.h b/net/ssl/ssl_server_config.h
new file mode 100644
index 0000000..6e712fb
--- /dev/null
+++ b/net/ssl/ssl_server_config.h
@@ -0,0 +1,63 @@
+// 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.
+
+#ifndef NET_SSL_SSL_SERVER_CONFIG_H_
+#define NET_SSL_SSL_SERVER_CONFIG_H_
+
+#include <stdint.h>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/ssl/ssl_config.h"
+
+namespace net {
+
+// A collection of server-side SSL-related configuration settings.
+struct NET_EXPORT SSLServerConfig {
+ // Defaults
+ SSLServerConfig();
+ ~SSLServerConfig();
+
+ // The minimum and maximum protocol versions that are enabled.
+ // (Use the SSL_PROTOCOL_VERSION_xxx enumerators defined in ssl_config.h)
+ // SSL 2.0 and SSL 3.0 are not supported. If version_max < version_min, it
+ // means no protocol versions are enabled.
+ uint16_t version_min;
+ uint16_t version_max;
+
+ // Presorted list of cipher suites which should be explicitly prevented from
+ // being used in addition to those disabled by the net built-in policy.
+ //
+ // By default, all cipher suites supported by the underlying SSL
+ // implementation will be enabled except for:
+ // - Null encryption cipher suites.
+ // - Weak cipher suites: < 80 bits of security strength.
+ // - FORTEZZA cipher suites (obsolete).
+ // - IDEA cipher suites (RFC 5469 explains why).
+ // - Anonymous cipher suites.
+ // - ECDSA cipher suites on platforms that do not support ECDSA signed
+ // certificates, as servers may use the presence of such ciphersuites as a
+ // hint to send an ECDSA certificate.
+ // The ciphers listed in |disabled_cipher_suites| will be removed in addition
+ // to the above list.
+ //
+ // Though cipher suites are sent in TLS as "uint8 CipherSuite[2]", in
+ // big-endian form, they should be declared in host byte order, with the
+ // first uint8 occupying the most significant byte.
+ // Ex: To disable TLS_RSA_WITH_RC4_128_MD5, specify 0x0004, while to
+ // disable TLS_ECDH_ECDSA_WITH_RC4_128_SHA, specify 0xC002.
+ std::vector<uint16_t> disabled_cipher_suites;
+
+ // If true, causes only ECDHE cipher suites to be enabled.
+ bool require_ecdhe;
+
+ // Requires a client certificate for client authentication from the client.
+ // This doesn't currently enforce certificate validity.
+ bool require_client_cert;
+};
+
+} // namespace net
+
+#endif // NET_SSL_SSL_SERVER_CONFIG_H_
diff --git a/net/test/embedded_test_server/embedded_test_server.cc b/net/test/embedded_test_server/embedded_test_server.cc
index 62158b4..8725fa1 100644
--- a/net/test/embedded_test_server/embedded_test_server.cc
+++ b/net/test/embedded_test_server/embedded_test_server.cc
@@ -10,6 +10,7 @@
#include "base/location.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
+#include "base/path_service.h"
#include "base/process/process_metrics.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
@@ -17,88 +18,40 @@
#include "base/strings/stringprintf.h"
#include "base/thread_task_runner_handle.h"
#include "base/threading/thread_restrictions.h"
+#include "crypto/rsa_private_key.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
+#include "net/base/test_data_directory.h"
+#include "net/cert/pem_tokenizer.h"
+#include "net/cert/test_root_certs.h"
+#include "net/socket/ssl_server_socket.h"
#include "net/socket/stream_socket.h"
#include "net/socket/tcp_server_socket.h"
+#include "net/ssl/ssl_server_config.h"
+#include "net/test/cert_test_util.h"
#include "net/test/embedded_test_server/embedded_test_server_connection_listener.h"
#include "net/test/embedded_test_server/http_connection.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 {
namespace test_server {
-namespace {
+EmbeddedTestServer::EmbeddedTestServer() : EmbeddedTestServer(TYPE_HTTP) {}
-class CustomHttpResponse : public HttpResponse {
- public:
- CustomHttpResponse(const std::string& headers, const std::string& contents)
- : headers_(headers), contents_(contents) {
- }
-
- std::string ToResponseString() const override {
- return headers_ + "\r\n" + contents_;
- }
+EmbeddedTestServer::EmbeddedTestServer(Type type)
+ : is_using_ssl_(type == TYPE_HTTPS),
+ connection_listener_(nullptr),
+ port_(0),
+ cert_(CERT_OK) {
+ DCHECK(thread_checker_.CalledOnValidThread());
- private:
- std::string headers_;
- std::string contents_;
-
- DISALLOW_COPY_AND_ASSIGN(CustomHttpResponse);
-};
-
-// 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;
-
- 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 relative_gurl(relative_url);
- if (relative_gurl.is_valid())
- relative_url = relative_gurl.PathForRequest();
-
- // Trim the first byte ('/').
- std::string request_path = relative_url.substr(1);
-
- // Remove the query string if present.
- size_t query_pos = request_path.find('?');
- if (query_pos != std::string::npos)
- request_path = request_path.substr(0, query_pos);
-
- base::FilePath file_path(server_root.AppendASCII(request_path));
- std::string file_contents;
- if (!base::ReadFileToString(file_path, &file_contents))
- return scoped_ptr<HttpResponse>();
-
- 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 scoped_ptr<HttpResponse>();
-
- scoped_ptr<CustomHttpResponse> http_response(
- new CustomHttpResponse(headers_contents, file_contents));
- return http_response.Pass();
+ if (is_using_ssl_) {
+ TestRootCerts* root_certs = TestRootCerts::GetInstance();
+ base::FilePath certs_dir(GetTestCertsDirectory());
+ root_certs->AddFromFile(certs_dir.AppendASCII("root_ca_cert.pem"));
}
-
- scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
- http_response->set_code(HTTP_OK);
- http_response->set_content(file_contents);
- return http_response.Pass();
-}
-
-} // namespace
-
-EmbeddedTestServer::EmbeddedTestServer()
- : connection_listener_(nullptr), port_(0) {
- DCHECK(thread_checker_.CalledOnValidThread());
}
EmbeddedTestServer::~EmbeddedTestServer() {
@@ -115,7 +68,7 @@ void EmbeddedTestServer::SetConnectionListener(
connection_listener_ = listener;
}
-bool EmbeddedTestServer::InitializeAndWaitUntilReady() {
+bool EmbeddedTestServer::Start() {
bool success = InitializeAndListen();
if (!success)
return false;
@@ -123,6 +76,10 @@ bool EmbeddedTestServer::InitializeAndWaitUntilReady() {
return true;
}
+bool EmbeddedTestServer::InitializeAndWaitUntilReady() {
+ return Start();
+}
+
bool EmbeddedTestServer::InitializeAndListen() {
DCHECK(!Started());
@@ -142,7 +99,15 @@ bool EmbeddedTestServer::InitializeAndListen() {
return false;
}
- base_url_ = GURL(std::string("http://") + local_endpoint_.ToString());
+ if (is_using_ssl_) {
+ base_url_ = GURL("https://" + local_endpoint_.ToString());
+ if (cert_ == CERT_MISMATCHED_NAME || cert_ == CERT_COMMON_NAME_IS_DOMAIN) {
+ base_url_ = GURL(
+ base::StringPrintf("https://localhost:%d", local_endpoint_.port()));
+ }
+ } else {
+ base_url_ = GURL("http://" + local_endpoint_.ToString());
+ }
port_ = local_endpoint_.port();
listen_socket_->DetachFromThread();
@@ -183,13 +148,21 @@ void EmbeddedTestServer::HandleRequest(HttpConnection* connection,
scoped_ptr<HttpResponse> response;
- for (size_t i = 0; i < request_handlers_.size(); ++i) {
- response = request_handlers_[i].Run(*request);
+ for (const auto& handler : request_handlers_) {
+ response = handler.Run(*request);
if (response)
break;
}
if (!response) {
+ for (const auto& handler : default_request_handlers_) {
+ response = handler.Run(*request);
+ if (response)
+ break;
+ }
+ }
+
+ if (!response) {
LOG(WARNING) << "Request not handled. Returning 404: "
<< request->relative_url;
scoped_ptr<BasicHttpResponse> not_found_response(new BasicHttpResponse);
@@ -197,9 +170,10 @@ void EmbeddedTestServer::HandleRequest(HttpConnection* connection,
response = not_found_response.Pass();
}
- connection->SendResponse(response.Pass(),
- base::Bind(&EmbeddedTestServer::DidClose,
- base::Unretained(this), connection));
+ response->SendResponse(base::Bind(&HttpConnection::SendResponseBytes,
+ base::Unretained(connection)),
+ base::Bind(&EmbeddedTestServer::DidClose,
+ base::Unretained(this), connection));
}
GURL EmbeddedTestServer::GetURL(const std::string& relative_url) const {
@@ -223,16 +197,104 @@ bool EmbeddedTestServer::GetAddressList(AddressList* address_list) const {
return true;
}
+void EmbeddedTestServer::SetSSLConfig(ServerCertificate cert,
+ const SSLServerConfig& ssl_config) {
+ DCHECK(!Started());
+ cert_ = cert;
+ ssl_config_ = ssl_config;
+}
+
+void EmbeddedTestServer::SetSSLConfig(ServerCertificate cert) {
+ SetSSLConfig(cert, SSLServerConfig());
+}
+
+std::string EmbeddedTestServer::GetCertificateName() const {
+ DCHECK(is_using_ssl_);
+ switch (cert_) {
+ case CERT_OK:
+ case CERT_MISMATCHED_NAME:
+ return "ok_cert.pem";
+ case CERT_COMMON_NAME_IS_DOMAIN:
+ return "localhost_cert.pem";
+ case CERT_EXPIRED:
+ return "expired_cert.pem";
+ case CERT_CHAIN_WRONG_ROOT:
+ return "redundant-server-chain.pem";
+ case CERT_BAD_VALIDITY:
+ return "bad_validity.pem";
+ }
+
+ return "ok_cert.pem";
+}
+
+scoped_refptr<X509Certificate> EmbeddedTestServer::GetCertificate() const {
+ DCHECK(is_using_ssl_);
+ base::FilePath certs_dir(GetTestCertsDirectory());
+ return ImportCertFromFile(certs_dir, GetCertificateName());
+}
+
void EmbeddedTestServer::ServeFilesFromDirectory(
const base::FilePath& directory) {
RegisterRequestHandler(base::Bind(&HandleFileRequest, directory));
}
+void EmbeddedTestServer::ServeFilesFromSourceDirectory(
+ const std::string& relative) {
+ base::FilePath test_data_dir;
+ CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir));
+ ServeFilesFromDirectory(test_data_dir.AppendASCII(relative));
+}
+
+void EmbeddedTestServer::ServeFilesFromSourceDirectory(
+ const base::FilePath& relative) {
+ base::FilePath test_data_dir;
+ CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir));
+ ServeFilesFromDirectory(test_data_dir.Append(relative));
+}
+
+void EmbeddedTestServer::AddDefaultHandlers(const base::FilePath& directory) {
+ // TODO(svaldez): Add additional default handlers.
+ ServeFilesFromSourceDirectory(directory);
+}
+
void EmbeddedTestServer::RegisterRequestHandler(
const HandleRequestCallback& callback) {
+ // TODO(svaldez): Add check to prevent RegisterHandler from being called
+ // after the server has started. https://crbug.com/546060
request_handlers_.push_back(callback);
}
+void EmbeddedTestServer::RegisterDefaultHandler(
+ const HandleRequestCallback& callback) {
+ // TODO(svaldez): Add check to prevent RegisterHandler from being called
+ // after the server has started. https://crbug.com/546060
+ default_request_handlers_.push_back(callback);
+}
+
+scoped_ptr<StreamSocket> EmbeddedTestServer::DoSSLUpgrade(
+ scoped_ptr<StreamSocket> connection) {
+ DCHECK(io_thread_->task_runner()->BelongsToCurrentThread());
+
+ base::FilePath certs_dir(GetTestCertsDirectory());
+ std::string cert_name = GetCertificateName();
+
+ base::FilePath key_path = certs_dir.AppendASCII(cert_name);
+ std::string key_string;
+ CHECK(base::ReadFileToString(key_path, &key_string));
+ std::vector<std::string> headers;
+ headers.push_back("PRIVATE KEY");
+ PEMTokenizer pem_tokenizer(key_string, headers);
+ pem_tokenizer.GetNext();
+ std::vector<uint8_t> key_vector;
+ key_vector.assign(pem_tokenizer.data().begin(), pem_tokenizer.data().end());
+
+ scoped_ptr<crypto::RSAPrivateKey> server_key(
+ crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(key_vector));
+
+ return CreateSSLServerSocket(connection.Pass(), GetCertificate().get(),
+ server_key.get(), ssl_config_);
+}
+
void EmbeddedTestServer::DoAcceptLoop() {
int rv = OK;
while (rv == OK) {
@@ -251,17 +313,37 @@ void EmbeddedTestServer::OnAcceptCompleted(int rv) {
DoAcceptLoop();
}
+void EmbeddedTestServer::OnHandshakeDone(HttpConnection* connection, int rv) {
+ if (connection->socket_->IsConnected())
+ ReadData(connection);
+ else
+ DidClose(connection);
+}
+
void EmbeddedTestServer::HandleAcceptResult(scoped_ptr<StreamSocket> socket) {
DCHECK(io_thread_->task_runner()->BelongsToCurrentThread());
if (connection_listener_)
connection_listener_->AcceptedSocket(*socket);
+ if (is_using_ssl_)
+ socket = DoSSLUpgrade(socket.Pass());
+
HttpConnection* http_connection = new HttpConnection(
socket.Pass(),
base::Bind(&EmbeddedTestServer::HandleRequest, base::Unretained(this)));
connections_[http_connection->socket_.get()] = http_connection;
- ReadData(http_connection);
+ if (is_using_ssl_) {
+ SSLServerSocket* ssl_socket =
+ static_cast<SSLServerSocket*>(http_connection->socket_.get());
+ int rv = ssl_socket->Handshake(
+ base::Bind(&EmbeddedTestServer::OnHandshakeDone, base::Unretained(this),
+ http_connection));
+ if (rv != ERR_IO_PENDING)
+ OnHandshakeDone(http_connection, rv);
+ } else {
+ ReadData(http_connection);
+ }
}
void EmbeddedTestServer::ReadData(HttpConnection* connection) {
diff --git a/net/test/embedded_test_server/embedded_test_server.h b/net/test/embedded_test_server/embedded_test_server.h
index 031ceaf..16d0442 100644
--- a/net/test/embedded_test_server/embedded_test_server.h
+++ b/net/test/embedded_test_server/embedded_test_server.h
@@ -12,18 +12,21 @@
#include "base/basictypes.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/threading/thread.h"
#include "base/threading/thread_checker.h"
+#include "crypto/rsa_private_key.h"
#include "net/base/address_list.h"
+#include "net/base/host_port_pair.h"
#include "net/base/ip_endpoint.h"
+#include "net/cert/x509_certificate.h"
+#include "net/socket/stream_socket.h"
+#include "net/socket/tcp_server_socket.h"
+#include "net/ssl/ssl_server_config.h"
#include "url/gurl.h"
-namespace base {
-class FilePath;
-}
-
namespace net {
class StreamSocket;
@@ -45,7 +48,7 @@ struct HttpRequest;
//
// void SetUp() {
// test_server_.reset(new EmbeddedTestServer());
-// ASSERT_TRUE(test_server_.InitializeAndWaitUntilReady());
+// ASSERT_TRUE(test_server_.Start());
// test_server_->RegisterRequestHandler(
// base::Bind(&FooTest::HandleRequest, base::Unretained(this)));
// }
@@ -63,11 +66,10 @@ struct HttpRequest;
// }
//
// For a test that spawns another process such as browser_tests, it is
-// suggested to call InitializeAndWaitUntilReady in SetUpOnMainThread after
-// the process is spawned. If you have to do it before the process spawns,
-// you need to first setup the listen socket so that there is no no other
-// threads running while spawning the process. To do so, please follow
-// the following example:
+// suggested to call Start in SetUpOnMainThread after the process is spawned.
+// If you have to do it before the process spawns, you need to first setup the
+// listen socket so that there is no no other threads running while spawning
+// the process. To do so, please follow the following example:
//
// void SetUp() {
// ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
@@ -82,12 +84,39 @@ struct HttpRequest;
//
class EmbeddedTestServer {
public:
+ enum Type {
+ TYPE_HTTP,
+ TYPE_HTTPS,
+ };
+
+ enum ServerCertificate {
+ CERT_OK,
+
+ CERT_MISMATCHED_NAME,
+ CERT_EXPIRED,
+
+ // A certificate with invalid notBefore and notAfter times. Windows'
+ // certificate library will not parse this certificate.
+ CERT_BAD_VALIDITY,
+
+ // Cross-signed certificate to test PKIX path building. Contains an
+ // intermediate cross-signed by an unknown root, while the client (via
+ // TestRootStore) is expected to have a self-signed version of the
+ // intermediate.
+ CERT_CHAIN_WRONG_ROOT,
+
+ // Causes the testserver to use a hostname that is a domain
+ // instead of an IP.
+ CERT_COMMON_NAME_IS_DOMAIN,
+ };
+
typedef base::Callback<scoped_ptr<HttpResponse>(
const HttpRequest& request)> HandleRequestCallback;
- // Creates a http test server. InitializeAndWaitUntilReady() must be called
- // to start the server.
+ // Creates a http test server. Start() must be called to start the server.
+ // |type| indicates the protocol type of the server (HTTP/HTTPS).
EmbeddedTestServer();
+ explicit EmbeddedTestServer(Type type);
~EmbeddedTestServer();
// Sets a connection listener, that would be notified when various connection
@@ -98,7 +127,11 @@ class EmbeddedTestServer {
// Initializes and waits until the server is ready to accept requests.
// This is the equivalent of calling InitializeAndListen() followed by
// StartAcceptingConnections().
- // Returns whether a listening socket has been succesfully created.
+ // Returns whether a listening socket has been successfully created.
+ bool Start();
+
+ // Deprecated method that calls Start().
+ // TODO(svaldez): Remove and replace with Start().
bool InitializeAndWaitUntilReady() WARN_UNUSED_RESULT;
// Starts listening for incoming connections but will not yet accept them.
@@ -116,6 +149,10 @@ class EmbeddedTestServer {
return listen_socket_.get() != NULL;
}
+ HostPortPair host_port_pair() const {
+ return HostPortPair::FromURL(base_url_);
+ }
+
// Returns the base URL to the server, which looks like
// http://127.0.0.1:<port>/, where <port> is the actual port number used by
// the server.
@@ -133,26 +170,55 @@ class EmbeddedTestServer {
const std::string& relative_url) const;
// Returns the address list needed to connect to the server.
- bool GetAddressList(net::AddressList* address_list) const WARN_UNUSED_RESULT;
+ bool GetAddressList(AddressList* address_list) const WARN_UNUSED_RESULT;
// Returns the port number used by the server.
uint16 port() const { return port_; }
+ void SetSSLConfig(ServerCertificate cert, const SSLServerConfig& ssl_config);
+ void SetSSLConfig(ServerCertificate cert);
+
+ // Returns the file name of the certificate the server is using. The test
+ // certificates can be found in net/data/ssl/certificates/.
+ std::string GetCertificateName() const;
+
+ // Returns the certificate that the server is using.
+ scoped_refptr<X509Certificate> GetCertificate() const;
+
// Registers request handler which serves files from |directory|.
// For instance, a request to "/foo.html" is served by "foo.html" under
// |directory|. Files under sub directories are also handled in the same way
// (i.e. "/foo/bar.html" is served by "foo/bar.html" under |directory|).
+ // TODO(svaldez): Merge ServeFilesFromDirectory and
+ // ServeFilesFromSourceDirectory.
void ServeFilesFromDirectory(const base::FilePath& directory);
+ // Serves files relative to DIR_SOURCE_ROOT.
+ void ServeFilesFromSourceDirectory(const std::string& relative);
+ void ServeFilesFromSourceDirectory(const base::FilePath& relative);
+
+ // Registers the default handlers and serve additional files from the
+ // |directory| directory, relative to DIR_SOURCE_ROOT.
+ void AddDefaultHandlers(const base::FilePath& directory);
+
// The most general purpose method. Any request processing can be added using
// this method. Takes ownership of the object. The |callback| is called
// on UI thread.
void RegisterRequestHandler(const HandleRequestCallback& callback);
+ // Adds default handlers, including those added by AddDefaultHandlers, to be
+ // tried after all other user-specified handlers have been tried.
+ void RegisterDefaultHandler(const HandleRequestCallback& callback);
+
private:
// Shuts down the server.
void ShutdownOnIOThread();
+ // Upgrade the TCP connection to one over SSL.
+ scoped_ptr<StreamSocket> DoSSLUpgrade(scoped_ptr<StreamSocket> connection);
+ // Handles async callback when the SSL handshake has been completed.
+ void OnHandshakeDone(HttpConnection* connection, int rv);
+
// Begins accepting new client connections.
void DoAcceptLoop();
// Handles async callback when there is a new client socket. |rv| is the
@@ -184,6 +250,8 @@ class EmbeddedTestServer {
bool PostTaskToIOThreadAndWait(
const base::Closure& closure) WARN_UNUSED_RESULT;
+ const bool is_using_ssl_;
+
scoped_ptr<base::Thread> io_thread_;
scoped_ptr<TCPServerSocket> listen_socket_;
@@ -197,15 +265,23 @@ class EmbeddedTestServer {
// Owns the HttpConnection objects.
std::map<StreamSocket*, HttpConnection*> connections_;
- // Vector of registered request handlers.
+ // Vector of registered and default request handlers.
std::vector<HandleRequestCallback> request_handlers_;
+ std::vector<HandleRequestCallback> default_request_handlers_;
base::ThreadChecker thread_checker_;
+ net::SSLServerConfig ssl_config_;
+ ServerCertificate cert_;
+
DISALLOW_COPY_AND_ASSIGN(EmbeddedTestServer);
};
} // namespace test_server
+
+// TODO(svaldez): Refactor EmbeddedTestServer to be in the net namespace.
+using test_server::EmbeddedTestServer;
+
} // namespace net
#endif // NET_TEST_EMBEDDED_TEST_SERVER_EMBEDDED_TEST_SERVER_H_
diff --git a/net/test/embedded_test_server/embedded_test_server_unittest.cc b/net/test/embedded_test_server/embedded_test_server_unittest.cc
index 91cb16f..e008ac2 100644
--- a/net/test/embedded_test_server/embedded_test_server_unittest.cc
+++ b/net/test/embedded_test_server/embedded_test_server_unittest.cc
@@ -4,12 +4,14 @@
#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "base/memory/weak_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/threading/thread.h"
+#include "crypto/nss_util.h"
#include "net/base/test_completion_callback.h"
#include "net/http/http_response_headers.h"
#include "net/log/test_net_log.h"
@@ -18,12 +20,17 @@
#include "net/test/embedded_test_server/embedded_test_server_connection_listener.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
-#include "net/test/spawned_test_server/base_test_server.h"
+#include "net/test/embedded_test_server/request_handler_util.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
+#if defined(USE_NSS_CERTS) || defined(OS_IOS)
+#include "net/cert_net/nss_ocsp.h"
+#endif
+
namespace net {
namespace test_server {
@@ -101,8 +108,9 @@ class TestConnectionListener
DISALLOW_COPY_AND_ASSIGN(TestConnectionListener);
};
-class EmbeddedTestServerTest: public testing::Test,
- public URLFetcherDelegate {
+class EmbeddedTestServerTest
+ : public testing::TestWithParam<EmbeddedTestServer::Type>,
+ public URLFetcherDelegate {
public:
EmbeddedTestServerTest()
: num_responses_received_(0),
@@ -111,6 +119,15 @@ class EmbeddedTestServerTest: public testing::Test,
}
void SetUp() override {
+#if defined(USE_NSS_CERTS) || defined(OS_IOS)
+ // This is needed so NSS's HTTP client functions are initialized on the
+ // right thread. These tests create SSLClientSockets on a different thread.
+ // TODO(davidben): Initialization can't be deferred to SSLClientSocket. See
+ // https://crbug.com/539520.
+ crypto::EnsureNSSInit();
+ EnsureNSSHttpIOInit();
+#endif
+
base::Thread::Options thread_options;
thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
ASSERT_TRUE(io_thread_.StartWithOptions(thread_options));
@@ -118,13 +135,16 @@ class EmbeddedTestServerTest: public testing::Test,
request_context_getter_ =
new TestURLRequestContextGetter(io_thread_.task_runner());
- server_.reset(new EmbeddedTestServer);
+ server_.reset(new EmbeddedTestServer(GetParam()));
server_->SetConnectionListener(&connection_listener_);
- ASSERT_TRUE(server_->InitializeAndWaitUntilReady());
}
void TearDown() override {
- ASSERT_TRUE(server_->ShutdownAndWaitUntilComplete());
+ if (server_->Started())
+ ASSERT_TRUE(server_->ShutdownAndWaitUntilComplete());
+#if defined(USE_NSS_CERTS) || defined(OS_IOS)
+ ShutdownNSSHttpIO();
+#endif
}
// URLFetcherDelegate override.
@@ -173,24 +193,44 @@ class EmbeddedTestServerTest: public testing::Test,
scoped_ptr<EmbeddedTestServer> server_;
};
-TEST_F(EmbeddedTestServerTest, GetBaseURL) {
- EXPECT_EQ(base::StringPrintf("http://127.0.0.1:%u/", server_->port()),
- server_->base_url().spec());
+TEST_P(EmbeddedTestServerTest, GetBaseURL) {
+ ASSERT_TRUE(server_->Start());
+ if (GetParam() == EmbeddedTestServer::TYPE_HTTPS) {
+ EXPECT_EQ(base::StringPrintf("https://127.0.0.1:%u/", server_->port()),
+ server_->base_url().spec());
+ } else {
+ EXPECT_EQ(base::StringPrintf("http://127.0.0.1:%u/", server_->port()),
+ server_->base_url().spec());
+ }
}
-TEST_F(EmbeddedTestServerTest, GetURL) {
- EXPECT_EQ(base::StringPrintf("http://127.0.0.1:%u/path?query=foo",
- server_->port()),
- server_->GetURL("/path?query=foo").spec());
+TEST_P(EmbeddedTestServerTest, GetURL) {
+ ASSERT_TRUE(server_->Start());
+ if (GetParam() == EmbeddedTestServer::TYPE_HTTPS) {
+ EXPECT_EQ(base::StringPrintf("https://127.0.0.1:%u/path?query=foo",
+ server_->port()),
+ server_->GetURL("/path?query=foo").spec());
+ } else {
+ EXPECT_EQ(base::StringPrintf("http://127.0.0.1:%u/path?query=foo",
+ server_->port()),
+ server_->GetURL("/path?query=foo").spec());
+ }
}
-TEST_F(EmbeddedTestServerTest, GetURLWithHostname) {
- EXPECT_EQ(base::StringPrintf("http://foo.com:%d/path?query=foo",
- server_->port()),
- server_->GetURL("foo.com", "/path?query=foo").spec());
+TEST_P(EmbeddedTestServerTest, GetURLWithHostname) {
+ ASSERT_TRUE(server_->Start());
+ if (GetParam() == EmbeddedTestServer::TYPE_HTTPS) {
+ EXPECT_EQ(base::StringPrintf("https://foo.com:%d/path?query=foo",
+ server_->port()),
+ server_->GetURL("foo.com", "/path?query=foo").spec());
+ } else {
+ EXPECT_EQ(
+ base::StringPrintf("http://foo.com:%d/path?query=foo", server_->port()),
+ server_->GetURL("foo.com", "/path?query=foo").spec());
+ }
}
-TEST_F(EmbeddedTestServerTest, RegisterRequestHandler) {
+TEST_P(EmbeddedTestServerTest, RegisterRequestHandler) {
server_->RegisterRequestHandler(
base::Bind(&EmbeddedTestServerTest::HandleRequest,
base::Unretained(this),
@@ -198,6 +238,7 @@ TEST_F(EmbeddedTestServerTest, RegisterRequestHandler) {
"<b>Worked!</b>",
"text/html",
HTTP_OK));
+ ASSERT_TRUE(server_->Start());
scoped_ptr<URLFetcher> fetcher =
URLFetcher::Create(server_->GetURL("/test?q=foo"), URLFetcher::GET, this);
@@ -213,11 +254,12 @@ TEST_F(EmbeddedTestServerTest, RegisterRequestHandler) {
EXPECT_EQ("/test?q=foo", request_relative_url_);
}
-TEST_F(EmbeddedTestServerTest, ServeFilesFromDirectory) {
+TEST_P(EmbeddedTestServerTest, ServeFilesFromDirectory) {
base::FilePath src_dir;
ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
server_->ServeFilesFromDirectory(
src_dir.AppendASCII("net").AppendASCII("data"));
+ ASSERT_TRUE(server_->Start());
scoped_ptr<URLFetcher> fetcher =
URLFetcher::Create(server_->GetURL("/test.html"), URLFetcher::GET, this);
@@ -228,10 +270,12 @@ TEST_F(EmbeddedTestServerTest, ServeFilesFromDirectory) {
EXPECT_EQ(URLRequestStatus::SUCCESS, fetcher->GetStatus().status());
EXPECT_EQ(HTTP_OK, fetcher->GetResponseCode());
EXPECT_EQ("<p>Hello World!</p>", GetContentFromFetcher(*fetcher));
- EXPECT_EQ("", GetContentTypeFromFetcher(*fetcher));
+ EXPECT_EQ("text/html", GetContentTypeFromFetcher(*fetcher));
}
-TEST_F(EmbeddedTestServerTest, DefaultNotFoundResponse) {
+TEST_P(EmbeddedTestServerTest, DefaultNotFoundResponse) {
+ ASSERT_TRUE(server_->Start());
+
scoped_ptr<URLFetcher> fetcher = URLFetcher::Create(
server_->GetURL("/non-existent"), URLFetcher::GET, this);
fetcher->SetRequestContext(request_context_getter_.get());
@@ -242,7 +286,9 @@ TEST_F(EmbeddedTestServerTest, DefaultNotFoundResponse) {
EXPECT_EQ(HTTP_NOT_FOUND, fetcher->GetResponseCode());
}
-TEST_F(EmbeddedTestServerTest, ConnectionListenerAccept) {
+TEST_P(EmbeddedTestServerTest, ConnectionListenerAccept) {
+ ASSERT_TRUE(server_->Start());
+
TestNetLog net_log;
net::AddressList address_list;
EXPECT_TRUE(server_->GetAddressList(&address_list));
@@ -259,7 +305,9 @@ TEST_F(EmbeddedTestServerTest, ConnectionListenerAccept) {
EXPECT_FALSE(connection_listener_.DidReadFromSocket());
}
-TEST_F(EmbeddedTestServerTest, ConnectionListenerRead) {
+TEST_P(EmbeddedTestServerTest, ConnectionListenerRead) {
+ ASSERT_TRUE(server_->Start());
+
scoped_ptr<URLFetcher> fetcher = URLFetcher::Create(
server_->GetURL("/non-existent"), URLFetcher::GET, this);
fetcher->SetRequestContext(request_context_getter_.get());
@@ -270,7 +318,7 @@ TEST_F(EmbeddedTestServerTest, ConnectionListenerRead) {
EXPECT_TRUE(connection_listener_.DidReadFromSocket());
}
-TEST_F(EmbeddedTestServerTest, ConcurrentFetches) {
+TEST_P(EmbeddedTestServerTest, ConcurrentFetches) {
server_->RegisterRequestHandler(
base::Bind(&EmbeddedTestServerTest::HandleRequest,
base::Unretained(this),
@@ -292,6 +340,7 @@ TEST_F(EmbeddedTestServerTest, ConcurrentFetches) {
"No chocolates",
"text/plain",
HTTP_NOT_FOUND));
+ ASSERT_TRUE(server_->Start());
scoped_ptr<URLFetcher> fetcher1 =
URLFetcher::Create(server_->GetURL("/test1"), URLFetcher::GET, this);
@@ -325,14 +374,141 @@ TEST_F(EmbeddedTestServerTest, ConcurrentFetches) {
EXPECT_EQ("text/plain", GetContentTypeFromFetcher(*fetcher3));
}
+namespace {
+
+class CancelRequestDelegate : public TestDelegate {
+ public:
+ CancelRequestDelegate() {}
+ ~CancelRequestDelegate() override {}
+
+ void OnResponseStarted(URLRequest* request) override {
+ TestDelegate::OnResponseStarted(request);
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop_.QuitClosure(), base::TimeDelta::FromSeconds(1));
+ }
+
+ void WaitUntilDone() { run_loop_.Run(); }
+
+ private:
+ base::RunLoop run_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(CancelRequestDelegate);
+};
+
+class InfiniteResponse : public BasicHttpResponse {
+ public:
+ InfiniteResponse() : weak_ptr_factory_(this) {}
+
+ void SendResponse(const SendBytesCallback& send,
+ const SendCompleteCallback& done) override {
+ send.Run(ToResponseString(),
+ base::Bind(&InfiniteResponse::SendInfinite,
+ weak_ptr_factory_.GetWeakPtr(), send));
+ }
+
+ private:
+ void SendInfinite(const SendBytesCallback& send) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(send, "echo",
+ base::Bind(&InfiniteResponse::SendInfinite,
+ weak_ptr_factory_.GetWeakPtr(), send)));
+ }
+
+ base::WeakPtrFactory<InfiniteResponse> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(InfiniteResponse);
+};
+
+scoped_ptr<HttpResponse> HandleInfiniteRequest(const HttpRequest& request) {
+ return make_scoped_ptr(new InfiniteResponse);
+}
+}
+
+// Tests the case the connection is closed while the server is sending a
+// response. May non-deterministically end up at one of three paths
+// (Discover the close event synchronously, asynchronously, or server
+// shutting down before it is discovered).
+TEST_P(EmbeddedTestServerTest, CloseDuringWrite) {
+ CancelRequestDelegate cancel_delegate;
+ TestURLRequestContext context;
+ cancel_delegate.set_cancel_in_response_started(true);
+ server_->RegisterRequestHandler(base::Bind(
+ &HandlePrefixedRequest, "/infinite", base::Bind(&HandleInfiniteRequest)));
+ ASSERT_TRUE(server_->Start());
+
+ scoped_ptr<URLRequest> request = context.CreateRequest(
+ server_->GetURL("/infinite"), DEFAULT_PRIORITY, &cancel_delegate);
+ request->Start();
+ cancel_delegate.WaitUntilDone();
+}
+
+struct CertificateValuesEntry {
+ const EmbeddedTestServer::ServerCertificate server_cert;
+ const bool is_expired;
+ const char* common_name;
+ const char* root;
+};
+
+const CertificateValuesEntry kCertificateValuesEntry[] = {
+ {EmbeddedTestServer::CERT_OK, false, "127.0.0.1", "Test Root CA"},
+ {EmbeddedTestServer::CERT_MISMATCHED_NAME, false, "127.0.0.1",
+ "Test Root CA"},
+ {EmbeddedTestServer::CERT_COMMON_NAME_IS_DOMAIN, false, "localhost",
+ "Test Root CA"},
+ {EmbeddedTestServer::CERT_EXPIRED, true, "127.0.0.1", "Test Root CA"},
+ {EmbeddedTestServer::CERT_CHAIN_WRONG_ROOT, false, "127.0.0.1", "B CA"},
+#if !defined(OS_WIN)
+ {EmbeddedTestServer::CERT_BAD_VALIDITY, true, "Leaf Certificate",
+ "Test Root CA"},
+#endif
+};
+
+TEST_P(EmbeddedTestServerTest, GetCertificate) {
+ if (GetParam() != EmbeddedTestServer::TYPE_HTTPS)
+ return;
+
+ for (const auto& certEntry : kCertificateValuesEntry) {
+ server_->SetSSLConfig(certEntry.server_cert);
+ scoped_refptr<X509Certificate> cert = server_->GetCertificate();
+ DCHECK(cert.get());
+ EXPECT_EQ(cert->HasExpired(), certEntry.is_expired);
+ EXPECT_EQ(cert->subject().common_name, certEntry.common_name);
+ EXPECT_EQ(cert->issuer().common_name, certEntry.root);
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(EmbeddedTestServerTestInstantiation,
+ EmbeddedTestServerTest,
+ testing::Values(EmbeddedTestServer::TYPE_HTTP,
+ EmbeddedTestServer::TYPE_HTTPS));
+
// Below test exercises EmbeddedTestServer's ability to cope with the situation
// where there is no MessageLoop available on the thread at EmbeddedTestServer
// initialization and/or destruction.
-typedef std::tr1::tuple<bool, bool> ThreadingTestParams;
+typedef std::tr1::tuple<bool, bool, EmbeddedTestServer::Type>
+ ThreadingTestParams;
class EmbeddedTestServerThreadingTest
- : public testing::TestWithParam<ThreadingTestParams> {};
+ : public testing::TestWithParam<ThreadingTestParams> {
+ void SetUp() override {
+#if defined(USE_NSS_CERTS) || defined(OS_IOS)
+ // This is needed so NSS's HTTP client functions are initialized on the
+ // right thread. These tests create SSLClientSockets on a different thread.
+ // TODO(davidben): Initialization can't be deferred to SSLClientSocket. See
+ // https://crbug.com/539520.
+ crypto::EnsureNSSInit();
+ EnsureNSSHttpIOInit();
+#endif
+ }
+
+ void TearDown() override {
+#if defined(USE_NSS_CERTS) || defined(OS_IOS)
+ ShutdownNSSHttpIO();
+#endif
+ }
+};
class EmbeddedTestServerThreadingTestDelegate
: public base::PlatformThread::Delegate,
@@ -340,9 +516,11 @@ class EmbeddedTestServerThreadingTestDelegate
public:
EmbeddedTestServerThreadingTestDelegate(
bool message_loop_present_on_initialize,
- bool message_loop_present_on_shutdown)
+ bool message_loop_present_on_shutdown,
+ EmbeddedTestServer::Type type)
: message_loop_present_on_initialize_(message_loop_present_on_initialize),
- message_loop_present_on_shutdown_(message_loop_present_on_shutdown) {}
+ message_loop_present_on_shutdown_(message_loop_present_on_shutdown),
+ type_(type) {}
// base::PlatformThread::Delegate:
void ThreadMain() override {
@@ -358,10 +536,10 @@ class EmbeddedTestServerThreadingTestDelegate
loop.reset(new base::MessageLoopForIO);
// Create the test server instance.
- EmbeddedTestServer server;
+ EmbeddedTestServer server(type_);
base::FilePath src_dir;
ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
- ASSERT_TRUE(server.InitializeAndWaitUntilReady());
+ ASSERT_TRUE(server.Start());
// Make a request and wait for the reply.
if (!loop)
@@ -388,8 +566,9 @@ class EmbeddedTestServerThreadingTestDelegate
}
private:
- bool message_loop_present_on_initialize_;
- bool message_loop_present_on_shutdown_;
+ const bool message_loop_present_on_initialize_;
+ const bool message_loop_present_on_shutdown_;
+ const EmbeddedTestServer::Type type_;
DISALLOW_COPY_AND_ASSIGN(EmbeddedTestServerThreadingTestDelegate);
};
@@ -400,15 +579,19 @@ TEST_P(EmbeddedTestServerThreadingTest, RunTest) {
// main test thread.
base::PlatformThreadHandle thread_handle;
EmbeddedTestServerThreadingTestDelegate delegate(
- std::tr1::get<0>(GetParam()),
- std::tr1::get<1>(GetParam()));
+ std::tr1::get<0>(GetParam()), std::tr1::get<1>(GetParam()),
+ std::tr1::get<2>(GetParam()));
ASSERT_TRUE(base::PlatformThread::Create(0, &delegate, &thread_handle));
base::PlatformThread::Join(thread_handle);
}
-INSTANTIATE_TEST_CASE_P(EmbeddedTestServerThreadingTestInstantiation,
- EmbeddedTestServerThreadingTest,
- testing::Combine(testing::Bool(), testing::Bool()));
+INSTANTIATE_TEST_CASE_P(
+ EmbeddedTestServerThreadingTestInstantiation,
+ EmbeddedTestServerThreadingTest,
+ testing::Combine(testing::Bool(),
+ testing::Bool(),
+ testing::Values(EmbeddedTestServer::TYPE_HTTP,
+ EmbeddedTestServer::TYPE_HTTPS)));
} // namespace test_server
} // namespace net
diff --git a/net/test/embedded_test_server/http_connection.cc b/net/test/embedded_test_server/http_connection.cc
index 83d2e9f..e60e945 100644
--- a/net/test/embedded_test_server/http_connection.cc
+++ b/net/test/embedded_test_server/http_connection.cc
@@ -6,7 +6,6 @@
#include "net/base/net_errors.h"
#include "net/socket/stream_socket.h"
-#include "net/test/embedded_test_server/http_response.h"
namespace net {
namespace test_server {
@@ -20,9 +19,8 @@ HttpConnection::HttpConnection(scoped_ptr<StreamSocket> socket,
HttpConnection::~HttpConnection() {
}
-void HttpConnection::SendResponse(scoped_ptr<HttpResponse> response,
- const base::Closure& callback) {
- const std::string response_string = response->ToResponseString();
+void HttpConnection::SendResponseBytes(const std::string& response_string,
+ const SendCompleteCallback& callback) {
if (response_string.length() > 0) {
scoped_refptr<DrainableIOBuffer> write_buf(new DrainableIOBuffer(
new StringIOBuffer(response_string), response_string.length()));
@@ -42,6 +40,8 @@ void HttpConnection::SendInternal(const base::Closure& callback,
if (rv == ERR_IO_PENDING)
return;
+ if (rv < 0)
+ break;
buf->DidConsume(rv);
}
@@ -53,6 +53,10 @@ void HttpConnection::SendInternal(const base::Closure& callback,
void HttpConnection::OnSendInternalDone(const base::Closure& callback,
scoped_refptr<DrainableIOBuffer> buf,
int rv) {
+ if (rv < 0) {
+ callback.Run();
+ return;
+ }
buf->DidConsume(rv);
SendInternal(callback, buf);
}
diff --git a/net/test/embedded_test_server/http_connection.h b/net/test/embedded_test_server/http_connection.h
index 0668f0d..ee304eb 100644
--- a/net/test/embedded_test_server/http_connection.h
+++ b/net/test/embedded_test_server/http_connection.h
@@ -13,6 +13,7 @@
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
namespace net {
@@ -37,9 +38,9 @@ class HttpConnection {
const HandleRequestCallback& callback);
~HttpConnection();
- // Sends the HTTP response to the client.
- void SendResponse(scoped_ptr<HttpResponse> response,
- const base::Closure& callback);
+ // Sends the |response_string| to the client and calls |callback| once done.
+ void SendResponseBytes(const std::string& response_string,
+ const SendCompleteCallback& callback);
// Accepts raw chunk of data from the client. Internally, passes it to the
// HttpRequestParser class. If a request is parsed, then |callback_| is
diff --git a/net/test/embedded_test_server/http_request.cc b/net/test/embedded_test_server/http_request.cc
index 9e0c80f..23a4f92 100644
--- a/net/test/embedded_test_server/http_request.cc
+++ b/net/test/embedded_test_server/http_request.cc
@@ -11,6 +11,7 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "net/http/http_chunked_decoder.h"
+#include "url/gurl.h"
namespace net {
namespace test_server {
@@ -35,6 +36,11 @@ HttpRequest::HttpRequest() : method(METHOD_UNKNOWN),
HttpRequest::~HttpRequest() {
}
+GURL HttpRequest::GetURL() const {
+ // TODO(svaldez): Use real URL from the EmbeddedTestServer.
+ return GURL("http://localhost" + relative_url);
+}
+
HttpRequestParser::HttpRequestParser()
: http_request_(new HttpRequest()),
buffer_position_(0),
@@ -98,7 +104,15 @@ HttpRequestParser::ParseResult HttpRequestParser::ParseHeaders() {
// Address.
// Don't build an absolute URL as the parser does not know (should not
// know) anything about the server address.
- http_request_->relative_url = header_line_tokens[1];
+ GURL url(header_line_tokens[1]);
+ if (url.is_valid()) {
+ http_request_->relative_url = url.path();
+ } else if (header_line_tokens[1][0] == '/') {
+ http_request_->relative_url = header_line_tokens[1];
+ } else {
+ http_request_->relative_url = "/" + header_line_tokens[1];
+ }
+
// Protocol.
const std::string protocol = base::ToLowerASCII(header_line_tokens[2]);
CHECK(protocol == "http/1.0" || protocol == "http/1.1") <<
@@ -139,7 +153,10 @@ HttpRequestParser::ParseResult HttpRequestParser::ParseHeaders() {
const bool success = base::StringToSizeT(
http_request_->headers["Content-Length"],
&declared_content_length_);
- DCHECK(success) << "Malformed Content-Length header's value.";
+ if (!success) {
+ declared_content_length_ = 0;
+ LOG(WARNING) << "Malformed Content-Length header's value.";
+ }
} else if (http_request_->headers.count("Transfer-Encoding") > 0) {
if (http_request_->headers["Transfer-Encoding"] == "chunked") {
http_request_->has_content = true;
@@ -224,9 +241,11 @@ HttpMethod HttpRequestParser::GetMethodType(const std::string& token) const {
return METHOD_DELETE;
} else if (token == "patch") {
return METHOD_PATCH;
+ } else if (token == "connect") {
+ return METHOD_CONNECT;
}
- NOTREACHED() << "Method not implemented: " << token;
- return METHOD_UNKNOWN;
+ LOG(WARNING) << "Method not implemented: " << token;
+ return METHOD_GET;
}
} // namespace test_server
diff --git a/net/test/embedded_test_server/http_request.h b/net/test/embedded_test_server/http_request.h
index c299378..9a142e8 100644
--- a/net/test/embedded_test_server/http_request.h
+++ b/net/test/embedded_test_server/http_request.h
@@ -11,6 +11,7 @@
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_piece.h"
+#include "url/gurl.h"
namespace net {
@@ -27,6 +28,7 @@ enum HttpMethod {
METHOD_PUT,
METHOD_DELETE,
METHOD_PATCH,
+ METHOD_CONNECT,
};
// Represents a HTTP request. Since it can be big, use scoped_ptr to pass it
@@ -36,6 +38,10 @@ struct HttpRequest {
HttpRequest();
~HttpRequest();
+ // Returns a GURL as a convenience to extract the path and query strings.
+ // TODO(svaldez): Use provided URL if available.
+ GURL GetURL() const;
+
std::string relative_url; // Starts with '/'. Example: "/test?query=foo"
HttpMethod method;
std::string method_string;
diff --git a/net/test/embedded_test_server/http_response.cc b/net/test/embedded_test_server/http_response.cc
index 04155b5..51e7561 100644
--- a/net/test/embedded_test_server/http_response.cc
+++ b/net/test/embedded_test_server/http_response.cc
@@ -15,6 +15,26 @@ namespace test_server {
HttpResponse::~HttpResponse() {
}
+RawHttpResponse::RawHttpResponse(const std::string& headers,
+ const std::string& contents)
+ : headers_(headers), contents_(contents) {}
+
+RawHttpResponse::~RawHttpResponse() {}
+
+void RawHttpResponse::SendResponse(const SendBytesCallback& send,
+ const SendCompleteCallback& done) {
+ std::string response;
+ if (!headers_.empty())
+ response = headers_ + "\r\n" + contents_;
+ else
+ response = contents_;
+ send.Run(response, done);
+}
+
+void RawHttpResponse::AddHeader(const std::string& key_value_pair) {
+ headers_.append(base::StringPrintf("%s\r\n", key_value_pair.c_str()));
+}
+
BasicHttpResponse::BasicHttpResponse() : code_(HTTP_OK) {
}
@@ -33,11 +53,10 @@ std::string BasicHttpResponse::ToResponseString() const {
code_,
http_reason_phrase.c_str());
base::StringAppendF(&response_builder, "Connection: close\r\n");
- base::StringAppendF(&response_builder,
- "Content-Length: %" PRIuS "\r\n",
+
+ base::StringAppendF(&response_builder, "Content-Length: %" PRIuS "\r\n",
content_.size());
- base::StringAppendF(&response_builder,
- "Content-Type: %s\r\n",
+ base::StringAppendF(&response_builder, "Content-Type: %s\r\n",
content_type_.c_str());
for (size_t i = 0; i < custom_headers_.size(); ++i) {
const std::string& header_name = custom_headers_[i].first;
@@ -54,5 +73,10 @@ std::string BasicHttpResponse::ToResponseString() const {
return response_builder + content_;
}
+void BasicHttpResponse::SendResponse(const SendBytesCallback& send,
+ const SendCompleteCallback& done) {
+ send.Run(ToResponseString(), done);
+}
+
} // namespace test_server
} // namespace net
diff --git a/net/test/embedded_test_server/http_response.h b/net/test/embedded_test_server/http_response.h
index d4df75b..cc4be86 100644
--- a/net/test/embedded_test_server/http_response.h
+++ b/net/test/embedded_test_server/http_response.h
@@ -5,10 +5,10 @@
#ifndef NET_TEST_EMBEDDED_TEST_SERVER_HTTP_RESPONSE_H_
#define NET_TEST_EMBEDDED_TEST_SERVER_HTTP_RESPONSE_H_
-#include <map>
#include <string>
#include "base/basictypes.h"
+#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/strings/string_split.h"
#include "net/http/http_status_code.h"
@@ -16,19 +16,30 @@
namespace net {
namespace test_server {
+// Callback called when the response is done being sent.
+using SendCompleteCallback = base::Callback<void(void)>;
+
+// Callback called when the response is ready to be sent that takes the
+// |response| that is being sent along with the callback |write_done| that is
+// called when the response has been fully written.
+using SendBytesCallback =
+ base::Callback<void(const std::string& response,
+ const SendCompleteCallback& write_done)>;
+
// Interface for HTTP response implementations.
class HttpResponse{
public:
virtual ~HttpResponse();
- // Returns raw contents to be written to the network socket
- // in response. If you intend to make this a valid HTTP response,
- // it should start with "HTTP/x.x" line, followed by response headers.
- virtual std::string ToResponseString() const = 0;
+ // |send| will send the specified data to the network socket, and invoke
+ // |write_done| when complete. When the entire response has been sent,
+ // |done| must be called.
+ virtual void SendResponse(const SendBytesCallback& send,
+ const SendCompleteCallback& done) = 0;
};
// This class is used to handle basic HTTP responses with commonly used
-// response headers such as "Content-Type".
+// response headers such as "Content-Type". Sends the response immediately.
class BasicHttpResponse : public HttpResponse {
public:
BasicHttpResponse();
@@ -54,7 +65,10 @@ class BasicHttpResponse : public HttpResponse {
}
// Generates and returns a http response string.
- std::string ToResponseString() const override;
+ std::string ToResponseString() const;
+
+ void SendResponse(const SendBytesCallback& send,
+ const SendCompleteCallback& done) override;
private:
HttpStatusCode code_;
@@ -65,6 +79,23 @@ class BasicHttpResponse : public HttpResponse {
DISALLOW_COPY_AND_ASSIGN(BasicHttpResponse);
};
+class RawHttpResponse : public HttpResponse {
+ public:
+ RawHttpResponse(const std::string& headers, const std::string& contents);
+ ~RawHttpResponse() override;
+
+ void SendResponse(const SendBytesCallback& send,
+ const SendCompleteCallback& done) override;
+
+ void AddHeader(const std::string& key_value_pair);
+
+ private:
+ std::string headers_;
+ const std::string contents_;
+
+ DISALLOW_COPY_AND_ASSIGN(RawHttpResponse);
+};
+
} // namespace test_server
} // namespace net
diff --git a/net/test/embedded_test_server/request_handler_util.cc b/net/test/embedded_test_server/request_handler_util.cc
new file mode 100644
index 0000000..aacfcc5
--- /dev/null
+++ b/net/test/embedded_test_server/request_handler_util.cc
@@ -0,0 +1,228 @@
+// 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 "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/threading/thread_restrictions.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 "url/gurl.h"
+
+namespace net {
+namespace test_server {
+namespace {
+
+const UnescapeRule::Type kUnescapeAll =
+ UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS |
+ UnescapeRule::SPOOFING_AND_CONTROL_CHARS |
+ UnescapeRule::REPLACE_PLUS_WITH_SPACE;
+
+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(".exe")))
+ return "application/octet-stream";
+ if (path.MatchesExtension(FILE_PATH_LITERAL(".gif")))
+ return "image/gif";
+ 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(".html")) ||
+ path.MatchesExtension(FILE_PATH_LITERAL(".htm"))) {
+ return "text/html";
+ }
+ return "";
+}
+
+} // namespace
+
+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);
+}
+
+scoped_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()) {
+ queries[net::UnescapeURLComponent(it.GetKey(), kUnescapeAll)].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|.
+scoped_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::ThreadRestrictions::ScopedAllowIO allow_io;
+
+ // 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);
+
+ scoped_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 failed_response.Pass();
+ }
+ }
+
+ if (query.find("expected_headers") != query.end()) {
+ for (const auto& header : query["expected_headers"]) {
+ if (header.find(":") == std::string::npos)
+ return failed_response.Pass();
+ 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 failed_response.Pass();
+ }
+ }
+ }
+
+ // 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 failed_response.Pass();
+ 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 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 nullptr;
+
+ return make_scoped_ptr(
+ new RawHttpResponse(headers_contents, file_contents));
+ }
+
+ scoped_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 http_response.Pass();
+}
+
+} // namespace test_server
+} // namespace net
diff --git a/net/test/embedded_test_server/request_handler_util.h b/net/test/embedded_test_server/request_handler_util.h
new file mode 100644
index 0000000..2ed8d13
--- /dev/null
+++ b/net/test/embedded_test_server/request_handler_util.h
@@ -0,0 +1,63 @@
+// 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.
+
+#ifndef NET_TEST_EMBEDDED_TEST_SERVER_REQUEST_HANDLER_UTIL_H_
+#define NET_TEST_EMBEDDED_TEST_SERVER_REQUEST_HANDLER_UTIL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_split.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_response.h"
+
+namespace url {
+class GURL;
+}
+
+namespace net {
+namespace test_server {
+struct HttpRequest;
+
+// This file is only meant for compatibility with testserver.py. No
+// additional handlers should be added here that don't affect multiple
+// distinct tests.
+
+using RequestQuery = std::map<std::string, std::vector<std::string>>;
+
+// Return whether |request| starts with a URL path of |url|.
+bool ShouldHandle(const HttpRequest& request, const std::string& prefix_path);
+
+// Calls |handler| if the |request| URL starts with |prefix|.
+scoped_ptr<HttpResponse> HandlePrefixedRequest(
+ const std::string& prefix,
+ const EmbeddedTestServer::HandleRequestCallback& handler,
+ const HttpRequest& request);
+
+// Parses |url| to get the query and places it into a map.
+RequestQuery ParseQuery(const GURL& url);
+
+// Returns a path that serves the contents of the file at |original_path|
+// with all the text matching the elements of |text_to_replace| replaced
+// with the corresponding values. The path is returned in |replacement_path|.
+// The result path is only usable by HandleFileRequest which will perform the
+// actual replacements of the file contents.
+// TODO(svaldez): Modify to return |replacement_path| instead of passing by
+// reference.
+void GetFilePathWithReplacements(const std::string& original_path,
+ const base::StringPairs& text_to_replace,
+ std::string* replacement_path);
+
+// Handles |request| by serving a file from under |server_root|.
+scoped_ptr<HttpResponse> HandleFileRequest(const base::FilePath& server_root,
+ const HttpRequest& request);
+
+} // namespace test_server
+} // namespace net
+
+#endif // NET_TEST_EMBEDDED_TEST_SERVER_REQUEST_HANDLER_UTIL_H_