summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjianli@chromium.org <jianli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-31 02:42:36 +0000
committerjianli@chromium.org <jianli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-31 02:42:36 +0000
commit8107004fbfe9f7ea4be59f6620273c1206c12f42 (patch)
treef86046c0bb77f471f60d088626ad291360b356ff
parent766f613f1c7022941e2e182543d0208a61210a16 (diff)
downloadchromium_src-8107004fbfe9f7ea4be59f6620273c1206c12f42.zip
chromium_src-8107004fbfe9f7ea4be59f6620273c1206c12f42.tar.gz
chromium_src-8107004fbfe9f7ea4be59f6620273c1206c12f42.tar.bz2
Support handling blob URL and resolve blob references in upload data.
BUG=none TEST=unittest Review URL: http://codereview.chromium.org/3282003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@57938 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--base/file_util_proxy.cc38
-rw-r--r--base/file_util_proxy.h14
-rw-r--r--chrome/browser/browser_main.cc4
-rw-r--r--chrome/browser/child_process_security_policy.cc1
-rw-r--r--chrome/browser/net/blob_url_request_job_factory.cc32
-rw-r--r--chrome/browser/net/blob_url_request_job_factory.h10
-rw-r--r--chrome/browser/renderer_host/resource_dispatcher_host.cc8
-rw-r--r--chrome/browser/renderer_host/resource_dispatcher_host_request_info.h1
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/common/resource_dispatcher.cc9
-rw-r--r--chrome/common/url_constants.cc1
-rw-r--r--chrome/common/url_constants.h1
-rw-r--r--chrome/renderer/renderer_glue.cc3
-rw-r--r--net/base/upload_data.h25
-rw-r--r--webkit/blob/blob_data.cc5
-rw-r--r--webkit/blob/blob_data.h90
-rw-r--r--webkit/blob/blob_storage_controller.cc81
-rw-r--r--webkit/blob/blob_storage_controller.h8
-rw-r--r--webkit/blob/blob_storage_controller_unittest.cc172
-rw-r--r--webkit/blob/blob_url_request_job.cc532
-rw-r--r--webkit/blob/blob_url_request_job.h87
-rw-r--r--webkit/blob/blob_url_request_job_unittest.cc454
-rw-r--r--webkit/blob/webkit_blob.gypi2
-rw-r--r--webkit/glue/mock_resource_loader_bridge.h1
-rw-r--r--webkit/glue/resource_loader_bridge.h4
-rw-r--r--webkit/glue/weburlloader_impl.cc3
-rw-r--r--webkit/support/webkit_support.gypi2
-rw-r--r--webkit/tools/test_shell/simple_resource_loader_bridge.cc20
-rw-r--r--webkit/tools/test_shell/test_shell.cc16
-rw-r--r--webkit/tools/test_shell/test_shell.gypi1
-rw-r--r--webkit/tools/test_shell/test_shell_request_context.cc3
-rw-r--r--webkit/tools/test_shell/test_shell_request_context.h10
-rw-r--r--webkit/tools/test_shell/test_shell_webblobregistry_impl.cc90
-rw-r--r--webkit/tools/test_shell/test_shell_webblobregistry_impl.h47
-rw-r--r--webkit/tools/test_shell/test_shell_webkit_init.cc2
-rw-r--r--webkit/tools/test_shell/test_shell_webkit_init.h6
36 files changed, 1721 insertions, 64 deletions
diff --git a/base/file_util_proxy.cc b/base/file_util_proxy.cc
index b08d4a7..3863b37 100644
--- a/base/file_util_proxy.cc
+++ b/base/file_util_proxy.cc
@@ -7,6 +7,8 @@
#include "base/file_util.h"
#include "base/message_loop_proxy.h"
+// TODO(jianli): Move the code from anonymous namespace to base namespace so
+// that all of the base:: prefixes would be unnecessary.
namespace {
class MessageLoopRelay
@@ -205,6 +207,33 @@ class RelayDelete : public RelayWithStatusCallback {
bool recursive_;
};
+class RelayGetFileInfo : public MessageLoopRelay {
+ public:
+ RelayGetFileInfo(const FilePath& file_path,
+ base::FileUtilProxy::GetFileInfoCallback* callback)
+ : callback_(callback),
+ file_path_(file_path),
+ exists_(false) {
+ DCHECK(callback);
+ }
+
+ protected:
+ virtual void RunWork() {
+ exists_ = file_util::GetFileInfo(file_path_, &file_info_);
+ }
+
+ virtual void RunCallback() {
+ callback_->Run(exists_, file_info_);
+ delete callback_;
+ }
+
+ private:
+ base::FileUtilProxy::GetFileInfoCallback* callback_;
+ FilePath file_path_;
+ bool exists_;
+ file_util::FileInfo file_info_;
+};
+
bool Start(const tracked_objects::Location& from_here,
scoped_refptr<base::MessageLoopProxy> message_loop_proxy,
scoped_refptr<MessageLoopRelay> relay) {
@@ -257,4 +286,13 @@ bool FileUtilProxy::RecursiveDelete(
new RelayDelete(file_path, true, callback));
}
+// static
+bool FileUtilProxy::GetFileInfo(
+ scoped_refptr<MessageLoopProxy> message_loop_proxy,
+ const FilePath& file_path,
+ GetFileInfoCallback* callback) {
+ return Start(FROM_HERE, message_loop_proxy,
+ new RelayGetFileInfo(file_path, callback));
+}
+
} // namespace base
diff --git a/base/file_util_proxy.h b/base/file_util_proxy.h
index 87cd081..ff04e5b 100644
--- a/base/file_util_proxy.h
+++ b/base/file_util_proxy.h
@@ -10,6 +10,10 @@
#include "base/ref_counted.h"
#include "base/tracked_objects.h"
+namespace file_util {
+struct FileInfo;
+}
+
namespace base {
class MessageLoopProxy;
@@ -55,6 +59,16 @@ class FileUtilProxy {
const FilePath& file_path,
StatusCallback* callback);
+ // Retrieves the information about a file. It is invalid to pass NULL for the
+ // callback.
+ typedef Callback2<bool /*exists*/,
+ const file_util::FileInfo& /*file_info*/
+ >::Type GetFileInfoCallback;
+ static bool GetFileInfo(
+ scoped_refptr<MessageLoopProxy> message_loop_proxy,
+ const FilePath& file_path,
+ GetFileInfoCallback* callback);
+
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(FileUtilProxy);
};
diff --git a/chrome/browser/browser_main.cc b/chrome/browser/browser_main.cc
index b51aba9..ce92751 100644
--- a/chrome/browser/browser_main.cc
+++ b/chrome/browser/browser_main.cc
@@ -48,6 +48,7 @@
#include "chrome/browser/metrics/histogram_synchronizer.h"
#include "chrome/browser/metrics/metrics_log.h"
#include "chrome/browser/metrics/metrics_service.h"
+#include "chrome/browser/net/blob_url_request_job_factory.h"
#include "chrome/browser/net/predictor_api.h"
#include "chrome/browser/net/metadata_url_request.h"
#include "chrome/browser/net/sdch_dictionary_fetcher.h"
@@ -1317,9 +1318,10 @@ int BrowserMain(const MainFunctionParams& parameters) {
RegisterURLRequestChromeJob();
RegisterExtensionProtocols();
RegisterMetadataURLRequestHandler();
+ RegisterBlobURLRequestJobFactory();
// In unittest mode, this will do nothing. In normal mode, this will create
- // the global GoogleURLTracker and IntranetRedirectDetector instances, which
+ // the global GoogleURLTracker and IntranetRedirectDetector instances, which
// will promptly go to sleep for five and seven seconds, respectively (to
// avoid slowing startup), and wake up afterwards to see if they should do
// anything else.
diff --git a/chrome/browser/child_process_security_policy.cc b/chrome/browser/child_process_security_policy.cc
index 723483d..54ee31e 100644
--- a/chrome/browser/child_process_security_policy.cc
+++ b/chrome/browser/child_process_security_policy.cc
@@ -109,6 +109,7 @@ ChildProcessSecurityPolicy::ChildProcessSecurityPolicy() {
RegisterWebSafeScheme(chrome::kDataScheme);
RegisterWebSafeScheme("feed");
RegisterWebSafeScheme(chrome::kExtensionScheme);
+ RegisterWebSafeScheme(chrome::kBlobScheme);
// We know about the following psuedo schemes and treat them specially.
RegisterPseudoScheme(chrome::kAboutScheme);
diff --git a/chrome/browser/net/blob_url_request_job_factory.cc b/chrome/browser/net/blob_url_request_job_factory.cc
new file mode 100644
index 0000000..dab29d5
--- /dev/null
+++ b/chrome/browser/net/blob_url_request_job_factory.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2010 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 "chrome/browser/net/blob_url_request_job_factory.h"
+
+#include "chrome/browser/chrome_blob_storage_context.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/net/chrome_url_request_context.h"
+#include "chrome/common/url_constants.h"
+#include "webkit/blob/blob_storage_controller.h"
+#include "webkit/blob/blob_url_request_job.h"
+
+namespace {
+
+URLRequestJob* BlobURLRequestJobFactory(URLRequest* request,
+ const std::string& scheme) {
+ webkit_blob::BlobStorageController* blob_storage_controller =
+ static_cast<ChromeURLRequestContext*>(request->context())->
+ blob_storage_context()->controller();
+ return new webkit_blob::BlobURLRequestJob(
+ request,
+ blob_storage_controller->GetBlobDataFromUrl(request->url()),
+ ChromeThread::GetMessageLoopProxyForThread(ChromeThread::FILE));
+}
+
+}
+
+void RegisterBlobURLRequestJobFactory() {
+ URLRequest::RegisterProtocolFactory(chrome::kBlobScheme,
+ &BlobURLRequestJobFactory);
+}
diff --git a/chrome/browser/net/blob_url_request_job_factory.h b/chrome/browser/net/blob_url_request_job_factory.h
new file mode 100644
index 0000000..b009eaf
--- /dev/null
+++ b/chrome/browser/net/blob_url_request_job_factory.h
@@ -0,0 +1,10 @@
+// Copyright (c) 2010 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 CHROME_BROWSER_NET_BLOB_URL_REQUEST_JOB_FACTORY_H_
+#define CHROME_BROWSER_NET_BLOB_URL_REQUEST_JOB_FACTORY_H_
+
+void RegisterBlobURLRequestJobFactory();
+
+#endif // CHROME_BROWSER_NET_BLOB_URL_REQUEST_JOB_FACTORY_H_
diff --git a/chrome/browser/renderer_host/resource_dispatcher_host.cc b/chrome/browser/renderer_host/resource_dispatcher_host.cc
index f9b2bb2..8026df0 100644
--- a/chrome/browser/renderer_host/resource_dispatcher_host.cc
+++ b/chrome/browser/renderer_host/resource_dispatcher_host.cc
@@ -17,6 +17,7 @@
#include "base/time.h"
#include "chrome/browser/cert_store.h"
#include "chrome/browser/child_process_security_policy.h"
+#include "chrome/browser/chrome_blob_storage_context.h"
#include "chrome/browser/cross_site_request_manager.h"
#include "chrome/browser/download/download_file_manager.h"
#include "chrome/browser/download/download_manager.h"
@@ -68,6 +69,7 @@
#include "net/url_request/url_request_context.h"
#include "webkit/appcache/appcache_interceptor.h"
#include "webkit/appcache/appcache_interfaces.h"
+#include "webkit/blob/blob_storage_controller.h"
// TODO(oshima): Enable this for other platforms.
#if defined(OS_CHROMEOS)
@@ -371,6 +373,12 @@ void ResourceDispatcherHost::BeginRequest(
}
}
+ // Might need to resolve the blob references in the upload data.
+ if (request_data.upload_data) {
+ context->blob_storage_context()->controller()->
+ ResolveBlobReferencesInUploadData(request_data.upload_data.get());
+ }
+
if (is_shutdown_ ||
!ShouldServiceRequest(process_type, child_id, request_data)) {
URLRequestStatus status(URLRequestStatus::FAILED, net::ERR_ABORTED);
diff --git a/chrome/browser/renderer_host/resource_dispatcher_host_request_info.h b/chrome/browser/renderer_host/resource_dispatcher_host_request_info.h
index 201351e..f5b7535 100644
--- a/chrome/browser/renderer_host/resource_dispatcher_host_request_info.h
+++ b/chrome/browser/renderer_host/resource_dispatcher_host_request_info.h
@@ -128,6 +128,7 @@ class ResourceDispatcherHostRequestInfo : public URLRequest::UserData {
// When there is upload data, this is the byte count of that data. When there
// is no upload, this will be 0.
uint64 upload_size() const { return upload_size_; }
+ void set_upload_size(uint64 upload_size) { upload_size_ = upload_size; }
// When we're uploading data, this is the the byte offset into the uploaded
// data that we've uploaded that we've send an upload progress update about.
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 9f56d0b..36c74bb 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -2024,6 +2024,8 @@
'browser/nacl_host/nacl_broker_service_win.h',
'browser/nacl_host/nacl_process_host.cc',
'browser/nacl_host/nacl_process_host.h',
+ 'browser/net/blob_url_request_job_factory.cc',
+ 'browser/net/blob_url_request_job_factory.h',
'browser/net/browser_url_util.cc',
'browser/net/browser_url_util.h',
'browser/net/chrome_cookie_notification_details.h',
diff --git a/chrome/common/resource_dispatcher.cc b/chrome/common/resource_dispatcher.cc
index a8993ed..976995e 100644
--- a/chrome/common/resource_dispatcher.cc
+++ b/chrome/common/resource_dispatcher.cc
@@ -60,6 +60,7 @@ class IPCResourceLoaderBridge : public ResourceLoaderBridge {
uint64 offset,
uint64 length,
const base::Time& expected_modification_time);
+ virtual void AppendBlobToUpload(const GURL& blob_url);
virtual void SetUploadIdentifier(int64 identifier);
virtual bool Start(Peer* peer);
virtual void Cancel();
@@ -169,6 +170,14 @@ void IPCResourceLoaderBridge::AppendFileRangeToUpload(
expected_modification_time);
}
+void IPCResourceLoaderBridge::AppendBlobToUpload(const GURL& blob_url) {
+ DCHECK(request_id_ == -1) << "request already started";
+
+ if (!request_.upload_data)
+ request_.upload_data = new net::UploadData();
+ request_.upload_data->AppendBlob(blob_url);
+}
+
void IPCResourceLoaderBridge::SetUploadIdentifier(int64 identifier) {
DCHECK(request_id_ == -1) << "request already started";
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index 031a13d..f746cd0 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -10,6 +10,7 @@
namespace chrome {
const char kAboutScheme[] = "about";
+const char kBlobScheme[] = "blob";
const char kChromeInternalScheme[] = "chrome-internal";
const char kChromeUIScheme[] = "chrome";
const char kDataScheme[] = "data";
diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h
index bb726fe..579441c 100644
--- a/chrome/common/url_constants.h
+++ b/chrome/common/url_constants.h
@@ -12,6 +12,7 @@ namespace chrome {
// Canonical schemes you can use as input to GURL.SchemeIs().
extern const char kAboutScheme[];
+extern const char kBlobScheme[];
extern const char kChromeInternalScheme[];
extern const char kChromeUIScheme[]; // The scheme used for DOMUIs.
extern const char kCrosScheme[]; // The scheme used for ChromeOS.
diff --git a/chrome/renderer/renderer_glue.cc b/chrome/renderer/renderer_glue.cc
index 9469e8c..a17d9c5 100644
--- a/chrome/renderer/renderer_glue.cc
+++ b/chrome/renderer/renderer_glue.cc
@@ -259,7 +259,8 @@ bool IsProtocolSupportedForMedia(const GURL& url) {
if (url.SchemeIsFile() || url.SchemeIs(chrome::kHttpScheme) ||
url.SchemeIs(chrome::kHttpsScheme) ||
url.SchemeIs(chrome::kDataScheme) ||
- url.SchemeIs(chrome::kExtensionScheme))
+ url.SchemeIs(chrome::kExtensionScheme) ||
+ url.SchemeIs(chrome::kBlobScheme))
return true;
return false;
}
diff --git a/net/base/upload_data.h b/net/base/upload_data.h
index ac80ceb..53c5498 100644
--- a/net/base/upload_data.h
+++ b/net/base/upload_data.h
@@ -172,6 +172,31 @@ class UploadData : public base::RefCounted<UploadData> {
int64 identifier_;
};
+#if defined(UNIT_TEST)
+inline bool operator==(const UploadData::Element& a,
+ const UploadData::Element& b) {
+ if (a.type() != b.type())
+ return false;
+ if (a.type() == UploadData::TYPE_BYTES)
+ return a.bytes() == b.bytes();
+ if (a.type() == UploadData::TYPE_FILE) {
+ return a.file_path() == b.file_path() &&
+ a.file_range_offset() == b.file_range_offset() &&
+ a.file_range_length() == b.file_range_length() &&
+ a.expected_file_modification_time() ==
+ b.expected_file_modification_time();
+ }
+ if (a.type() == UploadData::TYPE_BLOB)
+ return a.blob_url() == b.blob_url();
+ return false;
+}
+
+inline bool operator!=(const UploadData::Element& a,
+ const UploadData::Element& b) {
+ return !(a == b);
+}
+#endif // defined(UNIT_TEST)
+
} // namespace net
#endif // NET_BASE_UPLOAD_DATA_H_
diff --git a/webkit/blob/blob_data.cc b/webkit/blob/blob_data.cc
index d72750e..4afcf8a 100644
--- a/webkit/blob/blob_data.cc
+++ b/webkit/blob/blob_data.cc
@@ -33,8 +33,11 @@ BlobData::BlobData(const WebBlobData& data) {
while (data.itemAt(i++, item)) {
switch (item.type) {
case WebBlobData::Item::TypeData:
- if (!item.data.isEmpty())
+ if (!item.data.isEmpty()) {
+ // WebBlobData does not allow partial data.
+ DCHECK(!item.offset && item.length == -1);
AppendData(item.data);
+ }
break;
case WebBlobData::Item::TypeFile:
AppendFile(
diff --git a/webkit/blob/blob_data.h b/webkit/blob/blob_data.h
index 96f4011..ddd0e65 100644
--- a/webkit/blob/blob_data.h
+++ b/webkit/blob/blob_data.h
@@ -73,34 +73,6 @@ class BlobData : public base::RefCounted<BlobData> {
length_ = length;
}
-#if defined(UNIT_TEST)
- bool operator==(const Item& other) const {
- if (type_ != other.type_)
- return false;
- if (type_ == TYPE_DATA) {
- return data_ == other.data_ &&
- offset_ == other.offset_ &&
- length_ == other.length_;
- }
- if (type_ == TYPE_FILE) {
- return file_path_ == other.file_path_ &&
- offset_ == other.offset_ &&
- length_ == other.length_ &&
- expected_modification_time_ == other.expected_modification_time_;
- }
- if (type_ == TYPE_BLOB) {
- return blob_url_ == other.blob_url_ &&
- offset_ == other.offset_ &&
- length_ == other.length_;
- }
- return false;
- }
-
- bool operator!=(const Item& other) const {
- return !(*this == other);
- }
-#endif // defined(UNIT_TEST)
-
private:
Type type_;
@@ -165,22 +137,6 @@ class BlobData : public base::RefCounted<BlobData> {
content_disposition_ = content_disposition;
}
-#if defined(UNIT_TEST)
- bool operator==(const BlobData& other) const {
- if (content_type_ != other.content_type_)
- return false;
- if (content_disposition_ != other.content_disposition_)
- return false;
- if (items_.size() != other.items_.size())
- return false;
- for (size_t i = 0; i < items_.size(); ++i) {
- if (items_[i] != other.items_[i])
- return false;
- }
- return true;
- }
-#endif // defined(UNIT_TEST)
-
private:
friend class base::RefCounted<BlobData>;
@@ -191,6 +147,52 @@ class BlobData : public base::RefCounted<BlobData> {
std::vector<Item> items_;
};
+#if defined(UNIT_TEST)
+inline bool operator==(const BlobData::Item& a, const BlobData::Item& b) {
+ if (a.type() != b.type())
+ return false;
+ if (a.type() == BlobData::TYPE_DATA) {
+ return a.data() == b.data() &&
+ a.offset() == b.offset() &&
+ a.length() == b.length();
+ }
+ if (a.type() == BlobData::TYPE_FILE) {
+ return a.file_path() == b.file_path() &&
+ a.offset() == b.offset() &&
+ a.length() == b.length() &&
+ a.expected_modification_time() == b.expected_modification_time();
+ }
+ if (a.type() == BlobData::TYPE_BLOB) {
+ return a.blob_url() == b.blob_url() &&
+ a.offset() == b.offset() &&
+ a.length() == b.length();
+ }
+ return false;
+}
+
+inline bool operator!=(const BlobData::Item& a, const BlobData::Item& b) {
+ return !(a == b);
+}
+
+inline bool operator==(const BlobData& a, const BlobData& b) {
+ if (a.content_type() != b.content_type())
+ return false;
+ if (a.content_disposition() != b.content_disposition())
+ return false;
+ if (a.items().size() != b.items().size())
+ return false;
+ for (size_t i = 0; i < a.items().size(); ++i) {
+ if (a.items()[i] != b.items()[i])
+ return false;
+ }
+ return true;
+}
+
+inline bool operator!=(const BlobData& a, const BlobData& b) {
+ return !(a == b);
+}
+#endif // defined(UNIT_TEST)
+
} // namespace webkit_blob
#endif // WEBKIT_BLOB_BLOB_DATA_H_
diff --git a/webkit/blob/blob_storage_controller.cc b/webkit/blob/blob_storage_controller.cc
index b552d10..274c381 100644
--- a/webkit/blob/blob_storage_controller.cc
+++ b/webkit/blob/blob_storage_controller.cc
@@ -6,6 +6,7 @@
#include "base/logging.h"
#include "googleurl/src/gurl.h"
+#include "net/base/upload_data.h"
#include "webkit/blob/blob_data.h"
namespace webkit_blob {
@@ -70,9 +71,12 @@ void BlobStorageController::RegisterBlobUrl(
blob_data->items().begin();
iter != blob_data->items().end(); ++iter) {
switch (iter->type()) {
- case BlobData::TYPE_DATA:
+ case BlobData::TYPE_DATA: {
+ // WebBlobData does not allow partial data.
+ DCHECK(!(iter->offset()) && iter->length() == iter->data().size());
target_blob_data->AppendData(iter->data());
break;
+ }
case BlobData::TYPE_FILE:
target_blob_data->AppendFile(iter->file_path(),
iter->offset(),
@@ -80,12 +84,11 @@ void BlobStorageController::RegisterBlobUrl(
iter->expected_modification_time());
break;
case BlobData::TYPE_BLOB: {
- scoped_refptr<BlobData> src_blob_data =
- blob_map_[iter->blob_url().spec()];
- DCHECK(src_blob_data.get());
- if (src_blob_data.get())
+ BlobData* src_blob_data = GetBlobDataFromUrl(iter->blob_url());
+ DCHECK(src_blob_data);
+ if (src_blob_data)
AppendStorageItems(target_blob_data.get(),
- src_blob_data.get(),
+ src_blob_data,
iter->offset(),
iter->length());
break;
@@ -99,6 +102,7 @@ void BlobStorageController::RegisterBlobUrl(
void BlobStorageController::RegisterBlobUrlFrom(
const GURL& url, const GURL& src_url) {
BlobData* blob_data = GetBlobDataFromUrl(src_url);
+ DCHECK(blob_data);
if (!blob_data)
return;
@@ -110,8 +114,69 @@ void BlobStorageController::UnregisterBlobUrl(const GURL& url) {
}
BlobData* BlobStorageController::GetBlobDataFromUrl(const GURL& url) {
- return (blob_map_.find(url.spec()) == blob_map_.end()) ?
- NULL : blob_map_[url.spec()].get();
+ BlobMap::iterator found = blob_map_.find(url.spec());
+ return (found != blob_map_.end()) ? found->second : NULL;
+}
+
+void BlobStorageController::ResolveBlobReferencesInUploadData(
+ net::UploadData* upload_data) {
+ DCHECK(upload_data);
+
+ std::vector<net::UploadData::Element>* uploads = upload_data->elements();
+ std::vector<net::UploadData::Element>::iterator iter;
+ for (iter = uploads->begin(); iter != uploads->end();) {
+ if (iter->type() != net::UploadData::TYPE_BLOB) {
+ iter++;
+ continue;
+ }
+
+ // Find the referred blob data.
+ webkit_blob::BlobData* blob_data = GetBlobDataFromUrl(iter->blob_url());
+ DCHECK(blob_data);
+ if (!blob_data) {
+ // TODO(jianli): We should probably fail uploading the data
+ iter++;
+ continue;
+ }
+
+ // Remove this element.
+ iter = uploads->erase(iter);
+
+ // If there is no element in the referred blob data, continue the loop.
+ // Note that we should not increase iter since it already points to the one
+ // after the removed element.
+ if (blob_data->items().empty())
+ continue;
+
+ // Insert the elements in the referred blob data.
+ // Note that we traverse from the bottom so that the elements can be
+ // inserted in the original order.
+ for (size_t i = blob_data->items().size(); i > 0; --i) {
+ iter = uploads->insert(iter, net::UploadData::Element());
+
+ const webkit_blob::BlobData::Item& item = blob_data->items().at(i - 1);
+ switch (item.type()) {
+ case webkit_blob::BlobData::TYPE_DATA:
+ // TODO(jianli): Figure out how to avoid copying the data.
+ iter->SetToBytes(
+ &item.data().at(0) + static_cast<int>(item.offset()),
+ static_cast<int>(item.length()));
+ break;
+ case webkit_blob::BlobData::TYPE_FILE:
+ // TODO(michaeln): Ensure that any temp files survive till the
+ // URLRequest is done with the upload.
+ iter->SetToFilePathRange(
+ item.file_path(),
+ item.offset(),
+ item.length(),
+ item.expected_modification_time());
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+ }
}
} // namespace webkit_blob
diff --git a/webkit/blob/blob_storage_controller.h b/webkit/blob/blob_storage_controller.h
index dd98f18..6c735c4 100644
--- a/webkit/blob/blob_storage_controller.h
+++ b/webkit/blob/blob_storage_controller.h
@@ -11,6 +11,10 @@
class GURL;
+namespace net {
+class UploadData;
+}
+
namespace webkit_blob {
class BlobData;
@@ -26,6 +30,10 @@ class BlobStorageController {
void UnregisterBlobUrl(const GURL& url);
BlobData* GetBlobDataFromUrl(const GURL& url);
+ // If there is any blob reference in the upload data, it will get resolved
+ // and updated in place.
+ void ResolveBlobReferencesInUploadData(net::UploadData* upload_data);
+
private:
void AppendStorageItems(BlobData* target_blob_data,
BlobData* src_blob_data,
diff --git a/webkit/blob/blob_storage_controller_unittest.cc b/webkit/blob/blob_storage_controller_unittest.cc
index 3bd0b63..e977585 100644
--- a/webkit/blob/blob_storage_controller_unittest.cc
+++ b/webkit/blob/blob_storage_controller_unittest.cc
@@ -6,10 +6,13 @@
#include "base/time.h"
#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
+#include "net/base/upload_data.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "webkit/blob/blob_data.h"
#include "webkit/blob/blob_storage_controller.h"
+using net::UploadData;
+
namespace webkit_blob {
TEST(BlobStorageControllerTest, RegisterBlobUrl) {
@@ -68,18 +71,169 @@ TEST(BlobStorageControllerTest, RegisterBlobUrl) {
ASSERT_TRUE(blob_data_found != NULL);
EXPECT_TRUE(*blob_data_found == *blob_data1);
- // Test registering a blob URL referring to non-existent blob URL.
- GURL nonexistent_blob_url("blob://url_none");
- GURL blob_url4("blob://url_4");
- blob_storage_controller->RegisterBlobUrlFrom(blob_url4, nonexistent_blob_url);
-
- blob_data_found = blob_storage_controller->GetBlobDataFromUrl(blob_url4);
- EXPECT_FALSE(blob_data_found != NULL);
-
// Test unregistering a blob URL.
blob_storage_controller->UnregisterBlobUrl(blob_url3);
blob_data_found = blob_storage_controller->GetBlobDataFromUrl(blob_url3);
- EXPECT_FALSE(blob_data_found != NULL);
+ EXPECT_TRUE(!blob_data_found);
+}
+
+TEST(BlobStorageControllerTest, ResolveBlobReferencesInUploadData) {
+ // Setup blob data for testing.
+ base::Time time1, time2;
+ base::Time::FromString(L"Tue, 15 Nov 1994, 12:45:26 GMT", &time1);
+ base::Time::FromString(L"Mon, 14 Nov 1994, 11:30:49 GMT", &time2);
+
+ scoped_ptr<BlobStorageController> blob_storage_controller(
+ new BlobStorageController());
+
+ scoped_refptr<BlobData> blob_data = new BlobData();
+
+ GURL blob_url0("blob://url_0");
+ blob_storage_controller->RegisterBlobUrl(blob_url0, blob_data);
+
+ blob_data->AppendData("BlobData");
+ blob_data->AppendFile(
+ FilePath(FILE_PATH_LITERAL("BlobFile.txt")), 0, 20, time1);
+
+ GURL blob_url1("blob://url_1");
+ blob_storage_controller->RegisterBlobUrl(blob_url1, blob_data);
+
+ GURL blob_url2("blob://url_2");
+ blob_storage_controller->RegisterBlobUrlFrom(blob_url2, blob_url1);
+
+ GURL blob_url3("blob://url_3");
+ blob_storage_controller->RegisterBlobUrlFrom(blob_url3, blob_url2);
+
+ // Setup upload data elements for comparison.
+ UploadData::Element blob_element1, blob_element2;
+ blob_element1.SetToBytes(
+ blob_data->items().at(0).data().c_str() +
+ static_cast<int>(blob_data->items().at(0).offset()),
+ static_cast<int>(blob_data->items().at(0).length()));
+ blob_element2.SetToFilePathRange(
+ blob_data->items().at(1).file_path(),
+ blob_data->items().at(1).offset(),
+ blob_data->items().at(1).length(),
+ blob_data->items().at(1).expected_modification_time());
+
+ UploadData::Element upload_element1, upload_element2;
+ upload_element1.SetToBytes("Hello", 5);
+ upload_element2.SetToFilePathRange(
+ FilePath(FILE_PATH_LITERAL("foo1.txt")), 0, 20, time2);
+
+ // Test no blob reference.
+ scoped_refptr<UploadData> upload_data = new UploadData();
+ upload_data->AppendBytes(
+ &upload_element1.bytes().at(0),
+ upload_element1.bytes().size());
+ upload_data->AppendFileRange(
+ upload_element2.file_path(),
+ upload_element2.file_range_offset(),
+ upload_element2.file_range_length(),
+ upload_element2.expected_file_modification_time());
+
+ blob_storage_controller->ResolveBlobReferencesInUploadData(upload_data.get());
+ ASSERT_EQ(upload_data->elements()->size(), 2U);
+ EXPECT_TRUE(upload_data->elements()->at(0) == upload_element1);
+ EXPECT_TRUE(upload_data->elements()->at(1) == upload_element2);
+
+ // Test having only one blob reference that refers to empty blob data.
+ upload_data = new UploadData();
+ upload_data->AppendBlob(blob_url0);
+
+ blob_storage_controller->ResolveBlobReferencesInUploadData(upload_data.get());
+ ASSERT_EQ(upload_data->elements()->size(), 0U);
+
+ // Test having only one blob reference.
+ upload_data = new UploadData();
+ upload_data->AppendBlob(blob_url1);
+
+ blob_storage_controller->ResolveBlobReferencesInUploadData(upload_data.get());
+ ASSERT_EQ(upload_data->elements()->size(), 2U);
+ EXPECT_TRUE(upload_data->elements()->at(0) == blob_element1);
+ EXPECT_TRUE(upload_data->elements()->at(1) == blob_element2);
+
+ // Test having one blob reference at the beginning.
+ upload_data = new UploadData();
+ upload_data->AppendBlob(blob_url1);
+ upload_data->AppendBytes(
+ &upload_element1.bytes().at(0),
+ upload_element1.bytes().size());
+ upload_data->AppendFileRange(
+ upload_element2.file_path(),
+ upload_element2.file_range_offset(),
+ upload_element2.file_range_length(),
+ upload_element2.expected_file_modification_time());
+
+ blob_storage_controller->ResolveBlobReferencesInUploadData(upload_data.get());
+ ASSERT_EQ(upload_data->elements()->size(), 4U);
+ EXPECT_TRUE(upload_data->elements()->at(0) == blob_element1);
+ EXPECT_TRUE(upload_data->elements()->at(1) == blob_element2);
+ EXPECT_TRUE(upload_data->elements()->at(2) == upload_element1);
+ EXPECT_TRUE(upload_data->elements()->at(3) == upload_element2);
+
+ // Test having one blob reference at the end.
+ upload_data = new UploadData();
+ upload_data->AppendBytes(
+ &upload_element1.bytes().at(0),
+ upload_element1.bytes().size());
+ upload_data->AppendFileRange(
+ upload_element2.file_path(),
+ upload_element2.file_range_offset(),
+ upload_element2.file_range_length(),
+ upload_element2.expected_file_modification_time());
+ upload_data->AppendBlob(blob_url1);
+
+ blob_storage_controller->ResolveBlobReferencesInUploadData(upload_data.get());
+ ASSERT_EQ(upload_data->elements()->size(), 4U);
+ EXPECT_TRUE(upload_data->elements()->at(0) == upload_element1);
+ EXPECT_TRUE(upload_data->elements()->at(1) == upload_element2);
+ EXPECT_TRUE(upload_data->elements()->at(2) == blob_element1);
+ EXPECT_TRUE(upload_data->elements()->at(3) == blob_element2);
+
+ // Test having one blob reference in the middle.
+ upload_data = new UploadData();
+ upload_data->AppendBytes(
+ &upload_element1.bytes().at(0),
+ upload_element1.bytes().size());
+ upload_data->AppendBlob(blob_url1);
+ upload_data->AppendFileRange(
+ upload_element2.file_path(),
+ upload_element2.file_range_offset(),
+ upload_element2.file_range_length(),
+ upload_element2.expected_file_modification_time());
+
+ blob_storage_controller->ResolveBlobReferencesInUploadData(upload_data.get());
+ ASSERT_EQ(upload_data->elements()->size(), 4U);
+ EXPECT_TRUE(upload_data->elements()->at(0) == upload_element1);
+ EXPECT_TRUE(upload_data->elements()->at(1) == blob_element1);
+ EXPECT_TRUE(upload_data->elements()->at(2) == blob_element2);
+ EXPECT_TRUE(upload_data->elements()->at(3) == upload_element2);
+
+ // Test having multiple blob references.
+ upload_data = new UploadData();
+ upload_data->AppendBlob(blob_url1);
+ upload_data->AppendBytes(
+ &upload_element1.bytes().at(0),
+ upload_element1.bytes().size());
+ upload_data->AppendBlob(blob_url2);
+ upload_data->AppendBlob(blob_url3);
+ upload_data->AppendFileRange(
+ upload_element2.file_path(),
+ upload_element2.file_range_offset(),
+ upload_element2.file_range_length(),
+ upload_element2.expected_file_modification_time());
+
+ blob_storage_controller->ResolveBlobReferencesInUploadData(upload_data.get());
+ ASSERT_EQ(upload_data->elements()->size(), 8U);
+ EXPECT_TRUE(upload_data->elements()->at(0) == blob_element1);
+ EXPECT_TRUE(upload_data->elements()->at(1) == blob_element2);
+ EXPECT_TRUE(upload_data->elements()->at(2) == upload_element1);
+ EXPECT_TRUE(upload_data->elements()->at(3) == blob_element1);
+ EXPECT_TRUE(upload_data->elements()->at(4) == blob_element2);
+ EXPECT_TRUE(upload_data->elements()->at(5) == blob_element1);
+ EXPECT_TRUE(upload_data->elements()->at(6) == blob_element2);
+ EXPECT_TRUE(upload_data->elements()->at(7) == upload_element2);
}
} // namespace webkit_blob
diff --git a/webkit/blob/blob_url_request_job.cc b/webkit/blob/blob_url_request_job.cc
new file mode 100644
index 0000000..4f81d75
--- /dev/null
+++ b/webkit/blob/blob_url_request_job.cc
@@ -0,0 +1,532 @@
+// Copyright (c) 20010 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/blob/blob_url_request_job.h"
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/file_util_proxy.h"
+#include "base/message_loop.h"
+#include "base/message_loop_proxy.h"
+#include "base/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_error_job.h"
+#include "net/url_request/url_request_status.h"
+
+namespace webkit_blob {
+
+static const int kHTTPOk = 200;
+static const int kHTTPPartialContent = 206;
+static const int kHTTPNotAllowed = 403;
+static const int kHTTPNotFound = 404;
+static const int kHTTPMethodNotAllow = 405;
+static const int kHTTPRequestedRangeNotSatisfiable = 416;
+static const int kHTTPInternalError = 500;
+
+static const char* kHTTPOKText = "OK";
+static const char* kHTTPPartialContentText = "Partial Content";
+static const char* kHTTPNotAllowedText = "Not Allowed";
+static const char* kHTTPNotFoundText = "Not Found";
+static const char* kHTTPMethodNotAllowText = "Method Not Allowed";
+static const char* kHTTPRequestedRangeNotSatisfiableText =
+ "Requested Range Not Satisfiable";
+static const char* kHTTPInternalErrorText = "Internal Server Error";
+
+BlobURLRequestJob::BlobURLRequestJob(
+ URLRequest* request,
+ BlobData* blob_data,
+ base::MessageLoopProxy* file_thread_proxy)
+ : URLRequestJob(request),
+ callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
+ blob_data_(blob_data),
+ file_thread_proxy_(file_thread_proxy),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ io_callback_(this, &BlobURLRequestJob::DidRead)),
+ item_index_(0),
+ total_size_(0),
+ current_item_offset_(0),
+ remaining_bytes_(0),
+ read_buf_offset_(0),
+ read_buf_size_(0),
+ read_buf_remaining_bytes_(0),
+ error_(false),
+ headers_set_(false),
+ byte_range_set_(false) {
+}
+
+BlobURLRequestJob::~BlobURLRequestJob() {
+}
+
+void BlobURLRequestJob::Start() {
+ // 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_) {
+ NotifyFailure(net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+
+ // Continue asynchronously.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &BlobURLRequestJob::CountSize));
+}
+
+void BlobURLRequestJob::Kill() {
+ stream_.Close();
+
+ URLRequestJob::Kill();
+}
+
+void BlobURLRequestJob::ResolveFile(const FilePath& file_path) {
+ // If the file thread proxy is provided, we can use it get the file info.
+ if (file_thread_proxy_) {
+ base::FileUtilProxy::GetFileInfo(
+ file_thread_proxy_,
+ file_path,
+ callback_factory_.NewCallback(&BlobURLRequestJob::DidResolve));
+ return;
+ }
+
+ // Otherwise, we use current thread, i.e. IO thread, as this is the case when
+ // we run the unittest or test shell.
+ // TODO(jianli): Consider using the proxy of current thread.
+ file_util::FileInfo file_info;
+ bool exists = file_util::GetFileInfo(file_path, &file_info);
+
+ // Continue asynchronously.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &BlobURLRequestJob::DidResolve, exists, file_info));
+}
+
+void BlobURLRequestJob::DidResolve(
+ bool exists, const file_util::FileInfo& file_info) {
+ // We may have been orphaned...
+ if (!request_)
+ return;
+
+ // If the file does not exist, bail out.
+ if (!exists) {
+ NotifyFailure(net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+
+ // Validate the expected modification time.
+ // Note that the expected modification time from WebKit is based on
+ // time_t precision. So we have to convert both to time_t to compare.
+ const BlobData::Item& item = blob_data_->items().at(item_index_);
+ DCHECK(item.type() == BlobData::TYPE_FILE);
+
+ if (!item.expected_modification_time().is_null() &&
+ item.expected_modification_time().ToTimeT() !=
+ file_info.last_modified.ToTimeT()) {
+ NotifyFailure(net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+
+ // If item length is -1, we need to use the file size being resolved
+ // in the real time.
+ int64 item_length = static_cast<int64>(item.length());
+ if (item_length == -1)
+ item_length = file_info.size;
+
+ // Cache the size and add it to the total size.
+ item_length_list_.push_back(item_length);
+ total_size_ += item_length;
+
+ // Continue counting the size for the remaining items.
+ item_index_++;
+ CountSize();
+}
+
+void BlobURLRequestJob::CountSize() {
+ for (; item_index_ < blob_data_->items().size(); ++item_index_) {
+ const BlobData::Item& item = blob_data_->items().at(item_index_);
+ int64 item_length = static_cast<int64>(item.length());
+
+ // If there is a file item, do the resolving.
+ if (item.type() == BlobData::TYPE_FILE) {
+ ResolveFile(item.file_path());
+ return;
+ }
+
+ // Cache the size and add it to the total size.
+ item_length_list_.push_back(item_length);
+ total_size_ += item_length;
+ }
+
+ // Reset item_index_ since it will be reused to read the items.
+ item_index_ = 0;
+
+ // 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::Seek(int64 offset) {
+ // Skip the initial items that are not in the range.
+ for (item_index_ = 0;
+ item_index_ < blob_data_->items().size() &&
+ offset >= item_length_list_[item_index_];
+ ++item_index_) {
+ offset -= item_length_list_[item_index_];
+ }
+
+ // Set the offset that need to jump to for the first item in the range.
+ current_item_offset_ = offset;
+}
+
+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_);
+ read_buf_ = dest;
+ read_buf_offset_ = 0;
+ read_buf_size_ = dest_size;
+ read_buf_remaining_bytes_ = dest_size;
+
+ return ReadLoop(bytes_read);
+}
+
+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_remaining_bytes_ > 0) {
+ if (!ReadItem())
+ return false;
+ }
+
+ *bytes_read = ReadCompleted();
+ return true;
+}
+
+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 (item_index_ >= blob_data_->items().size()) {
+ NotifyFailure(net::ERR_FAILED);
+ return false;
+ }
+
+ const BlobData::Item& item = blob_data_->items().at(item_index_);
+
+ // Compute the bytes to read for current item.
+ int64 current_item_remaining_bytes =
+ item_length_list_[item_index_] - current_item_offset_;
+ int bytes_to_read = (read_buf_remaining_bytes_ > current_item_remaining_bytes)
+ ? static_cast<int>(current_item_remaining_bytes)
+ : read_buf_remaining_bytes_;
+ if (bytes_to_read > remaining_bytes_)
+ bytes_to_read = static_cast<int>(remaining_bytes_);
+
+ // If nothing to read for current item, advance to next item.
+ if (bytes_to_read == 0) {
+ AdvanceItem();
+ return ReadItem();
+ }
+
+ // Do the reading.
+ switch (item.type()) {
+ case BlobData::TYPE_DATA:
+ return ReadBytes(item, bytes_to_read);
+ case BlobData::TYPE_FILE:
+ return ReadFile(item, bytes_to_read);
+ default:
+ DCHECK(false);
+ return false;
+ }
+}
+
+bool BlobURLRequestJob::ReadBytes(const BlobData::Item& item,
+ int bytes_to_read) {
+ DCHECK(read_buf_remaining_bytes_ >= bytes_to_read);
+
+ memcpy(read_buf_->data() + read_buf_offset_,
+ &item.data().at(0) + item.offset() + current_item_offset_,
+ bytes_to_read);
+
+ AdvanceBytesRead(bytes_to_read);
+ return true;
+}
+
+bool BlobURLRequestJob::ReadFile(const BlobData::Item& item,
+ int bytes_to_read) {
+ DCHECK(read_buf_remaining_bytes_ >= bytes_to_read);
+
+ // Open the file if not yet.
+ if (!stream_.IsOpen()) {
+ int rv = stream_.Open(item.file_path(), base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ | base::PLATFORM_FILE_ASYNC);
+ if (rv != net::OK) {
+ NotifyFailure(net::ERR_FAILED);
+ return false;
+ }
+
+ // Seek the file if needed.
+ int64 offset = current_item_offset_ + static_cast<int64>(item.offset());
+ if (offset > 0) {
+ if (offset != stream_.Seek(net::FROM_BEGIN, offset)) {
+ NotifyFailure(net::ERR_FAILED);
+ return false;
+ }
+ }
+ }
+
+ // Start the asynchronous reading.
+ int rv = stream_.Read(read_buf_->data() + read_buf_offset_,
+ bytes_to_read,
+ &io_callback_);
+
+ // If I/O pending error is returned, we just need to wait.
+ if (rv == net::ERR_IO_PENDING) {
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ return false;
+ }
+
+ // For all other errors, bail out.
+ if (rv < 0) {
+ NotifyFailure(net::ERR_FAILED);
+ return false;
+ }
+
+ // Otherwise, data is immediately available.
+ AdvanceBytesRead(rv);
+ return true;
+}
+
+void BlobURLRequestJob::DidRead(int result) {
+ if (result < 0) {
+ NotifyFailure(net::ERR_FAILED);
+ return;
+ }
+ SetStatus(URLRequestStatus()); // Clear the IO_PENDING status
+
+ AdvanceBytesRead(result);
+
+ // If the read buffer is completely filled, we're done.
+ if (!read_buf_remaining_bytes_) {
+ int bytes_read = ReadCompleted();
+ NotifyReadComplete(bytes_read);
+ return;
+ }
+
+ // Otherwise, continue the reading.
+ int bytes_read = 0;
+ if (ReadLoop(&bytes_read))
+ NotifyReadComplete(bytes_read);
+}
+
+void BlobURLRequestJob::AdvanceItem() {
+ // Close the stream if the current item is a file.
+ if (stream_.IsOpen())
+ stream_.Close();
+
+ // Advance to the next item.
+ 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_[item_index_])
+ AdvanceItem();
+
+ // Subtract the remaining bytes.
+ remaining_bytes_ -= result;
+ DCHECK_GE(remaining_bytes_, 0);
+
+ // Adjust the read buffer.
+ read_buf_offset_ += result;
+ read_buf_remaining_bytes_ -= result;
+ DCHECK_GE(read_buf_remaining_bytes_, 0);
+}
+
+int BlobURLRequestJob::ReadCompleted() {
+ int bytes_read = read_buf_size_ - read_buf_remaining_bytes_;
+ read_buf_ = NULL;
+ read_buf_offset_ = 0;
+ read_buf_size_ = 0;
+ read_buf_remaining_bytes_ = 0;
+ return bytes_read;
+}
+
+void BlobURLRequestJob::HeadersCompleted(int status_code,
+ const std::string& status_text) {
+ std::string status("HTTP/1.1 ");
+ status.append(base::IntToString(status_code));
+ status.append(" ");
+ status.append(status_text);
+ status.append("\0\0", 2);
+ net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status);
+
+ if (status_code == kHTTPOk || status_code == kHTTPPartialContent) {
+ 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();
+
+ headers_set_ = true;
+}
+
+void BlobURLRequestJob::NotifySuccess() {
+ int status_code = 0;
+ std::string status_text;
+ if (byte_range_set_ && byte_range_.IsValid()) {
+ status_code = kHTTPPartialContent;
+ status_text += kHTTPPartialContentText;
+ } else {
+ status_code = kHTTPOk;
+ status_text = kHTTPOKText;
+ }
+ HeadersCompleted(status_code, status_text);
+}
+
+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 (headers_set_) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, error_code));
+ return;
+ }
+
+ int status_code = 0;
+ std::string status_txt;
+ switch (error_code) {
+ case net::ERR_ACCESS_DENIED:
+ status_code = kHTTPNotAllowed;
+ status_txt = kHTTPNotAllowedText;
+ break;
+ case net::ERR_FILE_NOT_FOUND:
+ status_code = kHTTPNotFound;
+ status_txt = kHTTPNotFoundText;
+ break;
+ case net::ERR_METHOD_NOT_SUPPORTED:
+ status_code = kHTTPMethodNotAllow;
+ status_txt = kHTTPMethodNotAllowText;
+ break;
+ case net::ERR_REQUEST_RANGE_NOT_SATISFIABLE:
+ status_code = kHTTPRequestedRangeNotSatisfiable;
+ status_txt = kHTTPRequestedRangeNotSatisfiableText;
+ break;
+ case net::ERR_FAILED:
+ status_code = kHTTPInternalError;
+ status_txt = kHTTPInternalErrorText;
+ break;
+ default:
+ DCHECK(false);
+ status_code = kHTTPInternalError;
+ status_txt = kHTTPInternalErrorText;
+ break;
+ }
+ HeadersCompleted(status_code, status_txt);
+}
+
+bool BlobURLRequestJob::GetMimeType(std::string* mime_type) const {
+ if (!response_info_.get())
+ return false;
+
+ return response_info_->headers->GetMimeType(mime_type);
+}
+
+void BlobURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
+ if (response_info_.get())
+ *info = *response_info_;
+}
+
+int BlobURLRequestJob::GetResponseCode() const {
+ if (!response_info_.get())
+ 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);
+ }
+ }
+ }
+}
+
+} // namespace webkit_blob \ No newline at end of file
diff --git a/webkit/blob/blob_url_request_job.h b/webkit/blob/blob_url_request_job.h
new file mode 100644
index 0000000..9d24839
--- /dev/null
+++ b/webkit/blob/blob_url_request_job.h
@@ -0,0 +1,87 @@
+// Copyright (c) 20010 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_BLOB_URL_REQUEST_JOB_H_
+#define WEBKIT_BLOB_BLOB_URL_REQUEST_JOB_H_
+
+#include "base/ref_counted.h"
+#include "base/scoped_callback_factory.h"
+#include "base/scoped_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/file_stream.h"
+#include "net/http/http_byte_range.h"
+#include "net/url_request/url_request_job.h"
+#include "webkit/blob/blob_data.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace file_util {
+struct FileInfo;
+}
+
+namespace webkit_blob {
+
+// A request job that handles reading blob URLs.
+class BlobURLRequestJob : public URLRequestJob {
+ public:
+ BlobURLRequestJob(URLRequest* request,
+ BlobData* blob_data,
+ base::MessageLoopProxy* resolving_message_loop_proxy);
+ virtual ~BlobURLRequestJob();
+
+ // URLRequestJob methods.
+ virtual void Start();
+ virtual void Kill();
+ virtual bool ReadRawData(net::IOBuffer* buf, int buf_size, int* bytes_read);
+ virtual bool GetMimeType(std::string* mime_type) const;
+ virtual void GetResponseInfo(net::HttpResponseInfo* info);
+ virtual int GetResponseCode() const;
+ virtual void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers);
+
+ private:
+ void ResolveFile(const FilePath& file_path);
+ void CountSize();
+ void Seek(int64 offset);
+ void AdvanceItem();
+ void AdvanceBytesRead(int result);
+ bool ReadLoop(int* bytes_read);
+ bool ReadItem();
+ bool ReadBytes(const BlobData::Item& item, int bytes_to_read);
+ bool ReadFile(const BlobData::Item& item, int bytes_to_read);
+ void HeadersCompleted(int status_code, const std::string& status_txt);
+ int ReadCompleted();
+ void NotifySuccess();
+ void NotifyFailure(int);
+
+ void DidResolve(bool exists, const file_util::FileInfo& file_info);
+ void DidRead(int result);
+
+ base::ScopedCallbackFactory<BlobURLRequestJob> callback_factory_;
+ scoped_refptr<BlobData> blob_data_;
+ scoped_refptr<base::MessageLoopProxy> file_thread_proxy_;
+ net::CompletionCallbackImpl<BlobURLRequestJob> io_callback_;
+ std::vector<int64> item_length_list_;
+ net::FileStream stream_;
+ size_t item_index_;
+ int64 total_size_;
+ int64 current_item_offset_;
+ int64 remaining_bytes_;
+ scoped_refptr<net::IOBuffer> read_buf_;
+ int read_buf_offset_;
+ int read_buf_size_;
+ int read_buf_remaining_bytes_;
+ bool error_;
+ bool headers_set_;
+ bool byte_range_set_;
+ net::HttpByteRange byte_range_;
+ scoped_ptr<net::HttpResponseInfo> response_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlobURLRequestJob);
+};
+
+} // namespace webkit_blob
+
+#endif // WEBKIT_BLOB_BLOB_URL_REQUEST_JOB_H_
diff --git a/webkit/blob/blob_url_request_job_unittest.cc b/webkit/blob/blob_url_request_job_unittest.cc
new file mode 100644
index 0000000..338c885
--- /dev/null
+++ b/webkit/blob/blob_url_request_job_unittest.cc
@@ -0,0 +1,454 @@
+// Copyright (c) 2010 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 <stack>
+#include <utility>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/scoped_temp_dir.h"
+#include "base/task.h"
+#include "base/thread.h"
+#include "base/time.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/waitable_event.h"
+#include "net/base/file_stream.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/url_request/url_request.h"
+#include "net/url_request/url_request_error_job.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/blob/blob_data.h"
+#include "webkit/blob/blob_url_request_job.h"
+
+namespace webkit_blob {
+
+static const int kBufferSize = 1024;
+static const char kTestData1[] = "Hello";
+static const char kTestData2[] = "Here it is data.";
+static const char kTestFileData1[] = "0123456789";
+static const char kTestFileData2[] = "This is sample file.";
+static const char kTestContentType[] = "foo/bar";
+static const char kTestContentDisposition[] = "attachment; filename=foo.txt";
+
+class BlobURLRequestJobTest : public testing::Test {
+ public:
+
+ // Test Harness -------------------------------------------------------------
+ // TODO(jianli): share this test harness with AppCacheURLRequestJobTest
+
+ class MockURLRequestDelegate : public URLRequest::Delegate {
+ public:
+ explicit MockURLRequestDelegate(BlobURLRequestJobTest* test)
+ : test_(test),
+ received_data_(new net::IOBuffer(kBufferSize)) {
+ }
+
+ virtual void OnResponseStarted(URLRequest* request) {
+ if (request->status().is_success()) {
+ EXPECT_TRUE(request->response_headers());
+ ReadSome(request);
+ } else {
+ RequestComplete();
+ }
+ }
+
+ virtual void OnReadCompleted(URLRequest* request, int bytes_read) {
+ if (bytes_read > 0)
+ ReceiveData(request, bytes_read);
+ else
+ RequestComplete();
+ }
+
+ const std::string& response_data() const { return response_data_; }
+
+ private:
+ void ReadSome(URLRequest* request) {
+ if (request->job()->is_done()) {
+ 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(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() {
+ test_->ScheduleNextTask();
+ }
+
+ BlobURLRequestJobTest* test_;
+ scoped_refptr<net::IOBuffer> received_data_;
+ std::string response_data_;
+ };
+
+ // Helper class run a test on our io_thread. The io_thread
+ // is spun up once and reused for all tests.
+ template <class Method>
+ class WrapperTask : public Task {
+ public:
+ WrapperTask(BlobURLRequestJobTest* test, Method method)
+ : test_(test), method_(method) {
+ }
+
+ virtual void Run() {
+ test_->SetUpTest();
+ (test_->*method_)();
+ }
+
+ private:
+ BlobURLRequestJobTest* test_;
+ Method method_;
+ };
+
+ static void SetUpTestCase() {
+ temp_dir_.reset(new ScopedTempDir());
+ ASSERT_TRUE(temp_dir_->CreateUniqueTempDir());
+
+ temp_file1_ = temp_dir_->path().AppendASCII("BlobFile1.dat");
+ file_util::WriteFile(temp_file1_, kTestFileData1,
+ arraysize(kTestFileData1) - 1);
+ file_util::FileInfo 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");
+ file_util::WriteFile(temp_file2_, kTestFileData2,
+ arraysize(kTestFileData2) - 1);
+ file_util::FileInfo file_info2;
+ file_util::GetFileInfo(temp_file2_, &file_info2);
+ temp_file_modification_time2_ = file_info2.last_modified;
+
+ io_thread_.reset(new base::Thread("BlobRLRequestJobTest Thread"));
+ base::Thread::Options options(MessageLoop::TYPE_IO, 0);
+ io_thread_->StartWithOptions(options);
+ }
+
+ static void TearDownTestCase() {
+ io_thread_.reset(NULL);
+ temp_dir_.reset(NULL);
+ }
+
+ static URLRequestJob* BlobURLRequestJobFactory(URLRequest* request,
+ const std::string& scheme) {
+ BlobURLRequestJob* temp = blob_url_request_job_;
+ blob_url_request_job_ = NULL;
+ return temp;
+ }
+
+ BlobURLRequestJobTest()
+ : expected_status_code_(0) {
+ }
+
+ template <class Method>
+ void RunTestOnIOThread(Method method) {
+ test_finished_event_ .reset(new base::WaitableEvent(false, false));
+ io_thread_->message_loop()->PostTask(
+ FROM_HERE, new WrapperTask<Method>(this, method));
+ test_finished_event_->Wait();
+ }
+
+ void SetUpTest() {
+ DCHECK(MessageLoop::current() == io_thread_->message_loop());
+
+ URLRequest::RegisterProtocolFactory("blob", &BlobURLRequestJobFactory);
+ url_request_delegate_.reset(new MockURLRequestDelegate(this));
+ }
+
+ void TearDownTest() {
+ DCHECK(MessageLoop::current() == io_thread_->message_loop());
+
+ request_.reset();
+ url_request_delegate_.reset();
+
+ DCHECK(!blob_url_request_job_);
+ URLRequest::RegisterProtocolFactory("blob", NULL);
+ }
+
+ void TestFinished() {
+ // We unwind the stack prior to finishing up to let stack
+ // based objects get deleted.
+ DCHECK(MessageLoop::current() == io_thread_->message_loop());
+ MessageLoop::current()->PostTask(FROM_HERE,
+ NewRunnableMethod(
+ this, &BlobURLRequestJobTest::TestFinishedUnwound));
+ }
+
+ void TestFinishedUnwound() {
+ TearDownTest();
+ test_finished_event_->Signal();
+ }
+
+ void PushNextTask(Task* task) {
+ task_stack_.push(std::pair<Task*, bool>(task, false));
+ }
+
+ void PushNextTaskAsImmediate(Task* task) {
+ task_stack_.push(std::pair<Task*, bool>(task, true));
+ }
+
+ void ScheduleNextTask() {
+ DCHECK(MessageLoop::current() == io_thread_->message_loop());
+ if (task_stack_.empty()) {
+ TestFinished();
+ return;
+ }
+ scoped_ptr<Task> task(task_stack_.top().first);
+ bool immediate = task_stack_.top().second;
+ task_stack_.pop();
+ if (immediate)
+ task->Run();
+ else
+ MessageLoop::current()->PostTask(FROM_HERE, task.release());
+ }
+
+ void TestSuccessRequest(BlobData* blob_data,
+ const std::string& expected_response) {
+ PushNextTask(NewRunnableMethod(
+ this, &BlobURLRequestJobTest::VerifyResponse));
+ expected_status_code_ = 200;
+ expected_response_ = expected_response;
+ return TestRequest("GET", net::HttpRequestHeaders(), blob_data);
+ }
+
+ void TestErrorRequest(BlobData* blob_data,
+ int expected_status_code) {
+ PushNextTask(NewRunnableMethod(
+ this, &BlobURLRequestJobTest::VerifyResponse));
+ expected_status_code_ = expected_status_code;
+ expected_response_ = "";
+ return TestRequest("GET", net::HttpRequestHeaders(), blob_data);
+ }
+
+ void TestRequest(const std::string& method,
+ const net::HttpRequestHeaders& extra_headers,
+ BlobData* blob_data) {
+ // This test has async steps.
+ request_.reset(new URLRequest(GURL("blob:blah"),
+ url_request_delegate_.get()));
+ request_->set_method(method);
+ blob_url_request_job_ = new BlobURLRequestJob(request_.get(),
+ blob_data, NULL);
+
+ // Start the request.
+ if (!extra_headers.IsEmpty())
+ request_->SetExtraRequestHeaders(extra_headers);
+ request_->Start();
+
+ // Completion is async.
+ }
+
+ void VerifyResponse() {
+ EXPECT_TRUE(request_->status().is_success());
+ EXPECT_EQ(request_->response_headers()->response_code(),
+ expected_status_code_);
+ EXPECT_STREQ(url_request_delegate_->response_data().c_str(),
+ expected_response_.c_str());
+ TestFinished();
+ }
+
+ // Test Cases ---------------------------------------------------------------
+
+ void TestGetSimpleDataRequest() {
+ scoped_refptr<BlobData> blob_data = new BlobData();
+ blob_data->AppendData(kTestData1);
+ TestSuccessRequest(blob_data, kTestData1);
+ }
+
+ void TestGetSimpleFileRequest() {
+ scoped_refptr<BlobData> blob_data = new BlobData();
+ blob_data->AppendFile(temp_file1_, 0, -1, base::Time());
+ TestSuccessRequest(blob_data, kTestFileData1);
+ }
+
+ void TestGetNonExistentFileRequest() {
+ FilePath non_existent_file =
+ temp_file1_.InsertBeforeExtension(FILE_PATH_LITERAL("-na"));
+ scoped_refptr<BlobData> blob_data = new BlobData();
+ blob_data->AppendFile(non_existent_file, 0, -1, base::Time());
+ TestErrorRequest(blob_data, 404);
+ }
+
+ void TestGetChangedFileRequest() {
+ scoped_refptr<BlobData> blob_data = new BlobData();
+ base::Time old_time =
+ temp_file_modification_time1_ - base::TimeDelta::FromSeconds(10);
+ blob_data->AppendFile(temp_file1_, 0, 3, old_time);
+ TestErrorRequest(blob_data, 404);
+ }
+
+ void TestGetSlicedDataRequest() {
+ scoped_refptr<BlobData> blob_data = new BlobData();
+ blob_data->AppendData(kTestData2, 2, 4);
+ std::string result(kTestData2 + 2, 4);
+ TestSuccessRequest(blob_data, result);
+ }
+
+ void TestGetSlicedFileRequest() {
+ scoped_refptr<BlobData> blob_data = new BlobData();
+ blob_data->AppendFile(temp_file1_, 2, 4, temp_file_modification_time1_);
+ std::string result(kTestFileData1 + 2, 4);
+ TestSuccessRequest(blob_data, result);
+ }
+
+ scoped_refptr<BlobData> BuildComplicatedData(std::string* expected_result) {
+ scoped_refptr<BlobData> blob_data = new BlobData();
+ blob_data->AppendData(kTestData1, 1, 2);
+ blob_data->AppendFile(temp_file1_, 2, 3, temp_file_modification_time1_);
+ blob_data->AppendData(kTestData2, 3, 4);
+ blob_data->AppendFile(temp_file2_, 4, 5, temp_file_modification_time2_);
+ *expected_result = std::string(kTestData1 + 1, 2);
+ *expected_result += std::string(kTestFileData1 + 2, 3);
+ *expected_result += std::string(kTestData2 + 3, 4);
+ *expected_result += std::string(kTestFileData2 + 4, 5);
+ return blob_data;
+ }
+
+ void TestGetComplicatedDataAndFileRequest() {
+ std::string result;
+ scoped_refptr<BlobData> blob_data = BuildComplicatedData(&result);
+ TestSuccessRequest(blob_data, result);
+ }
+
+ void TestGetRangeRequest1() {
+ std::string result;
+ scoped_refptr<BlobData> blob_data = BuildComplicatedData(&result);
+ net::HttpRequestHeaders extra_headers;
+ extra_headers.SetHeader(net::HttpRequestHeaders::kRange, "bytes=5-10");
+ PushNextTask(NewRunnableMethod(
+ this, &BlobURLRequestJobTest::VerifyResponse));
+ expected_status_code_ = 206;
+ expected_response_ = result.substr(5, 10 - 5 + 1);
+ return TestRequest("GET", extra_headers, blob_data);
+ }
+
+ void TestGetRangeRequest2() {
+ std::string result;
+ scoped_refptr<BlobData> blob_data = BuildComplicatedData(&result);
+ net::HttpRequestHeaders extra_headers;
+ extra_headers.SetHeader(net::HttpRequestHeaders::kRange, "bytes=-10");
+ PushNextTask(NewRunnableMethod(
+ this, &BlobURLRequestJobTest::VerifyResponse));
+ expected_status_code_ = 206;
+ expected_response_ = result.substr(result.length() - 10);
+ return TestRequest("GET", extra_headers, blob_data);
+ }
+
+ void TestExtraHeaders() {
+ scoped_refptr<BlobData> blob_data = new BlobData();
+ blob_data->set_content_type(kTestContentType);
+ blob_data->set_content_disposition(kTestContentDisposition);
+ blob_data->AppendData(kTestData1);
+ PushNextTask(NewRunnableMethod(
+ this, &BlobURLRequestJobTest::VerifyResponseForTestExtraHeaders));
+ TestRequest("GET", net::HttpRequestHeaders(), blob_data);
+ }
+
+ void VerifyResponseForTestExtraHeaders() {
+ EXPECT_TRUE(request_->status().is_success());
+ EXPECT_EQ(request_->response_headers()->response_code(), 200);
+ EXPECT_STREQ(url_request_delegate_->response_data().c_str(), kTestData1);
+ std::string content_type;
+ EXPECT_TRUE(request_->response_headers()->GetMimeType(&content_type));
+ EXPECT_STREQ(content_type.c_str(), kTestContentType);
+ void* iter = NULL;
+ std::string content_disposition;
+ EXPECT_TRUE(request_->response_headers()->EnumerateHeader(
+ &iter, "Content-Disposition", &content_disposition));
+ EXPECT_STREQ(content_disposition.c_str(), kTestContentDisposition);
+ TestFinished();
+ }
+
+ private:
+ static scoped_ptr<ScopedTempDir> temp_dir_;
+ static FilePath temp_file1_;
+ static FilePath temp_file2_;
+ static base::Time temp_file_modification_time1_;
+ static base::Time temp_file_modification_time2_;
+ static scoped_ptr<base::Thread> io_thread_;
+ static BlobURLRequestJob* blob_url_request_job_;
+
+ scoped_ptr<base::WaitableEvent> test_finished_event_;
+ std::stack<std::pair<Task*, bool> > task_stack_;
+ scoped_ptr<URLRequest> request_;
+ scoped_ptr<MockURLRequestDelegate> url_request_delegate_;
+ int expected_status_code_;
+ std::string expected_response_;
+};
+
+// static
+scoped_ptr<ScopedTempDir> BlobURLRequestJobTest::temp_dir_;
+FilePath BlobURLRequestJobTest::temp_file1_;
+FilePath BlobURLRequestJobTest::temp_file2_;
+base::Time BlobURLRequestJobTest::temp_file_modification_time1_;
+base::Time BlobURLRequestJobTest::temp_file_modification_time2_;
+scoped_ptr<base::Thread> BlobURLRequestJobTest::io_thread_;
+BlobURLRequestJob* BlobURLRequestJobTest::blob_url_request_job_ = NULL;
+
+TEST_F(BlobURLRequestJobTest, TestGetSimpleDataRequest) {
+ RunTestOnIOThread(&BlobURLRequestJobTest::TestGetSimpleDataRequest);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetSimpleFileRequest) {
+ RunTestOnIOThread(&BlobURLRequestJobTest::TestGetSimpleFileRequest);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetSlicedDataRequest) {
+ RunTestOnIOThread(&BlobURLRequestJobTest::TestGetSlicedDataRequest);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetSlicedFileRequest) {
+ RunTestOnIOThread(&BlobURLRequestJobTest::TestGetSlicedFileRequest);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetNonExistentFileRequest) {
+ RunTestOnIOThread(&BlobURLRequestJobTest::TestGetNonExistentFileRequest);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetChangedFileRequest) {
+ RunTestOnIOThread(&BlobURLRequestJobTest::TestGetChangedFileRequest);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetComplicatedDataAndFileRequest) {
+ RunTestOnIOThread(
+ &BlobURLRequestJobTest::TestGetComplicatedDataAndFileRequest);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetRangeRequest1) {
+ RunTestOnIOThread(&BlobURLRequestJobTest::TestGetRangeRequest1);
+}
+
+TEST_F(BlobURLRequestJobTest, TestGetRangeRequest2) {
+ RunTestOnIOThread(&BlobURLRequestJobTest::TestGetRangeRequest2);
+}
+
+TEST_F(BlobURLRequestJobTest, TestExtraHeaders) {
+ RunTestOnIOThread(&BlobURLRequestJobTest::TestExtraHeaders);
+}
+
+} // namespace webkit_blob
+
+// BlobURLRequestJobTest is expected to always live longer than the
+// runnable methods. This lets us call NewRunnableMethod on its instances.
+DISABLE_RUNNABLE_METHOD_REFCOUNT(webkit_blob::BlobURLRequestJobTest);
diff --git a/webkit/blob/webkit_blob.gypi b/webkit/blob/webkit_blob.gypi
index fcfddfb..e057efb 100644
--- a/webkit/blob/webkit_blob.gypi
+++ b/webkit/blob/webkit_blob.gypi
@@ -18,6 +18,8 @@
'blob_data.h',
'blob_storage_controller.cc',
'blob_storage_controller.h',
+ 'blob_url_request_job.cc',
+ 'blob_url_request_job.h',
],
'conditions': [
['inside_chromium_build==0', {
diff --git a/webkit/glue/mock_resource_loader_bridge.h b/webkit/glue/mock_resource_loader_bridge.h
index 2f60393..49c41ed 100644
--- a/webkit/glue/mock_resource_loader_bridge.h
+++ b/webkit/glue/mock_resource_loader_bridge.h
@@ -27,6 +27,7 @@ class MockResourceLoaderBridge : public webkit_glue::ResourceLoaderBridge {
uint64 offset,
uint64 length,
const base::Time& expected_modification_time));
+ MOCK_METHOD1(AppendBlobToUpload, void(const GURL& blob_url));
MOCK_METHOD1(SetUploadIdentifier, void(int64 identifier));
MOCK_METHOD1(Start, bool(ResourceLoaderBridge::Peer* peer));
MOCK_METHOD0(Cancel, void());
diff --git a/webkit/glue/resource_loader_bridge.h b/webkit/glue/resource_loader_bridge.h
index e4d439f..68e6766 100644
--- a/webkit/glue/resource_loader_bridge.h
+++ b/webkit/glue/resource_loader_bridge.h
@@ -318,6 +318,10 @@ class ResourceLoaderBridge {
uint64 length,
const base::Time& expected_modification_time) = 0;
+ // Call this method before calling Start() to append the contents of a blob
+ // to the request body. May only be used with HTTP(S) POST requests.
+ virtual void AppendBlobToUpload(const GURL& blob_url) = 0;
+
// Call this method before calling Start() to assign an upload identifier to
// this request. This is used to enable caching of POST responses. A value
// of 0 implies the unspecified identifier.
diff --git a/webkit/glue/weburlloader_impl.cc b/webkit/glue/weburlloader_impl.cc
index 3b61bc0..d1b986a 100644
--- a/webkit/glue/weburlloader_impl.cc
+++ b/webkit/glue/weburlloader_impl.cc
@@ -432,6 +432,9 @@ void WebURLLoaderImpl::Context::Start(
base::Time::FromDoubleT(element.fileInfo.modificationTime));
}
break;
+ case WebHTTPBody::Element::TypeBlob:
+ bridge_->AppendBlobToUpload(GURL(element.blobURL));
+ break;
default:
NOTREACHED();
}
diff --git a/webkit/support/webkit_support.gypi b/webkit/support/webkit_support.gypi
index 9d502bf..63cf05a 100644
--- a/webkit/support/webkit_support.gypi
+++ b/webkit/support/webkit_support.gypi
@@ -59,6 +59,8 @@
'<(DEPTH)/webkit/tools/test_shell/simple_webcookiejar_impl.h',
'<(DEPTH)/webkit/tools/test_shell/test_shell_request_context.cc',
'<(DEPTH)/webkit/tools/test_shell/test_shell_request_context.h',
+ '<(DEPTH)/webkit/tools/test_shell/test_shell_webblobregistry_impl.cc',
+ '<(DEPTH)/webkit/tools/test_shell/test_shell_webblobregistry_impl.h',
'<(DEPTH)/webkit/tools/test_shell/test_shell_webmimeregistry_impl.cc',
'<(DEPTH)/webkit/tools/test_shell/test_shell_webmimeregistry_impl.h',
],
diff --git a/webkit/tools/test_shell/simple_resource_loader_bridge.cc b/webkit/tools/test_shell/simple_resource_loader_bridge.cc
index 26af3d4..75d78da 100644
--- a/webkit/tools/test_shell/simple_resource_loader_bridge.cc
+++ b/webkit/tools/test_shell/simple_resource_loader_bridge.cc
@@ -58,10 +58,12 @@
#endif
#include "net/url_request/url_request.h"
#include "webkit/appcache/appcache_interfaces.h"
+#include "webkit/blob/blob_storage_controller.h"
#include "webkit/glue/resource_loader_bridge.h"
#include "webkit/tools/test_shell/simple_appcache_system.h"
#include "webkit/tools/test_shell/simple_socket_stream_bridge.h"
#include "webkit/tools/test_shell/test_shell_request_context.h"
+#include "webkit/tools/test_shell/test_shell_webblobregistry_impl.h"
using webkit_glue::ResourceLoaderBridge;
using net::StaticCookiePolicy;
@@ -120,10 +122,14 @@ class IOThread : public base::Thread {
SimpleAppCacheSystem::InitializeOnIOThread(g_request_context);
SimpleSocketStreamBridge::InitializeOnIOThread(g_request_context);
+ TestShellWebBlobRegistryImpl::InitializeOnIOThread(
+ static_cast<TestShellRequestContext*>(g_request_context)->
+ blob_storage_controller());
}
virtual void CleanUp() {
SimpleSocketStreamBridge::Cleanup();
+ TestShellWebBlobRegistryImpl::Cleanup();
if (g_request_context) {
g_request_context->Release();
g_request_context = NULL;
@@ -263,6 +269,13 @@ class RequestProxy : public URLRequest::Delegate,
// actions performed on the owner's thread.
void AsyncStart(RequestParams* params) {
+ // Might need to resolve the blob references in the upload data.
+ if (params->upload) {
+ static_cast<TestShellRequestContext*>(g_request_context)->
+ blob_storage_controller()->ResolveBlobReferencesInUploadData(
+ params->upload.get());
+ }
+
request_.reset(new URLRequest(params->url, this));
request_->set_method(params->method);
request_->set_first_party_for_cookies(params->first_party_for_cookies);
@@ -582,6 +595,13 @@ class ResourceLoaderBridgeImpl : public ResourceLoaderBridge {
expected_modification_time);
}
+ virtual void AppendBlobToUpload(const GURL& blob_url) {
+ DCHECK(params_.get());
+ if (!params_->upload)
+ params_->upload = new net::UploadData();
+ params_->upload->AppendBlob(blob_url);
+ }
+
virtual void SetUploadIdentifier(int64 identifier) {
DCHECK(params_.get());
if (!params_->upload)
diff --git a/webkit/tools/test_shell/test_shell.cc b/webkit/tools/test_shell/test_shell.cc
index 6b2c748..3117277 100644
--- a/webkit/tools/test_shell/test_shell.cc
+++ b/webkit/tools/test_shell/test_shell.cc
@@ -43,6 +43,8 @@
#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h"
#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h"
#include "third_party/WebKit/WebKit/chromium/public/WebView.h"
+#include "webkit/blob/blob_storage_controller.h"
+#include "webkit/blob/blob_url_request_job.h"
#include "webkit/glue/glue_serialize.h"
#include "webkit/glue/webkit_glue.h"
#include "webkit/glue/webpreferences.h"
@@ -52,6 +54,7 @@
#include "webkit/tools/test_shell/test_navigation_controller.h"
#include "webkit/tools/test_shell/test_shell_devtools_agent.h"
#include "webkit/tools/test_shell/test_shell_devtools_client.h"
+#include "webkit/tools/test_shell/test_shell_request_context.h"
#include "webkit/tools/test_shell/test_shell_switches.h"
#include "webkit/tools/test_shell/test_webview_delegate.h"
@@ -100,6 +103,17 @@ class URLRequestTestShellFileJob : public URLRequestFileJob {
DISALLOW_COPY_AND_ASSIGN(URLRequestTestShellFileJob);
};
+URLRequestJob* BlobURLRequestJobFactory(URLRequest* request,
+ const std::string& scheme) {
+ webkit_blob::BlobStorageController* blob_storage_controller =
+ static_cast<TestShellRequestContext*>(request->context())->
+ blob_storage_controller();
+ return new webkit_blob::BlobURLRequestJob(
+ request,
+ blob_storage_controller->GetBlobDataFromUrl(request->url()),
+ NULL);
+}
+
} // namespace
// Initialize static member variable
@@ -142,6 +156,8 @@ TestShell::TestShell()
filter->AddHostnameHandler("test-shell-resource", "inspector",
&URLRequestTestShellFileJob::InspectorFactory);
url_util::AddStandardScheme("test-shell-resource");
+
+ URLRequest::RegisterProtocolFactory("blob", &BlobURLRequestJobFactory);
}
TestShell::~TestShell() {
diff --git a/webkit/tools/test_shell/test_shell.gypi b/webkit/tools/test_shell/test_shell.gypi
index 08c5cfc..a815944 100644
--- a/webkit/tools/test_shell/test_shell.gypi
+++ b/webkit/tools/test_shell/test_shell.gypi
@@ -369,6 +369,7 @@
'../../appcache/mock_appcache_service.h',
'../../appcache/mock_appcache_storage_unittest.cc',
'../../blob/blob_storage_controller_unittest.cc',
+ '../../blob/blob_url_request_job_unittest.cc',
'../../database/databases_table_unittest.cc',
'../../database/database_tracker_unittest.cc',
'../../database/database_util_unittest.cc',
diff --git a/webkit/tools/test_shell/test_shell_request_context.cc b/webkit/tools/test_shell/test_shell_request_context.cc
index 2ca35a4..82e7d01 100644
--- a/webkit/tools/test_shell/test_shell_request_context.cc
+++ b/webkit/tools/test_shell/test_shell_request_context.cc
@@ -16,6 +16,7 @@
#include "net/proxy/proxy_config_service.h"
#include "net/proxy/proxy_config_service_fixed.h"
#include "net/proxy/proxy_service.h"
+#include "webkit/blob/blob_storage_controller.h"
#include "webkit/glue/webkit_glue.h"
#include "webkit/tools/test_shell/simple_resource_loader_bridge.h"
@@ -79,6 +80,8 @@ void TestShellRequestContext::Init(
http_transaction_factory_ = cache;
ftp_transaction_factory_ = new net::FtpNetworkLayer(host_resolver_);
+
+ blob_storage_controller_.reset(new webkit_blob::BlobStorageController());
}
TestShellRequestContext::~TestShellRequestContext() {
diff --git a/webkit/tools/test_shell/test_shell_request_context.h b/webkit/tools/test_shell/test_shell_request_context.h
index c03d7909..1cb4ec1 100644
--- a/webkit/tools/test_shell/test_shell_request_context.h
+++ b/webkit/tools/test_shell/test_shell_request_context.h
@@ -11,6 +11,10 @@
class FilePath;
+namespace webkit_blob {
+class BlobStorageController;
+}
+
// A basic URLRequestContext that only provides an in-memory cookie store.
class TestShellRequestContext : public URLRequestContext {
public:
@@ -25,11 +29,17 @@ class TestShellRequestContext : public URLRequestContext {
virtual const std::string& GetUserAgent(const GURL& url) const;
+ webkit_blob::BlobStorageController* blob_storage_controller() const {
+ return blob_storage_controller_.get();
+ }
+
private:
~TestShellRequestContext();
void Init(const FilePath& cache_path, net::HttpCache::Mode cache_mode,
bool no_proxy);
+
+ scoped_ptr<webkit_blob::BlobStorageController> blob_storage_controller_;
};
#endif // WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_REQUEST_CONTEXT_H__
diff --git a/webkit/tools/test_shell/test_shell_webblobregistry_impl.cc b/webkit/tools/test_shell/test_shell_webblobregistry_impl.cc
new file mode 100644
index 0000000..a21a108
--- /dev/null
+++ b/webkit/tools/test_shell/test_shell_webblobregistry_impl.cc
@@ -0,0 +1,90 @@
+// Copyright (c) 2010 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/tools/test_shell/test_shell_webblobregistry_impl.h"
+
+#include "base/message_loop.h"
+#include "googleurl/src/gurl.h"
+#include "third_party/WebKit/WebKit/chromium/public/WebBlobData.h"
+#include "third_party/WebKit/WebKit/chromium/public/WebBlobStorageData.h"
+#include "third_party/WebKit/WebKit/chromium/public/WebString.h"
+#include "third_party/WebKit/WebKit/chromium/public/WebURL.h"
+#include "webkit/blob/blob_data.h"
+#include "webkit/blob/blob_storage_controller.h"
+
+using WebKit::WebBlobData;
+using WebKit::WebBlobStorageData;
+using WebKit::WebString;
+using WebKit::WebURL;
+
+MessageLoop* g_io_thread;
+webkit_blob::BlobStorageController* g_blob_storage_controller;
+
+/* static */
+void TestShellWebBlobRegistryImpl::InitializeOnIOThread(
+ webkit_blob::BlobStorageController* blob_storage_controller) {
+ g_io_thread = MessageLoop::current();
+ g_blob_storage_controller = blob_storage_controller;
+}
+
+/* static */
+void TestShellWebBlobRegistryImpl::Cleanup() {
+ g_io_thread = NULL;
+ g_blob_storage_controller = NULL;
+}
+
+TestShellWebBlobRegistryImpl::TestShellWebBlobRegistryImpl() {
+}
+
+void TestShellWebBlobRegistryImpl::registerBlobURL(
+ const WebURL& url, WebBlobData& data) {
+ DCHECK(g_io_thread);
+ scoped_refptr<webkit_blob::BlobData> blob_data(
+ new webkit_blob::BlobData(data));
+ blob_data->AddRef(); // Release on DoRegisterBlobURL.
+ g_io_thread->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this,
+ &TestShellWebBlobRegistryImpl::DoRegisterBlobUrl,
+ url,
+ blob_data.get()));
+}
+
+void TestShellWebBlobRegistryImpl::registerBlobURL(
+ const WebURL& url, const WebURL& src_url) {
+ DCHECK(g_io_thread);
+ g_io_thread->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this,
+ &TestShellWebBlobRegistryImpl::DoRegisterBlobUrlFrom,
+ url,
+ src_url));
+}
+
+void TestShellWebBlobRegistryImpl::unregisterBlobURL(const WebURL& url) {
+ DCHECK(g_io_thread);
+ g_io_thread->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this,
+ &TestShellWebBlobRegistryImpl::DoUnregisterBlobUrl,
+ url));
+}
+
+void TestShellWebBlobRegistryImpl::DoRegisterBlobUrl(
+ const GURL& url, webkit_blob::BlobData* blob_data) {
+ DCHECK(g_blob_storage_controller);
+ g_blob_storage_controller->RegisterBlobUrl(url, blob_data);
+ blob_data->Release();
+}
+
+void TestShellWebBlobRegistryImpl::DoRegisterBlobUrlFrom(
+ const GURL& url, const GURL& src_url) {
+ DCHECK(g_blob_storage_controller);
+ g_blob_storage_controller->RegisterBlobUrlFrom(url, src_url);
+}
+
+void TestShellWebBlobRegistryImpl::DoUnregisterBlobUrl(const GURL& url) {
+ DCHECK(g_blob_storage_controller);
+ g_blob_storage_controller->UnregisterBlobUrl(url);
+}
diff --git a/webkit/tools/test_shell/test_shell_webblobregistry_impl.h b/webkit/tools/test_shell/test_shell_webblobregistry_impl.h
new file mode 100644
index 0000000..3d7ea43
--- /dev/null
+++ b/webkit/tools/test_shell/test_shell_webblobregistry_impl.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2010 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_TOOLS_TEST_SHELL_TEST_SHELL_WEBBLOBREGISTRY_IMPL_H_
+#define WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_WEBBLOBREGISTRY_IMPL_H_
+
+#include "base/ref_counted.h"
+#include "third_party/WebKit/WebKit/chromium/public/WebBlobRegistry.h"
+
+class GURL;
+
+namespace webkit_blob {
+class BlobData;
+class BlobStorageController;
+}
+
+class TestShellWebBlobRegistryImpl
+ : public WebKit::WebBlobRegistry,
+ public base::RefCountedThreadSafe<TestShellWebBlobRegistryImpl> {
+ public:
+ static void InitializeOnIOThread(
+ webkit_blob::BlobStorageController* blob_storage_controller);
+ static void Cleanup();
+
+ TestShellWebBlobRegistryImpl();
+
+ // See WebBlobRegistry.h for documentation on these functions.
+ virtual void registerBlobURL(const WebKit::WebURL& url,
+ WebKit::WebBlobData& data);
+ virtual void registerBlobURL(const WebKit::WebURL& url,
+ const WebKit::WebURL& src_url);
+ virtual void unregisterBlobURL(const WebKit::WebURL& url);
+
+ // Run on I/O thread.
+ void DoRegisterBlobUrl(const GURL& url, webkit_blob::BlobData* blob_data);
+ void DoRegisterBlobUrlFrom(const GURL& url, const GURL& src_url);
+ void DoUnregisterBlobUrl(const GURL& url);
+
+ protected:
+ friend class base::RefCountedThreadSafe<TestShellWebBlobRegistryImpl>;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestShellWebBlobRegistryImpl);
+};
+
+#endif // WEBKIT_TOOLS_TEST_SHELL_TEST_SHELL_WEBBLOBREGISTRY_IMPL_H_
diff --git a/webkit/tools/test_shell/test_shell_webkit_init.cc b/webkit/tools/test_shell/test_shell_webkit_init.cc
index 706eaa6..5f458fe 100644
--- a/webkit/tools/test_shell/test_shell_webkit_init.cc
+++ b/webkit/tools/test_shell/test_shell_webkit_init.cc
@@ -66,6 +66,8 @@ TestShellWebKitInit::TestShellWebKitInit(bool layout_test_mode) {
WebKit::WebDatabase::setObserver(&database_system_);
+ blob_registry_ = new TestShellWebBlobRegistryImpl();
+
file_utilities_.set_sandbox_enabled(false);
#if defined(OS_WIN)
diff --git a/webkit/tools/test_shell/test_shell_webkit_init.h b/webkit/tools/test_shell/test_shell_webkit_init.h
index 4fdd1ce..f53db8c 100644
--- a/webkit/tools/test_shell/test_shell_webkit_init.h
+++ b/webkit/tools/test_shell/test_shell_webkit_init.h
@@ -18,6 +18,7 @@
#include "webkit/tools/test_shell/simple_database_system.h"
#include "webkit/tools/test_shell/simple_resource_loader_bridge.h"
#include "webkit/tools/test_shell/simple_webcookiejar_impl.h"
+#include "webkit/tools/test_shell/test_shell_webblobregistry_impl.h"
#include "webkit/tools/test_shell/test_shell_webmimeregistry_impl.h"
#if defined(OS_WIN)
@@ -47,6 +48,10 @@ class TestShellWebKitInit : public webkit_glue::WebKitClientImpl {
return &cookie_jar_;
}
+ virtual WebKit::WebBlobRegistry* blobRegistry() {
+ return blob_registry_.get();
+ }
+
virtual bool sandboxEnabled() {
return true;
}
@@ -141,6 +146,7 @@ class TestShellWebKitInit : public webkit_glue::WebKitClientImpl {
SimpleAppCacheSystem appcache_system_;
SimpleDatabaseSystem database_system_;
SimpleWebCookieJarImpl cookie_jar_;
+ scoped_refptr<TestShellWebBlobRegistryImpl> blob_registry_;
#if defined(OS_WIN)
WebKit::WebThemeEngine* active_theme_engine_;