1 // Copyright 2013 The Chromium Authors
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 <stdint.h>
8 #include <limits>
9 #include <memory>
10 #include <string>
11 #include <utility>
12 #include <vector>
13
14 #include "base/base64.h"
15 #include "base/files/file_util.h"
16 #include "base/json/json_reader.h"
17 #include "base/logging.h"
18 #include "base/notreached.h"
19 #include "base/path_service.h"
20 #include "base/strings/string_util.h"
21 #include "base/values.h"
22 #include "net/base/address_list.h"
23 #include "net/base/host_port_pair.h"
24 #include "net/base/net_errors.h"
25 #include "net/base/network_isolation_key.h"
26 #include "net/base/port_util.h"
27 #include "net/cert/x509_certificate.h"
28 #include "net/dns/public/dns_query_type.h"
29 #include "net/log/net_log_with_source.h"
30 #include "net/test/cert_test_util.h"
31 #include "net/test/test_data_directory.h"
32 #include "url/gurl.h"
33
34 namespace net {
35
36 namespace {
37
GetHostname(BaseTestServer::Type type,const BaseTestServer::SSLOptions & options)38 std::string GetHostname(BaseTestServer::Type type,
39 const BaseTestServer::SSLOptions& options) {
40 if (BaseTestServer::UsingSSL(type)) {
41 if (options.server_certificate ==
42 BaseTestServer::SSLOptions::CERT_MISMATCHED_NAME ||
43 options.server_certificate ==
44 BaseTestServer::SSLOptions::CERT_COMMON_NAME_IS_DOMAIN) {
45 // For |CERT_MISMATCHED_NAME|, return a different hostname string
46 // that resolves to the same hostname. For
47 // |CERT_COMMON_NAME_IS_DOMAIN|, the certificate is issued for
48 // "localhost" instead of "127.0.0.1".
49 return "localhost";
50 }
51 }
52
53 return "127.0.0.1";
54 }
55
GetLocalCertificatesDir(const base::FilePath & certificates_dir,base::FilePath * local_certificates_dir)56 bool GetLocalCertificatesDir(const base::FilePath& certificates_dir,
57 base::FilePath* local_certificates_dir) {
58 if (certificates_dir.IsAbsolute()) {
59 *local_certificates_dir = certificates_dir;
60 return true;
61 }
62
63 base::FilePath src_dir;
64 if (!base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &src_dir)) {
65 return false;
66 }
67
68 *local_certificates_dir = src_dir.Append(certificates_dir);
69 return true;
70 }
71
72 } // namespace
73
74 BaseTestServer::SSLOptions::SSLOptions() = default;
SSLOptions(ServerCertificate cert)75 BaseTestServer::SSLOptions::SSLOptions(ServerCertificate cert)
76 : server_certificate(cert) {}
SSLOptions(base::FilePath cert)77 BaseTestServer::SSLOptions::SSLOptions(base::FilePath cert)
78 : custom_certificate(std::move(cert)) {}
79 BaseTestServer::SSLOptions::SSLOptions(const SSLOptions& other) = default;
80
81 BaseTestServer::SSLOptions::~SSLOptions() = default;
82
GetCertificateFile() const83 base::FilePath BaseTestServer::SSLOptions::GetCertificateFile() const {
84 if (!custom_certificate.empty())
85 return custom_certificate;
86
87 switch (server_certificate) {
88 case CERT_OK:
89 case CERT_MISMATCHED_NAME:
90 return base::FilePath(FILE_PATH_LITERAL("ok_cert.pem"));
91 case CERT_COMMON_NAME_IS_DOMAIN:
92 return base::FilePath(FILE_PATH_LITERAL("localhost_cert.pem"));
93 case CERT_EXPIRED:
94 return base::FilePath(FILE_PATH_LITERAL("expired_cert.pem"));
95 case CERT_CHAIN_WRONG_ROOT:
96 // This chain uses its own dedicated test root certificate to avoid
97 // side-effects that may affect testing.
98 return base::FilePath(FILE_PATH_LITERAL("redundant-server-chain.pem"));
99 case CERT_BAD_VALIDITY:
100 return base::FilePath(FILE_PATH_LITERAL("bad_validity.pem"));
101 case CERT_KEY_USAGE_RSA_ENCIPHERMENT:
102 return base::FilePath(
103 FILE_PATH_LITERAL("key_usage_rsa_keyencipherment.pem"));
104 case CERT_KEY_USAGE_RSA_DIGITAL_SIGNATURE:
105 return base::FilePath(
106 FILE_PATH_LITERAL("key_usage_rsa_digitalsignature.pem"));
107 case CERT_TEST_NAMES:
108 return base::FilePath(FILE_PATH_LITERAL("test_names.pem"));
109 default:
110 NOTREACHED();
111 }
112 }
113
BaseTestServer(Type type)114 BaseTestServer::BaseTestServer(Type type) : type_(type) {
115 Init(GetHostname(type, ssl_options_));
116 }
117
BaseTestServer(Type type,const SSLOptions & ssl_options)118 BaseTestServer::BaseTestServer(Type type, const SSLOptions& ssl_options)
119 : ssl_options_(ssl_options), type_(type) {
120 DCHECK(UsingSSL(type));
121 Init(GetHostname(type, ssl_options));
122 }
123
124 BaseTestServer::~BaseTestServer() = default;
125
Start()126 bool BaseTestServer::Start() {
127 return StartInBackground() && BlockUntilStarted();
128 }
129
host_port_pair() const130 const HostPortPair& BaseTestServer::host_port_pair() const {
131 DCHECK(started_);
132 return host_port_pair_;
133 }
134
GetScheme() const135 std::string BaseTestServer::GetScheme() const {
136 switch (type_) {
137 case TYPE_WS:
138 return "ws";
139 case TYPE_WSS:
140 return "wss";
141 default:
142 NOTREACHED();
143 }
144 }
145
GetAddressList(AddressList * address_list) const146 bool BaseTestServer::GetAddressList(AddressList* address_list) const {
147 // Historically, this function did a DNS lookup because `host_port_pair_`
148 // could specify something other than localhost. Now it is always localhost.
149 DCHECK(host_port_pair_.host() == "127.0.0.1" ||
150 host_port_pair_.host() == "localhost");
151 DCHECK(address_list);
152 *address_list = AddressList(
153 IPEndPoint(IPAddress::IPv4Localhost(), host_port_pair_.port()));
154 return true;
155 }
156
GetPort()157 uint16_t BaseTestServer::GetPort() {
158 return host_port_pair_.port();
159 }
160
SetPort(uint16_t port)161 void BaseTestServer::SetPort(uint16_t port) {
162 host_port_pair_.set_port(port);
163 }
164
GetURL(const std::string & path) const165 GURL BaseTestServer::GetURL(const std::string& path) const {
166 return GURL(GetScheme() + "://" + host_port_pair_.ToString() + "/" + path);
167 }
168
GetURL(const std::string & hostname,const std::string & relative_url) const169 GURL BaseTestServer::GetURL(const std::string& hostname,
170 const std::string& relative_url) const {
171 GURL local_url = GetURL(relative_url);
172 GURL::Replacements replace_host;
173 replace_host.SetHostStr(hostname);
174 return local_url.ReplaceComponents(replace_host);
175 }
176
GetURLWithUser(const std::string & path,const std::string & user) const177 GURL BaseTestServer::GetURLWithUser(const std::string& path,
178 const std::string& user) const {
179 return GURL(GetScheme() + "://" + user + "@" + host_port_pair_.ToString() +
180 "/" + path);
181 }
182
GetURLWithUserAndPassword(const std::string & path,const std::string & user,const std::string & password) const183 GURL BaseTestServer::GetURLWithUserAndPassword(const std::string& path,
184 const std::string& user,
185 const std::string& password) const {
186 return GURL(GetScheme() + "://" + user + ":" + password + "@" +
187 host_port_pair_.ToString() + "/" + path);
188 }
189
190 // static
GetFilePathWithReplacements(const std::string & original_file_path,const std::vector<StringPair> & text_to_replace,std::string * replacement_path)191 bool BaseTestServer::GetFilePathWithReplacements(
192 const std::string& original_file_path,
193 const std::vector<StringPair>& text_to_replace,
194 std::string* replacement_path) {
195 std::string new_file_path = original_file_path;
196 bool first_query_parameter = true;
197 const std::vector<StringPair>::const_iterator end = text_to_replace.end();
198 for (auto it = text_to_replace.begin(); it != end; ++it) {
199 const std::string& old_text = it->first;
200 const std::string& new_text = it->second;
201 std::string base64_old = base::Base64Encode(old_text);
202 std::string base64_new = base::Base64Encode(new_text);
203 if (first_query_parameter) {
204 new_file_path += "?";
205 first_query_parameter = false;
206 } else {
207 new_file_path += "&";
208 }
209 new_file_path += "replace_text=";
210 new_file_path += base64_old;
211 new_file_path += ":";
212 new_file_path += base64_new;
213 }
214
215 *replacement_path = new_file_path;
216 return true;
217 }
218
RegisterTestCerts()219 ScopedTestRoot BaseTestServer::RegisterTestCerts() {
220 auto root = ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem");
221 if (!root)
222 return ScopedTestRoot();
223 return ScopedTestRoot(CertificateList{root});
224 }
225
LoadTestRootCert()226 bool BaseTestServer::LoadTestRootCert() {
227 scoped_test_root_ = RegisterTestCerts();
228 return !scoped_test_root_.IsEmpty();
229 }
230
GetCertificate() const231 scoped_refptr<X509Certificate> BaseTestServer::GetCertificate() const {
232 base::FilePath certificate_path;
233 if (!GetLocalCertificatesDir(certificates_dir_, &certificate_path))
234 return nullptr;
235
236 base::FilePath certificate_file(ssl_options_.GetCertificateFile());
237 if (certificate_file.value().empty())
238 return nullptr;
239
240 certificate_path = certificate_path.Append(certificate_file);
241
242 std::string cert_data;
243 if (!base::ReadFileToString(certificate_path, &cert_data))
244 return nullptr;
245
246 CertificateList certs_in_file =
247 X509Certificate::CreateCertificateListFromBytes(
248 base::as_byte_span(cert_data),
249 X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
250 if (certs_in_file.empty())
251 return nullptr;
252 return certs_in_file[0];
253 }
254
Init(const std::string & host)255 void BaseTestServer::Init(const std::string& host) {
256 host_port_pair_ = HostPortPair(host, 0);
257
258 // TODO(battre) Remove this after figuring out why the TestServer is flaky.
259 // http://crbug.com/96594
260 log_to_console_ = true;
261 }
262
SetResourcePath(const base::FilePath & document_root,const base::FilePath & certificates_dir)263 void BaseTestServer::SetResourcePath(const base::FilePath& document_root,
264 const base::FilePath& certificates_dir) {
265 // This method shouldn't get called twice.
266 DCHECK(certificates_dir_.empty());
267 document_root_ = document_root;
268 certificates_dir_ = certificates_dir;
269 DCHECK(!certificates_dir_.empty());
270 }
271
SetAndParseServerData(const std::string & server_data,int * port)272 bool BaseTestServer::SetAndParseServerData(const std::string& server_data,
273 int* port) {
274 VLOG(1) << "Server data: " << server_data;
275 auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(server_data);
276 if (!parsed_json.has_value()) {
277 LOG(ERROR) << "Could not parse server data: "
278 << parsed_json.error().message;
279 return false;
280 } else if (!parsed_json->is_dict()) {
281 LOG(ERROR) << "Could not parse server data: expecting a dictionary";
282 return false;
283 }
284
285 std::optional<int> port_value = parsed_json->GetDict().FindInt("port");
286 if (!port_value) {
287 LOG(ERROR) << "Could not find port value";
288 return false;
289 }
290
291 *port = *port_value;
292 if ((*port <= 0) || (*port > std::numeric_limits<uint16_t>::max())) {
293 LOG(ERROR) << "Invalid port value: " << port;
294 return false;
295 }
296
297 return true;
298 }
299
SetupWhenServerStarted()300 bool BaseTestServer::SetupWhenServerStarted() {
301 DCHECK(host_port_pair_.port());
302 DCHECK(!started_);
303
304 if (UsingSSL(type_) && !LoadTestRootCert()) {
305 LOG(ERROR) << "Could not load test root certificate.";
306 return false;
307 }
308
309 started_ = true;
310 allowed_port_ = std::make_unique<ScopedPortException>(host_port_pair_.port());
311 return true;
312 }
313
CleanUpWhenStoppingServer()314 void BaseTestServer::CleanUpWhenStoppingServer() {
315 scoped_test_root_.Reset({});
316 host_port_pair_.set_port(0);
317 allowed_port_.reset();
318 started_ = false;
319 }
320
GenerateArguments() const321 std::optional<base::Value::Dict> BaseTestServer::GenerateArguments() const {
322 base::Value::Dict arguments;
323 arguments.Set("host", host_port_pair_.host());
324 arguments.Set("port", host_port_pair_.port());
325 arguments.Set("data-dir", document_root_.AsUTF8Unsafe());
326
327 if (VLOG_IS_ON(1) || log_to_console_)
328 arguments.Set("log-to-console", base::Value());
329
330 if (ws_basic_auth_) {
331 DCHECK(type_ == TYPE_WS || type_ == TYPE_WSS);
332 arguments.Set("ws-basic-auth", base::Value());
333 }
334
335 if (redirect_connect_to_localhost_) {
336 DCHECK(type_ == TYPE_BASIC_AUTH_PROXY || type_ == TYPE_PROXY);
337 arguments.Set("redirect-connect-to-localhost", base::Value());
338 }
339
340 if (UsingSSL(type_)) {
341 // Check the certificate arguments of the HTTPS server.
342 base::FilePath certificate_path(certificates_dir_);
343 base::FilePath certificate_file(ssl_options_.GetCertificateFile());
344 if (!certificate_file.value().empty()) {
345 certificate_path = certificate_path.Append(certificate_file);
346 if (certificate_path.IsAbsolute() &&
347 !base::PathExists(certificate_path)) {
348 LOG(ERROR) << "Certificate path " << certificate_path.value()
349 << " doesn't exist. Can't launch https server.";
350 return std::nullopt;
351 }
352 arguments.Set("cert-and-key-file", certificate_path.AsUTF8Unsafe());
353 }
354
355 // Check the client certificate related arguments.
356 if (ssl_options_.request_client_certificate)
357 arguments.Set("ssl-client-auth", base::Value());
358
359 base::Value::List ssl_client_certs;
360
361 std::vector<base::FilePath>::const_iterator it;
362 for (it = ssl_options_.client_authorities.begin();
363 it != ssl_options_.client_authorities.end(); ++it) {
364 if (it->IsAbsolute() && !base::PathExists(*it)) {
365 LOG(ERROR) << "Client authority path " << it->value()
366 << " doesn't exist. Can't launch https server.";
367 return std::nullopt;
368 }
369 ssl_client_certs.Append(it->AsUTF8Unsafe());
370 }
371
372 if (ssl_client_certs.size()) {
373 arguments.Set("ssl-client-ca", std::move(ssl_client_certs));
374 }
375 }
376
377 return std::make_optional(std::move(arguments));
378 }
379
380 } // namespace net
381