blob: 8948a160d4315797d899c6b7f19158f7498b5318 [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 "mojo/public/cpp/system/data_pipe.h"
#include "services/network/public/mojom/request_context_frame_type.mojom-shared.h"
#include "third_party/blink/public/platform/platform.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/script_cached_metadata_handler.h"
#include "third_party/blink/renderer/platform/loader/fetch/source_keyed_cached_metadata_handler.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"
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) {
DCHECK_EQ(params.GetResourceRequest().GetFrameType(),
network::mojom::RequestContextFrameType::kNone);
params.SetRequestContext(WebURLRequest::kRequestContextImport);
return ToRawResource(fetcher->RequestResource(
params, RawResourceFactory(ResourceType::kImportResource), client));
}
RawResource* RawResource::Fetch(FetchParameters& params,
ResourceFetcher* fetcher,
RawResourceClient* client) {
DCHECK_EQ(params.GetResourceRequest().GetFrameType(),
network::mojom::RequestContextFrameType::kNone);
DCHECK_NE(params.GetResourceRequest().GetRequestContext(),
WebURLRequest::kRequestContextUnspecified);
return ToRawResource(fetcher->RequestResource(
params, RawResourceFactory(ResourceType::kRaw), client));
}
RawResource* RawResource::FetchMainResource(
FetchParameters& params,
ResourceFetcher* fetcher,
RawResourceClient* client,
const SubstituteData& substitute_data) {
DCHECK_NE(params.GetResourceRequest().GetFrameType(),
network::mojom::RequestContextFrameType::kNone);
DCHECK(params.GetResourceRequest().GetRequestContext() ==
WebURLRequest::kRequestContextForm ||
params.GetResourceRequest().GetRequestContext() ==
WebURLRequest::kRequestContextFrame ||
params.GetResourceRequest().GetRequestContext() ==
WebURLRequest::kRequestContextHyperlink ||
params.GetResourceRequest().GetRequestContext() ==
WebURLRequest::kRequestContextIframe ||
params.GetResourceRequest().GetRequestContext() ==
WebURLRequest::kRequestContextInternal ||
params.GetResourceRequest().GetRequestContext() ==
WebURLRequest::kRequestContextLocation);
return ToRawResource(fetcher->RequestResource(
params, RawResourceFactory(ResourceType::kMainResource), client,
substitute_data));
}
RawResource* RawResource::FetchMedia(FetchParameters& params,
ResourceFetcher* fetcher,
RawResourceClient* client) {
DCHECK_EQ(params.GetResourceRequest().GetFrameType(),
network::mojom::RequestContextFrameType::kNone);
auto context = params.GetResourceRequest().GetRequestContext();
DCHECK(context == WebURLRequest::kRequestContextAudio ||
context == WebURLRequest::kRequestContextVideo);
ResourceType type = (context == WebURLRequest::kRequestContextAudio)
? ResourceType::kAudio
: ResourceType::kVideo;
return ToRawResource(
fetcher->RequestResource(params, RawResourceFactory(type), client));
}
RawResource* RawResource::FetchTextTrack(FetchParameters& params,
ResourceFetcher* fetcher,
RawResourceClient* client) {
DCHECK_EQ(params.GetResourceRequest().GetFrameType(),
network::mojom::RequestContextFrameType::kNone);
params.SetRequestContext(WebURLRequest::kRequestContextTrack);
return ToRawResource(fetcher->RequestResource(
params, RawResourceFactory(ResourceType::kTextTrack), client));
}
RawResource* RawResource::FetchManifest(FetchParameters& params,
ResourceFetcher* fetcher,
RawResourceClient* client) {
DCHECK_EQ(params.GetResourceRequest().GetFrameType(),
network::mojom::RequestContextFrameType::kNone);
DCHECK_EQ(params.GetResourceRequest().GetRequestContext(),
WebURLRequest::kRequestContextManifest);
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 (data_pipe_writer_) {
DCHECK_EQ(kDoNotBufferData, GetDataBufferingPolicy());
data_pipe_writer_->Write(data, length);
} else {
Resource::AppendData(data, length);
}
}
void RawResource::DidAddClient(ResourceClient* c) {
// 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()) {
ResourceRequest request(redirect.request_);
client->RedirectReceived(this, request, redirect.redirect_response_);
if (!HasClient(c))
return;
}
if (!GetResponse().IsNull()) {
client->ResponseReceived(this, GetResponse(),
std::move(data_consumer_handle_));
}
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();
}
SourceKeyedCachedMetadataHandler* RawResource::InlineScriptCacheHandler() {
DCHECK_EQ(ResourceType::kMainResource, GetType());
return static_cast<SourceKeyedCachedMetadataHandler*>(
Resource::CacheHandler());
}
SingleCachedMetadataHandler* RawResource::ScriptCacheHandler() {
DCHECK_EQ(ResourceType::kRaw, GetType());
return static_cast<SingleCachedMetadataHandler*>(Resource::CacheHandler());
}
void RawResource::ResponseReceived(
const ResourceResponse& response,
std::unique_ptr<WebDataConsumerHandle> handle) {
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, nullptr);
DCHECK(!handle || !data_consumer_handle_);
if (!handle && Clients().size() > 0)
handle = std::move(data_consumer_handle_);
ResourceClientWalker<RawResourceClient> w(Clients());
DCHECK(Clients().size() <= 1 || !handle);
while (RawResourceClient* c = w.Next()) {
// |handle| is cleared when passed, but it's not a problem because |handle|
// is null when there are two or more clients, as asserted.
c->ResponseReceived(this, this->GetResponse(), std::move(handle));
}
}
CachedMetadataHandler* RawResource::CreateCachedMetadataHandler(
std::unique_ptr<CachedMetadataSender> send_callback) {
if (GetType() == ResourceType::kMainResource) {
// This is a document resource; create a cache handler that can handle
// multiple inline scripts.
return new SourceKeyedCachedMetadataHandler(Encoding(),
std::move(send_callback));
} else if (GetType() == ResourceType::kRaw) {
// This is a resource of indeterminate type, e.g. a fetched WebAssembly
// module; create a cache handler that can store a single metadata entry.
return new ScriptCachedMetadataHandler(Encoding(),
std::move(send_callback));
}
return Resource::CreateCachedMetadataHandler(std::move(send_callback));
}
void RawResource::SetSerializedCachedMetadata(const char* data, size_t size) {
Resource::SetSerializedCachedMetadata(data, size);
if (GetType() == ResourceType::kMainResource) {
SourceKeyedCachedMetadataHandler* cache_handler =
InlineScriptCacheHandler();
if (cache_handler) {
cache_handler->SetSerializedCachedMetadata(data, size);
}
} else if (GetType() == ResourceType::kRaw) {
ScriptCachedMetadataHandler* cache_handler =
static_cast<ScriptCachedMetadataHandler*>(Resource::CacheHandler());
if (cache_handler) {
cache_handler->SetSerializedCachedMetadata(data, size);
}
}
ResourceClientWalker<RawResourceClient> w(Clients());
while (RawResourceClient* c = w.Next())
c->SetSerializedCachedMetadata(this, data, size);
}
void RawResource::DidSendData(unsigned long long bytes_sent,
unsigned long long 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(int 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::ReportResourceTimingToClients(
const ResourceTimingInfo& info) {
ResourceClientWalker<RawResourceClient> w(Clients());
while (RawResourceClient* c = w.Next())
c->DidReceiveResourceTiming(this, info);
}
bool RawResource::MatchPreload(const FetchParameters& params,
base::SingleThreadTaskRunner* task_runner) {
if (!Resource::MatchPreload(params, task_runner))
return false;
// This is needed to call Platform::Current() below. Remove this branch
// when the calls are removed.
if (!IsMainThread())
return false;
if (!params.GetResourceRequest().UseStreamOnResponse())
return true;
if (ErrorOccurred())
return true;
// A preloaded resource is not for streaming.
DCHECK(!GetResourceRequest().UseStreamOnResponse());
DCHECK_EQ(GetDataBufferingPolicy(), kBufferData);
// Preloading for raw resources are not cached.
DCHECK(!IsMainThread() || !GetMemoryCache()->Contains(this));
constexpr auto kCapacity = 32 * 1024;
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes = kCapacity;
MojoResult result = mojo::CreateDataPipe(&options, &producer, &consumer);
if (result != MOJO_RESULT_OK)
return false;
data_consumer_handle_ =
Platform::Current()->CreateDataConsumerHandle(std::move(consumer));
data_pipe_writer_ = std::make_unique<BufferingDataPipeWriter>(
std::move(producer), task_runner);
if (Data()) {
for (const auto& span : *Data())
data_pipe_writer_->Write(span.data(), span.size());
}
SetDataBufferingPolicy(kDoNotBufferData);
if (IsLoaded())
data_pipe_writer_->Finish();
return true;
}
void RawResource::NotifyFinished() {
if (data_pipe_writer_)
data_pipe_writer_->Finish();
Resource::NotifyFinished();
}
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);
}
RawResourceClientStateChecker::RawResourceClientStateChecker()
: state_(kNotAddedAsClient) {}
RawResourceClientStateChecker::~RawResourceClientStateChecker() = default;
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_ = kSetSerializedCachedMetadata;
}
NOINLINE void RawResourceClientStateChecker::DataReceived() {
SECURITY_CHECK(state_ == kResponseReceived ||
state_ == kSetSerializedCachedMetadata ||
state_ == kDataReceived);
state_ = kDataReceived;
}
NOINLINE void RawResourceClientStateChecker::DataDownloaded() {
SECURITY_CHECK(state_ == kResponseReceived ||
state_ == kSetSerializedCachedMetadata ||
state_ == kDataDownloaded);
state_ = kDataDownloaded;
}
NOINLINE void RawResourceClientStateChecker::DidDownloadToBlob() {
SECURITY_CHECK(state_ == kResponseReceived ||
state_ == kSetSerializedCachedMetadata ||
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_ == kSetSerializedCachedMetadata ||
state_ == kDataReceived || state_ == kDataDownloaded ||
state_ == kDidDownloadToBlob));
state_ = kNotifyFinished;
}
} // namespace blink