| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/extension_webrequest_api.h" |
| |
| #include <algorithm> |
| |
| #include "base/bind.h" |
| #include "base/json/json_writer.h" |
| #include "base/metrics/histogram.h" |
| #include "base/string_number_conversions.h" |
| #include "base/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chrome_content_browser_client.h" |
| #include "chrome/browser/extensions/extension_event_router.h" |
| #include "chrome/browser/extensions/extension_info_map.h" |
| #include "chrome/browser/extensions/extension_prefs.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_tab_id_map.h" |
| #include "chrome/browser/extensions/extension_webrequest_api_constants.h" |
| #include "chrome/browser/extensions/extension_webrequest_time_tracker.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/renderer_host/chrome_render_message_filter.h" |
| #include "chrome/browser/renderer_host/web_cache_manager.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "chrome/common/extensions/extension_error_utils.h" |
| #include "chrome/common/extensions/extension_messages.h" |
| #include "chrome/common/extensions/url_pattern.h" |
| #include "chrome/common/url_constants.h" |
| #include "content/browser/browser_message_filter.h" |
| #include "content/browser/browser_thread.h" |
| #include "content/browser/renderer_host/resource_dispatcher_host.h" |
| #include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" |
| #include "googleurl/src/gurl.h" |
| #include "grit/generated_resources.h" |
| #include "net/base/auth.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/net_log.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_util.h" |
| #include "net/url_request/url_request.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace keys = extension_webrequest_api_constants; |
| |
| namespace { |
| |
| // List of all the webRequest events. |
| static const char* const kWebRequestEvents[] = { |
| keys::kOnBeforeRedirect, |
| keys::kOnBeforeRequest, |
| keys::kOnBeforeSendHeaders, |
| keys::kOnCompleted, |
| keys::kOnErrorOccurred, |
| keys::kOnSendHeaders, |
| keys::kOnAuthRequired, |
| keys::kOnResponseStarted, |
| keys::kOnHeadersReceived, |
| }; |
| |
| static const char* kResourceTypeStrings[] = { |
| "main_frame", |
| "sub_frame", |
| "stylesheet", |
| "script", |
| "image", |
| "object", |
| "xmlhttprequest", |
| "other", |
| "other", |
| }; |
| |
| static ResourceType::Type kResourceTypeValues[] = { |
| ResourceType::MAIN_FRAME, |
| ResourceType::SUB_FRAME, |
| ResourceType::STYLESHEET, |
| ResourceType::SCRIPT, |
| ResourceType::IMAGE, |
| ResourceType::OBJECT, |
| ResourceType::XHR, |
| ResourceType::LAST_TYPE, // represents "other" |
| // TODO(jochen): We duplicate the last entry, so the array's size is not a |
| // power of two. If it is, this triggers a bug in gcc 4.4 in Release builds |
| // (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=43949). Once we use a version |
| // of gcc with this bug fixed, or the array is changed so this duplicate |
| // entry is no longer required, this should be removed. |
| ResourceType::LAST_TYPE, |
| }; |
| |
| COMPILE_ASSERT( |
| arraysize(kResourceTypeStrings) == arraysize(kResourceTypeValues), |
| keep_resource_types_in_sync); |
| |
| #define ARRAYEND(array) (array + arraysize(array)) |
| |
| // NetLog parameter to indicate the ID of the extension that caused an event. |
| class NetLogExtensionIdParameter : public net::NetLog::EventParameters { |
| public: |
| explicit NetLogExtensionIdParameter(const std::string& extension_id) |
| : extension_id_(extension_id) {} |
| virtual ~NetLogExtensionIdParameter() {} |
| |
| virtual base::Value* ToValue() const OVERRIDE { |
| DictionaryValue* dict = new DictionaryValue(); |
| dict->SetString("extension_id", extension_id_); |
| return dict; |
| } |
| |
| private: |
| const std::string extension_id_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NetLogExtensionIdParameter); |
| }; |
| |
| // NetLog parameter to indicate that an extension modified a request. |
| class NetLogModificationParameter : public NetLogExtensionIdParameter { |
| public: |
| explicit NetLogModificationParameter(const std::string& extension_id) |
| : NetLogExtensionIdParameter(extension_id) {} |
| virtual ~NetLogModificationParameter() {} |
| |
| virtual base::Value* ToValue() const OVERRIDE { |
| Value* parent = NetLogExtensionIdParameter::ToValue(); |
| DCHECK(parent->IsType(Value::TYPE_DICTIONARY)); |
| DictionaryValue* dict = static_cast<DictionaryValue*>(parent); |
| dict->Set("modified_headers", modified_headers_.DeepCopy()); |
| dict->Set("deleted_headers", deleted_headers_.DeepCopy()); |
| return dict; |
| } |
| |
| void DeletedHeader(const std::string& key) { |
| deleted_headers_.Append(Value::CreateStringValue(key)); |
| } |
| |
| void ModifiedHeader(const std::string& key, const std::string& value) { |
| modified_headers_.Append(Value::CreateStringValue(key + ": " + value)); |
| } |
| |
| private: |
| ListValue modified_headers_; |
| ListValue deleted_headers_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NetLogModificationParameter); |
| }; |
| |
| // Returns the frame ID as it will be passed to the extension: |
| // 0 if the navigation happens in the main frame, or the frame ID |
| // modulo 32 bits otherwise. |
| // Keep this in sync with the GetFrameId() function in |
| // extension_webnavigation_api.cc. |
| int GetFrameId(bool is_main_frame, int64 frame_id) { |
| return is_main_frame ? 0 : static_cast<int>(frame_id); |
| } |
| |
| bool IsWebRequestEvent(const std::string& event_name) { |
| return std::find(kWebRequestEvents, ARRAYEND(kWebRequestEvents), |
| event_name) != ARRAYEND(kWebRequestEvents); |
| } |
| |
| // Returns true if the scheme is one we want to allow extensions to have access |
| // to. Extensions still need specific permissions for a given URL, which is |
| // covered by CanExtensionAccessURL. |
| bool HasWebRequestScheme(const GURL& url) { |
| return (url.SchemeIs(chrome::kAboutScheme) || |
| url.SchemeIs(chrome::kFileScheme) || |
| url.SchemeIs(chrome::kFtpScheme) || |
| url.SchemeIs(chrome::kHttpScheme) || |
| url.SchemeIs(chrome::kHttpsScheme) || |
| url.SchemeIs(chrome::kExtensionScheme)); |
| } |
| |
| bool CanExtensionAccessURL(const Extension* extension, const GURL& url) { |
| // about: URLs are not covered in host permissions, but are allowed anyway. |
| return (url.SchemeIs(chrome::kAboutScheme) || |
| extension->HasHostPermission(url) || |
| url.GetOrigin() == extension->url()); |
| } |
| |
| const char* ResourceTypeToString(ResourceType::Type type) { |
| ResourceType::Type* iter = |
| std::find(kResourceTypeValues, ARRAYEND(kResourceTypeValues), type); |
| if (iter == ARRAYEND(kResourceTypeValues)) |
| return "other"; |
| |
| return kResourceTypeStrings[iter - kResourceTypeValues]; |
| } |
| |
| bool ParseResourceType(const std::string& type_str, |
| ResourceType::Type* type) { |
| const char** iter = |
| std::find(kResourceTypeStrings, ARRAYEND(kResourceTypeStrings), type_str); |
| if (iter == ARRAYEND(kResourceTypeStrings)) |
| return false; |
| *type = kResourceTypeValues[iter - kResourceTypeStrings]; |
| return true; |
| } |
| |
| void ExtractRequestInfoDetails(net::URLRequest* request, |
| bool* is_main_frame, |
| int64* frame_id, |
| int* tab_id, |
| int* window_id, |
| ResourceType::Type* resource_type) { |
| if (!request->GetUserData(NULL)) |
| return; |
| |
| ResourceDispatcherHostRequestInfo* info = |
| ResourceDispatcherHost::InfoForRequest(request); |
| ExtensionTabIdMap::GetInstance()->GetTabAndWindowId( |
| info->child_id(), info->route_id(), tab_id, window_id); |
| *frame_id = info->frame_id(); |
| *is_main_frame = info->is_main_frame(); |
| |
| // Restrict the resource type to the values we care about. |
| ResourceType::Type* iter = |
| std::find(kResourceTypeValues, ARRAYEND(kResourceTypeValues), |
| info->resource_type()); |
| *resource_type = (iter != ARRAYEND(kResourceTypeValues)) ? |
| *iter : ResourceType::LAST_TYPE; |
| } |
| |
| // Extracts from |request| information for the keys requestId, url, method, |
| // frameId, tabId, type, and timeStamp and writes these into |out| to be passed |
| // on to extensions. |
| void ExtractRequestInfo(net::URLRequest* request, DictionaryValue* out) { |
| bool is_main_frame = false; |
| int64 frame_id = -1; |
| int frame_id_for_extension = -1; |
| int tab_id = -1; |
| int window_id = -1; |
| ResourceType::Type resource_type = ResourceType::LAST_TYPE; |
| ExtractRequestInfoDetails(request, &is_main_frame, &frame_id, &tab_id, |
| &window_id, &resource_type); |
| frame_id_for_extension = GetFrameId(is_main_frame, frame_id); |
| |
| out->SetString(keys::kRequestIdKey, |
| base::Uint64ToString(request->identifier())); |
| out->SetString(keys::kUrlKey, request->url().spec()); |
| out->SetString(keys::kMethodKey, request->method()); |
| out->SetInteger(keys::kFrameIdKey, frame_id_for_extension); |
| out->SetInteger(keys::kTabIdKey, tab_id); |
| out->SetString(keys::kTypeKey, ResourceTypeToString(resource_type)); |
| out->SetDouble(keys::kTimeStampKey, base::Time::Now().ToDoubleT() * 1000); |
| } |
| |
| // Creates a list of HttpHeaders (see extension_api.json). If |headers| is |
| // NULL, the list is empty. Ownership is passed to the caller. |
| ListValue* GetResponseHeadersList(const net::HttpResponseHeaders* headers) { |
| ListValue* headers_value = new ListValue(); |
| if (headers) { |
| void* iter = NULL; |
| std::string name; |
| std::string value; |
| while (headers->EnumerateHeaderLines(&iter, &name, &value)) { |
| DictionaryValue* header = new DictionaryValue(); |
| header->SetString(keys::kHeaderNameKey, name); |
| header->SetString(keys::kHeaderValueKey, value); |
| headers_value->Append(header); |
| } |
| } |
| return headers_value; |
| } |
| |
| ListValue* GetRequestHeadersList(const net::HttpRequestHeaders& headers) { |
| ListValue* headers_value = new ListValue(); |
| for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext(); ) { |
| DictionaryValue* header = new DictionaryValue(); |
| header->SetString(keys::kHeaderNameKey, it.name()); |
| header->SetString(keys::kHeaderValueKey, it.value()); |
| headers_value->Append(header); |
| } |
| return headers_value; |
| } |
| |
| // Creates a StringValue with the status line of |headers|. If |headers| is |
| // NULL, an empty string is returned. Ownership is passed to the caller. |
| StringValue* GetStatusLine(net::HttpResponseHeaders* headers) { |
| return new StringValue(headers ? headers->GetStatusLine() : ""); |
| } |
| |
| // Comparison operator that returns true if the extension that caused |
| // |a| was installed after the extension that caused |b|. |
| bool InDecreasingExtensionInstallationTimeOrder( |
| const linked_ptr<ExtensionWebRequestEventRouter::EventResponseDelta>& a, |
| const linked_ptr<ExtensionWebRequestEventRouter::EventResponseDelta>& b) { |
| return a->extension_install_time > b->extension_install_time; |
| } |
| |
| void NotifyWebRequestAPIUsed(void* profile_id, const Extension* extension) { |
| Profile* profile = reinterpret_cast<Profile*>(profile_id); |
| if (!g_browser_process->profile_manager()->IsValidProfile(profile)) |
| return; |
| |
| if (profile->GetExtensionService()->HasUsedWebRequest(extension)) |
| return; |
| profile->GetExtensionService()->SetHasUsedWebRequest(extension, true); |
| |
| content::BrowserContext* browser_context = profile; |
| for (RenderProcessHost::iterator it = RenderProcessHost::AllHostsIterator(); |
| !it.IsAtEnd(); it.Advance()) { |
| RenderProcessHost* host = it.GetCurrentValue(); |
| if (host->browser_context() == browser_context) |
| SendExtensionWebRequestStatusToHost(host); |
| } |
| } |
| |
| } // namespace |
| |
| // Represents a single unique listener to an event, along with whatever filter |
| // parameters and extra_info_spec were specified at the time the listener was |
| // added. |
| struct ExtensionWebRequestEventRouter::EventListener { |
| std::string extension_id; |
| std::string extension_name; |
| std::string sub_event_name; |
| RequestFilter filter; |
| int extra_info_spec; |
| base::WeakPtr<IPC::Message::Sender> ipc_sender; |
| mutable std::set<uint64> blocked_requests; |
| |
| // Comparator to work with std::set. |
| bool operator<(const EventListener& that) const { |
| if (extension_id < that.extension_id) |
| return true; |
| if (extension_id == that.extension_id && |
| sub_event_name < that.sub_event_name) |
| return true; |
| return false; |
| } |
| |
| EventListener() : extra_info_spec(0) {} |
| }; |
| |
| // Contains info about requests that are blocked waiting for a response from |
| // an extension. |
| struct ExtensionWebRequestEventRouter::BlockedRequest { |
| // The request that is being blocked. |
| net::URLRequest* request; |
| |
| // The event that we're currently blocked on. |
| EventTypes event; |
| |
| // The number of event handlers that we are awaiting a response from. |
| int num_handlers_blocking; |
| |
| // Pointer to NetLog to report significant changes to the request for |
| // debugging. |
| const net::BoundNetLog* net_log; |
| |
| // The callback to call when we get a response from all event handlers. |
| net::OldCompletionCallback* callback; |
| |
| // If non-empty, this contains the new URL that the request will redirect to. |
| // Only valid for OnBeforeRequest. |
| GURL* new_url; |
| |
| // The request headers that will be issued along with this request. Only valid |
| // for OnBeforeSendHeaders. |
| net::HttpRequestHeaders* request_headers; |
| |
| // The response headers that were received from the server. Only valid for |
| // OnHeadersReceived. |
| scoped_refptr<net::HttpResponseHeaders> original_response_headers; |
| |
| // Location where to override response headers. Only valid for |
| // OnHeadersReceived. |
| scoped_refptr<net::HttpResponseHeaders>* override_response_headers; |
| |
| // If non-empty, this contains the auth credentials that may be filled in. |
| // Only valid for OnAuthRequired. |
| net::AuthCredentials* auth_credentials; |
| |
| // The callback to invoke for auth. If |auth_callback.is_null()| is false, |
| // |callback| must be NULL. |
| // Only valid for OnAuthRequired. |
| net::NetworkDelegate::AuthCallback auth_callback; |
| |
| // Time the request was paused. Used for logging purposes. |
| base::Time blocking_time; |
| |
| // Changes requested by extensions. |
| EventResponseDeltas response_deltas; |
| |
| BlockedRequest() |
| : request(NULL), |
| event(kInvalidEvent), |
| num_handlers_blocking(0), |
| net_log(NULL), |
| callback(NULL), |
| new_url(NULL), |
| request_headers(NULL), |
| override_response_headers(NULL), |
| auth_credentials(NULL) {} |
| }; |
| |
| bool ExtensionWebRequestEventRouter::RequestFilter::InitFromValue( |
| const DictionaryValue& value, std::string* error) { |
| for (DictionaryValue::key_iterator key = value.begin_keys(); |
| key != value.end_keys(); ++key) { |
| if (*key == "urls") { |
| ListValue* urls_value = NULL; |
| if (!value.GetList("urls", &urls_value)) |
| return false; |
| for (size_t i = 0; i < urls_value->GetSize(); ++i) { |
| std::string url; |
| URLPattern pattern(URLPattern::SCHEME_ALL); |
| if (!urls_value->GetString(i, &url) || |
| pattern.Parse(url, URLPattern::ERROR_ON_PORTS) != |
| URLPattern::PARSE_SUCCESS) { |
| *error = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kInvalidRequestFilterUrl, url); |
| return false; |
| } |
| urls.AddPattern(pattern); |
| } |
| } else if (*key == "types") { |
| ListValue* types_value = NULL; |
| if (!value.GetList("types", &types_value)) |
| return false; |
| for (size_t i = 0; i < types_value->GetSize(); ++i) { |
| std::string type_str; |
| ResourceType::Type type; |
| if (!types_value->GetString(i, &type_str) || |
| !ParseResourceType(type_str, &type)) |
| return false; |
| types.push_back(type); |
| } |
| } else if (*key == "tabId") { |
| if (!value.GetInteger("tabId", &tab_id)) |
| return false; |
| } else if (*key == "windowId") { |
| if (!value.GetInteger("windowId", &window_id)) |
| return false; |
| } else { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // static |
| bool ExtensionWebRequestEventRouter::ExtraInfoSpec::InitFromValue( |
| const ListValue& value, int* extra_info_spec) { |
| *extra_info_spec = 0; |
| for (size_t i = 0; i < value.GetSize(); ++i) { |
| std::string str; |
| if (!value.GetString(i, &str)) |
| return false; |
| |
| if (str == "requestHeaders") |
| *extra_info_spec |= REQUEST_HEADERS; |
| else if (str == "responseHeaders") |
| *extra_info_spec |= RESPONSE_HEADERS; |
| else if (str == "blocking") |
| *extra_info_spec |= BLOCKING; |
| else if (str == "asyncBlocking") |
| *extra_info_spec |= ASYNC_BLOCKING; |
| else |
| return false; |
| |
| // BLOCKING and ASYNC_BLOCKING are mutually exclusive. |
| if ((*extra_info_spec & BLOCKING) && (*extra_info_spec & ASYNC_BLOCKING)) |
| return false; |
| } |
| return true; |
| } |
| |
| |
| ExtensionWebRequestEventRouter::EventResponse::EventResponse( |
| const std::string& extension_id, const base::Time& extension_install_time) |
| : extension_id(extension_id), |
| extension_install_time(extension_install_time), |
| cancel(false) { |
| } |
| |
| ExtensionWebRequestEventRouter::EventResponse::~EventResponse() { |
| } |
| |
| ExtensionWebRequestEventRouter::EventResponseDelta::EventResponseDelta( |
| const std::string& extension_id, const base::Time& extension_install_time) |
| : extension_id(extension_id), |
| extension_install_time(extension_install_time), |
| cancel(false) { |
| } |
| |
| ExtensionWebRequestEventRouter::EventResponseDelta::~EventResponseDelta() { |
| } |
| |
| |
| ExtensionWebRequestEventRouter::RequestFilter::RequestFilter() |
| : tab_id(-1), window_id(-1) { |
| } |
| |
| ExtensionWebRequestEventRouter::RequestFilter::~RequestFilter() { |
| } |
| |
| // |
| // ExtensionWebRequestEventRouter |
| // |
| |
| // static |
| ExtensionWebRequestEventRouter* ExtensionWebRequestEventRouter::GetInstance() { |
| return Singleton<ExtensionWebRequestEventRouter>::get(); |
| } |
| |
| ExtensionWebRequestEventRouter::ExtensionWebRequestEventRouter() |
| : request_time_tracker_(new ExtensionWebRequestTimeTracker) { |
| } |
| |
| ExtensionWebRequestEventRouter::~ExtensionWebRequestEventRouter() { |
| } |
| |
| int ExtensionWebRequestEventRouter::OnBeforeRequest( |
| void* profile, |
| ExtensionInfoMap* extension_info_map, |
| net::URLRequest* request, |
| net::OldCompletionCallback* callback, |
| GURL* new_url) { |
| // TODO(jochen): Figure out what to do with events from the system context. |
| if (!profile) |
| return net::OK; |
| |
| if (!HasWebRequestScheme(request->url())) |
| return net::OK; |
| |
| request_time_tracker_->LogRequestStartTime(request->identifier(), |
| base::Time::Now(), |
| request->url(), |
| profile); |
| |
| int extra_info_spec = 0; |
| std::vector<const EventListener*> listeners = |
| GetMatchingListeners(profile, extension_info_map, keys::kOnBeforeRequest, |
| request, &extra_info_spec); |
| if (listeners.empty()) |
| return net::OK; |
| |
| if (GetAndSetSignaled(request->identifier(), kOnBeforeRequest)) |
| return net::OK; |
| |
| ListValue args; |
| DictionaryValue* dict = new DictionaryValue(); |
| ExtractRequestInfo(request, dict); |
| args.Append(dict); |
| |
| if (DispatchEvent(profile, request, listeners, args)) { |
| blocked_requests_[request->identifier()].event = kOnBeforeRequest; |
| blocked_requests_[request->identifier()].callback = callback; |
| blocked_requests_[request->identifier()].new_url = new_url; |
| blocked_requests_[request->identifier()].net_log = &request->net_log(); |
| return net::ERR_IO_PENDING; |
| } |
| return net::OK; |
| } |
| |
| int ExtensionWebRequestEventRouter::OnBeforeSendHeaders( |
| void* profile, |
| ExtensionInfoMap* extension_info_map, |
| net::URLRequest* request, |
| net::OldCompletionCallback* callback, |
| net::HttpRequestHeaders* headers) { |
| // TODO(jochen): Figure out what to do with events from the system context. |
| if (!profile) |
| return net::OK; |
| |
| if (!HasWebRequestScheme(request->url())) |
| return net::OK; |
| |
| if (GetAndSetSignaled(request->identifier(), kOnBeforeSendHeaders)) |
| return net::OK; |
| |
| int extra_info_spec = 0; |
| std::vector<const EventListener*> listeners = |
| GetMatchingListeners(profile, extension_info_map, |
| keys::kOnBeforeSendHeaders, request, |
| &extra_info_spec); |
| if (listeners.empty()) |
| return net::OK; |
| |
| ListValue args; |
| DictionaryValue* dict = new DictionaryValue(); |
| ExtractRequestInfo(request, dict); |
| if (extra_info_spec & ExtraInfoSpec::REQUEST_HEADERS) |
| dict->Set(keys::kRequestHeadersKey, GetRequestHeadersList(*headers)); |
| args.Append(dict); |
| |
| if (DispatchEvent(profile, request, listeners, args)) { |
| blocked_requests_[request->identifier()].event = kOnBeforeSendHeaders; |
| blocked_requests_[request->identifier()].callback = callback; |
| blocked_requests_[request->identifier()].request_headers = headers; |
| blocked_requests_[request->identifier()].net_log = &request->net_log(); |
| return net::ERR_IO_PENDING; |
| } |
| return net::OK; |
| } |
| |
| void ExtensionWebRequestEventRouter::OnSendHeaders( |
| void* profile, |
| ExtensionInfoMap* extension_info_map, |
| net::URLRequest* request, |
| const net::HttpRequestHeaders& headers) { |
| if (!profile) |
| return; |
| |
| if (!HasWebRequestScheme(request->url())) |
| return; |
| |
| if (GetAndSetSignaled(request->identifier(), kOnSendHeaders)) |
| return; |
| |
| ClearSignaled(request->identifier(), kOnBeforeRedirect); |
| |
| int extra_info_spec = 0; |
| std::vector<const EventListener*> listeners = |
| GetMatchingListeners(profile, extension_info_map, |
| keys::kOnSendHeaders, request, &extra_info_spec); |
| if (listeners.empty()) |
| return; |
| |
| ListValue args; |
| DictionaryValue* dict = new DictionaryValue(); |
| ExtractRequestInfo(request, dict); |
| if (extra_info_spec & ExtraInfoSpec::REQUEST_HEADERS) |
| dict->Set(keys::kRequestHeadersKey, GetRequestHeadersList(headers)); |
| args.Append(dict); |
| |
| DispatchEvent(profile, request, listeners, args); |
| } |
| |
| int ExtensionWebRequestEventRouter::OnHeadersReceived( |
| void* profile, |
| ExtensionInfoMap* extension_info_map, |
| net::URLRequest* request, |
| net::OldCompletionCallback* callback, |
| net::HttpResponseHeaders* original_response_headers, |
| scoped_refptr<net::HttpResponseHeaders>* override_response_headers) { |
| if (!profile) |
| return net::OK; |
| |
| if (!HasWebRequestScheme(request->url())) |
| return net::OK; |
| |
| int extra_info_spec = 0; |
| std::vector<const EventListener*> listeners = |
| GetMatchingListeners(profile, extension_info_map, |
| keys::kOnHeadersReceived, request, |
| &extra_info_spec); |
| |
| if (listeners.empty()) |
| return net::OK; |
| |
| if (GetAndSetSignaled(request->identifier(), kOnHeadersReceived)) |
| return net::OK; |
| |
| ListValue args; |
| DictionaryValue* dict = new DictionaryValue(); |
| ExtractRequestInfo(request, dict); |
| dict->SetString(keys::kStatusLineKey, |
| original_response_headers->GetStatusLine()); |
| if (extra_info_spec & ExtraInfoSpec::RESPONSE_HEADERS) { |
| dict->Set(keys::kResponseHeadersKey, |
| GetResponseHeadersList(original_response_headers)); |
| } |
| args.Append(dict); |
| |
| if (DispatchEvent(profile, request, listeners, args)) { |
| blocked_requests_[request->identifier()].event = kOnHeadersReceived; |
| blocked_requests_[request->identifier()].callback = callback; |
| blocked_requests_[request->identifier()].net_log = &request->net_log(); |
| blocked_requests_[request->identifier()].override_response_headers = |
| override_response_headers; |
| blocked_requests_[request->identifier()].original_response_headers = |
| original_response_headers; |
| return net::ERR_IO_PENDING; |
| } |
| return net::OK; |
| } |
| |
| net::NetworkDelegate::AuthRequiredResponse |
| ExtensionWebRequestEventRouter::OnAuthRequired( |
| void* profile, |
| ExtensionInfoMap* extension_info_map, |
| net::URLRequest* request, |
| const net::AuthChallengeInfo& auth_info, |
| const net::NetworkDelegate::AuthCallback& callback, |
| net::AuthCredentials* credentials) { |
| // No profile means that this is for authentication challenges in the |
| // system context. Skip in that case. |
| if (!profile) |
| return net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION; |
| |
| if (!HasWebRequestScheme(request->url())) |
| return net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION; |
| |
| int extra_info_spec = 0; |
| std::vector<const EventListener*> listeners = |
| GetMatchingListeners(profile, extension_info_map, |
| keys::kOnAuthRequired, request, &extra_info_spec); |
| if (listeners.empty()) |
| return net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION; |
| |
| ListValue args; |
| DictionaryValue* dict = new DictionaryValue(); |
| ExtractRequestInfo(request, dict); |
| dict->SetBoolean(keys::kIsProxyKey, auth_info.is_proxy); |
| if (!auth_info.scheme.empty()) |
| dict->SetString(keys::kSchemeKey, auth_info.scheme); |
| if (!auth_info.realm.empty()) |
| dict->SetString(keys::kRealmKey, auth_info.realm); |
| DictionaryValue* challenger = new DictionaryValue(); |
| challenger->SetString(keys::kHostKey, auth_info.challenger.host()); |
| challenger->SetInteger(keys::kPortKey, auth_info.challenger.port()); |
| dict->Set(keys::kChallengerKey, challenger); |
| dict->Set(keys::kStatusLineKey, GetStatusLine(request->response_headers())); |
| if (extra_info_spec & ExtraInfoSpec::RESPONSE_HEADERS) { |
| dict->Set(keys::kResponseHeadersKey, |
| GetResponseHeadersList(request->response_headers())); |
| } |
| args.Append(dict); |
| |
| if (DispatchEvent(profile, request, listeners, args)) { |
| blocked_requests_[request->identifier()].event = kOnAuthRequired; |
| blocked_requests_[request->identifier()].auth_callback = callback; |
| blocked_requests_[request->identifier()].auth_credentials = credentials; |
| blocked_requests_[request->identifier()].net_log = &request->net_log(); |
| return net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_IO_PENDING; |
| } |
| return net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION; |
| } |
| |
| void ExtensionWebRequestEventRouter::OnBeforeRedirect( |
| void* profile, |
| ExtensionInfoMap* extension_info_map, |
| net::URLRequest* request, |
| const GURL& new_location) { |
| if (!profile) |
| return; |
| |
| if (!HasWebRequestScheme(request->url())) |
| return; |
| |
| if (GetAndSetSignaled(request->identifier(), kOnBeforeRedirect)) |
| return; |
| |
| ClearSignaled(request->identifier(), kOnBeforeRequest); |
| ClearSignaled(request->identifier(), kOnBeforeSendHeaders); |
| ClearSignaled(request->identifier(), kOnSendHeaders); |
| ClearSignaled(request->identifier(), kOnHeadersReceived); |
| |
| int extra_info_spec = 0; |
| std::vector<const EventListener*> listeners = |
| GetMatchingListeners(profile, extension_info_map, |
| keys::kOnBeforeRedirect, request, &extra_info_spec); |
| if (listeners.empty()) |
| return; |
| |
| int http_status_code = request->GetResponseCode(); |
| |
| std::string response_ip = request->GetSocketAddress().host(); |
| |
| ListValue args; |
| DictionaryValue* dict = new DictionaryValue(); |
| ExtractRequestInfo(request, dict); |
| dict->SetString(keys::kRedirectUrlKey, new_location.spec()); |
| dict->SetInteger(keys::kStatusCodeKey, http_status_code); |
| if (!response_ip.empty()) |
| dict->SetString(keys::kIpKey, response_ip); |
| dict->SetBoolean(keys::kFromCache, request->was_cached()); |
| dict->Set(keys::kStatusLineKey, GetStatusLine(request->response_headers())); |
| if (extra_info_spec & ExtraInfoSpec::RESPONSE_HEADERS) { |
| dict->Set(keys::kResponseHeadersKey, |
| GetResponseHeadersList(request->response_headers())); |
| } |
| args.Append(dict); |
| |
| DispatchEvent(profile, request, listeners, args); |
| } |
| |
| void ExtensionWebRequestEventRouter::OnResponseStarted( |
| void* profile, |
| ExtensionInfoMap* extension_info_map, |
| net::URLRequest* request) { |
| if (!profile) |
| return; |
| |
| if (!HasWebRequestScheme(request->url())) |
| return; |
| |
| // OnResponseStarted is even triggered, when the request was cancelled. |
| if (request->status().status() != net::URLRequestStatus::SUCCESS) |
| return; |
| |
| int extra_info_spec = 0; |
| std::vector<const EventListener*> listeners = |
| GetMatchingListeners(profile, extension_info_map, |
| keys::kOnResponseStarted, request, &extra_info_spec); |
| if (listeners.empty()) |
| return; |
| |
| // UrlRequestFileJobs do not send headers, so we simulate their behavior. |
| int response_code = 200; |
| if (request->response_headers()) |
| response_code = request->response_headers()->response_code(); |
| |
| std::string response_ip = request->GetSocketAddress().host(); |
| |
| ListValue args; |
| DictionaryValue* dict = new DictionaryValue(); |
| ExtractRequestInfo(request, dict); |
| if (!response_ip.empty()) |
| dict->SetString(keys::kIpKey, response_ip); |
| dict->SetBoolean(keys::kFromCache, request->was_cached()); |
| dict->SetInteger(keys::kStatusCodeKey, response_code); |
| dict->Set(keys::kStatusLineKey, GetStatusLine(request->response_headers())); |
| if (extra_info_spec & ExtraInfoSpec::RESPONSE_HEADERS) { |
| dict->Set(keys::kResponseHeadersKey, |
| GetResponseHeadersList(request->response_headers())); |
| } |
| args.Append(dict); |
| |
| DispatchEvent(profile, request, listeners, args); |
| } |
| |
| void ExtensionWebRequestEventRouter::OnCompleted( |
| void* profile, |
| ExtensionInfoMap* extension_info_map, |
| net::URLRequest* request) { |
| if (!profile) |
| return; |
| |
| if (!HasWebRequestScheme(request->url())) |
| return; |
| |
| request_time_tracker_->LogRequestEndTime(request->identifier(), |
| base::Time::Now()); |
| |
| DCHECK(request->status().status() == net::URLRequestStatus::SUCCESS); |
| |
| DCHECK(!GetAndSetSignaled(request->identifier(), kOnCompleted)); |
| |
| int extra_info_spec = 0; |
| std::vector<const EventListener*> listeners = |
| GetMatchingListeners(profile, extension_info_map, |
| keys::kOnCompleted, request, &extra_info_spec); |
| if (listeners.empty()) |
| return; |
| |
| // UrlRequestFileJobs do not send headers, so we simulate their behavior. |
| int response_code = 200; |
| if (request->response_headers()) |
| response_code = request->response_headers()->response_code(); |
| |
| std::string response_ip = request->GetSocketAddress().host(); |
| |
| ListValue args; |
| DictionaryValue* dict = new DictionaryValue(); |
| ExtractRequestInfo(request, dict); |
| dict->SetInteger(keys::kStatusCodeKey, response_code); |
| if (!response_ip.empty()) |
| dict->SetString(keys::kIpKey, response_ip); |
| dict->SetBoolean(keys::kFromCache, request->was_cached()); |
| dict->Set(keys::kStatusLineKey, GetStatusLine(request->response_headers())); |
| if (extra_info_spec & ExtraInfoSpec::RESPONSE_HEADERS) { |
| dict->Set(keys::kResponseHeadersKey, |
| GetResponseHeadersList(request->response_headers())); |
| } |
| args.Append(dict); |
| |
| DispatchEvent(profile, request, listeners, args); |
| } |
| |
| void ExtensionWebRequestEventRouter::OnErrorOccurred( |
| void* profile, |
| ExtensionInfoMap* extension_info_map, |
| net::URLRequest* request) { |
| if (!profile) |
| return; |
| |
| if (!HasWebRequestScheme(request->url())) |
| return; |
| |
| request_time_tracker_->LogRequestEndTime(request->identifier(), |
| base::Time::Now()); |
| |
| DCHECK(request->status().status() == net::URLRequestStatus::FAILED || |
| request->status().status() == net::URLRequestStatus::CANCELED); |
| |
| DCHECK(!GetAndSetSignaled(request->identifier(), kOnErrorOccurred)); |
| |
| int extra_info_spec = 0; |
| std::vector<const EventListener*> listeners = |
| GetMatchingListeners(profile, extension_info_map, |
| keys::kOnErrorOccurred, request, &extra_info_spec); |
| if (listeners.empty()) |
| return; |
| |
| std::string response_ip = request->GetSocketAddress().host(); |
| |
| ListValue args; |
| DictionaryValue* dict = new DictionaryValue(); |
| ExtractRequestInfo(request, dict); |
| if (!response_ip.empty()) |
| dict->SetString(keys::kIpKey, response_ip); |
| dict->SetBoolean(keys::kFromCache, request->was_cached()); |
| dict->SetString(keys::kErrorKey, |
| net::ErrorToString(request->status().error())); |
| args.Append(dict); |
| |
| DispatchEvent(profile, request, listeners, args); |
| } |
| |
| void ExtensionWebRequestEventRouter::OnURLRequestDestroyed( |
| void* profile, net::URLRequest* request) { |
| blocked_requests_.erase(request->identifier()); |
| signaled_requests_.erase(request->identifier()); |
| |
| request_time_tracker_->LogRequestEndTime(request->identifier(), |
| base::Time::Now()); |
| } |
| |
| bool ExtensionWebRequestEventRouter::DispatchEvent( |
| void* profile, |
| net::URLRequest* request, |
| const std::vector<const EventListener*>& listeners, |
| const ListValue& args) { |
| std::string json_args; |
| |
| // TODO(mpcomplete): Consider consolidating common (extension_id,json_args) |
| // pairs into a single message sent to a list of sub_event_names. |
| int num_handlers_blocking = 0; |
| for (std::vector<const EventListener*>::const_iterator it = listeners.begin(); |
| it != listeners.end(); ++it) { |
| // Filter out the optional keys that this listener didn't request. |
| scoped_ptr<ListValue> args_filtered(args.DeepCopy()); |
| DictionaryValue* dict = NULL; |
| CHECK(args_filtered->GetDictionary(0, &dict) && dict); |
| if (!((*it)->extra_info_spec & ExtraInfoSpec::REQUEST_HEADERS)) |
| dict->Remove(keys::kRequestHeadersKey, NULL); |
| if (!((*it)->extra_info_spec & ExtraInfoSpec::RESPONSE_HEADERS)) |
| dict->Remove(keys::kResponseHeadersKey, NULL); |
| |
| base::JSONWriter::Write(args_filtered.get(), false, &json_args); |
| |
| ExtensionEventRouter::DispatchEvent( |
| (*it)->ipc_sender.get(), (*it)->extension_id, (*it)->sub_event_name, |
| json_args, GURL()); |
| if ((*it)->extra_info_spec & |
| (ExtraInfoSpec::BLOCKING | ExtraInfoSpec::ASYNC_BLOCKING)) { |
| (*it)->blocked_requests.insert(request->identifier()); |
| ++num_handlers_blocking; |
| |
| request->SetLoadStateParam( |
| l10n_util::GetStringFUTF16(IDS_LOAD_STATE_PARAMETER_EXTENSION, |
| UTF8ToUTF16((*it)->extension_name))); |
| } |
| } |
| |
| if (num_handlers_blocking > 0) { |
| CHECK(blocked_requests_.find(request->identifier()) == |
| blocked_requests_.end()); |
| blocked_requests_[request->identifier()].request = request; |
| blocked_requests_[request->identifier()].num_handlers_blocking = |
| num_handlers_blocking; |
| blocked_requests_[request->identifier()].blocking_time = base::Time::Now(); |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void ExtensionWebRequestEventRouter::OnEventHandled( |
| void* profile, |
| const std::string& extension_id, |
| const std::string& event_name, |
| const std::string& sub_event_name, |
| uint64 request_id, |
| EventResponse* response) { |
| EventListener listener; |
| listener.extension_id = extension_id; |
| listener.sub_event_name = sub_event_name; |
| |
| // The listener may have been removed (e.g. due to the process going away) |
| // before we got here. |
| std::set<EventListener>::iterator found = |
| listeners_[profile][event_name].find(listener); |
| if (found != listeners_[profile][event_name].end()) |
| found->blocked_requests.erase(request_id); |
| |
| DecrementBlockCount(profile, extension_id, event_name, request_id, response); |
| } |
| |
| void ExtensionWebRequestEventRouter::AddEventListener( |
| void* profile, |
| const std::string& extension_id, |
| const std::string& extension_name, |
| const std::string& event_name, |
| const std::string& sub_event_name, |
| const RequestFilter& filter, |
| int extra_info_spec, |
| base::WeakPtr<IPC::Message::Sender> ipc_sender) { |
| if (!IsWebRequestEvent(event_name)) |
| return; |
| |
| EventListener listener; |
| listener.extension_id = extension_id; |
| listener.extension_name = extension_name; |
| listener.sub_event_name = sub_event_name; |
| listener.filter = filter; |
| listener.extra_info_spec = extra_info_spec; |
| listener.ipc_sender = ipc_sender; |
| |
| CHECK_EQ(listeners_[profile][event_name].count(listener), 0u) << |
| "extension=" << extension_id << " event=" << event_name; |
| listeners_[profile][event_name].insert(listener); |
| } |
| |
| void ExtensionWebRequestEventRouter::RemoveEventListener( |
| void* profile, |
| const std::string& extension_id, |
| const std::string& sub_event_name) { |
| size_t slash_sep = sub_event_name.find('/'); |
| std::string event_name = sub_event_name.substr(0, slash_sep); |
| |
| if (!IsWebRequestEvent(event_name)) |
| return; |
| |
| EventListener listener; |
| listener.extension_id = extension_id; |
| listener.sub_event_name = sub_event_name; |
| |
| // It's possible for AddEventListener to fail asynchronously. In that case, |
| // the renderer believes the listener exists, while the browser does not. |
| // Ignore a RemoveEventListener in that case. |
| std::set<EventListener>::iterator found = |
| listeners_[profile][event_name].find(listener); |
| if (found == listeners_[profile][event_name].end()) |
| return; |
| |
| CHECK_EQ(listeners_[profile][event_name].count(listener), 1u) << |
| "extension=" << extension_id << " event=" << event_name; |
| |
| // Unblock any request that this event listener may have been blocking. |
| for (std::set<uint64>::iterator it = found->blocked_requests.begin(); |
| it != found->blocked_requests.end(); ++it) { |
| DecrementBlockCount(profile, extension_id, event_name, *it, NULL); |
| } |
| |
| listeners_[profile][event_name].erase(listener); |
| } |
| |
| void ExtensionWebRequestEventRouter::OnOTRProfileCreated( |
| void* original_profile, void* otr_profile) { |
| cross_profile_map_[original_profile] = otr_profile; |
| cross_profile_map_[otr_profile] = original_profile; |
| } |
| |
| void ExtensionWebRequestEventRouter::OnOTRProfileDestroyed( |
| void* original_profile, void* otr_profile) { |
| cross_profile_map_.erase(otr_profile); |
| cross_profile_map_.erase(original_profile); |
| } |
| |
| void ExtensionWebRequestEventRouter::GetMatchingListenersImpl( |
| void* profile, |
| ExtensionInfoMap* extension_info_map, |
| bool crosses_incognito, |
| const std::string& event_name, |
| const GURL& url, |
| int tab_id, |
| int window_id, |
| ResourceType::Type resource_type, |
| int* extra_info_spec, |
| std::vector<const ExtensionWebRequestEventRouter::EventListener*>* |
| matching_listeners) { |
| std::set<EventListener>& listeners = listeners_[profile][event_name]; |
| for (std::set<EventListener>::iterator it = listeners.begin(); |
| it != listeners.end(); ++it) { |
| if (!it->ipc_sender.get()) { |
| // The IPC sender has been deleted. This listener will be removed soon |
| // via a call to RemoveEventListener. For now, just skip it. |
| continue; |
| } |
| |
| if (!it->filter.urls.is_empty() && !it->filter.urls.MatchesURL(url)) |
| continue; |
| if (it->filter.tab_id != -1 && tab_id != it->filter.tab_id) |
| continue; |
| if (it->filter.window_id != -1 && window_id != it->filter.window_id) |
| continue; |
| if (!it->filter.types.empty() && |
| std::find(it->filter.types.begin(), it->filter.types.end(), |
| resource_type) == it->filter.types.end()) |
| continue; |
| |
| // extension_info_map can be NULL if this is a system-level request. |
| if (extension_info_map) { |
| const Extension* extension = |
| extension_info_map->extensions().GetByID(it->extension_id); |
| |
| // Check if this event crosses incognito boundaries when it shouldn't. |
| if (!extension || |
| (crosses_incognito && |
| !extension_info_map->CanCrossIncognito(extension))) |
| continue; |
| |
| // Only send webRequest events for URLs the extension has access to. |
| if (!CanExtensionAccessURL(extension, url)) |
| continue; |
| } |
| |
| matching_listeners->push_back(&(*it)); |
| *extra_info_spec |= it->extra_info_spec; |
| } |
| } |
| |
| std::vector<const ExtensionWebRequestEventRouter::EventListener*> |
| ExtensionWebRequestEventRouter::GetMatchingListeners( |
| void* profile, |
| ExtensionInfoMap* extension_info_map, |
| const std::string& event_name, |
| net::URLRequest* request, |
| int* extra_info_spec) { |
| // TODO(mpcomplete): handle profile == NULL (should collect all listeners). |
| *extra_info_spec = 0; |
| |
| bool is_main_frame = false; |
| int64 frame_id = -1; |
| int tab_id = -1; |
| int window_id = -1; |
| ResourceType::Type resource_type = ResourceType::LAST_TYPE; |
| const GURL& url = request->url(); |
| |
| ExtractRequestInfoDetails(request, &is_main_frame, &frame_id, &tab_id, |
| &window_id, &resource_type); |
| |
| std::vector<const ExtensionWebRequestEventRouter::EventListener*> |
| matching_listeners; |
| |
| GetMatchingListenersImpl( |
| profile, extension_info_map, false, event_name, url, |
| tab_id, window_id, resource_type, extra_info_spec, &matching_listeners); |
| CrossProfileMap::const_iterator cross_profile = |
| cross_profile_map_.find(profile); |
| if (cross_profile != cross_profile_map_.end()) { |
| GetMatchingListenersImpl( |
| cross_profile->second, extension_info_map, true, event_name, url, |
| tab_id, window_id, resource_type, extra_info_spec, &matching_listeners); |
| } |
| |
| return matching_listeners; |
| } |
| |
| linked_ptr<ExtensionWebRequestEventRouter::EventResponseDelta> |
| ExtensionWebRequestEventRouter::CalculateDelta( |
| BlockedRequest* blocked_request, |
| EventResponse* response) const { |
| linked_ptr<EventResponseDelta> result( |
| new EventResponseDelta(response->extension_id, |
| response->extension_install_time)); |
| |
| result->cancel = response->cancel; |
| |
| if (blocked_request->event == kOnBeforeRequest) |
| result->new_url = response->new_url; |
| |
| if (blocked_request->event == kOnBeforeSendHeaders) { |
| net::HttpRequestHeaders* old_headers = blocked_request->request_headers; |
| net::HttpRequestHeaders* new_headers = response->request_headers.get(); |
| |
| // Find deleted headers. |
| { |
| net::HttpRequestHeaders::Iterator i(*old_headers); |
| while (i.GetNext()) { |
| if (!new_headers->HasHeader(i.name())) { |
| result->deleted_request_headers.push_back(i.name()); |
| } |
| } |
| } |
| |
| // Find modified headers. |
| { |
| net::HttpRequestHeaders::Iterator i(*new_headers); |
| while (i.GetNext()) { |
| std::string value; |
| if (!old_headers->GetHeader(i.name(), &value) || i.value() != value) { |
| result->modified_request_headers.SetHeader(i.name(), i.value()); |
| } |
| } |
| } |
| } |
| |
| if (blocked_request->event == kOnHeadersReceived) { |
| net::HttpResponseHeaders* old_headers = |
| blocked_request->original_response_headers.get(); |
| if (!response->response_headers_string.empty()) { |
| std::string new_headers_string = |
| old_headers->GetStatusLine() + "\n" + |
| response->response_headers_string; |
| |
| result->new_response_headers = |
| new net::HttpResponseHeaders( |
| net::HttpUtil::AssembleRawHeaders(new_headers_string.c_str(), |
| new_headers_string.length())); |
| } |
| } |
| |
| if (blocked_request->event == kOnAuthRequired) |
| result->auth_credentials.swap(response->auth_credentials); |
| |
| return result; |
| } |
| |
| void ExtensionWebRequestEventRouter::MergeOnBeforeRequestResponses( |
| BlockedRequest* request, |
| std::set<std::string>* conflicting_extensions) const { |
| EventResponseDeltas::iterator delta; |
| EventResponseDeltas& deltas = request->response_deltas; |
| |
| bool redirected = false; |
| for (delta = deltas.begin(); delta != deltas.end(); ++delta) { |
| if (!(*delta)->new_url.is_empty()) { |
| if (!redirected) { |
| *request->new_url = (*delta)->new_url; |
| redirected = true; |
| request->net_log->AddEvent( |
| net::NetLog::TYPE_CHROME_EXTENSION_REDIRECTED_REQUEST, |
| make_scoped_refptr( |
| new NetLogExtensionIdParameter((*delta)->extension_id))); |
| } else { |
| conflicting_extensions->insert((*delta)->extension_id); |
| request->net_log->AddEvent( |
| net::NetLog::TYPE_CHROME_EXTENSION_IGNORED_DUE_TO_CONFLICT, |
| make_scoped_refptr( |
| new NetLogExtensionIdParameter((*delta)->extension_id))); |
| } |
| } |
| } |
| } |
| |
| void ExtensionWebRequestEventRouter::MergeOnBeforeSendHeadersResponses( |
| BlockedRequest* request, |
| std::set<std::string>* conflicting_extensions) const { |
| EventResponseDeltas::iterator delta; |
| EventResponseDeltas& deltas = request->response_deltas; |
| |
| // Here we collect which headers we have removed or set to new values |
| // so far due to extensions of higher precedence. |
| std::set<std::string> removed_headers; |
| std::set<std::string> set_headers; |
| |
| // We assume here that the deltas are sorted in decreasing extension |
| // precedence (i.e. decreasing extension installation time). |
| for (delta = deltas.begin(); delta != deltas.end(); ++delta) { |
| if ((*delta)->modified_request_headers.IsEmpty() && |
| (*delta)->deleted_request_headers.empty()) { |
| continue; |
| } |
| |
| scoped_refptr<NetLogModificationParameter> log( |
| new NetLogModificationParameter((*delta)->extension_id)); |
| |
| // Check whether any modification affects a request header that |
| // has been modified differently before. As deltas is sorted by decreasing |
| // extension installation order, this takes care of precedence. |
| bool extension_conflicts = false; |
| { |
| net::HttpRequestHeaders::Iterator modification( |
| (*delta)->modified_request_headers); |
| while (modification.GetNext() && !extension_conflicts) { |
| // This modification sets |key| to |value|. |
| const std::string& key = modification.name(); |
| const std::string& value = modification.value(); |
| |
| // We must not delete anything that has been modified before. |
| if (removed_headers.find(key) != removed_headers.end()) |
| extension_conflicts = true; |
| |
| // We must not modify anything that has been set to a *different* |
| // value before. |
| if (set_headers.find(key) != set_headers.end()) { |
| std::string current_value; |
| if (!request->request_headers->GetHeader(key, ¤t_value) || |
| current_value != value) { |
| extension_conflicts = true; |
| } |
| } |
| } |
| } |
| |
| // Check whether any deletion affects a request header that has been |
| // modified before. |
| { |
| std::vector<std::string>::iterator key; |
| for (key = (*delta)->deleted_request_headers.begin(); |
| key != (*delta)->deleted_request_headers.end() && |
| !extension_conflicts; |
| ++key) { |
| if (set_headers.find(*key) != set_headers.end()) |
| extension_conflicts = true; |
| } |
| } |
| |
| // Now execute the modifications if there were no conflicts. |
| if (!extension_conflicts) { |
| // Copy all modifications into the original headers. |
| request->request_headers->MergeFrom((*delta)->modified_request_headers); |
| { |
| // Record which keys were changed. |
| net::HttpRequestHeaders::Iterator modification( |
| (*delta)->modified_request_headers); |
| while (modification.GetNext()) { |
| set_headers.insert(modification.name()); |
| log->ModifiedHeader(modification.name(), modification.value()); |
| } |
| } |
| |
| // Perform all deletions and record which keys were deleted. |
| { |
| std::vector<std::string>::iterator key; |
| for (key = (*delta)->deleted_request_headers.begin(); |
| key != (*delta)->deleted_request_headers.end(); |
| ++key) { |
| request->request_headers->RemoveHeader(*key); |
| removed_headers.insert(*key); |
| log->DeletedHeader(*key); |
| } |
| } |
| request->net_log->AddEvent( |
| net::NetLog::TYPE_CHROME_EXTENSION_MODIFIED_HEADERS, log); |
| } else { |
| conflicting_extensions->insert((*delta)->extension_id); |
| request->net_log->AddEvent( |
| net::NetLog::TYPE_CHROME_EXTENSION_IGNORED_DUE_TO_CONFLICT, |
| make_scoped_refptr( |
| new NetLogExtensionIdParameter((*delta)->extension_id))); |
| } |
| } |
| } |
| |
| void ExtensionWebRequestEventRouter::MergeOnHeadersReceivedResponses( |
| BlockedRequest* request, |
| std::set<std::string>* conflicting_extensions) const { |
| EventResponseDeltas::iterator delta; |
| EventResponseDeltas& deltas = request->response_deltas; |
| |
| // Whether any extension has overridden the response headers, yet. |
| bool headers_overridden = false; |
| |
| // We assume here that the deltas are sorted in decreasing extension |
| // precedence (i.e. decreasing extension installation time). |
| for (delta = deltas.begin(); delta != deltas.end(); ++delta) { |
| if (!(*delta)->new_response_headers.get()) |
| continue; |
| |
| scoped_refptr<NetLogModificationParameter> log( |
| new NetLogModificationParameter((*delta)->extension_id)); |
| |
| // Conflict if a second extension returned new response headers; |
| bool extension_conflicts = headers_overridden; |
| |
| if (!extension_conflicts) { |
| headers_overridden = true; |
| *request->override_response_headers = (*delta)->new_response_headers; |
| request->net_log->AddEvent( |
| net::NetLog::TYPE_CHROME_EXTENSION_MODIFIED_HEADERS, log); |
| } else { |
| conflicting_extensions->insert((*delta)->extension_id); |
| request->net_log->AddEvent( |
| net::NetLog::TYPE_CHROME_EXTENSION_IGNORED_DUE_TO_CONFLICT, |
| make_scoped_refptr( |
| new NetLogExtensionIdParameter((*delta)->extension_id))); |
| } |
| } |
| } |
| |
| bool ExtensionWebRequestEventRouter::MergeOnAuthRequiredResponses( |
| BlockedRequest* request, |
| std::set<std::string>* conflicting_extensions) const { |
| CHECK(request); |
| CHECK(request->auth_credentials); |
| bool credentials_set = false; |
| |
| const EventResponseDeltas& deltas = request->response_deltas; |
| for (EventResponseDeltas::const_iterator delta = deltas.begin(); |
| delta != deltas.end(); |
| ++delta) { |
| if (!(*delta)->auth_credentials.get()) |
| continue; |
| if (credentials_set) { |
| conflicting_extensions->insert((*delta)->extension_id); |
| request->net_log->AddEvent( |
| net::NetLog::TYPE_CHROME_EXTENSION_IGNORED_DUE_TO_CONFLICT, |
| make_scoped_refptr( |
| new NetLogExtensionIdParameter((*delta)->extension_id))); |
| } else { |
| request->net_log->AddEvent( |
| net::NetLog::TYPE_CHROME_EXTENSION_PROVIDE_AUTH_CREDENTIALS, |
| make_scoped_refptr( |
| new NetLogExtensionIdParameter((*delta)->extension_id))); |
| *request->auth_credentials = *(*delta)->auth_credentials; |
| credentials_set = true; |
| } |
| } |
| return credentials_set; |
| } |
| |
| void ExtensionWebRequestEventRouter::DecrementBlockCount( |
| void* profile, |
| const std::string& extension_id, |
| const std::string& event_name, |
| uint64 request_id, |
| EventResponse* response) { |
| scoped_ptr<EventResponse> response_scoped(response); |
| |
| // It's possible that this request was deleted, or cancelled by a previous |
| // event handler. If so, ignore this response. |
| if (blocked_requests_.find(request_id) == blocked_requests_.end()) |
| return; |
| |
| BlockedRequest& blocked_request = blocked_requests_[request_id]; |
| int num_handlers_blocking = --blocked_request.num_handlers_blocking; |
| CHECK_GE(num_handlers_blocking, 0); |
| |
| if (response) { |
| blocked_request.response_deltas.push_back( |
| CalculateDelta(&blocked_request, response)); |
| } |
| |
| base::TimeDelta block_time = |
| base::Time::Now() - blocked_request.blocking_time; |
| request_time_tracker_->IncrementExtensionBlockTime( |
| extension_id, request_id, block_time); |
| |
| if (num_handlers_blocking == 0) { |
| request_time_tracker_->IncrementTotalBlockTime(request_id, block_time); |
| |
| EventResponseDeltas& deltas = blocked_request.response_deltas; |
| bool canceled = false; |
| bool credentials_set = false; |
| for (EventResponseDeltas::const_iterator i = deltas.begin(); |
| i != deltas.end(); ++i) { |
| if ((*i)->cancel) { |
| canceled = true; |
| blocked_request.net_log->AddEvent( |
| net::NetLog::TYPE_CHROME_EXTENSION_ABORTED_REQUEST, |
| make_scoped_refptr( |
| new NetLogExtensionIdParameter((*i)->extension_id))); |
| break; |
| } |
| } |
| |
| deltas.sort(&InDecreasingExtensionInstallationTimeOrder); |
| std::set<std::string> conflicting_extensions; |
| |
| if (blocked_request.event == kOnBeforeRequest) { |
| CHECK(blocked_request.callback); |
| MergeOnBeforeRequestResponses(&blocked_request, |
| &conflicting_extensions); |
| } else if (blocked_request.event == kOnBeforeSendHeaders) { |
| CHECK(blocked_request.callback); |
| MergeOnBeforeSendHeadersResponses(&blocked_request, |
| &conflicting_extensions); |
| } else if (blocked_request.event == kOnHeadersReceived) { |
| CHECK(blocked_request.callback); |
| MergeOnHeadersReceivedResponses(&blocked_request, |
| &conflicting_extensions); |
| } else if (blocked_request.event == kOnAuthRequired) { |
| CHECK(!blocked_request.callback); |
| CHECK(!blocked_request.auth_callback.is_null()); |
| credentials_set = MergeOnAuthRequiredResponses( |
| &blocked_request, |
| &conflicting_extensions); |
| } else { |
| NOTREACHED(); |
| } |
| if (!conflicting_extensions.empty()) { |
| BrowserThread::PostTask( |
| BrowserThread::UI, |
| FROM_HERE, |
| base::Bind(&ExtensionWarningSet::NotifyWarningsOnUI, |
| profile, |
| conflicting_extensions, |
| ExtensionWarningSet::kNetworkConflict)); |
| } |
| |
| if (canceled) { |
| request_time_tracker_->SetRequestCanceled(request_id); |
| } else if (blocked_request.new_url && |
| !blocked_request.new_url->is_empty()) { |
| request_time_tracker_->SetRequestRedirected(request_id); |
| } |
| |
| if (blocked_request.callback) { |
| // This triggers onErrorOccurred. |
| int rv = canceled ? net::ERR_BLOCKED_BY_CLIENT : net::OK; |
| net::OldCompletionCallback* callback = blocked_request.callback; |
| // Ensure that request is removed before callback because the callback |
| // might trigger the next event. |
| blocked_requests_.erase(request_id); |
| callback->Run(rv); |
| } else if (!blocked_request.auth_callback.is_null()) { |
| net::NetworkDelegate::AuthRequiredResponse response = |
| net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION; |
| if (canceled) { |
| response = net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_CANCEL_AUTH; |
| } else if (credentials_set) { |
| response = net::NetworkDelegate::AUTH_REQUIRED_RESPONSE_SET_AUTH; |
| } |
| net::NetworkDelegate::AuthCallback callback = |
| blocked_request.auth_callback; |
| blocked_requests_.erase(request_id); |
| callback.Run(response); |
| } else { |
| blocked_requests_.erase(request_id); |
| } |
| } else { |
| // Update the URLRequest to indicate it is now blocked on a different |
| // extension. |
| std::set<EventListener>& listeners = listeners_[profile][event_name]; |
| |
| for (std::set<EventListener>::iterator it = listeners.begin(); |
| it != listeners.end(); ++it) { |
| if (it->blocked_requests.count(request_id)) { |
| blocked_request.request->SetLoadStateParam( |
| l10n_util::GetStringFUTF16(IDS_LOAD_STATE_PARAMETER_EXTENSION, |
| UTF8ToUTF16(it->extension_name))); |
| break; |
| } |
| } |
| } |
| } |
| |
| bool ExtensionWebRequestEventRouter::GetAndSetSignaled(uint64 request_id, |
| EventTypes event_type) { |
| SignaledRequestMap::iterator iter = signaled_requests_.find(request_id); |
| if (iter == signaled_requests_.end()) { |
| signaled_requests_[request_id] = event_type; |
| return false; |
| } |
| bool was_signaled_before = (iter->second & event_type) != 0; |
| iter->second |= event_type; |
| return was_signaled_before; |
| } |
| |
| void ExtensionWebRequestEventRouter::ClearSignaled(uint64 request_id, |
| EventTypes event_type) { |
| SignaledRequestMap::iterator iter = signaled_requests_.find(request_id); |
| if (iter == signaled_requests_.end()) |
| return; |
| iter->second &= ~event_type; |
| } |
| |
| bool WebRequestAddEventListener::RunImpl() { |
| // Argument 0 is the callback, which we don't use here. |
| |
| ExtensionWebRequestEventRouter::RequestFilter filter; |
| if (HasOptionalArgument(1)) { |
| DictionaryValue* value = NULL; |
| error_.clear(); |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &value)); |
| // Failure + an empty error string means a fatal error. |
| EXTENSION_FUNCTION_VALIDATE(filter.InitFromValue(*value, &error_) || |
| !error_.empty()); |
| if (!error_.empty()) |
| return false; |
| } |
| |
| int extra_info_spec = 0; |
| if (HasOptionalArgument(2)) { |
| ListValue* value = NULL; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetList(2, &value)); |
| EXTENSION_FUNCTION_VALIDATE( |
| ExtensionWebRequestEventRouter::ExtraInfoSpec::InitFromValue( |
| *value, &extra_info_spec)); |
| } |
| |
| std::string event_name; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetString(3, &event_name)); |
| |
| std::string sub_event_name; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetString(4, &sub_event_name)); |
| |
| const Extension* extension = |
| extension_info_map()->extensions().GetByID(extension_id()); |
| std::string extension_name = extension ? extension->name() : extension_id(); |
| |
| ExtensionWebRequestEventRouter::GetInstance()->AddEventListener( |
| profile_id(), extension_id(), extension_name, |
| event_name, sub_event_name, filter, |
| extra_info_spec, ipc_sender_weak()); |
| |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( |
| &NotifyWebRequestAPIUsed, |
| profile_id(), make_scoped_refptr(GetExtension()))); |
| |
| return true; |
| } |
| |
| bool WebRequestEventHandled::RunImpl() { |
| std::string event_name; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &event_name)); |
| |
| std::string sub_event_name; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &sub_event_name)); |
| |
| std::string request_id_str; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &request_id_str)); |
| // TODO(mpcomplete): string-to-uint64? |
| int64 request_id; |
| EXTENSION_FUNCTION_VALIDATE(base::StringToInt64(request_id_str, &request_id)); |
| |
| scoped_ptr<ExtensionWebRequestEventRouter::EventResponse> response; |
| if (HasOptionalArgument(3)) { |
| DictionaryValue* value = NULL; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(3, &value)); |
| |
| if (!value->empty()) { |
| base::Time install_time = |
| extension_info_map()->GetInstallTime(extension_id()); |
| response.reset(new ExtensionWebRequestEventRouter::EventResponse( |
| extension_id(), install_time)); |
| } |
| |
| if (value->HasKey("cancel")) { |
| // Don't allow cancel mixed with other keys. |
| if (value->HasKey("redirectUrl") || value->HasKey("requestHeaders")) { |
| error_ = keys::kInvalidBlockingResponse; |
| return false; |
| } |
| |
| bool cancel = false; |
| EXTENSION_FUNCTION_VALIDATE(value->GetBoolean("cancel", &cancel)); |
| response->cancel = cancel; |
| } |
| |
| if (value->HasKey("redirectUrl")) { |
| std::string new_url_str; |
| EXTENSION_FUNCTION_VALIDATE(value->GetString("redirectUrl", |
| &new_url_str)); |
| response->new_url = GURL(new_url_str); |
| if (!response->new_url.is_valid()) { |
| error_ = ExtensionErrorUtils::FormatErrorMessage( |
| keys::kInvalidRedirectUrl, new_url_str); |
| return false; |
| } |
| } |
| |
| if (value->HasKey("requestHeaders")) { |
| ListValue* request_headers_value = NULL; |
| response->request_headers.reset(new net::HttpRequestHeaders()); |
| EXTENSION_FUNCTION_VALIDATE(value->GetList(keys::kRequestHeadersKey, |
| &request_headers_value)); |
| for (size_t i = 0; i < request_headers_value->GetSize(); ++i) { |
| DictionaryValue* header_value = NULL; |
| std::string name; |
| std::string value; |
| EXTENSION_FUNCTION_VALIDATE( |
| request_headers_value->GetDictionary(i, &header_value)); |
| EXTENSION_FUNCTION_VALIDATE( |
| header_value->GetString(keys::kHeaderNameKey, &name)); |
| EXTENSION_FUNCTION_VALIDATE( |
| header_value->GetString(keys::kHeaderValueKey, &value)); |
| |
| response->request_headers->SetHeader(name, value); |
| } |
| } |
| |
| if (value->HasKey("responseHeaders")) { |
| std::string response_headers_string; |
| ListValue* response_headers_value = NULL; |
| EXTENSION_FUNCTION_VALIDATE(value->GetList(keys::kResponseHeadersKey, |
| &response_headers_value)); |
| for (size_t i = 0; i < response_headers_value->GetSize(); ++i) { |
| DictionaryValue* header_value = NULL; |
| std::string name; |
| std::string value; |
| EXTENSION_FUNCTION_VALIDATE( |
| response_headers_value->GetDictionary(i, &header_value)); |
| EXTENSION_FUNCTION_VALIDATE( |
| header_value->GetString(keys::kHeaderNameKey, &name)); |
| EXTENSION_FUNCTION_VALIDATE( |
| header_value->GetString(keys::kHeaderValueKey, &value)); |
| |
| response_headers_string += name + ": " + value + '\n'; |
| } |
| response_headers_string += '\n'; |
| response->response_headers_string.swap(response_headers_string); |
| } |
| |
| if (value->HasKey(keys::kAuthCredentialsKey)) { |
| DictionaryValue* credentials_value = NULL; |
| EXTENSION_FUNCTION_VALIDATE(value->GetDictionary( |
| keys::kAuthCredentialsKey, |
| &credentials_value)); |
| response->auth_credentials.reset(new net::AuthCredentials()); |
| EXTENSION_FUNCTION_VALIDATE( |
| credentials_value->GetString(keys::kUsernameKey, |
| &response->auth_credentials->username)); |
| EXTENSION_FUNCTION_VALIDATE( |
| credentials_value->GetString(keys::kPasswordKey, |
| &response->auth_credentials->password)); |
| } |
| } |
| |
| ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled( |
| profile_id(), extension_id(), event_name, sub_event_name, request_id, |
| response.release()); |
| |
| return true; |
| } |
| |
| bool WebRequestHandlerBehaviorChanged::RunImpl() { |
| WebCacheManager::GetInstance()->ClearCacheOnNavigation(); |
| return true; |
| } |
| |
| void SendExtensionWebRequestStatusToHost(RenderProcessHost* host) { |
| Profile* profile = Profile::FromBrowserContext(host->browser_context()); |
| if (!profile || !profile->GetExtensionService()) |
| return; |
| |
| bool adblock = false; |
| bool adblock_plus = false; |
| bool other = false; |
| const ExtensionList* extensions = |
| profile->GetExtensionService()->extensions(); |
| for (ExtensionList::const_iterator it = extensions->begin(); |
| it != extensions->end(); ++it) { |
| if (profile->GetExtensionService()->HasUsedWebRequest(*it)) { |
| if ((*it)->name().find("Adblock Plus") != std::string::npos) { |
| adblock_plus = true; |
| } else if ((*it)->name().find("AdBlock") != std::string::npos) { |
| adblock = true; |
| } else { |
| other = true; |
| } |
| } |
| } |
| |
| host->Send(new ExtensionMsg_UsingWebRequestAPI(adblock, adblock_plus, other)); |
| } |