diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/extensions/browser/api/web_request | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/extensions/browser/api/web_request')
6 files changed, 544 insertions, 139 deletions
diff --git a/chromium/extensions/browser/api/web_request/BUILD.gn b/chromium/extensions/browser/api/web_request/BUILD.gn index 117c87d9f72..ac64cd54722 100644 --- a/chromium/extensions/browser/api/web_request/BUILD.gn +++ b/chromium/extensions/browser/api/web_request/BUILD.gn @@ -43,6 +43,7 @@ source_set("web_request") { ] deps = [ + "//components/ukm/content", "//components/web_cache/browser", "//content/public/browser", "//content/public/common", diff --git a/chromium/extensions/browser/api/web_request/web_request_api.cc b/chromium/extensions/browser/api/web_request/web_request_api.cc index 50db1d2502b..7c5498c16b1 100644 --- a/chromium/extensions/browser/api/web_request/web_request_api.cc +++ b/chromium/extensions/browser/api/web_request/web_request_api.cc @@ -26,6 +26,7 @@ #include "base/task/post_task.h" #include "base/time/time.h" #include "base/values.h" +#include "components/ukm/content/source_url_recorder.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" @@ -689,11 +690,11 @@ bool WebRequestAPI::MaybeProxyURLLoaderFactory( mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>* header_client) { DCHECK_CURRENTLY_ON(BrowserThread::UI); + auto* web_contents = content::WebContents::FromRenderFrameHost(frame); if (!MayHaveProxies()) { bool skip_proxy = true; // There are a few internal WebUIs that use WebView tag that are whitelisted // for webRequest. - auto* web_contents = content::WebContents::FromRenderFrameHost(frame); if (web_contents && WebViewGuest::IsGuest(web_contents)) { auto* guest_web_contents = WebViewGuest::GetTopLevelWebContents(web_contents); @@ -742,12 +743,15 @@ bool WebRequestAPI::MaybeProxyURLLoaderFactory( (browser_context->IsOffTheRecord() && ExtensionsBrowserClient::Get()->GetOriginalContext(browser_context) == browser_context_)); + const ukm::SourceId ukm_source_id = + web_contents ? ukm::GetSourceIdForWebContentsDocument(web_contents) + : ukm::kInvalidSourceId; WebRequestProxyingURLLoaderFactory::StartProxying( browser_context, is_navigation ? -1 : render_process_id, &request_id_generator_, std::move(navigation_ui_data), std::move(navigation_id), std::move(proxied_receiver), std::move(target_factory_remote), std::move(header_client_receiver), - proxies_.get(), type); + proxies_.get(), type, ukm_source_id); return true; } @@ -893,10 +897,9 @@ struct ExtensionWebRequestEventRouter::BlockedRequest { // for OnBeforeSendHeaders. net::HttpRequestHeaders* request_headers = nullptr; - // The response headers that were received from the server and subsequently - // filtered by the Declarative Net Request API. Only valid for + // The response headers that were received from the server. Only valid for // OnHeadersReceived. - scoped_refptr<const net::HttpResponseHeaders> filtered_response_headers; + scoped_refptr<const net::HttpResponseHeaders> original_response_headers; // Location where to override response headers. Only valid for // OnHeadersReceived. @@ -1104,8 +1107,14 @@ int ExtensionWebRequestEventRouter::OnBeforeRequest( *new_url = action.redirect_url.value(); return net::OK; case DNRRequestAction::Type::MODIFY_HEADERS: - // TODO(crbug.com/947591): Evaluate modify headers DNR actions. - NOTREACHED(); + // Unlike other actions, allow web request extensions to intercept the + // request here. The headers will be modified during subsequent request + // stages. + DCHECK(std::all_of(request->dnr_actions->begin(), + request->dnr_actions->end(), [](const auto& action) { + return action.type == + DNRRequestAction::Type::MODIFY_HEADERS; + })); break; } } @@ -1136,15 +1145,20 @@ int ExtensionWebRequestEventRouter::OnBeforeSendHeaders( if (ShouldHideEvent(browser_context, *request)) return net::OK; - // TODO(crbug.com/947591): Handle request header modification by the - // Declarative Net Request API. - bool initialize_blocked_requests = false; initialize_blocked_requests |= ProcessDeclarativeRules(browser_context, keys::kOnBeforeSendHeadersEvent, request, ON_BEFORE_SEND_HEADERS, nullptr); + DCHECK(request->dnr_actions); + initialize_blocked_requests |= std::any_of( + request->dnr_actions->begin(), request->dnr_actions->end(), + [](const DNRRequestAction& action) { + return action.type == DNRRequestAction::Type::MODIFY_HEADERS && + !action.request_headers_to_modify.empty(); + }); + int extra_info_spec = 0; RawListeners listeners = GetMatchingListeners(browser_context, keys::kOnBeforeSendHeadersEvent, @@ -1217,16 +1231,19 @@ int ExtensionWebRequestEventRouter::OnHeadersReceived( if (ShouldHideEvent(browser_context, *request)) return net::OK; - // TODO(crbug.com/947591): Handle header modification by the Declarative Net - // Request API. - scoped_refptr<const net::HttpResponseHeaders> filtered_response_headers = - original_response_headers; - bool initialize_blocked_requests = false; + DCHECK(request->dnr_actions); + initialize_blocked_requests |= std::any_of( + request->dnr_actions->begin(), request->dnr_actions->end(), + [](const DNRRequestAction& action) { + return action.type == DNRRequestAction::Type::MODIFY_HEADERS && + !action.response_headers_to_modify.empty(); + }); + initialize_blocked_requests |= ProcessDeclarativeRules( browser_context, keys::kOnHeadersReceivedEvent, request, - ON_HEADERS_RECEIVED, filtered_response_headers.get()); + ON_HEADERS_RECEIVED, original_response_headers); int extra_info_spec = 0; RawListeners listeners = @@ -1237,8 +1254,7 @@ int ExtensionWebRequestEventRouter::OnHeadersReceived( !GetAndSetSignaled(request->id, kOnHeadersReceived)) { std::unique_ptr<WebRequestEventDetails> event_details( CreateEventDetails(*request, extra_info_spec)); - event_details->SetResponseHeaders(*request, - filtered_response_headers.get()); + event_details->SetResponseHeaders(*request, original_response_headers); initialize_blocked_requests |= DispatchEvent( browser_context, request, listeners, std::move(event_details)); @@ -1258,7 +1274,7 @@ int ExtensionWebRequestEventRouter::OnHeadersReceived( blocked_request.request = request; blocked_request.callback = std::move(callback); blocked_request.override_response_headers = override_response_headers; - blocked_request.filtered_response_headers = filtered_response_headers; + blocked_request.original_response_headers = original_response_headers; blocked_request.new_url = preserve_fragment_on_redirect_url; if (blocked_request.num_handlers_blocking == 0) { @@ -2014,7 +2030,7 @@ helpers::EventResponseDelta CalculateDelta( } case ExtensionWebRequestEventRouter::kOnHeadersReceived: { const net::HttpResponseHeaders* old_headers = - blocked_request->filtered_response_headers.get(); + blocked_request->original_response_headers.get(); helpers::ResponseHeaders* new_headers = response->response_headers.get(); return helpers::CalculateOnHeadersReceivedDelta( @@ -2229,6 +2245,7 @@ int ExtensionWebRequestEventRouter::ExecuteDeltas( helpers::MergeCancelOfResponses(blocked_request.response_deltas, &canceled); extension_web_request_api_helpers::IgnoredActions ignored_actions; + std::vector<const DNRRequestAction*> matched_dnr_actions; if (blocked_request.event == kOnBeforeRequest) { CHECK(!blocked_request.callback.is_null()); helpers::MergeOnBeforeRequestResponses( @@ -2240,15 +2257,14 @@ int ExtensionWebRequestEventRouter::ExecuteDeltas( *request, blocked_request.response_deltas, blocked_request.request_headers, &ignored_actions, &request_headers_removed, &request_headers_set, - &request_headers_modified); - + &request_headers_modified, &matched_dnr_actions); } else if (blocked_request.event == kOnHeadersReceived) { CHECK(!blocked_request.callback.is_null()); helpers::MergeOnHeadersReceivedResponses( *request, blocked_request.response_deltas, - blocked_request.filtered_response_headers.get(), + blocked_request.original_response_headers.get(), blocked_request.override_response_headers, blocked_request.new_url, - &ignored_actions, &response_headers_modified); + &ignored_actions, &response_headers_modified, &matched_dnr_actions); } else if (blocked_request.event == kOnAuthRequired) { CHECK(blocked_request.callback.is_null()); CHECK(!blocked_request.auth_callback.is_null()); @@ -2266,6 +2282,9 @@ int ExtensionWebRequestEventRouter::ExecuteDeltas( std::move(ignored_actions)); } + for (const DNRRequestAction* action : matched_dnr_actions) + OnDNRActionMatched(browser_context, *request, *action); + const bool redirected = blocked_request.new_url && !blocked_request.new_url->is_empty(); @@ -2330,7 +2349,7 @@ bool ExtensionWebRequestEventRouter::ProcessDeclarativeRules( const std::string& event_name, const WebRequestInfo* request, RequestStage request_stage, - const net::HttpResponseHeaders* filtered_response_headers) { + const net::HttpResponseHeaders* original_response_headers) { int rules_registry_id = request->is_web_view ? request->web_view_rules_registry_id : RulesRegistryService::kDefaultRulesRegistryID; @@ -2384,7 +2403,7 @@ bool ExtensionWebRequestEventRouter::ProcessDeclarativeRules( blocked_request.request = request; blocked_request.is_incognito |= IsIncognitoBrowserContext(browser_context); blocked_request.blocking_time = base::Time::Now(); - blocked_request.filtered_response_headers = filtered_response_headers; + blocked_request.original_response_headers = original_response_headers; return true; } @@ -2393,7 +2412,7 @@ bool ExtensionWebRequestEventRouter::ProcessDeclarativeRules( WebRequestRulesRegistry* rules_registry = it.first; helpers::EventResponseDeltas result = rules_registry->CreateDeltas( PermissionHelper::Get(browser_context), - WebRequestData(request, request_stage, filtered_response_headers), + WebRequestData(request, request_stage, original_response_headers), it.second); if (!result.empty()) { @@ -2422,7 +2441,7 @@ void ExtensionWebRequestEventRouter::OnRulesRegistryReady( BlockedRequest& blocked_request = it->second; ProcessDeclarativeRules(browser_context, event_name, blocked_request.request, request_stage, - blocked_request.filtered_response_headers.get()); + blocked_request.original_response_headers.get()); DecrementBlockCount(browser_context, std::string(), event_name, request_id, nullptr, 0 /* extra_info_spec */); } diff --git a/chromium/extensions/browser/api/web_request/web_request_api_helpers.cc b/chromium/extensions/browser/api/web_request/web_request_api_helpers.cc index 2e5874d5ac8..58012fe6fe5 100644 --- a/chromium/extensions/browser/api/web_request/web_request_api_helpers.cc +++ b/chromium/extensions/browser/api/web_request/web_request_api_helpers.cc @@ -23,7 +23,6 @@ #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" -#include "base/task/post_task.h" #include "base/time/time.h" #include "base/values.h" #include "components/web_cache/browser/web_cache_manager.h" @@ -31,6 +30,7 @@ #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/render_process_host.h" +#include "extensions/browser/api/declarative_net_request/request_action.h" #include "extensions/browser/api/extensions_api_client.h" #include "extensions/browser/api/web_request/web_request_api_constants.h" #include "extensions/browser/api/web_request/web_request_info.h" @@ -38,6 +38,7 @@ #include "extensions/browser/extension_system.h" #include "extensions/browser/extensions_browser_client.h" #include "extensions/browser/runtime_data.h" +#include "extensions/common/api/declarative_net_request.h" #include "extensions/common/extension_messages.h" #include "net/cookies/cookie_util.h" #include "net/cookies/parsed_cookie.h" @@ -56,11 +57,13 @@ using net::cookie_util::ParsedRequestCookies; namespace keys = extension_web_request_api_constants; namespace web_request = extensions::api::web_request; +using DNRRequestAction = extensions::declarative_net_request::RequestAction; namespace extension_web_request_api_helpers { namespace { +namespace dnr_api = extensions::api::declarative_net_request; using ParsedResponseCookies = std::vector<std::unique_ptr<net::ParsedCookie>>; void ClearCacheOnNavigationOnUI() { @@ -329,20 +332,178 @@ static_assert(static_cast<size_t>(ResponseHeaderType::kMaxValue) - 1 == static_assert(ValidateHeaderEntries(kResponseHeaderEntries), "Invalid response header entries"); -bool HasMatchingRemovedDNRRequestHeader( - const extensions::WebRequestInfo& request, - const std::string& header) { - // TODO(crbug.com/947591): Reimplement this method with - // |action.request_headers_to_modify|. - return false; +// Represents an action to be taken on a given header. +struct DNRHeaderAction { + DNRHeaderAction(const DNRRequestAction::HeaderInfo* header_info, + const extensions::ExtensionId* extension_id) + : header_info(header_info), extension_id(extension_id) {} + + // Returns whether for the same header, the operation specified by + // |next_action| conflicts with the operation specified by this action. + bool ConflictsWithSubsequentAction(const DNRHeaderAction& next_action) const { + DCHECK_EQ(header_info->header, next_action.header_info->header); + + switch (header_info->operation) { + case dnr_api::HEADER_OPERATION_APPEND: + return next_action.header_info->operation != + dnr_api::HEADER_OPERATION_APPEND; + case dnr_api::HEADER_OPERATION_SET: + return *extension_id != *next_action.extension_id || + next_action.header_info->operation != + dnr_api::HEADER_OPERATION_APPEND; + case dnr_api::HEADER_OPERATION_REMOVE: + return true; + case dnr_api::HEADER_OPERATION_NONE: + NOTREACHED(); + return true; + } + } + + // Non-owning pointers to HeaderInfo and ExtensionId. + const DNRRequestAction::HeaderInfo* header_info; + const extensions::ExtensionId* extension_id; +}; + +// Helper to modify request headers from +// |request_action.request_headers_to_modify|. Returns whether or not request +// headers were actually modified and modifies |removed_headers|, |set_headers| +// and |header_actions|. |header_actions| maps a header name to the operation +// to be performed on the header. +bool ModifyRequestHeadersForAction( + net::HttpRequestHeaders* headers, + const DNRRequestAction& request_action, + std::set<std::string>* removed_headers, + std::set<std::string>* set_headers, + std::map<base::StringPiece, DNRHeaderAction>* header_actions) { + bool request_headers_modified = false; + for (const DNRRequestAction::HeaderInfo& header_info : + request_action.request_headers_to_modify) { + bool header_modified = false; + const std::string& header = header_info.header; + + DNRHeaderAction header_action(&header_info, &request_action.extension_id); + auto iter = header_actions->find(header); + if (iter != header_actions->end() && + iter->second.ConflictsWithSubsequentAction(header_action)) { + continue; + } + header_actions->emplace(header, header_action); + + // TODO(crbug.com/1088103): Record request headers modified by the + // Declarative Net Request API. + switch (header_info.operation) { + case extensions::api::declarative_net_request::HEADER_OPERATION_SET: + headers->SetHeader(header, *header_info.value); + header_modified = true; + set_headers->insert(header); + break; + case extensions::api::declarative_net_request::HEADER_OPERATION_REMOVE: { + while (headers->HasHeader(header)) { + header_modified = true; + headers->RemoveHeader(header); + } + + if (header_modified) + removed_headers->insert(header); + break; + } + case extensions::api::declarative_net_request::HEADER_OPERATION_APPEND: + case extensions::api::declarative_net_request::HEADER_OPERATION_NONE: + NOTREACHED(); + } + + request_headers_modified |= header_modified; + } + + return request_headers_modified; } -bool HasMatchingRemovedDNRResponseHeader( - const extensions::WebRequestInfo& request, - const std::string& header) { - // TODO(crbug.com/947591): Reimplement this method with - // |action.response_headers_to_modify|. - return false; +// Helper to modify response headers from |request_action|. Returns whether or +// not response headers were actually modified and modifies |header_actions|. +// |header_actions| maps a header name to a list of operations to be performed +// on the header. +bool ModifyResponseHeadersForAction( + const net::HttpResponseHeaders* original_response_headers, + scoped_refptr<net::HttpResponseHeaders>* override_response_headers, + const DNRRequestAction& request_action, + std::map<base::StringPiece, std::vector<DNRHeaderAction>>* header_actions) { + bool response_headers_modified = false; + + // Check for |header| in |override_response_headers| if headers have been + // modified, otherwise, check in |original_response_headers|. + auto has_header = [&original_response_headers, + &override_response_headers](std::string header) { + return override_response_headers->get() + ? override_response_headers->get()->HasHeader(header) + : original_response_headers->HasHeader(header); + }; + + // Create a copy of |original_response_headers| iff we really want to modify + // the response headers. + auto create_override_headers_if_needed = + [&original_response_headers]( + scoped_refptr<net::HttpResponseHeaders>* override_response_headers) { + if (override_response_headers->get() == nullptr) { + *override_response_headers = + base::MakeRefCounted<net::HttpResponseHeaders>( + original_response_headers->raw_headers()); + } + }; + + for (const DNRRequestAction::HeaderInfo& header_info : + request_action.response_headers_to_modify) { + bool header_modified = false; + const std::string& header = header_info.header; + + DNRHeaderAction header_action(&header_info, &request_action.extension_id); + auto iter = header_actions->find(header); + + // Checking the first DNRHeaderAction should suffice for determining if a + // conflict exists, since the contents of |header_actions| for a given + // header will always be one of: + // [remove] + // [append+] one or more appends + // [set, append*] set, any number of appends from the same extension + if (iter != header_actions->end() && + (*header_actions)[header][0].ConflictsWithSubsequentAction( + header_action)) { + continue; + } + (*header_actions)[header].push_back(header_action); + + // TODO(crbug.com/1088103): Record response headers modified by the + // Declarative Net Request API. + switch (header_info.operation) { + case extensions::api::declarative_net_request::HEADER_OPERATION_REMOVE: { + if (has_header(header)) { + header_modified = true; + create_override_headers_if_needed(override_response_headers); + override_response_headers->get()->RemoveHeader(header); + } + + break; + } + case extensions::api::declarative_net_request::HEADER_OPERATION_APPEND: { + header_modified = true; + create_override_headers_if_needed(override_response_headers); + override_response_headers->get()->AddHeader(header, *header_info.value); + break; + } + case extensions::api::declarative_net_request::HEADER_OPERATION_SET: { + header_modified = true; + create_override_headers_if_needed(override_response_headers); + override_response_headers->get()->RemoveHeader(header); + override_response_headers->get()->AddHeader(header, *header_info.value); + break; + } + case extensions::api::declarative_net_request::HEADER_OPERATION_NONE: + NOTREACHED(); + } + + response_headers_modified |= header_modified; + } + + return response_headers_modified; } } // namespace @@ -920,16 +1081,36 @@ void MergeOnBeforeSendHeadersResponses( IgnoredActions* ignored_actions, std::set<std::string>* removed_headers, std::set<std::string>* set_headers, - bool* request_headers_modified) { + bool* request_headers_modified, + std::vector<const DNRRequestAction*>* matched_dnr_actions) { DCHECK(request_headers_modified); DCHECK(removed_headers->empty()); DCHECK(set_headers->empty()); + DCHECK(request.dnr_actions); + DCHECK(matched_dnr_actions); *request_headers_modified = false; - // Exhaustive subsets of |set_headers|. Split into a set for added headers and - // a set for overridden headers. - std::set<std::string> overridden_headers; - std::set<std::string> added_headers; + std::map<base::StringPiece, DNRHeaderAction> dnr_header_actions; + for (const auto& action : *request.dnr_actions) { + bool headers_modified_for_action = + ModifyRequestHeadersForAction(request_headers, action, removed_headers, + set_headers, &dnr_header_actions); + + *request_headers_modified |= headers_modified_for_action; + if (headers_modified_for_action) + matched_dnr_actions->push_back(&action); + } + + // A strict subset of |removed_headers| consisting of headers removed by the + // web request API. Used for metrics. + // TODO(crbug.com/1098945): Use base::StringPiece to avoid copying header + // names. + std::set<std::string> web_request_removed_headers; + + // Subsets of |set_headers| consisting of headers modified by the web request + // API. Split into a set for added headers and a set for overridden headers. + std::set<std::string> web_request_overridden_headers; + std::set<std::string> web_request_added_headers; // We assume here that the deltas are sorted in decreasing extension // precedence (i.e. decreasing extension installation time). @@ -951,16 +1132,20 @@ void MergeOnBeforeSendHeadersResponses( const std::string key = base::ToLowerASCII(modification.name()); const std::string& value = modification.value(); - // We must not modify anything that has been deleted before. - if (base::Contains(*removed_headers, key)) { + // We must not modify anything that was specified to be removed by the + // Declarative Net Request API. Note that the actual header + // modifications made by Declarative Net Request should be represented + // in |removed_headers| and |set_headers|. + auto iter = dnr_header_actions.find(key); + if (iter != dnr_header_actions.end() && + iter->second.header_info->operation == + dnr_api::HEADER_OPERATION_REMOVE) { extension_conflicts = true; break; } - // Prevent extensions from adding any header removed by the Declarative - // Net Request API. - DCHECK(request.dnr_actions); - if (HasMatchingRemovedDNRRequestHeader(request, key)) { + // We must not modify anything that has been deleted before. + if (base::Contains(*removed_headers, key)) { extension_conflicts = true; break; } @@ -998,11 +1183,11 @@ void MergeOnBeforeSendHeadersResponses( while (modification.GetNext()) { std::string key = base::ToLowerASCII(modification.name()); if (!request_headers->HasHeader(key)) { - added_headers.insert(key); - } else if (!base::Contains(added_headers, key)) { + web_request_added_headers.insert(key); + } else if (!base::Contains(web_request_added_headers, key)) { // Note: |key| will only be present in |added_headers| if this is an // identical edit. - overridden_headers.insert(key); + web_request_overridden_headers.insert(key); } set_headers->insert(key); @@ -1013,8 +1198,11 @@ void MergeOnBeforeSendHeadersResponses( // Perform all deletions and record which keys were deleted. { for (const auto& header : delta.deleted_request_headers) { + std::string lowercase_header = base::ToLowerASCII(header); + request_headers->RemoveHeader(header); - removed_headers->insert(base::ToLowerASCII(header)); + removed_headers->insert(lowercase_header); + web_request_removed_headers.insert(lowercase_header); } } *request_headers_modified = true; @@ -1039,23 +1227,24 @@ void MergeOnBeforeSendHeadersResponses( IsStringLowerCaseASCII)); DCHECK(std::all_of(set_headers->begin(), set_headers->end(), IsStringLowerCaseASCII)); - DCHECK(std::all_of(overridden_headers.begin(), overridden_headers.end(), - IsStringLowerCaseASCII)); - DCHECK(std::all_of(added_headers.begin(), added_headers.end(), - IsStringLowerCaseASCII)); - DCHECK(*set_headers == base::STLSetUnion<std::set<std::string>>( - added_headers, overridden_headers)); - DCHECK(base::STLSetIntersection<std::set<std::string>>(added_headers, - overridden_headers) + DCHECK(base::STLIncludes( + *set_headers, + base::STLSetUnion<std::set<std::string>>( + web_request_added_headers, web_request_overridden_headers))); + DCHECK(base::STLSetIntersection<std::set<std::string>>( + web_request_added_headers, web_request_overridden_headers) .empty()); DCHECK(base::STLSetIntersection<std::set<std::string>>(*removed_headers, *set_headers) .empty()); + DCHECK(base::STLIncludes(*removed_headers, web_request_removed_headers)); // Record request header removals, additions and modifications. - record_request_headers(*removed_headers, &RecordRequestHeaderRemoved); - record_request_headers(added_headers, &RecordRequestHeaderAdded); - record_request_headers(overridden_headers, &RecordRequestHeaderChanged); + record_request_headers(web_request_removed_headers, + &RecordRequestHeaderRemoved); + record_request_headers(web_request_added_headers, &RecordRequestHeaderAdded); + record_request_headers(web_request_overridden_headers, + &RecordRequestHeaderChanged); // Currently, conflicts are ignored while merging cookies. MergeCookiesInOnBeforeSendHeadersResponses(request.url, deltas, @@ -1290,10 +1479,25 @@ void MergeOnHeadersReceivedResponses( scoped_refptr<net::HttpResponseHeaders>* override_response_headers, GURL* preserve_fragment_on_redirect_url, IgnoredActions* ignored_actions, - bool* response_headers_modified) { + bool* response_headers_modified, + std::vector<const DNRRequestAction*>* matched_dnr_actions) { DCHECK(response_headers_modified); *response_headers_modified = false; + DCHECK(request.dnr_actions); + DCHECK(matched_dnr_actions); + + std::map<base::StringPiece, std::vector<DNRHeaderAction>> dnr_header_actions; + for (const auto& action : *request.dnr_actions) { + bool headers_modified_for_action = ModifyResponseHeadersForAction( + original_response_headers, override_response_headers, action, + &dnr_header_actions); + + *response_headers_modified |= headers_modified_for_action; + if (headers_modified_for_action) + matched_dnr_actions->push_back(&action); + } + // Here we collect which headers we have removed or added so far due to // extensions of higher precedence. Header keys are always stored as // lower case. @@ -1322,18 +1526,28 @@ void MergeOnHeadersReceivedResponses( // this takes care of precedence. bool extension_conflicts = false; for (const ResponseHeader& header : delta.deleted_response_headers) { - if (removed_headers.find(ToLowerCase(header)) != removed_headers.end()) { + ResponseHeader lowercase_header(ToLowerCase(header)); + if (base::Contains(removed_headers, lowercase_header) || + base::Contains(dnr_header_actions, lowercase_header.first)) { extension_conflicts = true; break; } } - // Prevent extensions from adding any response header which was removed by - // the Declarative Net Request API. - DCHECK(request.dnr_actions); + // Prevent extensions from adding any response header which was specified to + // be removed or set by the Declarative Net Request API. However, multiple + // appends are allowed. if (!extension_conflicts) { for (const ResponseHeader& header : delta.added_response_headers) { - if (HasMatchingRemovedDNRResponseHeader(request, header.first)) { + ResponseHeader lowercase_header(ToLowerCase(header)); + + auto it = dnr_header_actions.find(lowercase_header.first); + if (it == dnr_header_actions.end()) + continue; + + // Multiple appends are allowed. + if (it->second[0].header_info->operation != + dnr_api::HEADER_OPERATION_APPEND) { extension_conflicts = true; break; } @@ -1406,6 +1620,7 @@ void MergeOnHeadersReceivedResponses( std::set<base::StringPiece> modified_header_names; std::set<base::StringPiece> added_header_names; std::set<base::StringPiece> removed_header_names; + for (const ResponseHeader& header : added_headers) { // Skip logging this header if this was subsequently removed by an // extension. @@ -1467,8 +1682,8 @@ void ClearCacheOnNavigation() { if (content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) { ClearCacheOnNavigationOnUI(); } else { - base::PostTask(FROM_HERE, {content::BrowserThread::UI}, - base::BindOnce(&ClearCacheOnNavigationOnUI)); + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, base::BindOnce(&ClearCacheOnNavigationOnUI)); } } diff --git a/chromium/extensions/browser/api/web_request/web_request_api_helpers.h b/chromium/extensions/browser/api/web_request/web_request_api_helpers.h index a90a34b999b..cd7d01a62cc 100644 --- a/chromium/extensions/browser/api/web_request/web_request_api_helpers.h +++ b/chromium/extensions/browser/api/web_request/web_request_api_helpers.h @@ -38,6 +38,11 @@ class BrowserContext; namespace extensions { class Extension; struct WebRequestInfo; + +namespace declarative_net_request { +struct RequestAction; +} // namespace declarative_net_request + } // namespace extensions namespace extension_web_request_api_helpers { @@ -435,6 +440,8 @@ void MergeCookiesInOnBeforeSendHeadersResponses( // are tried to be resolved. // Stores in |request_headers_modified| whether the request headers were // modified. +// Any actions within |request.dnr_actions| which result in headers being +// modified are added to |matched_dnr_actions|. void MergeOnBeforeSendHeadersResponses( const extensions::WebRequestInfo& request, const EventResponseDeltas& deltas, @@ -442,7 +449,9 @@ void MergeOnBeforeSendHeadersResponses( IgnoredActions* ignored_actions, std::set<std::string>* removed_headers, std::set<std::string>* set_headers, - bool* request_headers_modified); + bool* request_headers_modified, + std::vector<const extensions::declarative_net_request::RequestAction*>* + matched_dnr_actions); // Modifies the "Set-Cookie" headers in |override_response_headers| according to // |deltas.response_cookie_modifications|. If |override_response_headers| is // NULL, a copy of |original_response_headers| is created. Conflicts are @@ -460,6 +469,8 @@ void MergeCookiesInOnHeadersReceivedResponses( // sure that the URL provided by the extension isn't modified by having its // fragment overwritten by that of the original URL). Stores in // |response_headers_modified| whether the response headers were modified. +// Any actions within |request.dnr_actions| which result in headers being +// modified are added to |matched_dnr_actions|. void MergeOnHeadersReceivedResponses( const extensions::WebRequestInfo& request, const EventResponseDeltas& deltas, @@ -467,7 +478,9 @@ void MergeOnHeadersReceivedResponses( scoped_refptr<net::HttpResponseHeaders>* override_response_headers, GURL* preserve_fragment_on_redirect_url, IgnoredActions* ignored_actions, - bool* response_headers_modified); + bool* response_headers_modified, + std::vector<const extensions::declarative_net_request::RequestAction*>* + matched_dnr_actions); // Merge the responses of blocked onAuthRequired handlers. The first // registered listener that supplies authentication credentials in a response, // if any, will have its authentication credentials used. |request| must be diff --git a/chromium/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc b/chromium/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc index dbb25b166a8..e26a21f26b6 100644 --- a/chromium/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc +++ b/chromium/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc @@ -10,6 +10,7 @@ #include "base/bind.h" #include "base/callback_helpers.h" #include "base/feature_list.h" +#include "base/metrics/histogram_macros.h" #include "base/strings/stringprintf.h" #include "base/task/post_task.h" #include "components/keyed_service/content/browser_context_keyed_service_shutdown_notifier_factory.h" @@ -21,12 +22,18 @@ #include "extensions/browser/api/web_request/permission_helper.h" #include "extensions/browser/extension_navigation_ui_data.h" #include "extensions/browser/extension_registry.h" +#include "extensions/common/extension_features.h" #include "extensions/common/manifest_handlers/web_accessible_resources_info.h" #include "net/base/completion_repeating_callback.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_status_code.h" #include "net/http/http_util.h" #include "net/url_request/redirect_info.h" #include "net/url_request/redirect_util.h" #include "net/url_request/url_request.h" +#include "services/metrics/public/cpp/ukm_builders.h" +#include "services/metrics/public/cpp/ukm_recorder.h" +#include "services/metrics/public/cpp/ukm_source_id.h" #include "services/network/public/cpp/features.h" #include "third_party/blink/public/common/loader/throttling_url_loader.h" #include "third_party/blink/public/platform/resource_request_blocked_reason.h" @@ -91,6 +98,7 @@ WebRequestProxyingURLLoaderFactory::InProgressRequest::InProgressRequest( int32_t network_service_request_id, int32_t routing_id, uint32_t options, + ukm::SourceId ukm_source_id, const network::ResourceRequest& request, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver, @@ -102,6 +110,7 @@ WebRequestProxyingURLLoaderFactory::InProgressRequest::InProgressRequest( network_service_request_id_(network_service_request_id), routing_id_(routing_id), options_(options), + ukm_source_id_(ukm_source_id), traffic_annotation_(traffic_annotation), proxied_loader_receiver_(this, std::move(loader_receiver)), target_client_(std::move(client)), @@ -111,10 +120,10 @@ WebRequestProxyingURLLoaderFactory::InProgressRequest::InProgressRequest( ExtensionWebRequestEventRouter::GetInstance() ->HasAnyExtraHeadersListener(factory_->browser_context_)) { // If there is a client error, clean up the request. - target_client_.set_disconnect_handler(base::BindOnce( - &WebRequestProxyingURLLoaderFactory::InProgressRequest::OnRequestError, - weak_factory_.GetWeakPtr(), - network::URLLoaderCompletionStatus(net::ERR_ABORTED))); + target_client_.set_disconnect_handler( + base::BindOnce(&WebRequestProxyingURLLoaderFactory::InProgressRequest:: + OnClientDisconnected, + weak_factory_.GetWeakPtr())); proxied_loader_receiver_.set_disconnect_with_reason_handler( base::BindOnce(&WebRequestProxyingURLLoaderFactory::InProgressRequest:: OnLoaderDisconnected, @@ -129,6 +138,7 @@ WebRequestProxyingURLLoaderFactory::InProgressRequest::InProgressRequest( request_(request), original_initiator_(request.request_initiator), request_id_(request_id), + ukm_source_id_(ukm::kInvalidSourceId), proxied_loader_receiver_(this), for_cors_preflight_(true), has_any_extra_headers_listeners_( @@ -136,6 +146,18 @@ WebRequestProxyingURLLoaderFactory::InProgressRequest::InProgressRequest( ->HasAnyExtraHeadersListener(factory_->browser_context_)) {} WebRequestProxyingURLLoaderFactory::InProgressRequest::~InProgressRequest() { + DCHECK_NE(state_, State::kInvalid); + if (request_.keepalive && !for_cors_preflight_) { + UMA_HISTOGRAM_ENUMERATION("Extensions.WebRequest.KeepaliveRequestState", + state_); + if (base::FeatureList::IsEnabled( + extensions_features::kReportKeepaliveUkm)) { + ukm::builders::Extensions_WebRequest_KeepaliveRequestFinished( + ukm_source_id_) + .SetState(state_) + .Record(ukm::UkmRecorder::Get()); + } + } // This is important to ensure that no outstanding blocking requests continue // to reference state owned by this object. if (info_) { @@ -186,20 +208,23 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: void WebRequestProxyingURLLoaderFactory::InProgressRequest::RestartInternal() { DCHECK_EQ(info_->url, request_.url) << "UpdateRequestInfo must have been called first"; + is_header_client_receiver_paused_ = false; // If the header client will be used, we start the request immediately, and // OnBeforeSendHeaders and OnSendHeaders will be handled there. Otherwise, // send these events before the request starts. base::RepeatingCallback<void(int)> continuation; + const auto state_on_error = State::kRejectedByOnBeforeRequest; if (current_request_uses_header_client_) { - continuation = base::BindRepeating( - &InProgressRequest::ContinueToStartRequest, weak_factory_.GetWeakPtr()); + continuation = + base::BindRepeating(&InProgressRequest::ContinueToStartRequest, + weak_factory_.GetWeakPtr(), state_on_error); } else if (for_cors_preflight_) { // In this case we do nothing because extensions should see nothing. return; } else { continuation = base::BindRepeating(&InProgressRequest::ContinueToBeforeSendHeaders, - weak_factory_.GetWeakPtr()); + weak_factory_.GetWeakPtr(), state_on_error); } redirect_url_ = GURL(); bool should_collapse_initiator = false; @@ -214,7 +239,7 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest::RestartInternal() { status.extended_error_code = static_cast<int>( blink::ResourceRequestBlockedReason::kCollapsedByClient); } - OnRequestError(status); + OnRequestError(status, state_on_error); return; } @@ -229,8 +254,10 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest::RestartInternal() { // Pause the header client, since we want to wait until OnBeforeRequest has // finished before processing any future events. - if (header_client_receiver_.is_bound()) + if (header_client_receiver_.is_bound()) { header_client_receiver_.Pause(); + is_header_client_receiver_paused_ = true; + } return; } DCHECK_EQ(net::OK, result); @@ -318,7 +345,7 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnReceiveRedirect( if (redirect_url_ != redirect_info.new_url && !IsRedirectSafe(request_.url, redirect_info.new_url, info_->is_navigation_request)) { - OnRequestError( + OnNetworkError( network::URLLoaderCompletionStatus(net::ERR_UNSAFE_REDIRECT)); return; } @@ -368,10 +395,11 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnComplete( const network::URLLoaderCompletionStatus& status) { if (status.error_code != net::OK) { - OnRequestError(status); + OnNetworkError(status); return; } + state_ = kCompleted; target_client_->OnComplete(status); ExtensionWebRequestEventRouter::GetInstance()->OnCompleted( factory_->browser_context_, &info_.value(), status.error_code); @@ -422,12 +450,14 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnLoaderCreated( header_client_receiver_.reset(); header_client_receiver_.Bind(std::move(receiver)); + if (is_header_client_receiver_paused_) + header_client_receiver_.Pause(); if (for_cors_preflight_) { // In this case we don't have |target_loader_| and // |proxied_client_receiver_|, and |receiver| is the only connection to the // network service, so we observe mojo connection errors. header_client_receiver_.set_disconnect_handler(base::BindOnce( - &WebRequestProxyingURLLoaderFactory::InProgressRequest::OnRequestError, + &WebRequestProxyingURLLoaderFactory::InProgressRequest::OnNetworkError, weak_factory_.GetWeakPtr(), network::URLLoaderCompletionStatus(net::ERR_FAILED))); } @@ -443,7 +473,7 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnBeforeSendHeaders( request_.headers = headers; on_before_send_headers_callback_ = std::move(callback); - ContinueToBeforeSendHeaders(net::OK); + ContinueToBeforeSendHeadersWithOk(); } void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnHeadersReceived( @@ -539,9 +569,10 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: } void WebRequestProxyingURLLoaderFactory::InProgressRequest:: - ContinueToBeforeSendHeaders(int error_code) { + ContinueToBeforeSendHeaders(State state_on_error, int error_code) { if (error_code != net::OK) { - OnRequestError(network::URLLoaderCompletionStatus(error_code)); + OnRequestError(network::URLLoaderCompletionStatus(error_code), + state_on_error); return; } @@ -558,8 +589,10 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: // intuitive), |onBeforeSendHeaders| is only dispatched for HTTP and HTTPS // requests. - auto continuation = base::BindRepeating( - &InProgressRequest::ContinueToSendHeaders, weak_factory_.GetWeakPtr()); + const auto state_on_error = State::kRejectedByOnBeforeSendHeaders; + auto continuation = + base::BindRepeating(&InProgressRequest::ContinueToSendHeaders, + weak_factory_.GetWeakPtr(), state_on_error); int result = ExtensionWebRequestEventRouter::GetInstance()->OnBeforeSendHeaders( factory_->browser_context_, &info_.value(), continuation, @@ -568,7 +601,8 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: if (result == net::ERR_BLOCKED_BY_CLIENT) { // The request was cancelled synchronously. Dispatch an error notification // and terminate the request. - OnRequestError(network::URLLoaderCompletionStatus(result)); + OnRequestError(network::URLLoaderCompletionStatus(result), + state_on_error); return; } @@ -585,21 +619,26 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: DCHECK_EQ(net::OK, result); } - ContinueToSendHeaders(std::set<std::string>(), std::set<std::string>(), - net::OK); + ContinueToSendHeadersWithOk(std::set<std::string>(), std::set<std::string>()); } void WebRequestProxyingURLLoaderFactory::InProgressRequest:: - ContinueToStartRequest(int error_code) { + ContinueToBeforeSendHeadersWithOk() { + ContinueToBeforeSendHeaders(State::kInvalid, net::OK); +} +void WebRequestProxyingURLLoaderFactory::InProgressRequest:: + ContinueToStartRequest(State state_on_error, int error_code) { if (error_code != net::OK) { - OnRequestError(network::URLLoaderCompletionStatus(error_code)); + OnRequestError(network::URLLoaderCompletionStatus(error_code), + state_on_error); return; } if (current_request_uses_header_client_ && !redirect_url_.is_empty()) { if (for_cors_preflight_) { // CORS preflight doesn't support redirect. - OnRequestError(network::URLLoaderCompletionStatus(net::ERR_FAILED)); + OnRequestError(network::URLLoaderCompletionStatus(net::ERR_FAILED), + state_on_error); return; } HandleBeforeRequestRedirect(); @@ -609,8 +648,10 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: if (proxied_client_receiver_.is_bound()) proxied_client_receiver_.Resume(); - if (header_client_receiver_.is_bound()) + if (header_client_receiver_.is_bound()) { header_client_receiver_.Resume(); + is_header_client_receiver_paused_ = false; + } if (for_cors_preflight_) { // For CORS preflight requests, we have already started the request in @@ -640,11 +681,18 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: } void WebRequestProxyingURLLoaderFactory::InProgressRequest:: - ContinueToSendHeaders(const std::set<std::string>& removed_headers, + ContinueToStartRequestWithOk() { + ContinueToStartRequest(State::kInvalid, net::OK); +} + +void WebRequestProxyingURLLoaderFactory::InProgressRequest:: + ContinueToSendHeaders(State state_on_error, + const std::set<std::string>& removed_headers, const std::set<std::string>& set_headers, int error_code) { if (error_code != net::OK) { - OnRequestError(network::URLLoaderCompletionStatus(error_code)); + OnRequestError(network::URLLoaderCompletionStatus(error_code), + state_on_error); return; } @@ -690,7 +738,13 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: } if (!current_request_uses_header_client_) - ContinueToStartRequest(net::OK); + ContinueToStartRequestWithOk(); +} + +void WebRequestProxyingURLLoaderFactory::InProgressRequest:: + ContinueToSendHeadersWithOk(const std::set<std::string>& removed_headers, + const std::set<std::string>& set_headers) { + ContinueToSendHeaders(State::kInvalid, removed_headers, set_headers, net::OK); } void WebRequestProxyingURLLoaderFactory::InProgressRequest::ContinueAuthRequest( @@ -698,6 +752,8 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest::ContinueAuthRequest( WebRequestAPI::AuthRequestCallback callback, int error_code) { if (error_code != net::OK) { + // Here we come from an onHeaderReceived failure. + state_ = State::kRejectedByOnHeadersReceivedForAuth; base::SequencedTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(callback), base::nullopt, true /* should_cancel */)); @@ -756,6 +812,7 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: AUTH_REQUIRED_RESPONSE_CANCEL_AUTH: completion = base::BindOnce(std::move(callback), base::nullopt, true /* should_cancel */); + state_ = State::kRejectedByOnAuthRequired; break; default: NOTREACHED(); @@ -770,7 +827,18 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: void WebRequestProxyingURLLoaderFactory::InProgressRequest:: ContinueToHandleOverrideHeaders(int error_code) { if (error_code != net::OK) { - OnRequestError(network::URLLoaderCompletionStatus(error_code)); + const int status_code = current_response_->headers + ? current_response_->headers->response_code() + : 0; + State state; + if (status_code == net::HTTP_UNAUTHORIZED) { + state = State::kRejectedByOnHeadersReceivedForAuth; + } else if (net::HttpResponseHeaders::IsRedirectResponseCode(status_code)) { + state = State::kRejectedByOnHeadersReceivedForRedirect; + } else { + state = State::kRejectedByOnHeadersReceivedForFinalResponse; + } + OnRequestError(network::URLLoaderCompletionStatus(error_code), state); return; } @@ -787,7 +855,8 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: } if (for_cors_preflight_ && !redirect_url_.is_empty()) { - OnRequestError(network::URLLoaderCompletionStatus(net::ERR_FAILED)); + OnRequestError(network::URLLoaderCompletionStatus(net::ERR_FAILED), + State::kRejectedByOnHeadersReceivedForRedirect); return; } @@ -816,10 +885,15 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: ContinueToResponseStarted(int error_code) { DCHECK(!for_cors_preflight_); if (error_code != net::OK) { - OnRequestError(network::URLLoaderCompletionStatus(error_code)); + OnRequestError(network::URLLoaderCompletionStatus(error_code), + State::kRejectedByOnHeadersReceivedForFinalResponse); return; } + if (state_ == State::kInProgress) { + state_ = State::kInProgressWithFinalResponseReceived; + } + DCHECK(!current_request_uses_header_client_ || !override_headers_); if (override_headers_) current_response_->headers = override_headers_; @@ -863,7 +937,8 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: ContinueToBeforeRedirect(const net::RedirectInfo& redirect_info, int error_code) { if (error_code != net::OK) { - OnRequestError(network::URLLoaderCompletionStatus(error_code)); + OnRequestError(network::URLLoaderCompletionStatus(error_code), + kRejectedByOnHeadersReceivedForRedirect); return; } @@ -907,7 +982,19 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: current_response_->headers.get(), &override_headers_, &redirect_url_); if (result == net::ERR_BLOCKED_BY_CLIENT) { - OnRequestError(network::URLLoaderCompletionStatus(result)); + const int status_code = current_response_->headers + ? current_response_->headers->response_code() + : 0; + State state; + if (status_code == net::HTTP_UNAUTHORIZED) { + state = State::kRejectedByOnHeadersReceivedForAuth; + } else if (net::HttpResponseHeaders::IsRedirectResponseCode( + status_code)) { + state = State::kRejectedByOnHeadersReceivedForRedirect; + } else { + state = State::kRejectedByOnHeadersReceivedForFinalResponse; + } + OnRequestError(network::URLLoaderCompletionStatus(result), state); return; } @@ -930,17 +1017,41 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: copyable_callback.Run(net::OK); } void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnRequestError( - const network::URLLoaderCompletionStatus& status) { + const network::URLLoaderCompletionStatus& status, + State state) { if (target_client_) target_client_->OnComplete(status); ExtensionWebRequestEventRouter::GetInstance()->OnErrorOccurred( factory_->browser_context_, &info_.value(), true /* started */, status.error_code); + state_ = state; // Deletes |this|. factory_->RemoveRequest(network_service_request_id_, request_id_); } +void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnNetworkError( + const network::URLLoaderCompletionStatus& status) { + State state = state_; + if (state_ == State::kInProgress) { + state = State::kRejectedByNetworkError; + } else if (state_ == State::kInProgressWithFinalResponseReceived) { + state = State::kRejectedByNetworkErrorAfterReceivingFinalResponse; + } + OnRequestError(status, state); +} + +void WebRequestProxyingURLLoaderFactory::InProgressRequest:: + OnClientDisconnected() { + State state = state_; + if (state_ == State::kInProgress) { + state = State::kDetachedFromClient; + } else if (state_ == State::kInProgressWithFinalResponseReceived) { + state = State::kDetachedFromClientAfterReceivingResponse; + } + OnRequestError(network::URLLoaderCompletionStatus(net::ERR_ABORTED), state); +} + void WebRequestProxyingURLLoaderFactory::InProgressRequest:: OnLoaderDisconnected(uint32_t custom_reason, const std::string& description) { @@ -953,10 +1064,11 @@ void WebRequestProxyingURLLoaderFactory::InProgressRequest:: factory_->request_id_generator_->SaveID( routing_id_, network_service_request_id_, request_id_); + state_ = State::kRedirectFollowedByAnotherInProgressRequest; // Deletes |this|. factory_->RemoveRequest(network_service_request_id_, request_id_); } else { - OnRequestError(network::URLLoaderCompletionStatus(net::ERR_ABORTED)); + OnNetworkError(network::URLLoaderCompletionStatus(net::ERR_ABORTED)); } } @@ -991,17 +1103,20 @@ WebRequestProxyingURLLoaderFactory::WebRequestProxyingURLLoaderFactory( mojo::PendingReceiver<network::mojom::TrustedURLLoaderHeaderClient> header_client_receiver, WebRequestAPI::ProxySet* proxies, - content::ContentBrowserClient::URLLoaderFactoryType loader_factory_type) + content::ContentBrowserClient::URLLoaderFactoryType loader_factory_type, + ukm::SourceId ukm_source_id) : browser_context_(browser_context), render_process_id_(render_process_id), request_id_generator_(request_id_generator), navigation_ui_data_(std::move(navigation_ui_data)), navigation_id_(std::move(navigation_id)), proxies_(proxies), - loader_factory_type_(loader_factory_type) { + loader_factory_type_(loader_factory_type), + ukm_source_id_(ukm_source_id) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - // base::Unretained is safe here because the callback will be canceled when - // |shutdown_notifier_| is destroyed, and |proxies_| owns this. + // base::Unretained is safe here because the callback will be + // canceled when |shutdown_notifier_| is destroyed, and |proxies_| + // owns this. shutdown_notifier_ = ShutdownNotifierFactory::GetInstance() ->Get(browser_context) @@ -1032,14 +1147,16 @@ void WebRequestProxyingURLLoaderFactory::StartProxying( mojo::PendingReceiver<network::mojom::TrustedURLLoaderHeaderClient> header_client_receiver, WebRequestAPI::ProxySet* proxies, - content::ContentBrowserClient::URLLoaderFactoryType loader_factory_type) { + content::ContentBrowserClient::URLLoaderFactoryType loader_factory_type, + ukm::SourceId ukm_source_id) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); auto proxy = std::make_unique<WebRequestProxyingURLLoaderFactory>( browser_context, render_process_id, request_id_generator, std::move(navigation_ui_data), std::move(navigation_id), std::move(loader_receiver), std::move(target_factory_remote), - std::move(header_client_receiver), proxies, loader_factory_type); + std::move(header_client_receiver), proxies, loader_factory_type, + ukm_source_id); proxies->AddProxy(std::move(proxy)); } @@ -1054,35 +1171,36 @@ void WebRequestProxyingURLLoaderFactory::CreateLoaderAndStart( const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - // Make sure we are not proxying a browser initiated non-navigation request - // except for loading service worker scripts. + // Make sure we are not proxying a browser initiated non-navigation + // request except for loading service worker scripts. DCHECK(render_process_id_ != -1 || navigation_ui_data_ || IsForServiceWorkerScript()); - // The |web_request_id| doesn't really matter. It just needs to be unique - // per-BrowserContext so extensions can make sense of it. Note that - // |network_service_request_id_| by contrast is not necessarily unique, so we - // don't use it for identity here. This request ID may be the same as a - // previous request if the previous request was redirected to a URL that - // required a different loader. + // The |web_request_id| doesn't really matter. It just needs to be + // unique per-BrowserContext so extensions can make sense of it. + // Note that |network_service_request_id_| by contrast is not + // necessarily unique, so we don't use it for identity here. This + // request ID may be the same as a previous request if the previous + // request was redirected to a URL that required a different loader. const uint64_t web_request_id = request_id_generator_->Generate(routing_id, request_id); if (request_id) { - // Only requests with a non-zero request ID can have their proxy associated - // with said ID. This is necessary to support correlation against any auth - // events received by the browser. Requests with a request ID of 0 therefore - // do not support dispatching |WebRequest.onAuthRequired| events. + // Only requests with a non-zero request ID can have their proxy + // associated with said ID. This is necessary to support + // correlation against any auth events received by the browser. + // Requests with a request ID of 0 therefore do not support + // dispatching |WebRequest.onAuthRequired| events. proxies_->AssociateProxyWithRequestId( this, content::GlobalRequestID(render_process_id_, request_id)); network_request_id_to_web_request_id_.emplace(request_id, web_request_id); } auto result = requests_.emplace( - web_request_id, - std::make_unique<InProgressRequest>( - this, web_request_id, request_id, routing_id, options, request, - traffic_annotation, std::move(loader_receiver), std::move(client))); + web_request_id, std::make_unique<InProgressRequest>( + this, web_request_id, request_id, routing_id, options, + ukm_source_id_, request, traffic_annotation, + std::move(loader_receiver), std::move(client))); result.first->second->Restart(); } diff --git a/chromium/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h b/chromium/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h index 35c4bda6134..67e79302271 100644 --- a/chromium/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h +++ b/chromium/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h @@ -25,6 +25,7 @@ #include "mojo/public/cpp/bindings/remote.h" #include "net/base/completion_once_callback.h" #include "net/traffic_annotation/network_traffic_annotation.h" +#include "services/metrics/public/cpp/ukm_source_id.h" #include "services/network/public/cpp/resource_request.h" #include "services/network/public/mojom/network_context.mojom.h" #include "services/network/public/mojom/url_loader.mojom.h" @@ -54,6 +55,7 @@ class WebRequestProxyingURLLoaderFactory int32_t routing_id, int32_t network_service_request_id, uint32_t options, + ukm::SourceId ukm_source_id, const network::ResourceRequest& request, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver, @@ -106,15 +108,41 @@ class WebRequestProxyingURLLoaderFactory OnHeadersReceivedCallback callback) override; private: + // The state of an InprogressRequest. This is reported via UMA and UKM + // at the end of the request, so do not change enum values. + enum State { + kInProgress = 0, + kInProgressWithFinalResponseReceived, + kInvalid, // This is an invalid state and must not be recorded. + kRedirectFollowedByAnotherInProgressRequest, + kRejectedByNetworkError, + kRejectedByNetworkErrorAfterReceivingFinalResponse, + kDetachedFromClient, + kDetachedFromClientAfterReceivingResponse, + kRejectedByOnBeforeRequest, + kRejectedByOnBeforeSendHeaders, + kRejectedByOnHeadersReceivedForFinalResponse, + kRejectedByOnHeadersReceivedForRedirect, + kRejectedByOnHeadersReceivedForAuth, + kRejectedByOnAuthRequired, + kCompleted, + kMaxValue = kCompleted, + }; // These two methods combined form the implementation of Restart(). void UpdateRequestInfo(); void RestartInternal(); - void ContinueToBeforeSendHeaders(int error_code); - void ContinueToSendHeaders(const std::set<std::string>& removed_headers, + void ContinueToBeforeSendHeaders(State state_on_error, int error_code); + void ContinueToBeforeSendHeadersWithOk(); + void ContinueToSendHeaders(State state_on_error, + const std::set<std::string>& removed_headers, const std::set<std::string>& set_headers, int error_code); - void ContinueToStartRequest(int error_code); + void ContinueToSendHeadersWithOk( + const std::set<std::string>& removed_headers, + const std::set<std::string>& set_headers); + void ContinueToStartRequest(State state_on_error, int error_code); + void ContinueToStartRequestWithOk(); void ContinueToHandleOverrideHeaders(int error_code); void ContinueToResponseStarted(int error_code); void ContinueAuthRequest(const net::AuthChallengeInfo& auth_info, @@ -127,7 +155,10 @@ class WebRequestProxyingURLLoaderFactory int error_code); void HandleResponseOrRedirectHeaders( net::CompletionOnceCallback continuation); - void OnRequestError(const network::URLLoaderCompletionStatus& status); + void OnRequestError(const network::URLLoaderCompletionStatus& status, + State state); + void OnNetworkError(const network::URLLoaderCompletionStatus& status); + void OnClientDisconnected(); void OnLoaderDisconnected(uint32_t custom_reason, const std::string& description); bool IsRedirectSafe(const GURL& from_url, @@ -142,6 +173,7 @@ class WebRequestProxyingURLLoaderFactory const int32_t network_service_request_id_ = 0; const int32_t routing_id_ = 0; const uint32_t options_ = 0; + const ukm::SourceId ukm_source_id_; const net::MutableNetworkTrafficAnnotationTag traffic_annotation_; mojo::Receiver<network::mojom::URLLoader> proxied_loader_receiver_; mojo::Remote<network::mojom::URLLoaderClient> target_client_; @@ -179,6 +211,7 @@ class WebRequestProxyingURLLoaderFactory OnHeadersReceivedCallback on_headers_received_callback_; mojo::Receiver<network::mojom::TrustedHeaderClient> header_client_receiver_{ this}; + bool is_header_client_receiver_paused_ = false; // If |has_any_extra_headers_listeners_| is set to false and a redirect is // in progress, this stores the parameters to FollowRedirect that came from @@ -195,6 +228,7 @@ class WebRequestProxyingURLLoaderFactory DISALLOW_COPY_AND_ASSIGN(FollowRedirectParams); }; std::unique_ptr<FollowRedirectParams> pending_follow_redirect_params_; + State state_ = State::kInProgress; base::WeakPtrFactory<InProgressRequest> weak_factory_{this}; @@ -213,7 +247,8 @@ class WebRequestProxyingURLLoaderFactory mojo::PendingReceiver<network::mojom::TrustedURLLoaderHeaderClient> header_client_receiver, WebRequestAPI::ProxySet* proxies, - content::ContentBrowserClient::URLLoaderFactoryType loader_factory_type); + content::ContentBrowserClient::URLLoaderFactoryType loader_factory_type, + ukm::SourceId ukm_source_id); ~WebRequestProxyingURLLoaderFactory() override; @@ -229,7 +264,8 @@ class WebRequestProxyingURLLoaderFactory mojo::PendingReceiver<network::mojom::TrustedURLLoaderHeaderClient> header_client_receiver, WebRequestAPI::ProxySet* proxies, - content::ContentBrowserClient::URLLoaderFactoryType loader_factory_type); + content::ContentBrowserClient::URLLoaderFactoryType loader_factory_type, + ukm::SourceId ukm_source_id); // network::mojom::URLLoaderFactory: void CreateLoaderAndStart( @@ -289,6 +325,9 @@ class WebRequestProxyingURLLoaderFactory const content::ContentBrowserClient::URLLoaderFactoryType loader_factory_type_; + // A UKM source ID associated with the content::WebContents of the initiator + // frame. + const ukm::SourceId ukm_source_id_; // Mapping from our own internally generated request ID to an // InProgressRequest instance. |