blob: c4f1754af790e14caed032f7f2d08d6a133de01a [file] [log] [blame]
/*
* Copyright (C) 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 AND ITS 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 APPLE 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/platform/loader/fetch/raw_resource.h"
#include <memory>
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/public/mojom/loader/request_context_frame_type.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/platform/loader/fetch/buffering_bytes_consumer.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_client_walker.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/response_body_loader.h"
#include "third_party/blink/renderer/platform/network/http_names.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
namespace blink {
RawResource* RawResource::FetchSynchronously(FetchParameters& params,
ResourceFetcher* fetcher,
RawResourceClient* client) {
params.MakeSynchronous();
return ToRawResource(fetcher->RequestResource(
params, RawResourceFactory(ResourceType::kRaw), client));
}
RawResource* RawResource::FetchImport(FetchParameters& params,
ResourceFetcher* fetcher,
RawResourceClient* client) {
params.SetRequestContext(mojom::blink::RequestContextType::IMPORT);
params.SetRequestDestination(network::mojom::RequestDestination::kEmpty);
return ToRawResource(fetcher->RequestResource(
params, RawResourceFactory(ResourceType::kImportResource), client));
}
RawResource* RawResource::Fetch(FetchParameters& params,
ResourceFetcher* fetcher,
RawResourceClient* client) {
DCHECK_NE(params.GetResourceRequest().GetRequestContext(),
mojom::blink::RequestContextType::UNSPECIFIED);
return ToRawResource(fetcher->RequestResource(
params, RawResourceFactory(ResourceType::kRaw), client));
}
RawResource* RawResource::FetchMedia(FetchParameters& params,
ResourceFetcher* fetcher,
RawResourceClient* client) {
auto context = params.GetResourceRequest().GetRequestContext();
DCHECK(context == mojom::blink::RequestContextType::AUDIO ||
context == mojom::blink::RequestContextType::VIDEO);
ResourceType type = (context == mojom::blink::RequestContextType::AUDIO)
? ResourceType::kAudio
: ResourceType::kVideo;
return ToRawResource(
fetcher->RequestResource(params, RawResourceFactory(type), client));
}
RawResource* RawResource::FetchTextTrack(FetchParameters& params,
ResourceFetcher* fetcher,
RawResourceClient* client) {
params.SetRequestContext(mojom::blink::RequestContextType::TRACK);
params.SetRequestDestination(network::mojom::RequestDestination::kTrack);
return ToRawResource(fetcher->RequestResource(
params, RawResourceFactory(ResourceType::kTextTrack), client));
}
RawResource* RawResource::FetchManifest(FetchParameters& params,
ResourceFetcher* fetcher,
RawResourceClient* client) {
DCHECK_EQ(params.GetResourceRequest().GetRequestContext(),
mojom::blink::RequestContextType::MANIFEST);
return ToRawResource(fetcher->RequestResource(
params, RawResourceFactory(ResourceType::kManifest), client));
}
RawResource::RawResource(const ResourceRequest& resource_request,
ResourceType type,
const ResourceLoaderOptions& options)
: Resource(resource_request, type, options) {}
void RawResource::AppendData(const char* data, size_t length) {
if (GetResourceRequest().UseStreamOnResponse())
return;
Resource::AppendData(data, length);
}
class RawResource::PreloadBytesConsumerClient final
: public GarbageCollected<PreloadBytesConsumerClient>,
public BytesConsumer::Client {
public:
PreloadBytesConsumerClient(BytesConsumer& bytes_consumer,
RawResource& resource,
RawResourceClient& client)
: bytes_consumer_(bytes_consumer),
resource_(resource),
client_(&client) {}
void OnStateChange() override {
auto* client = client_.Get();
if (!client) {
return;
}
while (resource_->HasClient(client)) {
const char* buffer = nullptr;
size_t available = 0;
auto result = bytes_consumer_->BeginRead(&buffer, &available);
if (result == BytesConsumer::Result::kShouldWait)
return;
if (result == BytesConsumer::Result::kOk) {
client->DataReceived(resource_, buffer, available);
result = bytes_consumer_->EndRead(available);
}
if (result != BytesConsumer::Result::kOk) {
return;
}
}
client_ = nullptr;
}
String DebugName() const override { return "PreloadBytesConsumerClient"; }
void Trace(Visitor* visitor) const override {
visitor->Trace(bytes_consumer_);
visitor->Trace(resource_);
visitor->Trace(client_);
BytesConsumer::Client::Trace(visitor);
}
private:
const Member<BytesConsumer> bytes_consumer_;
const Member<RawResource> resource_;
WeakMember<RawResourceClient> client_;
};
void RawResource::DidAddClient(ResourceClient* c) {
auto* bytes_consumer_for_preload = bytes_consumer_for_preload_.Release();
// CHECK()/RevalidationStartForbiddenScope are for
// https://crbug.com/640960#c24.
CHECK(!IsCacheValidator());
if (!HasClient(c))
return;
DCHECK(c->IsRawResourceClient());
RevalidationStartForbiddenScope revalidation_start_forbidden_scope(this);
RawResourceClient* client = static_cast<RawResourceClient*>(c);
for (const auto& redirect : RedirectChain()) {
client->RedirectReceived(this, ResourceRequest(redirect.request_),
redirect.redirect_response_);
if (!HasClient(c))
return;
}
if (!GetResponse().IsNull()) {
client->ResponseReceived(this, GetResponse());
}
if (!HasClient(c))
return;
if (bytes_consumer_for_preload) {
bytes_consumer_for_preload->StopBuffering();
if (matched_with_non_streaming_destination_) {
// In this case, the client needs individual chunks so we need
// PreloadBytesConsumerClient for the translation.
auto* preload_bytes_consumer_client =
MakeGarbageCollected<PreloadBytesConsumerClient>(
*bytes_consumer_for_preload, *this, *client);
bytes_consumer_for_preload->SetClient(preload_bytes_consumer_client);
preload_bytes_consumer_client->OnStateChange();
} else {
// In this case, we can simply pass the BytesConsumer to the client.
client->ResponseBodyReceived(this, *bytes_consumer_for_preload);
}
}
if (!HasClient(c))
return;
Resource::DidAddClient(client);
}
bool RawResource::WillFollowRedirect(
const ResourceRequest& new_request,
const ResourceResponse& redirect_response) {
bool follow = Resource::WillFollowRedirect(new_request, redirect_response);
// The base class method takes a const reference of a ResourceRequest and
// returns bool just for allowing RawResource to reject redirect. It must
// always return true.
DCHECK(follow);
DCHECK(!redirect_response.IsNull());
ResourceClientWalker<RawResourceClient> w(Clients());
while (RawResourceClient* c = w.Next()) {
if (!c->RedirectReceived(this, new_request, redirect_response))
follow = false;
}
return follow;
}
void RawResource::WillNotFollowRedirect() {
ResourceClientWalker<RawResourceClient> w(Clients());
while (RawResourceClient* c = w.Next())
c->RedirectBlocked();
}
scoped_refptr<BlobDataHandle> RawResource::DownloadedBlob() const {
return downloaded_blob_;
}
void RawResource::Trace(Visitor* visitor) const {
visitor->Trace(bytes_consumer_for_preload_);
Resource::Trace(visitor);
}
void RawResource::ResponseReceived(const ResourceResponse& response) {
if (response.WasFallbackRequiredByServiceWorker()) {
// The ServiceWorker asked us to re-fetch the request. This resource must
// not be reused.
// Note: This logic is needed here because ThreadableLoader handles
// CORS independently from ResourceLoader. Fix it.
if (IsMainThread())
GetMemoryCache()->Remove(this);
}
Resource::ResponseReceived(response);
ResourceClientWalker<RawResourceClient> w(Clients());
while (RawResourceClient* c = w.Next()) {
c->ResponseReceived(this, this->GetResponse());
}
}
void RawResource::ResponseBodyReceived(
ResponseBodyLoaderDrainableInterface& body_loader,
scoped_refptr<base::SingleThreadTaskRunner> loader_task_runner) {
DCHECK_LE(Clients().size(), 1u);
RawResourceClient* client =
ResourceClientWalker<RawResourceClient>(Clients()).Next();
if (!client && GetResourceRequest().UseStreamOnResponse()) {
// For preload, we want to store the body while dispatching
// onload and onerror events.
bytes_consumer_for_preload_ =
BufferingBytesConsumer::Create(&body_loader.DrainAsBytesConsumer());
return;
}
if (matched_with_non_streaming_destination_) {
DCHECK(GetResourceRequest().UseStreamOnResponse());
// The loading was initiated as a preload (hence UseStreamOnResponse is
// set), but this resource has been matched with a request without
// UseStreamOnResponse set.
auto& bytes_consumer_for_preload = body_loader.DrainAsBytesConsumer();
auto* preload_bytes_consumer_client =
MakeGarbageCollected<PreloadBytesConsumerClient>(
bytes_consumer_for_preload, *this, *client);
bytes_consumer_for_preload.SetClient(preload_bytes_consumer_client);
preload_bytes_consumer_client->OnStateChange();
return;
}
if (!GetResourceRequest().UseStreamOnResponse()) {
return;
}
client->ResponseBodyReceived(this, body_loader.DrainAsBytesConsumer());
}
void RawResource::SetSerializedCachedMetadata(mojo_base::BigBuffer data) {
// Resource ignores the cached metadata.
Resource::SetSerializedCachedMetadata(mojo_base::BigBuffer());
ResourceClientWalker<RawResourceClient> w(Clients());
// We rely on the fact that RawResource cannot have multiple clients.
CHECK_LE(Clients().size(), 1u);
if (RawResourceClient* c = w.Next()) {
c->CachedMetadataReceived(this, std::move(data));
}
}
void RawResource::DidSendData(uint64_t bytes_sent,
uint64_t total_bytes_to_be_sent) {
ResourceClientWalker<RawResourceClient> w(Clients());
while (RawResourceClient* c = w.Next())
c->DataSent(this, bytes_sent, total_bytes_to_be_sent);
}
void RawResource::DidDownloadData(uint64_t data_length) {
ResourceClientWalker<RawResourceClient> w(Clients());
while (RawResourceClient* c = w.Next())
c->DataDownloaded(this, data_length);
}
void RawResource::DidDownloadToBlob(scoped_refptr<BlobDataHandle> blob) {
downloaded_blob_ = blob;
ResourceClientWalker<RawResourceClient> w(Clients());
while (RawResourceClient* c = w.Next())
c->DidDownloadToBlob(this, blob);
}
void RawResource::MatchPreload(const FetchParameters& params) {
Resource::MatchPreload(params);
matched_with_non_streaming_destination_ =
!params.GetResourceRequest().UseStreamOnResponse();
}
static bool ShouldIgnoreHeaderForCacheReuse(AtomicString header_name) {
// FIXME: This list of headers that don't affect cache policy almost certainly
// isn't complete.
DEFINE_STATIC_LOCAL(
HashSet<AtomicString>, headers,
({"Cache-Control", "If-Modified-Since", "If-None-Match", "Origin",
"Pragma", "Purpose", "Referer", "User-Agent"}));
return headers.Contains(header_name);
}
Resource::MatchStatus RawResource::CanReuse(
const FetchParameters& new_fetch_parameters) const {
const ResourceRequest& new_request =
new_fetch_parameters.GetResourceRequest();
// Ensure most headers match the existing headers before continuing. Note that
// the list of ignored headers includes some headers explicitly related to
// caching. A more detailed check of caching policy will be performed later,
// this is simply a list of headers that we might permit to be different and
// still reuse the existing Resource.
const HTTPHeaderMap& new_headers = new_request.HttpHeaderFields();
const HTTPHeaderMap& old_headers = GetResourceRequest().HttpHeaderFields();
for (const auto& header : new_headers) {
AtomicString header_name = header.key;
if (!ShouldIgnoreHeaderForCacheReuse(header_name) &&
header.value != old_headers.Get(header_name)) {
return MatchStatus::kRequestHeadersDoNotMatch;
}
}
for (const auto& header : old_headers) {
AtomicString header_name = header.key;
if (!ShouldIgnoreHeaderForCacheReuse(header_name) &&
header.value != new_headers.Get(header_name)) {
return MatchStatus::kRequestHeadersDoNotMatch;
}
}
return Resource::CanReuse(new_fetch_parameters);
}
void RawResourceClient::DidDownloadToBlob(Resource*,
scoped_refptr<BlobDataHandle>) {}
RawResourceClientStateChecker::RawResourceClientStateChecker()
: state_(kNotAddedAsClient) {}
NOINLINE void RawResourceClientStateChecker::WillAddClient() {
SECURITY_CHECK(state_ == kNotAddedAsClient);
state_ = kStarted;
}
NOINLINE void RawResourceClientStateChecker::WillRemoveClient() {
SECURITY_CHECK(state_ != kNotAddedAsClient);
state_ = kNotAddedAsClient;
}
NOINLINE void RawResourceClientStateChecker::RedirectReceived() {
SECURITY_CHECK(state_ == kStarted);
}
NOINLINE void RawResourceClientStateChecker::RedirectBlocked() {
SECURITY_CHECK(state_ == kStarted);
state_ = kRedirectBlocked;
}
NOINLINE void RawResourceClientStateChecker::DataSent() {
SECURITY_CHECK(state_ == kStarted);
}
NOINLINE void RawResourceClientStateChecker::ResponseReceived() {
SECURITY_CHECK(state_ == kStarted);
state_ = kResponseReceived;
}
NOINLINE void RawResourceClientStateChecker::SetSerializedCachedMetadata() {
SECURITY_CHECK(state_ == kResponseReceived ||
state_ == kDataReceivedAsBytesConsumer);
}
NOINLINE void RawResourceClientStateChecker::ResponseBodyReceived() {
SECURITY_CHECK(state_ == kResponseReceived);
state_ = kDataReceivedAsBytesConsumer;
}
NOINLINE void RawResourceClientStateChecker::DataReceived() {
SECURITY_CHECK(state_ == kResponseReceived ||
state_ == kDataReceived);
state_ = kDataReceived;
}
NOINLINE void RawResourceClientStateChecker::DataDownloaded() {
SECURITY_CHECK(state_ == kResponseReceived ||
state_ == kDataDownloaded);
state_ = kDataDownloaded;
}
NOINLINE void RawResourceClientStateChecker::DidDownloadToBlob() {
SECURITY_CHECK(state_ == kResponseReceived ||
state_ == kDataDownloaded);
state_ = kDidDownloadToBlob;
}
NOINLINE void RawResourceClientStateChecker::NotifyFinished(
Resource* resource) {
SECURITY_CHECK(state_ != kNotAddedAsClient);
SECURITY_CHECK(state_ != kNotifyFinished);
SECURITY_CHECK(resource->ErrorOccurred() ||
(state_ == kResponseReceived || state_ == kDataReceived ||
state_ == kDataDownloaded ||
state_ == kDataReceivedAsBytesConsumer ||
state_ == kDidDownloadToBlob));
state_ = kNotifyFinished;
}
} // namespace blink