diff options
Diffstat (limited to 'chromium/webkit/browser/blob')
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_ |