1 // Copyright (c) 2012 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 <stddef.h>
6 #include <stdint.h>
7
8 #include <algorithm>
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/compiler_specific.h"
13 #include "base/files/file_util.h"
14 #include "base/guid.h"
15 #include "base/json/json_writer.h"
16 #include "base/location.h"
17 #include "base/logging.h"
18 #include "base/macros.h"
19 #include "base/memory/ref_counted_memory.h"
20 #include "base/message_loop/message_pump_type.h"
21 #include "base/single_thread_task_runner.h"
22 #include "base/strings/string_number_conversions.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/stringprintf.h"
25 #include "base/task/thread_pool.h"
26 #include "base/threading/thread.h"
27 #include "base/values.h"
28 #include "build/build_config.h"
29 #include "content/browser/devtools/devtools_http_handler.h"
30 #include "content/browser/devtools/devtools_manager.h"
31 #include "content/public/browser/browser_task_traits.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "content/public/browser/content_browser_client.h"
34 #include "content/public/browser/devtools_external_agent_proxy_delegate.h"
35 #include "content/public/browser/devtools_frontend_host.h"
36 #include "content/public/browser/devtools_manager_delegate.h"
37 #include "content/public/browser/devtools_socket_factory.h"
38 #include "content/public/common/content_client.h"
39 #include "content/public/common/url_constants.h"
40 #include "content/public/common/user_agent.h"
41 #include "net/base/escape.h"
42 #include "net/base/io_buffer.h"
43 #include "net/base/ip_endpoint.h"
44 #include "net/base/net_errors.h"
45 #include "net/base/url_util.h"
46 #include "net/server/http_server.h"
47 #include "net/server/http_server_request_info.h"
48 #include "net/server/http_server_response_info.h"
49 #include "net/socket/server_socket.h"
50 #include "net/traffic_annotation/network_traffic_annotation.h"
51 #include "v8/include/v8-version-string.h"
52
53 #if defined(OS_ANDROID)
54 #include "base/android/build_info.h"
55 #endif
56
57 #if !defined(OS_ANDROID) && !defined(OS_FUCHSIA)
58 #include "content/browser/devtools/grit/devtools_resources.h" // nogncheck
59 #endif
60
61 namespace content {
62
63 namespace {
64
65 const base::FilePath::CharType kDevToolsActivePortFileName[] =
66 FILE_PATH_LITERAL("DevToolsActivePort");
67
68 const char kDevToolsHandlerThreadName[] = "Chrome_DevToolsHandlerThread";
69
70 const char kPageUrlPrefix[] = "/devtools/page/";
71 const char kBrowserUrlPrefix[] = "/devtools/browser";
72
73 const char kTargetIdField[] = "id";
74 const char kTargetParentIdField[] = "parentId";
75 const char kTargetTypeField[] = "type";
76 const char kTargetTitleField[] = "title";
77 const char kTargetDescriptionField[] = "description";
78 const char kTargetUrlField[] = "url";
79 const char kTargetFaviconUrlField[] = "faviconUrl";
80 const char kTargetWebSocketDebuggerUrlField[] = "webSocketDebuggerUrl";
81 const char kTargetDevtoolsFrontendUrlField[] = "devtoolsFrontendUrl";
82
83 const int32_t kSendBufferSizeForDevTools = 256 * 1024 * 1024; // 256Mb
84 const int32_t kReceiveBufferSizeForDevTools = 100 * 1024 * 1024; // 100Mb
85
86 constexpr net::NetworkTrafficAnnotationTag
87 kDevtoolsHttpHandlerTrafficAnnotation =
88 net::DefineNetworkTrafficAnnotation("devtools_http_handler", R"(
89 semantics {
90 sender: "Devtools Http Handler"
91 description:
92 "This is a remote debugging server, only enabled by "
93 "'--remote-debugging-port' switch. It exposes debugging protocol "
94 "over websockets."
95 trigger: "Run with '--remote-debugging-port' switch."
96 data: "Debugging data, including any data on the open pages."
97 destination: OTHER
98 destination_other: "The data can be sent to any destination."
99 }
100 policy {
101 cookies_allowed: NO
102 setting:
103 "This request cannot be disabled in settings. However it will never "
104 "be made if user does not run with '--remote-debugging-port' switch."
105 policy_exception_justification:
106 "Not implemented, only used in Devtools and is behind a switch."
107 })");
108
RequestIsSafeToServe(const net::HttpServerRequestInfo & info)109 bool RequestIsSafeToServe(const net::HttpServerRequestInfo& info) {
110 // For browser-originating requests, serve only those that are coming from
111 // pages loaded off localhost or fixed IPs.
112 std::string header = info.GetHeaderValue("host");
113 if (header.empty())
114 return true;
115 GURL url = GURL("https://" + header);
116 return url.HostIsIPAddress() || net::IsLocalHostname(url.host(), nullptr);
117 }
118
119 } // namespace
120
121 // ServerWrapper -------------------------------------------------------------
122 // All methods in this class are only called on handler thread.
123 class ServerWrapper : net::HttpServer::Delegate {
124 public:
125 ServerWrapper(base::WeakPtr<DevToolsHttpHandler> handler,
126 std::unique_ptr<net::ServerSocket> socket,
127 const base::FilePath& debug_frontend_dir,
128 bool bundles_resources);
129
130 int GetLocalAddress(net::IPEndPoint* address);
131
132 void AcceptWebSocket(int connection_id,
133 const net::HttpServerRequestInfo& request);
134 void SendOverWebSocket(int connection_id, std::string message);
135 void SendResponse(int connection_id,
136 const net::HttpServerResponseInfo& response);
137 void Send200(int connection_id,
138 const std::string& data,
139 const std::string& mime_type);
140 void Send404(int connection_id);
141 void Send500(int connection_id, const std::string& message);
142 void Close(int connection_id);
143
~ServerWrapper()144 ~ServerWrapper() override {}
145
146 private:
147 // net::HttpServer::Delegate implementation.
OnConnect(int connection_id)148 void OnConnect(int connection_id) override {}
149 void OnHttpRequest(int connection_id,
150 const net::HttpServerRequestInfo& info) override;
151 void OnWebSocketRequest(int connection_id,
152 const net::HttpServerRequestInfo& info) override;
153 void OnWebSocketMessage(int connection_id, std::string data) override;
154 void OnClose(int connection_id) override;
155
156 base::WeakPtr<DevToolsHttpHandler> handler_;
157 std::unique_ptr<net::HttpServer> server_;
158 base::FilePath debug_frontend_dir_;
159 bool bundles_resources_;
160 };
161
ServerWrapper(base::WeakPtr<DevToolsHttpHandler> handler,std::unique_ptr<net::ServerSocket> socket,const base::FilePath & debug_frontend_dir,bool bundles_resources)162 ServerWrapper::ServerWrapper(base::WeakPtr<DevToolsHttpHandler> handler,
163 std::unique_ptr<net::ServerSocket> socket,
164 const base::FilePath& debug_frontend_dir,
165 bool bundles_resources)
166 : handler_(handler),
167 server_(new net::HttpServer(std::move(socket), this)),
168 debug_frontend_dir_(debug_frontend_dir),
169 bundles_resources_(bundles_resources) {}
170
GetLocalAddress(net::IPEndPoint * address)171 int ServerWrapper::GetLocalAddress(net::IPEndPoint* address) {
172 return server_->GetLocalAddress(address);
173 }
174
AcceptWebSocket(int connection_id,const net::HttpServerRequestInfo & request)175 void ServerWrapper::AcceptWebSocket(int connection_id,
176 const net::HttpServerRequestInfo& request) {
177 server_->SetSendBufferSize(connection_id, kSendBufferSizeForDevTools);
178 server_->SetReceiveBufferSize(connection_id, kReceiveBufferSizeForDevTools);
179 server_->AcceptWebSocket(connection_id, request,
180 kDevtoolsHttpHandlerTrafficAnnotation);
181 }
182
SendOverWebSocket(int connection_id,std::string message)183 void ServerWrapper::SendOverWebSocket(int connection_id, std::string message) {
184 server_->SendOverWebSocket(connection_id, std::move(message),
185 kDevtoolsHttpHandlerTrafficAnnotation);
186 }
187
SendResponse(int connection_id,const net::HttpServerResponseInfo & response)188 void ServerWrapper::SendResponse(int connection_id,
189 const net::HttpServerResponseInfo& response) {
190 server_->SendResponse(connection_id, response,
191 kDevtoolsHttpHandlerTrafficAnnotation);
192 }
193
Send200(int connection_id,const std::string & data,const std::string & mime_type)194 void ServerWrapper::Send200(int connection_id,
195 const std::string& data,
196 const std::string& mime_type) {
197 server_->Send200(connection_id, data, mime_type,
198 kDevtoolsHttpHandlerTrafficAnnotation);
199 }
200
Send404(int connection_id)201 void ServerWrapper::Send404(int connection_id) {
202 server_->Send404(connection_id, kDevtoolsHttpHandlerTrafficAnnotation);
203 }
204
Send500(int connection_id,const std::string & message)205 void ServerWrapper::Send500(int connection_id,
206 const std::string& message) {
207 server_->Send500(connection_id, message,
208 kDevtoolsHttpHandlerTrafficAnnotation);
209 }
210
Close(int connection_id)211 void ServerWrapper::Close(int connection_id) {
212 server_->Close(connection_id);
213 }
214
215 // Thread and ServerWrapper lifetime management ------------------------------
216
TerminateOnUI(std::unique_ptr<base::Thread> thread,std::unique_ptr<ServerWrapper> server_wrapper,std::unique_ptr<DevToolsSocketFactory> socket_factory)217 void TerminateOnUI(std::unique_ptr<base::Thread> thread,
218 std::unique_ptr<ServerWrapper> server_wrapper,
219 std::unique_ptr<DevToolsSocketFactory> socket_factory) {
220 DCHECK_CURRENTLY_ON(BrowserThread::UI);
221 if (server_wrapper)
222 thread->task_runner()->DeleteSoon(FROM_HERE, std::move(server_wrapper));
223 if (socket_factory)
224 thread->task_runner()->DeleteSoon(FROM_HERE, std::move(socket_factory));
225 if (thread) {
226 base::ThreadPool::PostTask(
227 FROM_HERE,
228 {base::WithBaseSyncPrimitives(), base::TaskPriority::BEST_EFFORT},
229 BindOnce([](std::unique_ptr<base::Thread>) {}, std::move(thread)));
230 }
231 }
232
ServerStartedOnUI(base::WeakPtr<DevToolsHttpHandler> handler,base::Thread * thread,ServerWrapper * server_wrapper,DevToolsSocketFactory * socket_factory,std::unique_ptr<net::IPEndPoint> ip_address)233 void ServerStartedOnUI(base::WeakPtr<DevToolsHttpHandler> handler,
234 base::Thread* thread,
235 ServerWrapper* server_wrapper,
236 DevToolsSocketFactory* socket_factory,
237 std::unique_ptr<net::IPEndPoint> ip_address) {
238 DCHECK_CURRENTLY_ON(BrowserThread::UI);
239 if (handler && thread && server_wrapper) {
240 handler->ServerStarted(
241 std::unique_ptr<base::Thread>(thread),
242 std::unique_ptr<ServerWrapper>(server_wrapper),
243 std::unique_ptr<DevToolsSocketFactory>(socket_factory),
244 std::move(ip_address));
245 } else {
246 TerminateOnUI(std::unique_ptr<base::Thread>(thread),
247 std::unique_ptr<ServerWrapper>(server_wrapper),
248 std::unique_ptr<DevToolsSocketFactory>(socket_factory));
249 }
250 }
251
StartServerOnHandlerThread(base::WeakPtr<DevToolsHttpHandler> handler,std::unique_ptr<base::Thread> thread,std::unique_ptr<DevToolsSocketFactory> socket_factory,const base::FilePath & output_directory,const base::FilePath & debug_frontend_dir,const std::string & browser_guid,bool bundles_resources)252 void StartServerOnHandlerThread(
253 base::WeakPtr<DevToolsHttpHandler> handler,
254 std::unique_ptr<base::Thread> thread,
255 std::unique_ptr<DevToolsSocketFactory> socket_factory,
256 const base::FilePath& output_directory,
257 const base::FilePath& debug_frontend_dir,
258 const std::string& browser_guid,
259 bool bundles_resources) {
260 DCHECK(thread->task_runner()->BelongsToCurrentThread());
261 std::unique_ptr<ServerWrapper> server_wrapper;
262 std::unique_ptr<net::ServerSocket> server_socket =
263 socket_factory->CreateForHttpServer();
264 std::unique_ptr<net::IPEndPoint> ip_address(new net::IPEndPoint);
265 if (server_socket) {
266 server_wrapper.reset(new ServerWrapper(handler, std::move(server_socket),
267 debug_frontend_dir,
268 bundles_resources));
269 if (server_wrapper->GetLocalAddress(ip_address.get()) != net::OK)
270 ip_address.reset();
271 } else {
272 ip_address.reset();
273 }
274
275 if (ip_address) {
276 std::string message = base::StringPrintf(
277 "\nDevTools listening on ws://%s%s\n", ip_address->ToString().c_str(),
278 browser_guid.c_str());
279 fprintf(stderr, "%s", message.c_str());
280 fflush(stderr);
281
282 // Write this port to a well-known file in the profile directory
283 // so Telemetry, ChromeDriver, etc. can pick it up.
284 if (!output_directory.empty()) {
285 base::FilePath path =
286 output_directory.Append(kDevToolsActivePortFileName);
287 std::string port_target_string = base::StringPrintf(
288 "%d\n%s", ip_address->port(), browser_guid.c_str());
289 if (base::WriteFile(path, port_target_string.c_str(),
290 static_cast<int>(port_target_string.length())) < 0) {
291 LOG(ERROR) << "Error writing DevTools active port to file";
292 }
293 }
294 } else {
295 #if !defined(OS_ANDROID)
296 // Android uses UNIX domain sockets which don't have an IP address.
297 LOG(ERROR) << "Cannot start http server for devtools.";
298 #endif
299 }
300
301 GetUIThreadTaskRunner({})->PostTask(
302 FROM_HERE,
303 base::BindOnce(&ServerStartedOnUI, std::move(handler), thread.release(),
304 server_wrapper.release(), socket_factory.release(),
305 std::move(ip_address)));
306 }
307
308 // DevToolsAgentHostClientImpl -----------------------------------------------
309 // An internal implementation of DevToolsAgentHostClient that delegates
310 // messages sent to a DebuggerShell instance.
311 class DevToolsAgentHostClientImpl : public DevToolsAgentHostClient {
312 public:
DevToolsAgentHostClientImpl(scoped_refptr<base::SingleThreadTaskRunner> task_runner,ServerWrapper * server_wrapper,int connection_id,scoped_refptr<DevToolsAgentHost> agent_host)313 DevToolsAgentHostClientImpl(
314 scoped_refptr<base::SingleThreadTaskRunner> task_runner,
315 ServerWrapper* server_wrapper,
316 int connection_id,
317 scoped_refptr<DevToolsAgentHost> agent_host)
318 : task_runner_(std::move(task_runner)),
319 server_wrapper_(server_wrapper),
320 connection_id_(connection_id),
321 agent_host_(agent_host) {
322 DCHECK_CURRENTLY_ON(BrowserThread::UI);
323 // TODO(dgozman): handle return value of AttachClient.
324 agent_host_->AttachClient(this);
325 }
326
~DevToolsAgentHostClientImpl()327 ~DevToolsAgentHostClientImpl() override {
328 DCHECK_CURRENTLY_ON(BrowserThread::UI);
329 if (agent_host_)
330 agent_host_->DetachClient(this);
331 }
332
AgentHostClosed(DevToolsAgentHost * agent_host)333 void AgentHostClosed(DevToolsAgentHost* agent_host) override {
334 DCHECK_CURRENTLY_ON(BrowserThread::UI);
335 DCHECK(agent_host == agent_host_.get());
336
337 constexpr char kMsg[] =
338 "{\"method\":\"Inspector.detached\","
339 "\"params\":{\"reason\":\"target_closed\"}}";
340 DispatchProtocolMessage(
341 agent_host, base::as_bytes(base::make_span(kMsg, strlen(kMsg))));
342
343 agent_host_ = nullptr;
344 task_runner_->PostTask(
345 FROM_HERE,
346 base::BindOnce(&ServerWrapper::Close, base::Unretained(server_wrapper_),
347 connection_id_));
348 }
349
DispatchProtocolMessage(DevToolsAgentHost * agent_host,base::span<const uint8_t> message)350 void DispatchProtocolMessage(DevToolsAgentHost* agent_host,
351 base::span<const uint8_t> message) override {
352 DCHECK_CURRENTLY_ON(BrowserThread::UI);
353 DCHECK(agent_host == agent_host_.get());
354 task_runner_->PostTask(
355 FROM_HERE,
356 base::BindOnce(&ServerWrapper::SendOverWebSocket,
357 base::Unretained(server_wrapper_), connection_id_,
358 std::string(message.begin(), message.end())));
359 }
360
OnMessage(base::span<const uint8_t> message)361 void OnMessage(base::span<const uint8_t> message) {
362 DCHECK_CURRENTLY_ON(BrowserThread::UI);
363 if (agent_host_)
364 agent_host_->DispatchProtocolMessage(this, message);
365 }
366
367 private:
368 const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
369 ServerWrapper* const server_wrapper_;
370 const int connection_id_;
371 scoped_refptr<DevToolsAgentHost> agent_host_;
372 };
373
TimeComparator(scoped_refptr<DevToolsAgentHost> host1,scoped_refptr<DevToolsAgentHost> host2)374 static bool TimeComparator(scoped_refptr<DevToolsAgentHost> host1,
375 scoped_refptr<DevToolsAgentHost> host2) {
376 return host1->GetLastActivityTime() > host2->GetLastActivityTime();
377 }
378
379 // DevToolsHttpHandler -------------------------------------------------------
380
~DevToolsHttpHandler()381 DevToolsHttpHandler::~DevToolsHttpHandler() {
382 // Disconnecting sessions might lead to the last minute messages generated
383 // by the targets. It is essential that this happens before we issue delete
384 // soon for the server wrapper.
385 connection_to_client_.clear();
386 TerminateOnUI(std::move(thread_), std::move(server_wrapper_),
387 std::move(socket_factory_));
388 }
389
PathWithoutParams(const std::string & path)390 static std::string PathWithoutParams(const std::string& path) {
391 size_t query_position = path.find('?');
392 if (query_position != std::string::npos)
393 return path.substr(0, query_position);
394 return path;
395 }
396
GetMimeType(const std::string & filename)397 static std::string GetMimeType(const std::string& filename) {
398 if (base::EndsWith(filename, ".html", base::CompareCase::INSENSITIVE_ASCII)) {
399 return "text/html";
400 } else if (base::EndsWith(filename, ".css",
401 base::CompareCase::INSENSITIVE_ASCII)) {
402 return "text/css";
403 } else if (base::EndsWith(filename, ".js",
404 base::CompareCase::INSENSITIVE_ASCII)) {
405 return "application/javascript";
406 } else if (base::EndsWith(filename, ".png",
407 base::CompareCase::INSENSITIVE_ASCII)) {
408 return "image/png";
409 } else if (base::EndsWith(filename, ".gif",
410 base::CompareCase::INSENSITIVE_ASCII)) {
411 return "image/gif";
412 } else if (base::EndsWith(filename, ".json",
413 base::CompareCase::INSENSITIVE_ASCII)) {
414 return "application/json";
415 } else if (base::EndsWith(filename, ".svg",
416 base::CompareCase::INSENSITIVE_ASCII)) {
417 return "image/svg+xml";
418 }
419 LOG(ERROR) << "GetMimeType doesn't know mime type for: "
420 << filename
421 << " text/plain will be returned";
422 return "text/plain";
423 }
424
OnHttpRequest(int connection_id,const net::HttpServerRequestInfo & info)425 void ServerWrapper::OnHttpRequest(int connection_id,
426 const net::HttpServerRequestInfo& info) {
427 if (!RequestIsSafeToServe(info)) {
428 Send500(connection_id,
429 "Host header is specified and is not an IP address or localhost.");
430 return;
431 }
432
433 server_->SetSendBufferSize(connection_id, kSendBufferSizeForDevTools);
434
435 if (base::StartsWith(info.path, "/json", base::CompareCase::SENSITIVE)) {
436 GetUIThreadTaskRunner({})->PostTask(
437 FROM_HERE, base::BindOnce(&DevToolsHttpHandler::OnJsonRequest, handler_,
438 connection_id, info));
439 return;
440 }
441
442 if (info.path.empty() || info.path == "/") {
443 // Discovery page request.
444 GetUIThreadTaskRunner({})->PostTask(
445 FROM_HERE, base::BindOnce(&DevToolsHttpHandler::OnDiscoveryPageRequest,
446 handler_, connection_id));
447 return;
448 }
449
450 if (!base::StartsWith(info.path, "/devtools/",
451 base::CompareCase::SENSITIVE)) {
452 server_->Send404(connection_id, kDevtoolsHttpHandlerTrafficAnnotation);
453 return;
454 }
455
456 std::string filename = PathWithoutParams(info.path.substr(10));
457 std::string mime_type = GetMimeType(filename);
458
459 if (!debug_frontend_dir_.empty()) {
460 base::FilePath path = debug_frontend_dir_.AppendASCII(filename);
461 std::string data;
462 base::ReadFileToString(path, &data);
463 server_->Send200(connection_id, data, mime_type,
464 kDevtoolsHttpHandlerTrafficAnnotation);
465 return;
466 }
467
468 if (bundles_resources_) {
469 GetUIThreadTaskRunner({})->PostTask(
470 FROM_HERE,
471 base::BindOnce(&DevToolsHttpHandler::OnFrontendResourceRequest,
472 handler_, connection_id, filename));
473 return;
474 }
475 server_->Send404(connection_id, kDevtoolsHttpHandlerTrafficAnnotation);
476 }
477
OnWebSocketRequest(int connection_id,const net::HttpServerRequestInfo & request)478 void ServerWrapper::OnWebSocketRequest(
479 int connection_id,
480 const net::HttpServerRequestInfo& request) {
481 GetUIThreadTaskRunner({})->PostTask(
482 FROM_HERE, base::BindOnce(&DevToolsHttpHandler::OnWebSocketRequest,
483 handler_, connection_id, request));
484 }
485
OnWebSocketMessage(int connection_id,std::string data)486 void ServerWrapper::OnWebSocketMessage(int connection_id, std::string data) {
487 GetUIThreadTaskRunner({})->PostTask(
488 FROM_HERE, base::BindOnce(&DevToolsHttpHandler::OnWebSocketMessage,
489 handler_, connection_id, std::move(data)));
490 }
491
OnClose(int connection_id)492 void ServerWrapper::OnClose(int connection_id) {
493 GetUIThreadTaskRunner({})->PostTask(
494 FROM_HERE,
495 base::BindOnce(&DevToolsHttpHandler::OnClose, handler_, connection_id));
496 }
497
GetFrontendURLInternal(scoped_refptr<DevToolsAgentHost> agent_host,const std::string & id,const std::string & host)498 std::string DevToolsHttpHandler::GetFrontendURLInternal(
499 scoped_refptr<DevToolsAgentHost> agent_host,
500 const std::string& id,
501 const std::string& host) {
502 std::string frontend_url;
503 if (delegate_->HasBundledFrontendResources()) {
504 frontend_url = "/devtools/inspector.html";
505 } else {
506 std::string type = agent_host->GetType();
507 bool is_worker = type == DevToolsAgentHost::kTypeServiceWorker ||
508 type == DevToolsAgentHost::kTypeSharedWorker;
509 frontend_url = base::StringPrintf(
510 "https://chrome-devtools-frontend.appspot.com/serve_rev/%s/%s.html",
511 GetWebKitRevision().c_str(), is_worker ? "worker_app" : "inspector");
512 }
513 return base::StringPrintf("%s?ws=%s%s%s", frontend_url.c_str(), host.c_str(),
514 kPageUrlPrefix, id.c_str());
515 }
516
ParseJsonPath(const std::string & path,std::string * command,std::string * target_id)517 static bool ParseJsonPath(
518 const std::string& path,
519 std::string* command,
520 std::string* target_id) {
521
522 // Fall back to list in case of empty query.
523 if (path.empty()) {
524 *command = "list";
525 return true;
526 }
527
528 if (!base::StartsWith(path, "/", base::CompareCase::SENSITIVE)) {
529 // Malformed command.
530 return false;
531 }
532 *command = path.substr(1);
533
534 size_t separator_pos = command->find("/");
535 if (separator_pos != std::string::npos) {
536 *target_id = command->substr(separator_pos + 1);
537 *command = command->substr(0, separator_pos);
538 }
539 return true;
540 }
541
OnJsonRequest(int connection_id,const net::HttpServerRequestInfo & info)542 void DevToolsHttpHandler::OnJsonRequest(
543 int connection_id,
544 const net::HttpServerRequestInfo& info) {
545 // Trim /json
546 std::string path = info.path.substr(5);
547
548 // Trim fragment and query
549 std::string query;
550 size_t query_pos = path.find('?');
551 if (query_pos != std::string::npos) {
552 query = path.substr(query_pos + 1);
553 path = path.substr(0, query_pos);
554 }
555
556 size_t fragment_pos = path.find('#');
557 if (fragment_pos != std::string::npos)
558 path = path.substr(0, fragment_pos);
559
560 std::string command;
561 std::string target_id;
562 if (!ParseJsonPath(path, &command, &target_id)) {
563 SendJson(connection_id, net::HTTP_NOT_FOUND, nullptr,
564 "Malformed query: " + info.path);
565 return;
566 }
567
568 if (command == "version") {
569 base::DictionaryValue version;
570 version.SetString("Protocol-Version",
571 DevToolsAgentHost::GetProtocolVersion());
572 version.SetString("WebKit-Version", GetWebKitVersion());
573 version.SetString("Browser", GetContentClient()->browser()->GetProduct());
574 version.SetString("User-Agent",
575 GetContentClient()->browser()->GetUserAgent());
576 version.SetString("V8-Version", V8_VERSION_STRING);
577 std::string host = info.GetHeaderValue("host");
578 version.SetString(
579 kTargetWebSocketDebuggerUrlField,
580 base::StringPrintf("ws://%s%s", host.c_str(), browser_guid_.c_str()));
581 #if defined(OS_ANDROID)
582 version.SetString(
583 "Android-Package",
584 base::android::BuildInfo::GetInstance()->host_package_name());
585 #endif
586 SendJson(connection_id, net::HTTP_OK, &version, std::string());
587 return;
588 }
589
590 if (command == "protocol") {
591 DecompressAndSendJsonProtocol(connection_id);
592 return;
593 }
594
595 if (command == "list") {
596 DevToolsManager* manager = DevToolsManager::GetInstance();
597 DevToolsAgentHost::List list =
598 manager->delegate() ? manager->delegate()->RemoteDebuggingTargets()
599 : DevToolsAgentHost::GetOrCreateAll();
600 RespondToJsonList(connection_id, info.GetHeaderValue("host"),
601 std::move(list));
602 return;
603 }
604
605 if (command == "new") {
606 GURL url(net::UnescapeBinaryURLComponent(query));
607 if (!url.is_valid())
608 url = GURL(url::kAboutBlankURL);
609 scoped_refptr<DevToolsAgentHost> agent_host = nullptr;
610 agent_host = delegate_->CreateNewTarget(url);
611 if (!agent_host) {
612 SendJson(connection_id, net::HTTP_INTERNAL_SERVER_ERROR, nullptr,
613 "Could not create new page");
614 return;
615 }
616 std::string host = info.GetHeaderValue("host");
617 std::unique_ptr<base::DictionaryValue> dictionary(
618 SerializeDescriptor(agent_host, host));
619 SendJson(connection_id, net::HTTP_OK, dictionary.get(), std::string());
620 return;
621 }
622
623 if (command == "activate" || command == "close") {
624 scoped_refptr<DevToolsAgentHost> agent_host =
625 DevToolsAgentHost::GetForId(target_id);
626 if (!agent_host) {
627 SendJson(connection_id, net::HTTP_NOT_FOUND, nullptr,
628 "No such target id: " + target_id);
629 return;
630 }
631
632 if (command == "activate") {
633 if (agent_host->Activate()) {
634 SendJson(connection_id, net::HTTP_OK, nullptr, "Target activated");
635 } else {
636 SendJson(connection_id, net::HTTP_INTERNAL_SERVER_ERROR, nullptr,
637 "Could not activate target id: " + target_id);
638 }
639 return;
640 }
641
642 if (command == "close") {
643 if (agent_host->Close()) {
644 SendJson(connection_id, net::HTTP_OK, nullptr, "Target is closing");
645 } else {
646 SendJson(connection_id, net::HTTP_INTERNAL_SERVER_ERROR, nullptr,
647 "Could not close target id: " + target_id);
648 }
649 return;
650 }
651 }
652 SendJson(connection_id, net::HTTP_NOT_FOUND, nullptr,
653 "Unknown command: " + command);
654 }
655
DecompressAndSendJsonProtocol(int connection_id)656 void DevToolsHttpHandler::DecompressAndSendJsonProtocol(int connection_id) {
657 #if defined(OS_ANDROID) || defined(OS_FUCHSIA)
658 NOTREACHED();
659 #else
660 scoped_refptr<base::RefCountedMemory> bytes =
661 GetContentClient()->GetDataResourceBytes(COMPRESSED_PROTOCOL_JSON);
662 std::string json_protocol(reinterpret_cast<const char*>(bytes->front()),
663 bytes->size());
664
665 net::HttpServerResponseInfo response(net::HTTP_OK);
666 response.SetBody(json_protocol, "application/json; charset=UTF-8");
667
668 thread_->task_runner()->PostTask(
669 FROM_HERE, base::BindOnce(&ServerWrapper::SendResponse,
670 base::Unretained(server_wrapper_.get()),
671 connection_id, response));
672 #endif // defined(OS_ANDROID) || defined(OS_FUCHSIA)
673 }
674
RespondToJsonList(int connection_id,const std::string & host,DevToolsAgentHost::List hosts)675 void DevToolsHttpHandler::RespondToJsonList(
676 int connection_id,
677 const std::string& host,
678 DevToolsAgentHost::List hosts) {
679 DevToolsAgentHost::List agent_hosts = std::move(hosts);
680 std::sort(agent_hosts.begin(), agent_hosts.end(), TimeComparator);
681 base::ListValue list_value;
682 for (auto& agent_host : agent_hosts)
683 list_value.Append(SerializeDescriptor(agent_host, host));
684 SendJson(connection_id, net::HTTP_OK, &list_value, std::string());
685 }
686
OnDiscoveryPageRequest(int connection_id)687 void DevToolsHttpHandler::OnDiscoveryPageRequest(int connection_id) {
688 std::string response = delegate_->GetDiscoveryPageHTML();
689 Send200(connection_id, response, "text/html; charset=UTF-8");
690 }
691
OnFrontendResourceRequest(int connection_id,const std::string & path)692 void DevToolsHttpHandler::OnFrontendResourceRequest(
693 int connection_id, const std::string& path) {
694 #if defined(OS_ANDROID)
695 Send404(connection_id);
696 #else
697 Send200(connection_id,
698 content::DevToolsFrontendHost::GetFrontendResource(path),
699 GetMimeType(path));
700 #endif
701 }
702
OnWebSocketRequest(int connection_id,const net::HttpServerRequestInfo & request)703 void DevToolsHttpHandler::OnWebSocketRequest(
704 int connection_id,
705 const net::HttpServerRequestInfo& request) {
706 if (!thread_)
707 return;
708
709 if (base::StartsWith(request.path, browser_guid_,
710 base::CompareCase::SENSITIVE)) {
711 scoped_refptr<DevToolsAgentHost> browser_agent =
712 DevToolsAgentHost::CreateForBrowser(
713 thread_->task_runner(),
714 base::BindRepeating(&DevToolsSocketFactory::CreateForTethering,
715 base::Unretained(socket_factory_.get())));
716 connection_to_client_[connection_id].reset(new DevToolsAgentHostClientImpl(
717 thread_->task_runner(), server_wrapper_.get(), connection_id,
718 browser_agent));
719 AcceptWebSocket(connection_id, request);
720 return;
721 }
722
723 if (!base::StartsWith(request.path, kPageUrlPrefix,
724 base::CompareCase::SENSITIVE)) {
725 Send404(connection_id);
726 return;
727 }
728
729 std::string target_id = request.path.substr(strlen(kPageUrlPrefix));
730 scoped_refptr<DevToolsAgentHost> agent =
731 DevToolsAgentHost::GetForId(target_id);
732 if (!agent) {
733 Send500(connection_id, "No such target id: " + target_id);
734 return;
735 }
736
737 connection_to_client_[connection_id].reset(new DevToolsAgentHostClientImpl(
738 thread_->task_runner(), server_wrapper_.get(), connection_id, agent));
739
740 AcceptWebSocket(connection_id, request);
741 }
742
OnWebSocketMessage(int connection_id,std::string data)743 void DevToolsHttpHandler::OnWebSocketMessage(int connection_id,
744 std::string data) {
745 auto it = connection_to_client_.find(connection_id);
746 if (it != connection_to_client_.end()) {
747 it->second->OnMessage(base::as_bytes(base::make_span(data)));
748 }
749 }
750
OnClose(int connection_id)751 void DevToolsHttpHandler::OnClose(int connection_id) {
752 connection_to_client_.erase(connection_id);
753 }
754
DevToolsHttpHandler(DevToolsManagerDelegate * delegate,std::unique_ptr<DevToolsSocketFactory> socket_factory,const base::FilePath & output_directory,const base::FilePath & debug_frontend_dir)755 DevToolsHttpHandler::DevToolsHttpHandler(
756 DevToolsManagerDelegate* delegate,
757 std::unique_ptr<DevToolsSocketFactory> socket_factory,
758 const base::FilePath& output_directory,
759 const base::FilePath& debug_frontend_dir)
760 : delegate_(delegate) {
761 browser_guid_ = delegate_->IsBrowserTargetDiscoverable()
762 ? kBrowserUrlPrefix
763 : base::StringPrintf("%s/%s", kBrowserUrlPrefix,
764 base::GenerateGUID().c_str());
765 std::unique_ptr<base::Thread> thread(
766 new base::Thread(kDevToolsHandlerThreadName));
767 base::Thread::Options options;
768 options.message_pump_type = base::MessagePumpType::IO;
769 if (thread->StartWithOptions(options)) {
770 auto task_runner = thread->task_runner();
771 task_runner->PostTask(
772 FROM_HERE,
773 base::BindOnce(&StartServerOnHandlerThread, weak_factory_.GetWeakPtr(),
774 std::move(thread), std::move(socket_factory),
775 output_directory, debug_frontend_dir, browser_guid_,
776 delegate_->HasBundledFrontendResources()));
777 }
778 }
779
ServerStarted(std::unique_ptr<base::Thread> thread,std::unique_ptr<ServerWrapper> server_wrapper,std::unique_ptr<DevToolsSocketFactory> socket_factory,std::unique_ptr<net::IPEndPoint> ip_address)780 void DevToolsHttpHandler::ServerStarted(
781 std::unique_ptr<base::Thread> thread,
782 std::unique_ptr<ServerWrapper> server_wrapper,
783 std::unique_ptr<DevToolsSocketFactory> socket_factory,
784 std::unique_ptr<net::IPEndPoint> ip_address) {
785 thread_ = std::move(thread);
786 server_wrapper_ = std::move(server_wrapper);
787 socket_factory_ = std::move(socket_factory);
788 server_ip_address_ = std::move(ip_address);
789 }
790
SendJson(int connection_id,net::HttpStatusCode status_code,base::Value * value,const std::string & message)791 void DevToolsHttpHandler::SendJson(int connection_id,
792 net::HttpStatusCode status_code,
793 base::Value* value,
794 const std::string& message) {
795 if (!thread_)
796 return;
797
798 // Serialize value and message.
799 std::string json_value;
800 if (value) {
801 base::JSONWriter::WriteWithOptions(
802 *value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_value);
803 }
804 std::string json_message;
805 base::JSONWriter::Write(base::Value(message), &json_message);
806
807 net::HttpServerResponseInfo response(status_code);
808 response.SetBody(json_value + message, "application/json; charset=UTF-8");
809
810 thread_->task_runner()->PostTask(
811 FROM_HERE, base::BindOnce(&ServerWrapper::SendResponse,
812 base::Unretained(server_wrapper_.get()),
813 connection_id, response));
814 }
815
Send200(int connection_id,const std::string & data,const std::string & mime_type)816 void DevToolsHttpHandler::Send200(int connection_id,
817 const std::string& data,
818 const std::string& mime_type) {
819 if (!thread_)
820 return;
821 thread_->task_runner()->PostTask(
822 FROM_HERE, base::BindOnce(&ServerWrapper::Send200,
823 base::Unretained(server_wrapper_.get()),
824 connection_id, data, mime_type));
825 }
826
Send404(int connection_id)827 void DevToolsHttpHandler::Send404(int connection_id) {
828 if (!thread_)
829 return;
830 thread_->task_runner()->PostTask(
831 FROM_HERE,
832 base::BindOnce(&ServerWrapper::Send404,
833 base::Unretained(server_wrapper_.get()), connection_id));
834 }
835
Send500(int connection_id,const std::string & message)836 void DevToolsHttpHandler::Send500(int connection_id,
837 const std::string& message) {
838 if (!thread_)
839 return;
840 thread_->task_runner()->PostTask(
841 FROM_HERE, base::BindOnce(&ServerWrapper::Send500,
842 base::Unretained(server_wrapper_.get()),
843 connection_id, message));
844 }
845
AcceptWebSocket(int connection_id,const net::HttpServerRequestInfo & request)846 void DevToolsHttpHandler::AcceptWebSocket(
847 int connection_id,
848 const net::HttpServerRequestInfo& request) {
849 if (!thread_)
850 return;
851 thread_->task_runner()->PostTask(
852 FROM_HERE, base::BindOnce(&ServerWrapper::AcceptWebSocket,
853 base::Unretained(server_wrapper_.get()),
854 connection_id, request));
855 }
856
SerializeDescriptor(scoped_refptr<DevToolsAgentHost> agent_host,const std::string & host)857 std::unique_ptr<base::DictionaryValue> DevToolsHttpHandler::SerializeDescriptor(
858 scoped_refptr<DevToolsAgentHost> agent_host,
859 const std::string& host) {
860 std::unique_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue);
861 std::string id = agent_host->GetId();
862 dictionary->SetString(kTargetIdField, id);
863 std::string parent_id = agent_host->GetParentId();
864 if (!parent_id.empty())
865 dictionary->SetString(kTargetParentIdField, parent_id);
866 dictionary->SetString(kTargetTypeField, agent_host->GetType());
867 dictionary->SetString(kTargetTitleField,
868 net::EscapeForHTML(agent_host->GetTitle()));
869 dictionary->SetString(kTargetDescriptionField, agent_host->GetDescription());
870
871 GURL url = agent_host->GetURL();
872 dictionary->SetString(kTargetUrlField, url.spec());
873
874 GURL favicon_url = agent_host->GetFaviconURL();
875 if (favicon_url.is_valid())
876 dictionary->SetString(kTargetFaviconUrlField, favicon_url.spec());
877
878 dictionary->SetString(kTargetWebSocketDebuggerUrlField,
879 base::StringPrintf("ws://%s%s%s", host.c_str(),
880 kPageUrlPrefix, id.c_str()));
881 std::string devtools_frontend_url =
882 GetFrontendURLInternal(agent_host, id, host);
883 dictionary->SetString(kTargetDevtoolsFrontendUrlField, devtools_frontend_url);
884
885 return dictionary;
886 }
887
888 } // namespace content
889