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/net_log.h"
20 #include "net/base/net_util.h"
21 #include "net/base/test_completion_callback.h"
22 #include "net/cert/test_root_certs.h"
23 #include "net/dns/host_resolver.h"
30 std::string
GetHostname(BaseTestServer::Type type
,
31 const BaseTestServer::SSLOptions
& options
) {
32 if (BaseTestServer::UsingSSL(type
) &&
33 options
.server_certificate
==
34 BaseTestServer::SSLOptions::CERT_MISMATCHED_NAME
) {
35 // Return a different hostname string that resolves to the same hostname.
39 // Use the 127.0.0.1 as default.
40 return BaseTestServer::kLocalhost
;
43 std::string
GetClientCertType(SSLClientCertType type
) {
45 case CLIENT_CERT_RSA_SIGN
:
47 case CLIENT_CERT_DSS_SIGN
:
49 case CLIENT_CERT_ECDSA_SIGN
:
57 void GetKeyExchangesList(int key_exchange
, base::ListValue
* values
) {
58 if (key_exchange
& BaseTestServer::SSLOptions::KEY_EXCHANGE_RSA
)
59 values
->Append(new base::StringValue("rsa"));
60 if (key_exchange
& BaseTestServer::SSLOptions::KEY_EXCHANGE_DHE_RSA
)
61 values
->Append(new base::StringValue("dhe_rsa"));
64 void GetCiphersList(int cipher
, base::ListValue
* values
) {
65 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_RC4
)
66 values
->Append(new base::StringValue("rc4"));
67 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_AES128
)
68 values
->Append(new base::StringValue("aes128"));
69 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_AES256
)
70 values
->Append(new base::StringValue("aes256"));
71 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_3DES
)
72 values
->Append(new base::StringValue("3des"));
75 base::StringValue
* GetTLSIntoleranceType(
76 BaseTestServer::SSLOptions::TLSIntoleranceType type
) {
78 case BaseTestServer::SSLOptions::TLS_INTOLERANCE_ALERT
:
79 return new base::StringValue("alert");
80 case BaseTestServer::SSLOptions::TLS_INTOLERANCE_CLOSE
:
81 return new base::StringValue("close");
82 case BaseTestServer::SSLOptions::TLS_INTOLERANCE_RESET
:
83 return new base::StringValue("reset");
86 return new base::StringValue("");
92 BaseTestServer::SSLOptions::SSLOptions()
93 : server_certificate(CERT_OK
),
96 request_client_certificate(false),
97 key_exchanges(SSLOptions::KEY_EXCHANGE_ANY
),
98 bulk_ciphers(SSLOptions::BULK_CIPHER_ANY
),
100 tls_intolerant(TLS_INTOLERANT_NONE
),
101 tls_intolerance_type(TLS_INTOLERANCE_ALERT
),
102 fallback_scsv_enabled(false),
103 staple_ocsp_response(false),
104 ocsp_server_unavailable(false),
106 disable_session_cache(false) {
109 BaseTestServer::SSLOptions::SSLOptions(
110 BaseTestServer::SSLOptions::ServerCertificate cert
)
111 : server_certificate(cert
),
112 ocsp_status(OCSP_OK
),
114 request_client_certificate(false),
115 key_exchanges(SSLOptions::KEY_EXCHANGE_ANY
),
116 bulk_ciphers(SSLOptions::BULK_CIPHER_ANY
),
117 record_resume(false),
118 tls_intolerant(TLS_INTOLERANT_NONE
),
119 tls_intolerance_type(TLS_INTOLERANCE_ALERT
),
120 fallback_scsv_enabled(false),
121 staple_ocsp_response(false),
122 ocsp_server_unavailable(false),
124 disable_session_cache(false) {
127 BaseTestServer::SSLOptions::~SSLOptions() {}
129 base::FilePath
BaseTestServer::SSLOptions::GetCertificateFile() const {
130 switch (server_certificate
) {
132 case CERT_MISMATCHED_NAME
:
133 return base::FilePath(FILE_PATH_LITERAL("ok_cert.pem"));
135 return base::FilePath(FILE_PATH_LITERAL("expired_cert.pem"));
136 case CERT_CHAIN_WRONG_ROOT
:
137 // This chain uses its own dedicated test root certificate to avoid
138 // side-effects that may affect testing.
139 return base::FilePath(FILE_PATH_LITERAL("redundant-server-chain.pem"));
141 return base::FilePath();
145 return base::FilePath();
148 std::string
BaseTestServer::SSLOptions::GetOCSPArgument() const {
149 if (server_certificate
!= CERT_AUTO
)
150 return std::string();
152 switch (ocsp_status
) {
159 case OCSP_UNAUTHORIZED
:
160 return "unauthorized";
165 return std::string();
169 const char BaseTestServer::kLocalhost
[] = "127.0.0.1";
171 BaseTestServer::BaseTestServer(Type type
, const std::string
& host
)
174 log_to_console_(false),
175 ws_basic_auth_(false) {
179 BaseTestServer::BaseTestServer(Type type
, const SSLOptions
& ssl_options
)
180 : ssl_options_(ssl_options
),
183 log_to_console_(false),
184 ws_basic_auth_(false) {
185 DCHECK(UsingSSL(type
));
186 Init(GetHostname(type
, ssl_options
));
189 BaseTestServer::~BaseTestServer() {}
191 const HostPortPair
& BaseTestServer::host_port_pair() const {
193 return host_port_pair_
;
196 const base::DictionaryValue
& BaseTestServer::server_data() const {
198 DCHECK(server_data_
.get());
199 return *server_data_
;
202 std::string
BaseTestServer::GetScheme() const {
219 return std::string();
222 bool BaseTestServer::GetAddressList(AddressList
* address_list
) const {
223 DCHECK(address_list
);
225 scoped_ptr
<HostResolver
> resolver(HostResolver::CreateDefaultResolver(NULL
));
226 HostResolver::RequestInfo
info(host_port_pair_
);
227 TestCompletionCallback callback
;
228 int rv
= resolver
->Resolve(info
,
234 if (rv
== ERR_IO_PENDING
)
235 rv
= callback
.WaitForResult();
237 LOG(ERROR
) << "Failed to resolve hostname: " << host_port_pair_
.host();
243 uint16
BaseTestServer::GetPort() {
244 return host_port_pair_
.port();
247 void BaseTestServer::SetPort(uint16 port
) {
248 host_port_pair_
.set_port(port
);
251 GURL
BaseTestServer::GetURL(const std::string
& path
) const {
252 return GURL(GetScheme() + "://" + host_port_pair_
.ToString() + "/" + path
);
255 GURL
BaseTestServer::GetURLWithUser(const std::string
& path
,
256 const std::string
& user
) const {
257 return GURL(GetScheme() + "://" + user
+ "@" + host_port_pair_
.ToString() +
261 GURL
BaseTestServer::GetURLWithUserAndPassword(const std::string
& path
,
262 const std::string
& user
,
263 const std::string
& password
) const {
264 return GURL(GetScheme() + "://" + user
+ ":" + password
+ "@" +
265 host_port_pair_
.ToString() + "/" + path
);
269 bool BaseTestServer::GetFilePathWithReplacements(
270 const std::string
& original_file_path
,
271 const std::vector
<StringPair
>& text_to_replace
,
272 std::string
* replacement_path
) {
273 std::string new_file_path
= original_file_path
;
274 bool first_query_parameter
= true;
275 const std::vector
<StringPair
>::const_iterator end
= text_to_replace
.end();
276 for (std::vector
<StringPair
>::const_iterator it
= text_to_replace
.begin();
279 const std::string
& old_text
= it
->first
;
280 const std::string
& new_text
= it
->second
;
281 std::string base64_old
;
282 std::string base64_new
;
283 base::Base64Encode(old_text
, &base64_old
);
284 base::Base64Encode(new_text
, &base64_new
);
285 if (first_query_parameter
) {
286 new_file_path
+= "?";
287 first_query_parameter
= false;
289 new_file_path
+= "&";
291 new_file_path
+= "replace_text=";
292 new_file_path
+= base64_old
;
293 new_file_path
+= ":";
294 new_file_path
+= base64_new
;
297 *replacement_path
= new_file_path
;
301 void BaseTestServer::Init(const std::string
& host
) {
302 host_port_pair_
= HostPortPair(host
, 0);
304 // TODO(battre) Remove this after figuring out why the TestServer is flaky.
305 // http://crbug.com/96594
306 log_to_console_
= true;
309 void BaseTestServer::SetResourcePath(const base::FilePath
& document_root
,
310 const base::FilePath
& certificates_dir
) {
311 // This method shouldn't get called twice.
312 DCHECK(certificates_dir_
.empty());
313 document_root_
= document_root
;
314 certificates_dir_
= certificates_dir
;
315 DCHECK(!certificates_dir_
.empty());
318 bool BaseTestServer::ParseServerData(const std::string
& server_data
) {
319 VLOG(1) << "Server data: " << server_data
;
320 base::JSONReader json_reader
;
321 scoped_ptr
<base::Value
> value(json_reader
.ReadToValue(server_data
));
322 if (!value
.get() || !value
->IsType(base::Value::TYPE_DICTIONARY
)) {
323 LOG(ERROR
) << "Could not parse server data: "
324 << json_reader
.GetErrorMessage();
328 server_data_
.reset(static_cast<base::DictionaryValue
*>(value
.release()));
330 if (!server_data_
->GetInteger("port", &port
)) {
331 LOG(ERROR
) << "Could not find port value";
334 if ((port
<= 0) || (port
> kuint16max
)) {
335 LOG(ERROR
) << "Invalid port value: " << port
;
338 host_port_pair_
.set_port(port
);
343 bool BaseTestServer::LoadTestRootCert() const {
344 TestRootCerts
* root_certs
= TestRootCerts::GetInstance();
348 // Should always use absolute path to load the root certificate.
349 base::FilePath root_certificate_path
= certificates_dir_
;
350 if (!certificates_dir_
.IsAbsolute()) {
351 base::FilePath src_dir
;
352 if (!PathService::Get(base::DIR_SOURCE_ROOT
, &src_dir
))
354 root_certificate_path
= src_dir
.Append(certificates_dir_
);
357 return root_certs
->AddFromFile(
358 root_certificate_path
.AppendASCII("root_ca_cert.pem"));
361 bool BaseTestServer::SetupWhenServerStarted() {
362 DCHECK(host_port_pair_
.port());
364 if (UsingSSL(type_
) && !LoadTestRootCert())
368 allowed_port_
.reset(new ScopedPortException(host_port_pair_
.port()));
372 void BaseTestServer::CleanUpWhenStoppingServer() {
373 TestRootCerts
* root_certs
= TestRootCerts::GetInstance();
376 host_port_pair_
.set_port(0);
377 allowed_port_
.reset();
381 // Generates a dictionary of arguments to pass to the Python test server via
382 // the test server spawner, in the form of
383 // { argument-name: argument-value, ... }
384 // Returns false if an invalid configuration is specified.
385 bool BaseTestServer::GenerateArguments(base::DictionaryValue
* arguments
) const {
388 arguments
->SetString("host", host_port_pair_
.host());
389 arguments
->SetInteger("port", host_port_pair_
.port());
390 arguments
->SetString("data-dir", document_root_
.value());
392 if (VLOG_IS_ON(1) || log_to_console_
)
393 arguments
->Set("log-to-console", base::Value::CreateNullValue());
395 if (ws_basic_auth_
) {
396 DCHECK(type_
== TYPE_WS
|| type_
== TYPE_WSS
);
397 arguments
->Set("ws-basic-auth", base::Value::CreateNullValue());
400 if (UsingSSL(type_
)) {
401 // Check the certificate arguments of the HTTPS server.
402 base::FilePath
certificate_path(certificates_dir_
);
403 base::FilePath
certificate_file(ssl_options_
.GetCertificateFile());
404 if (!certificate_file
.value().empty()) {
405 certificate_path
= certificate_path
.Append(certificate_file
);
406 if (certificate_path
.IsAbsolute() &&
407 !base::PathExists(certificate_path
)) {
408 LOG(ERROR
) << "Certificate path " << certificate_path
.value()
409 << " doesn't exist. Can't launch https server.";
412 arguments
->SetString("cert-and-key-file", certificate_path
.value());
415 // Check the client certificate related arguments.
416 if (ssl_options_
.request_client_certificate
)
417 arguments
->Set("ssl-client-auth", base::Value::CreateNullValue());
418 scoped_ptr
<base::ListValue
> ssl_client_certs(new base::ListValue());
420 std::vector
<base::FilePath
>::const_iterator it
;
421 for (it
= ssl_options_
.client_authorities
.begin();
422 it
!= ssl_options_
.client_authorities
.end(); ++it
) {
423 if (it
->IsAbsolute() && !base::PathExists(*it
)) {
424 LOG(ERROR
) << "Client authority path " << it
->value()
425 << " doesn't exist. Can't launch https server.";
428 ssl_client_certs
->Append(new base::StringValue(it
->value()));
431 if (ssl_client_certs
->GetSize())
432 arguments
->Set("ssl-client-ca", ssl_client_certs
.release());
434 scoped_ptr
<base::ListValue
> client_cert_types(new base::ListValue());
435 for (size_t i
= 0; i
< ssl_options_
.client_cert_types
.size(); i
++) {
436 client_cert_types
->Append(new base::StringValue(
437 GetClientCertType(ssl_options_
.client_cert_types
[i
])));
439 if (client_cert_types
->GetSize())
440 arguments
->Set("ssl-client-cert-type", client_cert_types
.release());
443 if (type_
== TYPE_HTTPS
) {
444 arguments
->Set("https", base::Value::CreateNullValue());
446 std::string ocsp_arg
= ssl_options_
.GetOCSPArgument();
447 if (!ocsp_arg
.empty())
448 arguments
->SetString("ocsp", ocsp_arg
);
450 if (ssl_options_
.cert_serial
!= 0) {
451 arguments
->SetInteger("cert-serial", ssl_options_
.cert_serial
);
454 // Check key exchange argument.
455 scoped_ptr
<base::ListValue
> key_exchange_values(new base::ListValue());
456 GetKeyExchangesList(ssl_options_
.key_exchanges
, key_exchange_values
.get());
457 if (key_exchange_values
->GetSize())
458 arguments
->Set("ssl-key-exchange", key_exchange_values
.release());
459 // Check bulk cipher argument.
460 scoped_ptr
<base::ListValue
> bulk_cipher_values(new base::ListValue());
461 GetCiphersList(ssl_options_
.bulk_ciphers
, bulk_cipher_values
.get());
462 if (bulk_cipher_values
->GetSize())
463 arguments
->Set("ssl-bulk-cipher", bulk_cipher_values
.release());
464 if (ssl_options_
.record_resume
)
465 arguments
->Set("https-record-resume", base::Value::CreateNullValue());
466 if (ssl_options_
.tls_intolerant
!= SSLOptions::TLS_INTOLERANT_NONE
) {
467 arguments
->SetInteger("tls-intolerant", ssl_options_
.tls_intolerant
);
468 arguments
->Set("tls-intolerance-type", GetTLSIntoleranceType(
469 ssl_options_
.tls_intolerance_type
));
471 if (ssl_options_
.fallback_scsv_enabled
)
472 arguments
->Set("fallback-scsv", base::Value::CreateNullValue());
473 if (!ssl_options_
.signed_cert_timestamps_tls_ext
.empty()) {
474 std::string b64_scts_tls_ext
;
475 base::Base64Encode(ssl_options_
.signed_cert_timestamps_tls_ext
,
477 arguments
->SetString("signed-cert-timestamps-tls-ext", b64_scts_tls_ext
);
479 if (ssl_options_
.staple_ocsp_response
)
480 arguments
->Set("staple-ocsp-response", base::Value::CreateNullValue());
481 if (ssl_options_
.ocsp_server_unavailable
) {
482 arguments
->Set("ocsp-server-unavailable",
483 base::Value::CreateNullValue());
485 if (ssl_options_
.enable_npn
)
486 arguments
->Set("enable-npn", base::Value::CreateNullValue());
487 if (ssl_options_
.disable_session_cache
)
488 arguments
->Set("disable-session-cache", base::Value::CreateNullValue());
491 return GenerateAdditionalArguments(arguments
);
494 bool BaseTestServer::GenerateAdditionalArguments(
495 base::DictionaryValue
* arguments
) const {