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_netlog_observer.h"
6
7#include "base/strings/string_util.h"
8#include "base/values.h"
9#include "content/public/browser/browser_thread.h"
10#include "content/public/browser/content_browser_client.h"
11#include "content/public/common/resource_response.h"
12#include "net/base/load_flags.h"
13#include "net/http/http_response_headers.h"
14#include "net/http/http_util.h"
15#include "net/spdy/spdy_header_block.h"
16#include "net/url_request/url_request.h"
17#include "net/url_request/url_request_netlog_params.h"
18
19namespace content {
20const size_t kMaxNumEntries = 1000;
21
22DevToolsNetLogObserver* DevToolsNetLogObserver::instance_ = NULL;
23
24DevToolsNetLogObserver::DevToolsNetLogObserver() {
25}
26
27DevToolsNetLogObserver::~DevToolsNetLogObserver() {
28}
29
30DevToolsNetLogObserver::ResourceInfo*
31DevToolsNetLogObserver::GetResourceInfo(uint32 id) {
32  RequestToInfoMap::iterator it = request_to_info_.find(id);
33  if (it != request_to_info_.end())
34    return it->second.get();
35  return NULL;
36}
37
38void DevToolsNetLogObserver::OnAddEntry(const net::NetLog::Entry& entry) {
39  // The events that the Observer is interested in only occur on the IO thread.
40  if (!BrowserThread::CurrentlyOn(BrowserThread::IO))
41    return;
42
43  if (entry.source().type == net::NetLog::SOURCE_URL_REQUEST)
44    OnAddURLRequestEntry(entry);
45}
46
47void DevToolsNetLogObserver::OnAddURLRequestEntry(
48    const net::NetLog::Entry& entry) {
49  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
50
51  bool is_begin = entry.phase() == net::NetLog::PHASE_BEGIN;
52  bool is_end = entry.phase() == net::NetLog::PHASE_END;
53
54  if (entry.type() == net::NetLog::TYPE_URL_REQUEST_START_JOB) {
55    if (is_begin) {
56      int load_flags;
57      scoped_ptr<base::Value> event_param(entry.ParametersToValue());
58      if (!net::StartEventLoadFlagsFromEventParams(event_param.get(),
59                                                   &load_flags)) {
60        return;
61      }
62
63      if (!(load_flags & net::LOAD_REPORT_RAW_HEADERS))
64        return;
65
66      if (request_to_info_.size() > kMaxNumEntries) {
67        LOG(WARNING) << "The raw headers observer url request count has grown "
68                        "larger than expected, resetting";
69        request_to_info_.clear();
70      }
71
72      request_to_info_[entry.source().id] = new ResourceInfo();
73    }
74    return;
75  } else if (entry.type() == net::NetLog::TYPE_REQUEST_ALIVE) {
76    // Cleanup records based on the TYPE_REQUEST_ALIVE entry.
77    if (is_end)
78      request_to_info_.erase(entry.source().id);
79    return;
80  }
81
82  ResourceInfo* info = GetResourceInfo(entry.source().id);
83  if (!info)
84    return;
85
86  switch (entry.type()) {
87    case net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST_HEADERS: {
88      scoped_ptr<base::Value> event_params(entry.ParametersToValue());
89      std::string request_line;
90      net::HttpRequestHeaders request_headers;
91
92      if (!net::HttpRequestHeaders::FromNetLogParam(event_params.get(),
93                                                    &request_headers,
94                                                    &request_line)) {
95        NOTREACHED();
96      }
97
98      // We need to clear headers in case the same url_request is reused for
99      // several http requests (e.g. see http://crbug.com/80157).
100      info->request_headers.clear();
101
102      for (net::HttpRequestHeaders::Iterator it(request_headers);
103           it.GetNext();) {
104        info->request_headers.push_back(std::make_pair(it.name(), it.value()));
105      }
106      info->request_headers_text = request_line + request_headers.ToString();
107      break;
108    }
109    case net::NetLog::TYPE_HTTP_TRANSACTION_SPDY_SEND_REQUEST_HEADERS: {
110      scoped_ptr<base::Value> event_params(entry.ParametersToValue());
111      net::SpdyHeaderBlock request_headers;
112
113      if (!net::SpdyHeaderBlockFromNetLogParam(event_params.get(),
114                                               &request_headers)) {
115        NOTREACHED();
116      }
117
118      // We need to clear headers in case the same url_request is reused for
119      // several http requests (e.g. see http://crbug.com/80157).
120      info->request_headers.clear();
121
122      for (net::SpdyHeaderBlock::const_iterator it = request_headers.begin();
123           it != request_headers.end(); ++it) {
124        info->request_headers.push_back(std::make_pair(it->first, it->second));
125      }
126      info->request_headers_text = "";
127      break;
128    }
129    case net::NetLog::TYPE_HTTP_TRANSACTION_READ_RESPONSE_HEADERS: {
130      scoped_ptr<base::Value> event_params(entry.ParametersToValue());
131
132      scoped_refptr<net::HttpResponseHeaders> response_headers;
133
134      if (!net::HttpResponseHeaders::FromNetLogParam(event_params.get(),
135                                                     &response_headers)) {
136        NOTREACHED();
137      }
138
139      info->http_status_code = response_headers->response_code();
140      info->http_status_text = response_headers->GetStatusText();
141      std::string name, value;
142
143      // We need to clear headers in case the same url_request is reused for
144      // several http requests (e.g. see http://crbug.com/80157).
145      info->response_headers.clear();
146
147      for (void* it = NULL;
148           response_headers->EnumerateHeaderLines(&it, &name, &value); ) {
149        info->response_headers.push_back(std::make_pair(name, value));
150      }
151
152      if (!info->request_headers_text.empty()) {
153        info->response_headers_text =
154            net::HttpUtil::ConvertHeadersBackToHTTPResponse(
155                response_headers->raw_headers());
156      } else {
157        // SPDY request.
158        info->response_headers_text = "";
159      }
160      break;
161    }
162    default:
163      break;
164  }
165}
166
167void DevToolsNetLogObserver::Attach() {
168  DCHECK(!instance_);
169  net::NetLog* net_log = GetContentClient()->browser()->GetNetLog();
170  if (net_log) {
171    instance_ = new DevToolsNetLogObserver();
172    net_log->AddThreadSafeObserver(instance_, net::NetLog::LOG_ALL_BUT_BYTES);
173  }
174}
175
176void DevToolsNetLogObserver::Detach() {
177  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
178
179  if (instance_) {
180    // Safest not to do this in the destructor to maintain thread safety across
181    // refactorings.
182    instance_->net_log()->RemoveThreadSafeObserver(instance_);
183    delete instance_;
184    instance_ = NULL;
185  }
186}
187
188DevToolsNetLogObserver* DevToolsNetLogObserver::GetInstance() {
189  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
190
191  return instance_;
192}
193
194// static
195void DevToolsNetLogObserver::PopulateResponseInfo(
196    net::URLRequest* request,
197    ResourceResponse* response) {
198  if (!(request->load_flags() & net::LOAD_REPORT_RAW_HEADERS))
199    return;
200
201  uint32 source_id = request->net_log().source().id;
202  DevToolsNetLogObserver* dev_tools_net_log_observer =
203      DevToolsNetLogObserver::GetInstance();
204  if (dev_tools_net_log_observer == NULL)
205    return;
206  response->head.devtools_info =
207      dev_tools_net_log_observer->GetResourceInfo(source_id);
208}
209
210}  // namespace content
211