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/extensions/extension_webrequest_api.h"
6
7#include <algorithm>
8
9#include "base/json/json_writer.h"
10#include "base/metrics/histogram.h"
11#include "base/string_number_conversions.h"
12#include "base/values.h"
13#include "chrome/browser/extensions/extension_event_router_forwarder.h"
14#include "chrome/browser/extensions/extension_tab_id_map.h"
15#include "chrome/browser/extensions/extension_webrequest_api_constants.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/common/extensions/extension.h"
18#include "chrome/common/extensions/extension_error_utils.h"
19#include "chrome/common/extensions/extension_extent.h"
20#include "chrome/common/extensions/url_pattern.h"
21#include "chrome/common/url_constants.h"
22#include "content/browser/browser_thread.h"
23#include "content/browser/renderer_host/resource_dispatcher_host.h"
24#include "content/browser/renderer_host/resource_dispatcher_host_request_info.h"
25#include "net/base/net_errors.h"
26#include "net/url_request/url_request.h"
27#include "googleurl/src/gurl.h"
28
29namespace keys = extension_webrequest_api_constants;
30
31namespace {
32
33// List of all the webRequest events.
34static const char* const kWebRequestEvents[] = {
35  keys::kOnBeforeRedirect,
36  keys::kOnBeforeRequest,
37  keys::kOnBeforeSendHeaders,
38  keys::kOnCompleted,
39  keys::kOnErrorOccurred,
40  keys::kOnHeadersReceived,
41  keys::kOnRequestSent
42};
43
44static const char* kResourceTypeStrings[] = {
45  "main_frame",
46  "sub_frame",
47  "stylesheet",
48  "script",
49  "image",
50  "object",
51  "other",
52};
53
54static ResourceType::Type kResourceTypeValues[] = {
55  ResourceType::MAIN_FRAME,
56  ResourceType::SUB_FRAME,
57  ResourceType::STYLESHEET,
58  ResourceType::SCRIPT,
59  ResourceType::IMAGE,
60  ResourceType::OBJECT,
61  ResourceType::LAST_TYPE,  // represents "other"
62};
63
64COMPILE_ASSERT(
65    arraysize(kResourceTypeStrings) == arraysize(kResourceTypeValues),
66    keep_resource_types_in_sync);
67
68#define ARRAYEND(array) (array + arraysize(array))
69
70static bool IsWebRequestEvent(const std::string& event_name) {
71  return std::find(kWebRequestEvents, ARRAYEND(kWebRequestEvents),
72                   event_name) != ARRAYEND(kWebRequestEvents);
73}
74
75static const char* ResourceTypeToString(ResourceType::Type type) {
76  ResourceType::Type* iter =
77      std::find(kResourceTypeValues, ARRAYEND(kResourceTypeValues), type);
78  if (iter == ARRAYEND(kResourceTypeValues))
79    return "other";
80
81  return kResourceTypeStrings[iter - kResourceTypeValues];
82}
83
84static bool ParseResourceType(const std::string& type_str,
85                              ResourceType::Type* type) {
86  const char** iter =
87      std::find(kResourceTypeStrings, ARRAYEND(kResourceTypeStrings), type_str);
88  if (iter == ARRAYEND(kResourceTypeStrings))
89    return false;
90  *type = kResourceTypeValues[iter - kResourceTypeStrings];
91  return true;
92}
93
94static void ExtractRequestInfo(net::URLRequest* request,
95                               int* tab_id,
96                               int* window_id,
97                               ResourceType::Type* resource_type) {
98  if (!request->GetUserData(NULL))
99    return;
100
101  ResourceDispatcherHostRequestInfo* info =
102      ResourceDispatcherHost::InfoForRequest(request);
103  ExtensionTabIdMap::GetInstance()->GetTabAndWindowId(
104      info->child_id(), info->route_id(), tab_id, window_id);
105
106  // Restrict the resource type to the values we care about.
107  ResourceType::Type* iter =
108      std::find(kResourceTypeValues, ARRAYEND(kResourceTypeValues),
109                info->resource_type());
110  *resource_type = (iter != ARRAYEND(kResourceTypeValues)) ?
111      *iter : ResourceType::LAST_TYPE;
112}
113
114static void AddEventListenerOnIOThread(
115    ProfileId profile_id,
116    const std::string& extension_id,
117    const std::string& event_name,
118    const std::string& sub_event_name,
119    const ExtensionWebRequestEventRouter::RequestFilter& filter,
120    int extra_info_spec) {
121  ExtensionWebRequestEventRouter::GetInstance()->AddEventListener(
122      profile_id, extension_id, event_name, sub_event_name, filter,
123      extra_info_spec);
124}
125
126static void EventHandledOnIOThread(
127    ProfileId profile_id,
128    const std::string& extension_id,
129    const std::string& event_name,
130    const std::string& sub_event_name,
131    uint64 request_id,
132    bool cancel,
133    const GURL& new_url) {
134  ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled(
135      profile_id, extension_id, event_name, sub_event_name, request_id,
136      cancel, new_url);
137}
138
139}  // namespace
140
141// Internal representation of the webRequest.RequestFilter type, used to
142// filter what network events an extension cares about.
143struct ExtensionWebRequestEventRouter::RequestFilter {
144  ExtensionExtent urls;
145  std::vector<ResourceType::Type> types;
146  int tab_id;
147  int window_id;
148
149  RequestFilter() : tab_id(-1), window_id(-1) {}
150  bool InitFromValue(const DictionaryValue& value);
151};
152
153// Internal representation of the extraInfoSpec parameter on webRequest events,
154// used to specify extra information to be included with network events.
155struct ExtensionWebRequestEventRouter::ExtraInfoSpec {
156  enum Flags {
157    REQUEST_LINE = 1<<0,
158    REQUEST_HEADERS = 1<<1,
159    STATUS_LINE = 1<<2,
160    RESPONSE_HEADERS = 1<<3,
161    REDIRECT_REQUEST_LINE = 1<<4,
162    REDIRECT_REQUEST_HEADERS = 1<<5,
163    BLOCKING = 1<<6,
164  };
165
166  static bool InitFromValue(const ListValue& value, int* extra_info_spec);
167};
168
169// Represents a single unique listener to an event, along with whatever filter
170// parameters and extra_info_spec were specified at the time the listener was
171// added.
172struct ExtensionWebRequestEventRouter::EventListener {
173  std::string extension_id;
174  std::string sub_event_name;
175  RequestFilter filter;
176  int extra_info_spec;
177  mutable std::set<uint64> blocked_requests;
178
179  // Comparator to work with std::set.
180  bool operator<(const EventListener& that) const {
181    if (extension_id < that.extension_id)
182      return true;
183    if (extension_id == that.extension_id &&
184        sub_event_name < that.sub_event_name)
185      return true;
186    return false;
187  }
188};
189
190// Contains info about requests that are blocked waiting for a response from
191// an extension.
192struct ExtensionWebRequestEventRouter::BlockedRequest {
193  // The number of event handlers that we are awaiting a response from.
194  int num_handlers_blocking;
195
196  // The callback to call when we get a response from all event handlers.
197  net::CompletionCallback* callback;
198
199  // If non-empty, this contains the new URL that the request will redirect to.
200  GURL* new_url;
201
202  // Time the request was issued. Used for logging purposes.
203  base::Time request_time;
204
205  BlockedRequest() : num_handlers_blocking(0), callback(NULL), new_url(NULL) {}
206};
207
208bool ExtensionWebRequestEventRouter::RequestFilter::InitFromValue(
209    const DictionaryValue& value) {
210  for (DictionaryValue::key_iterator key = value.begin_keys();
211       key != value.end_keys(); ++key) {
212    if (*key == "urls") {
213      ListValue* urls_value = NULL;
214      if (!value.GetList("urls", &urls_value))
215        return false;
216      for (size_t i = 0; i < urls_value->GetSize(); ++i) {
217        std::string url;
218        URLPattern pattern(URLPattern::SCHEME_ALL);
219        if (!urls_value->GetString(i, &url) ||
220            pattern.Parse(url, URLPattern::PARSE_STRICT) !=
221                URLPattern::PARSE_SUCCESS)
222          return false;
223        urls.AddPattern(pattern);
224      }
225    } else if (*key == "types") {
226      ListValue* types_value = NULL;
227      if (!value.GetList("types", &types_value))
228        return false;
229      for (size_t i = 0; i < types_value->GetSize(); ++i) {
230        std::string type_str;
231        ResourceType::Type type;
232        if (!types_value->GetString(i, &type_str) ||
233            !ParseResourceType(type_str, &type))
234          return false;
235        types.push_back(type);
236      }
237    } else if (*key == "tabId") {
238      if (!value.GetInteger("tabId", &tab_id))
239        return false;
240    } else if (*key == "windowId") {
241      if (!value.GetInteger("windowId", &window_id))
242        return false;
243    } else {
244      return false;
245    }
246  }
247  return true;
248}
249
250// static
251bool ExtensionWebRequestEventRouter::ExtraInfoSpec::InitFromValue(
252    const ListValue& value, int* extra_info_spec) {
253  *extra_info_spec = 0;
254  for (size_t i = 0; i < value.GetSize(); ++i) {
255    std::string str;
256    if (!value.GetString(i, &str))
257      return false;
258
259    // TODO(mpcomplete): not all of these are valid for every event.
260    if (str == "requestLine")
261      *extra_info_spec |= REQUEST_LINE;
262    else if (str == "requestHeaders")
263      *extra_info_spec |= REQUEST_HEADERS;
264    else if (str == "statusLine")
265      *extra_info_spec |= STATUS_LINE;
266    else if (str == "responseHeaders")
267      *extra_info_spec |= RESPONSE_HEADERS;
268    else if (str == "redirectRequestLine")
269      *extra_info_spec |= REDIRECT_REQUEST_LINE;
270    else if (str == "redirectRequestHeaders")
271      *extra_info_spec |= REDIRECT_REQUEST_HEADERS;
272    else if (str == "blocking")
273      *extra_info_spec |= BLOCKING;
274    else
275      return false;
276  }
277  return true;
278}
279
280// static
281ExtensionWebRequestEventRouter* ExtensionWebRequestEventRouter::GetInstance() {
282  return Singleton<ExtensionWebRequestEventRouter>::get();
283}
284
285ExtensionWebRequestEventRouter::ExtensionWebRequestEventRouter() {
286}
287
288ExtensionWebRequestEventRouter::~ExtensionWebRequestEventRouter() {
289}
290
291int ExtensionWebRequestEventRouter::OnBeforeRequest(
292    ProfileId profile_id,
293    ExtensionEventRouterForwarder* event_router,
294    net::URLRequest* request,
295    net::CompletionCallback* callback,
296    GURL* new_url) {
297  // TODO(jochen): Figure out what to do with events from the system context.
298  if (profile_id == Profile::kInvalidProfileId)
299    return net::OK;
300
301  int tab_id = -1;
302  int window_id = -1;
303  ResourceType::Type resource_type = ResourceType::LAST_TYPE;
304  ExtractRequestInfo(request, &tab_id, &window_id, &resource_type);
305
306  std::vector<const EventListener*> listeners =
307      GetMatchingListeners(profile_id, keys::kOnBeforeRequest, request->url(),
308                           tab_id, window_id, resource_type);
309  if (listeners.empty())
310    return net::OK;
311
312  // If this is an HTTP request, keep track of it. HTTP-specific events only
313  // have the request ID, so we'll need to look up the URLRequest from that.
314  if (request->url().SchemeIs(chrome::kHttpScheme) ||
315      request->url().SchemeIs(chrome::kHttpsScheme)) {
316    http_requests_[request->identifier()] = request;
317  }
318
319  ListValue args;
320  DictionaryValue* dict = new DictionaryValue();
321  dict->SetString(keys::kRequestIdKey,
322                  base::Uint64ToString(request->identifier()));
323  dict->SetString(keys::kUrlKey, request->url().spec());
324  dict->SetString(keys::kMethodKey, request->method());
325  dict->SetInteger(keys::kTabIdKey, tab_id);
326  dict->SetString(keys::kTypeKey, ResourceTypeToString(resource_type));
327  dict->SetDouble(keys::kTimeStampKey,
328                  request->request_time().ToDoubleT() * 1000);
329  args.Append(dict);
330
331  if (DispatchEvent(profile_id, event_router, request, callback, listeners,
332                    args)) {
333    blocked_requests_[request->identifier()].new_url = new_url;
334    return net::ERR_IO_PENDING;
335  }
336  return net::OK;
337}
338
339int ExtensionWebRequestEventRouter::OnBeforeSendHeaders(
340    ProfileId profile_id,
341    ExtensionEventRouterForwarder* event_router,
342    uint64 request_id,
343    net::CompletionCallback* callback,
344    net::HttpRequestHeaders* headers) {
345  // TODO(jochen): Figure out what to do with events from the system context.
346  if (profile_id == Profile::kInvalidProfileId)
347    return net::OK;
348
349  HttpRequestMap::iterator iter = http_requests_.find(request_id);
350  if (iter == http_requests_.end())
351    return net::OK;
352
353  net::URLRequest* request = iter->second;
354  http_requests_.erase(iter);
355
356  std::vector<const EventListener*> listeners =
357      GetMatchingListeners(profile_id, keys::kOnBeforeSendHeaders, request);
358  if (listeners.empty())
359    return net::OK;
360
361  ListValue args;
362  DictionaryValue* dict = new DictionaryValue();
363  dict->SetString(keys::kRequestIdKey,
364                  base::Uint64ToString(request->identifier()));
365  dict->SetString(keys::kUrlKey, request->url().spec());
366  dict->SetDouble(keys::kTimeStampKey,
367                  request->request_time().ToDoubleT() * 1000);
368  // TODO(mpcomplete): request headers.
369  args.Append(dict);
370
371  if (DispatchEvent(profile_id, event_router, request, callback, listeners,
372                    args))
373    return net::ERR_IO_PENDING;
374  return net::OK;
375}
376
377void ExtensionWebRequestEventRouter::OnURLRequestDestroyed(
378    ProfileId profile_id, net::URLRequest* request) {
379  http_requests_.erase(request->identifier());
380  blocked_requests_.erase(request->identifier());
381}
382
383bool ExtensionWebRequestEventRouter::DispatchEvent(
384    ProfileId profile_id,
385    ExtensionEventRouterForwarder* event_router,
386    net::URLRequest* request,
387    net::CompletionCallback* callback,
388    const std::vector<const EventListener*>& listeners,
389    const ListValue& args) {
390  std::string json_args;
391  base::JSONWriter::Write(&args, false, &json_args);
392
393  // TODO(mpcomplete): Consider consolidating common (extension_id,json_args)
394  // pairs into a single message sent to a list of sub_event_names.
395  int num_handlers_blocking = 0;
396  for (std::vector<const EventListener*>::const_iterator it = listeners.begin();
397       it != listeners.end(); ++it) {
398    event_router->DispatchEventToExtension(
399        (*it)->extension_id, (*it)->sub_event_name, json_args,
400        profile_id, true, GURL());
401    if (callback && (*it)->extra_info_spec & ExtraInfoSpec::BLOCKING) {
402      (*it)->blocked_requests.insert(request->identifier());
403      ++num_handlers_blocking;
404    }
405  }
406
407  if (num_handlers_blocking > 0) {
408    CHECK(blocked_requests_.find(request->identifier()) ==
409          blocked_requests_.end());
410    blocked_requests_[request->identifier()].num_handlers_blocking =
411        num_handlers_blocking;
412    blocked_requests_[request->identifier()].callback = callback;
413    blocked_requests_[request->identifier()].request_time =
414        request->request_time();
415
416    return true;
417  }
418
419  return false;
420}
421
422void ExtensionWebRequestEventRouter::OnEventHandled(
423    ProfileId profile_id,
424    const std::string& extension_id,
425    const std::string& event_name,
426    const std::string& sub_event_name,
427    uint64 request_id,
428    bool cancel,
429    const GURL& new_url) {
430  EventListener listener;
431  listener.extension_id = extension_id;
432  listener.sub_event_name = sub_event_name;
433
434  // The listener may have been removed (e.g. due to the process going away)
435  // before we got here.
436  std::set<EventListener>::iterator found =
437      listeners_[profile_id][event_name].find(listener);
438  if (found != listeners_[profile_id][event_name].end())
439    found->blocked_requests.erase(request_id);
440
441  DecrementBlockCount(request_id, cancel, new_url);
442}
443
444void ExtensionWebRequestEventRouter::AddEventListener(
445    ProfileId profile_id,
446    const std::string& extension_id,
447    const std::string& event_name,
448    const std::string& sub_event_name,
449    const RequestFilter& filter,
450    int extra_info_spec) {
451  if (!IsWebRequestEvent(event_name))
452    return;
453
454  EventListener listener;
455  listener.extension_id = extension_id;
456  listener.sub_event_name = sub_event_name;
457  listener.filter = filter;
458  listener.extra_info_spec = extra_info_spec;
459
460  CHECK_EQ(listeners_[profile_id][event_name].count(listener), 0u) <<
461      "extension=" << extension_id << " event=" << event_name;
462  listeners_[profile_id][event_name].insert(listener);
463}
464
465void ExtensionWebRequestEventRouter::RemoveEventListener(
466    ProfileId profile_id,
467    const std::string& extension_id,
468    const std::string& sub_event_name) {
469  size_t slash_sep = sub_event_name.find('/');
470  std::string event_name = sub_event_name.substr(0, slash_sep);
471
472  if (!IsWebRequestEvent(event_name))
473    return;
474
475  EventListener listener;
476  listener.extension_id = extension_id;
477  listener.sub_event_name = sub_event_name;
478
479  CHECK_EQ(listeners_[profile_id][event_name].count(listener), 1u) <<
480      "extension=" << extension_id << " event=" << event_name;
481
482  // Unblock any request that this event listener may have been blocking.
483  std::set<EventListener>::iterator found =
484      listeners_[profile_id][event_name].find(listener);
485  for (std::set<uint64>::iterator it = found->blocked_requests.begin();
486       it != found->blocked_requests.end(); ++it) {
487    DecrementBlockCount(*it, false, GURL());
488  }
489
490  listeners_[profile_id][event_name].erase(listener);
491}
492
493std::vector<const ExtensionWebRequestEventRouter::EventListener*>
494ExtensionWebRequestEventRouter::GetMatchingListeners(
495    ProfileId profile_id,
496    const std::string& event_name,
497    const GURL& url,
498    int tab_id,
499    int window_id,
500    ResourceType::Type resource_type) {
501  // TODO(mpcomplete): handle profile_id == invalid (should collect all
502  // listeners).
503  std::vector<const EventListener*> matching_listeners;
504  std::set<EventListener>& listeners = listeners_[profile_id][event_name];
505  for (std::set<EventListener>::iterator it = listeners.begin();
506       it != listeners.end(); ++it) {
507    if (!it->filter.urls.is_empty() && !it->filter.urls.ContainsURL(url))
508      continue;
509    if (it->filter.tab_id != -1 && tab_id != it->filter.tab_id)
510      continue;
511    if (it->filter.window_id != -1 && window_id != it->filter.window_id)
512      continue;
513    if (!it->filter.types.empty() &&
514        std::find(it->filter.types.begin(), it->filter.types.end(),
515                  resource_type) == it->filter.types.end())
516      continue;
517
518    matching_listeners.push_back(&(*it));
519  }
520  return matching_listeners;
521}
522
523std::vector<const ExtensionWebRequestEventRouter::EventListener*>
524ExtensionWebRequestEventRouter::GetMatchingListeners(
525    ProfileId profile_id,
526    const std::string& event_name,
527    net::URLRequest* request) {
528  int tab_id = -1;
529  int window_id = -1;
530  ResourceType::Type resource_type = ResourceType::LAST_TYPE;
531  ExtractRequestInfo(request, &tab_id, &window_id, &resource_type);
532
533  return GetMatchingListeners(
534      profile_id, event_name, request->url(), tab_id, window_id, resource_type);
535}
536
537void ExtensionWebRequestEventRouter::DecrementBlockCount(uint64 request_id,
538                                                         bool cancel,
539                                                         const GURL& new_url) {
540  // It's possible that this request was deleted, or cancelled by a previous
541  // event handler. If so, ignore this response.
542  if (blocked_requests_.find(request_id) == blocked_requests_.end())
543    return;
544
545  BlockedRequest& blocked_request = blocked_requests_[request_id];
546  int num_handlers_blocking = --blocked_request.num_handlers_blocking;
547  CHECK_GE(num_handlers_blocking, 0);
548
549  if (num_handlers_blocking == 0 || cancel || !new_url.is_empty()) {
550    HISTOGRAM_TIMES("Extensions.NetworkDelay",
551                     base::Time::Now() - blocked_request.request_time);
552
553    CHECK(blocked_request.callback);
554    if (!new_url.is_empty()) {
555      CHECK(new_url.is_valid());
556      *blocked_request.new_url = new_url;
557    }
558    blocked_request.callback->Run(cancel ? net::ERR_EMPTY_RESPONSE : net::OK);
559    blocked_requests_.erase(request_id);
560  }
561}
562
563bool WebRequestAddEventListener::RunImpl() {
564  // Argument 0 is the callback, which we don't use here.
565
566  ExtensionWebRequestEventRouter::RequestFilter filter;
567  if (HasOptionalArgument(1)) {
568    DictionaryValue* value = NULL;
569    EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &value));
570    EXTENSION_FUNCTION_VALIDATE(filter.InitFromValue(*value));
571  }
572
573  int extra_info_spec = 0;
574  if (HasOptionalArgument(2)) {
575    ListValue* value = NULL;
576    EXTENSION_FUNCTION_VALIDATE(args_->GetList(2, &value));
577    EXTENSION_FUNCTION_VALIDATE(
578        ExtensionWebRequestEventRouter::ExtraInfoSpec::InitFromValue(
579            *value, &extra_info_spec));
580  }
581
582  std::string event_name;
583  EXTENSION_FUNCTION_VALIDATE(args_->GetString(3, &event_name));
584
585  std::string sub_event_name;
586  EXTENSION_FUNCTION_VALIDATE(args_->GetString(4, &sub_event_name));
587
588  BrowserThread::PostTask(
589      BrowserThread::IO, FROM_HERE,
590      NewRunnableFunction(
591          &AddEventListenerOnIOThread,
592          profile()->GetRuntimeId(), extension_id(),
593          event_name, sub_event_name, filter, extra_info_spec));
594
595  return true;
596}
597
598bool WebRequestEventHandled::RunImpl() {
599  std::string event_name;
600  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &event_name));
601
602  std::string sub_event_name;
603  EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &sub_event_name));
604
605  std::string request_id_str;
606  EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &request_id_str));
607  // TODO(mpcomplete): string-to-uint64?
608  int64 request_id;
609  EXTENSION_FUNCTION_VALIDATE(base::StringToInt64(request_id_str, &request_id));
610
611  bool cancel = false;
612  GURL new_url;
613  if (HasOptionalArgument(3)) {
614    DictionaryValue* value = NULL;
615    EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(3, &value));
616
617    if (value->HasKey("cancel"))
618      EXTENSION_FUNCTION_VALIDATE(value->GetBoolean("cancel", &cancel));
619
620    std::string new_url_str;
621    if (value->HasKey("redirectUrl")) {
622      EXTENSION_FUNCTION_VALIDATE(value->GetString("redirectUrl",
623                                                   &new_url_str));
624      new_url = GURL(new_url_str);
625      if (!new_url.is_valid()) {
626        error_ = ExtensionErrorUtils::FormatErrorMessage(
627            keys::kInvalidRedirectUrl, new_url_str);
628        return false;
629      }
630    }
631  }
632
633  BrowserThread::PostTask(
634      BrowserThread::IO, FROM_HERE,
635      NewRunnableFunction(
636          &EventHandledOnIOThread,
637          profile()->GetRuntimeId(), extension_id(),
638          event_name, sub_event_name, request_id, cancel, new_url));
639
640  return true;
641}
642