summaryrefslogtreecommitdiff
path: root/chromium/webkit/browser/blob
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/webkit/browser/blob')
-rw-r--r--chromium/webkit/browser/blob/OWNERS1
-rw-r--r--chromium/webkit/browser/blob/blob_data_handle.cc56
-rw-r--r--chromium/webkit/browser/blob/blob_data_handle.h50
-rw-r--r--chromium/webkit/browser/blob/blob_storage_context.cc322
-rw-r--r--chromium/webkit/browser/blob/blob_storage_context.h110
-rw-r--r--chromium/webkit/browser/blob/blob_storage_context_unittest.cc206
-rw-r--r--chromium/webkit/browser/blob/blob_storage_controller.cc257
-rw-r--r--chromium/webkit/browser/blob/blob_storage_controller.h82
-rw-r--r--chromium/webkit/browser/blob/blob_storage_controller_unittest.cc77
-rw-r--r--chromium/webkit/browser/blob/blob_storage_host.cc113
-rw-r--r--chromium/webkit/browser/blob/blob_storage_host.h69
-rw-r--r--chromium/webkit/browser/blob/blob_url_request_job.cc572
-rw-r--r--chromium/webkit/browser/blob/blob_url_request_job.h131
-rw-r--r--chromium/webkit/browser/blob/blob_url_request_job_factory.cc51
-rw-r--r--chromium/webkit/browser/blob/blob_url_request_job_factory.h58
-rw-r--r--chromium/webkit/browser/blob/blob_url_request_job_unittest.cc451
-rw-r--r--chromium/webkit/browser/blob/file_stream_reader.h56
-rw-r--r--chromium/webkit/browser/blob/local_file_stream_reader.cc169
-rw-r--r--chromium/webkit/browser/blob/local_file_stream_reader.h84
-rw-r--r--chromium/webkit/browser/blob/local_file_stream_reader_unittest.cc262
-rw-r--r--chromium/webkit/browser/blob/mock_blob_url_request_context.cc74
-rw-r--r--chromium/webkit/browser/blob/mock_blob_url_request_context.h52
-rw-r--r--chromium/webkit/browser/blob/view_blob_internals_job.cc248
-rw-r--r--chromium/webkit/browser/blob/view_blob_internals_job.h57
24 files changed, 3608 insertions, 0 deletions
diff --git a/chromium/webkit/browser/blob/OWNERS b/chromium/webkit/browser/blob/OWNERS
new file mode 100644
index 00000000000..c7e8dcb8b5b
--- /dev/null
+++ b/chromium/webkit/browser/blob/OWNERS
@@ -0,0 +1 @@
+jianli@chromium.org
diff --git a/chromium/webkit/browser/blob/blob_data_handle.cc b/chromium/webkit/browser/blob/blob_data_handle.cc
new file mode 100644
index 00000000000..fd0ae54db73
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_data_handle.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/blob/blob_data_handle.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+#include "webkit/browser/blob/blob_storage_context.h"
+#include "webkit/common/blob/blob_data.h"
+
+namespace webkit_blob {
+
+BlobDataHandle::BlobDataHandle(BlobData* blob_data, BlobStorageContext* context,
+ base::SequencedTaskRunner* task_runner)
+ : blob_data_(blob_data),
+ context_(context->AsWeakPtr()),
+ io_task_runner_(task_runner) {
+ // Ensures the uuid remains registered and the underlying data is not deleted.
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ context_->IncrementBlobRefCount(blob_data->uuid());
+ blob_data_->AddRef();
+}
+
+BlobDataHandle::~BlobDataHandle() {
+ if (io_task_runner_->RunsTasksOnCurrentThread()) {
+ // Note: Do not test context_ or alter the blob_data_ refcount
+ // on the wrong thread.
+ if (context_.get())
+ context_->DecrementBlobRefCount(blob_data_->uuid());
+ blob_data_->Release();
+ return;
+ }
+
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&DeleteHelper, context_, base::Unretained(blob_data_)));
+}
+
+BlobData* BlobDataHandle::data() const {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ return blob_data_;
+}
+
+// static
+void BlobDataHandle::DeleteHelper(
+ base::WeakPtr<BlobStorageContext> context,
+ BlobData* blob_data) {
+ if (context.get())
+ context->DecrementBlobRefCount(blob_data->uuid());
+ blob_data->Release();
+}
+
+} // namespace webkit_blob
diff --git a/chromium/webkit/browser/blob/blob_data_handle.h b/chromium/webkit/browser/blob/blob_data_handle.h
new file mode 100644
index 00000000000..25eeefe259f
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_data_handle.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_BLOB_BLOB_DATA_HANDLE_H_
+#define WEBKIT_BROWSER_BLOB_BLOB_DATA_HANDLE_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/supports_user_data.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace webkit_blob {
+
+class BlobData;
+class BlobStorageContext;
+
+// A scoper object for use in chrome's main browser process, ensures
+// the underlying BlobData and its uuid remain in BlobStorageContext's
+// collection for the duration. This object has delete semantics and
+// maybe deleted on any thread.
+class WEBKIT_STORAGE_BROWSER_EXPORT BlobDataHandle
+ : public base::SupportsUserData::Data {
+ public:
+ virtual ~BlobDataHandle(); // Maybe be deleted on any thread.
+ BlobData* data() const; // May only be accessed on the IO thread.
+
+ private:
+ friend class BlobStorageContext;
+ BlobDataHandle(BlobData* blob_data, BlobStorageContext* context,
+ base::SequencedTaskRunner* task_runner);
+
+ static void DeleteHelper(
+ base::WeakPtr<BlobStorageContext> context,
+ BlobData* blob_data);
+
+ BlobData* blob_data_; // Intentionally a raw ptr to a non-thread-safe ref.
+ base::WeakPtr<BlobStorageContext> context_;
+ scoped_refptr<base::SequencedTaskRunner> io_task_runner_;
+};
+
+} // namespace webkit_blob
+
+#endif // WEBKIT_BROWSER_BLOB_BLOB_DATA_HANDLE_H_
diff --git a/chromium/webkit/browser/blob/blob_storage_context.cc b/chromium/webkit/browser/blob/blob_storage_context.cc
new file mode 100644
index 00000000000..4a0260736a0
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_storage_context.cc
@@ -0,0 +1,322 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/blob/blob_storage_context.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "url/gurl.h"
+#include "webkit/browser/blob/blob_data_handle.h"
+#include "webkit/common/blob/blob_data.h"
+
+namespace webkit_blob {
+
+namespace {
+
+// We can't use GURL directly for these hash fragment manipulations
+// since it doesn't have specific knowlege of the BlobURL format. GURL
+// treats BlobURLs as if they were PathURLs which don't support hash
+// fragments.
+
+bool BlobUrlHasRef(const GURL& url) {
+ return url.spec().find('#') != std::string::npos;
+}
+
+GURL ClearBlobUrlRef(const GURL& url) {
+ size_t hash_pos = url.spec().find('#');
+ if (hash_pos == std::string::npos)
+ return url;
+ return GURL(url.spec().substr(0, hash_pos));
+}
+
+// TODO(michaeln): use base::SysInfo::AmountOfPhysicalMemoryMB() in some
+// way to come up with a better limit.
+static const int64 kMaxMemoryUsage = 500 * 1024 * 1024; // Half a gig.
+
+} // namespace
+
+BlobStorageContext::BlobMapEntry::BlobMapEntry()
+ : refcount(0), flags(0) {
+}
+
+BlobStorageContext::BlobMapEntry::BlobMapEntry(
+ int refcount, int flags, BlobData* data)
+ : refcount(refcount), flags(flags), data(data) {
+}
+
+BlobStorageContext::BlobMapEntry::~BlobMapEntry() {
+}
+
+BlobStorageContext::BlobStorageContext()
+ : memory_usage_(0) {
+}
+
+BlobStorageContext::~BlobStorageContext() {
+}
+
+scoped_ptr<BlobDataHandle> BlobStorageContext::GetBlobDataFromUUID(
+ const std::string& uuid) {
+ scoped_ptr<BlobDataHandle> result;
+ BlobMap::iterator found = blob_map_.find(uuid);
+ if (found == blob_map_.end())
+ return result.Pass();
+ if (found->second.flags & EXCEEDED_MEMORY)
+ return result.Pass();
+ DCHECK(!(found->second.flags & BEING_BUILT));
+ result.reset(new BlobDataHandle(
+ found->second.data.get(), this, base::MessageLoopProxy::current().get()));
+ return result.Pass();
+}
+
+scoped_ptr<BlobDataHandle> BlobStorageContext::GetBlobDataFromPublicURL(
+ const GURL& url) {
+ BlobURLMap::iterator found = public_blob_urls_.find(
+ BlobUrlHasRef(url) ? ClearBlobUrlRef(url) : url);
+ if (found == public_blob_urls_.end())
+ return scoped_ptr<BlobDataHandle>();
+ return GetBlobDataFromUUID(found->second);
+}
+
+scoped_ptr<BlobDataHandle> BlobStorageContext::AddFinishedBlob(
+ const BlobData* data) {
+ StartBuildingBlob(data->uuid());
+ for (std::vector<BlobData::Item>::const_iterator iter =
+ data->items().begin();
+ iter != data->items().end(); ++iter) {
+ AppendBlobDataItem(data->uuid(), *iter);
+ }
+ FinishBuildingBlob(data->uuid(), data->content_type());
+ scoped_ptr<BlobDataHandle> handle = GetBlobDataFromUUID(data->uuid());
+ DecrementBlobRefCount(data->uuid());
+ return handle.Pass();
+}
+
+void BlobStorageContext::StartBuildingBlob(const std::string& uuid) {
+ DCHECK(!IsInUse(uuid) && !uuid.empty());
+ blob_map_[uuid] = BlobMapEntry(1, BEING_BUILT, new BlobData(uuid));
+}
+
+void BlobStorageContext::AppendBlobDataItem(
+ const std::string& uuid, const BlobData::Item& item) {
+ DCHECK(IsBeingBuilt(uuid));
+ BlobMap::iterator found = blob_map_.find(uuid);
+ if (found == blob_map_.end())
+ return;
+ if (found->second.flags & EXCEEDED_MEMORY)
+ return;
+ BlobData* target_blob_data = found->second.data.get();
+ DCHECK(target_blob_data);
+
+ bool exceeded_memory = false;
+
+ // The blob data is stored in the canonical way which only contains a
+ // list of Data, File, and FileSystem items. Aggregated TYPE_BLOB items
+ // are expanded into the primitive constituent types.
+ // 1) The Data item is denoted by the raw data and length.
+ // 2) The File item is denoted by the file path, the range and the expected
+ // modification time.
+ // 3) The FileSystem File item is denoted by the FileSystem URL, the range
+ // and the expected modification time.
+ // 4) The Blob items are expanded.
+ // TODO(michaeln): Would be nice to avoid copying Data items when expanding.
+
+ DCHECK(item.length() > 0);
+ switch (item.type()) {
+ case BlobData::Item::TYPE_BYTES:
+ DCHECK(!item.offset());
+ exceeded_memory = !AppendBytesItem(target_blob_data,
+ item.bytes(),
+ static_cast<int64>(item.length()));
+ break;
+ case BlobData::Item::TYPE_FILE:
+ AppendFileItem(target_blob_data,
+ item.path(),
+ item.offset(),
+ item.length(),
+ item.expected_modification_time());
+ break;
+ case BlobData::Item::TYPE_FILE_FILESYSTEM:
+ AppendFileSystemFileItem(target_blob_data,
+ item.filesystem_url(),
+ item.offset(),
+ item.length(),
+ item.expected_modification_time());
+ break;
+ case BlobData::Item::TYPE_BLOB: {
+ scoped_ptr<BlobDataHandle> src = GetBlobDataFromUUID(item.blob_uuid());
+ if (src)
+ exceeded_memory = !ExpandStorageItems(target_blob_data,
+ src->data(),
+ item.offset(),
+ item.length());
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ // If we're using too much memory, drop this blob's data.
+ // TODO(michaeln): Blob memory storage does not yet spill over to disk,
+ // as a stop gap, we'll prevent memory usage over a max amount.
+ if (exceeded_memory) {
+ memory_usage_ -= target_blob_data->GetMemoryUsage();
+ found->second.flags |= EXCEEDED_MEMORY;
+ found->second.data = new BlobData(uuid);
+ return;
+ }
+}
+
+void BlobStorageContext::FinishBuildingBlob(
+ const std::string& uuid, const std::string& content_type) {
+ DCHECK(IsBeingBuilt(uuid));
+ BlobMap::iterator found = blob_map_.find(uuid);
+ if (found == blob_map_.end())
+ return;
+ found->second.data->set_content_type(content_type);
+ found->second.flags &= ~BEING_BUILT;
+}
+
+void BlobStorageContext::CancelBuildingBlob(const std::string& uuid) {
+ DCHECK(IsBeingBuilt(uuid));
+ DecrementBlobRefCount(uuid);
+}
+
+void BlobStorageContext::IncrementBlobRefCount(const std::string& uuid) {
+ BlobMap::iterator found = blob_map_.find(uuid);
+ if (found == blob_map_.end()) {
+ DCHECK(false);
+ return;
+ }
+ ++(found->second.refcount);
+}
+
+void BlobStorageContext::DecrementBlobRefCount(const std::string& uuid) {
+ BlobMap::iterator found = blob_map_.find(uuid);
+ if (found == blob_map_.end())
+ return;
+ DCHECK_EQ(found->second.data->uuid(), uuid);
+ if (--(found->second.refcount) == 0) {
+ memory_usage_ -= found->second.data->GetMemoryUsage();
+ blob_map_.erase(found);
+ }
+}
+
+void BlobStorageContext::RegisterPublicBlobURL(
+ const GURL& blob_url, const std::string& uuid) {
+ DCHECK(!BlobUrlHasRef(blob_url));
+ DCHECK(IsInUse(uuid));
+ DCHECK(!IsUrlRegistered(blob_url));
+ IncrementBlobRefCount(uuid);
+ public_blob_urls_[blob_url] = uuid;
+}
+
+void BlobStorageContext::RevokePublicBlobURL(const GURL& blob_url) {
+ DCHECK(!BlobUrlHasRef(blob_url));
+ if (!IsUrlRegistered(blob_url))
+ return;
+ DecrementBlobRefCount(public_blob_urls_[blob_url]);
+ public_blob_urls_.erase(blob_url);
+}
+
+bool BlobStorageContext::ExpandStorageItems(
+ BlobData* target_blob_data, BlobData* src_blob_data,
+ uint64 offset, uint64 length) {
+ DCHECK(target_blob_data && src_blob_data &&
+ length != static_cast<uint64>(-1));
+
+ std::vector<BlobData::Item>::const_iterator iter =
+ src_blob_data->items().begin();
+ if (offset) {
+ for (; iter != src_blob_data->items().end(); ++iter) {
+ if (offset >= iter->length())
+ offset -= iter->length();
+ else
+ break;
+ }
+ }
+
+ for (; iter != src_blob_data->items().end() && length > 0; ++iter) {
+ uint64 current_length = iter->length() - offset;
+ uint64 new_length = current_length > length ? length : current_length;
+ if (iter->type() == BlobData::Item::TYPE_BYTES) {
+ if (!AppendBytesItem(
+ target_blob_data,
+ iter->bytes() + static_cast<size_t>(iter->offset() + offset),
+ static_cast<int64>(new_length))) {
+ return false; // exceeded memory
+ }
+ } else if (iter->type() == BlobData::Item::TYPE_FILE) {
+ AppendFileItem(target_blob_data,
+ iter->path(),
+ iter->offset() + offset,
+ new_length,
+ iter->expected_modification_time());
+ } else {
+ DCHECK(iter->type() == BlobData::Item::TYPE_FILE_FILESYSTEM);
+ AppendFileSystemFileItem(target_blob_data,
+ iter->filesystem_url(),
+ iter->offset() + offset,
+ new_length,
+ iter->expected_modification_time());
+ }
+ length -= new_length;
+ offset = 0;
+ }
+ return true;
+}
+
+bool BlobStorageContext::AppendBytesItem(
+ BlobData* target_blob_data, const char* bytes, int64 length) {
+ if (length < 0) {
+ DCHECK(false);
+ return false;
+ }
+ if (memory_usage_ + length > kMaxMemoryUsage)
+ return false;
+ target_blob_data->AppendData(bytes, static_cast<size_t>(length));
+ memory_usage_ += length;
+ return true;
+}
+
+void BlobStorageContext::AppendFileItem(
+ BlobData* target_blob_data,
+ const base::FilePath& file_path, uint64 offset, uint64 length,
+ const base::Time& expected_modification_time) {
+ target_blob_data->AppendFile(file_path, offset, length,
+ expected_modification_time);
+
+ // It may be a temporary file that should be deleted when no longer needed.
+ scoped_refptr<ShareableFileReference> shareable_file =
+ ShareableFileReference::Get(file_path);
+ if (shareable_file.get())
+ target_blob_data->AttachShareableFileReference(shareable_file.get());
+}
+
+void BlobStorageContext::AppendFileSystemFileItem(
+ BlobData* target_blob_data,
+ const GURL& filesystem_url, uint64 offset, uint64 length,
+ const base::Time& expected_modification_time) {
+ target_blob_data->AppendFileSystemFile(filesystem_url, offset, length,
+ expected_modification_time);
+}
+
+bool BlobStorageContext::IsInUse(const std::string& uuid) {
+ return blob_map_.find(uuid) != blob_map_.end();
+}
+
+bool BlobStorageContext::IsBeingBuilt(const std::string& uuid) {
+ BlobMap::iterator found = blob_map_.find(uuid);
+ if (found == blob_map_.end())
+ return false;
+ return found->second.flags & BEING_BUILT;
+}
+
+bool BlobStorageContext::IsUrlRegistered(const GURL& blob_url) {
+ return public_blob_urls_.find(blob_url) != public_blob_urls_.end();
+}
+
+} // namespace webkit_blob
diff --git a/chromium/webkit/browser/blob/blob_storage_context.h b/chromium/webkit/browser/blob/blob_storage_context.h
new file mode 100644
index 00000000000..c9747aea3ed
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_storage_context.h
@@ -0,0 +1,110 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_BLOB_BLOB_STORAGE_CONTEXT_H_
+#define WEBKIT_BROWSER_BLOB_BLOB_STORAGE_CONTEXT_H_
+
+#include <map>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/blob/blob_data.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+class Time;
+}
+
+namespace webkit_blob {
+
+class BlobDataHandle;
+
+// This class handles the logistics of blob Storage within the browser process,
+// and maintains a mapping from blob uuid to the data. The class is single
+// threaded and should only be used on the IO thread.
+// In chromium, there is one instance per profile.
+class WEBKIT_STORAGE_BROWSER_EXPORT BlobStorageContext
+ : public base::SupportsWeakPtr<BlobStorageContext> {
+ public:
+ BlobStorageContext();
+ ~BlobStorageContext();
+
+ scoped_ptr<BlobDataHandle> GetBlobDataFromUUID(const std::string& uuid);
+ scoped_ptr<BlobDataHandle> GetBlobDataFromPublicURL(const GURL& url);
+
+ // Useful for coining blobs from within the browser process. If the
+ // blob cannot be added due to memory consumption, returns NULL.
+ scoped_ptr<BlobDataHandle> AddFinishedBlob(const BlobData* blob_data);
+
+ private:
+ friend class BlobDataHandle;
+ friend class BlobStorageHost;
+
+ enum EntryFlags {
+ BEING_BUILT = 1 << 0,
+ EXCEEDED_MEMORY = 1 << 1,
+ };
+
+ struct BlobMapEntry {
+ int refcount;
+ int flags;
+ scoped_refptr<BlobData> data;
+
+ BlobMapEntry();
+ BlobMapEntry(int refcount, int flags, BlobData* data);
+ ~BlobMapEntry();
+ };
+
+ typedef std::map<std::string, BlobMapEntry>
+ BlobMap;
+ typedef std::map<GURL, std::string> BlobURLMap;
+
+ void StartBuildingBlob(const std::string& uuid);
+ void AppendBlobDataItem(const std::string& uuid,
+ const BlobData::Item& data_item);
+ void FinishBuildingBlob(const std::string& uuid, const std::string& type);
+ void CancelBuildingBlob(const std::string& uuid);
+ void IncrementBlobRefCount(const std::string& uuid);
+ void DecrementBlobRefCount(const std::string& uuid);
+ void RegisterPublicBlobURL(const GURL& url, const std::string& uuid);
+ void RevokePublicBlobURL(const GURL& url);
+
+ bool ExpandStorageItems(BlobData* target_blob_data,
+ BlobData* src_blob_data,
+ uint64 offset,
+ uint64 length);
+ bool AppendBytesItem(BlobData* target_blob_data,
+ const char* data, int64 length);
+ void AppendFileItem(BlobData* target_blob_data,
+ const base::FilePath& file_path,
+ uint64 offset, uint64 length,
+ const base::Time& expected_modification_time);
+ void AppendFileSystemFileItem(
+ BlobData* target_blob_data,
+ const GURL& url, uint64 offset, uint64 length,
+ const base::Time& expected_modification_time);
+
+ bool IsInUse(const std::string& uuid);
+ bool IsBeingBuilt(const std::string& uuid);
+ bool IsUrlRegistered(const GURL& blob_url);
+
+ BlobMap blob_map_;
+ BlobURLMap public_blob_urls_;
+
+ // Used to keep track of how much memory is being utitlized for blob data,
+ // we count only the items of TYPE_DATA which are held in memory and not
+ // items of TYPE_FILE.
+ int64 memory_usage_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlobStorageContext);
+};
+
+} // namespace webkit_blob
+
+#endif // WEBKIT_BROWSER_BLOB_BLOB_STORAGE_CONTEXT_H_
diff --git a/chromium/webkit/browser/blob/blob_storage_context_unittest.cc b/chromium/webkit/browser/blob/blob_storage_context_unittest.cc
new file mode 100644
index 00000000000..5ce15455ed7
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_storage_context_unittest.cc
@@ -0,0 +1,206 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/blob/blob_data_handle.h"
+#include "webkit/browser/blob/blob_storage_context.h"
+#include "webkit/browser/blob/blob_storage_host.h"
+
+namespace webkit_blob {
+
+namespace {
+void SetupBasicBlob(BlobStorageHost* host, const std::string& id) {
+ EXPECT_TRUE(host->StartBuildingBlob(id));
+ BlobData::Item item;
+ item.SetToBytes("1", 1);
+ EXPECT_TRUE(host->AppendBlobDataItem(id, item));
+ EXPECT_TRUE(host->FinishBuildingBlob(id, "text/plain"));
+ EXPECT_FALSE(host->StartBuildingBlob(id));
+}
+} // namespace
+
+TEST(BlobStorageContextTest, IncrementDecrementRef) {
+ BlobStorageContext context;
+ BlobStorageHost host(&context);
+ base::MessageLoop fake_io_message_loop;
+
+ // Build up a basic blob.
+ const std::string kId("id");
+ SetupBasicBlob(&host, kId);
+
+ // Make sure it's there, finish building implies a ref of one.
+ scoped_ptr<BlobDataHandle> blob_data_handle;
+ blob_data_handle = context.GetBlobDataFromUUID(kId);
+ EXPECT_TRUE(blob_data_handle);
+ blob_data_handle.reset();
+
+ // Make sure its still there after inc/dec.
+ EXPECT_TRUE(host.IncrementBlobRefCount(kId));
+ EXPECT_TRUE(host.DecrementBlobRefCount(kId));
+ blob_data_handle = context.GetBlobDataFromUUID(kId);
+ EXPECT_TRUE(blob_data_handle);
+ blob_data_handle.reset();
+
+ // Make sure it goes away in the end.
+ EXPECT_TRUE(host.DecrementBlobRefCount(kId));
+ blob_data_handle = context.GetBlobDataFromUUID(kId);
+ EXPECT_FALSE(blob_data_handle);
+ EXPECT_FALSE(host.DecrementBlobRefCount(kId));
+ EXPECT_FALSE(host.IncrementBlobRefCount(kId));
+}
+
+TEST(BlobStorageContextTest, BlobDataHandle) {
+ BlobStorageContext context;
+ BlobStorageHost host(&context);
+ base::MessageLoop fake_io_message_loop;
+
+ // Build up a basic blob.
+ const std::string kId("id");
+ SetupBasicBlob(&host, kId);
+
+ // Get a handle to it.
+ scoped_ptr<BlobDataHandle> blob_data_handle =
+ context.GetBlobDataFromUUID(kId);
+ EXPECT_TRUE(blob_data_handle);
+
+ // Drop the host's ref to it.
+ EXPECT_TRUE(host.DecrementBlobRefCount(kId));
+
+ // Should still be there due to the handle.
+ scoped_ptr<BlobDataHandle> another_handle =
+ context.GetBlobDataFromUUID(kId);
+ EXPECT_TRUE(another_handle);
+
+ // Should disappear after dropping both handles.
+ blob_data_handle.reset();
+ another_handle.reset();
+ blob_data_handle = context.GetBlobDataFromUUID(kId);
+ EXPECT_FALSE(blob_data_handle);
+}
+
+TEST(BlobStorageContextTest, CompoundBlobs) {
+ const std::string kId1("id1");
+ const std::string kId2("id2");
+ const std::string kId2Prime("id2.prime");
+
+ base::MessageLoop fake_io_message_loop;
+
+ // Setup a set of blob data for testing.
+ base::Time time1, time2;
+ base::Time::FromString("Tue, 15 Nov 1994, 12:45:26 GMT", &time1);
+ base::Time::FromString("Mon, 14 Nov 1994, 11:30:49 GMT", &time2);
+
+ scoped_refptr<BlobData> blob_data1(new BlobData(kId1));
+ blob_data1->AppendData("Data1");
+ blob_data1->AppendData("Data2");
+ blob_data1->AppendFile(base::FilePath(FILE_PATH_LITERAL("File1.txt")),
+ 10, 1024, time1);
+
+ scoped_refptr<BlobData> blob_data2(new BlobData(kId2));
+ blob_data2->AppendData("Data3");
+ blob_data2->AppendBlob(kId1, 8, 100);
+ blob_data2->AppendFile(base::FilePath(FILE_PATH_LITERAL("File2.txt")),
+ 0, 20, time2);
+
+ scoped_refptr<BlobData> canonicalized_blob_data2(new BlobData(kId2Prime));
+ canonicalized_blob_data2->AppendData("Data3");
+ canonicalized_blob_data2->AppendData("a2___", 2);
+ canonicalized_blob_data2->AppendFile(
+ base::FilePath(FILE_PATH_LITERAL("File1.txt")),
+ 10, 98, time1);
+ canonicalized_blob_data2->AppendFile(
+ base::FilePath(FILE_PATH_LITERAL("File2.txt")), 0, 20, time2);
+
+ BlobStorageContext context;
+ scoped_ptr<BlobDataHandle> blob_data_handle;
+
+ // Test a blob referring to only data and a file.
+ blob_data_handle = context.AddFinishedBlob(blob_data1.get());
+ ASSERT_TRUE(blob_data_handle.get());
+ EXPECT_TRUE(*(blob_data_handle->data()) == *blob_data1.get());
+
+ // Test a blob composed in part with another blob.
+ blob_data_handle = context.AddFinishedBlob(blob_data2.get());
+ ASSERT_TRUE(blob_data_handle.get());
+ EXPECT_TRUE(*(blob_data_handle->data()) == *canonicalized_blob_data2.get());
+}
+
+TEST(BlobStorageContextTest, PublicBlobUrls) {
+ BlobStorageContext context;
+ BlobStorageHost host(&context);
+ base::MessageLoop fake_io_message_loop;
+
+ // Build up a basic blob.
+ const std::string kId("id");
+ SetupBasicBlob(&host, kId);
+
+ // Now register a url for that blob.
+ GURL kUrl("blob:id");
+ EXPECT_TRUE(host.RegisterPublicBlobURL(kUrl, kId));
+ scoped_ptr<BlobDataHandle> blob_data_handle =
+ context.GetBlobDataFromPublicURL(kUrl);
+ ASSERT_TRUE(blob_data_handle.get());
+ EXPECT_EQ(kId, blob_data_handle->data()->uuid());
+ blob_data_handle.reset();
+
+ // The url registration should keep the blob alive even after
+ // explicit references are dropped.
+ EXPECT_TRUE(host.DecrementBlobRefCount(kId));
+ blob_data_handle = context.GetBlobDataFromPublicURL(kUrl);
+ EXPECT_TRUE(blob_data_handle);
+ blob_data_handle.reset();
+
+ // Finally get rid of the url registration and the blob.
+ EXPECT_TRUE(host.RevokePublicBlobURL(kUrl));
+ blob_data_handle = context.GetBlobDataFromPublicURL(kUrl);
+ EXPECT_TRUE(!blob_data_handle.get());
+ EXPECT_FALSE(host.RevokePublicBlobURL(kUrl));
+}
+
+TEST(BlobStorageContextTest, HostCleanup) {
+ BlobStorageContext context;
+ scoped_ptr<BlobStorageHost> host(new BlobStorageHost(&context));
+ base::MessageLoop fake_io_message_loop;
+
+ // Build up a basic blob and register a url
+ const std::string kId("id");
+ GURL kUrl("blob:id");
+ SetupBasicBlob(host.get(), kId);
+ EXPECT_TRUE(host->RegisterPublicBlobURL(kUrl, kId));
+
+ // All should disappear upon host deletion.
+ host.reset();
+ scoped_ptr<BlobDataHandle> handle = context.GetBlobDataFromPublicURL(kUrl);
+ EXPECT_TRUE(!handle.get());
+ handle = context.GetBlobDataFromUUID(kId);
+ EXPECT_TRUE(!handle.get());
+}
+
+TEST(BlobStorageContextTest, EarlyContextDeletion) {
+ scoped_ptr<BlobStorageContext> context(new BlobStorageContext);
+ BlobStorageHost host(context.get());
+ base::MessageLoop fake_io_message_loop;
+
+ // Deleting the context should not induce crashes.
+ context.reset();
+
+ const std::string kId("id");
+ GURL kUrl("blob:id");
+ EXPECT_FALSE(host.StartBuildingBlob(kId));
+ BlobData::Item item;
+ item.SetToBytes("1", 1);
+ EXPECT_FALSE(host.AppendBlobDataItem(kId, item));
+ EXPECT_FALSE(host.FinishBuildingBlob(kId, "text/plain"));
+ EXPECT_FALSE(host.RegisterPublicBlobURL(kUrl, kId));
+ EXPECT_FALSE(host.IncrementBlobRefCount(kId));
+ EXPECT_FALSE(host.DecrementBlobRefCount(kId));
+ EXPECT_FALSE(host.RevokePublicBlobURL(kUrl));
+}
+
+} // namespace webkit_blob
diff --git a/chromium/webkit/browser/blob/blob_storage_controller.cc b/chromium/webkit/browser/blob/blob_storage_controller.cc
new file mode 100644
index 00000000000..f5dd60ec99e
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_storage_controller.cc
@@ -0,0 +1,257 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/blob/blob_storage_controller.h"
+
+#include "base/logging.h"
+#include "url/gurl.h"
+#include "webkit/common/blob/blob_data.h"
+
+namespace webkit_blob {
+
+namespace {
+
+// We can't use GURL directly for these hash fragment manipulations
+// since it doesn't have specific knowlege of the BlobURL format. GURL
+// treats BlobURLs as if they were PathURLs which don't support hash
+// fragments.
+
+bool BlobUrlHasRef(const GURL& url) {
+ return url.spec().find('#') != std::string::npos;
+}
+
+GURL ClearBlobUrlRef(const GURL& url) {
+ size_t hash_pos = url.spec().find('#');
+ if (hash_pos == std::string::npos)
+ return url;
+ return GURL(url.spec().substr(0, hash_pos));
+}
+
+static const int64 kMaxMemoryUsage = 1024 * 1024 * 1024; // 1G
+
+} // namespace
+
+BlobStorageController::BlobStorageController()
+ : memory_usage_(0) {
+}
+
+BlobStorageController::~BlobStorageController() {
+}
+
+void BlobStorageController::StartBuildingBlob(const GURL& url) {
+ DCHECK(url.SchemeIs("blob"));
+ DCHECK(!BlobUrlHasRef(url));
+ BlobData* blob_data = new BlobData;
+ unfinalized_blob_map_[url.spec()] = blob_data;
+ IncrementBlobDataUsage(blob_data);
+}
+
+void BlobStorageController::AppendBlobDataItem(
+ const GURL& url, const BlobData::Item& item) {
+ DCHECK(url.SchemeIs("blob"));
+ DCHECK(!BlobUrlHasRef(url));
+ BlobMap::iterator found = unfinalized_blob_map_.find(url.spec());
+ if (found == unfinalized_blob_map_.end())
+ return;
+ BlobData* target_blob_data = found->second.get();
+ DCHECK(target_blob_data);
+
+ memory_usage_ -= target_blob_data->GetMemoryUsage();
+
+ // The blob data is stored in the "canonical" way. That is, it only contains a
+ // list of Data and File items.
+ // 1) The Data item is denoted by the raw data and the range.
+ // 2) The File item is denoted by the file path, the range and the expected
+ // modification time.
+ // 3) The FileSystem File item is denoted by the FileSystem URL, the range
+ // and the expected modification time.
+ // All the Blob items in the passing blob data are resolved and expanded into
+ // a set of Data and File items.
+
+ DCHECK(item.length() > 0);
+ switch (item.type()) {
+ case BlobData::Item::TYPE_BYTES:
+ DCHECK(!item.offset());
+ target_blob_data->AppendData(item.bytes(), item.length());
+ break;
+ case BlobData::Item::TYPE_FILE:
+ AppendFileItem(target_blob_data,
+ item.path(),
+ item.offset(),
+ item.length(),
+ item.expected_modification_time());
+ break;
+ case BlobData::Item::TYPE_FILE_FILESYSTEM:
+ AppendFileSystemFileItem(target_blob_data,
+ item.url(),
+ item.offset(),
+ item.length(),
+ item.expected_modification_time());
+ break;
+ case BlobData::Item::TYPE_BLOB: {
+ BlobData* src_blob_data = GetBlobDataFromUrl(item.url());
+ DCHECK(src_blob_data);
+ if (src_blob_data)
+ AppendStorageItems(target_blob_data,
+ src_blob_data,
+ item.offset(),
+ item.length());
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ memory_usage_ += target_blob_data->GetMemoryUsage();
+
+ // If we're using too much memory, drop this blob.
+ // TODO(michaeln): Blob memory storage does not yet spill over to disk,
+ // until it does, we'll prevent memory usage over a max amount.
+ if (memory_usage_ > kMaxMemoryUsage)
+ RemoveBlob(url);
+}
+
+void BlobStorageController::FinishBuildingBlob(
+ const GURL& url, const std::string& content_type) {
+ DCHECK(url.SchemeIs("blob"));
+ DCHECK(!BlobUrlHasRef(url));
+ BlobMap::iterator found = unfinalized_blob_map_.find(url.spec());
+ if (found == unfinalized_blob_map_.end())
+ return;
+ found->second->set_content_type(content_type);
+ blob_map_[url.spec()] = found->second;
+ unfinalized_blob_map_.erase(found);
+}
+
+void BlobStorageController::AddFinishedBlob(const GURL& url,
+ const BlobData* data) {
+ StartBuildingBlob(url);
+ for (std::vector<BlobData::Item>::const_iterator iter =
+ data->items().begin();
+ iter != data->items().end(); ++iter) {
+ AppendBlobDataItem(url, *iter);
+ }
+ FinishBuildingBlob(url, data->content_type());
+}
+
+void BlobStorageController::CloneBlob(
+ const GURL& url, const GURL& src_url) {
+ DCHECK(url.SchemeIs("blob"));
+ DCHECK(!BlobUrlHasRef(url));
+
+ BlobData* blob_data = GetBlobDataFromUrl(src_url);
+ DCHECK(blob_data);
+ if (!blob_data)
+ return;
+
+ blob_map_[url.spec()] = blob_data;
+ IncrementBlobDataUsage(blob_data);
+}
+
+void BlobStorageController::RemoveBlob(const GURL& url) {
+ DCHECK(url.SchemeIs("blob"));
+ DCHECK(!BlobUrlHasRef(url));
+
+ if (!RemoveFromMapHelper(&unfinalized_blob_map_, url))
+ RemoveFromMapHelper(&blob_map_, url);
+}
+
+bool BlobStorageController::RemoveFromMapHelper(
+ BlobMap* map, const GURL& url) {
+ BlobMap::iterator found = map->find(url.spec());
+ if (found == map->end())
+ return false;
+ if (DecrementBlobDataUsage(found->second.get()))
+ memory_usage_ -= found->second->GetMemoryUsage();
+ map->erase(found);
+ return true;
+}
+
+
+BlobData* BlobStorageController::GetBlobDataFromUrl(const GURL& url) {
+ BlobMap::iterator found = blob_map_.find(
+ BlobUrlHasRef(url) ? ClearBlobUrlRef(url).spec() : url.spec());
+ return (found != blob_map_.end()) ? found->second.get() : NULL;
+}
+
+void BlobStorageController::AppendStorageItems(
+ BlobData* target_blob_data, BlobData* src_blob_data,
+ uint64 offset, uint64 length) {
+ DCHECK(target_blob_data && src_blob_data &&
+ length != static_cast<uint64>(-1));
+
+ std::vector<BlobData::Item>::const_iterator iter =
+ src_blob_data->items().begin();
+ if (offset) {
+ for (; iter != src_blob_data->items().end(); ++iter) {
+ if (offset >= iter->length())
+ offset -= iter->length();
+ else
+ break;
+ }
+ }
+
+ for (; iter != src_blob_data->items().end() && length > 0; ++iter) {
+ uint64 current_length = iter->length() - offset;
+ uint64 new_length = current_length > length ? length : current_length;
+ if (iter->type() == BlobData::Item::TYPE_BYTES) {
+ target_blob_data->AppendData(
+ iter->bytes() + static_cast<size_t>(iter->offset() + offset),
+ static_cast<uint32>(new_length));
+ } else if (iter->type() == BlobData::Item::TYPE_FILE) {
+ AppendFileItem(target_blob_data,
+ iter->path(),
+ iter->offset() + offset,
+ new_length,
+ iter->expected_modification_time());
+ } else {
+ DCHECK(iter->type() == BlobData::Item::TYPE_FILE_FILESYSTEM);
+ AppendFileSystemFileItem(target_blob_data,
+ iter->url(),
+ iter->offset() + offset,
+ new_length,
+ iter->expected_modification_time());
+ }
+ length -= new_length;
+ offset = 0;
+ }
+}
+
+void BlobStorageController::AppendFileItem(
+ BlobData* target_blob_data,
+ const base::FilePath& file_path, uint64 offset, uint64 length,
+ const base::Time& expected_modification_time) {
+ target_blob_data->AppendFile(file_path, offset, length,
+ expected_modification_time);
+
+ // It may be a temporary file that should be deleted when no longer needed.
+ scoped_refptr<ShareableFileReference> shareable_file =
+ ShareableFileReference::Get(file_path);
+ if (shareable_file.get())
+ target_blob_data->AttachShareableFileReference(shareable_file.get());
+}
+
+void BlobStorageController::AppendFileSystemFileItem(
+ BlobData* target_blob_data,
+ const GURL& url, uint64 offset, uint64 length,
+ const base::Time& expected_modification_time) {
+ target_blob_data->AppendFileSystemFile(url, offset, length,
+ expected_modification_time);
+}
+
+void BlobStorageController::IncrementBlobDataUsage(BlobData* blob_data) {
+ blob_data_usage_count_[blob_data] += 1;
+}
+
+bool BlobStorageController::DecrementBlobDataUsage(BlobData* blob_data) {
+ BlobDataUsageMap::iterator found = blob_data_usage_count_.find(blob_data);
+ DCHECK(found != blob_data_usage_count_.end());
+ if (--(found->second))
+ return false; // Still in use
+ blob_data_usage_count_.erase(found);
+ return true;
+}
+
+} // namespace webkit_blob
diff --git a/chromium/webkit/browser/blob/blob_storage_controller.h b/chromium/webkit/browser/blob/blob_storage_controller.h
new file mode 100644
index 00000000000..03dfb08045b
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_storage_controller.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_BLOB_BLOB_STORAGE_CONTROLLER_H_
+#define WEBKIT_BROWSER_BLOB_BLOB_STORAGE_CONTROLLER_H_
+
+#include <map>
+#include <string>
+
+#include "base/containers/hash_tables.h"
+#include "base/memory/ref_counted.h"
+#include "base/process/process.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/blob/blob_data.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+class Time;
+}
+
+namespace webkit_blob {
+
+// This class handles the logistics of blob Storage within the browser process.
+class WEBKIT_STORAGE_BROWSER_EXPORT BlobStorageController {
+ public:
+ BlobStorageController();
+ ~BlobStorageController();
+
+ void StartBuildingBlob(const GURL& url);
+ void AppendBlobDataItem(const GURL& url, const BlobData::Item& data_item);
+ void FinishBuildingBlob(const GURL& url, const std::string& content_type);
+ void AddFinishedBlob(const GURL& url, const BlobData* blob_data);
+ void CloneBlob(const GURL& url, const GURL& src_url);
+ void RemoveBlob(const GURL& url);
+ BlobData* GetBlobDataFromUrl(const GURL& url);
+
+ private:
+ friend class ViewBlobInternalsJob;
+
+ typedef base::hash_map<std::string, scoped_refptr<BlobData> > BlobMap;
+ typedef std::map<BlobData*, int> BlobDataUsageMap;
+
+ void AppendStorageItems(BlobData* target_blob_data,
+ BlobData* src_blob_data,
+ uint64 offset,
+ uint64 length);
+ void AppendFileItem(BlobData* target_blob_data,
+ const base::FilePath& file_path, uint64 offset,
+ uint64 length,
+ const base::Time& expected_modification_time);
+ void AppendFileSystemFileItem(
+ BlobData* target_blob_data,
+ const GURL& url, uint64 offset, uint64 length,
+ const base::Time& expected_modification_time);
+
+ bool RemoveFromMapHelper(BlobMap* map, const GURL& url);
+
+ void IncrementBlobDataUsage(BlobData* blob_data);
+ // Returns true if no longer in use.
+ bool DecrementBlobDataUsage(BlobData* blob_data);
+
+ BlobMap blob_map_;
+ BlobMap unfinalized_blob_map_;
+
+ // Used to keep track of how much memory is being utitlized for blob data,
+ // we count only the items of TYPE_DATA which are held in memory and not
+ // items of TYPE_FILE.
+ int64 memory_usage_;
+
+ // Multiple urls can refer to the same blob data, this map keeps track of
+ // how many urls refer to a BlobData.
+ BlobDataUsageMap blob_data_usage_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlobStorageController);
+};
+
+} // namespace webkit_blob
+
+#endif // WEBKIT_BROWSER_BLOB_BLOB_STORAGE_CONTROLLER_H_
diff --git a/chromium/webkit/browser/blob/blob_storage_controller_unittest.cc b/chromium/webkit/browser/blob/blob_storage_controller_unittest.cc
new file mode 100644
index 00000000000..fbef876a57c
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_storage_controller_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/blob/blob_storage_controller.h"
+#include "webkit/common/blob/blob_data.h"
+
+namespace webkit_blob {
+
+TEST(BlobStorageControllerTest, RegisterBlobUrl) {
+ // Setup a set of blob data for testing.
+ base::Time time1, time2;
+ base::Time::FromString("Tue, 15 Nov 1994, 12:45:26 GMT", &time1);
+ base::Time::FromString("Mon, 14 Nov 1994, 11:30:49 GMT", &time2);
+
+ scoped_refptr<BlobData> blob_data1(new BlobData());
+ blob_data1->AppendData("Data1");
+ blob_data1->AppendData("Data2");
+ blob_data1->AppendFile(base::FilePath(FILE_PATH_LITERAL("File1.txt")),
+ 10, 1024, time1);
+
+ scoped_refptr<BlobData> blob_data2(new BlobData());
+ blob_data2->AppendData("Data3");
+ blob_data2->AppendBlob(GURL("blob://url_1"), 8, 100);
+ blob_data2->AppendFile(base::FilePath(FILE_PATH_LITERAL("File2.txt")),
+ 0, 20, time2);
+
+ scoped_refptr<BlobData> canonicalized_blob_data2(new BlobData());
+ canonicalized_blob_data2->AppendData("Data3");
+ canonicalized_blob_data2->AppendData("a2___", 2);
+ canonicalized_blob_data2->AppendFile(
+ base::FilePath(FILE_PATH_LITERAL("File1.txt")),
+ 10, 98, time1);
+ canonicalized_blob_data2->AppendFile(
+ base::FilePath(FILE_PATH_LITERAL("File2.txt")), 0, 20, time2);
+
+ BlobStorageController blob_storage_controller;
+
+ // Test registering a blob URL referring to the blob data containing only
+ // data and file.
+ GURL blob_url1("blob://url_1");
+ blob_storage_controller.AddFinishedBlob(blob_url1, blob_data1.get());
+
+ BlobData* blob_data_found =
+ blob_storage_controller.GetBlobDataFromUrl(blob_url1);
+ ASSERT_TRUE(blob_data_found != NULL);
+ EXPECT_TRUE(*blob_data_found == *blob_data1.get());
+
+ // Test registering a blob URL referring to the blob data containing data,
+ // file and blob.
+ GURL blob_url2("blob://url_2");
+ blob_storage_controller.AddFinishedBlob(blob_url2, blob_data2.get());
+
+ blob_data_found = blob_storage_controller.GetBlobDataFromUrl(blob_url2);
+ ASSERT_TRUE(blob_data_found != NULL);
+ EXPECT_TRUE(*blob_data_found == *canonicalized_blob_data2.get());
+
+ // Test registering a blob URL referring to existent blob URL.
+ GURL blob_url3("blob://url_3");
+ blob_storage_controller.CloneBlob(blob_url3, blob_url1);
+
+ blob_data_found = blob_storage_controller.GetBlobDataFromUrl(blob_url3);
+ ASSERT_TRUE(blob_data_found != NULL);
+ EXPECT_TRUE(*blob_data_found == *blob_data1.get());
+
+ // Test unregistering a blob URL.
+ blob_storage_controller.RemoveBlob(blob_url3);
+ blob_data_found = blob_storage_controller.GetBlobDataFromUrl(blob_url3);
+ EXPECT_TRUE(!blob_data_found);
+}
+
+} // namespace webkit_blob
diff --git a/chromium/webkit/browser/blob/blob_storage_host.cc b/chromium/webkit/browser/blob/blob_storage_host.cc
new file mode 100644
index 00000000000..eebf3fbace7
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_storage_host.cc
@@ -0,0 +1,113 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/blob/blob_storage_host.h"
+
+#include "base/sequenced_task_runner.h"
+#include "url/gurl.h"
+#include "webkit/browser/blob/blob_data_handle.h"
+#include "webkit/browser/blob/blob_storage_context.h"
+
+namespace webkit_blob {
+
+BlobStorageHost::BlobStorageHost(BlobStorageContext* context)
+ : context_(context->AsWeakPtr()) {
+}
+
+BlobStorageHost::~BlobStorageHost() {
+ if (!context_.get())
+ return;
+ for (std::set<GURL>::iterator iter = public_blob_urls_.begin();
+ iter != public_blob_urls_.end(); ++iter) {
+ context_->RevokePublicBlobURL(*iter);
+ }
+ for (BlobReferenceMap::iterator iter = blobs_inuse_map_.begin();
+ iter != blobs_inuse_map_.end(); ++iter) {
+ for (int i = 0; i < iter->second; ++i)
+ context_->DecrementBlobRefCount(iter->first);
+ }
+}
+
+bool BlobStorageHost::StartBuildingBlob(const std::string& uuid) {
+ if (!context_.get() || uuid.empty() || context_->IsInUse(uuid))
+ return false;
+ context_->StartBuildingBlob(uuid);
+ blobs_inuse_map_[uuid] = 1;
+ return true;
+}
+
+bool BlobStorageHost::AppendBlobDataItem(
+ const std::string& uuid, const BlobData::Item& data_item) {
+ if (!context_.get() || !IsBeingBuiltInHost(uuid))
+ return false;
+ context_->AppendBlobDataItem(uuid, data_item);
+ return true;
+}
+
+bool BlobStorageHost::CancelBuildingBlob(const std::string& uuid) {
+ if (!context_.get() || !IsBeingBuiltInHost(uuid))
+ return false;
+ blobs_inuse_map_.erase(uuid);
+ context_->CancelBuildingBlob(uuid);
+ return true;
+}
+
+bool BlobStorageHost::FinishBuildingBlob(
+ const std::string& uuid, const std::string& content_type) {
+ if (!context_.get() || !IsBeingBuiltInHost(uuid))
+ return false;
+ context_->FinishBuildingBlob(uuid, content_type);
+ return true;
+}
+
+bool BlobStorageHost::IncrementBlobRefCount(const std::string& uuid) {
+ if (!context_.get() || !context_->IsInUse(uuid) ||
+ context_->IsBeingBuilt(uuid))
+ return false;
+ context_->IncrementBlobRefCount(uuid);
+ blobs_inuse_map_[uuid] += 1;
+ return true;
+}
+
+bool BlobStorageHost::DecrementBlobRefCount(const std::string& uuid) {
+ if (!context_.get() || !IsInUseInHost(uuid))
+ return false;
+ context_->DecrementBlobRefCount(uuid);
+ blobs_inuse_map_[uuid] -= 1;
+ if (blobs_inuse_map_[uuid] == 0)
+ blobs_inuse_map_.erase(uuid);
+ return true;
+}
+
+bool BlobStorageHost::RegisterPublicBlobURL(
+ const GURL& blob_url, const std::string& uuid) {
+ if (!context_.get() || !IsInUseInHost(uuid) ||
+ context_->IsUrlRegistered(blob_url))
+ return false;
+ context_->RegisterPublicBlobURL(blob_url, uuid);
+ public_blob_urls_.insert(blob_url);
+ return true;
+}
+
+bool BlobStorageHost::RevokePublicBlobURL(const GURL& blob_url) {
+ if (!context_.get() || !IsUrlRegisteredInHost(blob_url))
+ return false;
+ context_->RevokePublicBlobURL(blob_url);
+ public_blob_urls_.erase(blob_url);
+ return true;
+}
+
+bool BlobStorageHost::IsInUseInHost(const std::string& uuid) {
+ return blobs_inuse_map_.find(uuid) != blobs_inuse_map_.end();
+}
+
+bool BlobStorageHost::IsBeingBuiltInHost(const std::string& uuid) {
+ return IsInUseInHost(uuid) && context_->IsBeingBuilt(uuid);
+}
+
+bool BlobStorageHost::IsUrlRegisteredInHost(const GURL& blob_url) {
+ return public_blob_urls_.find(blob_url) != public_blob_urls_.end();
+}
+
+} // namespace webkit_blob
diff --git a/chromium/webkit/browser/blob/blob_storage_host.h b/chromium/webkit/browser/blob/blob_storage_host.h
new file mode 100644
index 00000000000..954c32ce400
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_storage_host.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_BLOB_BLOB_STORAGE_HOST_H_
+#define WEBKIT_BROWSER_BLOB_BLOB_STORAGE_HOST_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/blob/blob_data.h"
+
+class GURL;
+
+namespace webkit_blob {
+
+class BlobDataHandle;
+class BlobStorageHost;
+class BlobStorageContext;
+
+// This class handles the logistics of blob storage for a single child process.
+// There is one instance per child process. When the child process
+// terminates all blob references attibutable to that process go away upon
+// destruction of the instance. The class is single threaded and should
+// only be used on the IO thread.
+class WEBKIT_STORAGE_BROWSER_EXPORT BlobStorageHost {
+ public:
+ explicit BlobStorageHost(BlobStorageContext* context);
+ ~BlobStorageHost();
+
+ // Methods to support the IPC message protocol.
+ // A false return indicates a problem with the inputs
+ // like a non-existent or pre-existent uuid or url.
+ bool StartBuildingBlob(const std::string& uuid) WARN_UNUSED_RESULT;
+ bool AppendBlobDataItem(const std::string& uuid,
+ const BlobData::Item& data_item) WARN_UNUSED_RESULT;
+ bool CancelBuildingBlob(const std::string& uuid) WARN_UNUSED_RESULT;
+ bool FinishBuildingBlob(const std::string& uuid,
+ const std::string& type) WARN_UNUSED_RESULT;
+ bool IncrementBlobRefCount(const std::string& uuid) WARN_UNUSED_RESULT;
+ bool DecrementBlobRefCount(const std::string& uuid) WARN_UNUSED_RESULT;
+ bool RegisterPublicBlobURL(const GURL& blob_url,
+ const std::string& uuid) WARN_UNUSED_RESULT;
+ bool RevokePublicBlobURL(const GURL& blob_url) WARN_UNUSED_RESULT;
+
+ private:
+ typedef std::map<std::string, int> BlobReferenceMap;
+
+ bool IsInUseInHost(const std::string& uuid);
+ bool IsBeingBuiltInHost(const std::string& uuid);
+ bool IsUrlRegisteredInHost(const GURL& blob_url);
+
+ // Collection of blob ids and a count of how many usages
+ // of that id are attributable to this consumer.
+ BlobReferenceMap blobs_inuse_map_;
+
+ // The set of public blob urls coined by this consumer.
+ std::set<GURL> public_blob_urls_;
+
+ base::WeakPtr<BlobStorageContext> context_;
+};
+
+} // namespace webkit_blob
+
+#endif // WEBKIT_BROWSER_BLOB_BLOB_STORAGE_HOST_H_
diff --git a/chromium/webkit/browser/blob/blob_url_request_job.cc b/chromium/webkit/browser/blob/blob_url_request_job.cc
new file mode 100644
index 00000000000..a845fc94a4a
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_url_request_job.cc
@@ -0,0 +1,572 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/blob/blob_url_request_job.h"
+
+#include <limits>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_util_proxy.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_status.h"
+#include "webkit/browser/blob/local_file_stream_reader.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+
+namespace webkit_blob {
+
+namespace {
+
+bool IsFileType(BlobData::Item::Type type) {
+ switch (type) {
+ case BlobData::Item::TYPE_FILE:
+ case BlobData::Item::TYPE_FILE_FILESYSTEM:
+ return true;
+ default:
+ return false;
+ }
+}
+
+} // namespace
+
+BlobURLRequestJob::BlobURLRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ BlobData* blob_data,
+ fileapi::FileSystemContext* file_system_context,
+ base::MessageLoopProxy* file_thread_proxy)
+ : net::URLRequestJob(request, network_delegate),
+ weak_factory_(this),
+ blob_data_(blob_data),
+ file_system_context_(file_system_context),
+ file_thread_proxy_(file_thread_proxy),
+ total_size_(0),
+ remaining_bytes_(0),
+ pending_get_file_info_count_(0),
+ current_item_index_(0),
+ current_item_offset_(0),
+ error_(false),
+ byte_range_set_(false) {
+ DCHECK(file_thread_proxy_.get());
+}
+
+void BlobURLRequestJob::Start() {
+ // Continue asynchronously.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&BlobURLRequestJob::DidStart, weak_factory_.GetWeakPtr()));
+}
+
+void BlobURLRequestJob::Kill() {
+ DeleteCurrentFileReader();
+
+ net::URLRequestJob::Kill();
+ weak_factory_.InvalidateWeakPtrs();
+}
+
+bool BlobURLRequestJob::ReadRawData(net::IOBuffer* dest,
+ int dest_size,
+ int* bytes_read) {
+ DCHECK_NE(dest_size, 0);
+ DCHECK(bytes_read);
+ DCHECK_GE(remaining_bytes_, 0);
+
+ // Bail out immediately if we encounter an error.
+ if (error_) {
+ *bytes_read = 0;
+ return true;
+ }
+
+ if (remaining_bytes_ < dest_size)
+ dest_size = static_cast<int>(remaining_bytes_);
+
+ // If we should copy zero bytes because |remaining_bytes_| is zero, short
+ // circuit here.
+ if (!dest_size) {
+ *bytes_read = 0;
+ return true;
+ }
+
+ // Keep track of the buffer.
+ DCHECK(!read_buf_.get());
+ read_buf_ = new net::DrainableIOBuffer(dest, dest_size);
+
+ return ReadLoop(bytes_read);
+}
+
+bool BlobURLRequestJob::GetMimeType(std::string* mime_type) const {
+ if (!response_info_)
+ return false;
+
+ return response_info_->headers->GetMimeType(mime_type);
+}
+
+void BlobURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
+ if (response_info_)
+ *info = *response_info_;
+}
+
+int BlobURLRequestJob::GetResponseCode() const {
+ if (!response_info_)
+ return -1;
+
+ return response_info_->headers->response_code();
+}
+
+void BlobURLRequestJob::SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) {
+ std::string range_header;
+ if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
+ // We only care about "Range" header here.
+ std::vector<net::HttpByteRange> ranges;
+ if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
+ if (ranges.size() == 1) {
+ byte_range_set_ = true;
+ byte_range_ = ranges[0];
+ } else {
+ // We don't support multiple range requests in one single URL request,
+ // because we need to do multipart encoding here.
+ // TODO(jianli): Support multipart byte range requests.
+ NotifyFailure(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
+ }
+ }
+ }
+}
+
+BlobURLRequestJob::~BlobURLRequestJob() {
+ STLDeleteValues(&index_to_reader_);
+}
+
+void BlobURLRequestJob::DidStart() {
+ error_ = false;
+
+ // We only support GET request per the spec.
+ if (request()->method() != "GET") {
+ NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED);
+ return;
+ }
+
+ // If the blob data is not present, bail out.
+ if (!blob_data_.get()) {
+ NotifyFailure(net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+
+ CountSize();
+}
+
+bool BlobURLRequestJob::AddItemLength(size_t index, int64 item_length) {
+ if (item_length > kint64max - total_size_) {
+ NotifyFailure(net::ERR_FAILED);
+ return false;
+ }
+
+ // Cache the size and add it to the total size.
+ DCHECK_LT(index, item_length_list_.size());
+ item_length_list_[index] = item_length;
+ total_size_ += item_length;
+ return true;
+}
+
+void BlobURLRequestJob::CountSize() {
+ pending_get_file_info_count_ = 0;
+ total_size_ = 0;
+ item_length_list_.resize(blob_data_->items().size());
+
+ for (size_t i = 0; i < blob_data_->items().size(); ++i) {
+ const BlobData::Item& item = blob_data_->items().at(i);
+ if (IsFileType(item.type())) {
+ ++pending_get_file_info_count_;
+ GetFileStreamReader(i)->GetLength(
+ base::Bind(&BlobURLRequestJob::DidGetFileItemLength,
+ weak_factory_.GetWeakPtr(), i));
+ continue;
+ }
+
+ if (!AddItemLength(i, item.length()))
+ return;
+ }
+
+ if (pending_get_file_info_count_ == 0)
+ DidCountSize(net::OK);
+}
+
+void BlobURLRequestJob::DidCountSize(int error) {
+ DCHECK(!error_);
+
+ // If an error occured, bail out.
+ if (error != net::OK) {
+ NotifyFailure(error);
+ return;
+ }
+
+ // Apply the range requirement.
+ if (!byte_range_.ComputeBounds(total_size_)) {
+ NotifyFailure(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
+ return;
+ }
+
+ remaining_bytes_ = byte_range_.last_byte_position() -
+ byte_range_.first_byte_position() + 1;
+ DCHECK_GE(remaining_bytes_, 0);
+
+ // Do the seek at the beginning of the request.
+ if (byte_range_.first_byte_position())
+ Seek(byte_range_.first_byte_position());
+
+ NotifySuccess();
+}
+
+void BlobURLRequestJob::DidGetFileItemLength(size_t index, int64 result) {
+ // Do nothing if we have encountered an error.
+ if (error_)
+ return;
+
+ if (result == net::ERR_UPLOAD_FILE_CHANGED) {
+ NotifyFailure(net::ERR_FILE_NOT_FOUND);
+ return;
+ } else if (result < 0) {
+ NotifyFailure(result);
+ return;
+ }
+
+ DCHECK_LT(index, blob_data_->items().size());
+ const BlobData::Item& item = blob_data_->items().at(index);
+ DCHECK(IsFileType(item.type()));
+
+ uint64 file_length = result;
+ uint64 item_offset = item.offset();
+ uint64 item_length = item.length();
+
+ if (item_offset > file_length) {
+ NotifyFailure(net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+
+ uint64 max_length = file_length - item_offset;
+
+ // If item length is -1, we need to use the file size being resolved
+ // in the real time.
+ if (item_length == static_cast<uint64>(-1)) {
+ item_length = max_length;
+ } else if (item_length > max_length) {
+ NotifyFailure(net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+
+ if (!AddItemLength(index, item_length))
+ return;
+
+ if (--pending_get_file_info_count_ == 0)
+ DidCountSize(net::OK);
+}
+
+void BlobURLRequestJob::Seek(int64 offset) {
+ // Skip the initial items that are not in the range.
+ for (current_item_index_ = 0;
+ current_item_index_ < blob_data_->items().size() &&
+ offset >= item_length_list_[current_item_index_];
+ ++current_item_index_) {
+ offset -= item_length_list_[current_item_index_];
+ }
+
+ // Set the offset that need to jump to for the first item in the range.
+ current_item_offset_ = offset;
+
+ if (offset == 0)
+ return;
+
+ // Adjust the offset of the first stream if it is of file type.
+ const BlobData::Item& item = blob_data_->items().at(current_item_index_);
+ if (IsFileType(item.type())) {
+ DeleteCurrentFileReader();
+ CreateFileStreamReader(current_item_index_, offset);
+ }
+}
+
+bool BlobURLRequestJob::ReadItem() {
+ // Are we done with reading all the blob data?
+ if (remaining_bytes_ == 0)
+ return true;
+
+ // If we get to the last item but still expect something to read, bail out
+ // since something is wrong.
+ if (current_item_index_ >= blob_data_->items().size()) {
+ NotifyFailure(net::ERR_FAILED);
+ return false;
+ }
+
+ // Compute the bytes to read for current item.
+ int bytes_to_read = ComputeBytesToRead();
+
+ // If nothing to read for current item, advance to next item.
+ if (bytes_to_read == 0) {
+ AdvanceItem();
+ return ReadItem();
+ }
+
+ // Do the reading.
+ const BlobData::Item& item = blob_data_->items().at(current_item_index_);
+ if (item.type() == BlobData::Item::TYPE_BYTES)
+ return ReadBytesItem(item, bytes_to_read);
+ if (IsFileType(item.type())) {
+ return ReadFileItem(GetFileStreamReader(current_item_index_),
+ bytes_to_read);
+ }
+ NOTREACHED();
+ return false;
+}
+
+void BlobURLRequestJob::AdvanceItem() {
+ // Close the file if the current item is a file.
+ DeleteCurrentFileReader();
+
+ // Advance to the next item.
+ current_item_index_++;
+ current_item_offset_ = 0;
+}
+
+void BlobURLRequestJob::AdvanceBytesRead(int result) {
+ DCHECK_GT(result, 0);
+
+ // Do we finish reading the current item?
+ current_item_offset_ += result;
+ if (current_item_offset_ == item_length_list_[current_item_index_])
+ AdvanceItem();
+
+ // Subtract the remaining bytes.
+ remaining_bytes_ -= result;
+ DCHECK_GE(remaining_bytes_, 0);
+
+ // Adjust the read buffer.
+ read_buf_->DidConsume(result);
+ DCHECK_GE(read_buf_->BytesRemaining(), 0);
+}
+
+bool BlobURLRequestJob::ReadBytesItem(const BlobData::Item& item,
+ int bytes_to_read) {
+ DCHECK_GE(read_buf_->BytesRemaining(), bytes_to_read);
+
+ memcpy(read_buf_->data(),
+ item.bytes() + item.offset() + current_item_offset_,
+ bytes_to_read);
+
+ AdvanceBytesRead(bytes_to_read);
+ return true;
+}
+
+bool BlobURLRequestJob::ReadFileItem(FileStreamReader* reader,
+ int bytes_to_read) {
+ DCHECK_GE(read_buf_->BytesRemaining(), bytes_to_read);
+ DCHECK(reader);
+ const int result = reader->Read(
+ read_buf_.get(),
+ bytes_to_read,
+ base::Bind(&BlobURLRequestJob::DidReadFile, base::Unretained(this)));
+ if (result >= 0) {
+ // Data is immediately available.
+ if (GetStatus().is_io_pending())
+ DidReadFile(result);
+ else
+ AdvanceBytesRead(result);
+ return true;
+ }
+ if (result == net::ERR_IO_PENDING)
+ SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
+ else
+ NotifyFailure(result);
+ return false;
+}
+
+void BlobURLRequestJob::DidReadFile(int result) {
+ if (result <= 0) {
+ NotifyFailure(net::ERR_FAILED);
+ return;
+ }
+ SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status
+
+ AdvanceBytesRead(result);
+
+ // If the read buffer is completely filled, we're done.
+ if (!read_buf_->BytesRemaining()) {
+ int bytes_read = BytesReadCompleted();
+ NotifyReadComplete(bytes_read);
+ return;
+ }
+
+ // Otherwise, continue the reading.
+ int bytes_read = 0;
+ if (ReadLoop(&bytes_read))
+ NotifyReadComplete(bytes_read);
+}
+
+void BlobURLRequestJob::DeleteCurrentFileReader() {
+ IndexToReaderMap::iterator found = index_to_reader_.find(current_item_index_);
+ if (found != index_to_reader_.end() && found->second) {
+ delete found->second;
+ index_to_reader_.erase(found);
+ }
+}
+
+int BlobURLRequestJob::BytesReadCompleted() {
+ int bytes_read = read_buf_->BytesConsumed();
+ read_buf_ = NULL;
+ return bytes_read;
+}
+
+int BlobURLRequestJob::ComputeBytesToRead() const {
+ int64 current_item_length = item_length_list_[current_item_index_];
+
+ int64 item_remaining = current_item_length - current_item_offset_;
+ int64 buf_remaining = read_buf_->BytesRemaining();
+ int64 max_remaining = std::numeric_limits<int>::max();
+
+ int64 min = std::min(std::min(std::min(item_remaining,
+ buf_remaining),
+ remaining_bytes_),
+ max_remaining);
+
+ return static_cast<int>(min);
+}
+
+bool BlobURLRequestJob::ReadLoop(int* bytes_read) {
+ // Read until we encounter an error or could not get the data immediately.
+ while (remaining_bytes_ > 0 && read_buf_->BytesRemaining() > 0) {
+ if (!ReadItem())
+ return false;
+ }
+
+ *bytes_read = BytesReadCompleted();
+ return true;
+}
+
+void BlobURLRequestJob::NotifySuccess() {
+ net::HttpStatusCode status_code = net::HTTP_OK;
+ if (byte_range_set_ && byte_range_.IsValid())
+ status_code = net::HTTP_PARTIAL_CONTENT;
+ HeadersCompleted(status_code);
+}
+
+void BlobURLRequestJob::NotifyFailure(int error_code) {
+ error_ = true;
+
+ // If we already return the headers on success, we can't change the headers
+ // now. Instead, we just error out.
+ if (response_info_) {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
+ error_code));
+ return;
+ }
+
+ net::HttpStatusCode status_code = net::HTTP_INTERNAL_SERVER_ERROR;
+ switch (error_code) {
+ case net::ERR_ACCESS_DENIED:
+ status_code = net::HTTP_FORBIDDEN;
+ break;
+ case net::ERR_FILE_NOT_FOUND:
+ status_code = net::HTTP_NOT_FOUND;
+ break;
+ case net::ERR_METHOD_NOT_SUPPORTED:
+ status_code = net::HTTP_METHOD_NOT_ALLOWED;
+ break;
+ case net::ERR_REQUEST_RANGE_NOT_SATISFIABLE:
+ status_code = net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE;
+ break;
+ case net::ERR_FAILED:
+ break;
+ default:
+ DCHECK(false);
+ break;
+ }
+ HeadersCompleted(status_code);
+}
+
+void BlobURLRequestJob::HeadersCompleted(net::HttpStatusCode status_code) {
+ std::string status("HTTP/1.1 ");
+ status.append(base::IntToString(status_code));
+ status.append(" ");
+ status.append(net::GetHttpReasonPhrase(status_code));
+ status.append("\0\0", 2);
+ net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status);
+
+ if (status_code == net::HTTP_OK || status_code == net::HTTP_PARTIAL_CONTENT) {
+ std::string content_length_header(net::HttpRequestHeaders::kContentLength);
+ content_length_header.append(": ");
+ content_length_header.append(base::Int64ToString(remaining_bytes_));
+ headers->AddHeader(content_length_header);
+ if (!blob_data_->content_type().empty()) {
+ std::string content_type_header(net::HttpRequestHeaders::kContentType);
+ content_type_header.append(": ");
+ content_type_header.append(blob_data_->content_type());
+ headers->AddHeader(content_type_header);
+ }
+ if (!blob_data_->content_disposition().empty()) {
+ std::string content_disposition_header("Content-Disposition: ");
+ content_disposition_header.append(blob_data_->content_disposition());
+ headers->AddHeader(content_disposition_header);
+ }
+ }
+
+ response_info_.reset(new net::HttpResponseInfo());
+ response_info_->headers = headers;
+
+ set_expected_content_size(remaining_bytes_);
+
+ NotifyHeadersComplete();
+}
+
+FileStreamReader* BlobURLRequestJob::GetFileStreamReader(size_t index) {
+ DCHECK_LT(index, blob_data_->items().size());
+ const BlobData::Item& item = blob_data_->items().at(index);
+ if (!IsFileType(item.type()))
+ return NULL;
+ if (index_to_reader_.find(index) == index_to_reader_.end())
+ CreateFileStreamReader(index, 0);
+ DCHECK(index_to_reader_[index]);
+ return index_to_reader_[index];
+}
+
+void BlobURLRequestJob::CreateFileStreamReader(size_t index,
+ int64 additional_offset) {
+ DCHECK_LT(index, blob_data_->items().size());
+ const BlobData::Item& item = blob_data_->items().at(index);
+ DCHECK(IsFileType(item.type()));
+ DCHECK_EQ(0U, index_to_reader_.count(index));
+
+ FileStreamReader* reader = NULL;
+ switch (item.type()) {
+ case BlobData::Item::TYPE_FILE:
+ reader = new LocalFileStreamReader(file_thread_proxy_.get(),
+ item.path(),
+ item.offset() + additional_offset,
+ item.expected_modification_time());
+ break;
+ case BlobData::Item::TYPE_FILE_FILESYSTEM:
+ reader = file_system_context_->CreateFileStreamReader(
+ fileapi::FileSystemURL(file_system_context_->CrackURL(item.url())),
+ item.offset() + additional_offset,
+ item.expected_modification_time()).release();
+ break;
+ default:
+ NOTREACHED();
+ }
+ DCHECK(reader);
+ index_to_reader_[index] = reader;
+}
+
+} // namespace webkit_blob
diff --git a/chromium/webkit/browser/blob/blob_url_request_job.h b/chromium/webkit/browser/blob/blob_url_request_job.h
new file mode 100644
index 00000000000..595728855d1
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_url_request_job.h
@@ -0,0 +1,131 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_H_
+#define WEBKIT_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_H_
+
+#include <map>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/platform_file.h"
+#include "net/http/http_byte_range.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_request_job.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/blob/blob_data.h"
+
+namespace base {
+class MessageLoopProxy;
+struct PlatformFileInfo;
+}
+
+namespace fileapi {
+class FileSystemContext;
+}
+
+namespace net {
+class DrainableIOBuffer;
+class IOBuffer;
+}
+
+namespace webkit_blob {
+
+class FileStreamReader;
+
+// A request job that handles reading blob URLs.
+class WEBKIT_STORAGE_BROWSER_EXPORT BlobURLRequestJob
+ : public net::URLRequestJob {
+ public:
+ BlobURLRequestJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ BlobData* blob_data,
+ fileapi::FileSystemContext* file_system_context,
+ base::MessageLoopProxy* resolving_message_loop_proxy);
+
+ // net::URLRequestJob methods.
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual bool ReadRawData(net::IOBuffer* buf,
+ int buf_size,
+ int* bytes_read) OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+ virtual void SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) OVERRIDE;
+
+ protected:
+ virtual ~BlobURLRequestJob();
+
+ private:
+ typedef std::map<size_t, FileStreamReader*> IndexToReaderMap;
+
+ // For preparing for read: get the size, apply the range and perform seek.
+ void DidStart();
+ bool AddItemLength(size_t index, int64 item_length);
+ void CountSize();
+ void DidCountSize(int error);
+ void DidGetFileItemLength(size_t index, int64 result);
+ void Seek(int64 offset);
+
+ // For reading the blob.
+ bool ReadLoop(int* bytes_read);
+ bool ReadItem();
+ void AdvanceItem();
+ void AdvanceBytesRead(int result);
+ bool ReadBytesItem(const BlobData::Item& item, int bytes_to_read);
+ bool ReadFileItem(FileStreamReader* reader, int bytes_to_read);
+
+ void DidReadFile(int result);
+ void DeleteCurrentFileReader();
+
+ int ComputeBytesToRead() const;
+ int BytesReadCompleted();
+
+ // These methods convert the result of blob data reading into response headers
+ // and pass it to URLRequestJob's NotifyDone() or NotifyHeadersComplete().
+ void NotifySuccess();
+ void NotifyFailure(int);
+ void HeadersCompleted(net::HttpStatusCode status_code);
+
+ // Returns a FileStreamReader for a blob item at |index|.
+ // If the item at |index| is not of file this returns NULL.
+ FileStreamReader* GetFileStreamReader(size_t index);
+
+ // Creates a FileStreamReader for the item at |index| with additional_offset.
+ void CreateFileStreamReader(size_t index, int64 additional_offset);
+
+ base::WeakPtrFactory<BlobURLRequestJob> weak_factory_;
+
+ scoped_refptr<BlobData> blob_data_;
+
+ // Variables for controlling read from |blob_data_|.
+ scoped_refptr<fileapi::FileSystemContext> file_system_context_;
+ scoped_refptr<base::MessageLoopProxy> file_thread_proxy_;
+ std::vector<int64> item_length_list_;
+ int64 total_size_;
+ int64 remaining_bytes_;
+ int pending_get_file_info_count_;
+ IndexToReaderMap index_to_reader_;
+ size_t current_item_index_;
+ int64 current_item_offset_;
+
+ // Holds the buffer for read data with the IOBuffer interface.
+ scoped_refptr<net::DrainableIOBuffer> read_buf_;
+
+ // Is set when NotifyFailure() is called and reset when DidStart is called.
+ bool error_;
+
+ bool byte_range_set_;
+ net::HttpByteRange byte_range_;
+
+ scoped_ptr<net::HttpResponseInfo> response_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlobURLRequestJob);
+};
+
+} // namespace webkit_blob
+
+#endif // WEBKIT_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_H_
diff --git a/chromium/webkit/browser/blob/blob_url_request_job_factory.cc b/chromium/webkit/browser/blob/blob_url_request_job_factory.cc
new file mode 100644
index 00000000000..cc600d1c228
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_url_request_job_factory.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/blob/blob_url_request_job_factory.h"
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "webkit/browser/blob/blob_storage_controller.h"
+#include "webkit/browser/blob/blob_url_request_job.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+
+namespace webkit_blob {
+
+BlobProtocolHandler::BlobProtocolHandler(
+ webkit_blob::BlobStorageController* blob_storage_controller,
+ fileapi::FileSystemContext* file_system_context,
+ base::MessageLoopProxy* loop_proxy)
+ : blob_storage_controller_(blob_storage_controller),
+ file_system_context_(file_system_context),
+ file_loop_proxy_(loop_proxy) {
+ DCHECK(blob_storage_controller_);
+ DCHECK(file_system_context_.get());
+ DCHECK(file_loop_proxy_.get());
+}
+
+BlobProtocolHandler::~BlobProtocolHandler() {}
+
+net::URLRequestJob* BlobProtocolHandler::MaybeCreateJob(
+ net::URLRequest* request, net::NetworkDelegate* network_delegate) const {
+ scoped_refptr<webkit_blob::BlobData> data = LookupBlobData(request);
+ if (!data.get()) {
+ // This request is not coming through resource dispatcher host.
+ data = blob_storage_controller_->GetBlobDataFromUrl(request->url());
+ }
+ return new webkit_blob::BlobURLRequestJob(request,
+ network_delegate,
+ data.get(),
+ file_system_context_.get(),
+ file_loop_proxy_.get());
+}
+
+scoped_refptr<webkit_blob::BlobData>
+BlobProtocolHandler::LookupBlobData(net::URLRequest* request) const {
+ return NULL;
+}
+
+} // namespace webkit_blob
diff --git a/chromium/webkit/browser/blob/blob_url_request_job_factory.h b/chromium/webkit/browser/blob/blob_url_request_job_factory.h
new file mode 100644
index 00000000000..2a78c95d1a6
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_url_request_job_factory.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_FACTORY_H_
+#define WEBKIT_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_FACTORY_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace base {
+class MessageLoopProxy;
+} // namespace base
+
+namespace fileapi {
+class FileSystemContext;
+} // namespace fileapi
+
+namespace net {
+class URLRequest;
+} // namespace net
+
+namespace webkit_blob {
+
+class BlobData;
+class BlobStorageController;
+
+class WEBKIT_STORAGE_BROWSER_EXPORT BlobProtocolHandler
+ : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ // |controller|'s lifetime should exceed the lifetime of the ProtocolHandler.
+ BlobProtocolHandler(BlobStorageController* blob_storage_controller,
+ fileapi::FileSystemContext* file_system_context,
+ base::MessageLoopProxy* file_loop_proxy);
+ virtual ~BlobProtocolHandler();
+
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE;
+
+ private:
+ virtual scoped_refptr<BlobData> LookupBlobData(
+ net::URLRequest* request) const;
+
+ // No scoped_refptr because |blob_storage_controller_| is owned by the
+ // ProfileIOData, which also owns this ProtocolHandler.
+ BlobStorageController* const blob_storage_controller_;
+ const scoped_refptr<fileapi::FileSystemContext> file_system_context_;
+ const scoped_refptr<base::MessageLoopProxy> file_loop_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlobProtocolHandler);
+};
+
+} // namespace webkit_blob
+
+#endif // WEBKIT_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_FACTORY_H_
diff --git a/chromium/webkit/browser/blob/blob_url_request_job_unittest.cc b/chromium/webkit/browser/blob/blob_url_request_job_unittest.cc
new file mode 100644
index 00000000000..4b0749ee35b
--- /dev/null
+++ b/chromium/webkit/browser/blob/blob_url_request_job_unittest.cc
@@ -0,0 +1,451 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+#include "net/base/io_buffer.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/blob/blob_url_request_job.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+#include "webkit/common/blob/blob_data.h"
+
+namespace webkit_blob {
+
+namespace {
+
+const int kBufferSize = 1024;
+const char kTestData1[] = "Hello";
+const char kTestData2[] = "Here it is data.";
+const char kTestFileData1[] = "0123456789";
+const char kTestFileData2[] = "This is sample file.";
+const char kTestFileSystemFileData1[] = "abcdefghijklmnop";
+const char kTestFileSystemFileData2[] = "File system file test data.";
+const char kTestContentType[] = "foo/bar";
+const char kTestContentDisposition[] = "attachment; filename=foo.txt";
+
+const char kFileSystemURLOrigin[] = "http://remote";
+const fileapi::FileSystemType kFileSystemType =
+ fileapi::kFileSystemTypeTemporary;
+
+} // namespace
+
+class BlobURLRequestJobTest : public testing::Test {
+ public:
+
+ // Test Harness -------------------------------------------------------------
+ // TODO(jianli): share this test harness with AppCacheURLRequestJobTest
+
+ class MockURLRequestDelegate : public net::URLRequest::Delegate {
+ public:
+ MockURLRequestDelegate()
+ : received_data_(new net::IOBuffer(kBufferSize)) {}
+
+ virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE {
+ if (request->status().is_success()) {
+ EXPECT_TRUE(request->response_headers());
+ ReadSome(request);
+ } else {
+ RequestComplete();
+ }
+ }
+
+ virtual void OnReadCompleted(net::URLRequest* request,
+ int bytes_read) OVERRIDE {
+ if (bytes_read > 0)
+ ReceiveData(request, bytes_read);
+ else
+ RequestComplete();
+ }
+
+ const std::string& response_data() const { return response_data_; }
+
+ private:
+ void ReadSome(net::URLRequest* request) {
+ if (!request->is_pending()) {
+ RequestComplete();
+ return;
+ }
+
+ int bytes_read = 0;
+ if (!request->Read(received_data_.get(), kBufferSize, &bytes_read)) {
+ if (!request->status().is_io_pending()) {
+ RequestComplete();
+ }
+ return;
+ }
+
+ ReceiveData(request, bytes_read);
+ }
+
+ void ReceiveData(net::URLRequest* request, int bytes_read) {
+ if (bytes_read) {
+ response_data_.append(received_data_->data(),
+ static_cast<size_t>(bytes_read));
+ ReadSome(request);
+ } else {
+ RequestComplete();
+ }
+ }
+
+ void RequestComplete() {
+ base::MessageLoop::current()->Quit();
+ }
+
+ scoped_refptr<net::IOBuffer> received_data_;
+ std::string response_data_;
+ };
+
+ // A simple ProtocolHandler implementation to create BlobURLRequestJob.
+ class MockProtocolHandler :
+ public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ MockProtocolHandler(BlobURLRequestJobTest* test) : test_(test) {}
+
+ // net::URLRequestJobFactory::ProtocolHandler override.
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE {
+ return new BlobURLRequestJob(request,
+ network_delegate,
+ test_->blob_data_.get(),
+ test_->file_system_context_.get(),
+ base::MessageLoopProxy::current().get());
+ }
+
+ private:
+ BlobURLRequestJobTest* test_;
+ };
+
+ BlobURLRequestJobTest()
+ : message_loop_(base::MessageLoop::TYPE_IO),
+ blob_data_(new BlobData()),
+ expected_status_code_(0) {}
+
+ virtual void SetUp() {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ temp_file1_ = temp_dir_.path().AppendASCII("BlobFile1.dat");
+ ASSERT_EQ(static_cast<int>(arraysize(kTestFileData1) - 1),
+ file_util::WriteFile(temp_file1_, kTestFileData1,
+ arraysize(kTestFileData1) - 1));
+ base::PlatformFileInfo file_info1;
+ file_util::GetFileInfo(temp_file1_, &file_info1);
+ temp_file_modification_time1_ = file_info1.last_modified;
+
+ temp_file2_ = temp_dir_.path().AppendASCII("BlobFile2.dat");
+ ASSERT_EQ(static_cast<int>(arraysize(kTestFileData2) - 1),
+ file_util::WriteFile(temp_file2_, kTestFileData2,
+ arraysize(kTestFileData2) - 1));
+ base::PlatformFileInfo file_info2;
+ file_util::GetFileInfo(temp_file2_, &file_info2);
+ temp_file_modification_time2_ = file_info2.last_modified;
+
+ url_request_job_factory_.SetProtocolHandler("blob",
+ new MockProtocolHandler(this));
+ url_request_context_.set_job_factory(&url_request_job_factory_);
+ }
+
+ virtual void TearDown() {
+ }
+
+ void SetUpFileSystem() {
+ // Prepare file system.
+ file_system_context_ = fileapi::CreateFileSystemContextForTesting(
+ NULL, temp_dir_.path());
+
+ file_system_context_->OpenFileSystem(
+ GURL(kFileSystemURLOrigin),
+ kFileSystemType,
+ fileapi::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&BlobURLRequestJobTest::OnValidateFileSystem,
+ base::Unretained(this)));
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_TRUE(file_system_root_url_.is_valid());
+
+ // Prepare files on file system.
+ const char kFilename1[] = "FileSystemFile1.dat";
+ temp_file_system_file1_ = GetFileSystemURL(kFilename1);
+ WriteFileSystemFile(kFilename1, kTestFileSystemFileData1,
+ arraysize(kTestFileSystemFileData1) - 1,
+ &temp_file_system_file_modification_time1_);
+ const char kFilename2[] = "FileSystemFile2.dat";
+ temp_file_system_file2_ = GetFileSystemURL(kFilename2);
+ WriteFileSystemFile(kFilename2, kTestFileSystemFileData2,
+ arraysize(kTestFileSystemFileData2) - 1,
+ &temp_file_system_file_modification_time2_);
+ }
+
+ GURL GetFileSystemURL(const std::string& filename) {
+ return GURL(file_system_root_url_.spec() + filename);
+ }
+
+ void WriteFileSystemFile(const std::string& filename,
+ const char* buf, int buf_size,
+ base::Time* modification_time) {
+ fileapi::FileSystemURL url =
+ file_system_context_->CreateCrackedFileSystemURL(
+ GURL(kFileSystemURLOrigin),
+ kFileSystemType,
+ base::FilePath().AppendASCII(filename));
+
+ fileapi::FileSystemFileUtil* file_util =
+ file_system_context_->GetFileUtil(kFileSystemType);
+
+ fileapi::FileSystemOperationContext context(file_system_context_.get());
+ context.set_allowed_bytes_growth(1024);
+
+ base::PlatformFile handle = base::kInvalidPlatformFileValue;
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK, file_util->CreateOrOpen(
+ &context,
+ url,
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE,
+ &handle,
+ &created));
+ EXPECT_TRUE(created);
+ ASSERT_NE(base::kInvalidPlatformFileValue, handle);
+ ASSERT_EQ(buf_size,
+ base::WritePlatformFile(handle, 0 /* offset */, buf, buf_size));
+ base::ClosePlatformFile(handle);
+
+ base::PlatformFileInfo file_info;
+ base::FilePath platform_path;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util->GetFileInfo(&context, url, &file_info,
+ &platform_path));
+ if (modification_time)
+ *modification_time = file_info.last_modified;
+ }
+
+ void OnValidateFileSystem(base::PlatformFileError result,
+ const std::string& name,
+ const GURL& root) {
+ ASSERT_EQ(base::PLATFORM_FILE_OK, result);
+ ASSERT_TRUE(root.is_valid());
+ file_system_root_url_ = root;
+ }
+
+ void TestSuccessRequest(const std::string& expected_response) {
+ expected_status_code_ = 200;
+ expected_response_ = expected_response;
+ TestRequest("GET", net::HttpRequestHeaders());
+ }
+
+ void TestErrorRequest(int expected_status_code) {
+ expected_status_code_ = expected_status_code;
+ expected_response_ = "";
+ TestRequest("GET", net::HttpRequestHeaders());
+ }
+
+ void TestRequest(const std::string& method,
+ const net::HttpRequestHeaders& extra_headers) {
+ request_.reset(url_request_context_.CreateRequest(
+ GURL("blob:blah"), &url_request_delegate_));
+ request_->set_method(method);
+ if (!extra_headers.IsEmpty())
+ request_->SetExtraRequestHeaders(extra_headers);
+ request_->Start();
+
+ base::MessageLoop::current()->Run();
+
+ // Verify response.
+ EXPECT_TRUE(request_->status().is_success());
+ EXPECT_EQ(expected_status_code_,
+ request_->response_headers()->response_code());
+ EXPECT_EQ(expected_response_, url_request_delegate_.response_data());
+ }
+
+ void BuildComplicatedData(std::string* expected_result) {
+ blob_data_->AppendData(kTestData1 + 1, 2);
+ blob_data_->AppendFile(temp_file1_, 2, 3, temp_file_modification_time1_);
+ blob_data_->AppendFileSystemFile(temp_file_system_file1_, 3, 4,
+ temp_file_system_file_modification_time1_);
+ blob_data_->AppendData(kTestData2 + 4, 5);
+ blob_data_->AppendFile(temp_file2_, 5, 6, temp_file_modification_time2_);
+ blob_data_->AppendFileSystemFile(temp_file_system_file2_, 6, 7,
+ temp_file_system_file_modification_time2_);
+ *expected_result = std::string(kTestData1 + 1, 2);
+ *expected_result += std::string(kTestFileData1 + 2, 3);
+ *expected_result += std::string(kTestFileSystemFileData1 + 3, 4);
+ *expected_result += std::string(kTestData2 + 4, 5);
+ *expected_result += std::string(kTestFileData2 + 5, 6);
+ *expected_result += std::string(kTestFileSystemFileData2 + 6, 7);
+ }
+
+ protected:
+ base::ScopedTempDir temp_dir_;
+ base::FilePath temp_file1_;
+ base::FilePath temp_file2_;
+ base::Time temp_file_modification_time1_;
+ base::Time temp_file_modification_time2_;
+ GURL file_system_root_url_;
+ GURL temp_file_system_file1_;
+ GURL temp_file_system_file2_;
+ base::Time temp_file_system_file_modification_time1_;
+ base::Time temp_file_system_file_modification_time2_;
+
+ base::MessageLoop message_loop_;
+ scoped_refptr<fileapi::FileSystemContext> file_system_context_;
+ scoped_refptr<BlobData> blob_data_;
+ net::URLRequestJobFactoryImpl url_request_job_factory_;
+ net::URLRequestContext url_request_context_;
+ MockURLRequestDelegate url_request_delegate_;
+ scoped_ptr<net::URLRequest> request_;
+
+ int expected_status_code_;
+ std::string expected_response_;
+};
+
+TEST_F(BlobURLRequestJobTest, TestGetSimpleDataRequest) {
+ blob_data_->AppendData(kTestData1);
+ TestSuccessRequest(kTestData1);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetSimpleFileRequest) {
+ blob_data_->AppendFile(temp_file1_, 0, -1, base::Time());
+ TestSuccessRequest(kTestFileData1);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetLargeFileRequest) {
+ base::FilePath large_temp_file = temp_dir_.path().AppendASCII("LargeBlob.dat");
+ std::string large_data;
+ large_data.reserve(kBufferSize * 5);
+ for (int i = 0; i < kBufferSize * 5; ++i)
+ large_data.append(1, static_cast<char>(i % 256));
+ ASSERT_EQ(static_cast<int>(large_data.size()),
+ file_util::WriteFile(large_temp_file, large_data.data(),
+ large_data.size()));
+ blob_data_->AppendFile(large_temp_file, 0, -1, base::Time());
+ TestSuccessRequest(large_data);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetNonExistentFileRequest) {
+ base::FilePath non_existent_file =
+ temp_file1_.InsertBeforeExtension(FILE_PATH_LITERAL("-na"));
+ blob_data_->AppendFile(non_existent_file, 0, -1, base::Time());
+ TestErrorRequest(404);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetChangedFileRequest) {
+ base::Time old_time =
+ temp_file_modification_time1_ - base::TimeDelta::FromSeconds(10);
+ blob_data_->AppendFile(temp_file1_, 0, 3, old_time);
+ TestErrorRequest(404);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetSlicedFileRequest) {
+ blob_data_->AppendFile(temp_file1_, 2, 4, temp_file_modification_time1_);
+ std::string result(kTestFileData1 + 2, 4);
+ TestSuccessRequest(result);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetSimpleFileSystemFileRequest) {
+ SetUpFileSystem();
+ blob_data_->AppendFileSystemFile(temp_file_system_file1_, 0, -1,
+ base::Time());
+ TestSuccessRequest(kTestFileSystemFileData1);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetLargeFileSystemFileRequest) {
+ SetUpFileSystem();
+ std::string large_data;
+ large_data.reserve(kBufferSize * 5);
+ for (int i = 0; i < kBufferSize * 5; ++i)
+ large_data.append(1, static_cast<char>(i % 256));
+
+ const char kFilename[] = "LargeBlob.dat";
+ WriteFileSystemFile(kFilename, large_data.data(), large_data.size(), NULL);
+
+ blob_data_->AppendFileSystemFile(GetFileSystemURL(kFilename),
+ 0, -1, base::Time());
+ TestSuccessRequest(large_data);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetNonExistentFileSystemFileRequest) {
+ SetUpFileSystem();
+ GURL non_existent_file = GetFileSystemURL("non-existent.dat");
+ blob_data_->AppendFileSystemFile(non_existent_file, 0, -1, base::Time());
+ TestErrorRequest(404);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetChangedFileSystemFileRequest) {
+ SetUpFileSystem();
+ base::Time old_time =
+ temp_file_system_file_modification_time1_ -
+ base::TimeDelta::FromSeconds(10);
+ blob_data_->AppendFileSystemFile(temp_file_system_file1_, 0, 3, old_time);
+ TestErrorRequest(404);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetSlicedFileSystemFileRequest) {
+ SetUpFileSystem();
+ blob_data_->AppendFileSystemFile(temp_file_system_file1_, 2, 4,
+ temp_file_system_file_modification_time1_);
+ std::string result(kTestFileSystemFileData1 + 2, 4);
+ TestSuccessRequest(result);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetComplicatedDataAndFileRequest) {
+ SetUpFileSystem();
+ std::string result;
+ BuildComplicatedData(&result);
+ TestSuccessRequest(result);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetRangeRequest1) {
+ SetUpFileSystem();
+ std::string result;
+ BuildComplicatedData(&result);
+ net::HttpRequestHeaders extra_headers;
+ extra_headers.SetHeader(net::HttpRequestHeaders::kRange, "bytes=5-10");
+ expected_status_code_ = 206;
+ expected_response_ = result.substr(5, 10 - 5 + 1);
+ TestRequest("GET", extra_headers);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetRangeRequest2) {
+ SetUpFileSystem();
+ std::string result;
+ BuildComplicatedData(&result);
+ net::HttpRequestHeaders extra_headers;
+ extra_headers.SetHeader(net::HttpRequestHeaders::kRange, "bytes=-10");
+ expected_status_code_ = 206;
+ expected_response_ = result.substr(result.length() - 10);
+ TestRequest("GET", extra_headers);
+}
+
+TEST_F(BlobURLRequestJobTest, TestExtraHeaders) {
+ blob_data_->set_content_type(kTestContentType);
+ blob_data_->set_content_disposition(kTestContentDisposition);
+ blob_data_->AppendData(kTestData1);
+ expected_status_code_ = 200;
+ expected_response_ = kTestData1;
+ TestRequest("GET", net::HttpRequestHeaders());
+
+ std::string content_type;
+ EXPECT_TRUE(request_->response_headers()->GetMimeType(&content_type));
+ EXPECT_EQ(kTestContentType, content_type);
+ void* iter = NULL;
+ std::string content_disposition;
+ EXPECT_TRUE(request_->response_headers()->EnumerateHeader(
+ &iter, "Content-Disposition", &content_disposition));
+ EXPECT_EQ(kTestContentDisposition, content_disposition);
+}
+
+} // namespace webkit_blob
diff --git a/chromium/webkit/browser/blob/file_stream_reader.h b/chromium/webkit/browser/blob/file_stream_reader.h
new file mode 100644
index 00000000000..dd49a52c2bc
--- /dev/null
+++ b/chromium/webkit/browser/blob/file_stream_reader.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BLOB_FILE_STREAM_READER_H_
+#define WEBKIT_BLOB_FILE_STREAM_READER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/completion_callback.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace net {
+class IOBuffer;
+}
+
+namespace webkit_blob {
+
+// A generic interface for reading a file-like object.
+class WEBKIT_STORAGE_BROWSER_EXPORT FileStreamReader {
+ public:
+ // It is valid to delete the reader at any time. If the stream is deleted
+ // while it has a pending read, its callback will not be called.
+ virtual ~FileStreamReader() {}
+
+ // Reads from the current cursor position asynchronously.
+ //
+ // Up to buf_len bytes will be copied into buf. (In other words, partial
+ // reads are allowed.) Returns the number of bytes copied, 0 if at
+ // end-of-file, or an error code if the operation could not be performed.
+ // If the read could not complete synchronously, then ERR_IO_PENDING is
+ // returned, and the callback will be run on the thread where Read()
+ // was called, when the read has completed.
+ //
+ // It is invalid to call Read while there is an in-flight Read operation.
+ //
+ // If the stream is deleted while it has an in-flight Read operation
+ // |callback| will not be called.
+ virtual int Read(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) = 0;
+
+ // Returns the length of the file if it could successfully retrieve the
+ // file info *and* its last modification time equals to
+ // expected modification time (rv >= 0 cases).
+ // Otherwise, a negative error code is returned (rv < 0 cases).
+ // If the stream is deleted while it has an in-flight GetLength operation
+ // |callback| will not be called.
+ // Note that the return type is int64 to return a larger file's size (a file
+ // larger than 2G) but an error code should fit in the int range (may be
+ // smaller than int64 range).
+ virtual int64 GetLength(const net::Int64CompletionCallback& callback) = 0;
+};
+
+} // namespace webkit_blob
+
+#endif // WEBKIT_BLOB_FILE_STREAM_READER_H_
diff --git a/chromium/webkit/browser/blob/local_file_stream_reader.cc b/chromium/webkit/browser/blob/local_file_stream_reader.cc
new file mode 100644
index 00000000000..a72709cb800
--- /dev/null
+++ b/chromium/webkit/browser/blob/local_file_stream_reader.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/blob/local_file_stream_reader.h"
+
+#include "base/file_util.h"
+#include "base/files/file_util_proxy.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/platform_file.h"
+#include "base/task_runner.h"
+#include "net/base/file_stream.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+namespace webkit_blob {
+
+namespace {
+
+const int kOpenFlagsForRead = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_ASYNC;
+
+// Verify if the underlying file has not been modified.
+bool VerifySnapshotTime(const base::Time& expected_modification_time,
+ const base::PlatformFileInfo& file_info) {
+ return expected_modification_time.is_null() ||
+ expected_modification_time.ToTimeT() ==
+ file_info.last_modified.ToTimeT();
+}
+
+} // namespace
+
+LocalFileStreamReader::LocalFileStreamReader(
+ base::TaskRunner* task_runner,
+ const base::FilePath& file_path,
+ int64 initial_offset,
+ const base::Time& expected_modification_time)
+ : task_runner_(task_runner),
+ file_path_(file_path),
+ initial_offset_(initial_offset),
+ expected_modification_time_(expected_modification_time),
+ has_pending_open_(false),
+ weak_factory_(this) {}
+
+LocalFileStreamReader::~LocalFileStreamReader() {
+}
+
+int LocalFileStreamReader::Read(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ DCHECK(!has_pending_open_);
+ if (stream_impl_)
+ return stream_impl_->Read(buf, buf_len, callback);
+ return Open(base::Bind(&LocalFileStreamReader::DidOpenForRead,
+ weak_factory_.GetWeakPtr(),
+ make_scoped_refptr(buf), buf_len, callback));
+}
+
+int64 LocalFileStreamReader::GetLength(
+ const net::Int64CompletionCallback& callback) {
+ const bool posted = base::FileUtilProxy::GetFileInfo(
+ task_runner_.get(),
+ file_path_,
+ base::Bind(&LocalFileStreamReader::DidGetFileInfoForGetLength,
+ weak_factory_.GetWeakPtr(),
+ callback));
+ DCHECK(posted);
+ return net::ERR_IO_PENDING;
+}
+
+int LocalFileStreamReader::Open(const net::CompletionCallback& callback) {
+ DCHECK(!has_pending_open_);
+ DCHECK(!stream_impl_.get());
+ has_pending_open_ = true;
+
+ // Call GetLength first to make it perform last-modified-time verification,
+ // and then call DidVerifyForOpen for do the rest.
+ return GetLength(base::Bind(&LocalFileStreamReader::DidVerifyForOpen,
+ weak_factory_.GetWeakPtr(), callback));
+}
+
+void LocalFileStreamReader::DidVerifyForOpen(
+ const net::CompletionCallback& callback,
+ int64 get_length_result) {
+ if (get_length_result < 0) {
+ callback.Run(static_cast<int>(get_length_result));
+ return;
+ }
+
+ stream_impl_.reset(new net::FileStream(NULL, task_runner_));
+ const int result = stream_impl_->Open(
+ file_path_, kOpenFlagsForRead,
+ base::Bind(&LocalFileStreamReader::DidOpenFileStream,
+ weak_factory_.GetWeakPtr(),
+ callback));
+ if (result != net::ERR_IO_PENDING)
+ callback.Run(result);
+}
+
+void LocalFileStreamReader::DidOpenFileStream(
+ const net::CompletionCallback& callback,
+ int result) {
+ if (result != net::OK) {
+ callback.Run(result);
+ return;
+ }
+ result = stream_impl_->Seek(
+ net::FROM_BEGIN, initial_offset_,
+ base::Bind(&LocalFileStreamReader::DidSeekFileStream,
+ weak_factory_.GetWeakPtr(),
+ callback));
+ if (result != net::ERR_IO_PENDING) {
+ callback.Run(result);
+ }
+}
+
+void LocalFileStreamReader::DidSeekFileStream(
+ const net::CompletionCallback& callback,
+ int64 seek_result) {
+ if (seek_result < 0) {
+ callback.Run(static_cast<int>(seek_result));
+ return;
+ }
+ if (seek_result != initial_offset_) {
+ callback.Run(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
+ return;
+ }
+ callback.Run(net::OK);
+}
+
+void LocalFileStreamReader::DidOpenForRead(
+ net::IOBuffer* buf,
+ int buf_len,
+ const net::CompletionCallback& callback,
+ int open_result) {
+ DCHECK(has_pending_open_);
+ has_pending_open_ = false;
+ if (open_result != net::OK) {
+ stream_impl_.reset();
+ callback.Run(open_result);
+ return;
+ }
+ DCHECK(stream_impl_.get());
+ const int read_result = stream_impl_->Read(buf, buf_len, callback);
+ if (read_result != net::ERR_IO_PENDING)
+ callback.Run(read_result);
+}
+
+void LocalFileStreamReader::DidGetFileInfoForGetLength(
+ const net::Int64CompletionCallback& callback,
+ base::PlatformFileError error,
+ const base::PlatformFileInfo& file_info) {
+ if (file_info.is_directory) {
+ callback.Run(net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+ if (error != base::PLATFORM_FILE_OK) {
+ callback.Run(net::PlatformFileErrorToNetError(error));
+ return;
+ }
+ if (!VerifySnapshotTime(expected_modification_time_, file_info)) {
+ callback.Run(net::ERR_UPLOAD_FILE_CHANGED);
+ return;
+ }
+ callback.Run(file_info.size);
+}
+
+} // namespace webkit_blob
diff --git a/chromium/webkit/browser/blob/local_file_stream_reader.h b/chromium/webkit/browser/blob/local_file_stream_reader.h
new file mode 100644
index 00000000000..143a43bf87e
--- /dev/null
+++ b/chromium/webkit/browser/blob/local_file_stream_reader.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_BLOB_LOCAL_FILE_STREAM_READER_H_
+#define WEBKIT_BROWSER_BLOB_LOCAL_FILE_STREAM_READER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/platform_file.h"
+#include "base/time/time.h"
+#include "webkit/browser/blob/file_stream_reader.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace base {
+class TaskRunner;
+}
+
+namespace net {
+class FileStream;
+}
+
+namespace webkit_blob {
+
+// A thin wrapper of net::FileStream with range support for sliced file
+// handling.
+class WEBKIT_STORAGE_BROWSER_EXPORT LocalFileStreamReader
+ : public FileStreamReader {
+ public:
+ // Creates a new FileReader for a local file |file_path|.
+ // |initial_offset| specifies the offset in the file where the first read
+ // should start. If the given offset is out of the file range any
+ // read operation may error out with net::ERR_REQUEST_RANGE_NOT_SATISFIABLE.
+ //
+ // |expected_modification_time| specifies the expected last modification
+ // If the value is non-null, the reader will check the underlying file's
+ // actual modification time to see if the file has been modified, and if
+ // it does any succeeding read operations should fail with
+ // ERR_UPLOAD_FILE_CHANGED error.
+ LocalFileStreamReader(base::TaskRunner* task_runner,
+ const base::FilePath& file_path,
+ int64 initial_offset,
+ const base::Time& expected_modification_time);
+ virtual ~LocalFileStreamReader();
+
+ // FileStreamReader overrides.
+ virtual int Read(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int64 GetLength(
+ const net::Int64CompletionCallback& callback) OVERRIDE;
+
+ private:
+ int Open(const net::CompletionCallback& callback);
+
+ // Callbacks that are chained from Open for Read.
+ void DidVerifyForOpen(const net::CompletionCallback& callback,
+ int64 get_length_result);
+ void DidOpenFileStream(const net::CompletionCallback& callback,
+ int result);
+ void DidSeekFileStream(const net::CompletionCallback& callback,
+ int64 seek_result);
+ void DidOpenForRead(net::IOBuffer* buf,
+ int buf_len,
+ const net::CompletionCallback& callback,
+ int open_result);
+
+ void DidGetFileInfoForGetLength(const net::Int64CompletionCallback& callback,
+ base::PlatformFileError error,
+ const base::PlatformFileInfo& file_info);
+
+ scoped_refptr<base::TaskRunner> task_runner_;
+ scoped_ptr<net::FileStream> stream_impl_;
+ const base::FilePath file_path_;
+ const int64 initial_offset_;
+ const base::Time expected_modification_time_;
+ bool has_pending_open_;
+ base::WeakPtrFactory<LocalFileStreamReader> weak_factory_;
+};
+
+} // namespace webkit_blob
+
+#endif // WEBKIT_BROWSER_BLOB_LOCAL_FILE_STREAM_READER_H_
diff --git a/chromium/webkit/browser/blob/local_file_stream_reader_unittest.cc b/chromium/webkit/browser/blob/local_file_stream_reader_unittest.cc
new file mode 100644
index 00000000000..f1ec01dbe3a
--- /dev/null
+++ b/chromium/webkit/browser/blob/local_file_stream_reader_unittest.cc
@@ -0,0 +1,262 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/blob/local_file_stream_reader.h"
+
+#include <string>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/platform_file.h"
+#include "base/threading/thread.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webkit_blob {
+
+namespace {
+
+const char kTestData[] = "0123456789";
+const int kTestDataSize = arraysize(kTestData) - 1;
+
+void ReadFromReader(LocalFileStreamReader* reader,
+ std::string* data, size_t size,
+ int* result) {
+ ASSERT_TRUE(reader != NULL);
+ ASSERT_TRUE(result != NULL);
+ *result = net::OK;
+ net::TestCompletionCallback callback;
+ size_t total_bytes_read = 0;
+ while (total_bytes_read < size) {
+ scoped_refptr<net::IOBufferWithSize> buf(
+ new net::IOBufferWithSize(size - total_bytes_read));
+ int rv = reader->Read(buf.get(), buf->size(), callback.callback());
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ if (rv < 0)
+ *result = rv;
+ if (rv <= 0)
+ break;
+ total_bytes_read += rv;
+ data->append(buf->data(), rv);
+ }
+}
+
+void NeverCalled(int) { ADD_FAILURE(); }
+void EmptyCallback() {}
+
+void QuitLoop() {
+ base::MessageLoop::current()->Quit();
+}
+
+} // namespace
+
+class LocalFileStreamReaderTest : public testing::Test {
+ public:
+ LocalFileStreamReaderTest()
+ : message_loop_(base::MessageLoop::TYPE_IO),
+ file_thread_("FileUtilProxyTestFileThread") {}
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(file_thread_.Start());
+ ASSERT_TRUE(dir_.CreateUniqueTempDir());
+
+ file_util::WriteFile(test_path(), kTestData, kTestDataSize);
+ base::PlatformFileInfo info;
+ ASSERT_TRUE(file_util::GetFileInfo(test_path(), &info));
+ test_file_modification_time_ = info.last_modified;
+ }
+
+ virtual void TearDown() OVERRIDE {
+ // Give another chance for deleted streams to perform Close.
+ base::MessageLoop::current()->RunUntilIdle();
+ file_thread_.Stop();
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ protected:
+ LocalFileStreamReader* CreateFileReader(
+ const base::FilePath& path,
+ int64 initial_offset,
+ const base::Time& expected_modification_time) {
+ return new LocalFileStreamReader(
+ file_task_runner(),
+ path,
+ initial_offset,
+ expected_modification_time);
+ }
+
+ void TouchTestFile() {
+ base::Time new_modified_time =
+ test_file_modification_time() - base::TimeDelta::FromSeconds(1);
+ ASSERT_TRUE(file_util::TouchFile(test_path(),
+ test_file_modification_time(),
+ new_modified_time));
+ }
+
+ base::MessageLoopProxy* file_task_runner() const {
+ return file_thread_.message_loop_proxy().get();
+ }
+
+ base::FilePath test_dir() const { return dir_.path(); }
+ base::FilePath test_path() const { return dir_.path().AppendASCII("test"); }
+ base::Time test_file_modification_time() const {
+ return test_file_modification_time_;
+ }
+
+ void EnsureFileTaskFinished() {
+ file_task_runner()->PostTaskAndReply(
+ FROM_HERE, base::Bind(&EmptyCallback), base::Bind(&QuitLoop));
+ base::MessageLoop::current()->Run();
+ }
+
+ private:
+ base::MessageLoop message_loop_;
+ base::Thread file_thread_;
+ base::ScopedTempDir dir_;
+ base::Time test_file_modification_time_;
+};
+
+TEST_F(LocalFileStreamReaderTest, NonExistent) {
+ base::FilePath nonexistent_path = test_dir().AppendASCII("nonexistent");
+ scoped_ptr<LocalFileStreamReader> reader(
+ CreateFileReader(nonexistent_path, 0, base::Time()));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, 10, &result);
+ ASSERT_EQ(net::ERR_FILE_NOT_FOUND, result);
+ ASSERT_EQ(0U, data.size());
+}
+
+TEST_F(LocalFileStreamReaderTest, Empty) {
+ base::FilePath empty_path = test_dir().AppendASCII("empty");
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ base::PlatformFile file = base::CreatePlatformFile(
+ empty_path,
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_READ,
+ NULL, &error);
+ ASSERT_EQ(base::PLATFORM_FILE_OK, error);
+ ASSERT_NE(base::kInvalidPlatformFileValue, file);
+ base::ClosePlatformFile(file);
+
+ scoped_ptr<LocalFileStreamReader> reader(
+ CreateFileReader(empty_path, 0, base::Time()));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, 10, &result);
+ ASSERT_EQ(net::OK, result);
+ ASSERT_EQ(0U, data.size());
+
+ net::TestInt64CompletionCallback callback;
+ int64 length_result = reader->GetLength(callback.callback());
+ if (length_result == net::ERR_IO_PENDING)
+ length_result = callback.WaitForResult();
+ ASSERT_EQ(0, result);
+}
+
+TEST_F(LocalFileStreamReaderTest, GetLengthNormal) {
+ scoped_ptr<LocalFileStreamReader> reader(
+ CreateFileReader(test_path(), 0, test_file_modification_time()));
+ net::TestInt64CompletionCallback callback;
+ int64 result = reader->GetLength(callback.callback());
+ if (result == net::ERR_IO_PENDING)
+ result = callback.WaitForResult();
+ ASSERT_EQ(kTestDataSize, result);
+}
+
+TEST_F(LocalFileStreamReaderTest, GetLengthAfterModified) {
+ // Touch file so that the file's modification time becomes different
+ // from what we expect.
+ TouchTestFile();
+
+ scoped_ptr<LocalFileStreamReader> reader(
+ CreateFileReader(test_path(), 0, test_file_modification_time()));
+ net::TestInt64CompletionCallback callback;
+ int64 result = reader->GetLength(callback.callback());
+ if (result == net::ERR_IO_PENDING)
+ result = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result);
+
+ // With NULL expected modification time this should work.
+ reader.reset(CreateFileReader(test_path(), 0, base::Time()));
+ result = reader->GetLength(callback.callback());
+ if (result == net::ERR_IO_PENDING)
+ result = callback.WaitForResult();
+ ASSERT_EQ(kTestDataSize, result);
+}
+
+TEST_F(LocalFileStreamReaderTest, GetLengthWithOffset) {
+ scoped_ptr<LocalFileStreamReader> reader(
+ CreateFileReader(test_path(), 3, base::Time()));
+ net::TestInt64CompletionCallback callback;
+ int64 result = reader->GetLength(callback.callback());
+ if (result == net::ERR_IO_PENDING)
+ result = callback.WaitForResult();
+ // Initial offset does not affect the result of GetLength.
+ ASSERT_EQ(kTestDataSize, result);
+}
+
+TEST_F(LocalFileStreamReaderTest, ReadNormal) {
+ scoped_ptr<LocalFileStreamReader> reader(
+ CreateFileReader(test_path(), 0, test_file_modification_time()));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, kTestDataSize, &result);
+ ASSERT_EQ(net::OK, result);
+ ASSERT_EQ(kTestData, data);
+}
+
+TEST_F(LocalFileStreamReaderTest, ReadAfterModified) {
+ // Touch file so that the file's modification time becomes different
+ // from what we expect.
+ TouchTestFile();
+
+ scoped_ptr<LocalFileStreamReader> reader(
+ CreateFileReader(test_path(), 0, test_file_modification_time()));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, kTestDataSize, &result);
+ ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result);
+ ASSERT_EQ(0U, data.size());
+
+ // With NULL expected modification time this should work.
+ data.clear();
+ reader.reset(CreateFileReader(test_path(), 0, base::Time()));
+ ReadFromReader(reader.get(), &data, kTestDataSize, &result);
+ ASSERT_EQ(net::OK, result);
+ ASSERT_EQ(kTestData, data);
+}
+
+TEST_F(LocalFileStreamReaderTest, ReadWithOffset) {
+ scoped_ptr<LocalFileStreamReader> reader(
+ CreateFileReader(test_path(), 3, base::Time()));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, kTestDataSize, &result);
+ ASSERT_EQ(net::OK, result);
+ ASSERT_EQ(&kTestData[3], data);
+}
+
+TEST_F(LocalFileStreamReaderTest, DeleteWithUnfinishedRead) {
+ scoped_ptr<LocalFileStreamReader> reader(
+ CreateFileReader(test_path(), 0, base::Time()));
+
+ net::TestCompletionCallback callback;
+ scoped_refptr<net::IOBufferWithSize> buf(
+ new net::IOBufferWithSize(kTestDataSize));
+ int rv = reader->Read(buf.get(), buf->size(), base::Bind(&NeverCalled));
+ ASSERT_TRUE(rv == net::ERR_IO_PENDING || rv >= 0);
+
+ // Delete immediately.
+ // Should not crash; nor should NeverCalled be callback.
+ reader.reset();
+ EnsureFileTaskFinished();
+}
+
+} // namespace webkit_blob
diff --git a/chromium/webkit/browser/blob/mock_blob_url_request_context.cc b/chromium/webkit/browser/blob/mock_blob_url_request_context.cc
new file mode 100644
index 00000000000..45c61340bfb
--- /dev/null
+++ b/chromium/webkit/browser/blob/mock_blob_url_request_context.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/blob/mock_blob_url_request_context.h"
+
+#include "webkit/browser/blob/blob_storage_controller.h"
+#include "webkit/browser/blob/blob_url_request_job.h"
+#include "webkit/common/blob/blob_data.h"
+
+namespace webkit_blob {
+
+namespace {
+
+class MockBlobProtocolHandler
+ : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ explicit MockBlobProtocolHandler(
+ BlobStorageController* blob_storage_controller,
+ fileapi::FileSystemContext* file_system_context)
+ : blob_storage_controller_(blob_storage_controller),
+ file_system_context_(file_system_context) {}
+
+ virtual ~MockBlobProtocolHandler() {}
+
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE {
+ return new BlobURLRequestJob(
+ request,
+ network_delegate,
+ blob_storage_controller_->GetBlobDataFromUrl(request->url()),
+ file_system_context_,
+ base::MessageLoopProxy::current().get());
+ }
+
+ private:
+ webkit_blob::BlobStorageController* const blob_storage_controller_;
+ fileapi::FileSystemContext* const file_system_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockBlobProtocolHandler);
+};
+
+} // namespace
+
+MockBlobURLRequestContext::MockBlobURLRequestContext(
+ fileapi::FileSystemContext* file_system_context)
+ : blob_storage_controller_(new BlobStorageController) {
+ // Job factory owns the protocol handler.
+ job_factory_.SetProtocolHandler(
+ "blob", new MockBlobProtocolHandler(blob_storage_controller_.get(),
+ file_system_context));
+ set_job_factory(&job_factory_);
+}
+
+MockBlobURLRequestContext::~MockBlobURLRequestContext() {}
+
+ScopedTextBlob::ScopedTextBlob(
+ const MockBlobURLRequestContext& request_context,
+ const GURL& blob_url,
+ const std::string& data)
+ : blob_url_(blob_url),
+ blob_storage_controller_(request_context.blob_storage_controller()) {
+ DCHECK(blob_storage_controller_);
+ scoped_refptr<BlobData> blob_data(new BlobData());
+ blob_data->AppendData(data);
+ blob_storage_controller_->AddFinishedBlob(blob_url_, blob_data.get());
+}
+
+ScopedTextBlob::~ScopedTextBlob() {
+ blob_storage_controller_->RemoveBlob(blob_url_);
+}
+
+} // namespace webkit_blob
diff --git a/chromium/webkit/browser/blob/mock_blob_url_request_context.h b/chromium/webkit/browser/blob/mock_blob_url_request_context.h
new file mode 100644
index 00000000000..b89027c95e7
--- /dev/null
+++ b/chromium/webkit/browser/blob/mock_blob_url_request_context.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BLOB_MOCK_BLOB_URL_REQUEST_CONTEXT_H_
+#define WEBKIT_BLOB_MOCK_BLOB_URL_REQUEST_CONTEXT_H_
+
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+
+namespace fileapi {
+class FileSystemContext;
+}
+
+namespace webkit_blob {
+
+class BlobStorageController;
+
+class MockBlobURLRequestContext : public net::URLRequestContext {
+ public:
+ MockBlobURLRequestContext(fileapi::FileSystemContext* file_system_context);
+ virtual ~MockBlobURLRequestContext();
+
+ BlobStorageController* blob_storage_controller() const {
+ return blob_storage_controller_.get();
+ }
+
+ private:
+ net::URLRequestJobFactoryImpl job_factory_;
+ scoped_ptr<BlobStorageController> blob_storage_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockBlobURLRequestContext);
+};
+
+class ScopedTextBlob {
+ public:
+ ScopedTextBlob(const MockBlobURLRequestContext& request_context,
+ const GURL& blob_url,
+ const std::string& data);
+ ~ScopedTextBlob();
+
+ private:
+ const GURL blob_url_;
+ BlobStorageController* blob_storage_controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedTextBlob);
+};
+
+} // namespace webkit_blob
+
+#endif // WEBKIT_BLOB_MOCK_BLOB_URL_REQUEST_CONTEXT_H_
diff --git a/chromium/webkit/browser/blob/view_blob_internals_job.cc b/chromium/webkit/browser/blob/view_blob_internals_job.cc
new file mode 100644
index 00000000000..41ef7883de5
--- /dev/null
+++ b/chromium/webkit/browser/blob/view_blob_internals_job.cc
@@ -0,0 +1,248 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/blob/view_blob_internals_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/format_macros.h"
+#include "base/i18n/number_formatting.h"
+#include "base/i18n/time_formatting.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/escape.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request.h"
+#include "webkit/browser/blob/blob_storage_controller.h"
+#include "webkit/common/blob/blob_data.h"
+
+namespace {
+
+const char kEmptyBlobStorageMessage[] = "No available blob data.";
+const char kRemove[] = "Remove";
+const char kContentType[] = "Content Type: ";
+const char kContentDisposition[] = "Content Disposition: ";
+const char kCount[] = "Count: ";
+const char kIndex[] = "Index: ";
+const char kType[] = "Type: ";
+const char kPath[] = "Path: ";
+const char kURL[] = "URL: ";
+const char kModificationTime[] = "Modification Time: ";
+const char kOffset[] = "Offset: ";
+const char kLength[] = "Length: ";
+
+void StartHTML(std::string* out) {
+ out->append(
+ "<!DOCTYPE HTML>"
+ "<html><title>Blob Storage Internals</title>"
+ "<meta http-equiv=\"Content-Security-Policy\""
+ " content=\"object-src 'none'; script-src 'none'\">\n"
+ "<style>\n"
+ "body { font-family: sans-serif; font-size: 0.8em; }\n"
+ "tt, code, pre { font-family: WebKitHack, monospace; }\n"
+ "form { display: inline }\n"
+ ".subsection_body { margin: 10px 0 10px 2em; }\n"
+ ".subsection_title { font-weight: bold; }\n"
+ "</style>\n"
+ "</head><body>\n");
+}
+
+void EndHTML(std::string* out) {
+ out->append("</body></html>");
+}
+
+void AddHTMLBoldText(const std::string& text, std::string* out) {
+ out->append("<b>");
+ out->append(net::EscapeForHTML(text));
+ out->append("</b>");
+}
+
+void StartHTMLList(std::string* out) {
+ out->append("<ul>");
+}
+
+void EndHTMLList(std::string* out) {
+ out->append("</ul>");
+}
+
+void AddHTMLListItem(const std::string& element_title,
+ const std::string& element_data,
+ std::string* out) {
+ out->append("<li>");
+ // No need to escape element_title since constant string is passed.
+ out->append(element_title);
+ out->append(net::EscapeForHTML(element_data));
+ out->append("</li>");
+}
+
+void AddHTMLButton(const std::string& title,
+ const std::string& command,
+ std::string* out) {
+ // No need to escape title since constant string is passed.
+ std::string escaped_command = net::EscapeForHTML(command.c_str());
+ base::StringAppendF(out,
+ "<form action=\"\" method=\"GET\">\n"
+ "<input type=\"hidden\" name=\"remove\" value=\"%s\">\n"
+ "<input type=\"submit\" value=\"%s\">\n"
+ "</form><br/>\n",
+ escaped_command.c_str(),
+ title.c_str());
+}
+
+} // namespace
+
+namespace webkit_blob {
+
+ViewBlobInternalsJob::ViewBlobInternalsJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ BlobStorageController* blob_storage_controller)
+ : net::URLRequestSimpleJob(request, network_delegate),
+ blob_storage_controller_(blob_storage_controller),
+ weak_factory_(this) {
+}
+
+ViewBlobInternalsJob::~ViewBlobInternalsJob() {
+}
+
+void ViewBlobInternalsJob::Start() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ViewBlobInternalsJob::DoWorkAsync,
+ weak_factory_.GetWeakPtr()));
+}
+
+bool ViewBlobInternalsJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ if (request_->url().has_query()) {
+ // Strip the query parameters.
+ GURL::Replacements replacements;
+ replacements.ClearQuery();
+ *location = request_->url().ReplaceComponents(replacements);
+ *http_status_code = 307;
+ return true;
+ }
+ return false;
+}
+
+void ViewBlobInternalsJob::Kill() {
+ net::URLRequestSimpleJob::Kill();
+ weak_factory_.InvalidateWeakPtrs();
+}
+
+void ViewBlobInternalsJob::DoWorkAsync() {
+ if (request_->url().has_query() &&
+ StartsWithASCII(request_->url().query(), "remove=", true)) {
+ std::string blob_url = request_->url().query().substr(strlen("remove="));
+ blob_url = net::UnescapeURLComponent(blob_url,
+ net::UnescapeRule::NORMAL | net::UnescapeRule::URL_SPECIAL_CHARS);
+ blob_storage_controller_->RemoveBlob(GURL(blob_url));
+ }
+
+ StartAsync();
+}
+
+int ViewBlobInternalsJob::GetData(
+ std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const net::CompletionCallback& callback) const {
+ mime_type->assign("text/html");
+ charset->assign("UTF-8");
+
+ data->clear();
+ StartHTML(data);
+ if (blob_storage_controller_->blob_map_.empty())
+ data->append(kEmptyBlobStorageMessage);
+ else
+ GenerateHTML(data);
+ EndHTML(data);
+ return net::OK;
+}
+
+void ViewBlobInternalsJob::GenerateHTML(std::string* out) const {
+ for (BlobStorageController::BlobMap::const_iterator iter =
+ blob_storage_controller_->blob_map_.begin();
+ iter != blob_storage_controller_->blob_map_.end();
+ ++iter) {
+ AddHTMLBoldText(iter->first, out);
+ AddHTMLButton(kRemove, iter->first, out);
+ GenerateHTMLForBlobData(*iter->second.get(), out);
+ }
+}
+
+void ViewBlobInternalsJob::GenerateHTMLForBlobData(const BlobData& blob_data,
+ std::string* out) {
+ StartHTMLList(out);
+
+ if (!blob_data.content_type().empty())
+ AddHTMLListItem(kContentType, blob_data.content_type(), out);
+ if (!blob_data.content_disposition().empty())
+ AddHTMLListItem(kContentDisposition, blob_data.content_disposition(), out);
+
+ bool has_multi_items = blob_data.items().size() > 1;
+ if (has_multi_items) {
+ AddHTMLListItem(kCount,
+ UTF16ToUTF8(base::FormatNumber(blob_data.items().size())), out);
+ }
+
+ for (size_t i = 0; i < blob_data.items().size(); ++i) {
+ if (has_multi_items) {
+ AddHTMLListItem(kIndex, UTF16ToUTF8(base::FormatNumber(i)), out);
+ StartHTMLList(out);
+ }
+ const BlobData::Item& item = blob_data.items().at(i);
+
+ switch (item.type()) {
+ case BlobData::Item::TYPE_BYTES:
+ AddHTMLListItem(kType, "data", out);
+ break;
+ case BlobData::Item::TYPE_FILE:
+ AddHTMLListItem(kType, "file", out);
+ AddHTMLListItem(kPath,
+ net::EscapeForHTML(item.path().AsUTF8Unsafe()),
+ out);
+ if (!item.expected_modification_time().is_null()) {
+ AddHTMLListItem(kModificationTime, UTF16ToUTF8(
+ TimeFormatFriendlyDateAndTime(item.expected_modification_time())),
+ out);
+ }
+ break;
+ case BlobData::Item::TYPE_BLOB:
+ AddHTMLListItem(kType, "blob", out);
+ AddHTMLListItem(kURL, item.url().spec(), out);
+ break;
+ case BlobData::Item::TYPE_FILE_FILESYSTEM:
+ AddHTMLListItem(kType, "filesystem", out);
+ AddHTMLListItem(kURL, item.url().spec(), out);
+ if (!item.expected_modification_time().is_null()) {
+ AddHTMLListItem(kModificationTime, UTF16ToUTF8(
+ TimeFormatFriendlyDateAndTime(item.expected_modification_time())),
+ out);
+ }
+ break;
+ case BlobData::Item::TYPE_UNKNOWN:
+ NOTREACHED();
+ break;
+ }
+ if (item.offset()) {
+ AddHTMLListItem(kOffset, UTF16ToUTF8(base::FormatNumber(
+ static_cast<int64>(item.offset()))), out);
+ }
+ if (static_cast<int64>(item.length()) != -1) {
+ AddHTMLListItem(kLength, UTF16ToUTF8(base::FormatNumber(
+ static_cast<int64>(item.length()))), out);
+ }
+
+ if (has_multi_items)
+ EndHTMLList(out);
+ }
+
+ EndHTMLList(out);
+}
+
+} // namespace webkit_blob
diff --git a/chromium/webkit/browser/blob/view_blob_internals_job.h b/chromium/webkit/browser/blob/view_blob_internals_job.h
new file mode 100644
index 00000000000..41687d9c3b4
--- /dev/null
+++ b/chromium/webkit/browser/blob/view_blob_internals_job.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_BLOB_VIEW_BLOB_INTERNALS_JOB_H_
+#define WEBKIT_BROWSER_BLOB_VIEW_BLOB_INTERNALS_JOB_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "net/url_request/url_request_simple_job.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace net {
+class URLRequest;
+} // namespace net
+
+namespace webkit_blob {
+
+class BlobData;
+class BlobStorageController;
+
+// A job subclass that implements a protocol to inspect the internal
+// state of blob registry.
+class WEBKIT_STORAGE_BROWSER_EXPORT ViewBlobInternalsJob
+ : public net::URLRequestSimpleJob {
+ public:
+ ViewBlobInternalsJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ BlobStorageController* blob_storage_controller);
+
+ virtual void Start() OVERRIDE;
+ virtual int GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const net::CompletionCallback& callback) const OVERRIDE;
+ virtual bool IsRedirectResponse(GURL* location,
+ int* http_status_code) OVERRIDE;
+ virtual void Kill() OVERRIDE;
+
+ private:
+ virtual ~ViewBlobInternalsJob();
+
+ void DoWorkAsync();
+ void GenerateHTML(std::string* out) const;
+ static void GenerateHTMLForBlobData(const BlobData& blob_data,
+ std::string* out);
+
+ BlobStorageController* blob_storage_controller_;
+ base::WeakPtrFactory<ViewBlobInternalsJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewBlobInternalsJob);
+};
+
+} // namespace webkit_blob
+
+#endif // WEBKIT_BROWSER_BLOB_VIEW_BLOB_INTERNALS_JOB_H_