blob: 833466a210bf5ca4426f5f15241e77c986045240 [file] [log] [blame]
/*
* 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