diff options
author | adamk@chromium.org <adamk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-08 03:29:35 +0000 |
---|---|---|
committer | adamk@chromium.org <adamk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-08 03:29:35 +0000 |
commit | 8fbe7994047c3ec60182b4b2540d825c95e9f6ef (patch) | |
tree | af6dfd9cb03fa40c22ffad2b8a739f425f6d8626 /webkit/fileapi | |
parent | 6aa800f9d7af07c758cacd2c7d0425c706e4f057 (diff) | |
download | chromium_src-8fbe7994047c3ec60182b4b2540d825c95e9f6ef.zip chromium_src-8fbe7994047c3ec60182b4b2540d825c95e9f6ef.tar.gz chromium_src-8fbe7994047c3ec60182b4b2540d825c95e9f6ef.tar.bz2 |
First crack at FileSystemURLRequestJob for handling filesystem: URLs.
Disabled behind a switch, "--enable-filesystem-url-scheme".
Review URL: http://codereview.chromium.org/6262015
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@74082 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/fileapi')
-rw-r--r-- | webkit/fileapi/file_system_dir_url_request_job.cc | 146 | ||||
-rw-r--r-- | webkit/fileapi/file_system_dir_url_request_job.h | 65 | ||||
-rw-r--r-- | webkit/fileapi/file_system_dir_url_request_job_unittest.cc | 169 | ||||
-rw-r--r-- | webkit/fileapi/file_system_url_request_job.cc | 258 | ||||
-rw-r--r-- | webkit/fileapi/file_system_url_request_job.h | 81 | ||||
-rw-r--r-- | webkit/fileapi/file_system_url_request_job_unittest.cc | 276 | ||||
-rw-r--r-- | webkit/fileapi/file_system_util.cc | 62 | ||||
-rw-r--r-- | webkit/fileapi/file_system_util.h | 21 | ||||
-rw-r--r-- | webkit/fileapi/file_system_util_unittest.cc | 75 | ||||
-rw-r--r-- | webkit/fileapi/webkit_fileapi.gypi | 6 |
10 files changed, 1159 insertions, 0 deletions
diff --git a/webkit/fileapi/file_system_dir_url_request_job.cc b/webkit/fileapi/file_system_dir_url_request_job.cc new file mode 100644 index 0000000..95ba462 --- /dev/null +++ b/webkit/fileapi/file_system_dir_url_request_job.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/fileapi/file_system_dir_url_request_job.h" + +#include <algorithm> + +#include "base/compiler_specific.h" +#include "base/file_util_proxy.h" +#include "base/message_loop.h" +#include "base/platform_file.h" +#include "base/sys_string_conversions.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "build/build_config.h" +#include "googleurl/src/gurl.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/url_request/url_request.h" +#include "webkit/fileapi/file_system_path_manager.h" +#include "webkit/fileapi/file_system_util.h" + +using net::URLRequest; +using net::URLRequestJob; +using net::URLRequestStatus; + +namespace fileapi { + +FileSystemDirURLRequestJob::FileSystemDirURLRequestJob( + URLRequest* request, FileSystemPathManager* path_manager, + scoped_refptr<base::MessageLoopProxy> file_thread_proxy) + : URLRequestJob(request), + path_manager_(path_manager), + ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), + ALLOW_THIS_IN_INITIALIZER_LIST(callback_factory_(this)), + file_thread_proxy_(file_thread_proxy) { +} + +FileSystemDirURLRequestJob::~FileSystemDirURLRequestJob() { +} + +void FileSystemDirURLRequestJob::Start() { + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod( + &FileSystemDirURLRequestJob::StartAsync)); +} + +void FileSystemDirURLRequestJob::Kill() { + URLRequestJob::Kill(); + callback_factory_.RevokeAll(); +} + +bool FileSystemDirURLRequestJob::ReadRawData(net::IOBuffer* dest, int dest_size, + int *bytes_read) { + + int count = std::min(dest_size, static_cast<int>(data_.size())); + if (count > 0) { + memcpy(dest->data(), data_.data(), count); + data_.erase(0, count); + } + *bytes_read = count; + return true; +} + +bool FileSystemDirURLRequestJob::GetMimeType(std::string* mime_type) const { + *mime_type = "text/html"; + return true; +} + +bool FileSystemDirURLRequestJob::GetCharset(std::string* charset) { + *charset = "utf-8"; + return true; +} + +void FileSystemDirURLRequestJob::StartAsync() { + FileSystemType type; + if (!CrackFileSystemURL(request_->url(), &origin_url_, &type, + &relative_dir_path_)) { + NotifyFailed(net::ERR_INVALID_URL); + return; + } + + path_manager_->GetFileSystemRootPath( + origin_url_, type, false, // create + callback_factory_.NewCallback( + &FileSystemDirURLRequestJob::DidGetRootPath)); +} + +void FileSystemDirURLRequestJob::DidGetRootPath(bool success, + const FilePath& root_path, + const std::string& name) { + if (!success) { + NotifyFailed(net::ERR_FILE_NOT_FOUND); + return; + } + + absolute_dir_path_ = root_path.Append(relative_dir_path_); + + // We assume it's a directory if we've gotten here: either the path + // ends with '/', or FileSystemDirURLRequestJob already statted it and + // found it to be a directory. + base::FileUtilProxy::ReadDirectory(file_thread_proxy_, absolute_dir_path_, + callback_factory_.NewCallback( + &FileSystemDirURLRequestJob::DidReadDirectory)); +} + +void FileSystemDirURLRequestJob::DidReadDirectory( + base::PlatformFileError error_code, + const std::vector<base::FileUtilProxy::Entry>& entries) { + if (error_code != base::PLATFORM_FILE_OK) { + NotifyFailed(error_code); + return; + } + +#if defined(OS_WIN) + const string16& title = absolute_dir_path_.value(); +#elif defined(OS_POSIX) + const string16& title = WideToUTF16( + base::SysNativeMBToWide(absolute_dir_path_.value())); +#endif + data_.append(net::GetDirectoryListingHeader(title)); + + typedef std::vector<base::FileUtilProxy::Entry>::const_iterator EntryIterator; + for (EntryIterator it = entries.begin(); it != entries.end(); ++it) { +#if defined(OS_WIN) + const string16& name = it->name; +#elif defined(OS_POSIX) + const string16& name = + WideToUTF16(base::SysNativeMBToWide(it->name)); +#endif + // TODO(adamk): Add file size? + data_.append(net::GetDirectoryListingEntry( + name, std::string(), it->is_directory, 0, base::Time())); + } + + set_expected_content_size(data_.size()); + NotifyHeadersComplete(); +} + +void FileSystemDirURLRequestJob::NotifyFailed(int rv) { + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); +} + +} // namespace fileapi diff --git a/webkit/fileapi/file_system_dir_url_request_job.h b/webkit/fileapi/file_system_dir_url_request_job.h new file mode 100644 index 0000000..e7b2184 --- /dev/null +++ b/webkit/fileapi/file_system_dir_url_request_job.h @@ -0,0 +1,65 @@ +// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_ +#define WEBKIT_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/file_path.h" +#include "base/file_util_proxy.h" +#include "base/message_loop_proxy.h" +#include "base/platform_file.h" +#include "base/scoped_callback_factory.h" +#include "base/task.h" +#include "googleurl/src/gurl.h" +#include "net/url_request/url_request_job.h" + +namespace fileapi { +class FileSystemPathManager; + +// A request job that handles reading filesystem: URLs for directories. +class FileSystemDirURLRequestJob : public net::URLRequestJob { + public: + FileSystemDirURLRequestJob( + net::URLRequest* request, FileSystemPathManager* path_manager, + scoped_refptr<base::MessageLoopProxy> file_thread_proxy); + + // 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 bool GetCharset(std::string* charset); + // TODO(adamk): Implement the rest of the methods required to simulate HTTP. + + private: + virtual ~FileSystemDirURLRequestJob(); + + void StartAsync(); + void DidGetRootPath(bool success, const FilePath& root_path, + const std::string& name); + void DidReadDirectory(base::PlatformFileError error_code, + const std::vector<base::FileUtilProxy::Entry>& entries); + + void NotifyFailed(int rv); + + std::string data_; + FilePath relative_dir_path_; + FilePath absolute_dir_path_; + GURL origin_url_; + FileSystemPathManager* const path_manager_; + + ScopedRunnableMethodFactory<FileSystemDirURLRequestJob> method_factory_; + base::ScopedCallbackFactory<FileSystemDirURLRequestJob> callback_factory_; + scoped_refptr<base::MessageLoopProxy> file_thread_proxy_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemDirURLRequestJob); +}; + +} // namespace fileapi + +#endif // WEBKIT_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_ diff --git a/webkit/fileapi/file_system_dir_url_request_job_unittest.cc b/webkit/fileapi/file_system_dir_url_request_job_unittest.cc new file mode 100644 index 0000000..4d81513 --- /dev/null +++ b/webkit/fileapi/file_system_dir_url_request_job_unittest.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// NOTE: These tests are run as part of "unit_tests" (in chrome/test/unit) +// rather than as part of test_shell_tests because they rely on being able +// to instantiate a MessageLoop of type TYPE_IO. test_shell_tests uses +// TYPE_UI, which URLRequest doesn't allow. +// + +#include "webkit/fileapi/file_system_dir_url_request_job.h" + +#include "build/build_config.h" + +#include <string> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/format_macros.h" +#include "base/message_loop.h" +#include "base/platform_file.h" +#include "base/scoped_temp_dir.h" +#include "base/string_piece.h" +#include "base/threading/thread.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/http/http_request_headers.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/fileapi/file_system_path_manager.h" + +namespace fileapi { +namespace { + +// We always use the TEMPORARY FileSystem in this test. +static const char kFileSystemURLPrefix[] = "filesystem:http://remote/temporary/"; + +class FileSystemDirURLRequestJobTest : public testing::Test { + protected: + FileSystemDirURLRequestJobTest() + : message_loop_(MessageLoop::TYPE_IO), // simulate an IO thread + ALLOW_THIS_IN_INITIALIZER_LIST(callback_factory_(this)) { + } + + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + // We use the main thread so that we can get the root path synchronously. + // TODO(adamk): Run this on the FILE thread we've created as well. + path_manager_.reset(new FileSystemPathManager( + base::MessageLoopProxy::CreateForCurrentThread(), + temp_dir_.path(), false, false)); + + path_manager_->GetFileSystemRootPath( + GURL("http://remote/"), kFileSystemTypeTemporary, true, // create + callback_factory_.NewCallback( + &FileSystemDirURLRequestJobTest::OnGetRootPath)); + MessageLoop::current()->RunAllPending(); + + file_thread_.reset( + new base::Thread("FileSystemDirURLRequestJobTest FILE Thread")); + base::Thread::Options options(MessageLoop::TYPE_IO, 0); + file_thread_->StartWithOptions(options); + + net::URLRequest::RegisterProtocolFactory( + "filesystem", &FileSystemDirURLRequestJobFactory); + } + + virtual void TearDown() { + // NOTE: order matters, request must die before delegate + request_.reset(NULL); + delegate_.reset(NULL); + + file_thread_.reset(NULL); + net::URLRequest::RegisterProtocolFactory("filesystem", NULL); + } + + void OnGetRootPath(bool success, const FilePath& root_path, + const std::string& name) { + ASSERT_TRUE(success); + root_path_ = root_path; + } + + void TestRequest(const GURL& url) { + delegate_.reset(new TestDelegate()); + delegate_->set_quit_on_redirect(true); + request_.reset(new net::URLRequest(url, delegate_.get())); + job_ = new FileSystemDirURLRequestJob(request_.get(), path_manager_.get(), + file_thread_->message_loop_proxy()); + + request_->Start(); + ASSERT_TRUE(request_->is_pending()); // verify that we're starting async + MessageLoop::current()->Run(); + } + + void CreateDirectory(const base::StringPiece dir_name) { + FilePath path = root_path_.AppendASCII(dir_name); + ASSERT_TRUE(file_util::CreateDirectory(path)); + } + + GURL CreateFileSystemURL(const std::string path) { + return GURL(kFileSystemURLPrefix + path); + } + + static net::URLRequestJob* FileSystemDirURLRequestJobFactory( + net::URLRequest* request, + const std::string& scheme) { + DCHECK(job_); + net::URLRequestJob* temp = job_; + job_ = NULL; + return temp; + } + + ScopedTempDir temp_dir_; + FilePath root_path_; + scoped_ptr<net::URLRequest> request_; + scoped_ptr<TestDelegate> delegate_; + scoped_ptr<FileSystemPathManager> path_manager_; + scoped_ptr<base::Thread> file_thread_; + + MessageLoop message_loop_; + base::ScopedCallbackFactory<FileSystemDirURLRequestJobTest> callback_factory_; + + static net::URLRequestJob* job_; +}; + +// static +net::URLRequestJob* FileSystemDirURLRequestJobTest::job_ = NULL; + +// TODO(adamk): Write tighter tests once we've decided on a format for directory +// listing responses. +TEST_F(FileSystemDirURLRequestJobTest, DirectoryListing) { + CreateDirectory("foo"); + CreateDirectory("foo/bar"); + CreateDirectory("foo/bar/baz"); + + TestRequest(CreateFileSystemURL("foo/bar/")); + + ASSERT_FALSE(request_->is_pending()); + EXPECT_EQ(1, delegate_->response_started_count()); + EXPECT_FALSE(delegate_->received_data_before_response()); + EXPECT_GT(delegate_->bytes_received(), 0); +} + +TEST_F(FileSystemDirURLRequestJobTest, InvalidURL) { + TestRequest(GURL("filesystem:/foo/bar/baz")); + ASSERT_FALSE(request_->is_pending()); + EXPECT_TRUE(delegate_->request_failed()); + ASSERT_FALSE(request_->status().is_success()); + EXPECT_EQ(net::ERR_INVALID_URL, request_->status().os_error()); +} + +TEST_F(FileSystemDirURLRequestJobTest, NoSuchRoot) { + TestRequest(GURL("filesystem:http://remote/persistent/somedir/")); + ASSERT_FALSE(request_->is_pending()); + ASSERT_FALSE(request_->status().is_success()); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().os_error()); +} + +TEST_F(FileSystemDirURLRequestJobTest, NoSuchDirectory) { + TestRequest(CreateFileSystemURL("somedir/")); + ASSERT_FALSE(request_->is_pending()); + ASSERT_FALSE(request_->status().is_success()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, request_->status().os_error()); +} + +} // namespace (anonymous) +} // namespace fileapi diff --git a/webkit/fileapi/file_system_url_request_job.cc b/webkit/fileapi/file_system_url_request_job.cc new file mode 100644 index 0000000..c8b1c76 --- /dev/null +++ b/webkit/fileapi/file_system_url_request_job.cc @@ -0,0 +1,258 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/fileapi/file_system_url_request_job.h" + +#include "base/compiler_specific.h" +#include "base/file_util_proxy.h" +#include "base/message_loop.h" +#include "base/platform_file.h" +#include "base/threading/thread_restrictions.h" +#include "build/build_config.h" +#include "googleurl/src/gurl.h" +#include "net/base/file_stream.h" +#include "net/base/io_buffer.h" +#include "net/base/mime_util.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/http/http_util.h" +#include "net/url_request/url_request.h" +#include "webkit/fileapi/file_system_path_manager.h" +#include "webkit/fileapi/file_system_util.h" + +using net::URLRequest; +using net::URLRequestJob; +using net::URLRequestStatus; + +namespace fileapi { + +static const int kFileFlags = base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_ASYNC; + +FileSystemURLRequestJob::FileSystemURLRequestJob( + URLRequest* request, FileSystemPathManager* path_manager, + scoped_refptr<base::MessageLoopProxy> file_thread_proxy) + : URLRequestJob(request), + path_manager_(path_manager), + ALLOW_THIS_IN_INITIALIZER_LIST( + io_callback_(this, &FileSystemURLRequestJob::DidRead)), + stream_(NULL), + is_directory_(false), + remaining_bytes_(0), + startup_error_(0), + ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), + ALLOW_THIS_IN_INITIALIZER_LIST(callback_factory_(this)), + file_thread_proxy_(file_thread_proxy) { +} + +FileSystemURLRequestJob::~FileSystemURLRequestJob() { + // Since we use the two-arg constructor of FileStream, we need to call Close() + // manually: ~FileStream won't call it for us. + if (stream_ != NULL) + stream_->Close(); +} + +void FileSystemURLRequestJob::Start() { + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod( + &FileSystemURLRequestJob::StartAsync)); +} + +void FileSystemURLRequestJob::Kill() { + if (stream_ != NULL) { + stream_->Close(); + stream_.reset(NULL); + } + + URLRequestJob::Kill(); + callback_factory_.RevokeAll(); +} + +bool FileSystemURLRequestJob::ReadRawData(net::IOBuffer* dest, int dest_size, + int *bytes_read) { + DCHECK_NE(dest_size, 0); + DCHECK(bytes_read); + DCHECK_GE(remaining_bytes_, 0); + + if (stream_ == NULL) + return false; + + if (remaining_bytes_ < dest_size) + dest_size = static_cast<int>(remaining_bytes_); + + if (!dest_size) { + *bytes_read = 0; + return true; + } + + int rv = stream_->Read(dest->data(), dest_size, &io_callback_); + if (rv >= 0) { + // Data is immediately available. + *bytes_read = rv; + remaining_bytes_ -= rv; + DCHECK_GE(remaining_bytes_, 0); + return true; + } + + // Otherwise, a read error occured. We may just need to wait... + if (rv == net::ERR_IO_PENDING) + SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); + else + NotifyFailed(rv); + return false; +} + +bool FileSystemURLRequestJob::GetMimeType(std::string* mime_type) const { + // URL requests should not block on the disk! On Windows this goes to the + // registry. + // http://code.google.com/p/chromium/issues/detail?id=59849 + base::ThreadRestrictions::ScopedAllowIO allow_io; + DCHECK(request_); + return net::GetMimeTypeFromFile(absolute_file_path_, mime_type); +} + +void FileSystemURLRequestJob::SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) { + std::string range_header; + if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) { + std::vector<net::HttpByteRange> ranges; + if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) { + if (ranges.size() == 1) + byte_range_ = ranges[0]; + else { + // We don't support multiple range requests in one single URL request. + // TODO(adamk): decide whether we want to support multiple range + // requests. + startup_error_ = net::ERR_REQUEST_RANGE_NOT_SATISFIABLE; + } + } + } +} + +void FileSystemURLRequestJob::StartAsync() { + if (startup_error_) { + NotifyFailed(startup_error_); + return; + } + + FileSystemType type; + if (!CrackFileSystemURL(request_->url(), &origin_url_, &type, + &relative_file_path_)) { + NotifyFailed(net::ERR_INVALID_URL); + return; + } + + path_manager_->GetFileSystemRootPath( + origin_url_, type, false, // create + callback_factory_.NewCallback(&FileSystemURLRequestJob::DidGetRootPath)); +} + +void FileSystemURLRequestJob::DidGetRootPath(bool success, + const FilePath& root_path, + const std::string& name) { + if (!success) { + NotifyFailed(net::ERR_FILE_NOT_FOUND); + return; + } + + absolute_file_path_ = root_path.Append(relative_file_path_); + + base::FileUtilProxy::GetFileInfo(file_thread_proxy_, absolute_file_path_, + callback_factory_.NewCallback(&FileSystemURLRequestJob::DidResolve)); +} + +void FileSystemURLRequestJob::DidResolve(base::PlatformFileError error_code, + const base::PlatformFileInfo& file_info) { + // We may have been orphaned... + if (!request_) + return; + + // We use FileSystemURLRequestJob to handle files as well as directories + // without trailing slash. + // If a directory does not exist, we return ERR_FILE_NOT_FOUND. Otherwise, + // we will append trailing slash and redirect to FileDirJob. + if (error_code != base::PLATFORM_FILE_OK) { + NotifyFailed(error_code); + return; + } + + is_directory_ = file_info.is_directory; + + if (!byte_range_.ComputeBounds(file_info.size)) { + NotifyFailed(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); + return; + } + + if (!is_directory_) { + base::FileUtilProxy::CreateOrOpen( + file_thread_proxy_, absolute_file_path_, kFileFlags, + callback_factory_.NewCallback(&FileSystemURLRequestJob::DidOpen)); + } else + NotifyHeadersComplete(); +} + +void FileSystemURLRequestJob::DidOpen(base::PlatformFileError error_code, + base::PassPlatformFile file, + bool created) { + if (error_code != base::PLATFORM_FILE_OK) { + NotifyFailed(error_code); + return; + } + + stream_.reset(new net::FileStream(file.ReleaseValue(), kFileFlags)); + + 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 (remaining_bytes_ > 0 && + byte_range_.first_byte_position() != 0 && + byte_range_.first_byte_position() != + stream_->Seek(net::FROM_BEGIN, byte_range_.first_byte_position())) { + NotifyFailed(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); + return; + } + + set_expected_content_size(remaining_bytes_); + NotifyHeadersComplete(); +} + +void FileSystemURLRequestJob::DidRead(int result) { + if (result > 0) + SetStatus(URLRequestStatus()); // Clear the IO_PENDING status + else if (result == 0) + NotifyDone(URLRequestStatus()); + else + NotifyFailed(result); + + remaining_bytes_ -= result; + DCHECK_GE(remaining_bytes_, 0); + + NotifyReadComplete(result); +} + +bool FileSystemURLRequestJob::IsRedirectResponse(GURL* location, + int* http_status_code) { + if (is_directory_) { + // This happens when we discovered the file is a directory, so needs a + // slash at the end of the path. + std::string new_path = request_->url().path(); + new_path.push_back('/'); + GURL::Replacements replacements; + replacements.SetPathStr(new_path); + *location = request_->url().ReplaceComponents(replacements); + *http_status_code = 301; // simulate a permanent redirect + return true; + } + + return false; +} + +void FileSystemURLRequestJob::NotifyFailed(int rv) { + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); +} + +} // namespace fileapi diff --git a/webkit/fileapi/file_system_url_request_job.h b/webkit/fileapi/file_system_url_request_job.h new file mode 100644 index 0000000..c49d873 --- /dev/null +++ b/webkit/fileapi/file_system_url_request_job.h @@ -0,0 +1,81 @@ +// Copyright (c) 2006-2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_ +#define WEBKIT_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_ +#pragma once + +#include <string> + +#include "base/file_path.h" +#include "base/message_loop_proxy.h" +#include "base/platform_file.h" +#include "base/scoped_callback_factory.h" +#include "base/scoped_ptr.h" +#include "base/task.h" +#include "googleurl/src/gurl.h" +#include "net/base/completion_callback.h" +#include "net/http/http_byte_range.h" +#include "net/url_request/url_request_job.h" + +namespace net { +class FileStream; +} + +namespace fileapi { +class FileSystemPathManager; + +// A request job that handles reading filesystem: URLs +class FileSystemURLRequestJob : public net::URLRequestJob { + public: + FileSystemURLRequestJob( + net::URLRequest* request, FileSystemPathManager* path_manager, + scoped_refptr<base::MessageLoopProxy> file_thread_proxy); + + // URLRequestJob methods: + virtual void Start(); + virtual void Kill(); + virtual bool ReadRawData(net::IOBuffer* buf, int buf_size, int* bytes_read); + virtual bool IsRedirectResponse(GURL* location, int* http_status_code); + virtual bool GetMimeType(std::string* mime_type) const; + virtual void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers); + // TODO(adamk): Implement the rest of the methods required to simulate HTTP. + + private: + virtual ~FileSystemURLRequestJob(); + + void StartAsync(); + void DidGetRootPath(bool success, const FilePath& root_path, + const std::string& name); + void DidResolve(base::PlatformFileError error_code, + const base::PlatformFileInfo& file_info); + void DidOpen(base::PlatformFileError error_code, + base::PassPlatformFile file, bool created); + void DidRead(int result); + + void NotifyFailed(int rv); + + FilePath relative_file_path_; + FilePath absolute_file_path_; + GURL origin_url_; + FileSystemPathManager* const path_manager_; + + net::CompletionCallbackImpl<FileSystemURLRequestJob> io_callback_; + scoped_ptr<net::FileStream> stream_; + bool is_directory_; + + net::HttpByteRange byte_range_; + int64 remaining_bytes_; + int startup_error_; + + ScopedRunnableMethodFactory<FileSystemURLRequestJob> method_factory_; + base::ScopedCallbackFactory<FileSystemURLRequestJob> callback_factory_; + scoped_refptr<base::MessageLoopProxy> file_thread_proxy_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemURLRequestJob); +}; + +} // namespace fileapi + +#endif // WEBKIT_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_ diff --git a/webkit/fileapi/file_system_url_request_job_unittest.cc b/webkit/fileapi/file_system_url_request_job_unittest.cc new file mode 100644 index 0000000..3e76338 --- /dev/null +++ b/webkit/fileapi/file_system_url_request_job_unittest.cc @@ -0,0 +1,276 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// NOTE: These tests are run as part of "unit_tests" (in chrome/test/unit) +// rather than as part of test_shell_tests because they rely on being able +// to instantiate a MessageLoop of type TYPE_IO. test_shell_tests uses +// TYPE_UI, which URLRequest doesn't allow. +// + +#include "webkit/fileapi/file_system_url_request_job.h" + +#include "build/build_config.h" + +#include <string> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/format_macros.h" +#include "base/message_loop.h" +#include "base/platform_file.h" +#include "base/scoped_temp_dir.h" +#include "base/string_piece.h" +#include "base/stringprintf.h" +#include "base/threading/thread.h" +#include "base/utf_string_conversions.h" +#include "net/base/load_flags.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/http/http_request_headers.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/fileapi/file_system_path_manager.h" + +namespace fileapi { +namespace { + +// We always use the TEMPORARY FileSystem in this test. +const char kFileSystemURLPrefix[] = "filesystem:http://remote/temporary/"; +const char kTestFileData[] = "0123456789"; + +void FillBuffer(char* buffer, size_t len) { + static bool called = false; + if (!called) { + called = true; + int seed = static_cast<int>(base::Time::Now().ToInternalValue()); + srand(seed); + } + + for (size_t i = 0; i < len; i++) { + buffer[i] = static_cast<char>(rand()); + if (!buffer[i]) + buffer[i] = 'g'; + } +} + +class FileSystemURLRequestJobTest : public testing::Test { + protected: + FileSystemURLRequestJobTest() + : message_loop_(MessageLoop::TYPE_IO), // simulate an IO thread + ALLOW_THIS_IN_INITIALIZER_LIST(callback_factory_(this)) { + } + + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + // We use the main thread so that we can get the root path synchronously. + // TODO(adamk): Run this on the FILE thread we've created as well. + path_manager_.reset(new FileSystemPathManager( + base::MessageLoopProxy::CreateForCurrentThread(), + temp_dir_.path(), false, false)); + + path_manager_->GetFileSystemRootPath( + GURL("http://remote/"), kFileSystemTypeTemporary, true, // create + callback_factory_.NewCallback( + &FileSystemURLRequestJobTest::OnGetRootPath)); + MessageLoop::current()->RunAllPending(); + + file_thread_.reset( + new base::Thread("FileSystemURLRequestJobTest FILE Thread")); + base::Thread::Options options(MessageLoop::TYPE_IO, 0); + file_thread_->StartWithOptions(options); + + net::URLRequest::RegisterProtocolFactory("filesystem", + &FileSystemURLRequestJobFactory); + } + + virtual void TearDown() { + // NOTE: order matters, request must die before delegate + request_.reset(NULL); + delegate_.reset(NULL); + + file_thread_.reset(NULL); + net::URLRequest::RegisterProtocolFactory("filesystem", NULL); + } + + void OnGetRootPath(bool success, const FilePath& root_path, + const std::string& name) { + ASSERT_TRUE(success); + origin_root_path_ = root_path; + } + + void TestRequest(const GURL& url) { + TestRequestWithHeaders(url, NULL); + } + + void TestRequestWithHeaders(const GURL& url, + const net::HttpRequestHeaders* headers) { + delegate_.reset(new TestDelegate()); + // Make delegate_ exit the MessageLoop when the request is done. + delegate_->set_quit_on_complete(true); + delegate_->set_quit_on_redirect(true); + request_.reset(new net::URLRequest(url, delegate_.get())); + if (headers) + request_->SetExtraRequestHeaders(*headers); + job_ = new FileSystemURLRequestJob(request_.get(), path_manager_.get(), + file_thread_->message_loop_proxy()); + + request_->Start(); + ASSERT_TRUE(request_->is_pending()); // verify that we're starting async + MessageLoop::current()->Run(); + } + + void WriteFile(const base::StringPiece file_name, + const char* buf, int buf_size) { + FilePath path = origin_root_path_.AppendASCII(file_name); + ASSERT_EQ(buf_size, file_util::WriteFile(path, buf, buf_size)); + } + + GURL CreateFileSystemURL(const std::string& path) { + return GURL(kFileSystemURLPrefix + path); + } + + static net::URLRequestJob* FileSystemURLRequestJobFactory( + net::URLRequest* request, + const std::string& scheme) { + DCHECK(job_); + net::URLRequestJob* temp = job_; + job_ = NULL; + return temp; + } + + ScopedTempDir temp_dir_; + FilePath origin_root_path_; + scoped_ptr<net::URLRequest> request_; + scoped_ptr<TestDelegate> delegate_; + scoped_ptr<FileSystemPathManager> path_manager_; + scoped_ptr<base::Thread> file_thread_; + + MessageLoop message_loop_; + base::ScopedCallbackFactory<FileSystemURLRequestJobTest> callback_factory_; + + static net::URLRequestJob* job_; +}; + +// static +net::URLRequestJob* FileSystemURLRequestJobTest::job_ = NULL; + +TEST_F(FileSystemURLRequestJobTest, FileTest) { + WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); + TestRequest(CreateFileSystemURL("file1.dat")); + + ASSERT_FALSE(request_->is_pending()); + EXPECT_EQ(1, delegate_->response_started_count()); + EXPECT_FALSE(delegate_->received_data_before_response()); + EXPECT_EQ(kTestFileData, delegate_->data_received()); +} + +TEST_F(FileSystemURLRequestJobTest, FileTestFullSpecifiedRange) { + const size_t buffer_size = 4000; + scoped_array<char> buffer(new char[buffer_size]); + FillBuffer(buffer.get(), buffer_size); + WriteFile("bigfile", buffer.get(), buffer_size); + + const size_t first_byte_position = 500; + const size_t last_byte_position = buffer_size - first_byte_position; + std::string partial_buffer_string(buffer.get() + first_byte_position, + buffer.get() + last_byte_position + 1); + + net::HttpRequestHeaders headers; + headers.SetHeader(net::HttpRequestHeaders::kRange, + base::StringPrintf( + "bytes=%" PRIuS "-%" PRIuS, + first_byte_position, last_byte_position)); + TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers); + + ASSERT_FALSE(request_->is_pending()); + EXPECT_EQ(1, delegate_->response_started_count()); + EXPECT_FALSE(delegate_->received_data_before_response()); + EXPECT_TRUE(partial_buffer_string == delegate_->data_received()); +} + +TEST_F(FileSystemURLRequestJobTest, FileTestHalfSpecifiedRange) { + const size_t buffer_size = 4000; + scoped_array<char> buffer(new char[buffer_size]); + FillBuffer(buffer.get(), buffer_size); + WriteFile("bigfile", buffer.get(), buffer_size); + + const size_t first_byte_position = 500; + std::string partial_buffer_string(buffer.get() + first_byte_position, + buffer.get() + buffer_size); + + net::HttpRequestHeaders headers; + headers.SetHeader(net::HttpRequestHeaders::kRange, + base::StringPrintf("bytes=%" PRIuS "-", + first_byte_position)); + TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers); + ASSERT_FALSE(request_->is_pending()); + EXPECT_EQ(1, delegate_->response_started_count()); + EXPECT_FALSE(delegate_->received_data_before_response()); + // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed. + EXPECT_TRUE(partial_buffer_string == delegate_->data_received()); +} + + +TEST_F(FileSystemURLRequestJobTest, FileTestMultipleRangesNotSupported) { + WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); + net::HttpRequestHeaders headers; + headers.SetHeader(net::HttpRequestHeaders::kRange, + "bytes=0-5,10-200,200-300"); + TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers); + ASSERT_FALSE(request_->is_pending()); + EXPECT_TRUE(delegate_->request_failed()); + EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, + request_->status().os_error()); +} + +TEST_F(FileSystemURLRequestJobTest, RangeOutOfBounds) { + WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); + net::HttpRequestHeaders headers; + headers.SetHeader(net::HttpRequestHeaders::kRange, "bytes=500-1000"); + TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers); + + ASSERT_FALSE(request_->is_pending()); + EXPECT_TRUE(delegate_->request_failed()); + EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, + request_->status().os_error()); +} + +TEST_F(FileSystemURLRequestJobTest, FileDirRedirect) { + ASSERT_TRUE(file_util::CreateDirectory(origin_root_path_.AppendASCII("dir"))); + TestRequest(CreateFileSystemURL("dir")); + + EXPECT_EQ(1, delegate_->received_redirect_count()); + EXPECT_TRUE(request_->status().is_success()); + EXPECT_FALSE(delegate_->request_failed()); + + // We've deferred the redirect; now cancel the request to avoid following it. + request_->Cancel(); + MessageLoop::current()->Run(); +} + +TEST_F(FileSystemURLRequestJobTest, InvalidURL) { + TestRequest(GURL("filesystem:/foo/bar/baz")); + ASSERT_FALSE(request_->is_pending()); + EXPECT_TRUE(delegate_->request_failed()); + EXPECT_EQ(net::ERR_INVALID_URL, request_->status().os_error()); +} + +TEST_F(FileSystemURLRequestJobTest, NoSuchRoot) { + TestRequest(GURL("filesystem:http://remote/persistent/somefile")); + ASSERT_FALSE(request_->is_pending()); + EXPECT_TRUE(delegate_->request_failed()); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().os_error()); +} + +TEST_F(FileSystemURLRequestJobTest, NoSuchFile) { + TestRequest(CreateFileSystemURL("somefile")); + ASSERT_FALSE(request_->is_pending()); + EXPECT_TRUE(delegate_->request_failed()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, request_->status().os_error()); +} + +} // namespace (anonymous) +} // namespace fileapi diff --git a/webkit/fileapi/file_system_util.cc b/webkit/fileapi/file_system_util.cc new file mode 100644 index 0000000..07aa5a6 --- /dev/null +++ b/webkit/fileapi/file_system_util.cc @@ -0,0 +1,62 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/fileapi/file_system_util.h" + +#include "build/build_config.h" + +#include "base/file_path.h" +#include "base/sys_string_conversions.h" +#include "googleurl/src/gurl.h" +#include "net/base/escape.h" +#include "webkit/fileapi/file_system_types.h" + +namespace fileapi { + +static const char kPersistentDir[] = "/persistent/"; +static const char kTemporaryDir[] = "/temporary/"; + +bool CrackFileSystemURL(const GURL& url, GURL* origin_url, FileSystemType* type, + FilePath* file_path) { + *origin_url = GURL(); + *type = kFileSystemTypeUnknown; + *file_path = FilePath(); + + if (url.scheme() != "filesystem") + return false; + + GURL bare_url(url.path()); + *origin_url = bare_url.GetOrigin(); + + // The input URL was malformed, bail out early. + if (origin_url->is_empty() || bare_url.path().empty()) + return false; + + std::string path = UnescapeURLComponent(bare_url.path(), + UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS); + if (path.compare(0, strlen(kPersistentDir), kPersistentDir) == 0) { + *type = kFileSystemTypePersistent; + path = path.substr(strlen(kPersistentDir)); + } else if (path.compare(0, strlen(kTemporaryDir), kTemporaryDir) == 0) { + *type = kFileSystemTypeTemporary; + path = path.substr(strlen(kTemporaryDir)); + } else { + return false; + } + + // Ensure the path is relative. + while (!path.empty() && path[0] == '/') + path.erase(0, 1); + +#if defined(OS_WIN) + const FilePath::StringType& sys_path = base::SysUTF8ToWide(path); +#elif defined(OS_POSIX) + const FilePath::StringType& sys_path = path; +#endif + + *file_path = FilePath(sys_path); + return true; +} + +} // namespace fileapi diff --git a/webkit/fileapi/file_system_util.h b/webkit/fileapi/file_system_util.h new file mode 100644 index 0000000..4331b1a --- /dev/null +++ b/webkit/fileapi/file_system_util.h @@ -0,0 +1,21 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_FILEAPI_FILE_SYSTEM_UTIL_H_ +#define WEBKIT_FILEAPI_FILE_SYSTEM_UTIL_H_ +#pragma once + +#include "webkit/fileapi/file_system_types.h" + +class FilePath; +class GURL; + +namespace fileapi { + +bool CrackFileSystemURL(const GURL& url, GURL* origin_url, FileSystemType* type, + FilePath* file_path); + +} // namespace fileapi + +#endif // WEBKIT_FILEAPI_FILE_SYSTEM_UTIL_H_ diff --git a/webkit/fileapi/file_system_util_unittest.cc b/webkit/fileapi/file_system_util_unittest.cc new file mode 100644 index 0000000..7cb4c59 --- /dev/null +++ b/webkit/fileapi/file_system_util_unittest.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/fileapi/file_system_util.h" + +#include "base/file_path.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/fileapi/file_system_types.h" + +namespace fileapi { +namespace { + +class FileSystemUtilTest : public testing::Test { + protected: + bool CrackFileSystemURL(const char* url) { + return fileapi::CrackFileSystemURL( + GURL(url), &origin_url_, &type_, &file_path_); + } + + GURL origin_url_; + FileSystemType type_; + FilePath file_path_; +}; + +TEST_F(FileSystemUtilTest, ParsePersistent) { + ASSERT_TRUE(CrackFileSystemURL( + "filesystem:http://chromium.org/persistent/directory/file")); + EXPECT_EQ("http://chromium.org/", origin_url_.spec()); + EXPECT_EQ(kFileSystemTypePersistent, type_); + EXPECT_EQ(FILE_PATH_LITERAL("directory/file"), file_path_.value()); +} + +TEST_F(FileSystemUtilTest, ParseTemporary) { + ASSERT_TRUE(CrackFileSystemURL( + "filesystem:http://chromium.org/temporary/directory/file")); + EXPECT_EQ("http://chromium.org/", origin_url_.spec()); + EXPECT_EQ(kFileSystemTypeTemporary, type_); + EXPECT_EQ(FILE_PATH_LITERAL("directory/file"), file_path_.value()); +} + +TEST_F(FileSystemUtilTest, EnsureFilePathIsRelative) { + ASSERT_TRUE(CrackFileSystemURL( + "filesystem:http://chromium.org/temporary/////directory/file")); + EXPECT_EQ("http://chromium.org/", origin_url_.spec()); + EXPECT_EQ(kFileSystemTypeTemporary, type_); + EXPECT_EQ(FILE_PATH_LITERAL("directory/file"), file_path_.value()); + EXPECT_FALSE(file_path_.IsAbsolute()); +} + +TEST_F(FileSystemUtilTest, RejectBadSchemes) { + EXPECT_FALSE(CrackFileSystemURL("http://chromium.org/")); + EXPECT_FALSE(CrackFileSystemURL("https://chromium.org/")); + EXPECT_FALSE(CrackFileSystemURL("file:///foo/bar")); + EXPECT_FALSE(CrackFileSystemURL("foobar:///foo/bar")); +} + +TEST_F(FileSystemUtilTest, UnescapePath) { + ASSERT_TRUE(CrackFileSystemURL( + "filesystem:http://chromium.org/persistent/%7Echromium/space%20bar")); + EXPECT_EQ(FILE_PATH_LITERAL("~chromium/space bar"), file_path_.value()); +} + +TEST_F(FileSystemUtilTest, RejectBadType) { + EXPECT_FALSE(CrackFileSystemURL("filesystem:http://c.org/foobar/file")); +} + +TEST_F(FileSystemUtilTest, RejectMalformedURL) { + EXPECT_FALSE(CrackFileSystemURL("filesystem:///foobar/file")); + EXPECT_FALSE(CrackFileSystemURL("filesystem:foobar/file")); +} + +} // namespace (anonymous) +} // namespace fileapi diff --git a/webkit/fileapi/webkit_fileapi.gypi b/webkit/fileapi/webkit_fileapi.gypi index e380a2c..f5f8675 100644 --- a/webkit/fileapi/webkit_fileapi.gypi +++ b/webkit/fileapi/webkit_fileapi.gypi @@ -15,6 +15,8 @@ ], 'sources': [ 'file_system_callback_dispatcher.h', + 'file_system_dir_url_request_job.cc', + 'file_system_dir_url_request_job.h', 'file_system_operation.cc', 'file_system_operation.h', 'file_system_path_manager.cc', @@ -22,6 +24,10 @@ 'file_system_quota_manager.cc', 'file_system_quota_manager.h', 'file_system_types.h', + 'file_system_url_request_job.cc', + 'file_system_url_request_job.h', + 'file_system_util.cc', + 'file_system_util.h', 'file_writer_delegate.cc', 'file_writer_delegate.h', 'sandboxed_file_system_context.cc', |