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