| /* |
| * 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 { |
| |
| 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; |
| forbid_cross_origin_redirects_ = 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 (forbid_cross_origin_redirects_ && url_ != response.CurrentRequestUrl() && |
| !SecurityOrigin::AreSameSchemeHostPort(url_, |
| response.CurrentRequestUrl())) { |
| // Forbid cross-origin redirects to ensure the request and response URLs |
| // have the same SecurityOrigin. |
| fetch_client_settings_object_fetcher_->Context().AddErrorConsoleMessage( |
| "Refused to cross-origin redirects of the top-level worker script.", |
| FetchContext::kSecuritySource); |
| NotifyError(); |
| return; |
| } |
| |
| identifier_ = identifier; |
| response_url_ = response.ResponseUrl(); |
| // The response URL may be empty if a service worker did respondWith(new |
| // Response()) to generate a response. |
| // TODO(falken): Change this to an empty URL if that is indeed spec |
| // conformant. |
| if (response_url_.IsEmpty()) |
| response_url_ = response.CurrentRequestUrl(); |
| |
| 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<char>>(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 |