Home | History | Annotate | Download | only in test
      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 
     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 
     62 TestServer::HTTPSOptions::HTTPSOptions()
     63     : server_certificate(CERT_OK),
     64       request_client_certificate(false),
     65       bulk_ciphers(HTTPSOptions::BULK_CIPHER_ANY) {}
     66 
     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 
     73 TestServer::HTTPSOptions::~HTTPSOptions() {}
     74 
     75 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 
     88 TestServer::TestServer(Type type, const FilePath& document_root)
     89     : type_(type),
     90       started_(false) {
     91   Init(document_root);
     92 }
     93 
     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 
    102 TestServer::~TestServer() {
    103   TestRootCerts* root_certs = TestRootCerts::GetInstance();
    104   root_certs->Clear();
    105   Stop();
    106 }
    107 
    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 
    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 
    166 const HostPortPair& TestServer::host_port_pair() const {
    167   DCHECK(started_);
    168   return host_port_pair_;
    169 }
    170 
    171 const DictionaryValue& TestServer::server_data() const {
    172   DCHECK(started_);
    173   return *server_data_;
    174 }
    175 
    176 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 
    191 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 
    205 GURL TestServer::GetURL(const std::string& path) const {
    206   return GURL(GetScheme() + "://" + host_port_pair_.ToString() +
    207               "/" + path);
    208 }
    209 
    210 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 
    217 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
    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 
    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 
    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 
    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 
    335 FilePath TestServer::GetRootCertificatePath() const {
    336   return certificates_dir_.AppendASCII("root_ca_cert.crt");
    337 }
    338 
    339 bool TestServer::LoadTestRootCert() {
    340   TestRootCerts* root_certs = TestRootCerts::GetInstance();
    341   return root_certs->AddFromFile(GetRootCertificatePath());
    342 }
    343 
    344 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