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
28namespace appcache {
29
30namespace {
31
32void DeferredCallback(const net::CompletionCallback& callback, int rv) {
33  callback.Run(rv);
34}
35
36}  // namespace
37
38AppCacheInfoCollection::AppCacheInfoCollection() {}
39
40AppCacheInfoCollection::~AppCacheInfoCollection() {}
41
42// AsyncHelper -------
43
44class AppCacheService::AsyncHelper
45    : public AppCacheStorage::Delegate {
46 public:
47  AsyncHelper(AppCacheService* service,
48              const net::CompletionCallback& callback)
49      : service_(service), callback_(callback) {
50    service_->pending_helpers_.insert(this);
51  }
52
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:
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
75void 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
86class AppCacheService::CanHandleOfflineHelper : AsyncHelper {
87 public:
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
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
120void 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
131class AppCacheService::DeleteHelper : public AsyncHelper {
132 public:
133  DeleteHelper(
134      AppCacheService* service, const GURL& manifest_url,
135      const net::CompletionCallback& callback)
136      : AsyncHelper(service, callback), manifest_url_(manifest_url) {
137  }
138
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
154void 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
166void 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
174class AppCacheService::DeleteOriginHelper : public AsyncHelper {
175 public:
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
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
206void 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
235void 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
246void AppCacheService::DeleteOriginHelper::OnGroupMadeObsolete(
247      appcache::AppCacheGroup* group, bool success) {
248  CacheCompleted(success);
249}
250
251void 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
266class AppCacheService::GetInfoHelper : AsyncHelper {
267 public:
268  GetInfoHelper(
269      AppCacheService* service, AppCacheInfoCollection* collection,
270      const net::CompletionCallback& callback)
271      : AsyncHelper(service, callback), collection_(collection) {
272  }
273
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
287void 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
297class AppCacheService::CheckResponseHelper : AsyncHelper {
298 public:
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
312  virtual void Start() OVERRIDE {
313    service_->storage()->LoadOrCreateGroup(manifest_url_, this);
314  }
315
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
346void 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
383void 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
402void 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// AppCacheService -------
431
432AppCacheService::AppCacheService(quota::QuotaManagerProxy* quota_manager_proxy)
433    : appcache_policy_(NULL), quota_client_(NULL), handler_factory_(NULL),
434      quota_manager_proxy_(quota_manager_proxy),
435      request_context_(NULL),
436      force_keep_session_state_(false) {
437  if (quota_manager_proxy_.get()) {
438    quota_client_ = new AppCacheQuotaClient(this);
439    quota_manager_proxy_->RegisterClient(quota_client_);
440  }
441}
442
443AppCacheService::~AppCacheService() {
444  DCHECK(backends_.empty());
445  std::for_each(pending_helpers_.begin(),
446                pending_helpers_.end(),
447                std::mem_fun(&AsyncHelper::Cancel));
448  STLDeleteElements(&pending_helpers_);
449  if (quota_client_)
450    quota_client_->NotifyAppCacheDestroyed();
451
452  // Destroy storage_ first; ~AppCacheStorageImpl accesses other data members
453  // (special_storage_policy_).
454  storage_.reset();
455}
456
457void AppCacheService::Initialize(const base::FilePath& cache_directory,
458                                 base::MessageLoopProxy* db_thread,
459                                 base::MessageLoopProxy* cache_thread) {
460  DCHECK(!storage_.get());
461  AppCacheStorageImpl* storage = new AppCacheStorageImpl(this);
462  storage->Initialize(cache_directory, db_thread, cache_thread);
463  storage_.reset(storage);
464}
465
466void AppCacheService::CanHandleMainResourceOffline(
467    const GURL& url,
468    const GURL& first_party,
469    const net::CompletionCallback& callback) {
470  CanHandleOfflineHelper* helper =
471      new CanHandleOfflineHelper(this, url, first_party, callback);
472  helper->Start();
473}
474
475void AppCacheService::GetAllAppCacheInfo(
476    AppCacheInfoCollection* collection,
477    const net::CompletionCallback& callback) {
478  DCHECK(collection);
479  GetInfoHelper* helper = new GetInfoHelper(this, collection, callback);
480  helper->Start();
481}
482
483void AppCacheService::DeleteAppCacheGroup(
484    const GURL& manifest_url,
485    const net::CompletionCallback& callback) {
486  DeleteHelper* helper = new DeleteHelper(this, manifest_url, callback);
487  helper->Start();
488}
489
490void AppCacheService::DeleteAppCachesForOrigin(
491    const GURL& origin,  const net::CompletionCallback& callback) {
492  DeleteOriginHelper* helper = new DeleteOriginHelper(this, origin, callback);
493  helper->Start();
494}
495
496void AppCacheService::CheckAppCacheResponse(const GURL& manifest_url,
497                                            int64 cache_id,
498                                            int64 response_id) {
499  CheckResponseHelper* helper = new CheckResponseHelper(
500      this, manifest_url, cache_id, response_id);
501  helper->Start();
502}
503
504void AppCacheService::set_special_storage_policy(
505    quota::SpecialStoragePolicy* policy) {
506  special_storage_policy_ = policy;
507}
508
509void AppCacheService::RegisterBackend(
510    AppCacheBackendImpl* backend_impl) {
511  DCHECK(backends_.find(backend_impl->process_id()) == backends_.end());
512  backends_.insert(
513      BackendMap::value_type(backend_impl->process_id(), backend_impl));
514}
515
516void AppCacheService::UnregisterBackend(
517    AppCacheBackendImpl* backend_impl) {
518  backends_.erase(backend_impl->process_id());
519}
520
521}  // namespace appcache
522