1 // Copyright (c) 2012 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/appcache/appcache_storage_impl.h"
6 
7 #include <stddef.h>
8 
9 #include <algorithm>
10 #include <functional>
11 #include <limits>
12 #include <set>
13 #include <vector>
14 
15 #include "base/bind.h"
16 #include "base/callback_helpers.h"
17 #include "base/files/file_util.h"
18 #include "base/location.h"
19 #include "base/logging.h"
20 #include "base/single_thread_task_runner.h"
21 #include "base/stl_util.h"
22 #include "base/strings/string_util.h"
23 #include "base/threading/sequenced_task_runner_handle.h"
24 #include "content/browser/appcache/appcache.h"
25 #include "content/browser/appcache/appcache_database.h"
26 #include "content/browser/appcache/appcache_disk_cache_ops.h"
27 #include "content/browser/appcache/appcache_entry.h"
28 #include "content/browser/appcache/appcache_group.h"
29 #include "content/browser/appcache/appcache_histograms.h"
30 #include "content/browser/appcache/appcache_policy.h"
31 #include "content/browser/appcache/appcache_quota_client.h"
32 #include "content/browser/appcache/appcache_response_info.h"
33 #include "content/browser/appcache/appcache_service_impl.h"
34 #include "content/public/browser/browser_task_traits.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "net/base/cache_type.h"
37 #include "net/base/net_errors.h"
38 #include "sql/database.h"
39 #include "sql/transaction.h"
40 #include "storage/browser/quota/quota_client.h"
41 #include "storage/browser/quota/quota_manager.h"
42 #include "storage/browser/quota/quota_manager_proxy.h"
43 #include "third_party/blink/public/common/features.h"
44 #include "third_party/blink/public/mojom/appcache/appcache_info.mojom.h"
45 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
46 
47 namespace content {
48 
49 namespace {
50 
51 constexpr const int kMB = 1024 * 1024;
52 
53 // Hard coded default when not using quota management.
54 constexpr const int kDefaultQuota = 5 * kMB;
55 
56 constexpr base::FilePath::CharType kDiskCacheDirectoryName[] =
57     FILE_PATH_LITERAL("Cache");
58 constexpr base::FilePath::CharType kAppCacheDatabaseName[] =
59     FILE_PATH_LITERAL("Index");
60 
61 // Helpers for clearing data from the AppCacheDatabase.
DeleteGroupAndRelatedRecords(AppCacheDatabase * database,int64_t group_id,std::vector<int64_t> * deletable_response_ids)62 bool DeleteGroupAndRelatedRecords(
63     AppCacheDatabase* database,
64     int64_t group_id,
65     std::vector<int64_t>* deletable_response_ids) {
66   AppCacheDatabase::CacheRecord cache_record;
67   bool success = false;
68   if (database->FindCacheForGroup(group_id, &cache_record)) {
69     database->FindResponseIdsForCacheAsVector(cache_record.cache_id,
70                                               deletable_response_ids);
71     success = database->DeleteGroup(group_id) &&
72               database->DeleteCache(cache_record.cache_id) &&
73               database->DeleteEntriesForCache(cache_record.cache_id) &&
74               database->DeleteNamespacesForCache(cache_record.cache_id) &&
75               database->DeleteOnlineSafeListForCache(cache_record.cache_id) &&
76               database->InsertDeletableResponseIds(*deletable_response_ids);
77   } else {
78     NOTREACHED() << "A existing group without a cache is unexpected";
79     success = database->DeleteGroup(group_id);
80   }
81   return success;
82 }
83 
84 }  // namespace
85 
86 // static
ClearSessionOnlyOrigins(std::unique_ptr<AppCacheDatabase> database,scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,bool force_keep_session_state)87 void AppCacheStorageImpl::ClearSessionOnlyOrigins(
88     std::unique_ptr<AppCacheDatabase> database,
89     scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
90     bool force_keep_session_state) {
91   // If saving session state, only delete the database.
92   if (force_keep_session_state)
93     return;
94 
95   bool has_session_only_appcaches =
96       special_storage_policy.get() &&
97       special_storage_policy->HasSessionOnlyOrigins();
98 
99   // Clearning only session-only databases, and there are none.
100   if (!has_session_only_appcaches)
101     return;
102 
103   std::set<url::Origin> origins;
104   database->FindOriginsWithGroups(&origins);
105   if (origins.empty())
106     return;  // nothing to delete
107 
108   sql::Database* connection = database->db_connection();
109   if (!connection) {
110     NOTREACHED() << "Missing database connection.";
111     return;
112   }
113 
114   for (const url::Origin& origin : origins) {
115     if (!special_storage_policy->IsStorageSessionOnly(origin.GetURL()))
116       continue;
117     if (special_storage_policy->IsStorageProtected(origin.GetURL()))
118       continue;
119 
120     std::vector<AppCacheDatabase::GroupRecord> groups;
121     database->FindGroupsForOrigin(origin, &groups);
122     for (const auto& group : groups) {
123       sql::Transaction transaction(connection);
124       if (!transaction.Begin()) {
125         NOTREACHED() << "Failed to start transaction";
126         return;
127       }
128       std::vector<int64_t> deletable_response_ids;
129       bool success = DeleteGroupAndRelatedRecords(
130           database.get(), group.group_id, &deletable_response_ids);
131       success = success && transaction.Commit();
132       DCHECK(success);
133     }  // for each group
134   }  // for each origin
135 }
136 
137 // DatabaseTask -----------------------------------------
138 
139 class AppCacheStorageImpl::DatabaseTask
140     : public base::RefCountedThreadSafe<DatabaseTask> {
141  public:
DatabaseTask(AppCacheStorageImpl * storage)142   explicit DatabaseTask(AppCacheStorageImpl* storage)
143       : storage_(storage),
144         database_(storage->database_.get()),
145         io_thread_(base::SequencedTaskRunnerHandle::Get()) {
146     DCHECK(io_thread_.get());
147   }
148 
AddDelegate(DelegateReference * delegate_reference)149   void AddDelegate(DelegateReference* delegate_reference) {
150     delegates_.push_back(base::WrapRefCounted(delegate_reference));
151   }
152 
153   // Schedules a task to be Run() on the DB thread. Tasks
154   // are run in the order in which they are scheduled.
155   void Schedule();
156 
157   // Called on the DB thread.
158   virtual void Run() = 0;
159 
160   // Called on the IO thread after Run() has completed.
RunCompleted()161   virtual void RunCompleted() {}
162 
163   // Once scheduled a task cannot be cancelled, but the
164   // call to RunCompleted may be. This method should only be
165   // called on the IO thread. This is used by AppCacheStorageImpl
166   // to cancel the completion calls when AppCacheStorageImpl is
167   // destructed. This method may be overriden to release or delete
168   // additional data associated with the task that is not DB thread
169   // safe. If overriden, this base class method must be called from
170   // within the override.
171   virtual void CancelCompletion();
172 
173  protected:
174   friend class base::RefCountedThreadSafe<DatabaseTask>;
175   virtual ~DatabaseTask() = default;
176 
177   AppCacheStorageImpl* storage_;
178   AppCacheDatabase* const database_;
179   std::vector<scoped_refptr<DelegateReference>> delegates_;
180 
181  private:
182   void CallRun();
183   void CallRunCompleted();
184   void OnFatalError();
185 
186   const scoped_refptr<base::SequencedTaskRunner> io_thread_;
187 };
188 
Schedule()189 void AppCacheStorageImpl::DatabaseTask::Schedule() {
190   DCHECK(storage_);
191   DCHECK(io_thread_->RunsTasksInCurrentSequence());
192   if (!storage_->database_)
193     return;
194 
195   if (storage_->db_task_runner_->PostTask(
196           FROM_HERE, base::BindOnce(&DatabaseTask::CallRun, this))) {
197     storage_->scheduled_database_tasks_.push_back(this);
198   } else {
199     NOTREACHED() << "Thread for database tasks is not running.";
200   }
201 }
202 
CancelCompletion()203 void AppCacheStorageImpl::DatabaseTask::CancelCompletion() {
204   DCHECK(io_thread_->RunsTasksInCurrentSequence());
205   delegates_.clear();
206   storage_ = nullptr;
207 }
208 
CallRun()209 void AppCacheStorageImpl::DatabaseTask::CallRun() {
210   if (!database_->is_disabled()) {
211     Run();
212     if (database_->was_corruption_detected()) {
213       database_->Disable();
214     }
215     if (database_->is_disabled()) {
216       io_thread_->PostTask(FROM_HERE,
217                            base::BindOnce(&DatabaseTask::OnFatalError, this));
218     }
219   }
220   io_thread_->PostTask(FROM_HERE,
221                        base::BindOnce(&DatabaseTask::CallRunCompleted, this));
222 }
223 
CallRunCompleted()224 void AppCacheStorageImpl::DatabaseTask::CallRunCompleted() {
225   if (storage_) {
226     DCHECK(io_thread_->RunsTasksInCurrentSequence());
227     DCHECK(storage_->scheduled_database_tasks_.front() == this);
228     storage_->scheduled_database_tasks_.pop_front();
229     RunCompleted();
230     delegates_.clear();
231   }
232 }
233 
OnFatalError()234 void AppCacheStorageImpl::DatabaseTask::OnFatalError() {
235   if (storage_) {
236     DCHECK(io_thread_->RunsTasksInCurrentSequence());
237     storage_->Disable();
238     storage_->DeleteAndStartOver();
239   }
240 }
241 
242 // InitTask -------
243 
244 class AppCacheStorageImpl::InitTask : public DatabaseTask {
245  public:
InitTask(AppCacheStorageImpl * storage)246   explicit InitTask(AppCacheStorageImpl* storage)
247       : DatabaseTask(storage), last_group_id_(0),
248         last_cache_id_(0), last_response_id_(0),
249         last_deletable_response_rowid_(0) {
250     if (!storage->is_incognito_) {
251       db_file_path_ =
252           storage->cache_directory_.Append(kAppCacheDatabaseName);
253       disk_cache_directory_ =
254           storage->cache_directory_.Append(kDiskCacheDirectoryName);
255     }
256   }
257 
258   // DatabaseTask:
259   void Run() override;
260   void RunCompleted() override;
261 
262  protected:
263   ~InitTask() override = default;
264 
265  private:
266   base::FilePath db_file_path_;
267   base::FilePath disk_cache_directory_;
268   int64_t last_group_id_;
269   int64_t last_cache_id_;
270   int64_t last_response_id_;
271   int64_t last_deletable_response_rowid_;
272   std::map<url::Origin, int64_t> usage_map_;
273 };
274 
Run()275 void AppCacheStorageImpl::InitTask::Run() {
276   // If there is no sql database, ensure there is no disk cache either.
277   if (!db_file_path_.empty() &&
278       !base::PathExists(db_file_path_) &&
279       base::DirectoryExists(disk_cache_directory_)) {
280     base::DeletePathRecursively(disk_cache_directory_);
281     if (base::DirectoryExists(disk_cache_directory_)) {
282       database_->Disable();  // This triggers OnFatalError handling.
283       return;
284     }
285   }
286 
287   database_->FindLastStorageIds(
288       &last_group_id_, &last_cache_id_, &last_response_id_,
289       &last_deletable_response_rowid_);
290   database_->GetAllOriginUsage(&usage_map_);
291 }
292 
RunCompleted()293 void AppCacheStorageImpl::InitTask::RunCompleted() {
294   storage_->last_group_id_ = last_group_id_;
295   storage_->last_cache_id_ = last_cache_id_;
296   storage_->last_response_id_ = last_response_id_;
297   storage_->last_deletable_response_rowid_ = last_deletable_response_rowid_;
298 
299   if (!storage_->is_disabled()) {
300     storage_->usage_map_.swap(usage_map_);
301     const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5);
302     base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
303         FROM_HERE,
304         base::BindOnce(
305             &AppCacheStorageImpl::DelayedStartDeletingUnusedResponses,
306             storage_->weak_factory_.GetWeakPtr()),
307         kDelay);
308   }
309 
310   storage_->service()->NotifyStorageReady();
311 }
312 
313 // DisableDatabaseTask -------
314 
315 class AppCacheStorageImpl::DisableDatabaseTask : public DatabaseTask {
316  public:
DisableDatabaseTask(AppCacheStorageImpl * storage)317   explicit DisableDatabaseTask(AppCacheStorageImpl* storage)
318       : DatabaseTask(storage) {}
319 
320   // DatabaseTask:
Run()321   void Run() override { database_->Disable(); }
322 
323  protected:
324   ~DisableDatabaseTask() override = default;
325 };
326 
327 // GetAllInfoTask -------
328 
329 class AppCacheStorageImpl::GetAllInfoTask : public DatabaseTask {
330  public:
GetAllInfoTask(AppCacheStorageImpl * storage)331   explicit GetAllInfoTask(AppCacheStorageImpl* storage)
332       : DatabaseTask(storage),
333         info_collection_(base::MakeRefCounted<AppCacheInfoCollection>()) {}
334 
335   // DatabaseTask:
336   void Run() override;
337   void RunCompleted() override;
338 
339  protected:
340   ~GetAllInfoTask() override = default;
341 
342  private:
343   scoped_refptr<AppCacheInfoCollection> info_collection_;
344 };
345 
Run()346 void AppCacheStorageImpl::GetAllInfoTask::Run() {
347   std::set<url::Origin> origins;
348   database_->FindOriginsWithGroups(&origins);
349   for (const url::Origin& origin : origins) {
350     std::vector<blink::mojom::AppCacheInfo> infos;
351 
352     std::vector<AppCacheDatabase::GroupRecord> groups;
353     database_->FindGroupsForOrigin(origin, &groups);
354     for (const auto& group : groups) {
355       AppCacheDatabase::CacheRecord cache_record;
356       database_->FindCacheForGroup(group.group_id, &cache_record);
357       if (!database_->HasValidOriginTrialToken(&cache_record))
358         continue;
359 
360       blink::mojom::AppCacheInfo info;
361       info.manifest_url = group.manifest_url;
362       info.creation_time = group.creation_time;
363       info.response_sizes = cache_record.cache_size;
364       info.padding_sizes = cache_record.padding_size;
365       info.last_access_time = group.last_access_time;
366       info.last_update_time = cache_record.update_time;
367       info.token_expires = cache_record.token_expires;
368       info.cache_id = cache_record.cache_id;
369       info.group_id = group.group_id;
370       info.is_complete = true;
371       info.manifest_parser_version = cache_record.manifest_parser_version;
372       info.manifest_scope = cache_record.manifest_scope;
373       infos.push_back(info);
374     }
375 
376     // It's possible that all the origins have a group that is invalid due to
377     // the origin trial.  Ignore these.
378     if (infos.empty())
379       continue;
380 
381     info_collection_->infos_by_origin[origin] = std::move(infos);
382   }
383 }
384 
RunCompleted()385 void AppCacheStorageImpl::GetAllInfoTask::RunCompleted() {
386   DCHECK_EQ(1U, delegates_.size());
387   AppCacheStorage::ForEachDelegate(
388       delegates_, [&](AppCacheStorage::Delegate* delegate) {
389         delegate->OnAllInfo(info_collection_.get());
390       });
391 }
392 
393 // StoreOrLoadTask -------
394 
395 class AppCacheStorageImpl::StoreOrLoadTask : public DatabaseTask {
396  protected:
StoreOrLoadTask(AppCacheStorageImpl * storage)397   explicit StoreOrLoadTask(AppCacheStorageImpl* storage)
398       : DatabaseTask(storage) {}
399   ~StoreOrLoadTask() override = default;
400 
401   bool FindRelatedCacheRecords(int64_t cache_id);
402   void CreateCacheAndGroupFromRecords(
403       scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group);
404 
405   AppCacheDatabase::GroupRecord group_record_;
406   AppCacheDatabase::CacheRecord cache_record_;
407   std::vector<AppCacheDatabase::EntryRecord> entry_records_;
408   std::vector<AppCacheDatabase::NamespaceRecord>
409       intercept_namespace_records_;
410   std::vector<AppCacheDatabase::NamespaceRecord>
411       fallback_namespace_records_;
412   std::vector<AppCacheDatabase::OnlineSafeListRecord> online_safelist_records_;
413 };
414 
FindRelatedCacheRecords(int64_t cache_id)415 bool AppCacheStorageImpl::StoreOrLoadTask::FindRelatedCacheRecords(
416     int64_t cache_id) {
417   return database_->FindEntriesForCache(cache_id, &entry_records_) &&
418          database_->FindNamespacesForCache(cache_id,
419                                            &intercept_namespace_records_,
420                                            &fallback_namespace_records_) &&
421          database_->FindOnlineSafeListForCache(cache_id,
422                                                &online_safelist_records_);
423 }
424 
CreateCacheAndGroupFromRecords(scoped_refptr<AppCache> * cache,scoped_refptr<AppCacheGroup> * group)425 void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords(
426     scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group) {
427   DCHECK(storage_ && cache && group);
428 
429   (*cache) = storage_->working_set_.GetCache(cache_record_.cache_id);
430   if (cache->get()) {
431     (*group) = cache->get()->owning_group();
432     DCHECK(group->get());
433     DCHECK_EQ(group_record_.group_id, group->get()->group_id());
434 
435     // TODO(pwnall): A removed histogram shows that, very rarely,
436     //               cache->get()->GetEntry(group_record_.manifest_url))
437     //               return null here. This was supposed to help investigate
438     //               https://crbug.com/95101
439     storage_->NotifyStorageAccessed(group_record_.origin);
440     return;
441   }
442 
443   *cache = base::MakeRefCounted<AppCache>(storage_, cache_record_.cache_id);
444   cache->get()->InitializeWithDatabaseRecords(
445       cache_record_, entry_records_, intercept_namespace_records_,
446       fallback_namespace_records_, online_safelist_records_);
447   cache->get()->set_complete(true);
448 
449   *group = storage_->working_set_.GetGroup(group_record_.manifest_url);
450   if (group->get()) {
451     DCHECK(group_record_.group_id == group->get()->group_id());
452     group->get()->AddCache(cache->get());
453   } else {
454     *group = base::MakeRefCounted<AppCacheGroup>(
455         storage_, group_record_.manifest_url, group_record_.group_id);
456     group->get()->set_creation_time(group_record_.creation_time);
457     group->get()->set_last_full_update_check_time(
458         group_record_.last_full_update_check_time);
459     group->get()->set_first_evictable_error_time(
460         group_record_.first_evictable_error_time);
461     group->get()->AddCache(cache->get());
462 
463     // TODO(pwnall): A removed histogram shows that, very rarely,
464     //               cache->get()->GetEntry(group_record_.manifest_url))
465     //               return null here. This was supposed to help investigate
466     //               https://crbug.com/95101
467   }
468   DCHECK(group->get()->newest_complete_cache() == cache->get());
469 
470   // We have to update foriegn entries if MarkEntryAsForeignTasks
471   // are in flight.
472   std::vector<GURL> urls =
473       storage_->GetPendingForeignMarkingsForCache(cache->get()->cache_id());
474   for (const auto& url : urls) {
475     // Skip any entries that were marked as foreign but that don't actually
476     // exist. This shouldn't happen other than with misbehaving renderers, but
477     // we've always just ignored these when the cache already exists when
478     // MarkEntryAsForeign is called, so also ignore them here when the cache
479     // still had to be created.
480     // If AppCache wouldn't be in maintenance mode only, we might want to
481     // (async) ReportBadMessage here and in MarkEntryAsForeign, and deal
482     // with any resulting crashes, but for now just keep the existing behavior.
483     if (!cache->get()->GetEntry(url))
484       continue;
485     cache->get()->GetEntry(url)->add_types(AppCacheEntry::FOREIGN);
486   }
487 
488   storage_->NotifyStorageAccessed(group_record_.origin);
489 
490   // TODO(michaeln): Maybe verify that the responses we expect to exist
491   // do actually exist in the disk_cache (and if not then what?)
492 }
493 
494 // CacheLoadTask -------
495 
496 class AppCacheStorageImpl::CacheLoadTask : public StoreOrLoadTask {
497  public:
CacheLoadTask(int64_t cache_id,AppCacheStorageImpl * storage)498   CacheLoadTask(int64_t cache_id, AppCacheStorageImpl* storage)
499       : StoreOrLoadTask(storage), cache_id_(cache_id), success_(false) {}
500 
501   // DatabaseTask:
502   void Run() override;
503   void RunCompleted() override;
504 
505  protected:
506   ~CacheLoadTask() override = default;
507 
508  private:
509   int64_t cache_id_;
510   bool success_;
511 };
512 
Run()513 void AppCacheStorageImpl::CacheLoadTask::Run() {
514   success_ = database_->FindCache(cache_id_, &cache_record_);
515   if (!success_)
516     return;
517   if (!database_->HasValidOriginTrialToken(&cache_record_)) {
518     success_ = false;
519     return;
520   }
521 
522   success_ = database_->FindGroup(cache_record_.group_id, &group_record_) &&
523              FindRelatedCacheRecords(cache_id_);
524 
525   if (success_)
526     database_->LazyUpdateLastAccessTime(group_record_.group_id,
527                                         base::Time::Now());
528 }
529 
RunCompleted()530 void AppCacheStorageImpl::CacheLoadTask::RunCompleted() {
531   storage_->pending_cache_loads_.erase(cache_id_);
532   scoped_refptr<AppCache> cache;
533   scoped_refptr<AppCacheGroup> group;
534   if (success_ && !storage_->is_disabled()) {
535     storage_->LazilyCommitLastAccessTimes();
536     DCHECK(cache_record_.cache_id == cache_id_);
537     CreateCacheAndGroupFromRecords(&cache, &group);
538   }
539   AppCacheStorage::ForEachDelegate(
540       delegates_, [&](AppCacheStorage::Delegate* delegate) {
541         delegate->OnCacheLoaded(cache.get(), cache_id_);
542       });
543 }
544 
545 // GroupLoadTask -------
546 
547 class AppCacheStorageImpl::GroupLoadTask : public StoreOrLoadTask {
548  public:
GroupLoadTask(GURL manifest_url,AppCacheStorageImpl * storage)549   GroupLoadTask(GURL manifest_url, AppCacheStorageImpl* storage)
550       : StoreOrLoadTask(storage), manifest_url_(manifest_url),
551         success_(false) {}
552 
553   // DatabaseTask:
554   void Run() override;
555   void RunCompleted() override;
556 
557  protected:
558   ~GroupLoadTask() override = default;
559 
560  private:
561   GURL manifest_url_;
562   bool success_;
563 };
564 
Run()565 void AppCacheStorageImpl::GroupLoadTask::Run() {
566   success_ =
567       database_->FindGroupForManifestUrl(manifest_url_, &group_record_) &&
568       database_->FindCacheForGroup(group_record_.group_id, &cache_record_);
569 
570   if (!success_)
571     return;
572   if (!database_->HasValidOriginTrialToken(&cache_record_)) {
573     success_ = false;
574     return;
575   }
576 
577   success_ = FindRelatedCacheRecords(cache_record_.cache_id);
578 
579   if (success_) {
580     database_->LazyUpdateLastAccessTime(group_record_.group_id,
581                                         base::Time::Now());
582   }
583 }
584 
RunCompleted()585 void AppCacheStorageImpl::GroupLoadTask::RunCompleted() {
586   storage_->pending_group_loads_.erase(manifest_url_);
587   scoped_refptr<AppCacheGroup> group;
588   scoped_refptr<AppCache> cache;
589   if (!storage_->is_disabled()) {
590     if (success_) {
591       storage_->LazilyCommitLastAccessTimes();
592       DCHECK(group_record_.manifest_url == manifest_url_);
593       CreateCacheAndGroupFromRecords(&cache, &group);
594     } else {
595       group = storage_->working_set_.GetGroup(manifest_url_);
596       if (!group.get()) {
597         group = base::MakeRefCounted<AppCacheGroup>(storage_, manifest_url_,
598                                                     storage_->NewGroupId());
599       }
600     }
601   }
602   AppCacheStorage::ForEachDelegate(
603       delegates_, [&](AppCacheStorage::Delegate* delegate) {
604         delegate->OnGroupLoaded(group.get(), manifest_url_);
605       });
606 }
607 
608 // StoreGroupAndCacheTask -------
609 
610 class AppCacheStorageImpl::StoreGroupAndCacheTask : public StoreOrLoadTask {
611  public:
612   StoreGroupAndCacheTask(AppCacheStorageImpl* storage, AppCacheGroup* group,
613                          AppCache* newest_cache);
614 
615   void GetQuotaThenSchedule();
616   void OnQuotaCallback(blink::mojom::QuotaStatusCode status,
617                        int64_t usage,
618                        int64_t quota);
619 
620   // DatabaseTask:
621   void Run() override;
622   void RunCompleted() override;
623   void CancelCompletion() override;
624 
625  protected:
626   ~StoreGroupAndCacheTask() override = default;
627 
628  private:
629   scoped_refptr<AppCacheGroup> group_;
630   scoped_refptr<AppCache> cache_;
631   bool success_;
632   bool would_exceed_quota_;
633   int64_t space_available_;
634   int64_t new_origin_usage_;
635   std::vector<int64_t> newly_deletable_response_ids_;
636 };
637 
StoreGroupAndCacheTask(AppCacheStorageImpl * storage,AppCacheGroup * group,AppCache * newest_cache)638 AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask(
639     AppCacheStorageImpl* storage,
640     AppCacheGroup* group,
641     AppCache* newest_cache)
642     : StoreOrLoadTask(storage),
643       group_(group),
644       cache_(newest_cache),
645       success_(false),
646       would_exceed_quota_(false),
647       space_available_(-1),
648       new_origin_usage_(-1) {
649   group_record_.group_id = group->group_id();
650   group_record_.manifest_url = group->manifest_url();
651   group_record_.origin = url::Origin::Create(group_record_.manifest_url);
652   group_record_.last_full_update_check_time =
653       group->last_full_update_check_time();
654   group_record_.first_evictable_error_time =
655       group->first_evictable_error_time();
656   newest_cache->ToDatabaseRecords(
657       group, &cache_record_, &entry_records_, &intercept_namespace_records_,
658       &fallback_namespace_records_, &online_safelist_records_);
659 }
660 
GetQuotaThenSchedule()661 void AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule() {
662   if (!storage_->service()->quota_manager_proxy()) {
663     if (storage_->service()->special_storage_policy() &&
664         storage_->service()->special_storage_policy()->IsStorageUnlimited(
665             group_record_.origin.GetURL()))
666       space_available_ = std::numeric_limits<int64_t>::max();
667     Schedule();
668     return;
669   }
670 
671   // We have to ask the quota manager for the value.
672   storage_->pending_quota_queries_.insert(this);
673   storage_->service()->quota_manager_proxy()->GetUsageAndQuota(
674       base::ThreadTaskRunnerHandle::Get().get(), group_record_.origin,
675       blink::mojom::StorageType::kTemporary,
676       base::BindOnce(&StoreGroupAndCacheTask::OnQuotaCallback, this));
677 }
678 
OnQuotaCallback(blink::mojom::QuotaStatusCode status,int64_t usage,int64_t quota)679 void AppCacheStorageImpl::StoreGroupAndCacheTask::OnQuotaCallback(
680     blink::mojom::QuotaStatusCode status,
681     int64_t usage,
682     int64_t quota) {
683   if (storage_) {
684     if (status == blink::mojom::QuotaStatusCode::kOk)
685       space_available_ = std::max(static_cast<int64_t>(0), quota - usage);
686     else
687       space_available_ = 0;
688     storage_->pending_quota_queries_.erase(this);
689     Schedule();
690   }
691 }
692 
Run()693 void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() {
694   DCHECK(!success_);
695   sql::Database* const connection = database_->db_connection();
696   if (!connection)
697     return;
698 
699   sql::Transaction transaction(connection);
700   if (!transaction.Begin())
701     return;
702 
703   int64_t old_origin_usage = database_->GetOriginUsage(group_record_.origin);
704 
705   AppCacheDatabase::GroupRecord existing_group;
706   success_ = database_->FindGroup(group_record_.group_id, &existing_group);
707   if (!success_) {
708     group_record_.creation_time = base::Time::Now();
709     group_record_.last_access_time = base::Time::Now();
710     success_ = database_->InsertGroup(&group_record_);
711   } else {
712     DCHECK(group_record_.group_id == existing_group.group_id);
713     DCHECK(group_record_.manifest_url == existing_group.manifest_url);
714     DCHECK(group_record_.origin == existing_group.origin);
715 
716     database_->UpdateLastAccessTime(group_record_.group_id,
717                                     base::Time::Now());
718 
719     database_->UpdateEvictionTimes(group_record_.group_id,
720                                    group_record_.last_full_update_check_time,
721                                    group_record_.first_evictable_error_time);
722 
723     AppCacheDatabase::CacheRecord cache;
724     if (database_->FindCacheForGroup(group_record_.group_id, &cache)) {
725       // Get the set of response ids in the old cache.
726       std::set<int64_t> existing_response_ids;
727       database_->FindResponseIdsForCacheAsSet(cache.cache_id,
728                                               &existing_response_ids);
729 
730       // Remove those that remain in the new cache.
731       for (const auto& entry : entry_records_)
732         existing_response_ids.erase(entry.response_id);
733 
734       // The rest are deletable.
735       for (const auto& id : existing_response_ids)
736         newly_deletable_response_ids_.push_back(id);
737 
738       success_ =
739           database_->DeleteCache(cache.cache_id) &&
740           database_->DeleteEntriesForCache(cache.cache_id) &&
741           database_->DeleteNamespacesForCache(cache.cache_id) &&
742           database_->DeleteOnlineSafeListForCache(cache.cache_id) &&
743           database_->InsertDeletableResponseIds(newly_deletable_response_ids_);
744       // TODO(michaeln): store group_id too with deletable ids
745     } else {
746       NOTREACHED() << "A existing group without a cache is unexpected";
747     }
748   }
749 
750   success_ = success_ && database_->InsertCache(&cache_record_) &&
751              database_->InsertEntryRecords(entry_records_) &&
752              database_->InsertNamespaceRecords(intercept_namespace_records_) &&
753              database_->InsertNamespaceRecords(fallback_namespace_records_) &&
754              database_->InsertOnlineSafeListRecords(online_safelist_records_);
755 
756   if (!success_)
757     return;
758 
759   new_origin_usage_ = database_->GetOriginUsage(group_record_.origin);
760 
761   // Only check quota when the new usage exceeds the old usage.
762   if (new_origin_usage_ <= old_origin_usage) {
763     success_ = transaction.Commit();
764     return;
765   }
766 
767   // Use a simple hard-coded value when not using quota management.
768   if (space_available_ == -1) {
769     if (new_origin_usage_ > kDefaultQuota) {
770       would_exceed_quota_ = true;
771       success_ = false;
772       return;
773     }
774     success_ = transaction.Commit();
775     return;
776   }
777 
778   // Check limits based on the space availbable given to us via the
779   // quota system.
780   int64_t delta = new_origin_usage_ - old_origin_usage;
781   if (delta > space_available_) {
782     would_exceed_quota_ = true;
783     success_ = false;
784     return;
785   }
786 
787   success_ = transaction.Commit();
788 }
789 
RunCompleted()790 void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() {
791   if (success_) {
792     storage_->UpdateUsageMapAndNotify(
793         url::Origin::Create(group_->manifest_url()), new_origin_usage_);
794     if (cache_.get() != group_->newest_complete_cache()) {
795       cache_->set_complete(true);
796       group_->AddCache(cache_.get());
797     }
798     if (group_->creation_time().is_null())
799       group_->set_creation_time(group_record_.creation_time);
800     group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
801   }
802   AppCacheStorage::ForEachDelegate(
803       delegates_, [&](AppCacheStorage::Delegate* delegate) {
804         delegate->OnGroupAndNewestCacheStored(group_.get(), cache_.get(),
805                                               success_, would_exceed_quota_);
806       });
807   group_ = nullptr;
808   cache_ = nullptr;
809 
810   // TODO(michaeln): if (would_exceed_quota_) what if the current usage
811   // also exceeds the quota? http://crbug.com/83968
812 }
813 
CancelCompletion()814 void AppCacheStorageImpl::StoreGroupAndCacheTask::CancelCompletion() {
815   // Overriden to safely drop our reference to the group and cache
816   // which are not thread safe refcounted.
817   DatabaseTask::CancelCompletion();
818   group_ = nullptr;
819   cache_ = nullptr;
820 }
821 
822 // FindMainResponseTask -------
823 
824 // Helpers for FindMainResponseTask::Run()
825 namespace {
826 class SortByCachePreference {
827  public:
SortByCachePreference(int64_t preferred_id,const std::set<int64_t> & in_use_ids)828   SortByCachePreference(int64_t preferred_id,
829                         const std::set<int64_t>& in_use_ids)
830       : preferred_id_(preferred_id), in_use_ids_(in_use_ids) {}
operator ()(const AppCacheDatabase::EntryRecord & lhs,const AppCacheDatabase::EntryRecord & rhs)831   bool operator()(
832       const AppCacheDatabase::EntryRecord& lhs,
833       const AppCacheDatabase::EntryRecord& rhs) {
834     return compute_value(lhs) > compute_value(rhs);
835   }
836  private:
compute_value(const AppCacheDatabase::EntryRecord & entry)837   int compute_value(const AppCacheDatabase::EntryRecord& entry) {
838     if (entry.cache_id == preferred_id_)
839       return 100;
840     else if (in_use_ids_.find(entry.cache_id) != in_use_ids_.end())
841       return 50;
842     return 0;
843   }
844   int64_t preferred_id_;
845   const std::set<int64_t>& in_use_ids_;
846 };
847 
SortByLength(const AppCacheDatabase::NamespaceRecord & lhs,const AppCacheDatabase::NamespaceRecord & rhs)848 bool SortByLength(
849     const AppCacheDatabase::NamespaceRecord& lhs,
850     const AppCacheDatabase::NamespaceRecord& rhs) {
851   return lhs.namespace_.namespace_url.spec().length() >
852          rhs.namespace_.namespace_url.spec().length();
853 }
854 
855 class NetworkNamespaceHelper {
856  public:
NetworkNamespaceHelper(AppCacheDatabase * database)857   explicit NetworkNamespaceHelper(AppCacheDatabase* database)
858       : database_(database) {
859   }
860 
IsInNetworkNamespace(const GURL & url,int64_t cache_id)861   bool IsInNetworkNamespace(const GURL& url, int64_t cache_id) {
862     std::pair<SafeListMap::iterator, bool> result = namespaces_map_.insert(
863         SafeListMap::value_type(cache_id, std::vector<AppCacheNamespace>()));
864     if (result.second)
865       GetOnlineSafeListForCache(cache_id, &result.first->second);
866     return AppCache::FindNamespace(result.first->second, url) != nullptr;
867   }
868 
869  private:
GetOnlineSafeListForCache(int64_t cache_id,std::vector<AppCacheNamespace> * namespaces)870   void GetOnlineSafeListForCache(int64_t cache_id,
871                                  std::vector<AppCacheNamespace>* namespaces) {
872     DCHECK(namespaces && namespaces->empty());
873     using SafeListVector = std::vector<AppCacheDatabase::OnlineSafeListRecord>;
874     SafeListVector records;
875     if (!database_->FindOnlineSafeListForCache(cache_id, &records))
876       return;
877 
878     for (const auto& record : records) {
879       namespaces->push_back(AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE,
880                                               record.namespace_url, GURL()));
881     }
882   }
883 
884   // Key is cache id
885   using SafeListMap = std::map<int64_t, std::vector<AppCacheNamespace>>;
886   SafeListMap namespaces_map_;
887   AppCacheDatabase* const database_;
888 };
889 
890 }  // namespace
891 
892 class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask {
893  public:
FindMainResponseTask(AppCacheStorageImpl * storage,const GURL & url,const GURL & preferred_manifest_url,const AppCacheWorkingSet::GroupMap * groups_in_use)894   FindMainResponseTask(AppCacheStorageImpl* storage,
895                        const GURL& url,
896                        const GURL& preferred_manifest_url,
897                        const AppCacheWorkingSet::GroupMap* groups_in_use)
898       : DatabaseTask(storage),
899         url_(url),
900         preferred_manifest_url_(preferred_manifest_url),
901         cache_id_(blink::mojom::kAppCacheNoCacheId),
902         group_id_(0) {
903     if (groups_in_use) {
904       for (const auto& pair : *groups_in_use) {
905         AppCacheGroup* group = pair.second;
906         AppCache* cache = group->newest_complete_cache();
907         if (group->is_obsolete() || !cache)
908           continue;
909         cache_ids_in_use_.insert(cache->cache_id());
910       }
911     }
912   }
913 
914   // DatabaseTask:
915   void Run() override;
916   void RunCompleted() override;
917 
918  protected:
919   ~FindMainResponseTask() override = default;
920 
921  private:
922   using NamespaceRecordPtrVector =
923       std::vector<AppCacheDatabase::NamespaceRecord*>;
924 
925   bool FindExactMatch(int64_t preferred_id);
926   bool FindNamespaceMatch(int64_t preferred_id);
927   bool FindNamespaceHelper(int64_t preferred_cache_id,
928                            AppCacheDatabase::NamespaceRecordVector* namespaces,
929                            NetworkNamespaceHelper* network_namespace_helper);
930   bool FindFirstValidNamespace(const NamespaceRecordPtrVector& namespaces);
931 
932   GURL url_;
933   GURL preferred_manifest_url_;
934   std::set<int64_t> cache_ids_in_use_;
935   AppCacheEntry entry_;
936   AppCacheEntry fallback_entry_;
937   GURL namespace_entry_url_;
938   int64_t cache_id_;
939   int64_t group_id_;
940   GURL manifest_url_;
941 };
942 
Run()943 void AppCacheStorageImpl::FindMainResponseTask::Run() {
944   // NOTE: The heuristics around choosing amoungst multiple candidates
945   // is underspecified, and just plain not fully understood. This needs
946   // to be refined.
947 
948   // The 'preferred_manifest_url' is the url of the manifest associated
949   // with the page that opened or embedded the page being loaded now.
950   // We have a strong preference to use resources from that cache.
951   // We also have a lesser bias to use resources from caches that are currently
952   // being used by other unrelated pages.
953   // TODO(michaeln): come up with a 'preferred_manifest_url' in more cases
954   // - when navigating a frame whose current contents are from an appcache
955   // - when clicking an href in a frame that is appcached
956   int64_t preferred_cache_id = blink::mojom::kAppCacheNoCacheId;
957   if (!preferred_manifest_url_.is_empty()) {
958     AppCacheDatabase::GroupRecord preferred_group;
959     AppCacheDatabase::CacheRecord preferred_cache;
960     if (database_->FindGroupForManifestUrl(preferred_manifest_url_,
961                                            &preferred_group) &&
962         database_->FindCacheForGroup(preferred_group.group_id,
963                                      &preferred_cache) &&
964         database_->HasValidOriginTrialToken(&preferred_cache)) {
965       preferred_cache_id = preferred_cache.cache_id;
966     }
967   }
968 
969   if (FindExactMatch(preferred_cache_id) ||
970       FindNamespaceMatch(preferred_cache_id)) {
971     // We found something.
972     DCHECK(cache_id_ != blink::mojom::kAppCacheNoCacheId &&
973            !manifest_url_.is_empty() && group_id_ != 0);
974     return;
975   }
976 
977   // We didn't find anything.
978   DCHECK(cache_id_ == blink::mojom::kAppCacheNoCacheId &&
979          manifest_url_.is_empty() && group_id_ == 0);
980 }
981 
FindExactMatch(int64_t preferred_cache_id)982 bool AppCacheStorageImpl::FindMainResponseTask::FindExactMatch(
983     int64_t preferred_cache_id) {
984   std::vector<AppCacheDatabase::EntryRecord> entries;
985   if (database_->FindEntriesForUrl(url_, &entries) && !entries.empty()) {
986     // Sort them in order of preference, from the preferred_cache first,
987     // followed by hits from caches that are 'in use', then the rest.
988     std::sort(entries.begin(), entries.end(),
989               SortByCachePreference(preferred_cache_id, cache_ids_in_use_));
990 
991     // Take the first with a valid, non-foreign entry.
992     for (const auto& entry : entries) {
993       AppCacheDatabase::GroupRecord group_record;
994       AppCacheDatabase::CacheRecord cache_record;
995       if ((entry.flags & AppCacheEntry::FOREIGN) ||
996           !database_->FindGroupForCache(entry.cache_id, &group_record) ||
997           !database_->FindCache(entry.cache_id, &cache_record) ||
998           !database_->HasValidOriginTrialToken(&cache_record)) {
999         continue;
1000       }
1001 
1002       manifest_url_ = group_record.manifest_url;
1003       group_id_ = group_record.group_id;
1004       entry_ = AppCacheEntry(entry.flags, entry.response_id);
1005       cache_id_ = entry.cache_id;
1006       return true;  // We found an exact match.
1007     }
1008   }
1009   return false;
1010 }
1011 
FindNamespaceMatch(int64_t preferred_cache_id)1012 bool AppCacheStorageImpl::FindMainResponseTask::FindNamespaceMatch(
1013     int64_t preferred_cache_id) {
1014   AppCacheDatabase::NamespaceRecordVector all_intercepts;
1015   AppCacheDatabase::NamespaceRecordVector all_fallbacks;
1016   if (!database_->FindNamespacesForOrigin(url::Origin::Create(url_),
1017                                           &all_intercepts, &all_fallbacks) ||
1018       (all_intercepts.empty() && all_fallbacks.empty())) {
1019     return false;
1020   }
1021 
1022   NetworkNamespaceHelper network_namespace_helper(database_);
1023   if (FindNamespaceHelper(preferred_cache_id,
1024                           &all_intercepts,
1025                           &network_namespace_helper) ||
1026       FindNamespaceHelper(preferred_cache_id,
1027                           &all_fallbacks,
1028                           &network_namespace_helper)) {
1029     return true;
1030   }
1031   return false;
1032 }
1033 
FindNamespaceHelper(int64_t preferred_cache_id,AppCacheDatabase::NamespaceRecordVector * namespaces,NetworkNamespaceHelper * network_namespace_helper)1034 bool AppCacheStorageImpl::FindMainResponseTask::FindNamespaceHelper(
1035     int64_t preferred_cache_id,
1036     AppCacheDatabase::NamespaceRecordVector* namespaces,
1037     NetworkNamespaceHelper* network_namespace_helper) {
1038   // Sort them by length, longer matches within the same cache/bucket take
1039   // precedence.
1040   std::sort(namespaces->begin(), namespaces->end(), SortByLength);
1041 
1042   NamespaceRecordPtrVector preferred_namespaces;
1043   NamespaceRecordPtrVector inuse_namespaces;
1044   NamespaceRecordPtrVector other_namespaces;
1045   for (auto& namespace_record : *namespaces) {
1046     // Skip those that aren't a match.
1047     if (!namespace_record.namespace_.IsMatch(url_))
1048       continue;
1049 
1050     // Skip namespaces where the requested url falls into a network
1051     // namespace of its containing appcache.
1052     if (network_namespace_helper->IsInNetworkNamespace(
1053             url_, namespace_record.cache_id))
1054       continue;
1055 
1056     // Bin them into one of our three buckets.
1057     if (namespace_record.cache_id == preferred_cache_id)
1058       preferred_namespaces.push_back(&namespace_record);
1059     else if (cache_ids_in_use_.find(namespace_record.cache_id) !=
1060              cache_ids_in_use_.end())
1061       inuse_namespaces.push_back(&namespace_record);
1062     else
1063       other_namespaces.push_back(&namespace_record);
1064   }
1065 
1066   if (FindFirstValidNamespace(preferred_namespaces) ||
1067       FindFirstValidNamespace(inuse_namespaces) ||
1068       FindFirstValidNamespace(other_namespaces))
1069     return true;  // We found one.
1070 
1071   // We didn't find anything.
1072   return false;
1073 }
1074 
1075 bool AppCacheStorageImpl::
FindFirstValidNamespace(const NamespaceRecordPtrVector & namespaces)1076 FindMainResponseTask::FindFirstValidNamespace(
1077     const NamespaceRecordPtrVector& namespaces) {
1078   // Take the first with a valid, non-foreign entry.
1079   for (auto* namespace_record : namespaces) {
1080     AppCacheDatabase::EntryRecord entry_record;
1081     if (database_->FindEntry(namespace_record->cache_id,
1082                              namespace_record->namespace_.target_url,
1083                              &entry_record)) {
1084       AppCacheDatabase::GroupRecord group_record;
1085       AppCacheDatabase::CacheRecord cache_record;
1086       if (entry_record.flags & AppCacheEntry::FOREIGN)
1087         continue;
1088       if (!database_->FindGroupForCache(entry_record.cache_id, &group_record))
1089         continue;
1090       if (!database_->FindCache(entry_record.cache_id, &cache_record))
1091         continue;
1092       if (!database_->HasValidOriginTrialToken(&cache_record)) {
1093         continue;
1094       }
1095       manifest_url_ = group_record.manifest_url;
1096       group_id_ = group_record.group_id;
1097       cache_id_ = namespace_record->cache_id;
1098       namespace_entry_url_ = namespace_record->namespace_.target_url;
1099       if (namespace_record->namespace_.type == APPCACHE_FALLBACK_NAMESPACE)
1100         fallback_entry_ = AppCacheEntry(entry_record.flags,
1101                                         entry_record.response_id);
1102       else
1103         entry_ = AppCacheEntry(entry_record.flags, entry_record.response_id);
1104       return true;  // We found one.
1105     }
1106   }
1107   return false;  // We didn't find a match.
1108 }
1109 
RunCompleted()1110 void AppCacheStorageImpl::FindMainResponseTask::RunCompleted() {
1111   storage_->CallOnMainResponseFound(
1112       &delegates_, url_, entry_, namespace_entry_url_, fallback_entry_,
1113       cache_id_, group_id_, manifest_url_);
1114 }
1115 
1116 // MarkEntryAsForeignTask -------
1117 
1118 class AppCacheStorageImpl::MarkEntryAsForeignTask : public DatabaseTask {
1119  public:
MarkEntryAsForeignTask(AppCacheStorageImpl * storage,const GURL & url,int64_t cache_id)1120   MarkEntryAsForeignTask(AppCacheStorageImpl* storage,
1121                          const GURL& url,
1122                          int64_t cache_id)
1123       : DatabaseTask(storage), cache_id_(cache_id), entry_url_(url) {}
1124 
1125   // DatabaseTask:
1126   void Run() override;
1127   void RunCompleted() override;
1128 
1129  protected:
1130   ~MarkEntryAsForeignTask() override = default;
1131 
1132  private:
1133   int64_t cache_id_;
1134   GURL entry_url_;
1135 };
1136 
Run()1137 void AppCacheStorageImpl::MarkEntryAsForeignTask::Run() {
1138   database_->AddEntryFlags(entry_url_, cache_id_, AppCacheEntry::FOREIGN);
1139 }
1140 
RunCompleted()1141 void AppCacheStorageImpl::MarkEntryAsForeignTask::RunCompleted() {
1142   DCHECK(storage_->pending_foreign_markings_.front().first == entry_url_ &&
1143          storage_->pending_foreign_markings_.front().second == cache_id_);
1144   storage_->pending_foreign_markings_.pop_front();
1145 }
1146 
1147 // MakeGroupObsoleteTask -------
1148 
1149 class AppCacheStorageImpl::MakeGroupObsoleteTask : public DatabaseTask {
1150  public:
1151   MakeGroupObsoleteTask(AppCacheStorageImpl* storage,
1152                         AppCacheGroup* group,
1153                         int response_code);
1154 
1155   // DatabaseTask:
1156   void Run() override;
1157   void RunCompleted() override;
1158   void CancelCompletion() override;
1159 
1160  protected:
1161   ~MakeGroupObsoleteTask() override = default;
1162 
1163  private:
1164   scoped_refptr<AppCacheGroup> group_;
1165   int64_t group_id_;
1166   url::Origin origin_;
1167   bool success_;
1168   int response_code_;
1169   int64_t new_origin_usage_;
1170   std::vector<int64_t> newly_deletable_response_ids_;
1171 };
1172 
MakeGroupObsoleteTask(AppCacheStorageImpl * storage,AppCacheGroup * group,int response_code)1173 AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask(
1174     AppCacheStorageImpl* storage,
1175     AppCacheGroup* group,
1176     int response_code)
1177     : DatabaseTask(storage),
1178       group_(group),
1179       group_id_(group->group_id()),
1180       origin_(url::Origin::Create(group->manifest_url())),
1181       success_(false),
1182       response_code_(response_code),
1183       new_origin_usage_(-1) {}
1184 
Run()1185 void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() {
1186   DCHECK(!success_);
1187   sql::Database* connection = database_->db_connection();
1188   if (!connection)
1189     return;
1190 
1191   sql::Transaction transaction(connection);
1192   if (!transaction.Begin())
1193     return;
1194 
1195   AppCacheDatabase::GroupRecord group_record;
1196   if (!database_->FindGroup(group_id_, &group_record)) {
1197     // This group doesn't exists in the database, nothing todo here.
1198     new_origin_usage_ = database_->GetOriginUsage(origin_);
1199     success_ = true;
1200     return;
1201   }
1202 
1203   DCHECK_EQ(group_record.origin, origin_);
1204   success_ = DeleteGroupAndRelatedRecords(database_,
1205                                           group_id_,
1206                                           &newly_deletable_response_ids_);
1207 
1208   new_origin_usage_ = database_->GetOriginUsage(origin_);
1209   success_ = success_ && transaction.Commit();
1210 }
1211 
RunCompleted()1212 void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() {
1213   if (success_) {
1214     group_->set_obsolete(true);
1215     if (!storage_->is_disabled()) {
1216       storage_->UpdateUsageMapAndNotify(origin_, new_origin_usage_);
1217       group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
1218 
1219       // Also remove from the working set, caches for an 'obsolete' group
1220       // may linger in use, but the group itself cannot be looked up by
1221       // 'manifest_url' in the working set any longer.
1222       storage_->working_set()->RemoveGroup(group_.get());
1223     }
1224   }
1225 
1226   AppCacheStorage::ForEachDelegate(
1227       delegates_, [&](AppCacheStorage::Delegate* delegate) {
1228         delegate->OnGroupMadeObsolete(group_.get(), success_, response_code_);
1229       });
1230   group_ = nullptr;
1231 }
1232 
CancelCompletion()1233 void AppCacheStorageImpl::MakeGroupObsoleteTask::CancelCompletion() {
1234   // Overriden to safely drop our reference to the group
1235   // which is not thread safe refcounted.
1236   DatabaseTask::CancelCompletion();
1237   group_ = nullptr;
1238 }
1239 
1240 // GetDeletableResponseIdsTask -------
1241 
1242 class AppCacheStorageImpl::GetDeletableResponseIdsTask : public DatabaseTask {
1243  public:
GetDeletableResponseIdsTask(AppCacheStorageImpl * storage,int64_t max_rowid)1244   GetDeletableResponseIdsTask(AppCacheStorageImpl* storage, int64_t max_rowid)
1245       : DatabaseTask(storage), max_rowid_(max_rowid) {}
1246 
1247   // DatabaseTask:
1248   void Run() override;
1249   void RunCompleted() override;
1250 
1251  protected:
1252   ~GetDeletableResponseIdsTask() override = default;
1253 
1254  private:
1255   int64_t max_rowid_;
1256   std::vector<int64_t> response_ids_;
1257 };
1258 
Run()1259 void AppCacheStorageImpl::GetDeletableResponseIdsTask::Run() {
1260   const int kSqlLimit = 1000;
1261   database_->GetDeletableResponseIds(&response_ids_, max_rowid_, kSqlLimit);
1262   // TODO(michaeln): retrieve group_ids too
1263 }
1264 
RunCompleted()1265 void AppCacheStorageImpl::GetDeletableResponseIdsTask::RunCompleted() {
1266   if (!response_ids_.empty())
1267     storage_->StartDeletingResponses(response_ids_);
1268 }
1269 
1270 // InsertDeletableResponseIdsTask -------
1271 
1272 class AppCacheStorageImpl::InsertDeletableResponseIdsTask
1273     : public DatabaseTask {
1274  public:
InsertDeletableResponseIdsTask(AppCacheStorageImpl * storage)1275   explicit InsertDeletableResponseIdsTask(AppCacheStorageImpl* storage)
1276       : DatabaseTask(storage) {}
1277 
1278   // DatabaseTask:
1279   void Run() override;
1280 
1281   std::vector<int64_t> response_ids_;
1282 
1283  protected:
1284   ~InsertDeletableResponseIdsTask() override = default;
1285 };
1286 
Run()1287 void AppCacheStorageImpl::InsertDeletableResponseIdsTask::Run() {
1288   database_->InsertDeletableResponseIds(response_ids_);
1289   // TODO(michaeln): store group_ids too
1290 }
1291 
1292 // DeleteDeletableResponseIdsTask -------
1293 
1294 class AppCacheStorageImpl::DeleteDeletableResponseIdsTask
1295     : public DatabaseTask {
1296  public:
DeleteDeletableResponseIdsTask(AppCacheStorageImpl * storage)1297   explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl* storage)
1298       : DatabaseTask(storage) {}
1299 
1300   // DatabaseTask:
1301   void Run() override;
1302 
1303   std::vector<int64_t> response_ids_;
1304 
1305  protected:
1306   ~DeleteDeletableResponseIdsTask() override = default;
1307 };
1308 
Run()1309 void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run() {
1310   database_->DeleteDeletableResponseIds(response_ids_);
1311 }
1312 
1313 // LazyUpdateLastAccessTimeTask -------
1314 
1315 class AppCacheStorageImpl::LazyUpdateLastAccessTimeTask
1316     : public DatabaseTask {
1317  public:
LazyUpdateLastAccessTimeTask(AppCacheStorageImpl * storage,AppCacheGroup * group,base::Time time)1318   LazyUpdateLastAccessTimeTask(
1319       AppCacheStorageImpl* storage, AppCacheGroup* group, base::Time time)
1320       : DatabaseTask(storage), group_id_(group->group_id()),
1321         last_access_time_(time) {
1322     storage->NotifyStorageAccessed(url::Origin::Create(group->manifest_url()));
1323   }
1324 
1325   // DatabaseTask:
1326   void Run() override;
1327   void RunCompleted() override;
1328 
1329  protected:
1330   ~LazyUpdateLastAccessTimeTask() override = default;
1331 
1332  private:
1333   int64_t group_id_;
1334   base::Time last_access_time_;
1335 };
1336 
Run()1337 void AppCacheStorageImpl::LazyUpdateLastAccessTimeTask::Run() {
1338   database_->LazyUpdateLastAccessTime(group_id_, last_access_time_);
1339 }
1340 
RunCompleted()1341 void AppCacheStorageImpl::LazyUpdateLastAccessTimeTask::RunCompleted() {
1342   storage_->LazilyCommitLastAccessTimes();
1343 }
1344 
1345 // CommitLastAccessTimesTask -------
1346 
1347 class AppCacheStorageImpl::CommitLastAccessTimesTask
1348     : public DatabaseTask {
1349  public:
CommitLastAccessTimesTask(AppCacheStorageImpl * storage)1350   explicit CommitLastAccessTimesTask(AppCacheStorageImpl* storage)
1351       : DatabaseTask(storage) {}
1352 
1353   // DatabaseTask:
Run()1354   void Run() override {
1355     database_->CommitLazyLastAccessTimes();
1356   }
1357 
1358  protected:
1359   ~CommitLastAccessTimesTask() override = default;
1360 };
1361 
1362 // UpdateEvictionTimes -------
1363 
1364 class AppCacheStorageImpl::UpdateEvictionTimesTask
1365     : public DatabaseTask {
1366  public:
UpdateEvictionTimesTask(AppCacheStorageImpl * storage,AppCacheGroup * group)1367   UpdateEvictionTimesTask(AppCacheStorageImpl* storage, AppCacheGroup* group)
1368       : DatabaseTask(storage),
1369         group_id_(group->group_id()),
1370         last_full_update_check_time_(group->last_full_update_check_time()),
1371         first_evictable_error_time_(group->first_evictable_error_time()) {}
1372 
1373   // DatabaseTask:
1374   void Run() override;
1375 
1376  protected:
1377   ~UpdateEvictionTimesTask() override = default;
1378 
1379  private:
1380   int64_t group_id_;
1381   base::Time last_full_update_check_time_;
1382   base::Time first_evictable_error_time_;
1383 };
1384 
Run()1385 void AppCacheStorageImpl::UpdateEvictionTimesTask::Run() {
1386   database_->UpdateEvictionTimes(group_id_, last_full_update_check_time_,
1387                                  first_evictable_error_time_);
1388 }
1389 
1390 // AppCacheStorageImpl ---------------------------------------------------
1391 
AppCacheStorageImpl(AppCacheServiceImpl * service)1392 AppCacheStorageImpl::AppCacheStorageImpl(AppCacheServiceImpl* service)
1393     : AppCacheStorage(service) {}
1394 
~AppCacheStorageImpl()1395 AppCacheStorageImpl::~AppCacheStorageImpl() {
1396   for (StoreGroupAndCacheTask* task : pending_quota_queries_)
1397     task->CancelCompletion();
1398   for (DatabaseTask* task : scheduled_database_tasks_)
1399     task->CancelCompletion();
1400 
1401   if (database_) {
1402     db_task_runner_->PostTask(
1403         FROM_HERE,
1404         base::BindOnce(&ClearSessionOnlyOrigins, std::move(database_),
1405                        base::WrapRefCounted(service_->special_storage_policy()),
1406                        service()->force_keep_session_state()));
1407   }
1408 }
1409 
Initialize(const base::FilePath & cache_directory,const scoped_refptr<base::SequencedTaskRunner> & db_task_runner)1410 void AppCacheStorageImpl::Initialize(
1411     const base::FilePath& cache_directory,
1412     const scoped_refptr<base::SequencedTaskRunner>& db_task_runner) {
1413   cache_directory_ = cache_directory;
1414   is_incognito_ = cache_directory_.empty();
1415 
1416   base::FilePath db_file_path;
1417   if (!is_incognito_)
1418     db_file_path = cache_directory_.Append(kAppCacheDatabaseName);
1419   database_ = std::make_unique<AppCacheDatabase>(db_file_path);
1420   DCHECK(service_);
1421   DCHECK(service_->appcache_policy());
1422   database_->SetOriginTrialRequired(
1423       service_->appcache_policy()->IsOriginTrialRequiredForAppCache());
1424 
1425   db_task_runner_ = db_task_runner;
1426 
1427   auto task = base::MakeRefCounted<InitTask>(this);
1428   task->Schedule();
1429 }
1430 
Disable()1431 void AppCacheStorageImpl::Disable() {
1432   if (is_disabled_)
1433     return;
1434   VLOG(1) << "Disabling appcache storage.";
1435   is_disabled_ = true;
1436   ClearUsageMapAndNotify();
1437   working_set()->Disable();
1438   if (disk_cache_)
1439     disk_cache_->Disable();
1440   auto task = base::MakeRefCounted<DisableDatabaseTask>(this);
1441   task->Schedule();
1442 }
1443 
GetAllInfo(Delegate * delegate)1444 void AppCacheStorageImpl::GetAllInfo(Delegate* delegate) {
1445   DCHECK(delegate);
1446   auto task = base::MakeRefCounted<GetAllInfoTask>(this);
1447   task->AddDelegate(GetOrCreateDelegateReference(delegate));
1448   task->Schedule();
1449 }
1450 
LoadCache(int64_t id,Delegate * delegate)1451 void AppCacheStorageImpl::LoadCache(int64_t id, Delegate* delegate) {
1452   DCHECK(delegate);
1453   if (is_disabled_) {
1454     delegate->OnCacheLoaded(nullptr, id);
1455     return;
1456   }
1457 
1458   AppCache* cache = working_set_.GetCache(id);
1459   if (cache) {
1460     delegate->OnCacheLoaded(cache, id);
1461     if (cache->owning_group()) {
1462       auto update_task = base::MakeRefCounted<LazyUpdateLastAccessTimeTask>(
1463           this, cache->owning_group(), base::Time::Now());
1464       update_task->Schedule();
1465     }
1466     return;
1467   }
1468   scoped_refptr<CacheLoadTask> task(GetPendingCacheLoadTask(id));
1469   if (task.get()) {
1470     task->AddDelegate(GetOrCreateDelegateReference(delegate));
1471     return;
1472   }
1473   task = base::MakeRefCounted<CacheLoadTask>(id, this);
1474   task->AddDelegate(GetOrCreateDelegateReference(delegate));
1475   task->Schedule();
1476   pending_cache_loads_[id] = task.get();
1477 }
1478 
LoadOrCreateGroup(const GURL & manifest_url,Delegate * delegate)1479 void AppCacheStorageImpl::LoadOrCreateGroup(
1480     const GURL& manifest_url, Delegate* delegate) {
1481   DCHECK(delegate);
1482   if (is_disabled_) {
1483     delegate->OnGroupLoaded(nullptr, manifest_url);
1484     return;
1485   }
1486 
1487   AppCacheGroup* group = working_set_.GetGroup(manifest_url);
1488   if (group) {
1489     delegate->OnGroupLoaded(group, manifest_url);
1490     auto update_task = base::MakeRefCounted<LazyUpdateLastAccessTimeTask>(
1491         this, group, base::Time::Now());
1492     update_task->Schedule();
1493     return;
1494   }
1495 
1496   scoped_refptr<GroupLoadTask> task(GetPendingGroupLoadTask(manifest_url));
1497   if (task.get()) {
1498     task->AddDelegate(GetOrCreateDelegateReference(delegate));
1499     return;
1500   }
1501 
1502   if (usage_map_.find(url::Origin::Create(manifest_url)) == usage_map_.end()) {
1503     // No need to query the database, return a new group immediately.
1504     auto new_group =
1505         base::MakeRefCounted<AppCacheGroup>(this, manifest_url, NewGroupId());
1506     delegate->OnGroupLoaded(new_group.get(), manifest_url);
1507     return;
1508   }
1509 
1510   task = base::MakeRefCounted<GroupLoadTask>(manifest_url, this);
1511   task->AddDelegate(GetOrCreateDelegateReference(delegate));
1512   task->Schedule();
1513   pending_group_loads_[manifest_url] = task.get();
1514 }
1515 
StoreGroupAndNewestCache(AppCacheGroup * group,AppCache * newest_cache,Delegate * delegate)1516 void AppCacheStorageImpl::StoreGroupAndNewestCache(
1517     AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) {
1518   // TODO(michaeln): distinguish between a simple update of an existing
1519   // cache that just adds new master entry(s), and the insertion of a
1520   // whole new cache. The StoreGroupAndCacheTask as written will handle
1521   // the simple update case in a very heavy weight way (delete all and
1522   // the reinsert all over again).
1523   DCHECK(group && delegate && newest_cache);
1524   auto task =
1525       base::MakeRefCounted<StoreGroupAndCacheTask>(this, group, newest_cache);
1526   task->AddDelegate(GetOrCreateDelegateReference(delegate));
1527   task->GetQuotaThenSchedule();
1528 }
1529 
FindResponseForMainRequest(const GURL & url,const GURL & preferred_manifest_url,Delegate * delegate)1530 void AppCacheStorageImpl::FindResponseForMainRequest(
1531     const GURL& url, const GURL& preferred_manifest_url,
1532     Delegate* delegate) {
1533   DCHECK(delegate);
1534 
1535   const GURL* url_ptr = &url;
1536   GURL url_no_ref;
1537   if (url.has_ref()) {
1538     GURL::Replacements replacements;
1539     replacements.ClearRef();
1540     url_no_ref = url.ReplaceComponents(replacements);
1541     url_ptr = &url_no_ref;
1542   }
1543 
1544   const url::Origin origin(url::Origin::Create(url));
1545 
1546   // First look in our working set for a direct hit without having to query
1547   // the database.
1548   const AppCacheWorkingSet::GroupMap* groups_in_use =
1549       working_set()->GetGroupsInOrigin(origin);
1550   if (groups_in_use) {
1551     if (!preferred_manifest_url.is_empty()) {
1552       auto found = groups_in_use->find(preferred_manifest_url);
1553       if (found != groups_in_use->end() &&
1554           FindResponseForMainRequestInGroup(
1555               found->second, *url_ptr, delegate)) {
1556           return;
1557       }
1558     } else {
1559       for (const auto& pair : *groups_in_use) {
1560         if (FindResponseForMainRequestInGroup(pair.second, *url_ptr,
1561                                               delegate)) {
1562           return;
1563         }
1564       }
1565     }
1566   }
1567 
1568   if (IsInitTaskComplete() &&  usage_map_.find(origin) == usage_map_.end()) {
1569     // No need to query the database, return async'ly but without going thru
1570     // the DB thread.
1571     scoped_refptr<AppCacheGroup> no_group;
1572     scoped_refptr<AppCache> no_cache;
1573     ScheduleSimpleTask(base::BindOnce(
1574         &AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
1575         weak_factory_.GetWeakPtr(), url, AppCacheEntry(), no_group, no_cache,
1576         base::WrapRefCounted(GetOrCreateDelegateReference(delegate))));
1577     return;
1578   }
1579 
1580   // We have to query the database, schedule a database task to do so.
1581   auto task = base::MakeRefCounted<FindMainResponseTask>(
1582       this, *url_ptr, preferred_manifest_url, groups_in_use);
1583   task->AddDelegate(GetOrCreateDelegateReference(delegate));
1584   task->Schedule();
1585 }
1586 
FindResponseForMainRequestInGroup(AppCacheGroup * group,const GURL & url,Delegate * delegate)1587 bool AppCacheStorageImpl::FindResponseForMainRequestInGroup(
1588     AppCacheGroup* group,  const GURL& url, Delegate* delegate) {
1589   AppCache* cache = group->newest_complete_cache();
1590   if (group->is_obsolete() || !cache)
1591     return false;
1592 
1593   AppCacheEntry* entry = cache->GetEntry(url);
1594   if (!entry || entry->IsForeign())
1595     return false;
1596 
1597   ScheduleSimpleTask(base::BindOnce(
1598       &AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
1599       weak_factory_.GetWeakPtr(), url, *entry, base::WrapRefCounted(group),
1600       base::WrapRefCounted(cache),
1601       base::WrapRefCounted(GetOrCreateDelegateReference(delegate))));
1602   return true;
1603 }
1604 
DeliverShortCircuitedFindMainResponse(const GURL & url,const AppCacheEntry & found_entry,scoped_refptr<AppCacheGroup> group,scoped_refptr<AppCache> cache,scoped_refptr<DelegateReference> delegate_ref)1605 void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse(
1606     const GURL& url,
1607     const AppCacheEntry& found_entry,
1608     scoped_refptr<AppCacheGroup> group,
1609     scoped_refptr<AppCache> cache,
1610     scoped_refptr<DelegateReference> delegate_ref) {
1611   if (delegate_ref->delegate) {
1612     std::vector<scoped_refptr<DelegateReference>> delegates(1, delegate_ref);
1613     CallOnMainResponseFound(
1614         &delegates, url, found_entry, GURL(), AppCacheEntry(),
1615         cache.get() ? cache->cache_id() : blink::mojom::kAppCacheNoCacheId,
1616         group.get() ? group->group_id() : blink::mojom::kAppCacheNoCacheId,
1617         group.get() ? group->manifest_url() : GURL());
1618   }
1619 }
1620 
CallOnMainResponseFound(std::vector<scoped_refptr<DelegateReference>> * delegates,const GURL & url,const AppCacheEntry & entry,const GURL & namespace_entry_url,const AppCacheEntry & fallback_entry,int64_t cache_id,int64_t group_id,const GURL & manifest_url)1621 void AppCacheStorageImpl::CallOnMainResponseFound(
1622     std::vector<scoped_refptr<DelegateReference>>* delegates,
1623     const GURL& url,
1624     const AppCacheEntry& entry,
1625     const GURL& namespace_entry_url,
1626     const AppCacheEntry& fallback_entry,
1627     int64_t cache_id,
1628     int64_t group_id,
1629     const GURL& manifest_url) {
1630   AppCacheStorage::ForEachDelegate(
1631       *delegates, [&](AppCacheStorage::Delegate* delegate) {
1632         delegate->OnMainResponseFound(url, entry, namespace_entry_url,
1633                                       fallback_entry, cache_id, group_id,
1634                                       manifest_url);
1635       });
1636 }
1637 
FindResponseForSubRequest(AppCache * cache,const GURL & url,AppCacheEntry * found_entry,AppCacheEntry * found_fallback_entry,bool * found_network_namespace)1638 void AppCacheStorageImpl::FindResponseForSubRequest(
1639     AppCache* cache, const GURL& url,
1640     AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry,
1641     bool* found_network_namespace) {
1642   DCHECK(cache && cache->is_complete());
1643 
1644   // When a group is forcibly deleted, all subresource loads for pages
1645   // using caches in the group will result in a synthesized network errors.
1646   // Forcible deletion is not a function that is covered by the HTML5 spec.
1647   if (cache->owning_group()->is_being_deleted()) {
1648     *found_entry = AppCacheEntry();
1649     *found_fallback_entry = AppCacheEntry();
1650     *found_network_namespace = false;
1651     return;
1652   }
1653 
1654   GURL fallback_namespace_not_used;
1655   GURL intercept_namespace_not_used;
1656   cache->FindResponseForRequest(
1657       url, found_entry, &intercept_namespace_not_used,
1658       found_fallback_entry, &fallback_namespace_not_used,
1659       found_network_namespace);
1660 }
1661 
MarkEntryAsForeign(const GURL & entry_url,int64_t cache_id)1662 void AppCacheStorageImpl::MarkEntryAsForeign(const GURL& entry_url,
1663                                              int64_t cache_id) {
1664   AppCache* cache = working_set_.GetCache(cache_id);
1665   if (cache) {
1666     AppCacheEntry* entry = cache->GetEntry(entry_url);
1667     if (entry)
1668       entry->add_types(AppCacheEntry::FOREIGN);
1669   }
1670   auto task =
1671       base::MakeRefCounted<MarkEntryAsForeignTask>(this, entry_url, cache_id);
1672   task->Schedule();
1673   pending_foreign_markings_.push_back(std::make_pair(entry_url, cache_id));
1674 }
1675 
MakeGroupObsolete(AppCacheGroup * group,Delegate * delegate,int response_code)1676 void AppCacheStorageImpl::MakeGroupObsolete(AppCacheGroup* group,
1677                                             Delegate* delegate,
1678                                             int response_code) {
1679   DCHECK(group && delegate);
1680   auto task =
1681       base::MakeRefCounted<MakeGroupObsoleteTask>(this, group, response_code);
1682   task->AddDelegate(GetOrCreateDelegateReference(delegate));
1683   task->Schedule();
1684 }
1685 
StoreEvictionTimes(AppCacheGroup * group)1686 void AppCacheStorageImpl::StoreEvictionTimes(AppCacheGroup* group) {
1687   auto task = base::MakeRefCounted<UpdateEvictionTimesTask>(this, group);
1688   task->Schedule();
1689 }
1690 
1691 std::unique_ptr<AppCacheResponseReader>
CreateResponseReader(const GURL & manifest_url,int64_t response_id)1692 AppCacheStorageImpl::CreateResponseReader(const GURL& manifest_url,
1693                                           int64_t response_id) {
1694   return std::make_unique<AppCacheResponseReader>(
1695       response_id, is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
1696 }
1697 
1698 std::unique_ptr<AppCacheResponseWriter>
CreateResponseWriter(const GURL & manifest_url)1699 AppCacheStorageImpl::CreateResponseWriter(const GURL& manifest_url) {
1700   return std::make_unique<AppCacheResponseWriter>(
1701       NewResponseId(), is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
1702 }
1703 
1704 std::unique_ptr<AppCacheResponseMetadataWriter>
CreateResponseMetadataWriter(int64_t response_id)1705 AppCacheStorageImpl::CreateResponseMetadataWriter(int64_t response_id) {
1706   return std::make_unique<AppCacheResponseMetadataWriter>(
1707       response_id, is_disabled_ ? nullptr : disk_cache()->GetWeakPtr());
1708 }
1709 
DoomResponses(const GURL & manifest_url,const std::vector<int64_t> & response_ids)1710 void AppCacheStorageImpl::DoomResponses(
1711     const GURL& manifest_url,
1712     const std::vector<int64_t>& response_ids) {
1713   if (response_ids.empty())
1714     return;
1715 
1716   // Start deleting them from the disk cache lazily.
1717   StartDeletingResponses(response_ids);
1718 
1719   // Also schedule a database task to record these ids in the
1720   // deletable responses table.
1721   // TODO(michaeln): There is a race here. If the browser crashes
1722   // prior to committing these rows to the database and prior to us
1723   // having deleted them from the disk cache, we'll never delete them.
1724   auto task = base::MakeRefCounted<InsertDeletableResponseIdsTask>(this);
1725   task->response_ids_ = response_ids;
1726   task->Schedule();
1727 }
1728 
DeleteResponses(const GURL & manifest_url,const std::vector<int64_t> & response_ids)1729 void AppCacheStorageImpl::DeleteResponses(
1730     const GURL& manifest_url,
1731     const std::vector<int64_t>& response_ids) {
1732   if (response_ids.empty())
1733     return;
1734   StartDeletingResponses(response_ids);
1735 }
1736 
IsInitialized()1737 bool AppCacheStorageImpl::IsInitialized() {
1738   return IsInitTaskComplete();
1739 }
1740 
DelayedStartDeletingUnusedResponses()1741 void AppCacheStorageImpl::DelayedStartDeletingUnusedResponses() {
1742   // Only if we haven't already begun.
1743   if (!did_start_deleting_responses_) {
1744     auto task = base::MakeRefCounted<GetDeletableResponseIdsTask>(
1745         this, last_deletable_response_rowid_);
1746     task->Schedule();
1747   }
1748 }
1749 
StartDeletingResponses(const std::vector<int64_t> & response_ids)1750 void AppCacheStorageImpl::StartDeletingResponses(
1751     const std::vector<int64_t>& response_ids) {
1752   DCHECK(!response_ids.empty());
1753   did_start_deleting_responses_ = true;
1754   deletable_response_ids_.insert(
1755       deletable_response_ids_.end(),
1756       response_ids.begin(), response_ids.end());
1757   if (!is_response_deletion_scheduled_)
1758     ScheduleDeleteOneResponse();
1759 }
1760 
ScheduleDeleteOneResponse()1761 void AppCacheStorageImpl::ScheduleDeleteOneResponse() {
1762   DCHECK(!is_response_deletion_scheduled_);
1763   const base::TimeDelta kBriefDelay = base::TimeDelta::FromMilliseconds(10);
1764   base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
1765       FROM_HERE,
1766       base::BindOnce(&AppCacheStorageImpl::DeleteOneResponse,
1767                      weak_factory_.GetWeakPtr()),
1768       kBriefDelay);
1769   is_response_deletion_scheduled_ = true;
1770 }
1771 
DeleteOneResponse()1772 void AppCacheStorageImpl::DeleteOneResponse() {
1773   DCHECK(is_response_deletion_scheduled_);
1774   DCHECK(!deletable_response_ids_.empty());
1775 
1776   if (is_disabled_) {
1777     deletable_response_ids_.clear();
1778     deleted_response_ids_.clear();
1779     is_response_deletion_scheduled_ = false;
1780     return;
1781   }
1782 
1783   // TODO(michaeln): add group_id to DoomEntry args
1784   int64_t id = deletable_response_ids_.front();
1785   int rv = disk_cache()->DoomEntry(
1786       id, base::BindOnce(&AppCacheStorageImpl::OnDeletedOneResponse,
1787                          base::Unretained(this)));
1788   if (rv != net::ERR_IO_PENDING)
1789     OnDeletedOneResponse(rv);
1790 }
1791 
OnDeletedOneResponse(int rv)1792 void AppCacheStorageImpl::OnDeletedOneResponse(int rv) {
1793   is_response_deletion_scheduled_ = false;
1794   if (is_disabled_)
1795     return;
1796 
1797   int64_t id = deletable_response_ids_.front();
1798   deletable_response_ids_.pop_front();
1799   if (rv != net::ERR_ABORTED)
1800     deleted_response_ids_.push_back(id);
1801 
1802   const size_t kBatchSize = 50U;
1803   if (deleted_response_ids_.size() >= kBatchSize ||
1804       deletable_response_ids_.empty()) {
1805     auto task = base::MakeRefCounted<DeleteDeletableResponseIdsTask>(this);
1806     task->response_ids_.swap(deleted_response_ids_);
1807     task->Schedule();
1808   }
1809 
1810   if (deletable_response_ids_.empty()) {
1811     auto task = base::MakeRefCounted<GetDeletableResponseIdsTask>(
1812         this, last_deletable_response_rowid_);
1813     task->Schedule();
1814     return;
1815   }
1816 
1817   ScheduleDeleteOneResponse();
1818 }
1819 
1820 AppCacheStorageImpl::CacheLoadTask*
GetPendingCacheLoadTask(int64_t cache_id)1821 AppCacheStorageImpl::GetPendingCacheLoadTask(int64_t cache_id) {
1822   auto found = pending_cache_loads_.find(cache_id);
1823   if (found != pending_cache_loads_.end())
1824     return found->second;
1825   return nullptr;
1826 }
1827 
1828 AppCacheStorageImpl::GroupLoadTask*
GetPendingGroupLoadTask(const GURL & manifest_url)1829 AppCacheStorageImpl::GetPendingGroupLoadTask(const GURL& manifest_url) {
1830   auto found = pending_group_loads_.find(manifest_url);
1831   if (found != pending_group_loads_.end())
1832     return found->second;
1833   return nullptr;
1834 }
1835 
GetPendingForeignMarkingsForCache(int64_t cache_id)1836 std::vector<GURL> AppCacheStorageImpl::GetPendingForeignMarkingsForCache(
1837     int64_t cache_id) {
1838   std::vector<GURL> urls;
1839   for (const auto& pair : pending_foreign_markings_) {
1840     if (pair.second == cache_id)
1841       urls.push_back(pair.first);
1842   }
1843   return urls;
1844 }
1845 
ScheduleSimpleTask(base::OnceClosure task)1846 void AppCacheStorageImpl::ScheduleSimpleTask(base::OnceClosure task) {
1847   pending_simple_tasks_.push_back(std::move(task));
1848   base::SequencedTaskRunnerHandle::Get()->PostTask(
1849       FROM_HERE, base::BindOnce(&AppCacheStorageImpl::RunOnePendingSimpleTask,
1850                                 weak_factory_.GetWeakPtr()));
1851 }
1852 
RunOnePendingSimpleTask()1853 void AppCacheStorageImpl::RunOnePendingSimpleTask() {
1854   DCHECK(!pending_simple_tasks_.empty());
1855   base::OnceClosure task = std::move(pending_simple_tasks_.front());
1856   pending_simple_tasks_.pop_front();
1857   std::move(task).Run();
1858 }
1859 
disk_cache()1860 AppCacheDiskCache* AppCacheStorageImpl::disk_cache() {
1861   DCHECK(IsInitTaskComplete());
1862   DCHECK(!is_disabled_);
1863 
1864   if (!disk_cache_) {
1865     int rv = net::OK;
1866     disk_cache_ = std::make_unique<AppCacheDiskCache>();
1867     if (is_incognito_) {
1868       rv = disk_cache_->InitWithMemBackend(
1869           0, base::BindOnce(&AppCacheStorageImpl::OnDiskCacheInitialized,
1870                             base::Unretained(this)));
1871     } else {
1872       expecting_cleanup_complete_on_disable_ = true;
1873 
1874       rv = disk_cache_->InitWithDiskBackend(
1875           cache_directory_.Append(kDiskCacheDirectoryName), false,
1876           base::BindOnce(&AppCacheStorageImpl::OnDiskCacheCleanupComplete,
1877                          weak_factory_.GetWeakPtr()),
1878           base::BindOnce(&AppCacheStorageImpl::OnDiskCacheInitialized,
1879                          base::Unretained(this)));
1880     }
1881 
1882     if (rv != net::ERR_IO_PENDING)
1883       OnDiskCacheInitialized(rv);
1884   }
1885   return disk_cache_.get();
1886 }
1887 
OnDiskCacheInitialized(int rv)1888 void AppCacheStorageImpl::OnDiskCacheInitialized(int rv) {
1889   if (rv != net::OK) {
1890     // We're unable to open the disk cache, this is a fatal error that we can't
1891     // really recover from. We handle it by temporarily disabling the appcache
1892     // deleting the directory on disk and reinitializing the appcache system.
1893     Disable();
1894     if (rv != net::ERR_ABORTED)
1895       DeleteAndStartOver();
1896   }
1897 }
1898 
DeleteAndStartOver()1899 void AppCacheStorageImpl::DeleteAndStartOver() {
1900   DCHECK(is_disabled_);
1901   if (!is_incognito_) {
1902     VLOG(1) << "Deleting existing appcache data and starting over.";
1903 
1904     // We can have tasks in flight to close file handles on both the db
1905     // and cache threads, we need to allow those tasks to cycle thru
1906     // prior to deleting the files and calling reinit.  We will know that the
1907     // cache ones will be finished once we get into OnDiskCacheCleanupComplete,
1908     // so let that known to synchronize with the DB thread.
1909     delete_and_start_over_pending_ = true;
1910 
1911     // Won't get a callback about cleanup being done, so call it ourselves.
1912     if (!expecting_cleanup_complete_on_disable_)
1913       OnDiskCacheCleanupComplete();
1914   }
1915 }
1916 
OnDiskCacheCleanupComplete()1917 void AppCacheStorageImpl::OnDiskCacheCleanupComplete() {
1918   expecting_cleanup_complete_on_disable_ = false;
1919   if (delete_and_start_over_pending_) {
1920     delete_and_start_over_pending_ = false;
1921     db_task_runner_->PostTaskAndReply(
1922         FROM_HERE,
1923         base::BindOnce(base::GetDeletePathRecursivelyCallback(),
1924                        cache_directory_),
1925         base::BindOnce(&AppCacheStorageImpl::CallScheduleReinitialize,
1926                        weak_factory_.GetWeakPtr()));
1927   }
1928 }
1929 
CallScheduleReinitialize()1930 void AppCacheStorageImpl::CallScheduleReinitialize() {
1931   service_->ScheduleReinitialize();
1932   // note: 'this' may be deleted at this point.
1933 }
1934 
LazilyCommitLastAccessTimes()1935 void AppCacheStorageImpl::LazilyCommitLastAccessTimes() {
1936   if (lazy_commit_timer_.IsRunning())
1937     return;
1938   const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5);
1939   lazy_commit_timer_.Start(
1940       FROM_HERE, kDelay,
1941       base::BindOnce(&AppCacheStorageImpl::OnLazyCommitTimer,
1942                      weak_factory_.GetWeakPtr()));
1943 }
1944 
OnLazyCommitTimer()1945 void AppCacheStorageImpl::OnLazyCommitTimer() {
1946   lazy_commit_timer_.Stop();
1947   if (is_disabled())
1948     return;
1949   auto task = base::MakeRefCounted<CommitLastAccessTimesTask>(this);
1950   task->Schedule();
1951 }
1952 
1953 }  // namespace content
1954