| // Copyright 2016 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 "content/browser/service_worker/link_header_support.h" |
| |
| #include "base/command_line.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "components/link_header_util/link_header_util.h" |
| #include "content/browser/loader/resource_message_filter.h" |
| #include "content/browser/loader/resource_request_info_impl.h" |
| #include "content/browser/service_worker/service_worker_context_wrapper.h" |
| #include "content/browser/service_worker/service_worker_request_handler.h" |
| #include "content/common/origin_trials/trial_token_validator.h" |
| #include "content/common/service_worker/service_worker_utils.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/common/browser_side_navigation_policy.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/origin_util.h" |
| #include "net/http/http_util.h" |
| #include "net/url_request/url_request.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| void RegisterServiceWorkerFinished(int64_t trace_id, bool result) { |
| TRACE_EVENT_ASYNC_END1("ServiceWorker", |
| "LinkHeaderResourceThrottle::HandleServiceWorkerLink", |
| trace_id, "Success", result); |
| } |
| |
| void HandleServiceWorkerLink( |
| const net::URLRequest* request, |
| const std::string& url, |
| const std::unordered_map<std::string, base::Optional<std::string>>& params, |
| ServiceWorkerContextWrapper* service_worker_context_for_testing) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures) && |
| !TrialTokenValidator::RequestEnablesFeature(request, "ForeignFetch")) { |
| // TODO(mek): Log attempt to use without having correct token? |
| return; |
| } |
| |
| if (ContainsKey(params, "anchor")) |
| return; |
| |
| const ResourceRequestInfoImpl* request_info = |
| ResourceRequestInfoImpl::ForRequest(request); |
| ResourceMessageFilter* filter = request_info->filter(); |
| ServiceWorkerContext* service_worker_context = |
| filter ? filter->service_worker_context() |
| : service_worker_context_for_testing; |
| if (IsBrowserSideNavigationEnabled() && |
| ServiceWorkerUtils::IsMainResourceType(request_info->GetResourceType()) && |
| !service_worker_context) { |
| service_worker_context = request_info->service_worker_context(); |
| } |
| |
| if (!service_worker_context) |
| return; |
| |
| ServiceWorkerProviderHost* provider_host = |
| ServiceWorkerRequestHandler::GetProviderHost(request); |
| |
| // If fetched from a service worker, make sure fetching service worker is |
| // controlling at least one client to prevent a service worker from spawning |
| // new service workers in the background. |
| if (provider_host && provider_host->IsHostToRunningServiceWorker()) { |
| if (!provider_host->running_hosted_version()->HasControllee()) |
| return; |
| } |
| |
| if (ServiceWorkerUtils::IsMainResourceType(request_info->GetResourceType())) { |
| // In case of navigations, make sure the navigation will actually result in |
| // a secure context. |
| if (!provider_host || !provider_host->IsContextSecureForServiceWorker()) |
| return; |
| } else { |
| // If this is not a navigation, make sure the request was initiated from a |
| // secure context. |
| if (!request_info->initiated_in_secure_context()) |
| return; |
| } |
| |
| // TODO(mek): support for a serviceworker link on a request that wouldn't ever |
| // be able to be intercepted by a serviceworker isn't very useful, so this |
| // should share logic with ServiceWorkerRequestHandler and |
| // ForeignFetchRequestHandler to limit the requests for which serviceworker |
| // links are processed. |
| |
| GURL context_url = request->url(); |
| GURL script_url = context_url.Resolve(url); |
| auto scope_param = params.find("scope"); |
| GURL scope_url = scope_param == params.end() |
| ? script_url.Resolve("./") |
| : context_url.Resolve(scope_param->second.value_or("")); |
| |
| if (!context_url.is_valid() || !script_url.is_valid() || |
| !scope_url.is_valid()) |
| return; |
| if (!ServiceWorkerUtils::AllOriginsMatchAndCanAccessServiceWorkers( |
| {context_url, scope_url, script_url})) { |
| return; |
| } |
| std::string error; |
| if (ServiceWorkerUtils::ContainsDisallowedCharacter(scope_url, script_url, |
| &error)) |
| return; |
| |
| int render_process_id = -1; |
| int render_frame_id = -1; |
| ResourceRequestInfo::GetRenderFrameForRequest(request, &render_process_id, |
| &render_frame_id); |
| |
| if (!GetContentClient()->browser()->AllowServiceWorker( |
| scope_url, request->first_party_for_cookies(), |
| request_info->GetContext(), render_process_id, render_frame_id)) |
| return; |
| |
| static int64_t trace_id = 0; |
| TRACE_EVENT_ASYNC_BEGIN2( |
| "ServiceWorker", "LinkHeaderResourceThrottle::HandleServiceWorkerLink", |
| ++trace_id, "Pattern", scope_url.spec(), "Script URL", script_url.spec()); |
| service_worker_context->RegisterServiceWorker( |
| scope_url, script_url, |
| base::Bind(&RegisterServiceWorkerFinished, trace_id)); |
| } |
| |
| void ProcessLinkHeaderValueForRequest( |
| const net::URLRequest* request, |
| std::string::const_iterator value_begin, |
| std::string::const_iterator value_end, |
| ServiceWorkerContextWrapper* service_worker_context_for_testing) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| std::string url; |
| std::unordered_map<std::string, base::Optional<std::string>> params; |
| if (!link_header_util::ParseLinkHeaderValue(value_begin, value_end, &url, |
| ¶ms)) |
| return; |
| |
| auto rel_param = params.find("rel"); |
| if (rel_param == params.end() || !rel_param->second) |
| return; |
| for (const auto& rel : base::SplitStringPiece(rel_param->second.value(), |
| HTTP_LWS, base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY)) { |
| if (base::EqualsCaseInsensitiveASCII(rel, "serviceworker")) |
| HandleServiceWorkerLink(request, url, params, |
| service_worker_context_for_testing); |
| } |
| } |
| |
| } // namespace |
| |
| void ProcessRequestForLinkHeaders(const net::URLRequest* request) { |
| std::string link_header; |
| request->GetResponseHeaderByName("link", &link_header); |
| if (link_header.empty()) |
| return; |
| |
| ProcessLinkHeaderForRequest(request, link_header); |
| } |
| |
| void ProcessLinkHeaderForRequest( |
| const net::URLRequest* request, |
| const std::string& link_header, |
| ServiceWorkerContextWrapper* service_worker_context_for_testing) { |
| for (const auto& value : link_header_util::SplitLinkHeader(link_header)) { |
| ProcessLinkHeaderValueForRequest(request, value.first, value.second, |
| service_worker_context_for_testing); |
| } |
| } |
| |
| } // namespace content |