// 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 #include #include #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/common/cache_storage/cache_storage_utils.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/weborigin/kurl.h" #include "third_party/blink/renderer/platform/weborigin/security_origin.h" #include "third_party/blink/renderer/platform/wtf/allocator/allocator.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/shared_buffer.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) { StringBuilder id; id.Append(security_origin); id.Append('|'); id.Append(cache_name); return id.ToString(); } 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 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()) { mojo::Remote cache_storage_remote; context->GetInterfaceProvider()->GetInterface( cache_storage_remote.BindNewPipeAndPassReceiver()); *result = cache_storage_remote.get(); caches->Set(security_origin, std::move(cache_storage_remote)); } 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); } std::string CacheStorageErrorString(mojom::blink::CacheStorageError error) { switch (error) { case mojom::blink::CacheStorageError::kErrorNotImplemented: return std::string("not implemented."); case mojom::blink::CacheStorageError::kErrorNotFound: return std::string("not found."); case mojom::blink::CacheStorageError::kErrorExists: return std::string("cache already exists."); case mojom::blink::CacheStorageError::kErrorQuotaExceeded: return std::string("quota exceeded."); case mojom::blink::CacheStorageError::kErrorCacheNameNotFound: return std::string("cache not found."); case mojom::blink::CacheStorageError::kErrorQueryTooLarge: return std::string("operation too large."); case mojom::blink::CacheStorageError::kErrorStorage: return std::string("storage failure."); case mojom::blink::CacheStorageError::kErrorDuplicateOperation: return std::string("duplicate operation."); case mojom::blink::CacheStorageError::kSuccess: // This function should only be called upon error. break; } NOTREACHED(); return std::string(); } 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 { public: ResponsesAccumulator(wtf_size_t num_responses, const DataRequestParams& params, mojom::blink::CacheStorageCacheAssociatedPtr cache_ptr, std::unique_ptr callback) : params_(params), num_responses_left_(num_responses), cache_ptr_(std::move(cache_ptr)), callback_(std::move(callback)) {} void Dispatch(Vector old_requests) { int64_t trace_id = blink::cache_storage::CreateTraceId(); TRACE_EVENT_WITH_FLOW0("CacheStorage", "ResponsesAccumulator::Dispatch", TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT); Vector 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(std::make_unique>(), 0); return; } responses_ = Vector(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(), trace_id, WTF::Bind( [](scoped_refptr 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(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::CodeUnitCompareLessThan(a.request_url, b.request_url); }); size_t returned_entries_count = responses_.size(); if (params_.skip_count > 0) responses_.EraseAt(0, params_.skip_count); if (static_cast(params_.page_size) < responses_.size()) { responses_.EraseAt(params_.page_size, responses_.size() - params_.page_size); } auto array = std::make_unique>(); for (const auto& request_response : responses_) { std::unique_ptr 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->emplace_back(std::move(entry)); } callback_->sendSuccess(std::move(array), returned_entries_count); } void SendFailure(const mojom::blink::CacheStorageError& error) { callback_->sendFailure(ProtocolResponse::Error( String::Format("Error requesting responses for cache %s : %s", params_.cache_name.Utf8().c_str(), CacheStorageErrorString(error).data()))); } std::unique_ptr> SerializeHeaders( const HTTPHeaderMap& headers) { auto result = std::make_unique>(); for (HTTPHeaderMap::const_iterator it = headers.begin(), end = headers.end(); it != end; ++it) { result->emplace_back( Header::create().setName(it->key).setValue(it->value).build()); } return result; } private: DataRequestParams params_; int num_responses_left_; Vector responses_; mojom::blink::CacheStorageCacheAssociatedPtr cache_ptr_; std::unique_ptr callback_; DISALLOW_COPY_AND_ASSIGN(ResponsesAccumulator); }; class GetCacheKeysForRequestData { USING_FAST_MALLOC(GetCacheKeysForRequestData); public: GetCacheKeysForRequestData( const DataRequestParams& params, mojom::blink::CacheStorageCacheAssociatedPtrInfo cache_ptr_info, std::unique_ptr callback) : params_(params), callback_(std::move(callback)) { cache_ptr_.Bind(std::move(cache_ptr_info)); } void Dispatch(std::unique_ptr self) { int64_t trace_id = blink::cache_storage::CreateTraceId(); TRACE_EVENT_WITH_FLOW0( "CacheStorage", "GetCacheKeysForRequestData::Dispatch", TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT); cache_ptr_->Keys( nullptr /* request */, mojom::blink::CacheQueryOptions::New(), trace_id, WTF::Bind( [](DataRequestParams params, std::unique_ptr 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().c_str(), CacheStorageErrorString(result->get_status()).data()))); } else { if (result->get_keys().IsEmpty()) { auto array = std::make_unique>(); self->callback_->sendSuccess(std::move(array), 0); return; } scoped_refptr 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 callback_; DISALLOW_COPY_AND_ASSIGN(GetCacheKeysForRequestData); }; class CachedResponseFileReaderLoaderClient final : private FileReaderLoaderClient { public: static void Load(scoped_refptr blob, std::unique_ptr callback) { new CachedResponseFileReaderLoaderClient(std::move(blob), std::move(callback)); } void DidStartLoading() override {} void DidFinishLoading() override { std::unique_ptr 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&& blob, std::unique_ptr&& callback) // TODO(hajimehoshi): Use a per-ExecutionContext task runner of // TaskType::kFileReading : loader_(std::make_unique( FileReaderLoader::kReadByClient, static_cast(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 loader_; std::unique_ptr callback_; scoped_refptr 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 callback) { int64_t trace_id = blink::cache_storage::CreateTraceId(); TRACE_EVENT_WITH_FLOW0("CacheStorage", "InspectorCacheStorageAgent::requestCacheNames", TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT); scoped_refptr 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(std::make_unique>()); 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( trace_id, WTF::Bind( [](String security_origin, std::unique_ptr callback, const Vector& caches) { auto array = std::make_unique>(); for (auto& cache : caches) { array->emplace_back( 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 path_filter, std::unique_ptr callback) { int64_t trace_id = blink::cache_storage::CreateTraceId(); TRACE_EVENT_WITH_FLOW0("CacheStorage", "InspectorCacheStorageAgent::requestEntries", TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT); 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, trace_id, WTF::Bind( [](DataRequestParams params, std::unique_ptr callback, mojom::blink::OpenResultPtr result) { if (result->is_status()) { callback->sendFailure(ProtocolResponse::Error(String::Format( "Error requesting cache %s: %s", params.cache_name.Utf8().c_str(), CacheStorageErrorString(result->get_status()).data()))); } else { auto request = std::make_unique( 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 callback) { int64_t trace_id = blink::cache_storage::CreateTraceId(); TRACE_EVENT_WITH_FLOW0("CacheStorage", "InspectorCacheStorageAgent::deleteCache", TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT); 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, trace_id, WTF::Bind( [](std::unique_ptr 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 callback) { int64_t trace_id = blink::cache_storage::CreateTraceId(); TRACE_EVENT_WITH_FLOW0("CacheStorage", "InspectorCacheStorageAgent::deleteEntry", TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT); 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, trace_id, WTF::Bind( [](String cache_name, String request, int64_t trace_id, std::unique_ptr callback, mojom::blink::OpenResultPtr result) { if (result->is_status()) { callback->sendFailure(ProtocolResponse::Error(String::Format( "Error requesting cache %s: %s", cache_name.Utf8().c_str(), CacheStorageErrorString(result->get_status()).data()))); } else { Vector 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), trace_id, WTF::Bind( [](mojom::blink::CacheStorageCacheAssociatedPtr cache_ptr, std::unique_ptr 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, trace_id, std::move(callback))); } void InspectorCacheStorageAgent::requestCachedResponse( const String& cache_id, const String& request_url, const std::unique_ptr> request_headers, std::unique_ptr callback) { int64_t trace_id = blink::cache_storage::CreateTraceId(); TRACE_EVENT_WITH_FLOW0("CacheStorage", "InspectorCacheStorageAgent::requestCachedResponse", TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT); 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 (const std::unique_ptr& header : *request_headers) { 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), trace_id, WTF::Bind( [](std::unique_ptr 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 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