summaryrefslogtreecommitdiffstats
path: root/webkit/blob
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 /webkit/blob
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
Diffstat (limited to 'webkit/blob')
-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
9 files changed, 1369 insertions, 62 deletions
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', {