1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/test/spawned_test_server/base_test_server.h"
10 #include "base/base64.h"
11 #include "base/files/file_util.h"
12 #include "base/json/json_reader.h"
13 #include "base/logging.h"
14 #include "base/path_service.h"
15 #include "base/values.h"
16 #include "net/base/address_list.h"
17 #include "net/base/host_port_pair.h"
18 #include "net/base/net_errors.h"
19 #include "net/base/port_util.h"
20 #include "net/base/test_completion_callback.h"
21 #include "net/cert/test_root_certs.h"
22 #include "net/dns/host_resolver.h"
23 #include "net/log/net_log.h"
30 std::string
GetHostname(BaseTestServer::Type type
,
31 const BaseTestServer::SSLOptions
& options
) {
32 if (BaseTestServer::UsingSSL(type
)) {
33 if (options
.server_certificate
==
34 BaseTestServer::SSLOptions::CERT_MISMATCHED_NAME
||
35 options
.server_certificate
==
36 BaseTestServer::SSLOptions::CERT_COMMON_NAME_IS_DOMAIN
) {
37 // For |CERT_MISMATCHED_NAME|, return a different hostname string
38 // that resolves to the same hostname. For
39 // |CERT_COMMON_NAME_IS_DOMAIN|, the certificate is issued for
40 // "localhost" instead of "127.0.0.1".
45 // Use the 127.0.0.1 as default.
46 return BaseTestServer::kLocalhost
;
49 std::string
GetClientCertType(SSLClientCertType type
) {
51 case CLIENT_CERT_RSA_SIGN
:
53 case CLIENT_CERT_ECDSA_SIGN
:
61 void GetKeyExchangesList(int key_exchange
, base::ListValue
* values
) {
62 if (key_exchange
& BaseTestServer::SSLOptions::KEY_EXCHANGE_RSA
)
63 values
->Append(new base::StringValue("rsa"));
64 if (key_exchange
& BaseTestServer::SSLOptions::KEY_EXCHANGE_DHE_RSA
)
65 values
->Append(new base::StringValue("dhe_rsa"));
66 if (key_exchange
& BaseTestServer::SSLOptions::KEY_EXCHANGE_ECDHE_RSA
)
67 values
->Append(new base::StringValue("ecdhe_rsa"));
70 void GetCiphersList(int cipher
, base::ListValue
* values
) {
71 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_RC4
)
72 values
->Append(new base::StringValue("rc4"));
73 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_AES128
)
74 values
->Append(new base::StringValue("aes128"));
75 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_AES256
)
76 values
->Append(new base::StringValue("aes256"));
77 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_3DES
)
78 values
->Append(new base::StringValue("3des"));
79 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_AES128GCM
)
80 values
->Append(new base::StringValue("aes128gcm"));
83 base::StringValue
* GetTLSIntoleranceType(
84 BaseTestServer::SSLOptions::TLSIntoleranceType type
) {
86 case BaseTestServer::SSLOptions::TLS_INTOLERANCE_ALERT
:
87 return new base::StringValue("alert");
88 case BaseTestServer::SSLOptions::TLS_INTOLERANCE_CLOSE
:
89 return new base::StringValue("close");
90 case BaseTestServer::SSLOptions::TLS_INTOLERANCE_RESET
:
91 return new base::StringValue("reset");
94 return new base::StringValue("");
100 BaseTestServer::SSLOptions::SSLOptions()
101 : server_certificate(CERT_OK
),
102 ocsp_status(OCSP_OK
),
104 request_client_certificate(false),
105 key_exchanges(SSLOptions::KEY_EXCHANGE_ANY
),
106 bulk_ciphers(SSLOptions::BULK_CIPHER_ANY
),
107 record_resume(false),
108 tls_intolerant(TLS_INTOLERANT_NONE
),
109 tls_intolerance_type(TLS_INTOLERANCE_ALERT
),
110 fallback_scsv_enabled(false),
111 staple_ocsp_response(false),
112 ocsp_server_unavailable(false),
114 alert_after_handshake(false) {
117 BaseTestServer::SSLOptions::SSLOptions(
118 BaseTestServer::SSLOptions::ServerCertificate cert
)
119 : server_certificate(cert
),
120 ocsp_status(OCSP_OK
),
122 request_client_certificate(false),
123 key_exchanges(SSLOptions::KEY_EXCHANGE_ANY
),
124 bulk_ciphers(SSLOptions::BULK_CIPHER_ANY
),
125 record_resume(false),
126 tls_intolerant(TLS_INTOLERANT_NONE
),
127 tls_intolerance_type(TLS_INTOLERANCE_ALERT
),
128 fallback_scsv_enabled(false),
129 staple_ocsp_response(false),
130 ocsp_server_unavailable(false),
132 alert_after_handshake(false) {
135 BaseTestServer::SSLOptions::~SSLOptions() {}
137 base::FilePath
BaseTestServer::SSLOptions::GetCertificateFile() const {
138 switch (server_certificate
) {
140 case CERT_MISMATCHED_NAME
:
141 return base::FilePath(FILE_PATH_LITERAL("ok_cert.pem"));
142 case CERT_COMMON_NAME_IS_DOMAIN
:
143 return base::FilePath(FILE_PATH_LITERAL("localhost_cert.pem"));
145 return base::FilePath(FILE_PATH_LITERAL("expired_cert.pem"));
146 case CERT_CHAIN_WRONG_ROOT
:
147 // This chain uses its own dedicated test root certificate to avoid
148 // side-effects that may affect testing.
149 return base::FilePath(FILE_PATH_LITERAL("redundant-server-chain.pem"));
151 return base::FilePath();
155 return base::FilePath();
158 std::string
BaseTestServer::SSLOptions::GetOCSPArgument() const {
159 if (server_certificate
!= CERT_AUTO
)
160 return std::string();
162 switch (ocsp_status
) {
169 case OCSP_UNAUTHORIZED
:
170 return "unauthorized";
175 return std::string();
179 const char BaseTestServer::kLocalhost
[] = "127.0.0.1";
181 BaseTestServer::BaseTestServer(Type type
, const std::string
& host
)
184 log_to_console_(false),
185 ws_basic_auth_(false),
186 no_anonymous_ftp_user_(false) {
190 BaseTestServer::BaseTestServer(Type type
, const SSLOptions
& ssl_options
)
191 : ssl_options_(ssl_options
),
194 log_to_console_(false),
195 ws_basic_auth_(false),
196 no_anonymous_ftp_user_(false) {
197 DCHECK(UsingSSL(type
));
198 Init(GetHostname(type
, ssl_options
));
201 BaseTestServer::~BaseTestServer() {}
203 const HostPortPair
& BaseTestServer::host_port_pair() const {
205 return host_port_pair_
;
208 const base::DictionaryValue
& BaseTestServer::server_data() const {
210 DCHECK(server_data_
.get());
211 return *server_data_
;
214 std::string
BaseTestServer::GetScheme() const {
231 return std::string();
234 bool BaseTestServer::GetAddressList(AddressList
* address_list
) const {
235 DCHECK(address_list
);
237 scoped_ptr
<HostResolver
> resolver(HostResolver::CreateDefaultResolver(NULL
));
238 HostResolver::RequestInfo
info(host_port_pair_
);
239 // Limit the lookup to IPv4. When started with the default
240 // address of kLocalhost, testserver.py only supports IPv4.
241 // If a custom hostname is used, it's possible that the test
242 // server will listen on both IPv4 and IPv6, so this will
243 // still work. The testserver does not support explicit
244 // IPv6 literal hostnames.
245 info
.set_address_family(ADDRESS_FAMILY_IPV4
);
246 TestCompletionCallback callback
;
247 int rv
= resolver
->Resolve(info
,
253 if (rv
== ERR_IO_PENDING
)
254 rv
= callback
.WaitForResult();
256 LOG(ERROR
) << "Failed to resolve hostname: " << host_port_pair_
.host();
262 uint16
BaseTestServer::GetPort() {
263 return host_port_pair_
.port();
266 void BaseTestServer::SetPort(uint16 port
) {
267 host_port_pair_
.set_port(port
);
270 GURL
BaseTestServer::GetURL(const std::string
& path
) const {
271 return GURL(GetScheme() + "://" + host_port_pair_
.ToString() + "/" + path
);
274 GURL
BaseTestServer::GetURLWithUser(const std::string
& path
,
275 const std::string
& user
) const {
276 return GURL(GetScheme() + "://" + user
+ "@" + host_port_pair_
.ToString() +
280 GURL
BaseTestServer::GetURLWithUserAndPassword(const std::string
& path
,
281 const std::string
& user
,
282 const std::string
& password
) const {
283 return GURL(GetScheme() + "://" + user
+ ":" + password
+ "@" +
284 host_port_pair_
.ToString() + "/" + path
);
288 bool BaseTestServer::GetFilePathWithReplacements(
289 const std::string
& original_file_path
,
290 const std::vector
<StringPair
>& text_to_replace
,
291 std::string
* replacement_path
) {
292 std::string new_file_path
= original_file_path
;
293 bool first_query_parameter
= true;
294 const std::vector
<StringPair
>::const_iterator end
= text_to_replace
.end();
295 for (std::vector
<StringPair
>::const_iterator it
= text_to_replace
.begin();
298 const std::string
& old_text
= it
->first
;
299 const std::string
& new_text
= it
->second
;
300 std::string base64_old
;
301 std::string base64_new
;
302 base::Base64Encode(old_text
, &base64_old
);
303 base::Base64Encode(new_text
, &base64_new
);
304 if (first_query_parameter
) {
305 new_file_path
+= "?";
306 first_query_parameter
= false;
308 new_file_path
+= "&";
310 new_file_path
+= "replace_text=";
311 new_file_path
+= base64_old
;
312 new_file_path
+= ":";
313 new_file_path
+= base64_new
;
316 *replacement_path
= new_file_path
;
320 bool BaseTestServer::LoadTestRootCert() const {
321 TestRootCerts
* root_certs
= TestRootCerts::GetInstance();
325 // Should always use absolute path to load the root certificate.
326 base::FilePath root_certificate_path
= certificates_dir_
;
327 if (!certificates_dir_
.IsAbsolute()) {
328 base::FilePath src_dir
;
329 if (!PathService::Get(base::DIR_SOURCE_ROOT
, &src_dir
))
331 root_certificate_path
= src_dir
.Append(certificates_dir_
);
334 return root_certs
->AddFromFile(
335 root_certificate_path
.AppendASCII("root_ca_cert.pem"));
338 void BaseTestServer::Init(const std::string
& host
) {
339 host_port_pair_
= HostPortPair(host
, 0);
341 // TODO(battre) Remove this after figuring out why the TestServer is flaky.
342 // http://crbug.com/96594
343 log_to_console_
= true;
346 void BaseTestServer::SetResourcePath(const base::FilePath
& document_root
,
347 const base::FilePath
& certificates_dir
) {
348 // This method shouldn't get called twice.
349 DCHECK(certificates_dir_
.empty());
350 document_root_
= document_root
;
351 certificates_dir_
= certificates_dir
;
352 DCHECK(!certificates_dir_
.empty());
355 bool BaseTestServer::ParseServerData(const std::string
& server_data
) {
356 VLOG(1) << "Server data: " << server_data
;
357 base::JSONReader json_reader
;
358 scoped_ptr
<base::Value
> value(json_reader
.ReadToValue(server_data
));
359 if (!value
.get() || !value
->IsType(base::Value::TYPE_DICTIONARY
)) {
360 LOG(ERROR
) << "Could not parse server data: "
361 << json_reader
.GetErrorMessage();
365 server_data_
.reset(static_cast<base::DictionaryValue
*>(value
.release()));
367 if (!server_data_
->GetInteger("port", &port
)) {
368 LOG(ERROR
) << "Could not find port value";
371 if ((port
<= 0) || (port
> kuint16max
)) {
372 LOG(ERROR
) << "Invalid port value: " << port
;
375 host_port_pair_
.set_port(port
);
380 bool BaseTestServer::SetupWhenServerStarted() {
381 DCHECK(host_port_pair_
.port());
383 if (UsingSSL(type_
) && !LoadTestRootCert())
387 allowed_port_
.reset(new ScopedPortException(host_port_pair_
.port()));
391 void BaseTestServer::CleanUpWhenStoppingServer() {
392 TestRootCerts
* root_certs
= TestRootCerts::GetInstance();
395 host_port_pair_
.set_port(0);
396 allowed_port_
.reset();
400 // Generates a dictionary of arguments to pass to the Python test server via
401 // the test server spawner, in the form of
402 // { argument-name: argument-value, ... }
403 // Returns false if an invalid configuration is specified.
404 bool BaseTestServer::GenerateArguments(base::DictionaryValue
* arguments
) const {
407 arguments
->SetString("host", host_port_pair_
.host());
408 arguments
->SetInteger("port", host_port_pair_
.port());
409 arguments
->SetString("data-dir", document_root_
.value());
411 if (VLOG_IS_ON(1) || log_to_console_
)
412 arguments
->Set("log-to-console", base::Value::CreateNullValue());
414 if (ws_basic_auth_
) {
415 DCHECK(type_
== TYPE_WS
|| type_
== TYPE_WSS
);
416 arguments
->Set("ws-basic-auth", base::Value::CreateNullValue());
419 if (no_anonymous_ftp_user_
) {
420 DCHECK_EQ(TYPE_FTP
, type_
);
421 arguments
->Set("no-anonymous-ftp-user", base::Value::CreateNullValue());
424 if (UsingSSL(type_
)) {
425 // Check the certificate arguments of the HTTPS server.
426 base::FilePath
certificate_path(certificates_dir_
);
427 base::FilePath
certificate_file(ssl_options_
.GetCertificateFile());
428 if (!certificate_file
.value().empty()) {
429 certificate_path
= certificate_path
.Append(certificate_file
);
430 if (certificate_path
.IsAbsolute() &&
431 !base::PathExists(certificate_path
)) {
432 LOG(ERROR
) << "Certificate path " << certificate_path
.value()
433 << " doesn't exist. Can't launch https server.";
436 arguments
->SetString("cert-and-key-file", certificate_path
.value());
439 // Check the client certificate related arguments.
440 if (ssl_options_
.request_client_certificate
)
441 arguments
->Set("ssl-client-auth", base::Value::CreateNullValue());
442 scoped_ptr
<base::ListValue
> ssl_client_certs(new base::ListValue());
444 std::vector
<base::FilePath
>::const_iterator it
;
445 for (it
= ssl_options_
.client_authorities
.begin();
446 it
!= ssl_options_
.client_authorities
.end(); ++it
) {
447 if (it
->IsAbsolute() && !base::PathExists(*it
)) {
448 LOG(ERROR
) << "Client authority path " << it
->value()
449 << " doesn't exist. Can't launch https server.";
452 ssl_client_certs
->Append(new base::StringValue(it
->value()));
455 if (ssl_client_certs
->GetSize())
456 arguments
->Set("ssl-client-ca", ssl_client_certs
.release());
458 scoped_ptr
<base::ListValue
> client_cert_types(new base::ListValue());
459 for (size_t i
= 0; i
< ssl_options_
.client_cert_types
.size(); i
++) {
460 client_cert_types
->Append(new base::StringValue(
461 GetClientCertType(ssl_options_
.client_cert_types
[i
])));
463 if (client_cert_types
->GetSize())
464 arguments
->Set("ssl-client-cert-type", client_cert_types
.release());
467 if (type_
== TYPE_HTTPS
) {
468 arguments
->Set("https", base::Value::CreateNullValue());
470 std::string ocsp_arg
= ssl_options_
.GetOCSPArgument();
471 if (!ocsp_arg
.empty())
472 arguments
->SetString("ocsp", ocsp_arg
);
474 if (ssl_options_
.cert_serial
!= 0) {
475 arguments
->SetInteger("cert-serial", ssl_options_
.cert_serial
);
478 // Check key exchange argument.
479 scoped_ptr
<base::ListValue
> key_exchange_values(new base::ListValue());
480 GetKeyExchangesList(ssl_options_
.key_exchanges
, key_exchange_values
.get());
481 if (key_exchange_values
->GetSize())
482 arguments
->Set("ssl-key-exchange", key_exchange_values
.release());
483 // Check bulk cipher argument.
484 scoped_ptr
<base::ListValue
> bulk_cipher_values(new base::ListValue());
485 GetCiphersList(ssl_options_
.bulk_ciphers
, bulk_cipher_values
.get());
486 if (bulk_cipher_values
->GetSize())
487 arguments
->Set("ssl-bulk-cipher", bulk_cipher_values
.release());
488 if (ssl_options_
.record_resume
)
489 arguments
->Set("https-record-resume", base::Value::CreateNullValue());
490 if (ssl_options_
.tls_intolerant
!= SSLOptions::TLS_INTOLERANT_NONE
) {
491 arguments
->SetInteger("tls-intolerant", ssl_options_
.tls_intolerant
);
492 arguments
->Set("tls-intolerance-type", GetTLSIntoleranceType(
493 ssl_options_
.tls_intolerance_type
));
495 if (ssl_options_
.fallback_scsv_enabled
)
496 arguments
->Set("fallback-scsv", base::Value::CreateNullValue());
497 if (!ssl_options_
.signed_cert_timestamps_tls_ext
.empty()) {
498 std::string b64_scts_tls_ext
;
499 base::Base64Encode(ssl_options_
.signed_cert_timestamps_tls_ext
,
501 arguments
->SetString("signed-cert-timestamps-tls-ext", b64_scts_tls_ext
);
503 if (ssl_options_
.staple_ocsp_response
)
504 arguments
->Set("staple-ocsp-response", base::Value::CreateNullValue());
505 if (ssl_options_
.ocsp_server_unavailable
) {
506 arguments
->Set("ocsp-server-unavailable",
507 base::Value::CreateNullValue());
509 if (ssl_options_
.enable_npn
)
510 arguments
->Set("enable-npn", base::Value::CreateNullValue());
511 if (ssl_options_
.alert_after_handshake
)
512 arguments
->Set("alert-after-handshake", base::Value::CreateNullValue());
515 return GenerateAdditionalArguments(arguments
);
518 bool BaseTestServer::GenerateAdditionalArguments(
519 base::DictionaryValue
* arguments
) const {