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 "content/browser/devtools/devtools_http_handler_impl.h"
6
7#include <algorithm>
8#include <utility>
9
10#include "base/bind.h"
11#include "base/compiler_specific.h"
12#include "base/files/file_util.h"
13#include "base/json/json_writer.h"
14#include "base/logging.h"
15#include "base/message_loop/message_loop_proxy.h"
16#include "base/stl_util.h"
17#include "base/strings/string_number_conversions.h"
18#include "base/threading/thread.h"
19#include "base/values.h"
20#include "content/browser/devtools/devtools_browser_target.h"
21#include "content/browser/devtools/devtools_manager.h"
22#include "content/browser/devtools/devtools_protocol.h"
23#include "content/browser/devtools/devtools_protocol_constants.h"
24#include "content/browser/devtools/devtools_system_info_handler.h"
25#include "content/browser/devtools/devtools_tracing_handler.h"
26#include "content/browser/devtools/tethering_handler.h"
27#include "content/common/devtools_messages.h"
28#include "content/public/browser/browser_thread.h"
29#include "content/public/browser/devtools_agent_host.h"
30#include "content/public/browser/devtools_http_handler_delegate.h"
31#include "content/public/browser/devtools_target.h"
32#include "content/public/common/content_client.h"
33#include "content/public/common/url_constants.h"
34#include "content/public/common/user_agent.h"
35#include "grit/devtools_resources_map.h"
36#include "net/base/escape.h"
37#include "net/base/io_buffer.h"
38#include "net/base/ip_endpoint.h"
39#include "net/base/net_errors.h"
40#include "net/server/http_server_request_info.h"
41#include "net/server/http_server_response_info.h"
42#include "net/socket/server_socket.h"
43
44#if defined(OS_ANDROID)
45#include "base/android/build_info.h"
46#endif
47
48namespace content {
49
50namespace {
51
52const base::FilePath::CharType kDevToolsActivePortFileName[] =
53    FILE_PATH_LITERAL("DevToolsActivePort");
54
55const char kDevToolsHandlerThreadName[] = "Chrome_DevToolsHandlerThread";
56
57const char kThumbUrlPrefix[] = "/thumb/";
58const char kPageUrlPrefix[] = "/devtools/page/";
59
60const char kTargetIdField[] = "id";
61const char kTargetParentIdField[] = "parentId";
62const char kTargetTypeField[] = "type";
63const char kTargetTitleField[] = "title";
64const char kTargetDescriptionField[] = "description";
65const char kTargetUrlField[] = "url";
66const char kTargetThumbnailUrlField[] = "thumbnailUrl";
67const char kTargetFaviconUrlField[] = "faviconUrl";
68const char kTargetWebSocketDebuggerUrlField[] = "webSocketDebuggerUrl";
69const char kTargetDevtoolsFrontendUrlField[] = "devtoolsFrontendUrl";
70
71// Maximum write buffer size of devtools http/websocket connectinos.
72const int32 kSendBufferSizeForDevTools = 100 * 1024 * 1024;  // 100Mb
73
74// An internal implementation of DevToolsAgentHostClient that delegates
75// messages sent to a DebuggerShell instance.
76class DevToolsAgentHostClientImpl : public DevToolsAgentHostClient {
77 public:
78  DevToolsAgentHostClientImpl(base::MessageLoop* message_loop,
79                              net::HttpServer* server,
80                              int connection_id,
81                              DevToolsAgentHost* agent_host)
82      : message_loop_(message_loop),
83        server_(server),
84        connection_id_(connection_id),
85        agent_host_(agent_host) {
86    agent_host_->AttachClient(this);
87  }
88
89  virtual ~DevToolsAgentHostClientImpl() {
90    if (agent_host_.get())
91      agent_host_->DetachClient();
92  }
93
94  virtual void AgentHostClosed(
95      DevToolsAgentHost* agent_host,
96      bool replaced_with_another_client) OVERRIDE {
97    DCHECK(agent_host == agent_host_.get());
98    agent_host_ = NULL;
99
100    base::DictionaryValue notification;
101    notification.SetString(
102        devtools::Inspector::detached::kParamReason,
103        replaced_with_another_client ?
104            "replaced_with_devtools" : "target_closed");
105    std::string response = DevToolsProtocol::CreateNotification(
106        devtools::Inspector::detached::kName,
107        notification.DeepCopy())->Serialize();
108    message_loop_->PostTask(
109        FROM_HERE,
110        base::Bind(&net::HttpServer::SendOverWebSocket,
111                   base::Unretained(server_),
112                   connection_id_,
113                   response));
114
115    message_loop_->PostTask(
116        FROM_HERE,
117        base::Bind(&net::HttpServer::Close,
118                   base::Unretained(server_),
119                   connection_id_));
120  }
121
122  virtual void DispatchProtocolMessage(
123      DevToolsAgentHost* agent_host, const std::string& message) OVERRIDE {
124    DCHECK(agent_host == agent_host_.get());
125    message_loop_->PostTask(
126        FROM_HERE,
127        base::Bind(&net::HttpServer::SendOverWebSocket,
128                   base::Unretained(server_),
129                   connection_id_,
130                   message));
131  }
132
133  void OnMessage(const std::string& message) {
134    if (agent_host_.get())
135      agent_host_->DispatchProtocolMessage(message);
136  }
137
138 private:
139  base::MessageLoop* const message_loop_;
140  net::HttpServer* const server_;
141  const int connection_id_;
142  scoped_refptr<DevToolsAgentHost> agent_host_;
143};
144
145static bool TimeComparator(const DevToolsTarget* target1,
146                           const DevToolsTarget* target2) {
147  return target1->GetLastActivityTime() > target2->GetLastActivityTime();
148}
149
150}  // namespace
151
152// static
153bool DevToolsHttpHandler::IsSupportedProtocolVersion(
154    const std::string& version) {
155  return devtools::IsSupportedProtocolVersion(version);
156}
157
158// static
159int DevToolsHttpHandler::GetFrontendResourceId(const std::string& name) {
160  for (size_t i = 0; i < kDevtoolsResourcesSize; ++i) {
161    if (name == kDevtoolsResources[i].name)
162      return kDevtoolsResources[i].value;
163  }
164  return -1;
165}
166
167// static
168DevToolsHttpHandler* DevToolsHttpHandler::Start(
169    scoped_ptr<ServerSocketFactory> server_socket_factory,
170    const std::string& frontend_url,
171    DevToolsHttpHandlerDelegate* delegate,
172    const base::FilePath& active_port_output_directory) {
173  DevToolsHttpHandlerImpl* http_handler =
174      new DevToolsHttpHandlerImpl(server_socket_factory.Pass(),
175                                  frontend_url,
176                                  delegate,
177                                  active_port_output_directory);
178  http_handler->Start();
179  return http_handler;
180}
181
182DevToolsHttpHandler::ServerSocketFactory::ServerSocketFactory(
183    const std::string& address,
184    int port,
185    int backlog)
186    : address_(address),
187      port_(port),
188      backlog_(backlog) {
189}
190
191DevToolsHttpHandler::ServerSocketFactory::~ServerSocketFactory() {
192}
193
194scoped_ptr<net::ServerSocket>
195DevToolsHttpHandler::ServerSocketFactory::CreateAndListen() const {
196  scoped_ptr<net::ServerSocket> socket = Create();
197  if (socket &&
198      socket->ListenWithAddressAndPort(address_, port_, backlog_) == net::OK) {
199    return socket.Pass();
200  }
201  return scoped_ptr<net::ServerSocket>();
202}
203
204DevToolsHttpHandlerImpl::~DevToolsHttpHandlerImpl() {
205  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
206  // Stop() must be called prior to destruction.
207  DCHECK(server_.get() == NULL);
208  DCHECK(thread_.get() == NULL);
209  STLDeleteValues(&target_map_);
210}
211
212void DevToolsHttpHandlerImpl::Start() {
213  if (thread_)
214    return;
215  thread_.reset(new base::Thread(kDevToolsHandlerThreadName));
216  BrowserThread::PostTask(
217      BrowserThread::FILE, FROM_HERE,
218      base::Bind(&DevToolsHttpHandlerImpl::StartHandlerThread, this));
219}
220
221// Runs on FILE thread.
222void DevToolsHttpHandlerImpl::StartHandlerThread() {
223  base::Thread::Options options;
224  options.message_loop_type = base::MessageLoop::TYPE_IO;
225  if (!thread_->StartWithOptions(options)) {
226    BrowserThread::PostTask(
227        BrowserThread::UI, FROM_HERE,
228        base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThread, this));
229    return;
230  }
231
232  thread_->message_loop()->PostTask(
233      FROM_HERE,
234      base::Bind(&DevToolsHttpHandlerImpl::Init, this));
235}
236
237void DevToolsHttpHandlerImpl::ResetHandlerThread() {
238  thread_.reset();
239}
240
241void DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease() {
242  ResetHandlerThread();
243  Release();
244}
245
246void DevToolsHttpHandlerImpl::Stop() {
247  if (!thread_)
248    return;
249  BrowserThread::PostTaskAndReply(
250      BrowserThread::FILE, FROM_HERE,
251      base::Bind(&DevToolsHttpHandlerImpl::StopHandlerThread, this),
252      base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThreadAndRelease, this));
253}
254
255void DevToolsHttpHandlerImpl::StopWithoutRelease() {
256  if (!thread_)
257    return;
258  BrowserThread::PostTaskAndReply(
259      BrowserThread::FILE, FROM_HERE,
260      base::Bind(&DevToolsHttpHandlerImpl::StopHandlerThread, this),
261      base::Bind(&DevToolsHttpHandlerImpl::ResetHandlerThread, this));
262}
263
264GURL DevToolsHttpHandlerImpl::GetFrontendURL() {
265  net::IPEndPoint ip_address;
266  if (server_ && server_->GetLocalAddress(&ip_address))
267    return GURL();
268  return GURL(std::string("http://") + ip_address.ToString() + frontend_url_);
269}
270
271static std::string PathWithoutParams(const std::string& path) {
272  size_t query_position = path.find("?");
273  if (query_position != std::string::npos)
274    return path.substr(0, query_position);
275  return path;
276}
277
278static std::string GetMimeType(const std::string& filename) {
279  if (EndsWith(filename, ".html", false)) {
280    return "text/html";
281  } else if (EndsWith(filename, ".css", false)) {
282    return "text/css";
283  } else if (EndsWith(filename, ".js", false)) {
284    return "application/javascript";
285  } else if (EndsWith(filename, ".png", false)) {
286    return "image/png";
287  } else if (EndsWith(filename, ".gif", false)) {
288    return "image/gif";
289  } else if (EndsWith(filename, ".json", false)) {
290    return "application/json";
291  }
292  LOG(ERROR) << "GetMimeType doesn't know mime type for: "
293             << filename
294             << " text/plain will be returned";
295  NOTREACHED();
296  return "text/plain";
297}
298
299void DevToolsHttpHandlerImpl::OnHttpRequest(
300    int connection_id,
301    const net::HttpServerRequestInfo& info) {
302  server_->SetSendBufferSize(connection_id, kSendBufferSizeForDevTools);
303
304  if (info.path.find("/json") == 0) {
305    BrowserThread::PostTask(
306        BrowserThread::UI,
307        FROM_HERE,
308        base::Bind(&DevToolsHttpHandlerImpl::OnJsonRequestUI,
309                   this,
310                   connection_id,
311                   info));
312    return;
313  }
314
315  if (info.path.find(kThumbUrlPrefix) == 0) {
316    // Thumbnail request.
317    const std::string target_id = info.path.substr(strlen(kThumbUrlPrefix));
318    DevToolsTarget* target = GetTarget(target_id);
319    GURL page_url;
320    if (target)
321      page_url = target->GetURL();
322    BrowserThread::PostTask(
323        BrowserThread::UI,
324        FROM_HERE,
325        base::Bind(&DevToolsHttpHandlerImpl::OnThumbnailRequestUI,
326                   this,
327                   connection_id,
328                   page_url));
329    return;
330  }
331
332  if (info.path == "" || info.path == "/") {
333    // Discovery page request.
334    BrowserThread::PostTask(
335        BrowserThread::UI,
336        FROM_HERE,
337        base::Bind(&DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI,
338                   this,
339                   connection_id));
340    return;
341  }
342
343  if (info.path.find("/devtools/") != 0) {
344    server_->Send404(connection_id);
345    return;
346  }
347
348  std::string filename = PathWithoutParams(info.path.substr(10));
349  std::string mime_type = GetMimeType(filename);
350
351  base::FilePath frontend_dir = delegate_->GetDebugFrontendDir();
352  if (!frontend_dir.empty()) {
353    base::FilePath path = frontend_dir.AppendASCII(filename);
354    std::string data;
355    base::ReadFileToString(path, &data);
356    server_->Send200(connection_id, data, mime_type);
357    return;
358  }
359  if (delegate_->BundlesFrontendResources()) {
360    int resource_id = DevToolsHttpHandler::GetFrontendResourceId(filename);
361    if (resource_id != -1) {
362      base::StringPiece data = GetContentClient()->GetDataResource(
363              resource_id, ui::SCALE_FACTOR_NONE);
364      server_->Send200(connection_id, data.as_string(), mime_type);
365      return;
366    }
367  }
368  server_->Send404(connection_id);
369}
370
371void DevToolsHttpHandlerImpl::OnWebSocketRequest(
372    int connection_id,
373    const net::HttpServerRequestInfo& request) {
374  std::string browser_prefix = "/devtools/browser";
375  size_t browser_pos = request.path.find(browser_prefix);
376  if (browser_pos == 0) {
377    scoped_refptr<DevToolsBrowserTarget> browser_target =
378        new DevToolsBrowserTarget(server_.get(), connection_id);
379    browser_target->RegisterDomainHandler(
380        devtools::Tracing::kName,
381        new DevToolsTracingHandler(DevToolsTracingHandler::Browser),
382        true /* handle on UI thread */);
383    browser_target->RegisterDomainHandler(
384        devtools::Tethering::kName,
385        new TetheringHandler(delegate_.get()),
386        false /* handle on this thread */);
387    browser_target->RegisterDomainHandler(
388        devtools::SystemInfo::kName,
389        new DevToolsSystemInfoHandler(),
390        true /* handle on UI thread */);
391    browser_targets_[connection_id] = browser_target;
392
393    server_->SetSendBufferSize(connection_id, kSendBufferSizeForDevTools);
394    server_->AcceptWebSocket(connection_id, request);
395    return;
396  }
397
398  BrowserThread::PostTask(
399      BrowserThread::UI,
400      FROM_HERE,
401      base::Bind(
402          &DevToolsHttpHandlerImpl::OnWebSocketRequestUI,
403          this,
404          connection_id,
405          request));
406}
407
408void DevToolsHttpHandlerImpl::OnWebSocketMessage(
409    int connection_id,
410    const std::string& data) {
411  BrowserTargets::iterator it = browser_targets_.find(connection_id);
412  if (it != browser_targets_.end()) {
413    it->second->HandleMessage(data);
414    return;
415  }
416
417  BrowserThread::PostTask(
418      BrowserThread::UI,
419      FROM_HERE,
420      base::Bind(
421          &DevToolsHttpHandlerImpl::OnWebSocketMessageUI,
422          this,
423          connection_id,
424          data));
425}
426
427void DevToolsHttpHandlerImpl::OnClose(int connection_id) {
428  BrowserTargets::iterator it = browser_targets_.find(connection_id);
429  if (it != browser_targets_.end()) {
430    it->second->Detach();
431    browser_targets_.erase(it);
432    return;
433  }
434
435  BrowserThread::PostTask(
436      BrowserThread::UI,
437      FROM_HERE,
438      base::Bind(
439          &DevToolsHttpHandlerImpl::OnCloseUI,
440          this,
441          connection_id));
442}
443
444std::string DevToolsHttpHandlerImpl::GetFrontendURLInternal(
445    const std::string id,
446    const std::string& host) {
447  return base::StringPrintf(
448      "%s%sws=%s%s%s",
449      frontend_url_.c_str(),
450      frontend_url_.find("?") == std::string::npos ? "?" : "&",
451      host.c_str(),
452      kPageUrlPrefix,
453      id.c_str());
454}
455
456static bool ParseJsonPath(
457    const std::string& path,
458    std::string* command,
459    std::string* target_id) {
460
461  // Fall back to list in case of empty query.
462  if (path.empty()) {
463    *command = "list";
464    return true;
465  }
466
467  if (path.find("/") != 0) {
468    // Malformed command.
469    return false;
470  }
471  *command = path.substr(1);
472
473  size_t separator_pos = command->find("/");
474  if (separator_pos != std::string::npos) {
475    *target_id = command->substr(separator_pos + 1);
476    *command = command->substr(0, separator_pos);
477  }
478  return true;
479}
480
481void DevToolsHttpHandlerImpl::OnJsonRequestUI(
482    int connection_id,
483    const net::HttpServerRequestInfo& info) {
484  // Trim /json
485  std::string path = info.path.substr(5);
486
487  // Trim fragment and query
488  std::string query;
489  size_t query_pos = path.find("?");
490  if (query_pos != std::string::npos) {
491    query = path.substr(query_pos + 1);
492    path = path.substr(0, query_pos);
493  }
494
495  size_t fragment_pos = path.find("#");
496  if (fragment_pos != std::string::npos)
497    path = path.substr(0, fragment_pos);
498
499  std::string command;
500  std::string target_id;
501  if (!ParseJsonPath(path, &command, &target_id)) {
502    SendJson(connection_id,
503             net::HTTP_NOT_FOUND,
504             NULL,
505             "Malformed query: " + info.path);
506    return;
507  }
508
509  if (command == "version") {
510    base::DictionaryValue version;
511    version.SetString("Protocol-Version", devtools::kProtocolVersion);
512    version.SetString("WebKit-Version", GetWebKitVersion());
513    version.SetString("Browser", GetContentClient()->GetProduct());
514    version.SetString("User-Agent", GetContentClient()->GetUserAgent());
515#if defined(OS_ANDROID)
516    version.SetString("Android-Package",
517        base::android::BuildInfo::GetInstance()->package_name());
518#endif
519    SendJson(connection_id, net::HTTP_OK, &version, std::string());
520    return;
521  }
522
523  if (command == "list") {
524    std::string host = info.headers["host"];
525    AddRef();  // Balanced in OnTargetListReceived.
526    DevToolsManagerDelegate* manager_delegate =
527        DevToolsManager::GetInstance()->delegate();
528    if (manager_delegate) {
529      manager_delegate->EnumerateTargets(
530          base::Bind(&DevToolsHttpHandlerImpl::OnTargetListReceived,
531                     this, connection_id, host));
532    } else {
533      DevToolsManagerDelegate::TargetList empty_list;
534      OnTargetListReceived(connection_id, host, empty_list);
535    }
536    return;
537  }
538
539  if (command == "new") {
540    GURL url(net::UnescapeURLComponent(
541        query, net::UnescapeRule::URL_SPECIAL_CHARS));
542    if (!url.is_valid())
543      url = GURL(url::kAboutBlankURL);
544    scoped_ptr<DevToolsTarget> target;
545    DevToolsManagerDelegate* manager_delegate =
546        DevToolsManager::GetInstance()->delegate();
547    if (manager_delegate)
548      target = manager_delegate->CreateNewTarget(url);
549    if (!target) {
550      SendJson(connection_id,
551               net::HTTP_INTERNAL_SERVER_ERROR,
552               NULL,
553               "Could not create new page");
554      return;
555    }
556    std::string host = info.headers["host"];
557    scoped_ptr<base::DictionaryValue> dictionary(
558        SerializeTarget(*target.get(), host));
559    SendJson(connection_id, net::HTTP_OK, dictionary.get(), std::string());
560    const std::string target_id = target->GetId();
561    target_map_[target_id] = target.release();
562    return;
563  }
564
565  if (command == "activate" || command == "close") {
566    DevToolsTarget* target = GetTarget(target_id);
567    if (!target) {
568      SendJson(connection_id,
569               net::HTTP_NOT_FOUND,
570               NULL,
571               "No such target id: " + target_id);
572      return;
573    }
574
575    if (command == "activate") {
576      if (target->Activate()) {
577        SendJson(connection_id, net::HTTP_OK, NULL, "Target activated");
578      } else {
579        SendJson(connection_id,
580                 net::HTTP_INTERNAL_SERVER_ERROR,
581                 NULL,
582                 "Could not activate target id: " + target_id);
583      }
584      return;
585    }
586
587    if (command == "close") {
588      if (target->Close()) {
589        SendJson(connection_id, net::HTTP_OK, NULL, "Target is closing");
590      } else {
591        SendJson(connection_id,
592                 net::HTTP_INTERNAL_SERVER_ERROR,
593                 NULL,
594                 "Could not close target id: " + target_id);
595      }
596      return;
597    }
598  }
599  SendJson(connection_id,
600           net::HTTP_NOT_FOUND,
601           NULL,
602           "Unknown command: " + command);
603  return;
604}
605
606void DevToolsHttpHandlerImpl::OnTargetListReceived(
607    int connection_id,
608    const std::string& host,
609    const DevToolsManagerDelegate::TargetList& targets) {
610  DevToolsManagerDelegate::TargetList sorted_targets = targets;
611  std::sort(sorted_targets.begin(), sorted_targets.end(), TimeComparator);
612
613  STLDeleteValues(&target_map_);
614  base::ListValue list_value;
615  for (DevToolsManagerDelegate::TargetList::const_iterator it =
616      sorted_targets.begin(); it != sorted_targets.end(); ++it) {
617    DevToolsTarget* target = *it;
618    target_map_[target->GetId()] = target;
619    list_value.Append(SerializeTarget(*target, host));
620  }
621  SendJson(connection_id, net::HTTP_OK, &list_value, std::string());
622  Release();  // Balanced in OnJsonRequestUI.
623}
624
625DevToolsTarget* DevToolsHttpHandlerImpl::GetTarget(const std::string& id) {
626  TargetMap::const_iterator it = target_map_.find(id);
627  if (it == target_map_.end())
628    return NULL;
629  return it->second;
630}
631
632void DevToolsHttpHandlerImpl::OnThumbnailRequestUI(
633    int connection_id, const GURL& page_url) {
634  DevToolsManagerDelegate* manager_delegate =
635      DevToolsManager::GetInstance()->delegate();
636  std::string data =
637      manager_delegate ? manager_delegate->GetPageThumbnailData(page_url) : "";
638  if (!data.empty())
639    Send200(connection_id, data, "image/png");
640  else
641    Send404(connection_id);
642}
643
644void DevToolsHttpHandlerImpl::OnDiscoveryPageRequestUI(int connection_id) {
645  std::string response = delegate_->GetDiscoveryPageHTML();
646  Send200(connection_id, response, "text/html; charset=UTF-8");
647}
648
649void DevToolsHttpHandlerImpl::OnWebSocketRequestUI(
650    int connection_id,
651    const net::HttpServerRequestInfo& request) {
652  if (!thread_)
653    return;
654
655  size_t pos = request.path.find(kPageUrlPrefix);
656  if (pos != 0) {
657    Send404(connection_id);
658    return;
659  }
660
661  std::string page_id = request.path.substr(strlen(kPageUrlPrefix));
662  DevToolsTarget* target = GetTarget(page_id);
663  scoped_refptr<DevToolsAgentHost> agent =
664      target ? target->GetAgentHost() : NULL;
665  if (!agent.get()) {
666    Send500(connection_id, "No such target id: " + page_id);
667    return;
668  }
669
670  if (agent->IsAttached()) {
671    Send500(connection_id,
672            "Target with given id is being inspected: " + page_id);
673    return;
674  }
675
676  DevToolsAgentHostClientImpl* client_host = new DevToolsAgentHostClientImpl(
677      thread_->message_loop(), server_.get(), connection_id, agent.get());
678  connection_to_client_ui_[connection_id] = client_host;
679
680  AcceptWebSocket(connection_id, request);
681}
682
683void DevToolsHttpHandlerImpl::OnWebSocketMessageUI(
684    int connection_id,
685    const std::string& data) {
686  ConnectionToClientMap::iterator it =
687      connection_to_client_ui_.find(connection_id);
688  if (it == connection_to_client_ui_.end())
689    return;
690
691  DevToolsAgentHostClientImpl* client =
692      static_cast<DevToolsAgentHostClientImpl*>(it->second);
693  client->OnMessage(data);
694}
695
696void DevToolsHttpHandlerImpl::OnCloseUI(int connection_id) {
697  ConnectionToClientMap::iterator it =
698      connection_to_client_ui_.find(connection_id);
699  if (it != connection_to_client_ui_.end()) {
700    DevToolsAgentHostClientImpl* client =
701        static_cast<DevToolsAgentHostClientImpl*>(it->second);
702    delete client;
703    connection_to_client_ui_.erase(connection_id);
704  }
705}
706
707DevToolsHttpHandlerImpl::DevToolsHttpHandlerImpl(
708    scoped_ptr<ServerSocketFactory> server_socket_factory,
709    const std::string& frontend_url,
710    DevToolsHttpHandlerDelegate* delegate,
711    const base::FilePath& active_port_output_directory)
712    : frontend_url_(frontend_url),
713      server_socket_factory_(server_socket_factory.Pass()),
714      delegate_(delegate),
715      active_port_output_directory_(active_port_output_directory) {
716  if (frontend_url_.empty())
717    frontend_url_ = "/devtools/devtools.html";
718
719  // Balanced in ResetHandlerThreadAndRelease().
720  AddRef();
721}
722
723// Runs on the handler thread
724void DevToolsHttpHandlerImpl::Init() {
725  scoped_ptr<net::ServerSocket> server_socket =
726      server_socket_factory_->CreateAndListen();
727  if (!server_socket) {
728    LOG(ERROR) << "Cannot start http server for devtools. Stop devtools.";
729    BrowserThread::PostTask(
730        BrowserThread::UI, FROM_HERE,
731        base::Bind(&DevToolsHttpHandlerImpl::StopWithoutRelease, this));
732    return;
733  }
734
735  server_.reset(new net::HttpServer(server_socket.Pass(), this));
736  if (!active_port_output_directory_.empty())
737    WriteActivePortToUserProfile();
738}
739
740// Runs on the handler thread
741void DevToolsHttpHandlerImpl::Teardown() {
742  server_.reset(NULL);
743}
744
745// Runs on FILE thread to make sure that it is serialized against
746// {Start|Stop}HandlerThread and to allow calling pthread_join.
747void DevToolsHttpHandlerImpl::StopHandlerThread() {
748  if (!thread_->message_loop())
749    return;
750  thread_->message_loop()->PostTask(
751      FROM_HERE,
752      base::Bind(&DevToolsHttpHandlerImpl::Teardown, this));
753  // Thread::Stop joins the thread.
754  thread_->Stop();
755}
756
757void DevToolsHttpHandlerImpl::WriteActivePortToUserProfile() {
758  DCHECK(!active_port_output_directory_.empty());
759  net::IPEndPoint endpoint;
760  int err;
761  if ((err = server_->GetLocalAddress(&endpoint)) != net::OK) {
762    LOG(ERROR) << "Error " << err << " getting local address";
763    return;
764  }
765
766  // Write this port to a well-known file in the profile directory
767  // so Telemetry can pick it up.
768  base::FilePath path = active_port_output_directory_.Append(
769      kDevToolsActivePortFileName);
770  std::string port_string = base::IntToString(endpoint.port());
771  if (base::WriteFile(path, port_string.c_str(), port_string.length()) < 0) {
772    LOG(ERROR) << "Error writing DevTools active port to file";
773  }
774}
775
776void DevToolsHttpHandlerImpl::SendJson(int connection_id,
777                                       net::HttpStatusCode status_code,
778                                       base::Value* value,
779                                       const std::string& message) {
780  if (!thread_)
781    return;
782
783  // Serialize value and message.
784  std::string json_value;
785  if (value) {
786    base::JSONWriter::WriteWithOptions(value,
787                                       base::JSONWriter::OPTIONS_PRETTY_PRINT,
788                                       &json_value);
789  }
790  std::string json_message;
791  scoped_ptr<base::Value> message_object(new base::StringValue(message));
792  base::JSONWriter::Write(message_object.get(), &json_message);
793
794  net::HttpServerResponseInfo response(status_code);
795  response.SetBody(json_value + message, "application/json; charset=UTF-8");
796
797  thread_->message_loop()->PostTask(
798      FROM_HERE,
799      base::Bind(&net::HttpServer::SendResponse,
800                 base::Unretained(server_.get()),
801                 connection_id,
802                 response));
803}
804
805void DevToolsHttpHandlerImpl::Send200(int connection_id,
806                                      const std::string& data,
807                                      const std::string& mime_type) {
808  if (!thread_)
809    return;
810  thread_->message_loop()->PostTask(
811      FROM_HERE,
812      base::Bind(&net::HttpServer::Send200,
813                 base::Unretained(server_.get()),
814                 connection_id,
815                 data,
816                 mime_type));
817}
818
819void DevToolsHttpHandlerImpl::Send404(int connection_id) {
820  if (!thread_)
821    return;
822  thread_->message_loop()->PostTask(
823      FROM_HERE,
824      base::Bind(&net::HttpServer::Send404,
825                 base::Unretained(server_.get()),
826                 connection_id));
827}
828
829void DevToolsHttpHandlerImpl::Send500(int connection_id,
830                                      const std::string& message) {
831  if (!thread_)
832    return;
833  thread_->message_loop()->PostTask(
834      FROM_HERE,
835      base::Bind(&net::HttpServer::Send500,
836                 base::Unretained(server_.get()),
837                 connection_id,
838                 message));
839}
840
841void DevToolsHttpHandlerImpl::AcceptWebSocket(
842    int connection_id,
843    const net::HttpServerRequestInfo& request) {
844  if (!thread_)
845    return;
846  thread_->message_loop()->PostTask(
847      FROM_HERE,
848      base::Bind(&net::HttpServer::SetSendBufferSize,
849                 base::Unretained(server_.get()),
850                 connection_id,
851                 kSendBufferSizeForDevTools));
852  thread_->message_loop()->PostTask(
853      FROM_HERE,
854      base::Bind(&net::HttpServer::AcceptWebSocket,
855                 base::Unretained(server_.get()),
856                 connection_id,
857                 request));
858}
859
860base::DictionaryValue* DevToolsHttpHandlerImpl::SerializeTarget(
861    const DevToolsTarget& target,
862    const std::string& host) {
863  base::DictionaryValue* dictionary = new base::DictionaryValue;
864
865  std::string id = target.GetId();
866  dictionary->SetString(kTargetIdField, id);
867  std::string parent_id = target.GetParentId();
868  if (!parent_id.empty())
869    dictionary->SetString(kTargetParentIdField, parent_id);
870  dictionary->SetString(kTargetTypeField, target.GetType());
871  dictionary->SetString(kTargetTitleField,
872                        net::EscapeForHTML(target.GetTitle()));
873  dictionary->SetString(kTargetDescriptionField, target.GetDescription());
874
875  GURL url = target.GetURL();
876  dictionary->SetString(kTargetUrlField, url.spec());
877
878  GURL favicon_url = target.GetFaviconURL();
879  if (favicon_url.is_valid())
880    dictionary->SetString(kTargetFaviconUrlField, favicon_url.spec());
881
882  DevToolsManagerDelegate* manager_delegate =
883      DevToolsManager::GetInstance()->delegate();
884  if (manager_delegate &&
885      !manager_delegate->GetPageThumbnailData(url).empty()) {
886    dictionary->SetString(kTargetThumbnailUrlField,
887                          std::string(kThumbUrlPrefix) + id);
888  }
889
890  if (!target.IsAttached()) {
891    dictionary->SetString(kTargetWebSocketDebuggerUrlField,
892                          base::StringPrintf("ws://%s%s%s",
893                                             host.c_str(),
894                                             kPageUrlPrefix,
895                                             id.c_str()));
896    std::string devtools_frontend_url = GetFrontendURLInternal(
897        id.c_str(),
898        host);
899    dictionary->SetString(
900        kTargetDevtoolsFrontendUrlField, devtools_frontend_url);
901  }
902
903  return dictionary;
904}
905
906}  // namespace content
907