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. 4 5 #include "net/test/spawned_test_server/base_test_server.h" 6 7 #include <string> 8 #include <vector> 9 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" 24 #include "url/gurl.h" 25 26 namespace net { 27 28 namespace { 29 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. 36 return "localhost"; 37 } 38 39 // Use the 127.0.0.1 as default. 40 return BaseTestServer::kLocalhost; 41 } 42 43 std::string GetClientCertType(SSLClientCertType type) { 44 switch (type) { 45 case CLIENT_CERT_RSA_SIGN: 46 return "rsa_sign"; 47 case CLIENT_CERT_DSS_SIGN: 48 return "dss_sign"; 49 case CLIENT_CERT_ECDSA_SIGN: 50 return "ecdsa_sign"; 51 default: 52 NOTREACHED(); 53 return ""; 54 } 55 } 56 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")); 62 } 63 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 } 74 75 base::StringValue* GetTLSIntoleranceType( 76 BaseTestServer::SSLOptions::TLSIntoleranceType type) { 77 switch (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"); 84 default: 85 NOTREACHED(); 86 return new base::StringValue(""); 87 } 88 } 89 90 } // namespace 91 92 BaseTestServer::SSLOptions::SSLOptions() 93 : server_certificate(CERT_OK), 94 ocsp_status(OCSP_OK), 95 cert_serial(0), 96 request_client_certificate(false), 97 key_exchanges(SSLOptions::KEY_EXCHANGE_ANY), 98 bulk_ciphers(SSLOptions::BULK_CIPHER_ANY), 99 record_resume(false), 100 tls_intolerant(TLS_INTOLERANT_NONE), 101 tls_intolerance_type(TLS_INTOLERANCE_ALERT), 102 fallback_scsv_enabled(false), 103 staple_ocsp_response(false), 104 enable_npn(false), 105 disable_session_cache(false) { 106 } 107 108 BaseTestServer::SSLOptions::SSLOptions( 109 BaseTestServer::SSLOptions::ServerCertificate cert) 110 : server_certificate(cert), 111 ocsp_status(OCSP_OK), 112 cert_serial(0), 113 request_client_certificate(false), 114 key_exchanges(SSLOptions::KEY_EXCHANGE_ANY), 115 bulk_ciphers(SSLOptions::BULK_CIPHER_ANY), 116 record_resume(false), 117 tls_intolerant(TLS_INTOLERANT_NONE), 118 tls_intolerance_type(TLS_INTOLERANCE_ALERT), 119 fallback_scsv_enabled(false), 120 staple_ocsp_response(false), 121 enable_npn(false), 122 disable_session_cache(false) { 123 } 124 125 BaseTestServer::SSLOptions::~SSLOptions() {} 126 127 base::FilePath BaseTestServer::SSLOptions::GetCertificateFile() const { 128 switch (server_certificate) { 129 case CERT_OK: 130 case CERT_MISMATCHED_NAME: 131 return base::FilePath(FILE_PATH_LITERAL("ok_cert.pem")); 132 case CERT_EXPIRED: 133 return base::FilePath(FILE_PATH_LITERAL("expired_cert.pem")); 134 case CERT_CHAIN_WRONG_ROOT: 135 // This chain uses its own dedicated test root certificate to avoid 136 // side-effects that may affect testing. 137 return base::FilePath(FILE_PATH_LITERAL("redundant-server-chain.pem")); 138 case CERT_AUTO: 139 return base::FilePath(); 140 default: 141 NOTREACHED(); 142 } 143 return base::FilePath(); 144 } 145 146 std::string BaseTestServer::SSLOptions::GetOCSPArgument() const { 147 if (server_certificate != CERT_AUTO) 148 return std::string(); 149 150 switch (ocsp_status) { 151 case OCSP_OK: 152 return "ok"; 153 case OCSP_REVOKED: 154 return "revoked"; 155 case OCSP_INVALID: 156 return "invalid"; 157 case OCSP_UNAUTHORIZED: 158 return "unauthorized"; 159 case OCSP_UNKNOWN: 160 return "unknown"; 161 default: 162 NOTREACHED(); 163 return std::string(); 164 } 165 } 166 167 const char BaseTestServer::kLocalhost[] = "127.0.0.1"; 168 169 BaseTestServer::BaseTestServer(Type type, const std::string& host) 170 : type_(type), 171 started_(false), 172 log_to_console_(false), 173 ws_basic_auth_(false) { 174 Init(host); 175 } 176 177 BaseTestServer::BaseTestServer(Type type, const SSLOptions& ssl_options) 178 : ssl_options_(ssl_options), 179 type_(type), 180 started_(false), 181 log_to_console_(false), 182 ws_basic_auth_(false) { 183 DCHECK(UsingSSL(type)); 184 Init(GetHostname(type, ssl_options)); 185 } 186 187 BaseTestServer::~BaseTestServer() {} 188 189 const HostPortPair& BaseTestServer::host_port_pair() const { 190 DCHECK(started_); 191 return host_port_pair_; 192 } 193 194 const base::DictionaryValue& BaseTestServer::server_data() const { 195 DCHECK(started_); 196 DCHECK(server_data_.get()); 197 return *server_data_; 198 } 199 200 std::string BaseTestServer::GetScheme() const { 201 switch (type_) { 202 case TYPE_FTP: 203 return "ftp"; 204 case TYPE_HTTP: 205 return "http"; 206 case TYPE_HTTPS: 207 return "https"; 208 case TYPE_WS: 209 return "ws"; 210 case TYPE_WSS: 211 return "wss"; 212 case TYPE_TCP_ECHO: 213 case TYPE_UDP_ECHO: 214 default: 215 NOTREACHED(); 216 } 217 return std::string(); 218 } 219 220 bool BaseTestServer::GetAddressList(AddressList* address_list) const { 221 DCHECK(address_list); 222 223 scoped_ptr<HostResolver> resolver(HostResolver::CreateDefaultResolver(NULL)); 224 HostResolver::RequestInfo info(host_port_pair_); 225 TestCompletionCallback callback; 226 int rv = resolver->Resolve(info, 227 DEFAULT_PRIORITY, 228 address_list, 229 callback.callback(), 230 NULL, 231 BoundNetLog()); 232 if (rv == ERR_IO_PENDING) 233 rv = callback.WaitForResult(); 234 if (rv != net::OK) { 235 LOG(ERROR) << "Failed to resolve hostname: " << host_port_pair_.host(); 236 return false; 237 } 238 return true; 239 } 240 241 uint16 BaseTestServer::GetPort() { 242 return host_port_pair_.port(); 243 } 244 245 void BaseTestServer::SetPort(uint16 port) { 246 host_port_pair_.set_port(port); 247 } 248 249 GURL BaseTestServer::GetURL(const std::string& path) const { 250 return GURL(GetScheme() + "://" + host_port_pair_.ToString() + "/" + path); 251 } 252 253 GURL BaseTestServer::GetURLWithUser(const std::string& path, 254 const std::string& user) const { 255 return GURL(GetScheme() + "://" + user + "@" + host_port_pair_.ToString() + 256 "/" + path); 257 } 258 259 GURL BaseTestServer::GetURLWithUserAndPassword(const std::string& path, 260 const std::string& user, 261 const std::string& password) const { 262 return GURL(GetScheme() + "://" + user + ":" + password + "@" + 263 host_port_pair_.ToString() + "/" + path); 264 } 265 266 // static 267 bool BaseTestServer::GetFilePathWithReplacements( 268 const std::string& original_file_path, 269 const std::vector<StringPair>& text_to_replace, 270 std::string* replacement_path) { 271 std::string new_file_path = original_file_path; 272 bool first_query_parameter = true; 273 const std::vector<StringPair>::const_iterator end = text_to_replace.end(); 274 for (std::vector<StringPair>::const_iterator it = text_to_replace.begin(); 275 it != end; 276 ++it) { 277 const std::string& old_text = it->first; 278 const std::string& new_text = it->second; 279 std::string base64_old; 280 std::string base64_new; 281 base::Base64Encode(old_text, &base64_old); 282 base::Base64Encode(new_text, &base64_new); 283 if (first_query_parameter) { 284 new_file_path += "?"; 285 first_query_parameter = false; 286 } else { 287 new_file_path += "&"; 288 } 289 new_file_path += "replace_text="; 290 new_file_path += base64_old; 291 new_file_path += ":"; 292 new_file_path += base64_new; 293 } 294 295 *replacement_path = new_file_path; 296 return true; 297 } 298 299 void BaseTestServer::Init(const std::string& host) { 300 host_port_pair_ = HostPortPair(host, 0); 301 302 // TODO(battre) Remove this after figuring out why the TestServer is flaky. 303 // http://crbug.com/96594 304 log_to_console_ = true; 305 } 306 307 void BaseTestServer::SetResourcePath(const base::FilePath& document_root, 308 const base::FilePath& certificates_dir) { 309 // This method shouldn't get called twice. 310 DCHECK(certificates_dir_.empty()); 311 document_root_ = document_root; 312 certificates_dir_ = certificates_dir; 313 DCHECK(!certificates_dir_.empty()); 314 } 315 316 bool BaseTestServer::ParseServerData(const std::string& server_data) { 317 VLOG(1) << "Server data: " << server_data; 318 base::JSONReader json_reader; 319 scoped_ptr<base::Value> value(json_reader.ReadToValue(server_data)); 320 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) { 321 LOG(ERROR) << "Could not parse server data: " 322 << json_reader.GetErrorMessage(); 323 return false; 324 } 325 326 server_data_.reset(static_cast<base::DictionaryValue*>(value.release())); 327 int port = 0; 328 if (!server_data_->GetInteger("port", &port)) { 329 LOG(ERROR) << "Could not find port value"; 330 return false; 331 } 332 if ((port <= 0) || (port > kuint16max)) { 333 LOG(ERROR) << "Invalid port value: " << port; 334 return false; 335 } 336 host_port_pair_.set_port(port); 337 338 return true; 339 } 340 341 bool BaseTestServer::LoadTestRootCert() const { 342 TestRootCerts* root_certs = TestRootCerts::GetInstance(); 343 if (!root_certs) 344 return false; 345 346 // Should always use absolute path to load the root certificate. 347 base::FilePath root_certificate_path = certificates_dir_; 348 if (!certificates_dir_.IsAbsolute()) { 349 base::FilePath src_dir; 350 if (!PathService::Get(base::DIR_SOURCE_ROOT, &src_dir)) 351 return false; 352 root_certificate_path = src_dir.Append(certificates_dir_); 353 } 354 355 return root_certs->AddFromFile( 356 root_certificate_path.AppendASCII("root_ca_cert.pem")); 357 } 358 359 bool BaseTestServer::SetupWhenServerStarted() { 360 DCHECK(host_port_pair_.port()); 361 362 if (UsingSSL(type_) && !LoadTestRootCert()) 363 return false; 364 365 started_ = true; 366 allowed_port_.reset(new ScopedPortException(host_port_pair_.port())); 367 return true; 368 } 369 370 void BaseTestServer::CleanUpWhenStoppingServer() { 371 TestRootCerts* root_certs = TestRootCerts::GetInstance(); 372 root_certs->Clear(); 373 374 host_port_pair_.set_port(0); 375 allowed_port_.reset(); 376 started_ = false; 377 } 378 379 // Generates a dictionary of arguments to pass to the Python test server via 380 // the test server spawner, in the form of 381 // { argument-name: argument-value, ... } 382 // Returns false if an invalid configuration is specified. 383 bool BaseTestServer::GenerateArguments(base::DictionaryValue* arguments) const { 384 DCHECK(arguments); 385 386 arguments->SetString("host", host_port_pair_.host()); 387 arguments->SetInteger("port", host_port_pair_.port()); 388 arguments->SetString("data-dir", document_root_.value()); 389 390 if (VLOG_IS_ON(1) || log_to_console_) 391 arguments->Set("log-to-console", base::Value::CreateNullValue()); 392 393 if (ws_basic_auth_) { 394 DCHECK(type_ == TYPE_WS || type_ == TYPE_WSS); 395 arguments->Set("ws-basic-auth", base::Value::CreateNullValue()); 396 } 397 398 if (UsingSSL(type_)) { 399 // Check the certificate arguments of the HTTPS server. 400 base::FilePath certificate_path(certificates_dir_); 401 base::FilePath certificate_file(ssl_options_.GetCertificateFile()); 402 if (!certificate_file.value().empty()) { 403 certificate_path = certificate_path.Append(certificate_file); 404 if (certificate_path.IsAbsolute() && 405 !base::PathExists(certificate_path)) { 406 LOG(ERROR) << "Certificate path " << certificate_path.value() 407 << " doesn't exist. Can't launch https server."; 408 return false; 409 } 410 arguments->SetString("cert-and-key-file", certificate_path.value()); 411 } 412 413 // Check the client certificate related arguments. 414 if (ssl_options_.request_client_certificate) 415 arguments->Set("ssl-client-auth", base::Value::CreateNullValue()); 416 scoped_ptr<base::ListValue> ssl_client_certs(new base::ListValue()); 417 418 std::vector<base::FilePath>::const_iterator it; 419 for (it = ssl_options_.client_authorities.begin(); 420 it != ssl_options_.client_authorities.end(); ++it) { 421 if (it->IsAbsolute() && !base::PathExists(*it)) { 422 LOG(ERROR) << "Client authority path " << it->value() 423 << " doesn't exist. Can't launch https server."; 424 return false; 425 } 426 ssl_client_certs->Append(new base::StringValue(it->value())); 427 } 428 429 if (ssl_client_certs->GetSize()) 430 arguments->Set("ssl-client-ca", ssl_client_certs.release()); 431 432 scoped_ptr<base::ListValue> client_cert_types(new base::ListValue()); 433 for (size_t i = 0; i < ssl_options_.client_cert_types.size(); i++) { 434 client_cert_types->Append(new base::StringValue( 435 GetClientCertType(ssl_options_.client_cert_types[i]))); 436 } 437 if (client_cert_types->GetSize()) 438 arguments->Set("ssl-client-cert-type", client_cert_types.release()); 439 } 440 441 if (type_ == TYPE_HTTPS) { 442 arguments->Set("https", base::Value::CreateNullValue()); 443 444 std::string ocsp_arg = ssl_options_.GetOCSPArgument(); 445 if (!ocsp_arg.empty()) 446 arguments->SetString("ocsp", ocsp_arg); 447 448 if (ssl_options_.cert_serial != 0) { 449 arguments->SetInteger("cert-serial", ssl_options_.cert_serial); 450 } 451 452 // Check key exchange argument. 453 scoped_ptr<base::ListValue> key_exchange_values(new base::ListValue()); 454 GetKeyExchangesList(ssl_options_.key_exchanges, key_exchange_values.get()); 455 if (key_exchange_values->GetSize()) 456 arguments->Set("ssl-key-exchange", key_exchange_values.release()); 457 // Check bulk cipher argument. 458 scoped_ptr<base::ListValue> bulk_cipher_values(new base::ListValue()); 459 GetCiphersList(ssl_options_.bulk_ciphers, bulk_cipher_values.get()); 460 if (bulk_cipher_values->GetSize()) 461 arguments->Set("ssl-bulk-cipher", bulk_cipher_values.release()); 462 if (ssl_options_.record_resume) 463 arguments->Set("https-record-resume", base::Value::CreateNullValue()); 464 if (ssl_options_.tls_intolerant != SSLOptions::TLS_INTOLERANT_NONE) { 465 arguments->SetInteger("tls-intolerant", ssl_options_.tls_intolerant); 466 arguments->Set("tls-intolerance-type", GetTLSIntoleranceType( 467 ssl_options_.tls_intolerance_type)); 468 } 469 if (ssl_options_.fallback_scsv_enabled) 470 arguments->Set("fallback-scsv", base::Value::CreateNullValue()); 471 if (!ssl_options_.signed_cert_timestamps_tls_ext.empty()) { 472 std::string b64_scts_tls_ext; 473 base::Base64Encode(ssl_options_.signed_cert_timestamps_tls_ext, 474 &b64_scts_tls_ext); 475 arguments->SetString("signed-cert-timestamps-tls-ext", b64_scts_tls_ext); 476 } 477 if (ssl_options_.staple_ocsp_response) 478 arguments->Set("staple-ocsp-response", base::Value::CreateNullValue()); 479 if (ssl_options_.enable_npn) 480 arguments->Set("enable-npn", base::Value::CreateNullValue()); 481 if (ssl_options_.disable_session_cache) 482 arguments->Set("disable-session-cache", base::Value::CreateNullValue()); 483 } 484 485 return GenerateAdditionalArguments(arguments); 486 } 487 488 bool BaseTestServer::GenerateAdditionalArguments( 489 base::DictionaryValue* arguments) const { 490 return true; 491 } 492 493 } // namespace net 494