blob: 047e741d013048f60305116c621ca3cbeb018928 [file] [log] [blame]
/*
* Copyright (C) 2010, 2011, 2012 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:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 THE COPYRIGHT
* OWNER 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/loader/web_associated_url_loader_impl.h"
#include <limits>
#include <memory>
#include <utility>
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/optional.h"
#include "services/network/public/cpp/request_destination.h"
#include "services/network/public/cpp/request_mode.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/resource_request_blocked_reason.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_http_header_visitor.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url_error.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/public/web/web_associated_url_loader_client.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
#include "third_party/blink/renderer/core/loader/threadable_loader.h"
#include "third_party/blink/renderer/core/loader/threadable_loader_client.h"
#include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h"
#include "third_party/blink/renderer/platform/exported/wrapped_resource_response.h"
#include "third_party/blink/renderer/platform/loader/cors/cors.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_utils.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
#include "third_party/blink/renderer/platform/network/http_parsers.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/timer.h"
#include "third_party/blink/renderer/platform/weborigin/referrer.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
namespace {
class HTTPRequestHeaderValidator : public WebHTTPHeaderVisitor {
public:
HTTPRequestHeaderValidator() : is_safe_(true) {}
~HTTPRequestHeaderValidator() override = default;
void VisitHeader(const WebString& name, const WebString& value) override;
bool IsSafe() const { return is_safe_; }
private:
bool is_safe_;
DISALLOW_COPY_AND_ASSIGN(HTTPRequestHeaderValidator);
};
void HTTPRequestHeaderValidator::VisitHeader(const WebString& name,
const WebString& value) {
is_safe_ = is_safe_ && IsValidHTTPToken(name) &&
!cors::IsForbiddenHeaderName(name) &&
IsValidHTTPHeaderValue(value);
}
} // namespace
// This class bridges the interface differences between WebCore and WebKit
// loader clients.
// It forwards its ThreadableLoaderClient notifications to a
// WebAssociatedURLLoaderClient.
class WebAssociatedURLLoaderImpl::ClientAdapter final
: public GarbageCollected<ClientAdapter>,
public ThreadableLoaderClient {
public:
ClientAdapter(WebAssociatedURLLoaderImpl*,
WebAssociatedURLLoaderClient*,
const WebAssociatedURLLoaderOptions&,
network::mojom::RequestMode,
network::mojom::CredentialsMode,
scoped_refptr<base::SingleThreadTaskRunner>);
// ThreadableLoaderClient
void DidSendData(uint64_t /*bytesSent*/,
uint64_t /*totalBytesToBeSent*/) override;
void DidReceiveResponse(uint64_t, const ResourceResponse&) override;
void DidDownloadData(uint64_t /*dataLength*/) override;
void DidReceiveData(const char*, unsigned /*dataLength*/) override;
void DidReceiveCachedMetadata(const char*, int /*dataLength*/) override;
void DidFinishLoading(uint64_t /*identifier*/) override;
void DidFail(const ResourceError&) override;
void DidFailRedirectCheck() override;
// ThreadableLoaderClient
bool WillFollowRedirect(
const KURL& /*new_url*/,
const ResourceResponse& /*redirect_response*/) override;
// Sets an error to be reported back to the client, asynchronously.
void SetDelayedError(const ResourceError&);
// Enables forwarding of error notifications to the
// WebAssociatedURLLoaderClient. These
// must be deferred until after the call to
// WebAssociatedURLLoader::loadAsynchronously() completes.
void EnableErrorNotifications();
// Stops loading and releases the ThreadableLoader as early as
// possible.
WebAssociatedURLLoaderClient* ReleaseClient() {
WebAssociatedURLLoaderClient* client = client_;
client_ = nullptr;
return client;
}
private:
void NotifyError(TimerBase*);
WebAssociatedURLLoaderImpl* loader_;
WebAssociatedURLLoaderClient* client_;
WebAssociatedURLLoaderOptions options_;
network::mojom::RequestMode request_mode_;
network::mojom::CredentialsMode credentials_mode_;
base::Optional<WebURLError> error_;
TaskRunnerTimer<ClientAdapter> error_timer_;
bool enable_error_notifications_;
bool did_fail_;
DISALLOW_COPY_AND_ASSIGN(ClientAdapter);
};
WebAssociatedURLLoaderImpl::ClientAdapter::ClientAdapter(
WebAssociatedURLLoaderImpl* loader,
WebAssociatedURLLoaderClient* client,
const WebAssociatedURLLoaderOptions& options,
network::mojom::RequestMode request_mode,
network::mojom::CredentialsMode credentials_mode,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: loader_(loader),
client_(client),
options_(options),
request_mode_(request_mode),
credentials_mode_(credentials_mode),
error_timer_(std::move(task_runner), this, &ClientAdapter::NotifyError),
enable_error_notifications_(false),
did_fail_(false) {
DCHECK(loader_);
DCHECK(client_);
}
bool WebAssociatedURLLoaderImpl::ClientAdapter::WillFollowRedirect(
const KURL& new_url,
const ResourceResponse& redirect_response) {
if (!client_)
return true;
WebURL wrapped_new_url(new_url);
WrappedResourceResponse wrapped_redirect_response(redirect_response);
return client_->WillFollowRedirect(wrapped_new_url,
wrapped_redirect_response);
}
void WebAssociatedURLLoaderImpl::ClientAdapter::DidSendData(
uint64_t bytes_sent,
uint64_t total_bytes_to_be_sent) {
if (!client_)
return;
client_->DidSendData(bytes_sent, total_bytes_to_be_sent);
}
void WebAssociatedURLLoaderImpl::ClientAdapter::DidReceiveResponse(
uint64_t,
const ResourceResponse& response) {
if (!client_)
return;
if (options_.expose_all_response_headers ||
(request_mode_ != network::mojom::RequestMode::kCors &&
request_mode_ !=
network::mojom::RequestMode::kCorsWithForcedPreflight)) {
// Use the original ResourceResponse.
client_->DidReceiveResponse(WrappedResourceResponse(response));
return;
}
HTTPHeaderSet exposed_headers =
cors::ExtractCorsExposedHeaderNamesList(credentials_mode_, response);
HTTPHeaderSet blocked_headers;
for (const auto& header : response.HttpHeaderFields()) {
if (FetchUtils::IsForbiddenResponseHeaderName(header.key) ||
(!cors::IsCorsSafelistedResponseHeader(header.key) &&
exposed_headers.find(header.key.Ascii()) == exposed_headers.end()))
blocked_headers.insert(header.key.Ascii());
}
if (blocked_headers.empty()) {
// Use the original ResourceResponse.
client_->DidReceiveResponse(WrappedResourceResponse(response));
return;
}
// If there are blocked headers, copy the response so we can remove them.
WebURLResponse validated_response = WrappedResourceResponse(response);
for (const auto& header : blocked_headers)
validated_response.ClearHttpHeaderField(WebString::FromASCII(header));
client_->DidReceiveResponse(validated_response);
}
void WebAssociatedURLLoaderImpl::ClientAdapter::DidDownloadData(
uint64_t data_length) {
if (!client_)
return;
client_->DidDownloadData(data_length);
}
void WebAssociatedURLLoaderImpl::ClientAdapter::DidReceiveData(
const char* data,
unsigned data_length) {
if (!client_)
return;
CHECK_LE(data_length, static_cast<unsigned>(std::numeric_limits<int>::max()));
client_->DidReceiveData(data, data_length);
}
void WebAssociatedURLLoaderImpl::ClientAdapter::DidReceiveCachedMetadata(
const char* data,
int data_length) {
if (!client_)
return;
client_->DidReceiveCachedMetadata(data, data_length);
}
void WebAssociatedURLLoaderImpl::ClientAdapter::DidFinishLoading(
uint64_t identifier) {
if (!client_)
return;
loader_->ClientAdapterDone();
ReleaseClient()->DidFinishLoading();
// |this| may be dead here.
}
void WebAssociatedURLLoaderImpl::ClientAdapter::DidFail(
const ResourceError& error) {
if (!client_)
return;
loader_->ClientAdapterDone();
did_fail_ = true;
error_ = static_cast<WebURLError>(error);
if (enable_error_notifications_)
NotifyError(&error_timer_);
}
void WebAssociatedURLLoaderImpl::ClientAdapter::DidFailRedirectCheck() {
DidFail(ResourceError::Failure(NullURL()));
}
void WebAssociatedURLLoaderImpl::ClientAdapter::EnableErrorNotifications() {
enable_error_notifications_ = true;
// If an error has already been received, start a timer to report it to the
// client after WebAssociatedURLLoader::loadAsynchronously has returned to the
// caller.
if (did_fail_)
error_timer_.StartOneShot(base::TimeDelta(), FROM_HERE);
}
void WebAssociatedURLLoaderImpl::ClientAdapter::NotifyError(TimerBase* timer) {
DCHECK_EQ(timer, &error_timer_);
if (client_) {
DCHECK(error_);
ReleaseClient()->DidFail(*error_);
}
// |this| may be dead here.
}
class WebAssociatedURLLoaderImpl::Observer final
: public GarbageCollected<Observer>,
public ExecutionContextLifecycleObserver {
public:
Observer(WebAssociatedURLLoaderImpl* parent, ExecutionContext* context)
: ExecutionContextLifecycleObserver(context), parent_(parent) {}
void Dispose() {
parent_ = nullptr;
// TODO(keishi): Remove IsIteratingOverObservers() check when
// HeapObserverSet() supports removal while iterating.
if (!GetExecutionContext()
->ContextLifecycleObserverSet()
.IsIteratingOverObservers()) {
SetExecutionContext(nullptr);
}
}
void ContextDestroyed() override {
if (parent_)
parent_->ContextDestroyed();
}
void Trace(Visitor* visitor) const override {
ExecutionContextLifecycleObserver::Trace(visitor);
}
WebAssociatedURLLoaderImpl* parent_;
};
WebAssociatedURLLoaderImpl::WebAssociatedURLLoaderImpl(
ExecutionContext* context,
const WebAssociatedURLLoaderOptions& options)
: client_(nullptr),
options_(options),
observer_(MakeGarbageCollected<Observer>(this, context)) {}
WebAssociatedURLLoaderImpl::~WebAssociatedURLLoaderImpl() {
Cancel();
}
void WebAssociatedURLLoaderImpl::LoadAsynchronously(
const WebURLRequest& request,
WebAssociatedURLLoaderClient* client) {
DCHECK(!client_);
DCHECK(!loader_);
DCHECK(!client_adapter_);
DCHECK(client);
bool allow_load = true;
WebURLRequest new_request;
new_request.CopyFrom(request);
if (options_.untrusted_http) {
WebString method = new_request.HttpMethod();
allow_load = observer_ && IsValidHTTPToken(method) &&
!FetchUtils::IsForbiddenMethod(method);
if (allow_load) {
new_request.SetHttpMethod(FetchUtils::NormalizeMethod(method));
HTTPRequestHeaderValidator validator;
new_request.VisitHttpHeaderFields(&validator);
// The request's referrer string is not stored as a header, so we must
// consult it separately, if set.
if (request.ReferrerString() !=
blink::WebString(Referrer::ClientReferrerString())) {
DCHECK(cors::IsForbiddenHeaderName("Referer"));
// `Referer` is a forbidden header name, so we must disallow this to
// load.
allow_load = false;
}
allow_load = allow_load && validator.IsSafe();
}
}
new_request.ToMutableResourceRequest().SetCorsPreflightPolicy(
options_.preflight_policy);
scoped_refptr<base::SingleThreadTaskRunner> task_runner;
// |observer_| can be null if Cancel, ContextDestroyed or
// ClientAdapterDone gets called between creating the loader and
// calling LoadAsynchronously.
if (observer_) {
task_runner = observer_->GetExecutionContext()->GetTaskRunner(
TaskType::kInternalLoading);
} else {
task_runner = Thread::Current()->GetTaskRunner();
}
client_ = client;
client_adapter_ = MakeGarbageCollected<ClientAdapter>(
this, client, options_, request.GetMode(), request.GetCredentialsMode(),
std::move(task_runner));
if (allow_load) {
ResourceLoaderOptions resource_loader_options(
observer_->GetExecutionContext()->GetCurrentWorld());
resource_loader_options.data_buffering_policy = kDoNotBufferData;
if (options_.grant_universal_access) {
const auto request_mode = new_request.GetMode();
DCHECK(request_mode == network::mojom::RequestMode::kNoCors ||
request_mode == network::mojom::RequestMode::kNavigate);
// Some callers, notablly flash, with |grant_universal_access| want to
// have an origin matching with referrer.
KURL referrer(request.ToResourceRequest().ReferrerString());
scoped_refptr<SecurityOrigin> origin = SecurityOrigin::Create(referrer);
origin->GrantUniversalAccess();
new_request.ToMutableResourceRequest().SetRequestorOrigin(origin);
}
ResourceRequest& webcore_request = new_request.ToMutableResourceRequest();
mojom::RequestContextType context = webcore_request.GetRequestContext();
if (context == mojom::RequestContextType::UNSPECIFIED) {
// TODO(yoav): We load URLs without setting a TargetType (and therefore a
// request context) in several places in content/
// (P2PPortAllocatorSession::AllocateLegacyRelaySession, for example).
// Remove this once those places are patched up.
new_request.SetRequestContext(mojom::RequestContextType::INTERNAL);
new_request.SetRequestDestination(
network::mojom::RequestDestination::kEmpty);
} else if (context == mojom::RequestContextType::VIDEO) {
resource_loader_options.initiator_info.name =
fetch_initiator_type_names::kVideo;
} else if (context == mojom::RequestContextType::AUDIO) {
resource_loader_options.initiator_info.name =
fetch_initiator_type_names::kAudio;
}
if (observer_) {
loader_ = MakeGarbageCollected<ThreadableLoader>(
*observer_->GetExecutionContext(), client_adapter_,
resource_loader_options);
loader_->Start(std::move(webcore_request));
}
}
if (!loader_) {
client_adapter_->DidFail(ResourceError::CancelledDueToAccessCheckError(
request.Url(), ResourceRequestBlockedReason::kOther));
}
client_adapter_->EnableErrorNotifications();
}
void WebAssociatedURLLoaderImpl::Cancel() {
DisposeObserver();
CancelLoader();
ReleaseClient();
}
void WebAssociatedURLLoaderImpl::ClientAdapterDone() {
DisposeObserver();
ReleaseClient();
}
void WebAssociatedURLLoaderImpl::CancelLoader() {
if (!client_adapter_)
return;
// Prevent invocation of the WebAssociatedURLLoaderClient methods.
client_adapter_->ReleaseClient();
if (loader_) {
loader_->Cancel();
loader_ = nullptr;
}
client_adapter_ = nullptr;
}
void WebAssociatedURLLoaderImpl::SetDefersLoading(bool defers_loading) {
if (loader_)
loader_->SetDefersLoading(defers_loading);
}
void WebAssociatedURLLoaderImpl::SetLoadingTaskRunner(
base::SingleThreadTaskRunner*) {
// TODO(alexclarke): Maybe support this one day if it proves worthwhile.
}
void WebAssociatedURLLoaderImpl::ContextDestroyed() {
DisposeObserver();
CancelLoader();
if (!client_)
return;
ReleaseClient()->DidFail(WebURLError(ResourceError::CancelledError(KURL())));
// |this| may be dead here.
}
void WebAssociatedURLLoaderImpl::DisposeObserver() {
if (!observer_)
return;
// TODO(tyoshino): Remove this assert once Document is fixed so that
// contextDestroyed() is invoked for all kinds of Documents.
//
// Currently, the method of detecting Document destruction implemented here
// doesn't work for all kinds of Documents. In case we reached here after
// the Oilpan is destroyed, we just crash the renderer process to prevent
// UaF.
//
// We could consider just skipping the rest of code in case
// ThreadState::current() is null. However, the fact we reached here
// without cancelling the loader means that it's possible there're some
// non-Blink non-on-heap objects still facing on-heap Blink objects. E.g.
// there could be a WebURLLoader instance behind the
// ThreadableLoader instance. So, for safety, we chose to just
// crash here.
CHECK(ThreadState::Current());
observer_->Dispose();
observer_ = nullptr;
}
} // namespace blink