1 // Copyright (c) 2011 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/test_server.h"
6
7 #include <algorithm>
8 #include <string>
9 #include <vector>
10
11 #include "build/build_config.h"
12
13 #if defined(OS_MACOSX)
14 #include "net/base/x509_certificate.h"
15 #endif
16
17 #include "base/base64.h"
18 #include "base/command_line.h"
19 #include "base/debug/leak_annotations.h"
20 #include "base/file_util.h"
21 #include "base/json/json_reader.h"
22 #include "base/logging.h"
23 #include "base/memory/scoped_ptr.h"
24 #include "base/path_service.h"
25 #include "base/string_number_conversions.h"
26 #include "base/utf_string_conversions.h"
27 #include "base/values.h"
28 #include "googleurl/src/gurl.h"
29 #include "net/base/host_port_pair.h"
30 #include "net/base/host_resolver.h"
31 #include "net/base/net_errors.h"
32 #include "net/base/test_completion_callback.h"
33 #include "net/base/test_root_certs.h"
34 #include "net/socket/tcp_client_socket.h"
35 #include "net/test/python_utils.h"
36 #include "testing/platform_test.h"
37
38 namespace net {
39
40 namespace {
41
42 // Number of connection attempts for tests.
43 const int kServerConnectionAttempts = 10;
44
45 // Connection timeout in milliseconds for tests.
46 const int kServerConnectionTimeoutMs = 1000;
47
GetHostname(TestServer::Type type,const TestServer::HTTPSOptions & options)48 std::string GetHostname(TestServer::Type type,
49 const TestServer::HTTPSOptions& options) {
50 if (type == TestServer::TYPE_HTTPS &&
51 options.server_certificate ==
52 TestServer::HTTPSOptions::CERT_MISMATCHED_NAME) {
53 // Return a different hostname string that resolves to the same hostname.
54 return "localhost";
55 }
56
57 return "127.0.0.1";
58 }
59
60 } // namespace
61
HTTPSOptions()62 TestServer::HTTPSOptions::HTTPSOptions()
63 : server_certificate(CERT_OK),
64 request_client_certificate(false),
65 bulk_ciphers(HTTPSOptions::BULK_CIPHER_ANY) {}
66
HTTPSOptions(TestServer::HTTPSOptions::ServerCertificate cert)67 TestServer::HTTPSOptions::HTTPSOptions(
68 TestServer::HTTPSOptions::ServerCertificate cert)
69 : server_certificate(cert),
70 request_client_certificate(false),
71 bulk_ciphers(HTTPSOptions::BULK_CIPHER_ANY) {}
72
~HTTPSOptions()73 TestServer::HTTPSOptions::~HTTPSOptions() {}
74
GetCertificateFile() const75 FilePath TestServer::HTTPSOptions::GetCertificateFile() const {
76 switch (server_certificate) {
77 case CERT_OK:
78 case CERT_MISMATCHED_NAME:
79 return FilePath(FILE_PATH_LITERAL("ok_cert.pem"));
80 case CERT_EXPIRED:
81 return FilePath(FILE_PATH_LITERAL("expired_cert.pem"));
82 default:
83 NOTREACHED();
84 }
85 return FilePath();
86 }
87
TestServer(Type type,const FilePath & document_root)88 TestServer::TestServer(Type type, const FilePath& document_root)
89 : type_(type),
90 started_(false) {
91 Init(document_root);
92 }
93
TestServer(const HTTPSOptions & https_options,const FilePath & document_root)94 TestServer::TestServer(const HTTPSOptions& https_options,
95 const FilePath& document_root)
96 : https_options_(https_options),
97 type_(TYPE_HTTPS),
98 started_(false) {
99 Init(document_root);
100 }
101
~TestServer()102 TestServer::~TestServer() {
103 TestRootCerts* root_certs = TestRootCerts::GetInstance();
104 root_certs->Clear();
105 Stop();
106 }
107
Start()108 bool TestServer::Start() {
109 if (type_ == TYPE_HTTPS) {
110 if (!LoadTestRootCert())
111 return false;
112 }
113
114 // Get path to python server script
115 FilePath testserver_path;
116 if (!PathService::Get(base::DIR_SOURCE_ROOT, &testserver_path)) {
117 LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT";
118 return false;
119 }
120 testserver_path = testserver_path
121 .Append(FILE_PATH_LITERAL("net"))
122 .Append(FILE_PATH_LITERAL("tools"))
123 .Append(FILE_PATH_LITERAL("testserver"))
124 .Append(FILE_PATH_LITERAL("testserver.py"));
125
126 if (!SetPythonPath())
127 return false;
128
129 if (!LaunchPython(testserver_path))
130 return false;
131
132 if (!WaitToStart()) {
133 Stop();
134 return false;
135 }
136
137 allowed_port_.reset(new ScopedPortException(host_port_pair_.port()));
138
139 started_ = true;
140 return true;
141 }
142
Stop()143 bool TestServer::Stop() {
144 if (!process_handle_)
145 return true;
146
147 started_ = false;
148
149 // First check if the process has already terminated.
150 bool ret = base::WaitForSingleProcess(process_handle_, 0);
151 if (!ret)
152 ret = base::KillProcess(process_handle_, 1, true);
153
154 if (ret) {
155 base::CloseProcessHandle(process_handle_);
156 process_handle_ = base::kNullProcessHandle;
157 } else {
158 VLOG(1) << "Kill failed?";
159 }
160
161 allowed_port_.reset();
162
163 return ret;
164 }
165
host_port_pair() const166 const HostPortPair& TestServer::host_port_pair() const {
167 DCHECK(started_);
168 return host_port_pair_;
169 }
170
server_data() const171 const DictionaryValue& TestServer::server_data() const {
172 DCHECK(started_);
173 return *server_data_;
174 }
175
GetScheme() const176 std::string TestServer::GetScheme() const {
177 switch (type_) {
178 case TYPE_FTP:
179 return "ftp";
180 case TYPE_HTTP:
181 case TYPE_SYNC:
182 return "http";
183 case TYPE_HTTPS:
184 return "https";
185 default:
186 NOTREACHED();
187 }
188 return std::string();
189 }
190
GetAddressList(AddressList * address_list) const191 bool TestServer::GetAddressList(AddressList* address_list) const {
192 DCHECK(address_list);
193
194 scoped_ptr<HostResolver> resolver(
195 CreateSystemHostResolver(HostResolver::kDefaultParallelism, NULL, NULL));
196 HostResolver::RequestInfo info(host_port_pair_);
197 int rv = resolver->Resolve(info, address_list, NULL, NULL, BoundNetLog());
198 if (rv != net::OK) {
199 LOG(ERROR) << "Failed to resolve hostname: " << host_port_pair_.host();
200 return false;
201 }
202 return true;
203 }
204
GetURL(const std::string & path) const205 GURL TestServer::GetURL(const std::string& path) const {
206 return GURL(GetScheme() + "://" + host_port_pair_.ToString() +
207 "/" + path);
208 }
209
GetURLWithUser(const std::string & path,const std::string & user) const210 GURL TestServer::GetURLWithUser(const std::string& path,
211 const std::string& user) const {
212 return GURL(GetScheme() + "://" + user + "@" +
213 host_port_pair_.ToString() +
214 "/" + path);
215 }
216
GetURLWithUserAndPassword(const std::string & path,const std::string & user,const std::string & password) const217 GURL TestServer::GetURLWithUserAndPassword(const std::string& path,
218 const std::string& user,
219 const std::string& password) const {
220 return GURL(GetScheme() + "://" + user + ":" + password +
221 "@" + host_port_pair_.ToString() +
222 "/" + path);
223 }
224
225 // static
GetFilePathWithReplacements(const std::string & original_file_path,const std::vector<StringPair> & text_to_replace,std::string * replacement_path)226 bool TestServer::GetFilePathWithReplacements(
227 const std::string& original_file_path,
228 const std::vector<StringPair>& text_to_replace,
229 std::string* replacement_path) {
230 std::string new_file_path = original_file_path;
231 bool first_query_parameter = true;
232 const std::vector<StringPair>::const_iterator end = text_to_replace.end();
233 for (std::vector<StringPair>::const_iterator it = text_to_replace.begin();
234 it != end;
235 ++it) {
236 const std::string& old_text = it->first;
237 const std::string& new_text = it->second;
238 std::string base64_old;
239 std::string base64_new;
240 if (!base::Base64Encode(old_text, &base64_old))
241 return false;
242 if (!base::Base64Encode(new_text, &base64_new))
243 return false;
244 if (first_query_parameter) {
245 new_file_path += "?";
246 first_query_parameter = false;
247 } else {
248 new_file_path += "&";
249 }
250 new_file_path += "replace_text=";
251 new_file_path += base64_old;
252 new_file_path += ":";
253 new_file_path += base64_new;
254 }
255
256 *replacement_path = new_file_path;
257 return true;
258 }
259
Init(const FilePath & document_root)260 void TestServer::Init(const FilePath& document_root) {
261 // At this point, the port that the testserver will listen on is unknown.
262 // The testserver will listen on an ephemeral port, and write the port
263 // number out over a pipe that this TestServer object will read from. Once
264 // that is complete, the host_port_pair_ will contain the actual port.
265 host_port_pair_ = HostPortPair(GetHostname(type_, https_options_), 0);
266 process_handle_ = base::kNullProcessHandle;
267
268 FilePath src_dir;
269 PathService::Get(base::DIR_SOURCE_ROOT, &src_dir);
270
271 document_root_ = src_dir.Append(document_root);
272
273 certificates_dir_ = src_dir.Append(FILE_PATH_LITERAL("net"))
274 .Append(FILE_PATH_LITERAL("data"))
275 .Append(FILE_PATH_LITERAL("ssl"))
276 .Append(FILE_PATH_LITERAL("certificates"));
277 }
278
SetPythonPath()279 bool TestServer::SetPythonPath() {
280 FilePath third_party_dir;
281 if (!PathService::Get(base::DIR_SOURCE_ROOT, &third_party_dir)) {
282 LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT";
283 return false;
284 }
285 third_party_dir = third_party_dir.Append(FILE_PATH_LITERAL("third_party"));
286
287 // For simplejson. (simplejson, unlike all the other python modules
288 // we include, doesn't have an extra 'simplejson' directory, so we
289 // need to include its parent directory, i.e. third_party_dir).
290 AppendToPythonPath(third_party_dir);
291
292 AppendToPythonPath(third_party_dir.Append(FILE_PATH_LITERAL("tlslite")));
293 AppendToPythonPath(third_party_dir.Append(FILE_PATH_LITERAL("pyftpdlib")));
294
295 // Locate the Python code generated by the protocol buffers compiler.
296 FilePath pyproto_code_dir;
297 if (!GetPyProtoPath(&pyproto_code_dir)) {
298 LOG(WARNING) << "Cannot find pyproto dir for generated code. "
299 << "Testserver features that rely on it will not work";
300 return true;
301 }
302
303 AppendToPythonPath(pyproto_code_dir);
304 AppendToPythonPath(pyproto_code_dir.Append(FILE_PATH_LITERAL("sync_pb")));
305 AppendToPythonPath(pyproto_code_dir.Append(
306 FILE_PATH_LITERAL("device_management_pb")));
307
308 return true;
309 }
310
ParseServerData(const std::string & server_data)311 bool TestServer::ParseServerData(const std::string& server_data) {
312 VLOG(1) << "Server data: " << server_data;
313 base::JSONReader json_reader;
314 scoped_ptr<Value> value(json_reader.JsonToValue(server_data, true, false));
315 if (!value.get() ||
316 !value->IsType(Value::TYPE_DICTIONARY)) {
317 LOG(ERROR) << "Could not parse server data: "
318 << json_reader.GetErrorMessage();
319 return false;
320 }
321 server_data_.reset(static_cast<DictionaryValue*>(value.release()));
322 int port = 0;
323 if (!server_data_->GetInteger("port", &port)) {
324 LOG(ERROR) << "Could not find port value";
325 return false;
326 }
327 if ((port <= 0) || (port > kuint16max)) {
328 LOG(ERROR) << "Invalid port value: " << port;
329 return false;
330 }
331 host_port_pair_.set_port(port);
332 return true;
333 }
334
GetRootCertificatePath() const335 FilePath TestServer::GetRootCertificatePath() const {
336 return certificates_dir_.AppendASCII("root_ca_cert.crt");
337 }
338
LoadTestRootCert()339 bool TestServer::LoadTestRootCert() {
340 TestRootCerts* root_certs = TestRootCerts::GetInstance();
341 return root_certs->AddFromFile(GetRootCertificatePath());
342 }
343
AddCommandLineArguments(CommandLine * command_line) const344 bool TestServer::AddCommandLineArguments(CommandLine* command_line) const {
345 command_line->AppendSwitchASCII("port",
346 base::IntToString(host_port_pair_.port()));
347 command_line->AppendSwitchPath("data-dir", document_root_);
348
349 if (logging::GetMinLogLevel() == logging::LOG_VERBOSE) {
350 command_line->AppendArg("--log-to-console");
351 }
352
353 if (type_ == TYPE_FTP) {
354 command_line->AppendArg("-f");
355 } else if (type_ == TYPE_SYNC) {
356 command_line->AppendArg("--sync");
357 } else if (type_ == TYPE_HTTPS) {
358 FilePath certificate_path(certificates_dir_);
359 certificate_path = certificate_path.Append(
360 https_options_.GetCertificateFile());
361 if (!file_util::PathExists(certificate_path)) {
362 LOG(ERROR) << "Certificate path " << certificate_path.value()
363 << " doesn't exist. Can't launch https server.";
364 return false;
365 }
366 command_line->AppendSwitchPath("https", certificate_path);
367
368 if (https_options_.request_client_certificate)
369 command_line->AppendSwitch("ssl-client-auth");
370
371 for (std::vector<FilePath>::const_iterator it =
372 https_options_.client_authorities.begin();
373 it != https_options_.client_authorities.end(); ++it) {
374 if (!file_util::PathExists(*it)) {
375 LOG(ERROR) << "Client authority path " << it->value()
376 << " doesn't exist. Can't launch https server.";
377 return false;
378 }
379
380 command_line->AppendSwitchPath("ssl-client-ca", *it);
381 }
382
383 const char kBulkCipherSwitch[] = "ssl-bulk-cipher";
384 if (https_options_.bulk_ciphers & HTTPSOptions::BULK_CIPHER_RC4)
385 command_line->AppendSwitchASCII(kBulkCipherSwitch, "rc4");
386 if (https_options_.bulk_ciphers & HTTPSOptions::BULK_CIPHER_AES128)
387 command_line->AppendSwitchASCII(kBulkCipherSwitch, "aes128");
388 if (https_options_.bulk_ciphers & HTTPSOptions::BULK_CIPHER_AES256)
389 command_line->AppendSwitchASCII(kBulkCipherSwitch, "aes256");
390 if (https_options_.bulk_ciphers & HTTPSOptions::BULK_CIPHER_3DES)
391 command_line->AppendSwitchASCII(kBulkCipherSwitch, "3des");
392 }
393
394 return true;
395 }
396
397 } // namespace net
398