• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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