1 // Copyright 2013 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/spawned_test_server/remote_test_server.h"
6 
7 #include <stdint.h>
8 
9 #include <limits>
10 #include <vector>
11 
12 #include "base/base_paths.h"
13 #include "base/files/file_path.h"
14 #include "base/files/file_util.h"
15 #include "base/json/json_reader.h"
16 #include "base/json/json_writer.h"
17 #include "base/logging.h"
18 #include "base/message_loop/message_pump_type.h"
19 #include "base/path_service.h"
20 #include "base/strings/string_number_conversions.h"
21 #include "base/strings/string_split.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/threading/thread_restrictions.h"
24 #include "base/values.h"
25 #include "build/build_config.h"
26 #include "net/base/host_port_pair.h"
27 #include "net/base/ip_endpoint.h"
28 #include "net/base/net_errors.h"
29 #include "net/test/spawned_test_server/remote_test_server_spawner_request.h"
30 #include "url/gurl.h"
31 
32 namespace net {
33 
34 namespace {
35 
36 // Please keep in sync with dictionary SERVER_TYPES in testserver.py
GetServerTypeString(BaseTestServer::Type type)37 std::string GetServerTypeString(BaseTestServer::Type type) {
38   switch (type) {
39     case BaseTestServer::TYPE_FTP:
40       return "ftp";
41     case BaseTestServer::TYPE_HTTP:
42     case BaseTestServer::TYPE_HTTPS:
43       return "http";
44     case BaseTestServer::TYPE_WS:
45     case BaseTestServer::TYPE_WSS:
46       return "ws";
47     default:
48       NOTREACHED();
49   }
50   return std::string();
51 }
52 
53 // Returns platform-specific path to the config file for the test server.
GetTestServerConfigFilePath()54 base::FilePath GetTestServerConfigFilePath() {
55   base::FilePath dir;
56 #if defined(OS_ANDROID)
57   base::PathService::Get(base::DIR_ANDROID_EXTERNAL_STORAGE, &dir);
58 #else
59   base::PathService::Get(base::DIR_TEMP, &dir);
60 #endif
61   return dir.AppendASCII("net-test-server-config");
62 }
63 
64 // Reads base URL for the test server spawner. That URL is used to control the
65 // test server.
ReadSpawnerUrlFromConfig()66 std::string ReadSpawnerUrlFromConfig() {
67   base::ScopedAllowBlockingForTesting allow_blocking;
68 
69   base::FilePath config_path = GetTestServerConfigFilePath();
70 
71   if (!base::PathExists(config_path))
72     return "";
73 
74   std::string config_json;
75   if (!ReadFileToString(config_path, &config_json))
76     LOG(FATAL) << "Failed to read " << config_path.value();
77 
78   base::Optional<base::Value> config = base::JSONReader::Read(config_json);
79   if (!config)
80     LOG(FATAL) << "Failed to parse " << config_path.value();
81 
82   std::string* result = config->FindStringKey("spawner_url_base");
83   if (!result)
84     LOG(FATAL) << "spawner_url_base is not specified in the config";
85 
86   return *result;
87 }
88 
89 }  // namespace
90 
RemoteTestServer(Type type,const base::FilePath & document_root)91 RemoteTestServer::RemoteTestServer(Type type,
92                                    const base::FilePath& document_root)
93     : BaseTestServer(type), io_thread_("RemoteTestServer IO Thread") {
94   if (!Init(document_root))
95     NOTREACHED();
96 }
97 
RemoteTestServer(Type type,const SSLOptions & ssl_options,const base::FilePath & document_root)98 RemoteTestServer::RemoteTestServer(Type type,
99                                    const SSLOptions& ssl_options,
100                                    const base::FilePath& document_root)
101     : BaseTestServer(type, ssl_options),
102       io_thread_("RemoteTestServer IO Thread") {
103   if (!Init(document_root))
104     NOTREACHED();
105 }
106 
~RemoteTestServer()107 RemoteTestServer::~RemoteTestServer() {
108   Stop();
109 }
110 
StartInBackground()111 bool RemoteTestServer::StartInBackground() {
112   DCHECK(!started());
113   DCHECK(!start_request_);
114 
115   base::DictionaryValue arguments_dict;
116   if (!GenerateArguments(&arguments_dict))
117     return false;
118 
119   arguments_dict.Set("on-remote-server", std::make_unique<base::Value>());
120 
121   // Append the 'server-type' argument which is used by spawner server to
122   // pass right server type to Python test server.
123   arguments_dict.SetString("server-type", GetServerTypeString(type()));
124 
125   // Generate JSON-formatted argument string.
126   std::string arguments_string;
127   base::JSONWriter::Write(arguments_dict, &arguments_string);
128   if (arguments_string.empty())
129     return false;
130 
131   start_request_ = std::make_unique<RemoteTestServerSpawnerRequest>(
132       io_thread_.task_runner(), GetSpawnerUrl("start"), arguments_string);
133 
134   return true;
135 }
136 
BlockUntilStarted()137 bool RemoteTestServer::BlockUntilStarted() {
138   DCHECK(start_request_);
139 
140   std::string server_data_json;
141   bool request_result = start_request_->WaitForCompletion(&server_data_json);
142   start_request_.reset();
143   if (!request_result)
144     return false;
145 
146   // Parse server_data_json.
147   if (server_data_json.empty() ||
148       !SetAndParseServerData(server_data_json, &remote_port_)) {
149     LOG(ERROR) << "Could not parse server_data: " << server_data_json;
150     return false;
151   }
152 
153   SetPort(remote_port_);
154 
155   return SetupWhenServerStarted();
156 }
157 
Stop()158 bool RemoteTestServer::Stop() {
159   DCHECK(!start_request_);
160 
161   if (remote_port_) {
162     std::unique_ptr<RemoteTestServerSpawnerRequest> kill_request =
163         std::make_unique<RemoteTestServerSpawnerRequest>(
164             io_thread_.task_runner(),
165             GetSpawnerUrl(base::StringPrintf("kill?port=%d", remote_port_)),
166             std::string());
167 
168     if (!kill_request->WaitForCompletion(nullptr))
169       LOG(FATAL) << "Failed stopping RemoteTestServer";
170 
171     remote_port_ = 0;
172   }
173 
174   CleanUpWhenStoppingServer();
175 
176   return true;
177 }
178 
179 // On Android, the document root in the device is not the same as the document
180 // root in the host machine where the test server is launched. So prepend
181 // DIR_SOURCE_ROOT here to get the actual path of document root on the Android
182 // device.
GetDocumentRoot() const183 base::FilePath RemoteTestServer::GetDocumentRoot() const {
184   base::FilePath src_dir;
185   base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir);
186   return src_dir.Append(document_root());
187 }
188 
Init(const base::FilePath & document_root)189 bool RemoteTestServer::Init(const base::FilePath& document_root) {
190   if (document_root.IsAbsolute())
191     return false;
192 
193   spawner_url_base_ = ReadSpawnerUrlFromConfig();
194 
195   bool thread_started = io_thread_.StartWithOptions(
196       base::Thread::Options(base::MessagePumpType::IO, 0));
197   CHECK(thread_started);
198 
199   // Unlike LocalTestServer, RemoteTestServer passes relative paths to the test
200   // server. The test server fails on empty strings in some configurations.
201   base::FilePath fixed_root = document_root;
202   if (fixed_root.empty())
203     fixed_root = base::FilePath(base::FilePath::kCurrentDirectory);
204   SetResourcePath(fixed_root, base::FilePath()
205                                   .AppendASCII("net")
206                                   .AppendASCII("data")
207                                   .AppendASCII("ssl")
208                                   .AppendASCII("certificates"));
209   return true;
210 }
211 
GetSpawnerUrl(const std::string & command) const212 GURL RemoteTestServer::GetSpawnerUrl(const std::string& command) const {
213   CHECK(!spawner_url_base_.empty());
214   std::string url = spawner_url_base_ + "/" + command;
215   GURL result = GURL(url);
216   CHECK(result.is_valid()) << url;
217   return result;
218 }
219 
220 }  // namespace net
221