1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/cache_storage/cache_storage_dispatcher_host.h"
6
7 #include "base/bind.h"
8 #include "base/feature_list.h"
9 #include "base/metrics/histogram_macros.h"
10 #include "base/strings/string16.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/time/time.h"
13 #include "base/trace_event/trace_event.h"
14 #include "base/trace_event/traced_value.h"
15 #include "content/browser/cache_storage/cache_storage.h"
16 #include "content/browser/cache_storage/cache_storage_cache.h"
17 #include "content/browser/cache_storage/cache_storage_context_impl.h"
18 #include "content/browser/cache_storage/cache_storage_histogram_utils.h"
19 #include "content/browser/cache_storage/cache_storage_manager.h"
20 #include "content/browser/cache_storage/cache_storage_trace_utils.h"
21 #include "content/common/background_fetch/background_fetch_types.h"
22 #include "content/public/common/content_features.h"
23 #include "content/public/common/origin_util.h"
24 #include "content/public/common/referrer_type_converters.h"
25 #include "mojo/public/cpp/bindings/message.h"
26 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
27 #include "net/http/http_response_headers.h"
28 #include "services/network/public/cpp/cross_origin_embedder_policy.h"
29 #include "services/network/public/cpp/cross_origin_resource_policy.h"
30 #include "services/network/public/mojom/cross_origin_embedder_policy.mojom.h"
31 #include "third_party/blink/public/common/blob/blob_utils.h"
32 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
33 #include "url/gurl.h"
34 #include "url/origin.h"
35
36 namespace content {
37
38 namespace {
39
40 using blink::mojom::CacheStorageError;
41 using blink::mojom::CacheStorageVerboseError;
42 using network::CrossOriginEmbedderPolicy;
43 using network::CrossOriginResourcePolicy;
44 using network::mojom::FetchResponseType;
45 using network::mojom::RequestMode;
46
47 // TODO(lucmult): Check this before binding.
OriginCanAccessCacheStorage(const url::Origin & origin)48 bool OriginCanAccessCacheStorage(const url::Origin& origin) {
49 return !origin.opaque() && IsOriginSecure(origin.GetURL());
50 }
51
52 // Verifies that the BatchOperation list conforms to the constraints imposed
53 // by the web exposed Cache API. Don't permit compromised renderers to use
54 // unexpected operation combinations.
ValidBatchOperations(const std::vector<blink::mojom::BatchOperationPtr> & batch_operations)55 bool ValidBatchOperations(
56 const std::vector<blink::mojom::BatchOperationPtr>& batch_operations) {
57 // At least one operation is required.
58 if (batch_operations.empty())
59 return false;
60 blink::mojom::OperationType type = batch_operations[0]->operation_type;
61 // We must have a defined operation type. All other enum values allowed
62 // by the mojo validator are permitted here.
63 if (type == blink::mojom::OperationType::kUndefined)
64 return false;
65 // Delete operations should only be sent one at a time.
66 if (type == blink::mojom::OperationType::kDelete &&
67 batch_operations.size() > 1) {
68 return false;
69 }
70 // All operations in the list must be the same.
71 for (const auto& op : batch_operations) {
72 if (op->operation_type != type)
73 return false;
74 }
75 return true;
76 }
77
EagerlyReadResponseBody(blink::mojom::FetchAPIResponsePtr response)78 blink::mojom::MatchResultPtr EagerlyReadResponseBody(
79 blink::mojom::FetchAPIResponsePtr response) {
80 if (!response->blob ||
81 !base::FeatureList::IsEnabled(features::kCacheStorageEagerReading)) {
82 return blink::mojom::MatchResult::NewResponse(std::move(response));
83 }
84
85 MojoCreateDataPipeOptions options;
86 options.struct_size = sizeof(MojoCreateDataPipeOptions);
87 options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
88 options.element_num_bytes = 1;
89 options.capacity_num_bytes =
90 blink::BlobUtils::GetDataPipeCapacity(response->blob->size);
91
92 mojo::ScopedDataPipeProducerHandle producer_handle;
93 mojo::ScopedDataPipeConsumerHandle consumer_handle;
94 MojoResult rv = CreateDataPipe(&options, &producer_handle, &consumer_handle);
95 if (rv != MOJO_RESULT_OK)
96 return blink::mojom::MatchResult::NewResponse(std::move(response));
97
98 mojo::PendingRemote<blink::mojom::BlobReaderClient> reader_client;
99 auto pending_receiver = reader_client.InitWithNewPipeAndPassReceiver();
100
101 mojo::Remote<blink::mojom::Blob> blob(std::move(response->blob->blob));
102 blob->ReadAll(std::move(producer_handle), std::move(reader_client));
103
104 // Clear the main body blob entry. There should still be a |side_data_blob|
105 // value for reading code cache, however.
106 response->blob = nullptr;
107 DCHECK(response->side_data_blob);
108
109 return blink::mojom::MatchResult::NewEagerResponse(
110 blink::mojom::EagerResponse::New(std::move(response),
111 std::move(consumer_handle),
112 std::move(pending_receiver)));
113 }
114
115 // Enforce the Cross-Origin-Resource-Policy (CORP) of the response
116 // against the requesting document's origin and
117 // Cross-Origin-Embedder-Policy (COEP).
118 // See https://github.com/w3c/ServiceWorker/issues/1490.
ResponseBlockedByCrossOriginResourcePolicy(const blink::mojom::FetchAPIResponse * response,const url::Origin & document_origin,const CrossOriginEmbedderPolicy & document_coep,const mojo::Remote<network::mojom::CrossOriginEmbedderPolicyReporter> & coep_reporter)119 bool ResponseBlockedByCrossOriginResourcePolicy(
120 const blink::mojom::FetchAPIResponse* response,
121 const url::Origin& document_origin,
122 const CrossOriginEmbedderPolicy& document_coep,
123 const mojo::Remote<network::mojom::CrossOriginEmbedderPolicyReporter>&
124 coep_reporter) {
125 // optional short-circuit to avoid parsing CORP again and again when no COEP
126 // policy is defined.
127 if (document_coep.value ==
128 network::mojom::CrossOriginEmbedderPolicyValue::kNone &&
129 document_coep.report_only_value ==
130 network::mojom::CrossOriginEmbedderPolicyValue::kNone) {
131 return false;
132 }
133
134 // Cross-Origin-Resource-Policy is checked only for cross-origin responses
135 // that were requested by no-cors requests. Those result in opaque responses.
136 // See https://github.com/whatwg/fetch/issues/985.
137 if (response->response_type != FetchResponseType::kOpaque)
138 return false;
139
140 base::Optional<std::string> corp_header_value;
141 auto corp_header =
142 response->headers.find(network::CrossOriginResourcePolicy::kHeaderName);
143 if (corp_header != response->headers.end())
144 corp_header_value = corp_header->second;
145
146 return CrossOriginResourcePolicy::IsBlockedByHeaderValue(
147 response->url_list.back(), response->url_list.front(),
148 document_origin, corp_header_value, RequestMode::kNoCors,
149 document_origin, document_coep,
150 coep_reporter ? coep_reporter.get() : nullptr)
151 .has_value();
152 }
153
154 } // namespace
155
156 // Implements the mojom interface CacheStorageCache. It's owned by
157 // CacheStorageDispatcherHost and it's destroyed when client drops the mojo
158 // remote which in turn removes from UniqueAssociatedReceiverSet in
159 // CacheStorageDispatcherHost.
160 class CacheStorageDispatcherHost::CacheImpl
161 : public blink::mojom::CacheStorageCache {
162 public:
CacheImpl(CacheStorageCacheHandle cache_handle,const url::Origin & origin,const CrossOriginEmbedderPolicy & cross_origin_embedder_policy,mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter> coep_reporter)163 explicit CacheImpl(
164 CacheStorageCacheHandle cache_handle,
165 const url::Origin& origin,
166 const CrossOriginEmbedderPolicy& cross_origin_embedder_policy,
167 mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
168 coep_reporter)
169 : cache_handle_(std::move(cache_handle)),
170 origin_(origin),
171 cross_origin_embedder_policy_(cross_origin_embedder_policy),
172 coep_reporter_(std::move(coep_reporter)) {}
173
~CacheImpl()174 ~CacheImpl() override { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); }
175
176 // blink::mojom::CacheStorageCache implementation:
Match(blink::mojom::FetchAPIRequestPtr request,blink::mojom::CacheQueryOptionsPtr match_options,bool in_related_fetch_event,int64_t trace_id,MatchCallback callback)177 void Match(blink::mojom::FetchAPIRequestPtr request,
178 blink::mojom::CacheQueryOptionsPtr match_options,
179 bool in_related_fetch_event,
180 int64_t trace_id,
181 MatchCallback callback) override {
182 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
183 TRACE_EVENT_WITH_FLOW2("CacheStorage",
184 "CacheStorageDispatchHost::CacheImpl::Match",
185 TRACE_ID_GLOBAL(trace_id),
186 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
187 "request", CacheStorageTracedValue(request),
188 "options", CacheStorageTracedValue(match_options));
189
190 content::CacheStorageCache* cache = cache_handle_.value();
191 bool cache_initialized =
192 cache ? cache->GetInitState() ==
193 content::CacheStorageCache::InitState::Initialized
194 : false;
195
196 auto cb = base::BindOnce(
197 [](base::WeakPtr<CacheImpl> self, base::TimeTicks start_time,
198 bool ignore_search, bool in_related_fetch_event,
199 bool cache_initialized, int64_t trace_id,
200 blink::mojom::CacheStorageCache::MatchCallback callback,
201 blink::mojom::CacheStorageError error,
202 blink::mojom::FetchAPIResponsePtr response) {
203 if (!self)
204 return;
205 base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
206 UMA_HISTOGRAM_LONG_TIMES("ServiceWorkerCache.Cache.Browser.Match",
207 elapsed);
208 if (ignore_search) {
209 UMA_HISTOGRAM_LONG_TIMES(
210 "ServiceWorkerCache.Cache.Browser.Match.IgnoreSearch", elapsed);
211 }
212 if (cache_initialized) {
213 UMA_HISTOGRAM_LONG_TIMES(
214 "ServiceWorkerCache.Cache.Browser.Match.Initialized", elapsed);
215 }
216 if (in_related_fetch_event) {
217 UMA_HISTOGRAM_LONG_TIMES(
218 "ServiceWorkerCache.Cache.Browser.Match.RelatedFetchEvent",
219 elapsed);
220 }
221 if (error == CacheStorageError::kErrorNotFound) {
222 UMA_HISTOGRAM_LONG_TIMES(
223 "ServiceWorkerCache.Cache.Browser.Match.Miss", elapsed);
224 }
225 if (error != CacheStorageError::kSuccess) {
226 TRACE_EVENT_WITH_FLOW1(
227 "CacheStorage",
228 "CacheStorageDispatchHost::CacheImpl::Match::Callback",
229 TRACE_ID_GLOBAL(trace_id),
230 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "status",
231 CacheStorageTracedValue(error));
232 std::move(callback).Run(
233 blink::mojom::MatchResult::NewStatus(error));
234 return;
235 }
236
237 // Enforce the Cross-Origin-Resource-Policy (CORP) of the response
238 // against the requesting document's origin and
239 // Cross-Origin-Embedder-Policy (COEP).
240 if (ResponseBlockedByCrossOriginResourcePolicy(
241 response.get(), self->origin_,
242 self->cross_origin_embedder_policy_, self->coep_reporter_)) {
243 std::move(callback).Run(blink::mojom::MatchResult::NewStatus(
244 CacheStorageError::kErrorCrossOriginResourcePolicy));
245 return;
246 }
247
248 UMA_HISTOGRAM_LONG_TIMES("ServiceWorkerCache.Cache.Browser.Match.Hit",
249 elapsed);
250 TRACE_EVENT_WITH_FLOW1(
251 "CacheStorage",
252 "CacheStorageDispatchHost::CacheImpl::Match::Callback",
253 TRACE_ID_GLOBAL(trace_id),
254 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "response",
255 CacheStorageTracedValue(response));
256
257 blink::mojom::MatchResultPtr result;
258 if (in_related_fetch_event) {
259 result = EagerlyReadResponseBody(std::move(response));
260 } else {
261 result =
262 blink::mojom::MatchResult::NewResponse(std::move(response));
263 }
264 std::move(callback).Run(std::move(result));
265 },
266 weak_factory_.GetWeakPtr(), base::TimeTicks::Now(),
267 match_options->ignore_search, in_related_fetch_event, cache_initialized,
268 trace_id, std::move(callback));
269
270 if (!cache) {
271 std::move(cb).Run(CacheStorageError::kErrorNotFound, nullptr);
272 return;
273 }
274
275 CacheStorageSchedulerPriority priority =
276 CacheStorageSchedulerPriority::kNormal;
277 if (in_related_fetch_event &&
278 base::FeatureList::IsEnabled(
279 features::kCacheStorageHighPriorityMatch)) {
280 priority = CacheStorageSchedulerPriority::kHigh;
281 }
282
283 cache->Match(std::move(request), std::move(match_options), priority,
284 trace_id, std::move(cb));
285 }
286
MatchAll(blink::mojom::FetchAPIRequestPtr request,blink::mojom::CacheQueryOptionsPtr match_options,int64_t trace_id,MatchAllCallback callback)287 void MatchAll(blink::mojom::FetchAPIRequestPtr request,
288 blink::mojom::CacheQueryOptionsPtr match_options,
289 int64_t trace_id,
290 MatchAllCallback callback) override {
291 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
292 TRACE_EVENT_WITH_FLOW2("CacheStorage",
293 "CacheStorageDispatchHost::CacheImpl::MatchAll",
294 TRACE_ID_GLOBAL(trace_id),
295 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
296 "request", CacheStorageTracedValue(request),
297 "options", CacheStorageTracedValue(match_options));
298
299 auto cb = base::BindOnce(
300 [](base::WeakPtr<CacheImpl> self, base::TimeTicks start_time,
301 int64_t trace_id,
302 blink::mojom::CacheStorageCache::MatchAllCallback callback,
303 blink::mojom::CacheStorageError error,
304 std::vector<blink::mojom::FetchAPIResponsePtr> responses) {
305 if (!self)
306 return;
307 base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
308 UMA_HISTOGRAM_LONG_TIMES("ServiceWorkerCache.Cache.Browser.MatchAll",
309 elapsed);
310 if (error != CacheStorageError::kSuccess &&
311 error != CacheStorageError::kErrorNotFound) {
312 TRACE_EVENT_WITH_FLOW1(
313 "CacheStorage",
314 "CacheStorageDispatchHost::CacheImpl::MatchAll::Callback",
315 TRACE_ID_GLOBAL(trace_id),
316 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "status",
317 CacheStorageTracedValue(error));
318 std::move(callback).Run(
319 blink::mojom::MatchAllResult::NewStatus(error));
320 return;
321 }
322
323 // Enforce the Cross-Origin-Resource-Policy (CORP) of the response
324 // against the requesting document's origin and
325 // Cross-Origin-Embedder-Policy (COEP).
326 for (const auto& response : responses) {
327 if (ResponseBlockedByCrossOriginResourcePolicy(
328 response.get(), self->origin_,
329 self->cross_origin_embedder_policy_,
330 self->coep_reporter_)) {
331 std::move(callback).Run(blink::mojom::MatchAllResult::NewStatus(
332 CacheStorageError::kErrorCrossOriginResourcePolicy));
333 return;
334 }
335 }
336
337 TRACE_EVENT_WITH_FLOW1(
338 "CacheStorage",
339 "CacheStorageDispatchHost::CacheImpl::MatchAll::Callback",
340 TRACE_ID_GLOBAL(trace_id),
341 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
342 "response_list", CacheStorageTracedValue(responses));
343 std::move(callback).Run(
344 blink::mojom::MatchAllResult::NewResponses(std::move(responses)));
345 },
346 weak_factory_.GetWeakPtr(), base::TimeTicks::Now(), trace_id,
347 std::move(callback));
348
349 content::CacheStorageCache* cache = cache_handle_.value();
350 if (!cache) {
351 std::move(cb).Run(CacheStorageError::kErrorNotFound,
352 std::vector<blink::mojom::FetchAPIResponsePtr>());
353 return;
354 }
355
356 cache->MatchAll(std::move(request), std::move(match_options), trace_id,
357 std::move(cb));
358 }
359
Keys(blink::mojom::FetchAPIRequestPtr request,blink::mojom::CacheQueryOptionsPtr match_options,int64_t trace_id,KeysCallback callback)360 void Keys(blink::mojom::FetchAPIRequestPtr request,
361 blink::mojom::CacheQueryOptionsPtr match_options,
362 int64_t trace_id,
363 KeysCallback callback) override {
364 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
365 TRACE_EVENT_WITH_FLOW2("CacheStorage",
366 "CacheStorageDispatchHost::CacheImpl::Keys",
367 TRACE_ID_GLOBAL(trace_id),
368 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
369 "request", CacheStorageTracedValue(request),
370 "options", CacheStorageTracedValue(match_options));
371
372 auto cb = base::BindOnce(
373 [](base::TimeTicks start_time, int64_t trace_id,
374 blink::mojom::CacheStorageCache::KeysCallback callback,
375 blink::mojom::CacheStorageError error,
376 std::unique_ptr<content::CacheStorageCache::Requests> requests) {
377 UMA_HISTOGRAM_LONG_TIMES("ServiceWorkerCache.Cache.Browser.Keys",
378 base::TimeTicks::Now() - start_time);
379 if (error != CacheStorageError::kSuccess) {
380 TRACE_EVENT_WITH_FLOW1(
381 "CacheStorage",
382 "CacheStorageDispatchHost::CacheImpl::Keys::Callback",
383 TRACE_ID_GLOBAL(trace_id),
384 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "status",
385 CacheStorageTracedValue(error));
386 std::move(callback).Run(
387 blink::mojom::CacheKeysResult::NewStatus(error));
388 return;
389 }
390 std::vector<blink::mojom::FetchAPIRequestPtr> requests_;
391 for (const auto& request : *requests) {
392 requests_.push_back(
393 BackgroundFetchSettledFetch::CloneRequest(request));
394 }
395
396 TRACE_EVENT_WITH_FLOW1(
397 "CacheStorage",
398 "CacheStorageDispatchHost::CacheImpl::Keys::Callback",
399 TRACE_ID_GLOBAL(trace_id),
400 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
401 "request_list", CacheStorageTracedValue(requests_));
402
403 std::move(callback).Run(
404 blink::mojom::CacheKeysResult::NewKeys(std::move(requests_)));
405 },
406 base::TimeTicks::Now(), trace_id, std::move(callback));
407
408 content::CacheStorageCache* cache = cache_handle_.value();
409 if (!cache) {
410 std::move(cb).Run(CacheStorageError::kErrorNotFound, nullptr);
411 return;
412 }
413
414 cache->Keys(std::move(request), std::move(match_options), trace_id,
415 std::move(cb));
416 }
417
Batch(std::vector<blink::mojom::BatchOperationPtr> batch_operations,int64_t trace_id,BatchCallback callback)418 void Batch(std::vector<blink::mojom::BatchOperationPtr> batch_operations,
419 int64_t trace_id,
420 BatchCallback callback) override {
421 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
422 TRACE_EVENT_WITH_FLOW1(
423 "CacheStorage", "CacheStorageDispatchHost::CacheImpl::Batch",
424 TRACE_ID_GLOBAL(trace_id),
425 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "operation_list",
426 CacheStorageTracedValue(batch_operations));
427
428 if (!ValidBatchOperations(batch_operations)) {
429 mojo::ReportBadMessage("CSDH_UNEXPECTED_OPERATION");
430 return;
431 }
432
433 // Validated batch operations always have at least one entry.
434 blink::mojom::OperationType operation_type =
435 batch_operations[0]->operation_type;
436 int operation_count = batch_operations.size();
437
438 auto cb = base::BindOnce(
439 [](base::TimeTicks start_time,
440 blink::mojom::OperationType operation_type, int operation_count,
441 int64_t trace_id,
442 blink::mojom::CacheStorageCache::BatchCallback callback,
443 blink::mojom::CacheStorageVerboseErrorPtr error) {
444 base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
445 TRACE_EVENT_WITH_FLOW1(
446 "CacheStorage",
447 "CacheStorageDispatchHost::CacheImpl::Batch::Callback",
448 TRACE_ID_GLOBAL(trace_id),
449 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "status",
450 CacheStorageTracedValue(error->value));
451 if (operation_type == blink::mojom::OperationType::kDelete) {
452 DCHECK_EQ(operation_count, 1);
453 UMA_HISTOGRAM_LONG_TIMES(
454 "ServiceWorkerCache.Cache.Browser.DeleteOne", elapsed);
455 } else if (operation_count > 1) {
456 DCHECK_EQ(operation_type, blink::mojom::OperationType::kPut);
457 UMA_HISTOGRAM_LONG_TIMES("ServiceWorkerCache.Cache.Browser.PutMany",
458 elapsed);
459 } else {
460 DCHECK_EQ(operation_type, blink::mojom::OperationType::kPut);
461 UMA_HISTOGRAM_LONG_TIMES("ServiceWorkerCache.Cache.Browser.PutOne",
462 elapsed);
463 }
464 std::move(callback).Run(std::move(error));
465 },
466 base::TimeTicks::Now(), operation_type, operation_count, trace_id,
467 std::move(callback));
468
469 content::CacheStorageCache* cache = cache_handle_.value();
470 if (!cache) {
471 std::move(cb).Run(CacheStorageVerboseError::New(
472 CacheStorageError::kErrorNotFound, base::nullopt));
473 return;
474 }
475
476 cache->BatchOperation(
477 std::move(batch_operations), trace_id, std::move(cb),
478 base::BindOnce(
479 [](mojo::ReportBadMessageCallback bad_message_callback) {
480 std::move(bad_message_callback).Run("CSDH_UNEXPECTED_OPERATION");
481 },
482 mojo::GetBadMessageCallback()));
483 }
484
485 CacheStorageCacheHandle cache_handle_;
486 const url::Origin origin_;
487 const CrossOriginEmbedderPolicy cross_origin_embedder_policy_;
488 mojo::Remote<network::mojom::CrossOriginEmbedderPolicyReporter>
489 coep_reporter_;
490 SEQUENCE_CHECKER(sequence_checker_);
491
492 base::WeakPtrFactory<CacheImpl> weak_factory_{this};
493 DISALLOW_COPY_AND_ASSIGN(CacheImpl);
494 };
495
496 // Implements the mojom interface CacheStorage. It's owned by the
497 // CacheStorageDispatcherHost. The CacheStorageImpl is destroyed when the
498 // client drops its mojo remote which in turn removes from UniqueReceiverSet in
499 // CacheStorageDispatcherHost.
500 class CacheStorageDispatcherHost::CacheStorageImpl final
501 : public blink::mojom::CacheStorage {
502 public:
CacheStorageImpl(CacheStorageDispatcherHost * owner,const url::Origin & origin,const CrossOriginEmbedderPolicy & cross_origin_embedder_policy,mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter> coep_reporter)503 CacheStorageImpl(
504 CacheStorageDispatcherHost* owner,
505 const url::Origin& origin,
506 const CrossOriginEmbedderPolicy& cross_origin_embedder_policy,
507 mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
508 coep_reporter)
509 : owner_(owner),
510 origin_(origin),
511 cross_origin_embedder_policy_(cross_origin_embedder_policy),
512 coep_reporter_(std::move(coep_reporter)) {
513 // The CacheStorageHandle is empty to start and lazy initialized on first
514 // use via GetOrCreateCacheStorage(). In the future we could eagerly create
515 // the backend when the mojo connection is created.
516 }
517
~CacheStorageImpl()518 ~CacheStorageImpl() override {
519 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
520 }
521
522 // Mojo CacheStorage Interface implementation:
Keys(int64_t trace_id,blink::mojom::CacheStorage::KeysCallback callback)523 void Keys(int64_t trace_id,
524 blink::mojom::CacheStorage::KeysCallback callback) override {
525 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
526 TRACE_EVENT_WITH_FLOW0(
527 "CacheStorage", "CacheStorageDispatchHost::CacheStorageImpl::Keys",
528 TRACE_ID_GLOBAL(trace_id),
529 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
530
531 auto cb = base::BindOnce(
532 [](base::TimeTicks start_time, int64_t trace_id,
533 blink::mojom::CacheStorage::KeysCallback callback,
534 std::vector<std::string> cache_names) {
535 std::vector<base::string16> string16s;
536 for (const auto& name : cache_names) {
537 string16s.push_back(base::UTF8ToUTF16(name));
538 }
539 UMA_HISTOGRAM_LONG_TIMES(
540 "ServiceWorkerCache.CacheStorage.Browser.Keys",
541 base::TimeTicks::Now() - start_time);
542 TRACE_EVENT_WITH_FLOW1(
543 "CacheStorage",
544 "CacheStorageDispatchHost::CacheStorageImpl::Keys::Callback",
545 TRACE_ID_GLOBAL(trace_id),
546 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "key_list",
547 CacheStorageTracedValue(string16s));
548 std::move(callback).Run(string16s);
549 },
550 base::TimeTicks::Now(), trace_id, std::move(callback));
551
552 content::CacheStorage* cache_storage = GetOrCreateCacheStorage();
553 if (!cache_storage) {
554 std::move(cb).Run(std::vector<std::string>());
555 return;
556 }
557
558 cache_storage->EnumerateCaches(trace_id, std::move(cb));
559 }
560
Delete(const base::string16 & cache_name,int64_t trace_id,blink::mojom::CacheStorage::DeleteCallback callback)561 void Delete(const base::string16& cache_name,
562 int64_t trace_id,
563 blink::mojom::CacheStorage::DeleteCallback callback) override {
564 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
565 std::string utf8_cache_name = base::UTF16ToUTF8(cache_name);
566 TRACE_EVENT_WITH_FLOW1("CacheStorage",
567 "CacheStorageDispatchHost::CacheStorageImpl::Delete",
568 TRACE_ID_GLOBAL(trace_id),
569 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
570 "cache_name", utf8_cache_name);
571
572 auto cb = base::BindOnce(
573 [](base::TimeTicks start_time, int64_t trace_id,
574 blink::mojom::CacheStorage::DeleteCallback callback,
575 CacheStorageError error) {
576 UMA_HISTOGRAM_LONG_TIMES(
577 "ServiceWorkerCache.CacheStorage.Browser.Delete",
578 base::TimeTicks::Now() - start_time);
579 TRACE_EVENT_WITH_FLOW1(
580 "CacheStorage",
581 "CacheStorageDispatchHost::CacheStorageImpl::Delete::Callback",
582 TRACE_ID_GLOBAL(trace_id),
583 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "status",
584 CacheStorageTracedValue(error));
585 std::move(callback).Run(error);
586 },
587 base::TimeTicks::Now(), trace_id, std::move(callback));
588
589 content::CacheStorage* cache_storage = GetOrCreateCacheStorage();
590 if (!cache_storage) {
591 std::move(cb).Run(MakeErrorStorage(ErrorStorageType::kStorageHandleNull));
592 return;
593 }
594
595 cache_storage->DoomCache(utf8_cache_name, trace_id, std::move(cb));
596 }
597
Has(const base::string16 & cache_name,int64_t trace_id,blink::mojom::CacheStorage::HasCallback callback)598 void Has(const base::string16& cache_name,
599 int64_t trace_id,
600 blink::mojom::CacheStorage::HasCallback callback) override {
601 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
602 std::string utf8_cache_name = base::UTF16ToUTF8(cache_name);
603 TRACE_EVENT_WITH_FLOW1("CacheStorage",
604 "CacheStorageDispatchHost::CacheStorageImpl::Has",
605 TRACE_ID_GLOBAL(trace_id),
606 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
607 "cache_name", utf8_cache_name);
608
609 auto cb = base::BindOnce(
610 [](base::TimeTicks start_time, int64_t trace_id,
611 blink::mojom::CacheStorage::HasCallback callback, bool has_cache,
612 CacheStorageError error) {
613 if (!has_cache)
614 error = CacheStorageError::kErrorNotFound;
615 TRACE_EVENT_WITH_FLOW1(
616 "CacheStorage",
617 "CacheStorageDispatchHost::CacheStorageImpl::Has::Callback",
618 TRACE_ID_GLOBAL(trace_id),
619 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "status",
620 CacheStorageTracedValue(error));
621 UMA_HISTOGRAM_LONG_TIMES(
622 "ServiceWorkerCache.CacheStorage.Browser.Has",
623 base::TimeTicks::Now() - start_time);
624 std::move(callback).Run(error);
625 },
626 base::TimeTicks::Now(), trace_id, std::move(callback));
627
628 content::CacheStorage* cache_storage = GetOrCreateCacheStorage();
629 if (!cache_storage) {
630 std::move(cb).Run(/* has_cache = */ false,
631 MakeErrorStorage(ErrorStorageType::kStorageHandleNull));
632 return;
633 }
634
635 cache_storage->HasCache(utf8_cache_name, trace_id, std::move(cb));
636 }
637
Match(blink::mojom::FetchAPIRequestPtr request,blink::mojom::MultiCacheQueryOptionsPtr match_options,bool in_related_fetch_event,int64_t trace_id,blink::mojom::CacheStorage::MatchCallback callback)638 void Match(blink::mojom::FetchAPIRequestPtr request,
639 blink::mojom::MultiCacheQueryOptionsPtr match_options,
640 bool in_related_fetch_event,
641 int64_t trace_id,
642 blink::mojom::CacheStorage::MatchCallback callback) override {
643 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
644 TRACE_EVENT_WITH_FLOW2("CacheStorage",
645 "CacheStorageDispatchHost::CacheStorageImpl::Match",
646 TRACE_ID_GLOBAL(trace_id),
647 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
648 "request", CacheStorageTracedValue(request),
649 "options", CacheStorageTracedValue(match_options));
650
651 auto cb = BindOnce(
652 [](base::WeakPtr<CacheStorageImpl> self, base::TimeTicks start_time,
653 bool match_all_caches, bool in_related_fetch_event, int64_t trace_id,
654 blink::mojom::CacheStorage::MatchCallback callback,
655 CacheStorageError error,
656 blink::mojom::FetchAPIResponsePtr response) {
657 if (!self)
658 return;
659
660 base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
661 if (match_all_caches) {
662 UMA_HISTOGRAM_LONG_TIMES(
663 "ServiceWorkerCache.CacheStorage.Browser.MatchAllCaches",
664 elapsed);
665 } else {
666 UMA_HISTOGRAM_LONG_TIMES(
667 "ServiceWorkerCache.CacheStorage.Browser.MatchOneCache",
668 elapsed);
669 }
670 if (error != CacheStorageError::kSuccess) {
671 TRACE_EVENT_WITH_FLOW1(
672 "CacheStorage",
673 "CacheStorageDispatchHost::CacheStorageImpl::Match::Callback",
674 TRACE_ID_GLOBAL(trace_id),
675 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "status",
676 CacheStorageTracedValue(error));
677 std::move(callback).Run(
678 blink::mojom::MatchResult::NewStatus(error));
679 return;
680 }
681 TRACE_EVENT_WITH_FLOW1(
682 "CacheStorage",
683 "CacheStorageDispatchHost::CacheStorageImpl::Match::Callback",
684 TRACE_ID_GLOBAL(trace_id),
685 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "response",
686 CacheStorageTracedValue(response));
687
688 // Enforce the Cross-Origin-Resource-Policy (CORP) of the response
689 // against the requesting document's origin and
690 // Cross-Origin-Embedder-Policy (COEP).
691 if (ResponseBlockedByCrossOriginResourcePolicy(
692 response.get(), self->origin_,
693 self->cross_origin_embedder_policy_, self->coep_reporter_)) {
694 std::move(callback).Run(blink::mojom::MatchResult::NewStatus(
695 CacheStorageError::kErrorCrossOriginResourcePolicy));
696 return;
697 }
698
699 blink::mojom::MatchResultPtr result;
700 if (in_related_fetch_event) {
701 result = EagerlyReadResponseBody(std::move(response));
702 } else {
703 result =
704 blink::mojom::MatchResult::NewResponse(std::move(response));
705 }
706 std::move(callback).Run(std::move(result));
707 },
708 weak_factory_.GetWeakPtr(), base::TimeTicks::Now(),
709 !match_options->cache_name, in_related_fetch_event, trace_id,
710 std::move(callback));
711
712 content::CacheStorage* cache_storage = GetOrCreateCacheStorage();
713 if (!cache_storage) {
714 std::move(cb).Run(CacheStorageError::kErrorNotFound, nullptr);
715 return;
716 }
717
718 CacheStorageSchedulerPriority priority =
719 CacheStorageSchedulerPriority::kNormal;
720 if (in_related_fetch_event &&
721 base::FeatureList::IsEnabled(
722 features::kCacheStorageHighPriorityMatch)) {
723 priority = CacheStorageSchedulerPriority::kHigh;
724 }
725
726 if (!match_options->cache_name) {
727 cache_storage->MatchAllCaches(std::move(request),
728 std::move(match_options->query_options),
729 priority, trace_id, std::move(cb));
730 return;
731 }
732 std::string cache_name = base::UTF16ToUTF8(*match_options->cache_name);
733 cache_storage->MatchCache(std::move(cache_name), std::move(request),
734 std::move(match_options->query_options), priority,
735 trace_id, std::move(cb));
736 }
737
Open(const base::string16 & cache_name,int64_t trace_id,blink::mojom::CacheStorage::OpenCallback callback)738 void Open(const base::string16& cache_name,
739 int64_t trace_id,
740 blink::mojom::CacheStorage::OpenCallback callback) override {
741 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
742 std::string utf8_cache_name = base::UTF16ToUTF8(cache_name);
743 TRACE_EVENT_WITH_FLOW1("CacheStorage",
744 "CacheStorageDispatchHost::CacheStorageImpl::Open",
745 TRACE_ID_GLOBAL(trace_id),
746 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
747 "cache_name", utf8_cache_name);
748 content::CacheStorage* cache_storage = GetOrCreateCacheStorage();
749 auto cb = base::BindOnce(
750 [](base::WeakPtr<CacheStorageImpl> self, base::TimeTicks start_time,
751 int64_t trace_id, blink::mojom::CacheStorage::OpenCallback callback,
752 CacheStorageCacheHandle cache_handle, CacheStorageError error) {
753 if (!self)
754 return;
755
756 UMA_HISTOGRAM_LONG_TIMES(
757 "ServiceWorkerCache.CacheStorage.Browser.Open",
758 base::TimeTicks::Now() - start_time);
759
760 TRACE_EVENT_WITH_FLOW1(
761 "CacheStorage",
762 "CacheStorageDispatchHost::CacheStorageImpl::Open::Callback",
763 TRACE_ID_GLOBAL(trace_id),
764 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "status",
765 CacheStorageTracedValue(error));
766
767 if (error != CacheStorageError::kSuccess) {
768 std::move(callback).Run(blink::mojom::OpenResult::NewStatus(error));
769 return;
770 }
771
772 mojo::PendingAssociatedRemote<blink::mojom::CacheStorageCache>
773 pending_remote;
774 mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
775 coep_reporter;
776 if (self->coep_reporter_) {
777 self->coep_reporter_->Clone(
778 coep_reporter.InitWithNewPipeAndPassReceiver());
779 }
780 auto cache_impl = std::make_unique<CacheImpl>(
781 std::move(cache_handle), self->origin_,
782 self->cross_origin_embedder_policy_, std::move(coep_reporter));
783 self->owner_->AddCacheReceiver(
784 std::move(cache_impl),
785 pending_remote.InitWithNewEndpointAndPassReceiver());
786
787 std::move(callback).Run(
788 blink::mojom::OpenResult::NewCache(std::move(pending_remote)));
789 },
790 weak_factory_.GetWeakPtr(), base::TimeTicks::Now(), trace_id,
791 std::move(callback));
792
793 if (!cache_storage) {
794 std::move(cb).Run(CacheStorageCacheHandle(),
795 MakeErrorStorage(ErrorStorageType::kStorageHandleNull));
796 return;
797 }
798
799 cache_storage->OpenCache(utf8_cache_name, trace_id, std::move(cb));
800 }
801
802 private:
803 // Helper method that returns the current CacheStorageHandle value. If the
804 // handle is closed, then it attempts to open a new CacheStorageHandle
805 // automatically. This automatic open is necessary to re-attach to the
806 // backend after the browser storage has been wiped.
GetOrCreateCacheStorage()807 content::CacheStorage* GetOrCreateCacheStorage() {
808 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
809 DCHECK(owner_);
810 if (!cache_storage_handle_.value())
811 cache_storage_handle_ = owner_->OpenCacheStorage(origin_);
812 return cache_storage_handle_.value();
813 }
814
815 // Owns this.
816 CacheStorageDispatcherHost* const owner_;
817
818 const url::Origin origin_;
819 const CrossOriginEmbedderPolicy cross_origin_embedder_policy_;
820 mojo::Remote<network::mojom::CrossOriginEmbedderPolicyReporter>
821 coep_reporter_;
822 CacheStorageHandle cache_storage_handle_;
823
824 SEQUENCE_CHECKER(sequence_checker_);
825 base::WeakPtrFactory<CacheStorageImpl> weak_factory_{this};
826 DISALLOW_COPY_AND_ASSIGN(CacheStorageImpl);
827 };
828
829 CacheStorageDispatcherHost::CacheStorageDispatcherHost() = default;
830
~CacheStorageDispatcherHost()831 CacheStorageDispatcherHost::~CacheStorageDispatcherHost() {
832 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
833 }
834
Init(CacheStorageContextImpl * context)835 void CacheStorageDispatcherHost::Init(CacheStorageContextImpl* context) {
836 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
837 context_ = context;
838 }
839
AddReceiver(const CrossOriginEmbedderPolicy & cross_origin_embedder_policy,mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter> coep_reporter,const url::Origin & origin,mojo::PendingReceiver<blink::mojom::CacheStorage> receiver)840 void CacheStorageDispatcherHost::AddReceiver(
841 const CrossOriginEmbedderPolicy& cross_origin_embedder_policy,
842 mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
843 coep_reporter,
844 const url::Origin& origin,
845 mojo::PendingReceiver<blink::mojom::CacheStorage> receiver) {
846 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
847 auto impl = std::make_unique<CacheStorageImpl>(
848 this, origin, cross_origin_embedder_policy, std::move(coep_reporter));
849 receivers_.Add(std::move(impl), std::move(receiver));
850 }
851
AddCacheReceiver(std::unique_ptr<CacheImpl> cache_impl,mojo::PendingAssociatedReceiver<blink::mojom::CacheStorageCache> receiver)852 void CacheStorageDispatcherHost::AddCacheReceiver(
853 std::unique_ptr<CacheImpl> cache_impl,
854 mojo::PendingAssociatedReceiver<blink::mojom::CacheStorageCache> receiver) {
855 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
856 cache_receivers_.Add(std::move(cache_impl), std::move(receiver));
857 }
858
OpenCacheStorage(const url::Origin & origin)859 CacheStorageHandle CacheStorageDispatcherHost::OpenCacheStorage(
860 const url::Origin& origin) {
861 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
862 if (!context_ || !OriginCanAccessCacheStorage(origin))
863 return CacheStorageHandle();
864
865 scoped_refptr<CacheStorageManager> manager = context_->CacheManager();
866 if (!manager)
867 return CacheStorageHandle();
868
869 return manager->OpenCacheStorage(origin, CacheStorageOwner::kCacheAPI);
870 }
871
872 } // namespace content
873