| /* |
| * Copyright (C) 2009 Apple Inc. All Rights Reserved. |
| * Copyright (C) 2009, 2011 Google Inc. All Rights Reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| */ |
| |
| #include "third_party/blink/renderer/core/workers/worker_classic_script_loader.h" |
| |
| #include <memory> |
| #include "base/memory/scoped_refptr.h" |
| #include "third_party/blink/public/mojom/net/ip_address_space.mojom-blink.h" |
| #include "third_party/blink/renderer/core/execution_context/execution_context.h" |
| #include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h" |
| #include "third_party/blink/renderer/core/inspector/console_message.h" |
| #include "third_party/blink/renderer/core/loader/resource/script_resource.h" |
| #include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h" |
| #include "third_party/blink/renderer/core/workers/worker_global_scope.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h" |
| #include "third_party/blink/renderer/platform/network/content_security_policy_response_headers.h" |
| #include "third_party/blink/renderer/platform/network/http_names.h" |
| #include "third_party/blink/renderer/platform/network/network_utils.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_origin.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| // CheckSameOriginEnforcement() functions return non-null String on error. |
| // |
| // WorkerGlobalScope's SecurityOrigin is initialized to request URL's |
| // origin at the construction of WorkerGlobalScope, while |
| // WorkerGlobalScope's URL is set to response URL |
| // (ResourceResponse::ResponseURL()). |
| // These functions are used to ensure the SecurityOrigin and the URL to be |
| // consistent. https://crbug.com/861564 |
| // |
| // TODO(hiroshige): Merge with similar code in other places. |
| String CheckSameOriginEnforcement(const KURL& request_url, |
| const KURL& response_url) { |
| if (request_url != response_url && |
| !SecurityOrigin::AreSameSchemeHostPort(request_url, response_url)) { |
| return "Refused to load the top-level worker script from '" + |
| response_url.ElidedString() + |
| "' because it doesn't match the origin of the request URL '" + |
| request_url.ElidedString() + "'"; |
| } |
| return String(); |
| } |
| |
| String CheckSameOriginEnforcement(const KURL& request_url, |
| const ResourceResponse& response) { |
| // While this check is not strictly necessary as CurrentRequestUrl() is not |
| // used as WorkerGlobalScope's URL, it is probably safer to reject cases like |
| // Origin A(request_url) |
| // =(cross-origin redirects)=> Origin B(CurrentRequestUrl()) |
| // =(ServiceWorker interception)=> Origin A(ResponseUrl()) |
| // which doesn't seem to have valid use cases. |
| String error = |
| CheckSameOriginEnforcement(request_url, response.CurrentRequestUrl()); |
| if (!error.IsNull()) |
| return error; |
| |
| // This check is directly required to ensure the consistency between |
| // WorkerGlobalScope's SecurityOrigin and URL. |
| return CheckSameOriginEnforcement(request_url, response.ResponseUrl()); |
| } |
| |
| } // namespace |
| |
| WorkerClassicScriptLoader::WorkerClassicScriptLoader() |
| : response_address_space_(mojom::IPAddressSpace::kPublic) {} |
| |
| void WorkerClassicScriptLoader::LoadSynchronously( |
| ExecutionContext& execution_context, |
| ResourceFetcher* fetch_client_settings_object_fetcher, |
| const KURL& url, |
| mojom::RequestContextType request_context, |
| mojom::IPAddressSpace creation_address_space) { |
| DCHECK(fetch_client_settings_object_fetcher); |
| url_ = url; |
| fetch_client_settings_object_fetcher_ = fetch_client_settings_object_fetcher; |
| |
| ResourceRequest request(url); |
| request.SetHTTPMethod(http_names::kGET); |
| request.SetExternalRequestStateFromRequestorAddressSpace( |
| creation_address_space); |
| request.SetRequestContext(request_context); |
| |
| SECURITY_DCHECK(execution_context.IsWorkerGlobalScope()); |
| |
| ResourceLoaderOptions resource_loader_options; |
| resource_loader_options.parser_disposition = |
| ParserDisposition::kNotParserInserted; |
| resource_loader_options.synchronous_policy = kRequestSynchronously; |
| |
| threadable_loader_ = MakeGarbageCollected<ThreadableLoader>( |
| execution_context, this, resource_loader_options, |
| fetch_client_settings_object_fetcher); |
| threadable_loader_->Start(request); |
| } |
| |
| void WorkerClassicScriptLoader::LoadTopLevelScriptAsynchronously( |
| ExecutionContext& execution_context, |
| ResourceFetcher* fetch_client_settings_object_fetcher, |
| const KURL& url, |
| mojom::RequestContextType request_context, |
| network::mojom::FetchRequestMode fetch_request_mode, |
| network::mojom::FetchCredentialsMode fetch_credentials_mode, |
| mojom::IPAddressSpace creation_address_space, |
| base::OnceClosure response_callback, |
| base::OnceClosure finished_callback) { |
| DCHECK(fetch_client_settings_object_fetcher); |
| DCHECK(response_callback || finished_callback); |
| response_callback_ = std::move(response_callback); |
| finished_callback_ = std::move(finished_callback); |
| url_ = url; |
| fetch_client_settings_object_fetcher_ = fetch_client_settings_object_fetcher; |
| is_top_level_script_ = true; |
| |
| is_worker_global_scope_ = execution_context.IsWorkerGlobalScope(); |
| |
| ResourceRequest request(url); |
| request.SetHTTPMethod(http_names::kGET); |
| request.SetExternalRequestStateFromRequestorAddressSpace( |
| creation_address_space); |
| request.SetRequestContext(request_context); |
| request.SetFetchRequestMode(fetch_request_mode); |
| request.SetFetchCredentialsMode(fetch_credentials_mode); |
| |
| need_to_cancel_ = true; |
| threadable_loader_ = MakeGarbageCollected<ThreadableLoader>( |
| execution_context, this, ResourceLoaderOptions(), |
| fetch_client_settings_object_fetcher); |
| threadable_loader_->Start(request); |
| if (failed_) |
| NotifyFinished(); |
| } |
| |
| const KURL& WorkerClassicScriptLoader::ResponseURL() const { |
| DCHECK(!Failed()); |
| return response_url_; |
| } |
| |
| void WorkerClassicScriptLoader::DidReceiveResponse( |
| unsigned long identifier, |
| const ResourceResponse& response, |
| std::unique_ptr<WebDataConsumerHandle> handle) { |
| DCHECK(!handle); |
| if (response.HttpStatusCode() / 100 != 2 && response.HttpStatusCode()) { |
| NotifyError(); |
| return; |
| } |
| if (!AllowedByNosniff::MimeTypeAsScript( |
| fetch_client_settings_object_fetcher_->Context(), response, |
| fetch_client_settings_object_fetcher_->Context() |
| .GetFetchClientSettingsObject() |
| ->MimeTypeCheckForClassicWorkerScript(), |
| is_worker_global_scope_)) { |
| NotifyError(); |
| return; |
| } |
| |
| if (is_top_level_script_) { |
| String error = CheckSameOriginEnforcement(url_, response); |
| if (!error.IsNull()) { |
| fetch_client_settings_object_fetcher_->Context().AddErrorConsoleMessage( |
| error, FetchContext::kSecuritySource); |
| NotifyError(); |
| return; |
| } |
| } |
| |
| identifier_ = identifier; |
| response_url_ = response.ResponseUrl(); |
| response_encoding_ = response.TextEncodingName(); |
| app_cache_id_ = response.AppCacheID(); |
| |
| referrer_policy_ = response.HttpHeaderField(http_names::kReferrerPolicy); |
| ProcessContentSecurityPolicy(response); |
| origin_trial_tokens_ = OriginTrialContext::ParseHeaderValue( |
| response.HttpHeaderField(http_names::kOriginTrial)); |
| |
| if (network_utils::IsReservedIPAddress(response.RemoteIPAddress())) { |
| response_address_space_ = |
| SecurityOrigin::Create(response_url_)->IsLocalhost() |
| ? mojom::IPAddressSpace::kLocal |
| : mojom::IPAddressSpace::kPrivate; |
| } |
| |
| if (response_callback_) |
| std::move(response_callback_).Run(); |
| } |
| |
| void WorkerClassicScriptLoader::DidReceiveData(const char* data, unsigned len) { |
| if (failed_) |
| return; |
| |
| if (!decoder_) { |
| decoder_ = TextResourceDecoder::Create(TextResourceDecoderOptions( |
| TextResourceDecoderOptions::kPlainTextContent, |
| response_encoding_.IsEmpty() ? UTF8Encoding() |
| : WTF::TextEncoding(response_encoding_))); |
| } |
| |
| if (!len) |
| return; |
| |
| source_text_.Append(decoder_->Decode(data, len)); |
| } |
| |
| void WorkerClassicScriptLoader::DidReceiveCachedMetadata(const char* data, |
| int size) { |
| cached_metadata_ = std::make_unique<Vector<uint8_t>>(size); |
| memcpy(cached_metadata_->data(), data, size); |
| } |
| |
| void WorkerClassicScriptLoader::DidFinishLoading(unsigned long identifier) { |
| need_to_cancel_ = false; |
| if (!failed_ && decoder_) |
| source_text_.Append(decoder_->Flush()); |
| |
| NotifyFinished(); |
| } |
| |
| void WorkerClassicScriptLoader::DidFail(const ResourceError& error) { |
| need_to_cancel_ = false; |
| canceled_ = error.IsCancellation(); |
| NotifyError(); |
| } |
| |
| void WorkerClassicScriptLoader::DidFailRedirectCheck() { |
| // When didFailRedirectCheck() is called, the ResourceLoader for the script |
| // is not canceled yet. So we don't reset |m_needToCancel| here. |
| NotifyError(); |
| } |
| |
| void WorkerClassicScriptLoader::Trace(Visitor* visitor) { |
| visitor->Trace(threadable_loader_); |
| visitor->Trace(content_security_policy_); |
| visitor->Trace(fetch_client_settings_object_fetcher_); |
| ThreadableLoaderClient::Trace(visitor); |
| } |
| |
| void WorkerClassicScriptLoader::Cancel() { |
| if (!need_to_cancel_) |
| return; |
| need_to_cancel_ = false; |
| if (threadable_loader_) |
| threadable_loader_->Cancel(); |
| } |
| |
| String WorkerClassicScriptLoader::SourceText() { |
| return source_text_.ToString(); |
| } |
| |
| void WorkerClassicScriptLoader::NotifyError() { |
| failed_ = true; |
| // NotifyError() could be called before ThreadableLoader::Create() returns |
| // e.g. from DidFail(), and in that case threadable_loader_ is not yet set |
| // (i.e. still null). |
| // Since the callback invocation in NotifyFinished() potentially delete |
| // |this| object, the callback invocation should be postponed until the |
| // create() call returns. See LoadAsynchronously() for the postponed call. |
| if (threadable_loader_) |
| NotifyFinished(); |
| } |
| |
| void WorkerClassicScriptLoader::NotifyFinished() { |
| if (!finished_callback_) |
| return; |
| |
| std::move(finished_callback_).Run(); |
| } |
| |
| void WorkerClassicScriptLoader::ProcessContentSecurityPolicy( |
| const ResourceResponse& response) { |
| // Per http://www.w3.org/TR/CSP2/#processing-model-workers, if the Worker's |
| // URL is not a GUID, then it grabs its CSP from the response headers |
| // directly. Otherwise, the Worker inherits the policy from the parent |
| // document (which is implemented in WorkerMessagingProxy, and |
| // m_contentSecurityPolicy should be left as nullptr to inherit the policy). |
| if (!response.CurrentRequestUrl().ProtocolIs("blob") && |
| !response.CurrentRequestUrl().ProtocolIs("file") && |
| !response.CurrentRequestUrl().ProtocolIs("filesystem")) { |
| content_security_policy_ = ContentSecurityPolicy::Create(); |
| content_security_policy_->SetOverrideURLForSelf( |
| response.CurrentRequestUrl()); |
| content_security_policy_->DidReceiveHeaders( |
| ContentSecurityPolicyResponseHeaders(response)); |
| } |
| } |
| |
| } // namespace blink |