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 "webkit/browser/appcache/appcache_service.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_storage_impl.h"
25 #include "webkit/browser/quota/quota_manager.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 AppCacheService::AsyncHelper
45 : public AppCacheStorage::Delegate {
46 public:
AsyncHelper(AppCacheService * service,const net::CompletionCallback & callback)47 AsyncHelper(AppCacheService* 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 AppCacheService* service_;
72 net::CompletionCallback callback_;
73 };
74
Cancel()75 void AppCacheService::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 AppCacheService::CanHandleOfflineHelper : AsyncHelper {
87 public:
CanHandleOfflineHelper(AppCacheService * service,const GURL & url,const GURL & first_party,const net::CompletionCallback & callback)88 CanHandleOfflineHelper(
89 AppCacheService* 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 AppCacheService::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 AppCacheService::DeleteHelper : public AsyncHelper {
132 public:
DeleteHelper(AppCacheService * service,const GURL & manifest_url,const net::CompletionCallback & callback)133 DeleteHelper(
134 AppCacheService* 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(
148 appcache::AppCacheGroup* group, bool success) OVERRIDE;
149
150 GURL manifest_url_;
151 DISALLOW_COPY_AND_ASSIGN(DeleteHelper);
152 };
153
OnGroupLoaded(appcache::AppCacheGroup * group,const GURL & manifest_url)154 void AppCacheService::DeleteHelper::OnGroupLoaded(
155 appcache::AppCacheGroup* group, const GURL& manifest_url) {
156 if (group) {
157 group->set_being_deleted(true);
158 group->CancelUpdate();
159 service_->storage()->MakeGroupObsolete(group, this);
160 } else {
161 CallCallback(net::ERR_FAILED);
162 delete this;
163 }
164 }
165
OnGroupMadeObsolete(appcache::AppCacheGroup * group,bool success)166 void AppCacheService::DeleteHelper::OnGroupMadeObsolete(
167 appcache::AppCacheGroup* group, bool success) {
168 CallCallback(success ? net::OK : net::ERR_FAILED);
169 delete this;
170 }
171
172 // DeleteOriginHelper -------
173
174 class AppCacheService::DeleteOriginHelper : public AsyncHelper {
175 public:
DeleteOriginHelper(AppCacheService * service,const GURL & origin,const net::CompletionCallback & callback)176 DeleteOriginHelper(
177 AppCacheService* service, const GURL& origin,
178 const net::CompletionCallback& callback)
179 : AsyncHelper(service, callback), origin_(origin),
180 num_caches_to_delete_(0), successes_(0), failures_(0) {
181 }
182
Start()183 virtual void Start() OVERRIDE {
184 // We start by listing all caches, continues in OnAllInfo().
185 service_->storage()->GetAllInfo(this);
186 }
187
188 private:
189 // AppCacheStorage::Delegate implementation.
190 virtual void OnAllInfo(AppCacheInfoCollection* collection) OVERRIDE;
191 virtual void OnGroupLoaded(
192 appcache::AppCacheGroup* group, const GURL& manifest_url) OVERRIDE;
193 virtual void OnGroupMadeObsolete(
194 appcache::AppCacheGroup* group, bool success) OVERRIDE;
195
196 void CacheCompleted(bool success);
197
198 GURL origin_;
199 int num_caches_to_delete_;
200 int successes_;
201 int failures_;
202
203 DISALLOW_COPY_AND_ASSIGN(DeleteOriginHelper);
204 };
205
OnAllInfo(AppCacheInfoCollection * collection)206 void AppCacheService::DeleteOriginHelper::OnAllInfo(
207 AppCacheInfoCollection* collection) {
208 if (!collection) {
209 // Failed to get a listing.
210 CallCallback(net::ERR_FAILED);
211 delete this;
212 return;
213 }
214
215 std::map<GURL, AppCacheInfoVector>::iterator found =
216 collection->infos_by_origin.find(origin_);
217 if (found == collection->infos_by_origin.end() || found->second.empty()) {
218 // No caches for this origin.
219 CallCallback(net::OK);
220 delete this;
221 return;
222 }
223
224 // We have some caches to delete.
225 const AppCacheInfoVector& caches_to_delete = found->second;
226 successes_ = 0;
227 failures_ = 0;
228 num_caches_to_delete_ = static_cast<int>(caches_to_delete.size());
229 for (AppCacheInfoVector::const_iterator iter = caches_to_delete.begin();
230 iter != caches_to_delete.end(); ++iter) {
231 service_->storage()->LoadOrCreateGroup(iter->manifest_url, this);
232 }
233 }
234
OnGroupLoaded(appcache::AppCacheGroup * group,const GURL & manifest_url)235 void AppCacheService::DeleteOriginHelper::OnGroupLoaded(
236 appcache::AppCacheGroup* group, const GURL& manifest_url) {
237 if (group) {
238 group->set_being_deleted(true);
239 group->CancelUpdate();
240 service_->storage()->MakeGroupObsolete(group, this);
241 } else {
242 CacheCompleted(false);
243 }
244 }
245
OnGroupMadeObsolete(appcache::AppCacheGroup * group,bool success)246 void AppCacheService::DeleteOriginHelper::OnGroupMadeObsolete(
247 appcache::AppCacheGroup* group, bool success) {
248 CacheCompleted(success);
249 }
250
CacheCompleted(bool success)251 void AppCacheService::DeleteOriginHelper::CacheCompleted(bool success) {
252 if (success)
253 ++successes_;
254 else
255 ++failures_;
256 if ((successes_ + failures_) < num_caches_to_delete_)
257 return;
258
259 CallCallback(!failures_ ? net::OK : net::ERR_FAILED);
260 delete this;
261 }
262
263
264 // GetInfoHelper -------
265
266 class AppCacheService::GetInfoHelper : AsyncHelper {
267 public:
GetInfoHelper(AppCacheService * service,AppCacheInfoCollection * collection,const net::CompletionCallback & callback)268 GetInfoHelper(
269 AppCacheService* service, AppCacheInfoCollection* collection,
270 const net::CompletionCallback& callback)
271 : AsyncHelper(service, callback), collection_(collection) {
272 }
273
Start()274 virtual void Start() OVERRIDE {
275 service_->storage()->GetAllInfo(this);
276 }
277
278 private:
279 // AppCacheStorage::Delegate implementation.
280 virtual void OnAllInfo(AppCacheInfoCollection* collection) OVERRIDE;
281
282 scoped_refptr<AppCacheInfoCollection> collection_;
283
284 DISALLOW_COPY_AND_ASSIGN(GetInfoHelper);
285 };
286
OnAllInfo(AppCacheInfoCollection * collection)287 void AppCacheService::GetInfoHelper::OnAllInfo(
288 AppCacheInfoCollection* collection) {
289 if (collection)
290 collection->infos_by_origin.swap(collection_->infos_by_origin);
291 CallCallback(collection ? net::OK : net::ERR_FAILED);
292 delete this;
293 }
294
295 // CheckResponseHelper -------
296
297 class AppCacheService::CheckResponseHelper : AsyncHelper {
298 public:
CheckResponseHelper(AppCacheService * service,const GURL & manifest_url,int64 cache_id,int64 response_id)299 CheckResponseHelper(
300 AppCacheService* service, const GURL& manifest_url, int64 cache_id,
301 int64 response_id)
302 : AsyncHelper(service, net::CompletionCallback()),
303 manifest_url_(manifest_url),
304 cache_id_(cache_id),
305 response_id_(response_id),
306 kIOBufferSize(32 * 1024),
307 expected_total_size_(0),
308 amount_headers_read_(0),
309 amount_data_read_(0) {
310 }
311
Start()312 virtual void Start() OVERRIDE {
313 service_->storage()->LoadOrCreateGroup(manifest_url_, this);
314 }
315
Cancel()316 virtual void Cancel() OVERRIDE {
317 AppCacheHistograms::CountCheckResponseResult(
318 AppCacheHistograms::CHECK_CANCELED);
319 response_reader_.reset();
320 AsyncHelper::Cancel();
321 }
322
323 private:
324 virtual void OnGroupLoaded(AppCacheGroup* group,
325 const GURL& manifest_url) OVERRIDE;
326 void OnReadInfoComplete(int result);
327 void OnReadDataComplete(int result);
328
329 // Inputs describing what to check.
330 GURL manifest_url_;
331 int64 cache_id_;
332 int64 response_id_;
333
334 // Internals used to perform the checks.
335 const int kIOBufferSize;
336 scoped_refptr<AppCache> cache_;
337 scoped_ptr<AppCacheResponseReader> response_reader_;
338 scoped_refptr<HttpResponseInfoIOBuffer> info_buffer_;
339 scoped_refptr<net::IOBuffer> data_buffer_;
340 int64 expected_total_size_;
341 int amount_headers_read_;
342 int amount_data_read_;
343 DISALLOW_COPY_AND_ASSIGN(CheckResponseHelper);
344 };
345
OnGroupLoaded(AppCacheGroup * group,const GURL & manifest_url)346 void AppCacheService::CheckResponseHelper::OnGroupLoaded(
347 AppCacheGroup* group, const GURL& manifest_url) {
348 DCHECK_EQ(manifest_url_, manifest_url);
349 if (!group || !group->newest_complete_cache() || group->is_being_deleted() ||
350 group->is_obsolete()) {
351 AppCacheHistograms::CountCheckResponseResult(
352 AppCacheHistograms::MANIFEST_OUT_OF_DATE);
353 delete this;
354 return;
355 }
356
357 cache_ = group->newest_complete_cache();
358 const AppCacheEntry* entry = cache_->GetEntryWithResponseId(response_id_);
359 if (!entry) {
360 if (cache_->cache_id() == cache_id_) {
361 AppCacheHistograms::CountCheckResponseResult(
362 AppCacheHistograms::ENTRY_NOT_FOUND);
363 service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
364 } else {
365 AppCacheHistograms::CountCheckResponseResult(
366 AppCacheHistograms::RESPONSE_OUT_OF_DATE);
367 }
368 delete this;
369 return;
370 }
371
372 // Verify that we can read the response info and data.
373 expected_total_size_ = entry->response_size();
374 response_reader_.reset(service_->storage()->CreateResponseReader(
375 manifest_url_, group->group_id(), response_id_));
376 info_buffer_ = new HttpResponseInfoIOBuffer();
377 response_reader_->ReadInfo(
378 info_buffer_.get(),
379 base::Bind(&CheckResponseHelper::OnReadInfoComplete,
380 base::Unretained(this)));
381 }
382
OnReadInfoComplete(int result)383 void AppCacheService::CheckResponseHelper::OnReadInfoComplete(int result) {
384 if (result < 0) {
385 AppCacheHistograms::CountCheckResponseResult(
386 AppCacheHistograms::READ_HEADERS_ERROR);
387 service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
388 delete this;
389 return;
390 }
391 amount_headers_read_ = result;
392
393 // Start reading the data.
394 data_buffer_ = new net::IOBuffer(kIOBufferSize);
395 response_reader_->ReadData(
396 data_buffer_.get(),
397 kIOBufferSize,
398 base::Bind(&CheckResponseHelper::OnReadDataComplete,
399 base::Unretained(this)));
400 }
401
OnReadDataComplete(int result)402 void AppCacheService::CheckResponseHelper::OnReadDataComplete(int result) {
403 if (result > 0) {
404 // Keep reading until we've read thru everything or failed to read.
405 amount_data_read_ += result;
406 response_reader_->ReadData(
407 data_buffer_.get(),
408 kIOBufferSize,
409 base::Bind(&CheckResponseHelper::OnReadDataComplete,
410 base::Unretained(this)));
411 return;
412 }
413
414 AppCacheHistograms::CheckResponseResultType check_result;
415 if (result < 0)
416 check_result = AppCacheHistograms::READ_DATA_ERROR;
417 else if (info_buffer_->response_data_size != amount_data_read_ ||
418 expected_total_size_ != amount_data_read_ + amount_headers_read_)
419 check_result = AppCacheHistograms::UNEXPECTED_DATA_SIZE;
420 else
421 check_result = AppCacheHistograms::RESPONSE_OK;
422 AppCacheHistograms::CountCheckResponseResult(check_result);
423
424 if (check_result != AppCacheHistograms::RESPONSE_OK)
425 service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
426 delete this;
427 }
428
429
430 // AppCacheStorageReference ------
431
AppCacheStorageReference(scoped_ptr<AppCacheStorage> storage)432 AppCacheStorageReference::AppCacheStorageReference(
433 scoped_ptr<AppCacheStorage> storage)
434 : storage_(storage.Pass()) {}
~AppCacheStorageReference()435 AppCacheStorageReference::~AppCacheStorageReference() {}
436
437 // AppCacheService -------
438
AppCacheService(quota::QuotaManagerProxy * quota_manager_proxy)439 AppCacheService::AppCacheService(quota::QuotaManagerProxy* quota_manager_proxy)
440 : appcache_policy_(NULL), quota_client_(NULL), handler_factory_(NULL),
441 quota_manager_proxy_(quota_manager_proxy),
442 request_context_(NULL),
443 force_keep_session_state_(false),
444 was_reinitialized_(false) {
445 if (quota_manager_proxy_.get()) {
446 quota_client_ = new AppCacheQuotaClient(this);
447 quota_manager_proxy_->RegisterClient(quota_client_);
448 }
449 }
450
~AppCacheService()451 AppCacheService::~AppCacheService() {
452 DCHECK(backends_.empty());
453 std::for_each(pending_helpers_.begin(),
454 pending_helpers_.end(),
455 std::mem_fun(&AsyncHelper::Cancel));
456 STLDeleteElements(&pending_helpers_);
457 if (quota_client_)
458 quota_client_->NotifyAppCacheDestroyed();
459
460 // Destroy storage_ first; ~AppCacheStorageImpl accesses other data members
461 // (special_storage_policy_).
462 storage_.reset();
463 }
464
Initialize(const base::FilePath & cache_directory,base::MessageLoopProxy * db_thread,base::MessageLoopProxy * cache_thread)465 void AppCacheService::Initialize(const base::FilePath& cache_directory,
466 base::MessageLoopProxy* db_thread,
467 base::MessageLoopProxy* cache_thread) {
468 DCHECK(!storage_.get());
469 cache_directory_ = cache_directory;
470 db_thread_ = db_thread;
471 cache_thread_ = cache_thread;
472 AppCacheStorageImpl* storage = new AppCacheStorageImpl(this);
473 storage->Initialize(cache_directory, db_thread, cache_thread);
474 storage_.reset(storage);
475 }
476
Reinitialize()477 void AppCacheService::Reinitialize() {
478 AppCacheHistograms::CountReinitAttempt(was_reinitialized_);
479
480 // To avoid thrashing, we only do this once.
481 if (was_reinitialized_)
482 return;
483 was_reinitialized_ = true;
484
485 // Inform observers of about this and give them a chance to
486 // defer deletion of the old storage object.
487 scoped_refptr<AppCacheStorageReference>
488 old_storage_ref(new AppCacheStorageReference(storage_.Pass()));
489 FOR_EACH_OBSERVER(Observer, observers_,
490 OnServiceReinitialized(old_storage_ref.get()));
491
492 Initialize(cache_directory_, db_thread_, cache_thread_);
493 }
494
CanHandleMainResourceOffline(const GURL & url,const GURL & first_party,const net::CompletionCallback & callback)495 void AppCacheService::CanHandleMainResourceOffline(
496 const GURL& url,
497 const GURL& first_party,
498 const net::CompletionCallback& callback) {
499 CanHandleOfflineHelper* helper =
500 new CanHandleOfflineHelper(this, url, first_party, callback);
501 helper->Start();
502 }
503
GetAllAppCacheInfo(AppCacheInfoCollection * collection,const net::CompletionCallback & callback)504 void AppCacheService::GetAllAppCacheInfo(
505 AppCacheInfoCollection* collection,
506 const net::CompletionCallback& callback) {
507 DCHECK(collection);
508 GetInfoHelper* helper = new GetInfoHelper(this, collection, callback);
509 helper->Start();
510 }
511
DeleteAppCacheGroup(const GURL & manifest_url,const net::CompletionCallback & callback)512 void AppCacheService::DeleteAppCacheGroup(
513 const GURL& manifest_url,
514 const net::CompletionCallback& callback) {
515 DeleteHelper* helper = new DeleteHelper(this, manifest_url, callback);
516 helper->Start();
517 }
518
DeleteAppCachesForOrigin(const GURL & origin,const net::CompletionCallback & callback)519 void AppCacheService::DeleteAppCachesForOrigin(
520 const GURL& origin, const net::CompletionCallback& callback) {
521 DeleteOriginHelper* helper = new DeleteOriginHelper(this, origin, callback);
522 helper->Start();
523 }
524
CheckAppCacheResponse(const GURL & manifest_url,int64 cache_id,int64 response_id)525 void AppCacheService::CheckAppCacheResponse(const GURL& manifest_url,
526 int64 cache_id,
527 int64 response_id) {
528 CheckResponseHelper* helper = new CheckResponseHelper(
529 this, manifest_url, cache_id, response_id);
530 helper->Start();
531 }
532
set_special_storage_policy(quota::SpecialStoragePolicy * policy)533 void AppCacheService::set_special_storage_policy(
534 quota::SpecialStoragePolicy* policy) {
535 special_storage_policy_ = policy;
536 }
537
RegisterBackend(AppCacheBackendImpl * backend_impl)538 void AppCacheService::RegisterBackend(
539 AppCacheBackendImpl* backend_impl) {
540 DCHECK(backends_.find(backend_impl->process_id()) == backends_.end());
541 backends_.insert(
542 BackendMap::value_type(backend_impl->process_id(), backend_impl));
543 }
544
UnregisterBackend(AppCacheBackendImpl * backend_impl)545 void AppCacheService::UnregisterBackend(
546 AppCacheBackendImpl* backend_impl) {
547 backends_.erase(backend_impl->process_id());
548 }
549
550 } // namespace appcache
551