blob: e4ff2160d58a110523a196fa308063db209a715a [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/cache_storage/inspector_cache_storage_agent.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "services/network/public/mojom/fetch_api.mojom-blink.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_vector.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/fileapi/file_reader_loader.h"
#include "third_party/blink/renderer/core/fileapi/file_reader_loader_client.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/inspector/inspected_frames.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/platform/blob/blob_data.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/network/http_header_map.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/shared_buffer.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/ref_counted.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
using blink::protocol::Array;
// Renaming Cache since there is another blink::Cache.
using ProtocolCache = blink::protocol::CacheStorage::Cache;
using blink::protocol::CacheStorage::Cache;
using blink::protocol::CacheStorage::CachedResponse;
using blink::protocol::CacheStorage::CachedResponseType;
using blink::protocol::CacheStorage::DataEntry;
using blink::protocol::CacheStorage::Header;
// Renaming Response since there is another blink::Response.
using ProtocolResponse = blink::protocol::Response;
using DeleteCacheCallback =
blink::protocol::CacheStorage::Backend::DeleteCacheCallback;
using DeleteEntryCallback =
blink::protocol::CacheStorage::Backend::DeleteEntryCallback;
using RequestCacheNamesCallback =
blink::protocol::CacheStorage::Backend::RequestCacheNamesCallback;
using RequestEntriesCallback =
blink::protocol::CacheStorage::Backend::RequestEntriesCallback;
using RequestCachedResponseCallback =
blink::protocol::CacheStorage::Backend::RequestCachedResponseCallback;
namespace blink {
namespace {
String BuildCacheId(const String& security_origin, const String& cache_name) {
String id(security_origin);
id.append('|');
id.append(cache_name);
return id;
}
ProtocolResponse ParseCacheId(const String& id,
String* security_origin,
String* cache_name) {
wtf_size_t pipe = id.find('|');
if (pipe == WTF::kNotFound)
return ProtocolResponse::Error("Invalid cache id.");
*security_origin = id.Substring(0, pipe);
*cache_name = id.Substring(pipe + 1);
return ProtocolResponse::OK();
}
ProtocolResponse GetExecutionContext(InspectedFrames* frames,
const String& security_origin,
ExecutionContext** context) {
LocalFrame* frame = frames->FrameWithSecurityOrigin(security_origin);
if (!frame)
return ProtocolResponse::Error("No frame with origin " + security_origin);
blink::Document* document = frame->GetDocument();
if (!document)
return ProtocolResponse::Error("No execution context found");
*context = document;
return ProtocolResponse::OK();
}
ProtocolResponse AssertCacheStorage(
const String& security_origin,
InspectedFrames* frames,
InspectorCacheStorageAgent::CachesMap* caches,
mojom::blink::CacheStorage** result) {
scoped_refptr<const SecurityOrigin> sec_origin =
SecurityOrigin::CreateFromString(security_origin);
// Cache Storage API is restricted to trustworthy origins.
if (!sec_origin->IsPotentiallyTrustworthy()) {
return ProtocolResponse::Error(
sec_origin->IsPotentiallyTrustworthyErrorMessage());
}
ExecutionContext* context = nullptr;
ProtocolResponse response =
GetExecutionContext(frames, security_origin, &context);
if (!response.isSuccess())
return response;
auto it = caches->find(security_origin);
if (it == caches->end()) {
mojom::blink::CacheStoragePtr cache_storage_ptr;
context->GetInterfaceProvider()->GetInterface(
mojo::MakeRequest(&cache_storage_ptr));
*result = cache_storage_ptr.get();
caches->Set(security_origin, std::move(cache_storage_ptr));
} else {
*result = it->value.get();
}
return ProtocolResponse::OK();
}
ProtocolResponse AssertCacheStorageAndNameForId(
const String& cache_id,
InspectedFrames* frames,
String* cache_name,
InspectorCacheStorageAgent::CachesMap* caches,
mojom::blink::CacheStorage** result) {
String security_origin;
ProtocolResponse response =
ParseCacheId(cache_id, &security_origin, cache_name);
if (!response.isSuccess())
return response;
return AssertCacheStorage(security_origin, frames, caches, result);
}
CString CacheStorageErrorString(mojom::blink::CacheStorageError error) {
switch (error) {
case mojom::blink::CacheStorageError::kErrorNotImplemented:
return CString("not implemented.");
case mojom::blink::CacheStorageError::kErrorNotFound:
return CString("not found.");
case mojom::blink::CacheStorageError::kErrorExists:
return CString("cache already exists.");
case mojom::blink::CacheStorageError::kErrorQuotaExceeded:
return CString("quota exceeded.");
case mojom::blink::CacheStorageError::kErrorCacheNameNotFound:
return CString("cache not found.");
case mojom::blink::CacheStorageError::kErrorQueryTooLarge:
return CString("operation too large.");
case mojom::blink::CacheStorageError::kErrorStorage:
return CString("storage failure.");
case mojom::blink::CacheStorageError::kErrorDuplicateOperation:
return CString("duplicate operation.");
case mojom::blink::CacheStorageError::kSuccess:
// This function should only be called upon error.
break;
}
NOTREACHED();
return "";
}
CachedResponseType ResponseTypeToString(
network::mojom::FetchResponseType type) {
switch (type) {
case network::mojom::FetchResponseType::kBasic:
return protocol::CacheStorage::CachedResponseTypeEnum::Basic;
case network::mojom::FetchResponseType::kCors:
return protocol::CacheStorage::CachedResponseTypeEnum::Cors;
case network::mojom::FetchResponseType::kDefault:
return protocol::CacheStorage::CachedResponseTypeEnum::Default;
case network::mojom::FetchResponseType::kError:
return protocol::CacheStorage::CachedResponseTypeEnum::Error;
case network::mojom::FetchResponseType::kOpaque:
return protocol::CacheStorage::CachedResponseTypeEnum::OpaqueResponse;
case network::mojom::FetchResponseType::kOpaqueRedirect:
return protocol::CacheStorage::CachedResponseTypeEnum::OpaqueRedirect;
}
NOTREACHED();
return "";
}
struct DataRequestParams {
String cache_name;
int skip_count;
int page_size;
String path_filter;
};
struct RequestResponse {
String request_url;
String request_method;
HTTPHeaderMap request_headers;
int response_status;
String response_status_text;
double response_time;
network::mojom::FetchResponseType response_type;
HTTPHeaderMap response_headers;
};
class ResponsesAccumulator : public RefCounted<ResponsesAccumulator> {
public:
ResponsesAccumulator(wtf_size_t num_responses,
const DataRequestParams& params,
mojom::blink::CacheStorageCacheAssociatedPtr cache_ptr,
std::unique_ptr<RequestEntriesCallback> callback)
: params_(params),
num_responses_left_(num_responses),
cache_ptr_(std::move(cache_ptr)),
callback_(std::move(callback)) {}
void Dispatch(Vector<mojom::blink::FetchAPIRequestPtr> old_requests) {
Vector<mojom::blink::FetchAPIRequestPtr> requests;
if (params_.path_filter.IsEmpty()) {
requests = std::move(old_requests);
} else {
for (auto& request : old_requests) {
String urlPath(request->url.GetPath());
if (!urlPath.Contains(params_.path_filter,
WTF::kTextCaseUnicodeInsensitive))
continue;
requests.push_back(std::move(request));
}
}
wtf_size_t requestSize = requests.size();
if (!requestSize) {
callback_->sendSuccess(Array<DataEntry>::create(), false);
return;
}
responses_ = Vector<RequestResponse>(requestSize);
num_responses_left_ = requestSize;
for (auto& request : requests) {
// All FetchAPIRequests in cache_storage code are supposed to not contain
// a body.
DCHECK(!request->blob && !request->body);
auto request_clone_without_body = mojom::blink::FetchAPIRequest::New(
request->mode, request->is_main_resource_load,
request->request_context_type, request->frame_type, request->url,
request->method, request->headers, nullptr /* blob */,
nullptr /* body */, request->referrer.Clone(),
request->credentials_mode, request->cache_mode,
request->redirect_mode, request->integrity, request->priority,
request->fetch_window_id, request->keepalive, request->is_reload,
request->is_history_navigation);
cache_ptr_->Match(std::move(request),
mojom::blink::CacheQueryOptions::New(),
WTF::Bind(
[](scoped_refptr<ResponsesAccumulator> accumulator,
mojom::blink::FetchAPIRequestPtr request,
mojom::blink::MatchResultPtr result) {
if (result->is_status()) {
accumulator->SendFailure(result->get_status());
} else {
accumulator->AddRequestResponsePair(
request, result->get_response());
}
},
scoped_refptr<ResponsesAccumulator>(this),
std::move(request_clone_without_body)));
}
}
void AddRequestResponsePair(
const mojom::blink::FetchAPIRequestPtr& request,
const mojom::blink::FetchAPIResponsePtr& response) {
DCHECK_GT(num_responses_left_, 0);
RequestResponse& next_request_response =
responses_.at(responses_.size() - num_responses_left_);
next_request_response.request_url = request->url.GetString();
next_request_response.request_method = request->method;
for (const auto& header : request->headers) {
next_request_response.request_headers.Set(AtomicString(header.key),
AtomicString(header.value));
}
next_request_response.response_status = response->status_code;
next_request_response.response_status_text = response->status_text;
next_request_response.response_time = response->response_time.ToDoubleT();
next_request_response.response_type = response->response_type;
for (const auto& header : response->headers) {
next_request_response.response_headers.Set(AtomicString(header.key),
AtomicString(header.value));
}
if (--num_responses_left_ != 0)
return;
std::sort(responses_.begin(), responses_.end(),
[](const RequestResponse& a, const RequestResponse& b) {
return WTF::CodePointCompareLessThan(a.request_url,
b.request_url);
});
if (params_.skip_count > 0)
responses_.EraseAt(0, params_.skip_count);
bool has_more = false;
if (static_cast<size_t>(params_.page_size) < responses_.size()) {
responses_.EraseAt(params_.page_size,
responses_.size() - params_.page_size);
has_more = true;
}
std::unique_ptr<Array<DataEntry>> array = Array<DataEntry>::create();
for (const auto& request_response : responses_) {
std::unique_ptr<DataEntry> entry =
DataEntry::create()
.setRequestURL(request_response.request_url)
.setRequestMethod(request_response.request_method)
.setRequestHeaders(
SerializeHeaders(request_response.request_headers))
.setResponseStatus(request_response.response_status)
.setResponseStatusText(request_response.response_status_text)
.setResponseTime(request_response.response_time)
.setResponseType(
ResponseTypeToString(request_response.response_type))
.setResponseHeaders(
SerializeHeaders(request_response.response_headers))
.build();
array->addItem(std::move(entry));
}
callback_->sendSuccess(std::move(array), has_more);
}
void SendFailure(const mojom::blink::CacheStorageError& error) {
callback_->sendFailure(ProtocolResponse::Error(
String::Format("Error requesting responses for cache %s : %s",
params_.cache_name.Utf8().data(),
CacheStorageErrorString(error).data())));
}
std::unique_ptr<Array<Header>> SerializeHeaders(
const HTTPHeaderMap& headers) {
std::unique_ptr<Array<Header>> result = Array<Header>::create();
for (HTTPHeaderMap::const_iterator it = headers.begin(),
end = headers.end();
it != end; ++it) {
result->addItem(
Header::create().setName(it->key).setValue(it->value).build());
}
return result;
}
private:
DataRequestParams params_;
int num_responses_left_;
Vector<RequestResponse> responses_;
mojom::blink::CacheStorageCacheAssociatedPtr cache_ptr_;
std::unique_ptr<RequestEntriesCallback> callback_;
DISALLOW_COPY_AND_ASSIGN(ResponsesAccumulator);
};
class GetCacheKeysForRequestData {
public:
GetCacheKeysForRequestData(
const DataRequestParams& params,
mojom::blink::CacheStorageCacheAssociatedPtrInfo cache_ptr_info,
std::unique_ptr<RequestEntriesCallback> callback)
: params_(params), callback_(std::move(callback)) {
cache_ptr_.Bind(std::move(cache_ptr_info));
}
void Dispatch(std::unique_ptr<GetCacheKeysForRequestData> self) {
cache_ptr_->Keys(
nullptr /* request */, mojom::blink::CacheQueryOptions::New(),
WTF::Bind(
[](DataRequestParams params,
std::unique_ptr<GetCacheKeysForRequestData> self,
mojom::blink::CacheKeysResultPtr result) {
if (result->is_status()) {
self->callback_->sendFailure(
ProtocolResponse::Error(String::Format(
"Error requesting requests for cache %s: %s",
params.cache_name.Utf8().data(),
CacheStorageErrorString(result->get_status()).data())));
} else {
if (result->get_keys().IsEmpty()) {
std::unique_ptr<Array<DataEntry>> array =
Array<DataEntry>::create();
self->callback_->sendSuccess(std::move(array), false);
return;
}
scoped_refptr<ResponsesAccumulator> accumulator =
base::AdoptRef(new ResponsesAccumulator(
result->get_keys().size(), params,
std::move(self->cache_ptr_),
std::move(self->callback_)));
accumulator->Dispatch(std::move(result->get_keys()));
}
},
params_, std::move(self)));
}
private:
DataRequestParams params_;
mojom::blink::CacheStorageCacheAssociatedPtr cache_ptr_;
std::unique_ptr<RequestEntriesCallback> callback_;
DISALLOW_COPY_AND_ASSIGN(GetCacheKeysForRequestData);
};
class CachedResponseFileReaderLoaderClient final
: private FileReaderLoaderClient {
public:
static void Load(scoped_refptr<BlobDataHandle> blob,
std::unique_ptr<RequestCachedResponseCallback> callback) {
new CachedResponseFileReaderLoaderClient(std::move(blob),
std::move(callback));
}
void DidStartLoading() override {}
void DidFinishLoading() override {
std::unique_ptr<CachedResponse> response =
CachedResponse::create()
.setBody(protocol::Binary::fromSharedBuffer(data_))
.build();
callback_->sendSuccess(std::move(response));
dispose();
}
void DidFail(FileErrorCode error) override {
callback_->sendFailure(ProtocolResponse::Error(String::Format(
"Unable to read the cached response, error code: %d", error)));
dispose();
}
void DidReceiveDataForClient(const char* data,
unsigned data_length) override {
data_->Append(data, data_length);
}
private:
CachedResponseFileReaderLoaderClient(
scoped_refptr<BlobDataHandle>&& blob,
std::unique_ptr<RequestCachedResponseCallback>&& callback)
// TODO(hajimehoshi): Use a per-ExecutionContext task runner of
// TaskType::kFileReading
: loader_(std::make_unique<FileReaderLoader>(
FileReaderLoader::kReadByClient,
static_cast<FileReaderLoaderClient*>(this),
ThreadScheduler::Current()->DeprecatedDefaultTaskRunner())),
callback_(std::move(callback)),
data_(SharedBuffer::Create()) {
loader_->Start(std::move(blob));
}
~CachedResponseFileReaderLoaderClient() override = default;
void dispose() { delete this; }
std::unique_ptr<FileReaderLoader> loader_;
std::unique_ptr<RequestCachedResponseCallback> callback_;
scoped_refptr<SharedBuffer> data_;
DISALLOW_COPY_AND_ASSIGN(CachedResponseFileReaderLoaderClient);
};
} // namespace
InspectorCacheStorageAgent::InspectorCacheStorageAgent(InspectedFrames* frames)
: frames_(frames) {}
InspectorCacheStorageAgent::~InspectorCacheStorageAgent() = default;
void InspectorCacheStorageAgent::Trace(blink::Visitor* visitor) {
visitor->Trace(frames_);
InspectorBaseAgent::Trace(visitor);
}
void InspectorCacheStorageAgent::requestCacheNames(
const String& security_origin,
std::unique_ptr<RequestCacheNamesCallback> callback) {
scoped_refptr<const SecurityOrigin> sec_origin =
SecurityOrigin::CreateFromString(security_origin);
// Cache Storage API is restricted to trustworthy origins.
if (!sec_origin->IsPotentiallyTrustworthy()) {
// Don't treat this as an error, just don't attempt to open and enumerate
// the caches.
callback->sendSuccess(Array<ProtocolCache>::create());
return;
}
mojom::blink::CacheStorage* cache_storage = nullptr;
ProtocolResponse response =
AssertCacheStorage(security_origin, frames_, &caches_, &cache_storage);
if (!response.isSuccess()) {
callback->sendFailure(response);
return;
}
cache_storage->Keys(WTF::Bind(
[](String security_origin,
std::unique_ptr<RequestCacheNamesCallback> callback,
const Vector<String>& caches) {
std::unique_ptr<Array<ProtocolCache>> array =
Array<ProtocolCache>::create();
for (auto& cache : caches) {
array->addItem(ProtocolCache::create()
.setSecurityOrigin(security_origin)
.setCacheName(cache)
.setCacheId(BuildCacheId(security_origin, cache))
.build());
}
callback->sendSuccess(std::move(array));
},
security_origin, std::move(callback)));
}
void InspectorCacheStorageAgent::requestEntries(
const String& cache_id,
int skip_count,
int page_size,
protocol::Maybe<String> path_filter,
std::unique_ptr<RequestEntriesCallback> callback) {
String cache_name;
mojom::blink::CacheStorage* cache_storage = nullptr;
ProtocolResponse response = AssertCacheStorageAndNameForId(
cache_id, frames_, &cache_name, &caches_, &cache_storage);
if (!response.isSuccess()) {
callback->sendFailure(response);
return;
}
DataRequestParams params;
params.cache_name = cache_name;
params.page_size = page_size;
params.skip_count = skip_count;
params.path_filter = path_filter.fromMaybe("");
cache_storage->Open(
cache_name,
WTF::Bind(
[](DataRequestParams params,
std::unique_ptr<RequestEntriesCallback> callback,
mojom::blink::OpenResultPtr result) {
if (result->is_status()) {
callback->sendFailure(ProtocolResponse::Error(String::Format(
"Error requesting cache %s: %s",
params.cache_name.Utf8().data(),
CacheStorageErrorString(result->get_status()).data())));
} else {
auto request = std::make_unique<GetCacheKeysForRequestData>(
params, std::move(result->get_cache()), std::move(callback));
auto* request_ptr = request.get();
request_ptr->Dispatch(std::move(request));
}
},
params, std::move(callback)));
}
void InspectorCacheStorageAgent::deleteCache(
const String& cache_id,
std::unique_ptr<DeleteCacheCallback> callback) {
String cache_name;
mojom::blink::CacheStorage* cache_storage = nullptr;
ProtocolResponse response = AssertCacheStorageAndNameForId(
cache_id, frames_, &cache_name, &caches_, &cache_storage);
if (!response.isSuccess()) {
callback->sendFailure(response);
return;
}
cache_storage->Delete(
cache_name,
WTF::Bind(
[](std::unique_ptr<DeleteCacheCallback> callback,
mojom::blink::CacheStorageError error) {
if (error == mojom::blink::CacheStorageError::kSuccess) {
callback->sendSuccess();
} else {
callback->sendFailure(ProtocolResponse::Error(
String::Format("Error requesting cache names: %s",
CacheStorageErrorString(error).data())));
}
},
std::move(callback)));
}
void InspectorCacheStorageAgent::deleteEntry(
const String& cache_id,
const String& request,
std::unique_ptr<DeleteEntryCallback> callback) {
String cache_name;
mojom::blink::CacheStorage* cache_storage = nullptr;
ProtocolResponse response = AssertCacheStorageAndNameForId(
cache_id, frames_, &cache_name, &caches_, &cache_storage);
if (!response.isSuccess()) {
callback->sendFailure(response);
return;
}
cache_storage->Open(
cache_name,
WTF::Bind(
[](String cache_name, String request,
std::unique_ptr<DeleteEntryCallback> callback,
mojom::blink::OpenResultPtr result) {
if (result->is_status()) {
callback->sendFailure(ProtocolResponse::Error(String::Format(
"Error requesting cache %s: %s", cache_name.Utf8().data(),
CacheStorageErrorString(result->get_status()).data())));
} else {
Vector<mojom::blink::BatchOperationPtr> batch_operations;
batch_operations.push_back(mojom::blink::BatchOperation::New());
auto& operation = batch_operations.back();
operation->operation_type = mojom::blink::OperationType::kDelete;
operation->request = mojom::blink::FetchAPIRequest::New();
operation->request->url = KURL(request);
operation->request->method = String("GET");
mojom::blink::CacheStorageCacheAssociatedPtr cache_ptr;
cache_ptr.Bind(std::move(result->get_cache()));
auto* cache = cache_ptr.get();
cache->Batch(
std::move(batch_operations), true /* fail_on_duplicates */,
WTF::Bind(
[](mojom::blink::CacheStorageCacheAssociatedPtr cache_ptr,
std::unique_ptr<DeleteEntryCallback> callback,
mojom::blink::CacheStorageVerboseErrorPtr error) {
if (error->value !=
mojom::blink::CacheStorageError::kSuccess) {
callback->sendFailure(
ProtocolResponse::Error(String::Format(
"Error deleting cache entry: %s",
CacheStorageErrorString(error->value)
.data())));
} else {
callback->sendSuccess();
}
},
std::move(cache_ptr), std::move(callback)));
}
},
cache_name, request, std::move(callback)));
}
void InspectorCacheStorageAgent::requestCachedResponse(
const String& cache_id,
const String& request_url,
const std::unique_ptr<protocol::Array<protocol::CacheStorage::Header>>
request_headers,
std::unique_ptr<RequestCachedResponseCallback> callback) {
String cache_name;
mojom::blink::CacheStorage* cache_storage = nullptr;
ProtocolResponse response = AssertCacheStorageAndNameForId(
cache_id, frames_, &cache_name, &caches_, &cache_storage);
if (!response.isSuccess()) {
callback->sendFailure(response);
return;
}
auto request = mojom::blink::FetchAPIRequest::New();
request->url = KURL(request_url);
request->method = String("GET");
for (size_t i = 0, len = request_headers->length(); i < len; ++i) {
auto* header = request_headers->get(i);
request->headers.insert(header->getName(), header->getValue());
}
auto multi_query_options = mojom::blink::MultiCacheQueryOptions::New();
multi_query_options->query_options = mojom::blink::CacheQueryOptions::New();
multi_query_options->cache_name = cache_name;
cache_storage->Match(
std::move(request), std::move(multi_query_options),
WTF::Bind(
[](std::unique_ptr<RequestCachedResponseCallback> callback,
mojom::blink::MatchResultPtr result) {
if (result->is_status()) {
callback->sendFailure(ProtocolResponse::Error(String::Format(
"Unable to read cached response: %s",
CacheStorageErrorString(result->get_status()).data())));
} else {
std::unique_ptr<protocol::DictionaryValue> headers =
protocol::DictionaryValue::create();
if (!result->get_response()->blob) {
callback->sendSuccess(CachedResponse::create()
.setBody(protocol::Binary())
.build());
return;
}
CachedResponseFileReaderLoaderClient::Load(
std::move(result->get_response()->blob), std::move(callback));
}
},
std::move(callback)));
}
} // namespace blink