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 "chrome/browser/debugger/devtools_http_protocol_handler.h"
6
7#include <utility>
8
9#include "base/compiler_specific.h"
10#include "base/json/json_writer.h"
11#include "base/logging.h"
12#include "base/message_loop_proxy.h"
13#include "base/string_number_conversions.h"
14#include "base/threading/thread.h"
15#include "base/utf_string_conversions.h"
16#include "base/values.h"
17#include "chrome/browser/debugger/devtools_client_host.h"
18#include "chrome/browser/debugger/devtools_manager.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
21#include "chrome/browser/ui/webui/devtools_ui.h"
22#include "chrome/common/devtools_messages.h"
23#include "content/browser/browser_thread.h"
24#include "content/browser/tab_contents/tab_contents.h"
25#include "googleurl/src/gurl.h"
26#include "net/base/io_buffer.h"
27#include "net/server/http_server_request_info.h"
28#include "net/url_request/url_request_context.h"
29#include "net/url_request/url_request_context_getter.h"
30
31const int kBufferSize = 16 * 1024;
32
33namespace {
34
35// An internal implementation of DevToolsClientHost that delegates
36// messages sent for DevToolsClient to a DebuggerShell instance.
37class DevToolsClientHostImpl : public DevToolsClientHost {
38 public:
39  DevToolsClientHostImpl(
40      net::HttpServer* server,
41      int connection_id)
42      : server_(server),
43        connection_id_(connection_id) {
44  }
45  ~DevToolsClientHostImpl() {}
46
47  // DevToolsClientHost interface
48  virtual void InspectedTabClosing() {
49    BrowserThread::PostTask(
50        BrowserThread::IO,
51        FROM_HERE,
52        NewRunnableMethod(server_,
53                          &net::HttpServer::Close,
54                          connection_id_));
55  }
56
57  virtual void SendMessageToClient(const IPC::Message& msg) {
58    IPC_BEGIN_MESSAGE_MAP(DevToolsClientHostImpl, msg)
59      IPC_MESSAGE_HANDLER(DevToolsClientMsg_DispatchOnInspectorFrontend,
60                          OnDispatchOnInspectorFrontend);
61      IPC_MESSAGE_UNHANDLED_ERROR()
62    IPC_END_MESSAGE_MAP()
63  }
64
65  virtual void TabReplaced(TabContentsWrapper* new_tab) {
66  }
67
68  void NotifyCloseListener() {
69    DevToolsClientHost::NotifyCloseListener();
70  }
71 private:
72  // Message handling routines
73  void OnDispatchOnInspectorFrontend(const std::string& data) {
74    BrowserThread::PostTask(
75        BrowserThread::IO,
76        FROM_HERE,
77        NewRunnableMethod(server_,
78                          &net::HttpServer::SendOverWebSocket,
79                          connection_id_,
80                          data));
81  }
82
83  virtual void FrameNavigating(const std::string& url) {}
84  net::HttpServer* server_;
85  int connection_id_;
86};
87
88}  // namespace
89
90
91// static
92scoped_refptr<DevToolsHttpProtocolHandler> DevToolsHttpProtocolHandler::Start(
93    const std::string& ip,
94    int port,
95    const std::string& frontend_url,
96    TabContentsProvider* provider) {
97  scoped_refptr<DevToolsHttpProtocolHandler> http_handler =
98      new DevToolsHttpProtocolHandler(ip, port, frontend_url, provider);
99  http_handler->Start();
100  return http_handler;
101}
102
103DevToolsHttpProtocolHandler::~DevToolsHttpProtocolHandler() {
104  // Stop() must be called prior to this being called
105  DCHECK(server_.get() == NULL);
106}
107
108void DevToolsHttpProtocolHandler::Start() {
109  BrowserThread::PostTask(
110      BrowserThread::IO, FROM_HERE,
111      NewRunnableMethod(this, &DevToolsHttpProtocolHandler::Init));
112}
113
114void DevToolsHttpProtocolHandler::Stop() {
115  BrowserThread::PostTask(
116      BrowserThread::IO, FROM_HERE,
117      NewRunnableMethod(this, &DevToolsHttpProtocolHandler::Teardown));
118}
119
120void DevToolsHttpProtocolHandler::OnHttpRequest(
121    int connection_id,
122    const net::HttpServerRequestInfo& info) {
123  if (info.path == "" || info.path == "/") {
124    // Pages discovery request.
125    BrowserThread::PostTask(
126        BrowserThread::UI,
127        FROM_HERE,
128        NewRunnableMethod(this,
129                          &DevToolsHttpProtocolHandler::OnRootRequestUI,
130                          connection_id,
131                          info));
132    return;
133  }
134
135  if (info.path == "/json") {
136    // Pages discovery json request.
137    BrowserThread::PostTask(
138        BrowserThread::UI,
139        FROM_HERE,
140        NewRunnableMethod(this,
141                          &DevToolsHttpProtocolHandler::OnJsonRequestUI,
142                          connection_id,
143                          info));
144    return;
145  }
146
147  size_t pos = info.path.find("/devtools/");
148  if (pos != 0) {
149    server_->Send404(connection_id);
150    return;
151  }
152
153  // Proxy static files from chrome-devtools://devtools/*.
154  if (!Profile::GetDefaultRequestContext()) {
155    server_->Send404(connection_id);
156    return;
157  }
158
159  // Make sure DevTools data source is registered.
160  DevToolsUI::RegisterDevToolsDataSource();
161
162  net::URLRequest* request = new net::URLRequest(
163      GURL("chrome-devtools:/" + info.path), this);
164  Bind(request, connection_id);
165  request->set_context(
166      Profile::GetDefaultRequestContext()->GetURLRequestContext());
167  request->Start();
168}
169
170void DevToolsHttpProtocolHandler::OnWebSocketRequest(
171    int connection_id,
172    const net::HttpServerRequestInfo& request) {
173  BrowserThread::PostTask(
174      BrowserThread::UI,
175      FROM_HERE,
176      NewRunnableMethod(
177          this,
178          &DevToolsHttpProtocolHandler::OnWebSocketRequestUI,
179          connection_id,
180          request));
181}
182
183void DevToolsHttpProtocolHandler::OnWebSocketMessage(
184    int connection_id,
185    const std::string& data) {
186  BrowserThread::PostTask(
187      BrowserThread::UI,
188      FROM_HERE,
189      NewRunnableMethod(
190          this,
191          &DevToolsHttpProtocolHandler::OnWebSocketMessageUI,
192          connection_id,
193          data));
194}
195
196void DevToolsHttpProtocolHandler::OnClose(int connection_id) {
197  ConnectionToRequestsMap::iterator it =
198      connection_to_requests_io_.find(connection_id);
199  if (it != connection_to_requests_io_.end()) {
200    // Dispose delegating socket.
201    for (std::set<net::URLRequest*>::iterator it2 = it->second.begin();
202         it2 != it->second.end(); ++it2) {
203      net::URLRequest* request = *it2;
204      request->Cancel();
205      request_to_connection_io_.erase(request);
206      request_to_buffer_io_.erase(request);
207      delete request;
208    }
209    connection_to_requests_io_.erase(connection_id);
210  }
211
212  BrowserThread::PostTask(
213      BrowserThread::UI,
214      FROM_HERE,
215      NewRunnableMethod(
216          this,
217          &DevToolsHttpProtocolHandler::OnCloseUI,
218          connection_id));
219}
220
221struct PageInfo
222{
223  int id;
224  std::string url;
225  bool attached;
226  std::string title;
227  std::string favicon_url;
228};
229typedef std::vector<PageInfo> PageList;
230
231static PageList GeneratePageList(
232    DevToolsHttpProtocolHandler::TabContentsProvider* tab_contents_provider,
233    int connection_id,
234    const net::HttpServerRequestInfo& info) {
235  typedef DevToolsHttpProtocolHandler::InspectableTabs Tabs;
236  Tabs inspectable_tabs = tab_contents_provider->GetInspectableTabs();
237
238  PageList page_list;
239  for (Tabs::iterator it = inspectable_tabs.begin();
240       it != inspectable_tabs.end(); ++it) {
241
242    TabContentsWrapper* tab_contents = *it;
243    NavigationController& controller = tab_contents->controller();
244
245    NavigationEntry* entry = controller.GetActiveEntry();
246    if (entry == NULL || !entry->url().is_valid())
247      continue;
248
249    DevToolsClientHost* client_host = DevToolsManager::GetInstance()->
250        GetDevToolsClientHostFor(tab_contents->tab_contents()->
251                                      render_view_host());
252    PageInfo page_info;
253    page_info.id = controller.session_id().id();
254    page_info.attached = client_host != NULL;
255    page_info.url = entry->url().spec();
256    page_info.title = UTF16ToUTF8(entry->title());
257    page_info.favicon_url = entry->favicon().url().spec();
258    page_list.push_back(page_info);
259  }
260  return page_list;
261}
262
263void DevToolsHttpProtocolHandler::OnRootRequestUI(
264    int connection_id,
265    const net::HttpServerRequestInfo& info) {
266  std::string host = info.headers["Host"];
267  std::string response = "<html><body>";
268  PageList page_list = GeneratePageList(tab_contents_provider_.get(),
269                                        connection_id, info);
270  for (PageList::iterator i = page_list.begin();
271       i != page_list.end(); ++i) {
272
273    std::string frontendURL = StringPrintf("%s?host=%s&page=%d",
274                                           overriden_frontend_url_.c_str(),
275                                           host.c_str(),
276                                           i->id);
277    response += "<div>";
278    response += StringPrintf(
279        "<img style=\"margin-right:5px;width:16px;height:16px\" src=\"%s\">",
280        i->favicon_url.c_str());
281
282    if (i->attached) {
283      response += i->url.c_str();
284    } else {
285      response += StringPrintf("<a href=\"%s\">%s</a><br>",
286                               frontendURL.c_str(),
287                               i->url.c_str());
288    }
289    response += "</div>";
290  }
291  response += "</body></html>";
292  Send200(connection_id, response, "text/html; charset=UTF-8");
293}
294
295void DevToolsHttpProtocolHandler::OnJsonRequestUI(
296    int connection_id,
297    const net::HttpServerRequestInfo& info) {
298  PageList page_list = GeneratePageList(tab_contents_provider_.get(),
299                                        connection_id, info);
300  ListValue json_pages_list;
301  std::string host = info.headers["Host"];
302  for (PageList::iterator i = page_list.begin();
303       i != page_list.end(); ++i) {
304
305    DictionaryValue* page_info = new DictionaryValue;
306    json_pages_list.Append(page_info);
307    page_info->SetString("title", i->title);
308    page_info->SetString("url", i->url);
309    page_info->SetString("faviconUrl", i->favicon_url);
310    if (!i->attached) {
311      page_info->SetString("webSocketDebuggerUrl",
312                           StringPrintf("ws://%s/devtools/page/%d",
313                                        host.c_str(),
314                                        i->id));
315      page_info->SetString(
316          "devtoolsFrontendUrl",
317          StringPrintf("http://%s/devtools/devtools.html?page=%d",
318                       host.c_str(),
319                       i->id));
320    }
321  }
322
323  std::string response;
324  base::JSONWriter::Write(&json_pages_list, true, &response);
325  Send200(connection_id, response, "application/json; charset=UTF-8");
326}
327
328void DevToolsHttpProtocolHandler::OnWebSocketRequestUI(
329    int connection_id,
330    const net::HttpServerRequestInfo& request) {
331  std::string prefix = "/devtools/page/";
332  size_t pos = request.path.find(prefix);
333  if (pos != 0) {
334    Send404(connection_id);
335    return;
336  }
337  std::string page_id = request.path.substr(prefix.length());
338  int id = 0;
339  if (!base::StringToInt(page_id, &id)) {
340    Send500(connection_id, "Invalid page id: " + page_id);
341    return;
342  }
343
344  TabContents* tab_contents = GetTabContents(id);
345  if (tab_contents == NULL) {
346    Send500(connection_id, "No such page id: " + page_id);
347    return;
348  }
349
350  DevToolsManager* manager = DevToolsManager::GetInstance();
351  if (manager->GetDevToolsClientHostFor(tab_contents->render_view_host())) {
352    Send500(connection_id, "Page with given id is being inspected: " + page_id);
353    return;
354  }
355
356  DevToolsClientHostImpl* client_host =
357      new DevToolsClientHostImpl(server_, connection_id);
358  connection_to_client_host_ui_[connection_id] = client_host;
359
360  manager->RegisterDevToolsClientHostFor(
361      tab_contents->render_view_host(),
362      client_host);
363  manager->ForwardToDevToolsAgent(
364      client_host,
365      DevToolsAgentMsg_FrontendLoaded());
366
367  AcceptWebSocket(connection_id, request);
368}
369
370void DevToolsHttpProtocolHandler::OnWebSocketMessageUI(
371    int connection_id,
372    const std::string& data) {
373  ConnectionToClientHostMap::iterator it =
374      connection_to_client_host_ui_.find(connection_id);
375  if (it == connection_to_client_host_ui_.end())
376    return;
377
378  DevToolsManager* manager = DevToolsManager::GetInstance();
379  manager->ForwardToDevToolsAgent(
380      it->second,
381      DevToolsAgentMsg_DispatchOnInspectorBackend(data));
382}
383
384void DevToolsHttpProtocolHandler::OnCloseUI(int connection_id) {
385  ConnectionToClientHostMap::iterator it =
386      connection_to_client_host_ui_.find(connection_id);
387  if (it != connection_to_client_host_ui_.end()) {
388    DevToolsClientHostImpl* client_host =
389        static_cast<DevToolsClientHostImpl*>(it->second);
390    client_host->NotifyCloseListener();
391    delete client_host;
392    connection_to_client_host_ui_.erase(connection_id);
393  }
394}
395
396void DevToolsHttpProtocolHandler::OnResponseStarted(net::URLRequest* request) {
397  RequestToSocketMap::iterator it = request_to_connection_io_.find(request);
398  if (it == request_to_connection_io_.end())
399    return;
400
401  int connection_id = it->second;
402
403  std::string content_type;
404  request->GetMimeType(&content_type);
405
406  if (request->status().is_success()) {
407    server_->Send(connection_id, StringPrintf("HTTP/1.1 200 OK\r\n"
408                                              "Content-Type:%s\r\n"
409                                              "Transfer-Encoding: chunked\r\n"
410                                              "\r\n",
411                                              content_type.c_str()));
412  } else {
413    server_->Send404(connection_id);
414  }
415
416  int bytes_read = 0;
417  // Some servers may treat HEAD requests as GET requests.  To free up the
418  // network connection as soon as possible, signal that the request has
419  // completed immediately, without trying to read any data back (all we care
420  // about is the response code and headers, which we already have).
421  net::IOBuffer* buffer = request_to_buffer_io_[request].get();
422  if (request->status().is_success())
423    request->Read(buffer, kBufferSize, &bytes_read);
424  OnReadCompleted(request, bytes_read);
425}
426
427void DevToolsHttpProtocolHandler::OnReadCompleted(net::URLRequest* request,
428                                                  int bytes_read) {
429  RequestToSocketMap::iterator it = request_to_connection_io_.find(request);
430  if (it == request_to_connection_io_.end())
431    return;
432
433  int connection_id = it->second;
434
435  net::IOBuffer* buffer = request_to_buffer_io_[request].get();
436  do {
437    if (!request->status().is_success() || bytes_read <= 0)
438      break;
439    std::string chunk_size = StringPrintf("%X\r\n", bytes_read);
440    server_->Send(connection_id, chunk_size);
441    server_->Send(connection_id, buffer->data(), bytes_read);
442    server_->Send(connection_id, "\r\n");
443  } while (request->Read(buffer, kBufferSize, &bytes_read));
444
445
446  // See comments re: HEAD requests in OnResponseStarted().
447  if (!request->status().is_io_pending()) {
448    server_->Send(connection_id, "0\r\n\r\n");
449    RequestCompleted(request);
450  }
451}
452
453DevToolsHttpProtocolHandler::DevToolsHttpProtocolHandler(
454    const std::string& ip,
455    int port,
456    const std::string& frontend_host,
457    TabContentsProvider* provider)
458    : ip_(ip),
459      port_(port),
460      overriden_frontend_url_(frontend_host),
461      tab_contents_provider_(provider) {
462  if (overriden_frontend_url_.empty())
463      overriden_frontend_url_ = "/devtools/devtools.html";
464}
465
466void DevToolsHttpProtocolHandler::Init() {
467  server_ = new net::HttpServer(ip_, port_, this);
468}
469
470// Run on I/O thread
471void DevToolsHttpProtocolHandler::Teardown() {
472  server_ = NULL;
473}
474
475void DevToolsHttpProtocolHandler::Bind(net::URLRequest* request,
476                                       int connection_id) {
477  request_to_connection_io_[request] = connection_id;
478  ConnectionToRequestsMap::iterator it =
479      connection_to_requests_io_.find(connection_id);
480  if (it == connection_to_requests_io_.end()) {
481    std::pair<int, std::set<net::URLRequest*> > value(
482        connection_id,
483        std::set<net::URLRequest*>());
484    it = connection_to_requests_io_.insert(value).first;
485  }
486  it->second.insert(request);
487  request_to_buffer_io_[request] = new net::IOBuffer(kBufferSize);
488}
489
490void DevToolsHttpProtocolHandler::RequestCompleted(net::URLRequest* request) {
491  RequestToSocketMap::iterator it = request_to_connection_io_.find(request);
492  if (it == request_to_connection_io_.end())
493    return;
494
495  int connection_id = it->second;
496  request_to_connection_io_.erase(request);
497  ConnectionToRequestsMap::iterator it2 =
498      connection_to_requests_io_.find(connection_id);
499  it2->second.erase(request);
500  request_to_buffer_io_.erase(request);
501  delete request;
502}
503
504void DevToolsHttpProtocolHandler::Send200(int connection_id,
505                                          const std::string& data,
506                                          const std::string& mime_type) {
507  BrowserThread::PostTask(
508      BrowserThread::IO, FROM_HERE,
509      NewRunnableMethod(server_.get(),
510                        &net::HttpServer::Send200,
511                        connection_id,
512                        data,
513                        mime_type));
514}
515
516void DevToolsHttpProtocolHandler::Send404(int connection_id) {
517  BrowserThread::PostTask(
518      BrowserThread::IO, FROM_HERE,
519      NewRunnableMethod(server_.get(),
520                        &net::HttpServer::Send404,
521                        connection_id));
522}
523
524void DevToolsHttpProtocolHandler::Send500(int connection_id,
525                                          const std::string& message) {
526  BrowserThread::PostTask(
527      BrowserThread::IO, FROM_HERE,
528      NewRunnableMethod(server_.get(),
529                        &net::HttpServer::Send500,
530                        connection_id,
531                        message));
532}
533
534void DevToolsHttpProtocolHandler::AcceptWebSocket(
535    int connection_id,
536    const net::HttpServerRequestInfo& request) {
537  BrowserThread::PostTask(
538      BrowserThread::IO, FROM_HERE,
539      NewRunnableMethod(server_.get(),
540                        &net::HttpServer::AcceptWebSocket,
541                        connection_id,
542                        request));
543}
544
545TabContents* DevToolsHttpProtocolHandler::GetTabContents(int session_id) {
546  InspectableTabs inspectable_tabs =
547      tab_contents_provider_->GetInspectableTabs();
548
549  for (InspectableTabs::iterator it = inspectable_tabs.begin();
550       it != inspectable_tabs.end(); ++it) {
551    TabContentsWrapper* tab_contents = *it;
552    NavigationController& controller =
553        tab_contents->controller();
554    if (controller.session_id().id() == session_id)
555      return controller.tab_contents();
556  }
557  return NULL;
558}
559