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/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 void GetCiphersList(int cipher, base::ListValue* values) {
44 if (cipher & BaseTestServer::SSLOptions::BULK_CIPHER_RC4)
45 values->Append(new base::StringValue("rc4"));
46 if (cipher & BaseTestServer::SSLOptions::BULK_CIPHER_AES128)
47 values->Append(new base::StringValue("aes128"));
48 if (cipher & BaseTestServer::SSLOptions::BULK_CIPHER_AES256)
49 values->Append(new base::StringValue("aes256"));
50 if (cipher & BaseTestServer::SSLOptions::BULK_CIPHER_3DES)
51 values->Append(new base::StringValue("3des"));
56 BaseTestServer::SSLOptions::SSLOptions()
57 : server_certificate(CERT_OK),
60 request_client_certificate(false),
61 bulk_ciphers(SSLOptions::BULK_CIPHER_ANY),
63 tls_intolerant(TLS_INTOLERANT_NONE) {}
65 BaseTestServer::SSLOptions::SSLOptions(
66 BaseTestServer::SSLOptions::ServerCertificate cert)
67 : server_certificate(cert),
70 request_client_certificate(false),
71 bulk_ciphers(SSLOptions::BULK_CIPHER_ANY),
73 tls_intolerant(TLS_INTOLERANT_NONE) {}
75 BaseTestServer::SSLOptions::~SSLOptions() {}
77 base::FilePath BaseTestServer::SSLOptions::GetCertificateFile() const {
78 switch (server_certificate) {
80 case CERT_MISMATCHED_NAME:
81 return base::FilePath(FILE_PATH_LITERAL("ok_cert.pem"));
83 return base::FilePath(FILE_PATH_LITERAL("expired_cert.pem"));
84 case CERT_CHAIN_WRONG_ROOT:
85 // This chain uses its own dedicated test root certificate to avoid
86 // side-effects that may affect testing.
87 return base::FilePath(FILE_PATH_LITERAL("redundant-server-chain.pem"));
89 return base::FilePath();
93 return base::FilePath();
96 std::string BaseTestServer::SSLOptions::GetOCSPArgument() const {
97 if (server_certificate != CERT_AUTO)
100 switch (ocsp_status) {
107 case OCSP_UNAUTHORIZED:
108 return "unauthorized";
113 return std::string();
117 const char BaseTestServer::kLocalhost[] = "127.0.0.1";
119 BaseTestServer::BaseTestServer(Type type, const std::string& host)
122 log_to_console_(false) {
126 BaseTestServer::BaseTestServer(Type type, const SSLOptions& ssl_options)
127 : ssl_options_(ssl_options),
130 log_to_console_(false) {
131 DCHECK(UsingSSL(type));
132 Init(GetHostname(type, ssl_options));
135 BaseTestServer::~BaseTestServer() {}
137 const HostPortPair& BaseTestServer::host_port_pair() const {
139 return host_port_pair_;
142 const base::DictionaryValue& BaseTestServer::server_data() const {
144 DCHECK(server_data_.get());
145 return *server_data_;
148 std::string BaseTestServer::GetScheme() const {
165 return std::string();
168 bool BaseTestServer::GetAddressList(AddressList* address_list) const {
169 DCHECK(address_list);
171 scoped_ptr<HostResolver> resolver(HostResolver::CreateDefaultResolver(NULL));
172 HostResolver::RequestInfo info(host_port_pair_);
173 TestCompletionCallback callback;
174 int rv = resolver->Resolve(info,
180 if (rv == ERR_IO_PENDING)
181 rv = callback.WaitForResult();
183 LOG(ERROR) << "Failed to resolve hostname: " << host_port_pair_.host();
189 uint16 BaseTestServer::GetPort() {
190 return host_port_pair_.port();
193 void BaseTestServer::SetPort(uint16 port) {
194 host_port_pair_.set_port(port);
197 GURL BaseTestServer::GetURL(const std::string& path) const {
198 return GURL(GetScheme() + "://" + host_port_pair_.ToString() + "/" + path);
201 GURL BaseTestServer::GetURLWithUser(const std::string& path,
202 const std::string& user) const {
203 return GURL(GetScheme() + "://" + user + "@" + host_port_pair_.ToString() +
207 GURL BaseTestServer::GetURLWithUserAndPassword(const std::string& path,
208 const std::string& user,
209 const std::string& password) const {
210 return GURL(GetScheme() + "://" + user + ":" + password + "@" +
211 host_port_pair_.ToString() + "/" + path);
215 bool BaseTestServer::GetFilePathWithReplacements(
216 const std::string& original_file_path,
217 const std::vector<StringPair>& text_to_replace,
218 std::string* replacement_path) {
219 std::string new_file_path = original_file_path;
220 bool first_query_parameter = true;
221 const std::vector<StringPair>::const_iterator end = text_to_replace.end();
222 for (std::vector<StringPair>::const_iterator it = text_to_replace.begin();
225 const std::string& old_text = it->first;
226 const std::string& new_text = it->second;
227 std::string base64_old;
228 std::string base64_new;
229 if (!base::Base64Encode(old_text, &base64_old))
231 if (!base::Base64Encode(new_text, &base64_new))
233 if (first_query_parameter) {
234 new_file_path += "?";
235 first_query_parameter = false;
237 new_file_path += "&";
239 new_file_path += "replace_text=";
240 new_file_path += base64_old;
241 new_file_path += ":";
242 new_file_path += base64_new;
245 *replacement_path = new_file_path;
249 void BaseTestServer::Init(const std::string& host) {
250 host_port_pair_ = HostPortPair(host, 0);
252 // TODO(battre) Remove this after figuring out why the TestServer is flaky.
253 // http://crbug.com/96594
254 log_to_console_ = true;
257 void BaseTestServer::SetResourcePath(const base::FilePath& document_root,
258 const base::FilePath& certificates_dir) {
259 // This method shouldn't get called twice.
260 DCHECK(certificates_dir_.empty());
261 document_root_ = document_root;
262 certificates_dir_ = certificates_dir;
263 DCHECK(!certificates_dir_.empty());
266 bool BaseTestServer::ParseServerData(const std::string& server_data) {
267 VLOG(1) << "Server data: " << server_data;
268 base::JSONReader json_reader;
269 scoped_ptr<base::Value> value(json_reader.ReadToValue(server_data));
270 if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) {
271 LOG(ERROR) << "Could not parse server data: "
272 << json_reader.GetErrorMessage();
276 server_data_.reset(static_cast<base::DictionaryValue*>(value.release()));
278 if (!server_data_->GetInteger("port", &port)) {
279 LOG(ERROR) << "Could not find port value";
282 if ((port <= 0) || (port > kuint16max)) {
283 LOG(ERROR) << "Invalid port value: " << port;
286 host_port_pair_.set_port(port);
291 bool BaseTestServer::LoadTestRootCert() const {
292 TestRootCerts* root_certs = TestRootCerts::GetInstance();
296 // Should always use absolute path to load the root certificate.
297 base::FilePath root_certificate_path = certificates_dir_;
298 if (!certificates_dir_.IsAbsolute()) {
299 base::FilePath src_dir;
300 if (!PathService::Get(base::DIR_SOURCE_ROOT, &src_dir))
302 root_certificate_path = src_dir.Append(certificates_dir_);
305 return root_certs->AddFromFile(
306 root_certificate_path.AppendASCII("root_ca_cert.pem"));
309 bool BaseTestServer::SetupWhenServerStarted() {
310 DCHECK(host_port_pair_.port());
312 if (UsingSSL(type_) && !LoadTestRootCert())
316 allowed_port_.reset(new ScopedPortException(host_port_pair_.port()));
320 void BaseTestServer::CleanUpWhenStoppingServer() {
321 TestRootCerts* root_certs = TestRootCerts::GetInstance();
324 host_port_pair_.set_port(0);
325 allowed_port_.reset();
329 // Generates a dictionary of arguments to pass to the Python test server via
330 // the test server spawner, in the form of
331 // { argument-name: argument-value, ... }
332 // Returns false if an invalid configuration is specified.
333 bool BaseTestServer::GenerateArguments(base::DictionaryValue* arguments) const {
336 arguments->SetString("host", host_port_pair_.host());
337 arguments->SetInteger("port", host_port_pair_.port());
338 arguments->SetString("data-dir", document_root_.value());
340 if (VLOG_IS_ON(1) || log_to_console_)
341 arguments->Set("log-to-console", base::Value::CreateNullValue());
343 if (UsingSSL(type_)) {
344 // Check the certificate arguments of the HTTPS server.
345 base::FilePath certificate_path(certificates_dir_);
346 base::FilePath certificate_file(ssl_options_.GetCertificateFile());
347 if (!certificate_file.value().empty()) {
348 certificate_path = certificate_path.Append(certificate_file);
349 if (certificate_path.IsAbsolute() &&
350 !base::PathExists(certificate_path)) {
351 LOG(ERROR) << "Certificate path " << certificate_path.value()
352 << " doesn't exist. Can't launch https server.";
355 arguments->SetString("cert-and-key-file", certificate_path.value());
358 // Check the client certificate related arguments.
359 if (ssl_options_.request_client_certificate)
360 arguments->Set("ssl-client-auth", base::Value::CreateNullValue());
361 scoped_ptr<base::ListValue> ssl_client_certs(new base::ListValue());
363 std::vector<base::FilePath>::const_iterator it;
364 for (it = ssl_options_.client_authorities.begin();
365 it != ssl_options_.client_authorities.end(); ++it) {
366 if (it->IsAbsolute() && !base::PathExists(*it)) {
367 LOG(ERROR) << "Client authority path " << it->value()
368 << " doesn't exist. Can't launch https server.";
371 ssl_client_certs->Append(new base::StringValue(it->value()));
374 if (ssl_client_certs->GetSize())
375 arguments->Set("ssl-client-ca", ssl_client_certs.release());
378 if (type_ == TYPE_HTTPS) {
379 arguments->Set("https", base::Value::CreateNullValue());
381 std::string ocsp_arg = ssl_options_.GetOCSPArgument();
382 if (!ocsp_arg.empty())
383 arguments->SetString("ocsp", ocsp_arg);
385 if (ssl_options_.cert_serial != 0) {
386 arguments->Set("cert-serial",
387 base::Value::CreateIntegerValue(ssl_options_.cert_serial));
390 // Check bulk cipher argument.
391 scoped_ptr<base::ListValue> bulk_cipher_values(new base::ListValue());
392 GetCiphersList(ssl_options_.bulk_ciphers, bulk_cipher_values.get());
393 if (bulk_cipher_values->GetSize())
394 arguments->Set("ssl-bulk-cipher", bulk_cipher_values.release());
395 if (ssl_options_.record_resume)
396 arguments->Set("https-record-resume", base::Value::CreateNullValue());
397 if (ssl_options_.tls_intolerant != SSLOptions::TLS_INTOLERANT_NONE) {
398 arguments->Set("tls-intolerant",
399 new base::FundamentalValue(ssl_options_.tls_intolerant));
403 return GenerateAdditionalArguments(arguments);
406 bool BaseTestServer::GenerateAdditionalArguments(
407 base::DictionaryValue* arguments) const {