diff options
author | jianli@chromium.org <jianli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-31 02:42:36 +0000 |
---|---|---|
committer | jianli@chromium.org <jianli@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-31 02:42:36 +0000 |
commit | 8107004fbfe9f7ea4be59f6620273c1206c12f42 (patch) | |
tree | f86046c0bb77f471f60d088626ad291360b356ff /webkit/blob | |
parent | 766f613f1c7022941e2e182543d0208a61210a16 (diff) | |
download | chromium_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.cc | 5 | ||||
-rw-r--r-- | webkit/blob/blob_data.h | 90 | ||||
-rw-r--r-- | webkit/blob/blob_storage_controller.cc | 81 | ||||
-rw-r--r-- | webkit/blob/blob_storage_controller.h | 8 | ||||
-rw-r--r-- | webkit/blob/blob_storage_controller_unittest.cc | 172 | ||||
-rw-r--r-- | webkit/blob/blob_url_request_job.cc | 532 | ||||
-rw-r--r-- | webkit/blob/blob_url_request_job.h | 87 | ||||
-rw-r--r-- | webkit/blob/blob_url_request_job_unittest.cc | 454 | ||||
-rw-r--r-- | webkit/blob/webkit_blob.gypi | 2 |
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', { |