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/cert/x509_certificate.h"
23 #include "net/dns/host_resolver.h"
24 #include "net/log/net_log.h"
31 std::string
GetHostname(BaseTestServer::Type type
,
32 const BaseTestServer::SSLOptions
& options
) {
33 if (BaseTestServer::UsingSSL(type
)) {
34 if (options
.server_certificate
==
35 BaseTestServer::SSLOptions::CERT_MISMATCHED_NAME
||
36 options
.server_certificate
==
37 BaseTestServer::SSLOptions::CERT_COMMON_NAME_IS_DOMAIN
) {
38 // For |CERT_MISMATCHED_NAME|, return a different hostname string
39 // that resolves to the same hostname. For
40 // |CERT_COMMON_NAME_IS_DOMAIN|, the certificate is issued for
41 // "localhost" instead of "127.0.0.1".
46 // Use the 127.0.0.1 as default.
47 return BaseTestServer::kLocalhost
;
50 std::string
GetClientCertType(SSLClientCertType type
) {
52 case CLIENT_CERT_RSA_SIGN
:
54 case CLIENT_CERT_ECDSA_SIGN
:
62 void GetKeyExchangesList(int key_exchange
, base::ListValue
* values
) {
63 if (key_exchange
& BaseTestServer::SSLOptions::KEY_EXCHANGE_RSA
)
64 values
->Append(new base::StringValue("rsa"));
65 if (key_exchange
& BaseTestServer::SSLOptions::KEY_EXCHANGE_DHE_RSA
)
66 values
->Append(new base::StringValue("dhe_rsa"));
67 if (key_exchange
& BaseTestServer::SSLOptions::KEY_EXCHANGE_ECDHE_RSA
)
68 values
->Append(new base::StringValue("ecdhe_rsa"));
71 void GetCiphersList(int cipher
, base::ListValue
* values
) {
72 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_RC4
)
73 values
->Append(new base::StringValue("rc4"));
74 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_AES128
)
75 values
->Append(new base::StringValue("aes128"));
76 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_AES256
)
77 values
->Append(new base::StringValue("aes256"));
78 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_3DES
)
79 values
->Append(new base::StringValue("3des"));
80 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_AES128GCM
)
81 values
->Append(new base::StringValue("aes128gcm"));
84 base::StringValue
* GetTLSIntoleranceType(
85 BaseTestServer::SSLOptions::TLSIntoleranceType type
) {
87 case BaseTestServer::SSLOptions::TLS_INTOLERANCE_ALERT
:
88 return new base::StringValue("alert");
89 case BaseTestServer::SSLOptions::TLS_INTOLERANCE_CLOSE
:
90 return new base::StringValue("close");
91 case BaseTestServer::SSLOptions::TLS_INTOLERANCE_RESET
:
92 return new base::StringValue("reset");
95 return new base::StringValue("");
99 bool GetLocalCertificatesDir(const base::FilePath
& certificates_dir
,
100 base::FilePath
* local_certificates_dir
) {
101 if (certificates_dir
.IsAbsolute()) {
102 *local_certificates_dir
= certificates_dir
;
106 base::FilePath src_dir
;
107 if (!PathService::Get(base::DIR_SOURCE_ROOT
, &src_dir
))
110 *local_certificates_dir
= src_dir
.Append(certificates_dir
);
116 BaseTestServer::SSLOptions::SSLOptions()
117 : server_certificate(CERT_OK
),
118 ocsp_status(OCSP_OK
),
120 request_client_certificate(false),
121 key_exchanges(SSLOptions::KEY_EXCHANGE_ANY
),
122 bulk_ciphers(SSLOptions::BULK_CIPHER_ANY
),
123 record_resume(false),
124 tls_intolerant(TLS_INTOLERANT_NONE
),
125 tls_intolerance_type(TLS_INTOLERANCE_ALERT
),
126 fallback_scsv_enabled(false),
127 staple_ocsp_response(false),
128 ocsp_server_unavailable(false),
130 alert_after_handshake(false) {
133 BaseTestServer::SSLOptions::SSLOptions(
134 BaseTestServer::SSLOptions::ServerCertificate cert
)
135 : server_certificate(cert
),
136 ocsp_status(OCSP_OK
),
138 request_client_certificate(false),
139 key_exchanges(SSLOptions::KEY_EXCHANGE_ANY
),
140 bulk_ciphers(SSLOptions::BULK_CIPHER_ANY
),
141 record_resume(false),
142 tls_intolerant(TLS_INTOLERANT_NONE
),
143 tls_intolerance_type(TLS_INTOLERANCE_ALERT
),
144 fallback_scsv_enabled(false),
145 staple_ocsp_response(false),
146 ocsp_server_unavailable(false),
148 alert_after_handshake(false) {
151 BaseTestServer::SSLOptions::~SSLOptions() {}
153 base::FilePath
BaseTestServer::SSLOptions::GetCertificateFile() const {
154 switch (server_certificate
) {
156 case CERT_MISMATCHED_NAME
:
157 return base::FilePath(FILE_PATH_LITERAL("ok_cert.pem"));
158 case CERT_COMMON_NAME_IS_DOMAIN
:
159 return base::FilePath(FILE_PATH_LITERAL("localhost_cert.pem"));
161 return base::FilePath(FILE_PATH_LITERAL("expired_cert.pem"));
162 case CERT_CHAIN_WRONG_ROOT
:
163 // This chain uses its own dedicated test root certificate to avoid
164 // side-effects that may affect testing.
165 return base::FilePath(FILE_PATH_LITERAL("redundant-server-chain.pem"));
166 case CERT_BAD_VALIDITY
:
167 return base::FilePath(FILE_PATH_LITERAL("bad_validity.pem"));
169 return base::FilePath();
173 return base::FilePath();
176 std::string
BaseTestServer::SSLOptions::GetOCSPArgument() const {
177 if (server_certificate
!= CERT_AUTO
)
178 return std::string();
180 switch (ocsp_status
) {
187 case OCSP_UNAUTHORIZED
:
188 return "unauthorized";
193 return std::string();
197 const char BaseTestServer::kLocalhost
[] = "127.0.0.1";
199 BaseTestServer::BaseTestServer(Type type
, const std::string
& host
)
202 log_to_console_(false),
203 ws_basic_auth_(false),
204 no_anonymous_ftp_user_(false) {
208 BaseTestServer::BaseTestServer(Type type
, const SSLOptions
& ssl_options
)
209 : ssl_options_(ssl_options
),
212 log_to_console_(false),
213 ws_basic_auth_(false),
214 no_anonymous_ftp_user_(false) {
215 DCHECK(UsingSSL(type
));
216 Init(GetHostname(type
, ssl_options
));
219 BaseTestServer::~BaseTestServer() {}
221 const HostPortPair
& BaseTestServer::host_port_pair() const {
223 return host_port_pair_
;
226 const base::DictionaryValue
& BaseTestServer::server_data() const {
228 DCHECK(server_data_
.get());
229 return *server_data_
;
232 std::string
BaseTestServer::GetScheme() const {
249 return std::string();
252 bool BaseTestServer::GetAddressList(AddressList
* address_list
) const {
253 DCHECK(address_list
);
255 scoped_ptr
<HostResolver
> resolver(HostResolver::CreateDefaultResolver(NULL
));
256 HostResolver::RequestInfo
info(host_port_pair_
);
257 // Limit the lookup to IPv4. When started with the default
258 // address of kLocalhost, testserver.py only supports IPv4.
259 // If a custom hostname is used, it's possible that the test
260 // server will listen on both IPv4 and IPv6, so this will
261 // still work. The testserver does not support explicit
262 // IPv6 literal hostnames.
263 info
.set_address_family(ADDRESS_FAMILY_IPV4
);
264 TestCompletionCallback callback
;
265 int rv
= resolver
->Resolve(info
,
271 if (rv
== ERR_IO_PENDING
)
272 rv
= callback
.WaitForResult();
274 LOG(ERROR
) << "Failed to resolve hostname: " << host_port_pair_
.host();
280 uint16
BaseTestServer::GetPort() {
281 return host_port_pair_
.port();
284 void BaseTestServer::SetPort(uint16 port
) {
285 host_port_pair_
.set_port(port
);
288 GURL
BaseTestServer::GetURL(const std::string
& path
) const {
289 return GURL(GetScheme() + "://" + host_port_pair_
.ToString() + "/" + path
);
292 GURL
BaseTestServer::GetURLWithUser(const std::string
& path
,
293 const std::string
& user
) const {
294 return GURL(GetScheme() + "://" + user
+ "@" + host_port_pair_
.ToString() +
298 GURL
BaseTestServer::GetURLWithUserAndPassword(const std::string
& path
,
299 const std::string
& user
,
300 const std::string
& password
) const {
301 return GURL(GetScheme() + "://" + user
+ ":" + password
+ "@" +
302 host_port_pair_
.ToString() + "/" + path
);
306 bool BaseTestServer::GetFilePathWithReplacements(
307 const std::string
& original_file_path
,
308 const std::vector
<StringPair
>& text_to_replace
,
309 std::string
* replacement_path
) {
310 std::string new_file_path
= original_file_path
;
311 bool first_query_parameter
= true;
312 const std::vector
<StringPair
>::const_iterator end
= text_to_replace
.end();
313 for (std::vector
<StringPair
>::const_iterator it
= text_to_replace
.begin();
316 const std::string
& old_text
= it
->first
;
317 const std::string
& new_text
= it
->second
;
318 std::string base64_old
;
319 std::string base64_new
;
320 base::Base64Encode(old_text
, &base64_old
);
321 base::Base64Encode(new_text
, &base64_new
);
322 if (first_query_parameter
) {
323 new_file_path
+= "?";
324 first_query_parameter
= false;
326 new_file_path
+= "&";
328 new_file_path
+= "replace_text=";
329 new_file_path
+= base64_old
;
330 new_file_path
+= ":";
331 new_file_path
+= base64_new
;
334 *replacement_path
= new_file_path
;
338 bool BaseTestServer::LoadTestRootCert() const {
339 TestRootCerts
* root_certs
= TestRootCerts::GetInstance();
343 // Should always use absolute path to load the root certificate.
344 base::FilePath root_certificate_path
;
345 if (!GetLocalCertificatesDir(certificates_dir_
, &root_certificate_path
))
348 return root_certs
->AddFromFile(
349 root_certificate_path
.AppendASCII("root_ca_cert.pem"));
352 scoped_refptr
<X509Certificate
> BaseTestServer::GetCertificate() const {
353 base::FilePath certificate_path
;
354 if (!GetLocalCertificatesDir(certificates_dir_
, &certificate_path
))
357 base::FilePath
certificate_file(ssl_options_
.GetCertificateFile());
358 if (certificate_file
.value().empty())
361 certificate_path
= certificate_path
.Append(certificate_file
);
363 std::string cert_data
;
364 if (!base::ReadFileToString(certificate_path
, &cert_data
))
367 CertificateList certs_in_file
=
368 X509Certificate::CreateCertificateListFromBytes(
369 cert_data
.data(), cert_data
.size(),
370 X509Certificate::FORMAT_PEM_CERT_SEQUENCE
);
371 if (certs_in_file
.empty())
373 return certs_in_file
[0];
376 void BaseTestServer::Init(const std::string
& host
) {
377 host_port_pair_
= HostPortPair(host
, 0);
379 // TODO(battre) Remove this after figuring out why the TestServer is flaky.
380 // http://crbug.com/96594
381 log_to_console_
= true;
384 void BaseTestServer::SetResourcePath(const base::FilePath
& document_root
,
385 const base::FilePath
& certificates_dir
) {
386 // This method shouldn't get called twice.
387 DCHECK(certificates_dir_
.empty());
388 document_root_
= document_root
;
389 certificates_dir_
= certificates_dir
;
390 DCHECK(!certificates_dir_
.empty());
393 bool BaseTestServer::ParseServerData(const std::string
& server_data
) {
394 VLOG(1) << "Server data: " << server_data
;
395 base::JSONReader json_reader
;
396 scoped_ptr
<base::Value
> value(json_reader
.ReadToValue(server_data
));
397 if (!value
.get() || !value
->IsType(base::Value::TYPE_DICTIONARY
)) {
398 LOG(ERROR
) << "Could not parse server data: "
399 << json_reader
.GetErrorMessage();
403 server_data_
.reset(static_cast<base::DictionaryValue
*>(value
.release()));
405 if (!server_data_
->GetInteger("port", &port
)) {
406 LOG(ERROR
) << "Could not find port value";
409 if ((port
<= 0) || (port
> kuint16max
)) {
410 LOG(ERROR
) << "Invalid port value: " << port
;
413 host_port_pair_
.set_port(port
);
418 bool BaseTestServer::SetupWhenServerStarted() {
419 DCHECK(host_port_pair_
.port());
421 if (UsingSSL(type_
) && !LoadTestRootCert())
425 allowed_port_
.reset(new ScopedPortException(host_port_pair_
.port()));
429 void BaseTestServer::CleanUpWhenStoppingServer() {
430 TestRootCerts
* root_certs
= TestRootCerts::GetInstance();
433 host_port_pair_
.set_port(0);
434 allowed_port_
.reset();
438 // Generates a dictionary of arguments to pass to the Python test server via
439 // the test server spawner, in the form of
440 // { argument-name: argument-value, ... }
441 // Returns false if an invalid configuration is specified.
442 bool BaseTestServer::GenerateArguments(base::DictionaryValue
* arguments
) const {
445 arguments
->SetString("host", host_port_pair_
.host());
446 arguments
->SetInteger("port", host_port_pair_
.port());
447 arguments
->SetString("data-dir", document_root_
.value());
449 if (VLOG_IS_ON(1) || log_to_console_
)
450 arguments
->Set("log-to-console", base::Value::CreateNullValue());
452 if (ws_basic_auth_
) {
453 DCHECK(type_
== TYPE_WS
|| type_
== TYPE_WSS
);
454 arguments
->Set("ws-basic-auth", base::Value::CreateNullValue());
457 if (no_anonymous_ftp_user_
) {
458 DCHECK_EQ(TYPE_FTP
, type_
);
459 arguments
->Set("no-anonymous-ftp-user", base::Value::CreateNullValue());
462 if (UsingSSL(type_
)) {
463 // Check the certificate arguments of the HTTPS server.
464 base::FilePath
certificate_path(certificates_dir_
);
465 base::FilePath
certificate_file(ssl_options_
.GetCertificateFile());
466 if (!certificate_file
.value().empty()) {
467 certificate_path
= certificate_path
.Append(certificate_file
);
468 if (certificate_path
.IsAbsolute() &&
469 !base::PathExists(certificate_path
)) {
470 LOG(ERROR
) << "Certificate path " << certificate_path
.value()
471 << " doesn't exist. Can't launch https server.";
474 arguments
->SetString("cert-and-key-file", certificate_path
.value());
477 // Check the client certificate related arguments.
478 if (ssl_options_
.request_client_certificate
)
479 arguments
->Set("ssl-client-auth", base::Value::CreateNullValue());
480 scoped_ptr
<base::ListValue
> ssl_client_certs(new base::ListValue());
482 std::vector
<base::FilePath
>::const_iterator it
;
483 for (it
= ssl_options_
.client_authorities
.begin();
484 it
!= ssl_options_
.client_authorities
.end(); ++it
) {
485 if (it
->IsAbsolute() && !base::PathExists(*it
)) {
486 LOG(ERROR
) << "Client authority path " << it
->value()
487 << " doesn't exist. Can't launch https server.";
490 ssl_client_certs
->Append(new base::StringValue(it
->value()));
493 if (ssl_client_certs
->GetSize())
494 arguments
->Set("ssl-client-ca", ssl_client_certs
.release());
496 scoped_ptr
<base::ListValue
> client_cert_types(new base::ListValue());
497 for (size_t i
= 0; i
< ssl_options_
.client_cert_types
.size(); i
++) {
498 client_cert_types
->Append(new base::StringValue(
499 GetClientCertType(ssl_options_
.client_cert_types
[i
])));
501 if (client_cert_types
->GetSize())
502 arguments
->Set("ssl-client-cert-type", client_cert_types
.release());
505 if (type_
== TYPE_HTTPS
) {
506 arguments
->Set("https", base::Value::CreateNullValue());
508 std::string ocsp_arg
= ssl_options_
.GetOCSPArgument();
509 if (!ocsp_arg
.empty())
510 arguments
->SetString("ocsp", ocsp_arg
);
512 if (ssl_options_
.cert_serial
!= 0) {
513 arguments
->SetInteger("cert-serial", ssl_options_
.cert_serial
);
516 // Check key exchange argument.
517 scoped_ptr
<base::ListValue
> key_exchange_values(new base::ListValue());
518 GetKeyExchangesList(ssl_options_
.key_exchanges
, key_exchange_values
.get());
519 if (key_exchange_values
->GetSize())
520 arguments
->Set("ssl-key-exchange", key_exchange_values
.release());
521 // Check bulk cipher argument.
522 scoped_ptr
<base::ListValue
> bulk_cipher_values(new base::ListValue());
523 GetCiphersList(ssl_options_
.bulk_ciphers
, bulk_cipher_values
.get());
524 if (bulk_cipher_values
->GetSize())
525 arguments
->Set("ssl-bulk-cipher", bulk_cipher_values
.release());
526 if (ssl_options_
.record_resume
)
527 arguments
->Set("https-record-resume", base::Value::CreateNullValue());
528 if (ssl_options_
.tls_intolerant
!= SSLOptions::TLS_INTOLERANT_NONE
) {
529 arguments
->SetInteger("tls-intolerant", ssl_options_
.tls_intolerant
);
530 arguments
->Set("tls-intolerance-type", GetTLSIntoleranceType(
531 ssl_options_
.tls_intolerance_type
));
533 if (ssl_options_
.fallback_scsv_enabled
)
534 arguments
->Set("fallback-scsv", base::Value::CreateNullValue());
535 if (!ssl_options_
.signed_cert_timestamps_tls_ext
.empty()) {
536 std::string b64_scts_tls_ext
;
537 base::Base64Encode(ssl_options_
.signed_cert_timestamps_tls_ext
,
539 arguments
->SetString("signed-cert-timestamps-tls-ext", b64_scts_tls_ext
);
541 if (ssl_options_
.staple_ocsp_response
)
542 arguments
->Set("staple-ocsp-response", base::Value::CreateNullValue());
543 if (ssl_options_
.ocsp_server_unavailable
) {
544 arguments
->Set("ocsp-server-unavailable",
545 base::Value::CreateNullValue());
547 if (ssl_options_
.enable_npn
)
548 arguments
->Set("enable-npn", base::Value::CreateNullValue());
549 if (ssl_options_
.alert_after_handshake
)
550 arguments
->Set("alert-after-handshake", base::Value::CreateNullValue());
553 return GenerateAdditionalArguments(arguments
);
556 bool BaseTestServer::GenerateAdditionalArguments(
557 base::DictionaryValue
* arguments
) const {