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
38namespace net {
39
40namespace {
41
42// Number of connection attempts for tests.
43const int kServerConnectionAttempts = 10;
44
45// Connection timeout in milliseconds for tests.
46const int kServerConnectionTimeoutMs = 1000;
47
48std::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
62TestServer::HTTPSOptions::HTTPSOptions()
63    : server_certificate(CERT_OK),
64      request_client_certificate(false),
65      bulk_ciphers(HTTPSOptions::BULK_CIPHER_ANY) {}
66
67TestServer::HTTPSOptions::HTTPSOptions(
68    TestServer::HTTPSOptions::ServerCertificate cert)
69    : server_certificate(cert),
70      request_client_certificate(false),
71      bulk_ciphers(HTTPSOptions::BULK_CIPHER_ANY) {}
72
73TestServer::HTTPSOptions::~HTTPSOptions() {}
74
75FilePath 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
88TestServer::TestServer(Type type, const FilePath& document_root)
89    : type_(type),
90      started_(false) {
91  Init(document_root);
92}
93
94TestServer::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
102TestServer::~TestServer() {
103  TestRootCerts* root_certs = TestRootCerts::GetInstance();
104  root_certs->Clear();
105  Stop();
106}
107
108bool 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
143bool 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
166const HostPortPair& TestServer::host_port_pair() const {
167  DCHECK(started_);
168  return host_port_pair_;
169}
170
171const DictionaryValue& TestServer::server_data() const {
172  DCHECK(started_);
173  return *server_data_;
174}
175
176std::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
191bool 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
205GURL TestServer::GetURL(const std::string& path) const {
206  return GURL(GetScheme() + "://" + host_port_pair_.ToString() +
207              "/" + path);
208}
209
210GURL 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
217GURL 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
226bool 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
260void 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
279bool 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
311bool 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
335FilePath TestServer::GetRootCertificatePath() const {
336  return certificates_dir_.AppendASCII("root_ca_cert.crt");
337}
338
339bool TestServer::LoadTestRootCert() {
340  TestRootCerts* root_certs = TestRootCerts::GetInstance();
341  return root_certs->AddFromFile(GetRootCertificatePath());
342}
343
344bool 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