1 // Copyright 2014 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 "webkit/browser/appcache/appcache_service_impl.h"
6
7 #include <functional>
8
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/stl_util.h"
14 #include "net/base/completion_callback.h"
15 #include "net/base/io_buffer.h"
16 #include "webkit/browser/appcache/appcache.h"
17 #include "webkit/browser/appcache/appcache_backend_impl.h"
18 #include "webkit/browser/appcache/appcache_entry.h"
19 #include "webkit/browser/appcache/appcache_executable_handler.h"
20 #include "webkit/browser/appcache/appcache_histograms.h"
21 #include "webkit/browser/appcache/appcache_policy.h"
22 #include "webkit/browser/appcache/appcache_quota_client.h"
23 #include "webkit/browser/appcache/appcache_response.h"
24 #include "webkit/browser/appcache/appcache_service_impl.h"
25 #include "webkit/browser/appcache/appcache_storage_impl.h"
26 #include "webkit/browser/quota/special_storage_policy.h"
27
28 namespace appcache {
29
30 namespace {
31
DeferredCallback(const net::CompletionCallback & callback,int rv)32 void DeferredCallback(const net::CompletionCallback& callback, int rv) {
33 callback.Run(rv);
34 }
35
36 } // namespace
37
AppCacheInfoCollection()38 AppCacheInfoCollection::AppCacheInfoCollection() {}
39
~AppCacheInfoCollection()40 AppCacheInfoCollection::~AppCacheInfoCollection() {}
41
42 // AsyncHelper -------
43
44 class AppCacheServiceImpl::AsyncHelper
45 : public AppCacheStorage::Delegate {
46 public:
AsyncHelper(AppCacheServiceImpl * service,const net::CompletionCallback & callback)47 AsyncHelper(AppCacheServiceImpl* service,
48 const net::CompletionCallback& callback)
49 : service_(service), callback_(callback) {
50 service_->pending_helpers_.insert(this);
51 }
52
~AsyncHelper()53 virtual ~AsyncHelper() {
54 if (service_)
55 service_->pending_helpers_.erase(this);
56 }
57
58 virtual void Start() = 0;
59 virtual void Cancel();
60
61 protected:
CallCallback(int rv)62 void CallCallback(int rv) {
63 if (!callback_.is_null()) {
64 // Defer to guarantee async completion.
65 base::MessageLoop::current()->PostTask(
66 FROM_HERE, base::Bind(&DeferredCallback, callback_, rv));
67 }
68 callback_.Reset();
69 }
70
71 AppCacheServiceImpl* service_;
72 net::CompletionCallback callback_;
73 };
74
Cancel()75 void AppCacheServiceImpl::AsyncHelper::Cancel() {
76 if (!callback_.is_null()) {
77 callback_.Run(net::ERR_ABORTED);
78 callback_.Reset();
79 }
80 service_->storage()->CancelDelegateCallbacks(this);
81 service_ = NULL;
82 }
83
84 // CanHandleOfflineHelper -------
85
86 class AppCacheServiceImpl::CanHandleOfflineHelper : AsyncHelper {
87 public:
CanHandleOfflineHelper(AppCacheServiceImpl * service,const GURL & url,const GURL & first_party,const net::CompletionCallback & callback)88 CanHandleOfflineHelper(
89 AppCacheServiceImpl* service, const GURL& url,
90 const GURL& first_party, const net::CompletionCallback& callback)
91 : AsyncHelper(service, callback),
92 url_(url),
93 first_party_(first_party) {
94 }
95
Start()96 virtual void Start() OVERRIDE {
97 AppCachePolicy* policy = service_->appcache_policy();
98 if (policy && !policy->CanLoadAppCache(url_, first_party_)) {
99 CallCallback(net::ERR_FAILED);
100 delete this;
101 return;
102 }
103
104 service_->storage()->FindResponseForMainRequest(url_, GURL(), this);
105 }
106
107 private:
108 // AppCacheStorage::Delegate implementation.
109 virtual void OnMainResponseFound(
110 const GURL& url, const AppCacheEntry& entry,
111 const GURL& fallback_url, const AppCacheEntry& fallback_entry,
112 int64 cache_id, int64 group_id, const GURL& mainfest_url) OVERRIDE;
113
114 GURL url_;
115 GURL first_party_;
116
117 DISALLOW_COPY_AND_ASSIGN(CanHandleOfflineHelper);
118 };
119
OnMainResponseFound(const GURL & url,const AppCacheEntry & entry,const GURL & fallback_url,const AppCacheEntry & fallback_entry,int64 cache_id,int64 group_id,const GURL & manifest_url)120 void AppCacheServiceImpl::CanHandleOfflineHelper::OnMainResponseFound(
121 const GURL& url, const AppCacheEntry& entry,
122 const GURL& fallback_url, const AppCacheEntry& fallback_entry,
123 int64 cache_id, int64 group_id, const GURL& manifest_url) {
124 bool can = (entry.has_response_id() || fallback_entry.has_response_id());
125 CallCallback(can ? net::OK : net::ERR_FAILED);
126 delete this;
127 }
128
129 // DeleteHelper -------
130
131 class AppCacheServiceImpl::DeleteHelper : public AsyncHelper {
132 public:
DeleteHelper(AppCacheServiceImpl * service,const GURL & manifest_url,const net::CompletionCallback & callback)133 DeleteHelper(
134 AppCacheServiceImpl* service, const GURL& manifest_url,
135 const net::CompletionCallback& callback)
136 : AsyncHelper(service, callback), manifest_url_(manifest_url) {
137 }
138
Start()139 virtual void Start() OVERRIDE {
140 service_->storage()->LoadOrCreateGroup(manifest_url_, this);
141 }
142
143 private:
144 // AppCacheStorage::Delegate implementation.
145 virtual void OnGroupLoaded(
146 appcache::AppCacheGroup* group, const GURL& manifest_url) OVERRIDE;
147 virtual void OnGroupMadeObsolete(appcache::AppCacheGroup* group,
148 bool success,
149 int response_code) OVERRIDE;
150
151 GURL manifest_url_;
152 DISALLOW_COPY_AND_ASSIGN(DeleteHelper);
153 };
154
OnGroupLoaded(appcache::AppCacheGroup * group,const GURL & manifest_url)155 void AppCacheServiceImpl::DeleteHelper::OnGroupLoaded(
156 appcache::AppCacheGroup* group, const GURL& manifest_url) {
157 if (group) {
158 group->set_being_deleted(true);
159 group->CancelUpdate();
160 service_->storage()->MakeGroupObsolete(group, this, 0);
161 } else {
162 CallCallback(net::ERR_FAILED);
163 delete this;
164 }
165 }
166
OnGroupMadeObsolete(appcache::AppCacheGroup * group,bool success,int response_code)167 void AppCacheServiceImpl::DeleteHelper::OnGroupMadeObsolete(
168 appcache::AppCacheGroup* group,
169 bool success,
170 int response_code) {
171 CallCallback(success ? net::OK : net::ERR_FAILED);
172 delete this;
173 }
174
175 // DeleteOriginHelper -------
176
177 class AppCacheServiceImpl::DeleteOriginHelper : public AsyncHelper {
178 public:
DeleteOriginHelper(AppCacheServiceImpl * service,const GURL & origin,const net::CompletionCallback & callback)179 DeleteOriginHelper(
180 AppCacheServiceImpl* service, const GURL& origin,
181 const net::CompletionCallback& callback)
182 : AsyncHelper(service, callback), origin_(origin),
183 num_caches_to_delete_(0), successes_(0), failures_(0) {
184 }
185
Start()186 virtual void Start() OVERRIDE {
187 // We start by listing all caches, continues in OnAllInfo().
188 service_->storage()->GetAllInfo(this);
189 }
190
191 private:
192 // AppCacheStorage::Delegate implementation.
193 virtual void OnAllInfo(AppCacheInfoCollection* collection) OVERRIDE;
194 virtual void OnGroupLoaded(
195 appcache::AppCacheGroup* group, const GURL& manifest_url) OVERRIDE;
196 virtual void OnGroupMadeObsolete(appcache::AppCacheGroup* group,
197 bool success,
198 int response_code) OVERRIDE;
199
200 void CacheCompleted(bool success);
201
202 GURL origin_;
203 int num_caches_to_delete_;
204 int successes_;
205 int failures_;
206
207 DISALLOW_COPY_AND_ASSIGN(DeleteOriginHelper);
208 };
209
OnAllInfo(AppCacheInfoCollection * collection)210 void AppCacheServiceImpl::DeleteOriginHelper::OnAllInfo(
211 AppCacheInfoCollection* collection) {
212 if (!collection) {
213 // Failed to get a listing.
214 CallCallback(net::ERR_FAILED);
215 delete this;
216 return;
217 }
218
219 std::map<GURL, AppCacheInfoVector>::iterator found =
220 collection->infos_by_origin.find(origin_);
221 if (found == collection->infos_by_origin.end() || found->second.empty()) {
222 // No caches for this origin.
223 CallCallback(net::OK);
224 delete this;
225 return;
226 }
227
228 // We have some caches to delete.
229 const AppCacheInfoVector& caches_to_delete = found->second;
230 successes_ = 0;
231 failures_ = 0;
232 num_caches_to_delete_ = static_cast<int>(caches_to_delete.size());
233 for (AppCacheInfoVector::const_iterator iter = caches_to_delete.begin();
234 iter != caches_to_delete.end(); ++iter) {
235 service_->storage()->LoadOrCreateGroup(iter->manifest_url, this);
236 }
237 }
238
OnGroupLoaded(appcache::AppCacheGroup * group,const GURL & manifest_url)239 void AppCacheServiceImpl::DeleteOriginHelper::OnGroupLoaded(
240 appcache::AppCacheGroup* group, const GURL& manifest_url) {
241 if (group) {
242 group->set_being_deleted(true);
243 group->CancelUpdate();
244 service_->storage()->MakeGroupObsolete(group, this, 0);
245 } else {
246 CacheCompleted(false);
247 }
248 }
249
OnGroupMadeObsolete(appcache::AppCacheGroup * group,bool success,int response_code)250 void AppCacheServiceImpl::DeleteOriginHelper::OnGroupMadeObsolete(
251 appcache::AppCacheGroup* group,
252 bool success,
253 int response_code) {
254 CacheCompleted(success);
255 }
256
CacheCompleted(bool success)257 void AppCacheServiceImpl::DeleteOriginHelper::CacheCompleted(bool success) {
258 if (success)
259 ++successes_;
260 else
261 ++failures_;
262 if ((successes_ + failures_) < num_caches_to_delete_)
263 return;
264
265 CallCallback(!failures_ ? net::OK : net::ERR_FAILED);
266 delete this;
267 }
268
269
270 // GetInfoHelper -------
271
272 class AppCacheServiceImpl::GetInfoHelper : AsyncHelper {
273 public:
GetInfoHelper(AppCacheServiceImpl * service,AppCacheInfoCollection * collection,const net::CompletionCallback & callback)274 GetInfoHelper(
275 AppCacheServiceImpl* service, AppCacheInfoCollection* collection,
276 const net::CompletionCallback& callback)
277 : AsyncHelper(service, callback), collection_(collection) {
278 }
279
Start()280 virtual void Start() OVERRIDE {
281 service_->storage()->GetAllInfo(this);
282 }
283
284 private:
285 // AppCacheStorage::Delegate implementation.
286 virtual void OnAllInfo(AppCacheInfoCollection* collection) OVERRIDE;
287
288 scoped_refptr<AppCacheInfoCollection> collection_;
289
290 DISALLOW_COPY_AND_ASSIGN(GetInfoHelper);
291 };
292
OnAllInfo(AppCacheInfoCollection * collection)293 void AppCacheServiceImpl::GetInfoHelper::OnAllInfo(
294 AppCacheInfoCollection* collection) {
295 if (collection)
296 collection->infos_by_origin.swap(collection_->infos_by_origin);
297 CallCallback(collection ? net::OK : net::ERR_FAILED);
298 delete this;
299 }
300
301 // CheckResponseHelper -------
302
303 class AppCacheServiceImpl::CheckResponseHelper : AsyncHelper {
304 public:
CheckResponseHelper(AppCacheServiceImpl * service,const GURL & manifest_url,int64 cache_id,int64 response_id)305 CheckResponseHelper(
306 AppCacheServiceImpl* service, const GURL& manifest_url, int64 cache_id,
307 int64 response_id)
308 : AsyncHelper(service, net::CompletionCallback()),
309 manifest_url_(manifest_url),
310 cache_id_(cache_id),
311 response_id_(response_id),
312 kIOBufferSize(32 * 1024),
313 expected_total_size_(0),
314 amount_headers_read_(0),
315 amount_data_read_(0) {
316 }
317
Start()318 virtual void Start() OVERRIDE {
319 service_->storage()->LoadOrCreateGroup(manifest_url_, this);
320 }
321
Cancel()322 virtual void Cancel() OVERRIDE {
323 AppCacheHistograms::CountCheckResponseResult(
324 AppCacheHistograms::CHECK_CANCELED);
325 response_reader_.reset();
326 AsyncHelper::Cancel();
327 }
328
329 private:
330 virtual void OnGroupLoaded(AppCacheGroup* group,
331 const GURL& manifest_url) OVERRIDE;
332 void OnReadInfoComplete(int result);
333 void OnReadDataComplete(int result);
334
335 // Inputs describing what to check.
336 GURL manifest_url_;
337 int64 cache_id_;
338 int64 response_id_;
339
340 // Internals used to perform the checks.
341 const int kIOBufferSize;
342 scoped_refptr<AppCache> cache_;
343 scoped_ptr<AppCacheResponseReader> response_reader_;
344 scoped_refptr<HttpResponseInfoIOBuffer> info_buffer_;
345 scoped_refptr<net::IOBuffer> data_buffer_;
346 int64 expected_total_size_;
347 int amount_headers_read_;
348 int amount_data_read_;
349 DISALLOW_COPY_AND_ASSIGN(CheckResponseHelper);
350 };
351
OnGroupLoaded(AppCacheGroup * group,const GURL & manifest_url)352 void AppCacheServiceImpl::CheckResponseHelper::OnGroupLoaded(
353 AppCacheGroup* group, const GURL& manifest_url) {
354 DCHECK_EQ(manifest_url_, manifest_url);
355 if (!group || !group->newest_complete_cache() || group->is_being_deleted() ||
356 group->is_obsolete()) {
357 AppCacheHistograms::CountCheckResponseResult(
358 AppCacheHistograms::MANIFEST_OUT_OF_DATE);
359 delete this;
360 return;
361 }
362
363 cache_ = group->newest_complete_cache();
364 const AppCacheEntry* entry = cache_->GetEntryWithResponseId(response_id_);
365 if (!entry) {
366 if (cache_->cache_id() == cache_id_) {
367 AppCacheHistograms::CountCheckResponseResult(
368 AppCacheHistograms::ENTRY_NOT_FOUND);
369 service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
370 } else {
371 AppCacheHistograms::CountCheckResponseResult(
372 AppCacheHistograms::RESPONSE_OUT_OF_DATE);
373 }
374 delete this;
375 return;
376 }
377
378 // Verify that we can read the response info and data.
379 expected_total_size_ = entry->response_size();
380 response_reader_.reset(service_->storage()->CreateResponseReader(
381 manifest_url_, group->group_id(), response_id_));
382 info_buffer_ = new HttpResponseInfoIOBuffer();
383 response_reader_->ReadInfo(
384 info_buffer_.get(),
385 base::Bind(&CheckResponseHelper::OnReadInfoComplete,
386 base::Unretained(this)));
387 }
388
OnReadInfoComplete(int result)389 void AppCacheServiceImpl::CheckResponseHelper::OnReadInfoComplete(int result) {
390 if (result < 0) {
391 AppCacheHistograms::CountCheckResponseResult(
392 AppCacheHistograms::READ_HEADERS_ERROR);
393 service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
394 delete this;
395 return;
396 }
397 amount_headers_read_ = result;
398
399 // Start reading the data.
400 data_buffer_ = new net::IOBuffer(kIOBufferSize);
401 response_reader_->ReadData(
402 data_buffer_.get(),
403 kIOBufferSize,
404 base::Bind(&CheckResponseHelper::OnReadDataComplete,
405 base::Unretained(this)));
406 }
407
OnReadDataComplete(int result)408 void AppCacheServiceImpl::CheckResponseHelper::OnReadDataComplete(int result) {
409 if (result > 0) {
410 // Keep reading until we've read thru everything or failed to read.
411 amount_data_read_ += result;
412 response_reader_->ReadData(
413 data_buffer_.get(),
414 kIOBufferSize,
415 base::Bind(&CheckResponseHelper::OnReadDataComplete,
416 base::Unretained(this)));
417 return;
418 }
419
420 AppCacheHistograms::CheckResponseResultType check_result;
421 if (result < 0)
422 check_result = AppCacheHistograms::READ_DATA_ERROR;
423 else if (info_buffer_->response_data_size != amount_data_read_ ||
424 expected_total_size_ != amount_data_read_ + amount_headers_read_)
425 check_result = AppCacheHistograms::UNEXPECTED_DATA_SIZE;
426 else
427 check_result = AppCacheHistograms::RESPONSE_OK;
428 AppCacheHistograms::CountCheckResponseResult(check_result);
429
430 if (check_result != AppCacheHistograms::RESPONSE_OK)
431 service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
432 delete this;
433 }
434
435 // AppCacheStorageReference ------
436
AppCacheStorageReference(scoped_ptr<AppCacheStorage> storage)437 AppCacheStorageReference::AppCacheStorageReference(
438 scoped_ptr<AppCacheStorage> storage)
439 : storage_(storage.Pass()) {}
~AppCacheStorageReference()440 AppCacheStorageReference::~AppCacheStorageReference() {}
441
442 // AppCacheServiceImpl -------
443
AppCacheServiceImpl(quota::QuotaManagerProxy * quota_manager_proxy)444 AppCacheServiceImpl::AppCacheServiceImpl(quota::QuotaManagerProxy*
445 quota_manager_proxy)
446 : appcache_policy_(NULL), quota_client_(NULL), handler_factory_(NULL),
447 quota_manager_proxy_(quota_manager_proxy),
448 request_context_(NULL),
449 force_keep_session_state_(false) {
450 if (quota_manager_proxy_.get()) {
451 quota_client_ = new AppCacheQuotaClient(this);
452 quota_manager_proxy_->RegisterClient(quota_client_);
453 }
454 }
455
~AppCacheServiceImpl()456 AppCacheServiceImpl::~AppCacheServiceImpl() {
457 DCHECK(backends_.empty());
458 std::for_each(pending_helpers_.begin(),
459 pending_helpers_.end(),
460 std::mem_fun(&AsyncHelper::Cancel));
461 STLDeleteElements(&pending_helpers_);
462 if (quota_client_)
463 quota_client_->NotifyAppCacheDestroyed();
464
465 // Destroy storage_ first; ~AppCacheStorageImpl accesses other data members
466 // (special_storage_policy_).
467 storage_.reset();
468 }
469
Initialize(const base::FilePath & cache_directory,base::MessageLoopProxy * db_thread,base::MessageLoopProxy * cache_thread)470 void AppCacheServiceImpl::Initialize(const base::FilePath& cache_directory,
471 base::MessageLoopProxy* db_thread,
472 base::MessageLoopProxy* cache_thread) {
473 DCHECK(!storage_.get());
474 cache_directory_ = cache_directory;
475 db_thread_ = db_thread;
476 cache_thread_ = cache_thread;
477 AppCacheStorageImpl* storage = new AppCacheStorageImpl(this);
478 storage->Initialize(cache_directory, db_thread, cache_thread);
479 storage_.reset(storage);
480 }
481
ScheduleReinitialize()482 void AppCacheServiceImpl::ScheduleReinitialize() {
483 if (reinit_timer_.IsRunning())
484 return;
485
486 // Reinitialization only happens when corruption has been noticed.
487 // We don't want to thrash the disk but we also don't want to
488 // leave the appcache disabled for an indefinite period of time. Some
489 // users never shutdown the browser.
490
491 const base::TimeDelta kZeroDelta;
492 const base::TimeDelta kOneHour(base::TimeDelta::FromHours(1));
493 const base::TimeDelta k30Seconds(base::TimeDelta::FromSeconds(30));
494
495 // If the system managed to stay up for long enough, reset the
496 // delay so a new failure won't incur a long wait to get going again.
497 base::TimeDelta up_time = base::Time::Now() - last_reinit_time_;
498 if (next_reinit_delay_ != kZeroDelta && up_time > kOneHour)
499 next_reinit_delay_ = kZeroDelta;
500
501 reinit_timer_.Start(FROM_HERE, next_reinit_delay_,
502 this, &AppCacheServiceImpl::Reinitialize);
503
504 // Adjust the delay for next time.
505 base::TimeDelta increment = std::max(k30Seconds, next_reinit_delay_);
506 next_reinit_delay_ = std::min(next_reinit_delay_ + increment, kOneHour);
507 }
508
Reinitialize()509 void AppCacheServiceImpl::Reinitialize() {
510 AppCacheHistograms::CountReinitAttempt(!last_reinit_time_.is_null());
511 last_reinit_time_ = base::Time::Now();
512
513 // Inform observers of about this and give them a chance to
514 // defer deletion of the old storage object.
515 scoped_refptr<AppCacheStorageReference>
516 old_storage_ref(new AppCacheStorageReference(storage_.Pass()));
517 FOR_EACH_OBSERVER(Observer, observers_,
518 OnServiceReinitialized(old_storage_ref.get()));
519
520 Initialize(cache_directory_, db_thread_, cache_thread_);
521 }
522
CanHandleMainResourceOffline(const GURL & url,const GURL & first_party,const net::CompletionCallback & callback)523 void AppCacheServiceImpl::CanHandleMainResourceOffline(
524 const GURL& url,
525 const GURL& first_party,
526 const net::CompletionCallback& callback) {
527 CanHandleOfflineHelper* helper =
528 new CanHandleOfflineHelper(this, url, first_party, callback);
529 helper->Start();
530 }
531
GetAllAppCacheInfo(AppCacheInfoCollection * collection,const net::CompletionCallback & callback)532 void AppCacheServiceImpl::GetAllAppCacheInfo(
533 AppCacheInfoCollection* collection,
534 const net::CompletionCallback& callback) {
535 DCHECK(collection);
536 GetInfoHelper* helper = new GetInfoHelper(this, collection, callback);
537 helper->Start();
538 }
539
DeleteAppCacheGroup(const GURL & manifest_url,const net::CompletionCallback & callback)540 void AppCacheServiceImpl::DeleteAppCacheGroup(
541 const GURL& manifest_url,
542 const net::CompletionCallback& callback) {
543 DeleteHelper* helper = new DeleteHelper(this, manifest_url, callback);
544 helper->Start();
545 }
546
DeleteAppCachesForOrigin(const GURL & origin,const net::CompletionCallback & callback)547 void AppCacheServiceImpl::DeleteAppCachesForOrigin(
548 const GURL& origin, const net::CompletionCallback& callback) {
549 DeleteOriginHelper* helper = new DeleteOriginHelper(this, origin, callback);
550 helper->Start();
551 }
552
CheckAppCacheResponse(const GURL & manifest_url,int64 cache_id,int64 response_id)553 void AppCacheServiceImpl::CheckAppCacheResponse(const GURL& manifest_url,
554 int64 cache_id,
555 int64 response_id) {
556 CheckResponseHelper* helper = new CheckResponseHelper(
557 this, manifest_url, cache_id, response_id);
558 helper->Start();
559 }
560
set_special_storage_policy(quota::SpecialStoragePolicy * policy)561 void AppCacheServiceImpl::set_special_storage_policy(
562 quota::SpecialStoragePolicy* policy) {
563 special_storage_policy_ = policy;
564 }
565
RegisterBackend(AppCacheBackendImpl * backend_impl)566 void AppCacheServiceImpl::RegisterBackend(
567 AppCacheBackendImpl* backend_impl) {
568 DCHECK(backends_.find(backend_impl->process_id()) == backends_.end());
569 backends_.insert(
570 BackendMap::value_type(backend_impl->process_id(), backend_impl));
571 }
572
UnregisterBackend(AppCacheBackendImpl * backend_impl)573 void AppCacheServiceImpl::UnregisterBackend(
574 AppCacheBackendImpl* backend_impl) {
575 backends_.erase(backend_impl->process_id());
576 }
577
578 } // namespace appcache
579