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_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 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"));
73 if (cipher
& BaseTestServer::SSLOptions::BULK_CIPHER_AES128GCM
)
74 values
->Append(new base::StringValue("aes128gcm"));
77 base::StringValue
* GetTLSIntoleranceType(
78 BaseTestServer::SSLOptions::TLSIntoleranceType type
) {
80 case BaseTestServer::SSLOptions::TLS_INTOLERANCE_ALERT
:
81 return new base::StringValue("alert");
82 case BaseTestServer::SSLOptions::TLS_INTOLERANCE_CLOSE
:
83 return new base::StringValue("close");
84 case BaseTestServer::SSLOptions::TLS_INTOLERANCE_RESET
:
85 return new base::StringValue("reset");
88 return new base::StringValue("");
94 BaseTestServer::SSLOptions::SSLOptions()
95 : server_certificate(CERT_OK
),
98 request_client_certificate(false),
99 key_exchanges(SSLOptions::KEY_EXCHANGE_ANY
),
100 bulk_ciphers(SSLOptions::BULK_CIPHER_ANY
),
101 record_resume(false),
102 tls_intolerant(TLS_INTOLERANT_NONE
),
103 tls_intolerance_type(TLS_INTOLERANCE_ALERT
),
104 fallback_scsv_enabled(false),
105 staple_ocsp_response(false),
106 ocsp_server_unavailable(false),
108 alert_after_handshake(false) {
111 BaseTestServer::SSLOptions::SSLOptions(
112 BaseTestServer::SSLOptions::ServerCertificate cert
)
113 : server_certificate(cert
),
114 ocsp_status(OCSP_OK
),
116 request_client_certificate(false),
117 key_exchanges(SSLOptions::KEY_EXCHANGE_ANY
),
118 bulk_ciphers(SSLOptions::BULK_CIPHER_ANY
),
119 record_resume(false),
120 tls_intolerant(TLS_INTOLERANT_NONE
),
121 tls_intolerance_type(TLS_INTOLERANCE_ALERT
),
122 fallback_scsv_enabled(false),
123 staple_ocsp_response(false),
124 ocsp_server_unavailable(false),
126 alert_after_handshake(false) {
129 BaseTestServer::SSLOptions::~SSLOptions() {}
131 base::FilePath
BaseTestServer::SSLOptions::GetCertificateFile() const {
132 switch (server_certificate
) {
134 case CERT_MISMATCHED_NAME
:
135 return base::FilePath(FILE_PATH_LITERAL("ok_cert.pem"));
137 return base::FilePath(FILE_PATH_LITERAL("expired_cert.pem"));
138 case CERT_CHAIN_WRONG_ROOT
:
139 // This chain uses its own dedicated test root certificate to avoid
140 // side-effects that may affect testing.
141 return base::FilePath(FILE_PATH_LITERAL("redundant-server-chain.pem"));
143 return base::FilePath();
147 return base::FilePath();
150 std::string
BaseTestServer::SSLOptions::GetOCSPArgument() const {
151 if (server_certificate
!= CERT_AUTO
)
152 return std::string();
154 switch (ocsp_status
) {
161 case OCSP_UNAUTHORIZED
:
162 return "unauthorized";
167 return std::string();
171 const char BaseTestServer::kLocalhost
[] = "127.0.0.1";
173 BaseTestServer::BaseTestServer(Type type
, const std::string
& host
)
176 log_to_console_(false),
177 ws_basic_auth_(false) {
181 BaseTestServer::BaseTestServer(Type type
, const SSLOptions
& ssl_options
)
182 : ssl_options_(ssl_options
),
185 log_to_console_(false),
186 ws_basic_auth_(false) {
187 DCHECK(UsingSSL(type
));
188 Init(GetHostname(type
, ssl_options
));
191 BaseTestServer::~BaseTestServer() {}
193 const HostPortPair
& BaseTestServer::host_port_pair() const {
195 return host_port_pair_
;
198 const base::DictionaryValue
& BaseTestServer::server_data() const {
200 DCHECK(server_data_
.get());
201 return *server_data_
;
204 std::string
BaseTestServer::GetScheme() const {
221 return std::string();
224 bool BaseTestServer::GetAddressList(AddressList
* address_list
) const {
225 DCHECK(address_list
);
227 scoped_ptr
<HostResolver
> resolver(HostResolver::CreateDefaultResolver(NULL
));
228 HostResolver::RequestInfo
info(host_port_pair_
);
229 TestCompletionCallback callback
;
230 int rv
= resolver
->Resolve(info
,
236 if (rv
== ERR_IO_PENDING
)
237 rv
= callback
.WaitForResult();
239 LOG(ERROR
) << "Failed to resolve hostname: " << host_port_pair_
.host();
245 uint16
BaseTestServer::GetPort() {
246 return host_port_pair_
.port();
249 void BaseTestServer::SetPort(uint16 port
) {
250 host_port_pair_
.set_port(port
);
253 GURL
BaseTestServer::GetURL(const std::string
& path
) const {
254 return GURL(GetScheme() + "://" + host_port_pair_
.ToString() + "/" + path
);
257 GURL
BaseTestServer::GetURLWithUser(const std::string
& path
,
258 const std::string
& user
) const {
259 return GURL(GetScheme() + "://" + user
+ "@" + host_port_pair_
.ToString() +
263 GURL
BaseTestServer::GetURLWithUserAndPassword(const std::string
& path
,
264 const std::string
& user
,
265 const std::string
& password
) const {
266 return GURL(GetScheme() + "://" + user
+ ":" + password
+ "@" +
267 host_port_pair_
.ToString() + "/" + path
);
271 bool BaseTestServer::GetFilePathWithReplacements(
272 const std::string
& original_file_path
,
273 const std::vector
<StringPair
>& text_to_replace
,
274 std::string
* replacement_path
) {
275 std::string new_file_path
= original_file_path
;
276 bool first_query_parameter
= true;
277 const std::vector
<StringPair
>::const_iterator end
= text_to_replace
.end();
278 for (std::vector
<StringPair
>::const_iterator it
= text_to_replace
.begin();
281 const std::string
& old_text
= it
->first
;
282 const std::string
& new_text
= it
->second
;
283 std::string base64_old
;
284 std::string base64_new
;
285 base::Base64Encode(old_text
, &base64_old
);
286 base::Base64Encode(new_text
, &base64_new
);
287 if (first_query_parameter
) {
288 new_file_path
+= "?";
289 first_query_parameter
= false;
291 new_file_path
+= "&";
293 new_file_path
+= "replace_text=";
294 new_file_path
+= base64_old
;
295 new_file_path
+= ":";
296 new_file_path
+= base64_new
;
299 *replacement_path
= new_file_path
;
303 void BaseTestServer::Init(const std::string
& host
) {
304 host_port_pair_
= HostPortPair(host
, 0);
306 // TODO(battre) Remove this after figuring out why the TestServer is flaky.
307 // http://crbug.com/96594
308 log_to_console_
= true;
311 void BaseTestServer::SetResourcePath(const base::FilePath
& document_root
,
312 const base::FilePath
& certificates_dir
) {
313 // This method shouldn't get called twice.
314 DCHECK(certificates_dir_
.empty());
315 document_root_
= document_root
;
316 certificates_dir_
= certificates_dir
;
317 DCHECK(!certificates_dir_
.empty());
320 bool BaseTestServer::ParseServerData(const std::string
& server_data
) {
321 VLOG(1) << "Server data: " << server_data
;
322 base::JSONReader json_reader
;
323 scoped_ptr
<base::Value
> value(json_reader
.ReadToValue(server_data
));
324 if (!value
.get() || !value
->IsType(base::Value::TYPE_DICTIONARY
)) {
325 LOG(ERROR
) << "Could not parse server data: "
326 << json_reader
.GetErrorMessage();
330 server_data_
.reset(static_cast<base::DictionaryValue
*>(value
.release()));
332 if (!server_data_
->GetInteger("port", &port
)) {
333 LOG(ERROR
) << "Could not find port value";
336 if ((port
<= 0) || (port
> kuint16max
)) {
337 LOG(ERROR
) << "Invalid port value: " << port
;
340 host_port_pair_
.set_port(port
);
345 bool BaseTestServer::LoadTestRootCert() const {
346 TestRootCerts
* root_certs
= TestRootCerts::GetInstance();
350 // Should always use absolute path to load the root certificate.
351 base::FilePath root_certificate_path
= certificates_dir_
;
352 if (!certificates_dir_
.IsAbsolute()) {
353 base::FilePath src_dir
;
354 if (!PathService::Get(base::DIR_SOURCE_ROOT
, &src_dir
))
356 root_certificate_path
= src_dir
.Append(certificates_dir_
);
359 return root_certs
->AddFromFile(
360 root_certificate_path
.AppendASCII("root_ca_cert.pem"));
363 bool BaseTestServer::SetupWhenServerStarted() {
364 DCHECK(host_port_pair_
.port());
366 if (UsingSSL(type_
) && !LoadTestRootCert())
370 allowed_port_
.reset(new ScopedPortException(host_port_pair_
.port()));
374 void BaseTestServer::CleanUpWhenStoppingServer() {
375 TestRootCerts
* root_certs
= TestRootCerts::GetInstance();
378 host_port_pair_
.set_port(0);
379 allowed_port_
.reset();
383 // Generates a dictionary of arguments to pass to the Python test server via
384 // the test server spawner, in the form of
385 // { argument-name: argument-value, ... }
386 // Returns false if an invalid configuration is specified.
387 bool BaseTestServer::GenerateArguments(base::DictionaryValue
* arguments
) const {
390 arguments
->SetString("host", host_port_pair_
.host());
391 arguments
->SetInteger("port", host_port_pair_
.port());
392 arguments
->SetString("data-dir", document_root_
.value());
394 if (VLOG_IS_ON(1) || log_to_console_
)
395 arguments
->Set("log-to-console", base::Value::CreateNullValue());
397 if (ws_basic_auth_
) {
398 DCHECK(type_
== TYPE_WS
|| type_
== TYPE_WSS
);
399 arguments
->Set("ws-basic-auth", base::Value::CreateNullValue());
402 if (UsingSSL(type_
)) {
403 // Check the certificate arguments of the HTTPS server.
404 base::FilePath
certificate_path(certificates_dir_
);
405 base::FilePath
certificate_file(ssl_options_
.GetCertificateFile());
406 if (!certificate_file
.value().empty()) {
407 certificate_path
= certificate_path
.Append(certificate_file
);
408 if (certificate_path
.IsAbsolute() &&
409 !base::PathExists(certificate_path
)) {
410 LOG(ERROR
) << "Certificate path " << certificate_path
.value()
411 << " doesn't exist. Can't launch https server.";
414 arguments
->SetString("cert-and-key-file", certificate_path
.value());
417 // Check the client certificate related arguments.
418 if (ssl_options_
.request_client_certificate
)
419 arguments
->Set("ssl-client-auth", base::Value::CreateNullValue());
420 scoped_ptr
<base::ListValue
> ssl_client_certs(new base::ListValue());
422 std::vector
<base::FilePath
>::const_iterator it
;
423 for (it
= ssl_options_
.client_authorities
.begin();
424 it
!= ssl_options_
.client_authorities
.end(); ++it
) {
425 if (it
->IsAbsolute() && !base::PathExists(*it
)) {
426 LOG(ERROR
) << "Client authority path " << it
->value()
427 << " doesn't exist. Can't launch https server.";
430 ssl_client_certs
->Append(new base::StringValue(it
->value()));
433 if (ssl_client_certs
->GetSize())
434 arguments
->Set("ssl-client-ca", ssl_client_certs
.release());
436 scoped_ptr
<base::ListValue
> client_cert_types(new base::ListValue());
437 for (size_t i
= 0; i
< ssl_options_
.client_cert_types
.size(); i
++) {
438 client_cert_types
->Append(new base::StringValue(
439 GetClientCertType(ssl_options_
.client_cert_types
[i
])));
441 if (client_cert_types
->GetSize())
442 arguments
->Set("ssl-client-cert-type", client_cert_types
.release());
445 if (type_
== TYPE_HTTPS
) {
446 arguments
->Set("https", base::Value::CreateNullValue());
448 std::string ocsp_arg
= ssl_options_
.GetOCSPArgument();
449 if (!ocsp_arg
.empty())
450 arguments
->SetString("ocsp", ocsp_arg
);
452 if (ssl_options_
.cert_serial
!= 0) {
453 arguments
->SetInteger("cert-serial", ssl_options_
.cert_serial
);
456 // Check key exchange argument.
457 scoped_ptr
<base::ListValue
> key_exchange_values(new base::ListValue());
458 GetKeyExchangesList(ssl_options_
.key_exchanges
, key_exchange_values
.get());
459 if (key_exchange_values
->GetSize())
460 arguments
->Set("ssl-key-exchange", key_exchange_values
.release());
461 // Check bulk cipher argument.
462 scoped_ptr
<base::ListValue
> bulk_cipher_values(new base::ListValue());
463 GetCiphersList(ssl_options_
.bulk_ciphers
, bulk_cipher_values
.get());
464 if (bulk_cipher_values
->GetSize())
465 arguments
->Set("ssl-bulk-cipher", bulk_cipher_values
.release());
466 if (ssl_options_
.record_resume
)
467 arguments
->Set("https-record-resume", base::Value::CreateNullValue());
468 if (ssl_options_
.tls_intolerant
!= SSLOptions::TLS_INTOLERANT_NONE
) {
469 arguments
->SetInteger("tls-intolerant", ssl_options_
.tls_intolerant
);
470 arguments
->Set("tls-intolerance-type", GetTLSIntoleranceType(
471 ssl_options_
.tls_intolerance_type
));
473 if (ssl_options_
.fallback_scsv_enabled
)
474 arguments
->Set("fallback-scsv", base::Value::CreateNullValue());
475 if (!ssl_options_
.signed_cert_timestamps_tls_ext
.empty()) {
476 std::string b64_scts_tls_ext
;
477 base::Base64Encode(ssl_options_
.signed_cert_timestamps_tls_ext
,
479 arguments
->SetString("signed-cert-timestamps-tls-ext", b64_scts_tls_ext
);
481 if (ssl_options_
.staple_ocsp_response
)
482 arguments
->Set("staple-ocsp-response", base::Value::CreateNullValue());
483 if (ssl_options_
.ocsp_server_unavailable
) {
484 arguments
->Set("ocsp-server-unavailable",
485 base::Value::CreateNullValue());
487 if (ssl_options_
.enable_npn
)
488 arguments
->Set("enable-npn", base::Value::CreateNullValue());
489 if (ssl_options_
.alert_after_handshake
)
490 arguments
->Set("alert-after-handshake", base::Value::CreateNullValue());
493 return GenerateAdditionalArguments(arguments
);
496 bool BaseTestServer::GenerateAdditionalArguments(
497 base::DictionaryValue
* arguments
) const {