Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / content / browser / service_worker / service_worker_cache.cc
index d5028f0..6bf5acb 100644 (file)
 #include <string>
 
 #include "base/files/file_path.h"
+#include "base/guid.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/strings/string_util.h"
+#include "content/browser/service_worker/service_worker_cache.pb.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/disk_cache.h"
 #include "net/url_request/url_request_context.h"
-#include "webkit/browser/blob/blob_storage_context.h"
+#include "storage/browser/blob/blob_data_handle.h"
+#include "storage/browser/blob/blob_storage_context.h"
+#include "storage/browser/blob/blob_url_request_job_factory.h"
 
 namespace content {
 
+namespace {
+
+typedef scoped_ptr<disk_cache::Backend> ScopedBackendPtr;
+typedef base::Callback<void(bool)> BoolCallback;
+typedef base::Callback<void(disk_cache::ScopedEntryPtr, bool)>
+    EntryBoolCallback;
+typedef base::Callback<void(scoped_ptr<ServiceWorkerRequestResponseHeaders>)>
+    HeadersCallback;
+
+enum EntryIndex { INDEX_HEADERS = 0, INDEX_RESPONSE_BODY };
+
+// The maximum size of an individual cache. Ultimately cache size is controlled
+// per-origin.
+const int kMaxCacheBytes = 512 * 1024 * 1024;
+
+// Buffer size for cache and blob reading/writing.
+const int kBufferSize = 1024 * 512;
+
+struct ResponseReadContext {
+  ResponseReadContext(scoped_refptr<net::IOBufferWithSize> buff,
+                      scoped_refptr<storage::BlobData> blob)
+      : buffer(buff), blob_data(blob), total_bytes_read(0) {}
+
+  scoped_refptr<net::IOBufferWithSize> buffer;
+  scoped_refptr<storage::BlobData> blob_data;
+  int total_bytes_read;
+
+  DISALLOW_COPY_AND_ASSIGN(ResponseReadContext);
+};
+
+// Streams data from a blob and writes it to a given disk_cache::Entry.
+class BlobReader : public net::URLRequest::Delegate {
+ public:
+  typedef base::Callback<void(disk_cache::ScopedEntryPtr, bool)>
+      EntryBoolCallback;
+
+  BlobReader(disk_cache::ScopedEntryPtr entry)
+      : cache_entry_offset_(0),
+        buffer_(new net::IOBufferWithSize(kBufferSize)),
+        weak_ptr_factory_(this) {
+    DCHECK(entry);
+    entry_ = entry.Pass();
+  }
+
+  void StreamBlobToCache(net::URLRequestContext* request_context,
+                         scoped_ptr<storage::BlobDataHandle> blob_data_handle,
+                         const EntryBoolCallback& callback) {
+    callback_ = callback;
+    blob_request_ = storage::BlobProtocolHandler::CreateBlobRequest(
+        blob_data_handle.Pass(), request_context, this);
+    blob_request_->Start();
+  }
+
+  // net::URLRequest::Delegate overrides for reading blobs.
+  virtual void OnReceivedRedirect(net::URLRequest* request,
+                                  const net::RedirectInfo& redirect_info,
+                                  bool* defer_redirect) OVERRIDE {
+    NOTREACHED();
+  }
+  virtual void OnAuthRequired(net::URLRequest* request,
+                              net::AuthChallengeInfo* auth_info) OVERRIDE {
+    NOTREACHED();
+  }
+  virtual void OnCertificateRequested(
+      net::URLRequest* request,
+      net::SSLCertRequestInfo* cert_request_info) OVERRIDE {
+    NOTREACHED();
+  }
+  virtual void OnSSLCertificateError(net::URLRequest* request,
+                                     const net::SSLInfo& ssl_info,
+                                     bool fatal) OVERRIDE {
+    NOTREACHED();
+  }
+  virtual void OnBeforeNetworkStart(net::URLRequest* request,
+                                    bool* defer) OVERRIDE {
+    NOTREACHED();
+  }
+
+  virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE {
+    if (!request->status().is_success()) {
+      callback_.Run(entry_.Pass(), false);
+      return;
+    }
+    ReadFromBlob();
+  }
+
+  virtual void ReadFromBlob() {
+    int bytes_read = 0;
+    bool done =
+        blob_request_->Read(buffer_.get(), buffer_->size(), &bytes_read);
+    if (done)
+      OnReadCompleted(blob_request_.get(), bytes_read);
+  }
+
+  virtual void OnReadCompleted(net::URLRequest* request,
+                               int bytes_read) OVERRIDE {
+    if (!request->status().is_success()) {
+      callback_.Run(entry_.Pass(), false);
+      return;
+    }
+
+    if (bytes_read == 0) {
+      callback_.Run(entry_.Pass(), true);
+      return;
+    }
+
+    net::CompletionCallback cache_write_callback =
+        base::Bind(&BlobReader::DidWriteDataToEntry,
+                   weak_ptr_factory_.GetWeakPtr(),
+                   bytes_read);
+
+    int rv = entry_->WriteData(INDEX_RESPONSE_BODY,
+                               cache_entry_offset_,
+                               buffer_.get(),
+                               bytes_read,
+                               cache_write_callback,
+                               true /* truncate */);
+    if (rv != net::ERR_IO_PENDING)
+      cache_write_callback.Run(rv);
+  }
+
+  void DidWriteDataToEntry(int expected_bytes, int rv) {
+    if (rv != expected_bytes) {
+      callback_.Run(entry_.Pass(), false);
+      return;
+    }
+
+    cache_entry_offset_ += rv;
+    ReadFromBlob();
+  }
+
+ private:
+  int cache_entry_offset_;
+  disk_cache::ScopedEntryPtr entry_;
+  scoped_ptr<net::URLRequest> blob_request_;
+  EntryBoolCallback callback_;
+  scoped_refptr<net::IOBufferWithSize> buffer_;
+  base::WeakPtrFactory<BlobReader> weak_ptr_factory_;
+};
+
+// Put callbacks
+void PutDidCreateEntry(scoped_ptr<ServiceWorkerFetchRequest> request,
+                       scoped_ptr<ServiceWorkerResponse> response,
+                       const ServiceWorkerCache::ErrorCallback& callback,
+                       scoped_ptr<disk_cache::Entry*> entryptr,
+                       scoped_ptr<storage::BlobDataHandle> blob_data_handle,
+                       net::URLRequestContext* request_context,
+                       int rv);
+void PutDidWriteHeaders(scoped_ptr<ServiceWorkerResponse> response,
+                        const ServiceWorkerCache::ErrorCallback& callback,
+                        disk_cache::ScopedEntryPtr entry,
+                        scoped_ptr<storage::BlobDataHandle> blob_data_handle,
+                        net::URLRequestContext* request_context,
+                        int expected_bytes,
+                        int rv);
+void PutDidWriteBlobToCache(const ServiceWorkerCache::ErrorCallback& callback,
+                            scoped_ptr<BlobReader> blob_reader,
+                            disk_cache::ScopedEntryPtr entry,
+                            bool success);
+
+// Match callbacks
+void MatchDidOpenEntry(scoped_ptr<ServiceWorkerFetchRequest> request,
+                       const ServiceWorkerCache::ResponseCallback& callback,
+                       base::WeakPtr<storage::BlobStorageContext> blob_storage,
+                       scoped_ptr<disk_cache::Entry*> entryptr,
+                       int rv);
+void MatchDidReadHeaderData(
+    scoped_ptr<ServiceWorkerFetchRequest> request,
+    const ServiceWorkerCache::ResponseCallback& callback,
+    base::WeakPtr<storage::BlobStorageContext> blob_storage,
+    disk_cache::ScopedEntryPtr entry,
+    scoped_ptr<ServiceWorkerRequestResponseHeaders> headers);
+void MatchDidReadResponseBodyData(
+    scoped_ptr<ServiceWorkerFetchRequest> request,
+    const ServiceWorkerCache::ResponseCallback& callback,
+    base::WeakPtr<storage::BlobStorageContext> blob_storage,
+    disk_cache::ScopedEntryPtr entry,
+    scoped_ptr<ServiceWorkerResponse> response,
+    scoped_ptr<ResponseReadContext> response_context,
+    int rv);
+void MatchDoneWithBody(scoped_ptr<ServiceWorkerFetchRequest> request,
+                       const ServiceWorkerCache::ResponseCallback& callback,
+                       base::WeakPtr<storage::BlobStorageContext> blob_storage,
+                       scoped_ptr<ServiceWorkerResponse> response,
+                       scoped_ptr<ResponseReadContext> response_context);
+
+// Delete callbacks
+void DeleteDidOpenEntry(scoped_ptr<ServiceWorkerFetchRequest> request,
+                        const ServiceWorkerCache::ErrorCallback& callback,
+                        scoped_ptr<disk_cache::Entry*> entryptr,
+                        int rv);
+
+// Copy headers out of a cache entry and into a protobuf. The callback is
+// guaranteed to be run.
+void ReadHeaders(disk_cache::Entry* entry, const HeadersCallback& callback);
+void ReadHeadersDidReadHeaderData(
+    disk_cache::Entry* entry,
+    const HeadersCallback& callback,
+    const scoped_refptr<net::IOBufferWithSize>& buffer,
+    int rv);
+
+// CreateBackend callbacks
+void CreateBackendDidCreate(const ServiceWorkerCache::ErrorCallback& callback,
+                            scoped_ptr<ScopedBackendPtr> backend_ptr,
+                            base::WeakPtr<ServiceWorkerCache> cache,
+                            int rv);
+
+void PutDidCreateEntry(scoped_ptr<ServiceWorkerFetchRequest> request,
+                       scoped_ptr<ServiceWorkerResponse> response,
+                       const ServiceWorkerCache::ErrorCallback& callback,
+                       scoped_ptr<disk_cache::Entry*> entryptr,
+                       scoped_ptr<storage::BlobDataHandle> blob_data_handle,
+                       net::URLRequestContext* request_context,
+                       int rv) {
+  if (rv != net::OK) {
+    callback.Run(ServiceWorkerCache::ErrorTypeExists);
+    return;
+  }
+
+  DCHECK(entryptr);
+  disk_cache::ScopedEntryPtr entry(*entryptr);
+
+  ServiceWorkerRequestResponseHeaders headers;
+  headers.set_method(request->method);
+
+  headers.set_status_code(response->status_code);
+  headers.set_status_text(response->status_text);
+  for (ServiceWorkerHeaderMap::const_iterator it = request->headers.begin();
+       it != request->headers.end();
+       ++it) {
+    ServiceWorkerRequestResponseHeaders::HeaderMap* header_map =
+        headers.add_request_headers();
+    header_map->set_name(it->first);
+    header_map->set_value(it->second);
+  }
+
+  for (ServiceWorkerHeaderMap::const_iterator it = response->headers.begin();
+       it != response->headers.end();
+       ++it) {
+    ServiceWorkerRequestResponseHeaders::HeaderMap* header_map =
+        headers.add_response_headers();
+    header_map->set_name(it->first);
+    header_map->set_value(it->second);
+  }
+
+  scoped_ptr<std::string> serialized(new std::string());
+  if (!headers.SerializeToString(serialized.get())) {
+    callback.Run(ServiceWorkerCache::ErrorTypeStorage);
+    return;
+  }
+
+  scoped_refptr<net::StringIOBuffer> buffer(
+      new net::StringIOBuffer(serialized.Pass()));
+
+  // Get a temporary copy of the entry pointer before passing it in base::Bind.
+  disk_cache::Entry* tmp_entry_ptr = entry.get();
+
+  net::CompletionCallback write_headers_callback =
+      base::Bind(PutDidWriteHeaders,
+                 base::Passed(response.Pass()),
+                 callback,
+                 base::Passed(entry.Pass()),
+                 base::Passed(blob_data_handle.Pass()),
+                 request_context,
+                 buffer->size());
+
+  rv = tmp_entry_ptr->WriteData(INDEX_HEADERS,
+                                0 /* offset */,
+                                buffer.get(),
+                                buffer->size(),
+                                write_headers_callback,
+                                true /* truncate */);
+
+  if (rv != net::ERR_IO_PENDING)
+    write_headers_callback.Run(rv);
+}
+
+void PutDidWriteHeaders(scoped_ptr<ServiceWorkerResponse> response,
+                        const ServiceWorkerCache::ErrorCallback& callback,
+                        disk_cache::ScopedEntryPtr entry,
+                        scoped_ptr<storage::BlobDataHandle> blob_data_handle,
+                        net::URLRequestContext* request_context,
+                        int expected_bytes,
+                        int rv) {
+  if (rv != expected_bytes) {
+    entry->Doom();
+    callback.Run(ServiceWorkerCache::ErrorTypeStorage);
+    return;
+  }
+
+  // The metadata is written, now for the response content. The data is streamed
+  // from the blob into the cache entry.
+
+  if (response->blob_uuid.empty()) {
+    callback.Run(ServiceWorkerCache::ErrorTypeOK);
+    return;
+  }
+
+  DCHECK(blob_data_handle);
+
+  scoped_ptr<BlobReader> reader(new BlobReader(entry.Pass()));
+  BlobReader* reader_ptr = reader.get();
+
+  reader_ptr->StreamBlobToCache(
+      request_context,
+      blob_data_handle.Pass(),
+      base::Bind(
+          PutDidWriteBlobToCache, callback, base::Passed(reader.Pass())));
+}
+
+void PutDidWriteBlobToCache(const ServiceWorkerCache::ErrorCallback& callback,
+                            scoped_ptr<BlobReader> blob_reader,
+                            disk_cache::ScopedEntryPtr entry,
+                            bool success) {
+  if (!success) {
+    entry->Doom();
+    callback.Run(ServiceWorkerCache::ErrorTypeStorage);
+    return;
+  }
+
+  callback.Run(ServiceWorkerCache::ErrorTypeOK);
+}
+
+void MatchDidOpenEntry(scoped_ptr<ServiceWorkerFetchRequest> request,
+                       const ServiceWorkerCache::ResponseCallback& callback,
+                       base::WeakPtr<storage::BlobStorageContext> blob_storage,
+                       scoped_ptr<disk_cache::Entry*> entryptr,
+                       int rv) {
+  if (rv != net::OK) {
+    callback.Run(ServiceWorkerCache::ErrorTypeNotFound,
+                 scoped_ptr<ServiceWorkerResponse>(),
+                 scoped_ptr<storage::BlobDataHandle>());
+    return;
+  }
+
+  DCHECK(entryptr);
+  disk_cache::ScopedEntryPtr entry(*entryptr);
+
+  // Copy the entry pointer before passing it in base::Bind.
+  disk_cache::Entry* tmp_entry_ptr = entry.get();
+
+  HeadersCallback headers_callback = base::Bind(MatchDidReadHeaderData,
+                                                base::Passed(request.Pass()),
+                                                callback,
+                                                blob_storage,
+                                                base::Passed(entry.Pass()));
+
+  ReadHeaders(tmp_entry_ptr, headers_callback);
+}
+
+bool VaryMatches(const ServiceWorkerHeaderMap& request,
+                 const ServiceWorkerHeaderMap& cached_request,
+                 const ServiceWorkerHeaderMap& response) {
+  ServiceWorkerHeaderMap::const_iterator vary_iter = response.find("vary");
+  if (vary_iter == response.end())
+    return true;
+
+  std::vector<std::string> vary_keys;
+  Tokenize(vary_iter->second, ",", &vary_keys);
+  for (std::vector<std::string>::const_iterator it = vary_keys.begin();
+       it != vary_keys.end();
+       ++it) {
+    std::string trimmed;
+    base::TrimWhitespaceASCII(*it, base::TRIM_ALL, &trimmed);
+    if (trimmed == "*")
+      return false;
+
+    ServiceWorkerHeaderMap::const_iterator request_iter = request.find(trimmed);
+    ServiceWorkerHeaderMap::const_iterator cached_request_iter =
+        cached_request.find(trimmed);
+
+    // If the header exists in one but not the other, no match.
+    if ((request_iter == request.end()) !=
+        (cached_request_iter == cached_request.end()))
+      return false;
+
+    // If the header exists in one, it exists in both. Verify that the values
+    // are equal.
+    if (request_iter != request.end() &&
+        request_iter->second != cached_request_iter->second)
+      return false;
+  }
+
+  return true;
+}
+
+void MatchDidReadHeaderData(
+    scoped_ptr<ServiceWorkerFetchRequest> request,
+    const ServiceWorkerCache::ResponseCallback& callback,
+    base::WeakPtr<storage::BlobStorageContext> blob_storage,
+    disk_cache::ScopedEntryPtr entry,
+    scoped_ptr<ServiceWorkerRequestResponseHeaders> headers) {
+  if (!headers) {
+    callback.Run(ServiceWorkerCache::ErrorTypeStorage,
+                 scoped_ptr<ServiceWorkerResponse>(),
+                 scoped_ptr<storage::BlobDataHandle>());
+    return;
+  }
+
+  scoped_ptr<ServiceWorkerResponse> response(
+      new ServiceWorkerResponse(request->url,
+                                headers->status_code(),
+                                headers->status_text(),
+                                ServiceWorkerHeaderMap(),
+                                ""));
+
+  for (int i = 0; i < headers->response_headers_size(); ++i) {
+    const ServiceWorkerRequestResponseHeaders::HeaderMap header =
+        headers->response_headers(i);
+    response->headers.insert(std::make_pair(header.name(), header.value()));
+  }
+
+  ServiceWorkerHeaderMap cached_request_headers;
+  for (int i = 0; i < headers->request_headers_size(); ++i) {
+    const ServiceWorkerRequestResponseHeaders::HeaderMap header =
+        headers->request_headers(i);
+    cached_request_headers[header.name()] = header.value();
+  }
+
+  if (!VaryMatches(
+          request->headers, cached_request_headers, response->headers)) {
+    callback.Run(ServiceWorkerCache::ErrorTypeNotFound,
+                 scoped_ptr<ServiceWorkerResponse>(),
+                 scoped_ptr<storage::BlobDataHandle>());
+    return;
+  }
+
+  if (entry->GetDataSize(INDEX_RESPONSE_BODY) == 0) {
+    callback.Run(ServiceWorkerCache::ErrorTypeOK,
+                 response.Pass(),
+                 scoped_ptr<storage::BlobDataHandle>());
+    return;
+  }
+
+  // Stream the response body into a blob.
+  if (!blob_storage) {
+    callback.Run(ServiceWorkerCache::ErrorTypeStorage,
+                 scoped_ptr<ServiceWorkerResponse>(),
+                 scoped_ptr<storage::BlobDataHandle>());
+    return;
+  }
+
+  response->blob_uuid = base::GenerateGUID();
+
+  scoped_refptr<storage::BlobData> blob_data =
+      new storage::BlobData(response->blob_uuid);
+  scoped_refptr<net::IOBufferWithSize> response_body_buffer(
+      new net::IOBufferWithSize(kBufferSize));
+
+  scoped_ptr<ResponseReadContext> read_context(
+      new ResponseReadContext(response_body_buffer, blob_data));
+
+  // Copy the entry pointer before passing it in base::Bind.
+  disk_cache::Entry* tmp_entry_ptr = entry.get();
+
+  net::CompletionCallback read_callback =
+      base::Bind(MatchDidReadResponseBodyData,
+                 base::Passed(request.Pass()),
+                 callback,
+                 blob_storage,
+                 base::Passed(entry.Pass()),
+                 base::Passed(response.Pass()),
+                 base::Passed(read_context.Pass()));
+
+  int read_rv = tmp_entry_ptr->ReadData(INDEX_RESPONSE_BODY,
+                                        0,
+                                        response_body_buffer.get(),
+                                        response_body_buffer->size(),
+                                        read_callback);
+
+  if (read_rv != net::ERR_IO_PENDING)
+    read_callback.Run(read_rv);
+}
+
+void MatchDidReadResponseBodyData(
+    scoped_ptr<ServiceWorkerFetchRequest> request,
+    const ServiceWorkerCache::ResponseCallback& callback,
+    base::WeakPtr<storage::BlobStorageContext> blob_storage,
+    disk_cache::ScopedEntryPtr entry,
+    scoped_ptr<ServiceWorkerResponse> response,
+    scoped_ptr<ResponseReadContext> response_context,
+    int rv) {
+  if (rv < 0) {
+    callback.Run(ServiceWorkerCache::ErrorTypeStorage,
+                 scoped_ptr<ServiceWorkerResponse>(),
+                 scoped_ptr<storage::BlobDataHandle>());
+    return;
+  }
+
+  if (rv == 0) {
+    MatchDoneWithBody(request.Pass(),
+                      callback,
+                      blob_storage,
+                      response.Pass(),
+                      response_context.Pass());
+    return;
+  }
+
+  // TODO(jkarlin): This copying of the the entire cache response into memory is
+  // awful. Create a new interface around SimpleCache that provides access the
+  // data directly from the file. See bug http://crbug.com/403493.
+  response_context->blob_data->AppendData(response_context->buffer->data(), rv);
+  response_context->total_bytes_read += rv;
+  int total_bytes_read = response_context->total_bytes_read;
+
+  // Grab some pointers before passing them in bind.
+  net::IOBufferWithSize* buffer = response_context->buffer.get();
+  disk_cache::Entry* tmp_entry_ptr = entry.get();
+
+  net::CompletionCallback read_callback =
+      base::Bind(MatchDidReadResponseBodyData,
+                 base::Passed(request.Pass()),
+                 callback,
+                 blob_storage,
+                 base::Passed(entry.Pass()),
+                 base::Passed(response.Pass()),
+                 base::Passed(response_context.Pass()));
+
+  int read_rv = tmp_entry_ptr->ReadData(INDEX_RESPONSE_BODY,
+                                        total_bytes_read,
+                                        buffer,
+                                        buffer->size(),
+                                        read_callback);
+
+  if (read_rv != net::ERR_IO_PENDING)
+    read_callback.Run(read_rv);
+}
+
+void MatchDoneWithBody(scoped_ptr<ServiceWorkerFetchRequest> request,
+                       const ServiceWorkerCache::ResponseCallback& callback,
+                       base::WeakPtr<storage::BlobStorageContext> blob_storage,
+                       scoped_ptr<ServiceWorkerResponse> response,
+                       scoped_ptr<ResponseReadContext> response_context) {
+  if (!blob_storage) {
+    callback.Run(ServiceWorkerCache::ErrorTypeStorage,
+                 scoped_ptr<ServiceWorkerResponse>(),
+                 scoped_ptr<storage::BlobDataHandle>());
+    return;
+  }
+
+  scoped_ptr<storage::BlobDataHandle> blob_data_handle(
+      blob_storage->AddFinishedBlob(response_context->blob_data.get()));
+
+  callback.Run(ServiceWorkerCache::ErrorTypeOK,
+               response.Pass(),
+               blob_data_handle.Pass());
+}
+
+void DeleteDidOpenEntry(scoped_ptr<ServiceWorkerFetchRequest> request,
+                        const ServiceWorkerCache::ErrorCallback& callback,
+                        scoped_ptr<disk_cache::Entry*> entryptr,
+                        int rv) {
+  if (rv != net::OK) {
+    callback.Run(ServiceWorkerCache::ErrorTypeNotFound);
+    return;
+  }
+
+  DCHECK(entryptr);
+  disk_cache::ScopedEntryPtr entry(*entryptr);
+
+  entry->Doom();
+  callback.Run(ServiceWorkerCache::ErrorTypeOK);
+}
+
+void ReadHeaders(disk_cache::Entry* entry, const HeadersCallback& callback) {
+  DCHECK(entry);
+
+  scoped_refptr<net::IOBufferWithSize> buffer(
+      new net::IOBufferWithSize(entry->GetDataSize(INDEX_HEADERS)));
+
+  net::CompletionCallback read_header_callback =
+      base::Bind(ReadHeadersDidReadHeaderData, entry, callback, buffer);
+
+  int read_rv = entry->ReadData(
+      INDEX_HEADERS, 0, buffer.get(), buffer->size(), read_header_callback);
+
+  if (read_rv != net::ERR_IO_PENDING)
+    read_header_callback.Run(read_rv);
+}
+
+void ReadHeadersDidReadHeaderData(
+    disk_cache::Entry* entry,
+    const HeadersCallback& callback,
+    const scoped_refptr<net::IOBufferWithSize>& buffer,
+    int rv) {
+  if (rv != buffer->size()) {
+    callback.Run(scoped_ptr<ServiceWorkerRequestResponseHeaders>());
+    return;
+  }
+
+  scoped_ptr<ServiceWorkerRequestResponseHeaders> headers(
+      new ServiceWorkerRequestResponseHeaders());
+
+  if (!headers->ParseFromArray(buffer->data(), buffer->size())) {
+    callback.Run(scoped_ptr<ServiceWorkerRequestResponseHeaders>());
+    return;
+  }
+
+  callback.Run(headers.Pass());
+}
+
+void CreateBackendDidCreate(const ServiceWorkerCache::ErrorCallback& callback,
+                            scoped_ptr<ScopedBackendPtr> backend_ptr,
+                            base::WeakPtr<ServiceWorkerCache> cache,
+                            int rv) {
+  if (rv != net::OK || !cache) {
+    callback.Run(ServiceWorkerCache::ErrorTypeStorage);
+    return;
+  }
+
+  cache->set_backend(backend_ptr->Pass());
+  callback.Run(ServiceWorkerCache::ErrorTypeOK);
+}
+
+}  // namespace
+
+// The state needed to pass between ServiceWorkerCache::Keys callbacks.
+struct ServiceWorkerCache::KeysContext {
+  KeysContext(const ServiceWorkerCache::RequestsCallback& callback,
+              base::WeakPtr<ServiceWorkerCache> cache)
+      : original_callback(callback),
+        cache(cache),
+        out_keys(new ServiceWorkerCache::Requests()),
+        enumerated_entry(NULL) {}
+
+  ~KeysContext() {
+    for (size_t i = 0, max = entries.size(); i < max; ++i)
+      entries[i]->Close();
+    if (enumerated_entry)
+      enumerated_entry->Close();
+  }
+
+  // The callback passed to the Keys() function.
+  ServiceWorkerCache::RequestsCallback original_callback;
+
+  // The ServiceWorkerCache that Keys was called on.
+  base::WeakPtr<ServiceWorkerCache> cache;
+
+  // The vector of open entries in the backend.
+  Entries entries;
+
+  // The output of the Keys function.
+  scoped_ptr<ServiceWorkerCache::Requests> out_keys;
+
+  // Used for enumerating cache entries.
+  scoped_ptr<disk_cache::Backend::Iterator> backend_iterator;
+  disk_cache::Entry* enumerated_entry;
+};
+
 // static
-scoped_ptr<ServiceWorkerCache> ServiceWorkerCache::CreateMemoryCache(
-    const std::string& name,
+scoped_refptr<ServiceWorkerCache> ServiceWorkerCache::CreateMemoryCache(
     net::URLRequestContext* request_context,
-    base::WeakPtr<webkit_blob::BlobStorageContext> blob_context) {
-  return make_scoped_ptr(new ServiceWorkerCache(
-      base::FilePath(), name, request_context, blob_context));
+    base::WeakPtr<storage::BlobStorageContext> blob_context) {
+  return make_scoped_refptr(
+      new ServiceWorkerCache(base::FilePath(), request_context, blob_context));
 }
 
 // static
-scoped_ptr<ServiceWorkerCache> ServiceWorkerCache::CreatePersistentCache(
+scoped_refptr<ServiceWorkerCache> ServiceWorkerCache::CreatePersistentCache(
     const base::FilePath& path,
-    const std::string& name,
     net::URLRequestContext* request_context,
-    base::WeakPtr<webkit_blob::BlobStorageContext> blob_context) {
-  return make_scoped_ptr(
-      new ServiceWorkerCache(path, name, request_context, blob_context));
+    base::WeakPtr<storage::BlobStorageContext> blob_context) {
+  return make_scoped_refptr(
+      new ServiceWorkerCache(path, request_context, blob_context));
 }
 
-void ServiceWorkerCache::CreateBackend(
-    const base::Callback<void(bool)>& callback) {
-  callback.Run(true);
+ServiceWorkerCache::~ServiceWorkerCache() {
 }
 
 base::WeakPtr<ServiceWorkerCache> ServiceWorkerCache::AsWeakPtr() {
   return weak_ptr_factory_.GetWeakPtr();
 }
 
+void ServiceWorkerCache::Put(scoped_ptr<ServiceWorkerFetchRequest> request,
+                             scoped_ptr<ServiceWorkerResponse> response,
+                             const ErrorCallback& callback) {
+  scoped_ptr<storage::BlobDataHandle> blob_data_handle;
+
+  if (!response->blob_uuid.empty()) {
+    if (!blob_storage_context_) {
+      callback.Run(ErrorTypeStorage);
+      return;
+    }
+    blob_data_handle =
+        blob_storage_context_->GetBlobDataFromUUID(response->blob_uuid);
+    if (!blob_data_handle) {
+      callback.Run(ErrorTypeStorage);
+      return;
+    }
+  }
+
+  base::Closure continuation = base::Bind(&ServiceWorkerCache::PutImpl,
+                                          weak_ptr_factory_.GetWeakPtr(),
+                                          base::Passed(request.Pass()),
+                                          base::Passed(response.Pass()),
+                                          base::Passed(blob_data_handle.Pass()),
+                                          callback);
+
+  if (!initialized_) {
+    Init(continuation);
+    return;
+  }
+
+  continuation.Run();
+}
+
+void ServiceWorkerCache::Match(scoped_ptr<ServiceWorkerFetchRequest> request,
+                               const ResponseCallback& callback) {
+  if (!initialized_) {
+    Init(base::Bind(&ServiceWorkerCache::Match,
+                    weak_ptr_factory_.GetWeakPtr(),
+                    base::Passed(request.Pass()),
+                    callback));
+    return;
+  }
+  if (!backend_) {
+    callback.Run(ErrorTypeStorage,
+                 scoped_ptr<ServiceWorkerResponse>(),
+                 scoped_ptr<storage::BlobDataHandle>());
+    return;
+  }
+
+  scoped_ptr<disk_cache::Entry*> entry(new disk_cache::Entry*);
+
+  disk_cache::Entry** entry_ptr = entry.get();
+
+  ServiceWorkerFetchRequest* request_ptr = request.get();
+
+  net::CompletionCallback open_entry_callback =
+      base::Bind(MatchDidOpenEntry,
+                 base::Passed(request.Pass()),
+                 callback,
+                 blob_storage_context_,
+                 base::Passed(entry.Pass()));
+
+  int rv = backend_->OpenEntry(
+      request_ptr->url.spec(), entry_ptr, open_entry_callback);
+  if (rv != net::ERR_IO_PENDING)
+    open_entry_callback.Run(rv);
+}
+
+void ServiceWorkerCache::Delete(scoped_ptr<ServiceWorkerFetchRequest> request,
+                                const ErrorCallback& callback) {
+  if (!initialized_) {
+    Init(base::Bind(&ServiceWorkerCache::Delete,
+                    weak_ptr_factory_.GetWeakPtr(),
+                    base::Passed(request.Pass()),
+                    callback));
+    return;
+  }
+  if (!backend_) {
+    callback.Run(ErrorTypeStorage);
+    return;
+  }
+
+  scoped_ptr<disk_cache::Entry*> entry(new disk_cache::Entry*);
+
+  disk_cache::Entry** entry_ptr = entry.get();
+
+  ServiceWorkerFetchRequest* request_ptr = request.get();
+
+  net::CompletionCallback open_entry_callback =
+      base::Bind(DeleteDidOpenEntry,
+                 base::Passed(request.Pass()),
+                 callback,
+                 base::Passed(entry.Pass()));
+
+  int rv = backend_->OpenEntry(
+      request_ptr->url.spec(), entry_ptr, open_entry_callback);
+  if (rv != net::ERR_IO_PENDING)
+    open_entry_callback.Run(rv);
+}
+
+void ServiceWorkerCache::Keys(const RequestsCallback& callback) {
+  if (!initialized_) {
+    Init(base::Bind(
+        &ServiceWorkerCache::Keys, weak_ptr_factory_.GetWeakPtr(), callback));
+    return;
+  }
+  if (!backend_) {
+    callback.Run(ErrorTypeStorage, scoped_ptr<Requests>());
+    return;
+  }
+
+  // 1. Iterate through all of the entries, open them, and add them to a vector.
+  // 2. For each open entry:
+  //  2.1. Read the headers into a protobuf.
+  //  2.2. Copy the protobuf into a ServiceWorkerFetchRequest (a "key").
+  //  2.3. Push the response into a vector of requests to be returned.
+  // 3. Return the vector of requests (keys).
+
+  // The entries have to be loaded into a vector first because enumeration loops
+  // forever if you read data from a cache entry while enumerating.
+
+  scoped_ptr<KeysContext> keys_context(
+      new KeysContext(callback, weak_ptr_factory_.GetWeakPtr()));
+
+  keys_context->backend_iterator = backend_->CreateIterator();
+  disk_cache::Backend::Iterator& iterator = *keys_context->backend_iterator;
+  disk_cache::Entry** enumerated_entry = &keys_context->enumerated_entry;
+
+  net::CompletionCallback open_entry_callback =
+      base::Bind(KeysDidOpenNextEntry, base::Passed(keys_context.Pass()));
+
+  int rv = iterator.OpenNextEntry(enumerated_entry, open_entry_callback);
+
+  if (rv != net::ERR_IO_PENDING)
+    open_entry_callback.Run(rv);
+}
+
+void ServiceWorkerCache::Close() {
+  backend_.reset();
+}
+
 ServiceWorkerCache::ServiceWorkerCache(
     const base::FilePath& path,
-    const std::string& name,
     net::URLRequestContext* request_context,
-    base::WeakPtr<webkit_blob::BlobStorageContext> blob_context)
+    base::WeakPtr<storage::BlobStorageContext> blob_context)
     : path_(path),
-      name_(name),
       request_context_(request_context),
       blob_storage_context_(blob_context),
-      id_(0),
+      initialized_(false),
       weak_ptr_factory_(this) {
 }
 
-ServiceWorkerCache::~ServiceWorkerCache() {
+void ServiceWorkerCache::PutImpl(
+    scoped_ptr<ServiceWorkerFetchRequest> request,
+    scoped_ptr<ServiceWorkerResponse> response,
+    scoped_ptr<storage::BlobDataHandle> blob_data_handle,
+    const ErrorCallback& callback) {
+  if (!backend_) {
+    callback.Run(ErrorTypeStorage);
+    return;
+  }
+
+  scoped_ptr<disk_cache::Entry*> entry(new disk_cache::Entry*);
+
+  disk_cache::Entry** entry_ptr = entry.get();
+
+  ServiceWorkerFetchRequest* request_ptr = request.get();
+
+  net::CompletionCallback create_entry_callback =
+      base::Bind(PutDidCreateEntry,
+                 base::Passed(request.Pass()),
+                 base::Passed(response.Pass()),
+                 callback,
+                 base::Passed(entry.Pass()),
+                 base::Passed(blob_data_handle.Pass()),
+                 request_context_);
+
+  int rv = backend_->CreateEntry(
+      request_ptr->url.spec(), entry_ptr, create_entry_callback);
+
+  if (rv != net::ERR_IO_PENDING)
+    create_entry_callback.Run(rv);
+}
+
+// static
+void ServiceWorkerCache::KeysDidOpenNextEntry(
+    scoped_ptr<KeysContext> keys_context,
+    int rv) {
+  if (rv == net::ERR_FAILED) {
+    DCHECK(!keys_context->enumerated_entry);
+    // Enumeration is complete, extract the requests from the entries.
+    Entries::iterator iter = keys_context->entries.begin();
+    KeysProcessNextEntry(keys_context.Pass(), iter);
+    return;
+  }
+
+  base::WeakPtr<ServiceWorkerCache> cache = keys_context->cache;
+  if (rv < 0 || !cache) {
+    keys_context->original_callback.Run(ErrorTypeStorage,
+                                        scoped_ptr<Requests>());
+    return;
+  }
+
+  if (!cache->backend_) {
+    keys_context->original_callback.Run(ErrorTypeNotFound,
+                                        scoped_ptr<Requests>());
+    return;
+  }
+
+  // Store the entry.
+  keys_context->entries.push_back(keys_context->enumerated_entry);
+  keys_context->enumerated_entry = NULL;
+
+  // Enumerate the next entry.
+  disk_cache::Backend::Iterator& iterator = *keys_context->backend_iterator;
+  disk_cache::Entry** enumerated_entry = &keys_context->enumerated_entry;
+  net::CompletionCallback open_entry_callback =
+      base::Bind(KeysDidOpenNextEntry, base::Passed(keys_context.Pass()));
+
+  rv = iterator.OpenNextEntry(enumerated_entry, open_entry_callback);
+
+  if (rv != net::ERR_IO_PENDING)
+    open_entry_callback.Run(rv);
+}
+
+// static
+void ServiceWorkerCache::KeysProcessNextEntry(
+    scoped_ptr<KeysContext> keys_context,
+    const Entries::iterator& iter) {
+  if (iter == keys_context->entries.end()) {
+    // All done. Return all of the keys.
+    keys_context->original_callback.Run(ErrorTypeOK,
+                                        keys_context->out_keys.Pass());
+    return;
+  }
+
+  ReadHeaders(
+      *iter,
+      base::Bind(KeysDidReadHeaders, base::Passed(keys_context.Pass()), iter));
+}
+
+// static
+void ServiceWorkerCache::KeysDidReadHeaders(
+    scoped_ptr<KeysContext> keys_context,
+    const Entries::iterator& iter,
+    scoped_ptr<ServiceWorkerRequestResponseHeaders> headers) {
+  disk_cache::Entry* entry = *iter;
+
+  if (headers) {
+    keys_context->out_keys->push_back(
+        ServiceWorkerFetchRequest(GURL(entry->GetKey()),
+                                  headers->method(),
+                                  ServiceWorkerHeaderMap(),
+                                  GURL(),
+                                  false));
+
+    ServiceWorkerHeaderMap& req_headers =
+        keys_context->out_keys->back().headers;
+
+    for (int i = 0; i < headers->request_headers_size(); ++i) {
+      const ServiceWorkerRequestResponseHeaders::HeaderMap header =
+          headers->request_headers(i);
+      req_headers.insert(std::make_pair(header.name(), header.value()));
+    }
+  } else {
+    entry->Doom();
+  }
+
+  KeysProcessNextEntry(keys_context.Pass(), iter + 1);
+}
+
+void ServiceWorkerCache::CreateBackend(const ErrorCallback& callback) {
+  DCHECK(!backend_);
+
+  // Use APP_CACHE as opposed to DISK_CACHE to prevent cache eviction.
+  net::CacheType cache_type =
+      path_.empty() ? net::MEMORY_CACHE : net::APP_CACHE;
+
+  scoped_ptr<ScopedBackendPtr> backend_ptr(new ScopedBackendPtr());
+
+  // Temporary pointer so that backend_ptr can be Pass()'d in Bind below.
+  ScopedBackendPtr* backend = backend_ptr.get();
+
+  net::CompletionCallback create_cache_callback =
+      base::Bind(CreateBackendDidCreate,
+                 callback,
+                 base::Passed(backend_ptr.Pass()),
+                 weak_ptr_factory_.GetWeakPtr());
+
+  // TODO(jkarlin): Use the cache MessageLoopProxy that ServiceWorkerCacheCore
+  // has for disk caches.
+  int rv = disk_cache::CreateCacheBackend(
+      cache_type,
+      net::CACHE_BACKEND_SIMPLE,
+      path_,
+      kMaxCacheBytes,
+      false, /* force */
+      BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(),
+      NULL,
+      backend,
+      create_cache_callback);
+  if (rv != net::ERR_IO_PENDING)
+    create_cache_callback.Run(rv);
+}
+
+void ServiceWorkerCache::Init(const base::Closure& callback) {
+  init_callbacks_.push_back(callback);
+
+  // If this isn't the first call to Init then return as the initialization
+  // has already started.
+  if (init_callbacks_.size() > 1u)
+    return;
+
+  CreateBackend(base::Bind(&ServiceWorkerCache::InitDone,
+                           weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ServiceWorkerCache::InitDone(ErrorType error) {
+  initialized_ = true;
+  for (std::vector<base::Closure>::iterator it = init_callbacks_.begin();
+       it != init_callbacks_.end();
+       ++it) {
+    it->Run();
+  }
+  init_callbacks_.clear();
 }
 
 }  // namespace content