diff options
| author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
|---|---|---|
| committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
| commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
| tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/webkit/browser/fileapi | |
| download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz | |
Initial import.
Diffstat (limited to 'chromium/webkit/browser/fileapi')
132 files changed, 29101 insertions, 0 deletions
diff --git a/chromium/webkit/browser/fileapi/OWNERS b/chromium/webkit/browser/fileapi/OWNERS new file mode 100644 index 00000000000..6ca58a9dbe5 --- /dev/null +++ b/chromium/webkit/browser/fileapi/OWNERS @@ -0,0 +1,2 @@ +ericu@chromium.org +tzik@chromium.org diff --git a/chromium/webkit/browser/fileapi/async_file_test_helper.cc b/chromium/webkit/browser/fileapi/async_file_test_helper.cc new file mode 100644 index 00000000000..8a01fd2a474 --- /dev/null +++ b/chromium/webkit/browser/fileapi/async_file_test_helper.cc @@ -0,0 +1,239 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/run_loop.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +namespace { + +typedef FileSystemOperation::FileEntryList FileEntryList; + +void AssignAndQuit(base::RunLoop* run_loop, + base::PlatformFileError* result_out, + base::PlatformFileError result) { + *result_out = result; + run_loop->Quit(); +} + +base::Callback<void(base::PlatformFileError)> +AssignAndQuitCallback(base::RunLoop* run_loop, + base::PlatformFileError* result) { + return base::Bind(&AssignAndQuit, run_loop, base::Unretained(result)); +} + +void GetMetadataCallback(base::RunLoop* run_loop, + base::PlatformFileError* result_out, + base::PlatformFileInfo* file_info_out, + base::PlatformFileError result, + const base::PlatformFileInfo& file_info) { + *result_out = result; + if (file_info_out) + *file_info_out = file_info; + run_loop->Quit(); +} + +void CreateSnapshotFileCallback( + base::RunLoop* run_loop, + base::PlatformFileError* result_out, + base::FilePath* platform_path_out, + base::PlatformFileError result, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) { + DCHECK(!file_ref.get()); + *result_out = result; + if (platform_path_out) + *platform_path_out = platform_path; + run_loop->Quit(); +} + +void ReadDirectoryCallback(base::RunLoop* run_loop, + base::PlatformFileError* result_out, + FileEntryList* entries_out, + base::PlatformFileError result, + const FileEntryList& entries, + bool has_more) { + *result_out = result; + *entries_out = entries; + if (result != base::PLATFORM_FILE_OK || !has_more) + run_loop->Quit(); +} + +void DidGetUsageAndQuota(quota::QuotaStatusCode* status_out, + int64* usage_out, + int64* quota_out, + quota::QuotaStatusCode status, + int64 usage, + int64 quota) { + if (status_out) + *status_out = status; + if (usage_out) + *usage_out = usage; + if (quota_out) + *quota_out = quota; +} + +} // namespace + +const int64 AsyncFileTestHelper::kDontCheckSize = -1; + +base::PlatformFileError AsyncFileTestHelper::Copy( + FileSystemContext* context, + const FileSystemURL& src, + const FileSystemURL& dest) { + base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED; + base::RunLoop run_loop; + context->operation_runner()->Copy( + src, dest, AssignAndQuitCallback(&run_loop, &result)); + run_loop.Run(); + return result; +} + +base::PlatformFileError AsyncFileTestHelper::Move( + FileSystemContext* context, + const FileSystemURL& src, + const FileSystemURL& dest) { + base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED; + base::RunLoop run_loop; + context->operation_runner()->Move( + src, dest, AssignAndQuitCallback(&run_loop, &result)); + run_loop.Run(); + return result; +} + +base::PlatformFileError AsyncFileTestHelper::Remove( + FileSystemContext* context, + const FileSystemURL& url, + bool recursive) { + base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED; + base::RunLoop run_loop; + context->operation_runner()->Remove( + url, recursive, AssignAndQuitCallback(&run_loop, &result)); + run_loop.Run(); + return result; +} + +base::PlatformFileError AsyncFileTestHelper::ReadDirectory( + FileSystemContext* context, + const FileSystemURL& url, + FileEntryList* entries) { + base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED; + DCHECK(entries); + entries->clear(); + base::RunLoop run_loop; + context->operation_runner()->ReadDirectory( + url, base::Bind(&ReadDirectoryCallback, &run_loop, &result, entries)); + run_loop.Run(); + return result; +} + +base::PlatformFileError AsyncFileTestHelper::CreateDirectory( + FileSystemContext* context, + const FileSystemURL& url) { + base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED; + base::RunLoop run_loop; + context->operation_runner()->CreateDirectory( + url, + false /* exclusive */, + false /* recursive */, + AssignAndQuitCallback(&run_loop, &result)); + run_loop.Run(); + return result; +} + +base::PlatformFileError AsyncFileTestHelper::CreateFile( + FileSystemContext* context, + const FileSystemURL& url) { + base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED; + base::RunLoop run_loop; + context->operation_runner()->CreateFile( + url, false /* exclusive */, + AssignAndQuitCallback(&run_loop, &result)); + run_loop.Run(); + return result; +} + +base::PlatformFileError AsyncFileTestHelper::TruncateFile( + FileSystemContext* context, + const FileSystemURL& url, + size_t size) { + base::RunLoop run_loop; + base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED; + context->operation_runner()->Truncate( + url, size, AssignAndQuitCallback(&run_loop, &result)); + run_loop.Run(); + return result; +} + +base::PlatformFileError AsyncFileTestHelper::GetMetadata( + FileSystemContext* context, + const FileSystemURL& url, + base::PlatformFileInfo* file_info) { + base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED; + base::RunLoop run_loop; + context->operation_runner()->GetMetadata( + url, base::Bind(&GetMetadataCallback, &run_loop, &result, + file_info)); + run_loop.Run(); + return result; +} + +base::PlatformFileError AsyncFileTestHelper::GetPlatformPath( + FileSystemContext* context, + const FileSystemURL& url, + base::FilePath* platform_path) { + base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED; + base::RunLoop run_loop; + context->operation_runner()->CreateSnapshotFile( + url, base::Bind(&CreateSnapshotFileCallback, &run_loop, &result, + platform_path)); + run_loop.Run(); + return result; +} + +bool AsyncFileTestHelper::FileExists( + FileSystemContext* context, + const FileSystemURL& url, + int64 expected_size) { + base::PlatformFileInfo file_info; + base::PlatformFileError result = GetMetadata(context, url, &file_info); + if (result != base::PLATFORM_FILE_OK || file_info.is_directory) + return false; + return expected_size == kDontCheckSize || file_info.size == expected_size; +} + +bool AsyncFileTestHelper::DirectoryExists( + FileSystemContext* context, + const FileSystemURL& url) { + base::PlatformFileInfo file_info; + base::PlatformFileError result = GetMetadata(context, url, &file_info); + return (result == base::PLATFORM_FILE_OK) && file_info.is_directory; +} + +quota::QuotaStatusCode AsyncFileTestHelper::GetUsageAndQuota( + quota::QuotaManager* quota_manager, + const GURL& origin, + FileSystemType type, + int64* usage, + int64* quota) { + quota::QuotaStatusCode status = quota::kQuotaStatusUnknown; + quota_manager->GetUsageAndQuota( + origin, + FileSystemTypeToQuotaStorageType(type), + base::Bind(&DidGetUsageAndQuota, &status, usage, quota)); + base::MessageLoop::current()->RunUntilIdle(); + return status; +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/async_file_test_helper.h b/chromium/webkit/browser/fileapi/async_file_test_helper.h new file mode 100644 index 00000000000..cfa7981e03e --- /dev/null +++ b/chromium/webkit/browser/fileapi/async_file_test_helper.h @@ -0,0 +1,94 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_TEST_HELPER_H_ +#define WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_TEST_HELPER_H_ + +#include "base/basictypes.h" +#include "webkit/browser/fileapi/file_system_operation.h" +#include "webkit/common/fileapi/file_system_types.h" +#include "webkit/common/quota/quota_status_code.h" + +namespace quota { +class QuotaManager; +} + +namespace fileapi { + +class FileSystemContext; +class FileSystemURL; + +// A helper class to perform async file operations in a synchronous way. +class AsyncFileTestHelper { + public: + typedef FileSystemOperation::FileEntryList FileEntryList; + + static const int64 kDontCheckSize; + + // Performs Copy from |src| to |dest| and returns the status code. + static base::PlatformFileError Copy(FileSystemContext* context, + const FileSystemURL& src, + const FileSystemURL& dest); + + // Performs Move from |src| to |dest| and returns the status code. + static base::PlatformFileError Move(FileSystemContext* context, + const FileSystemURL& src, + const FileSystemURL& dest); + + // Removes the given |url|. + static base::PlatformFileError Remove(FileSystemContext* context, + const FileSystemURL& url, + bool recursive); + + // Performs ReadDirectory on |url|. + static base::PlatformFileError ReadDirectory(FileSystemContext* context, + const FileSystemURL& url, + FileEntryList* entries); + + // Creates a directory at |url|. + static base::PlatformFileError CreateDirectory(FileSystemContext* context, + const FileSystemURL& url); + + // Creates a file at |url|. + static base::PlatformFileError CreateFile(FileSystemContext* context, + const FileSystemURL& url); + + // Truncates the file |url| to |size|. + static base::PlatformFileError TruncateFile(FileSystemContext* context, + const FileSystemURL& url, + size_t size); + + // Retrieves PlatformFileInfo for |url| and populates |file_info|. + static base::PlatformFileError GetMetadata(FileSystemContext* context, + const FileSystemURL& url, + base::PlatformFileInfo* file_info); + + // Retrieves FilePath for |url| and populates |platform_path|. + static base::PlatformFileError GetPlatformPath(FileSystemContext* context, + const FileSystemURL& url, + base::FilePath* platform_path); + + // Returns true if a file exists at |url| with |size|. If |size| is + // kDontCheckSize it doesn't check the file size (but just check its + // existence). + static bool FileExists(FileSystemContext* context, + const FileSystemURL& url, + int64 size); + + // Returns true if a directory exists at |url|. + static bool DirectoryExists(FileSystemContext* context, + const FileSystemURL& url); + + // Returns usage and quota. It's valid to pass NULL to |usage| and/or |quota|. + static quota::QuotaStatusCode GetUsageAndQuota( + quota::QuotaManager* quota_manager, + const GURL& origin, + FileSystemType type, + int64* usage, + int64* quota); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_TEST_HELPER_H_ diff --git a/chromium/webkit/browser/fileapi/async_file_util.h b/chromium/webkit/browser/fileapi/async_file_util.h new file mode 100644 index 00000000000..dc8216eaac0 --- /dev/null +++ b/chromium/webkit/browser/fileapi/async_file_util.h @@ -0,0 +1,346 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_UTIL_H_ +#define WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_UTIL_H_ + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/files/file_util_proxy.h" +#include "base/memory/scoped_ptr.h" +#include "base/platform_file.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/fileapi/directory_entry.h" + +namespace base { +class Time; +} + +namespace webkit_blob { +class ShareableFileReference; +} + +namespace fileapi { + +class FileSystemOperationContext; +class FileSystemURL; + +// An interface which provides filesystem-specific file operations for +// FileSystemOperationImpl. +// +// Each filesystem which needs to be dispatched from FileSystemOperationImpl +// must implement this interface or a synchronous version of interface: +// FileSystemFileUtil. +// +// As far as an instance of this class is owned by a FileSystemBackend +// (which is owned by FileSystemContext), it's guaranteed that this instance's +// alive while FileSystemOperationContext given to each operation is kept +// alive. (Note that this instance might be freed on different thread +// from the thread it is created.) +// +// It is NOT valid to give null callback to this class, and implementors +// can assume that they don't get any null callbacks. +// +class WEBKIT_STORAGE_BROWSER_EXPORT AsyncFileUtil { + public: + typedef base::Callback< + void(base::PlatformFileError result)> StatusCallback; + + // |on_close_callback| will be called after the |file| is closed in the + // child process. |on_close_callback|.is_null() can be true, if no operation + // is needed on closing the file. + typedef base::Callback< + void(base::PlatformFileError result, + base::PassPlatformFile file, + const base::Closure& on_close_callback)> CreateOrOpenCallback; + + typedef base::Callback< + void(base::PlatformFileError result, + bool created)> EnsureFileExistsCallback; + + typedef base::Callback< + void(base::PlatformFileError result, + const base::PlatformFileInfo& file_info)> GetFileInfoCallback; + + typedef std::vector<DirectoryEntry> EntryList; + typedef base::Callback< + void(base::PlatformFileError result, + const EntryList& file_list, + bool has_more)> ReadDirectoryCallback; + + typedef base::Callback< + void(base::PlatformFileError result, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref + )> CreateSnapshotFileCallback; + + AsyncFileUtil() {} + virtual ~AsyncFileUtil() {} + + // Creates or opens a file with the given flags. + // If PLATFORM_FILE_CREATE is set in |file_flags| it always tries to create + // a new file at the given |url| and calls back with + // PLATFORM_FILE_ERROR_FILE_EXISTS if the |url| already exists. + // + // FileSystemOperationImpl::OpenFile calls this. + // This is used only by Pepper/NaCL File API. + // + virtual void CreateOrOpen( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + int file_flags, + const CreateOrOpenCallback& callback) = 0; + + // Ensures that the given |url| exist. This creates a empty new file + // at |url| if the |url| does not exist. + // + // FileSystemOperationImpl::CreateFile calls this. + // + // This reports following error code via |callback|: + // - PLATFORM_FILE_OK and created==true if a file has not existed and + // is created at |url|. + // - PLATFORM_FILE_OK and created==false if the file already exists. + // - Other error code (with created=false) if a file hasn't existed yet + // and there was an error while creating a new file. + // + virtual void EnsureFileExists( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const EnsureFileExistsCallback& callback) = 0; + + // Creates directory at given url. + // + // FileSystemOperationImpl::CreateDirectory calls this. + // + // This reports following error code via |callback|: + // - PLATFORM_FILE_ERROR_NOT_FOUND if the |url|'s parent directory + // does not exist and |recursive| is false. + // - PLATFORM_FILE_ERROR_EXISTS if a directory already exists at |url| + // and |exclusive| is true. + // - PLATFORM_FILE_ERROR_EXISTS if a file already exists at |url| + // (regardless of |exclusive| value). + // - Other error code if it failed to create a directory. + // + virtual void CreateDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback) = 0; + + // Retrieves the information about a file. + // + // FileSystemOperationImpl::GetMetadata calls this. + // + // This reports following error code via |callback|: + // - PLATFORM_FILE_ERROR_NOT_FOUND if the file doesn't exist. + // - Other error code if there was an error while retrieving the file info. + // + virtual void GetFileInfo( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const GetFileInfoCallback& callback) = 0; + + // Reads contents of a directory at |path|. + // + // FileSystemOperationImpl::ReadDirectory calls this. + // + // Note that the |name| field of each entry in |file_list| + // returned by |callback| should have a base file name + // of the entry relative to the directory, but not an absolute path. + // + // (E.g. if ReadDirectory is called for a directory + // 'path/to/dir' and the directory has entries 'a' and 'b', + // the returned |file_list| should include entries whose names + // are 'a' and 'b', but not '/path/to/dir/a' and '/path/to/dir/b'.) + // + // This reports following error code via |callback|: + // - PLATFORM_FILE_ERROR_NOT_FOUND if the target directory doesn't exist. + // - PLATFORM_FILE_ERROR_NOT_A_DIRECTORY if an entry exists at |url| but + // is a file (not a directory). + // + virtual void ReadDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const ReadDirectoryCallback& callback) = 0; + + // Modifies timestamps of a file or directory at |url| with + // |last_access_time| and |last_modified_time|. The function DOES NOT + // create a file unlike 'touch' command on Linux. + // + // FileSystemOperationImpl::TouchFile calls this. + // This is used only by Pepper/NaCL File API. + // + virtual void Touch( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) = 0; + + // Truncates a file at |path| to |length|. If |length| is larger than + // the original file size, the file will be extended, and the extended + // part is filled with null bytes. + // + // FileSystemOperationImpl::Truncate calls this. + // + // This reports following error code via |callback|: + // - PLATFORM_FILE_ERROR_NOT_FOUND if the file doesn't exist. + // + virtual void Truncate( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + int64 length, + const StatusCallback& callback) = 0; + + // Copies a file from |src_url| to |dest_url|. + // This must be called for files that belong to the same filesystem + // (i.e. type() and origin() of the |src_url| and |dest_url| must match). + // + // FileSystemOperationImpl::Copy calls this for same-filesystem copy case. + // + // This reports following error code via |callback|: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_url| + // or the parent directory of |dest_url| does not exist. + // - PLATFORM_FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file. + // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + virtual void CopyFileLocal( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) = 0; + + // Moves a local file from |src_url| to |dest_url|. + // This must be called for files that belong to the same filesystem + // (i.e. type() and origin() of the |src_url| and |dest_url| must match). + // + // FileSystemOperationImpl::Move calls this for same-filesystem move case. + // + // This reports following error code via |callback|: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_url| + // or the parent directory of |dest_url| does not exist. + // - PLATFORM_FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file. + // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + virtual void MoveFileLocal( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) = 0; + + // Copies in a single file from a different filesystem. + // + // FileSystemOperationImpl::Copy or Move calls this for cross-filesystem + // cases. + // + // This reports following error code via |callback|: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_file_path| + // or the parent directory of |dest_url| does not exist. + // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + virtual void CopyInForeignFile( + scoped_ptr<FileSystemOperationContext> context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) = 0; + + // Deletes a single file. + // + // FileSystemOperationImpl::RemoveFile calls this. + // + // This reports following error code via |callback|: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist. + // - PLATFORM_FILE_ERROR_NOT_A_FILE if |url| is not a file. + // + virtual void DeleteFile( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) = 0; + + // Removes a single empty directory. + // + // FileSystemOperationImpl::RemoveDirectory calls this. + // + // This reports following error code via |callback|: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist. + // - PLATFORM_FILE_ERROR_NOT_A_DIRECTORY if |url| is not a directory. + // - PLATFORM_FILE_ERROR_NOT_EMPTY if |url| is not empty. + // + virtual void DeleteDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) = 0; + + // Removes a single file or a single directory with its contents + // (i.e. files/subdirectories under the directory). + // + // FileSystemOperationImpl::Remove calls this. + // On some platforms, such as Chrome OS Drive File System, recursive file + // deletion can be implemented more efficiently than calling DeleteFile() and + // DeleteDirectory() for each files/directories. + // This method is optional, so if not supported, + // PLATFORM_ERROR_INVALID_OPERATION should be returned via |callback|. + // + // This reports following error code via |callback|: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist. + // - PLATFORM_ERROR_INVALID_OPERATION if this operation is not supported. + virtual void DeleteRecursively( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) = 0; + + // Creates a local snapshot file for a given |url| and returns the + // metadata and platform path of the snapshot file via |callback|. + // In regular filesystem cases the implementation may simply return + // the metadata of the file itself (as well as GetMetadata does), + // while in non-regular filesystem case the backend may create a + // temporary snapshot file which holds the file data and return + // the metadata of the temporary file. + // + // In the callback, it returns: + // |file_info| is the metadata of the snapshot file created. + // |platform_path| is the full absolute platform path to the snapshot + // file created. If a file is not backed by a real local file in + // the implementor's FileSystem, the implementor must create a + // local snapshot file and return the path of the created file. + // + // If implementors creates a temporary file for snapshotting and wants + // FileAPI backend to take care of the lifetime of the file (so that + // it won't get deleted while JS layer has any references to the created + // File/Blob object), it should return non-empty |file_ref|. + // Via the |file_ref| implementors can schedule a file deletion + // or arbitrary callbacks when the last reference of File/Blob is dropped. + // + // FileSystemOperationImpl::CreateSnapshotFile calls this. + // + // This reports following error code via |callback|: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist. + // - PLATFORM_FILE_ERROR_NOT_A_FILE if |url| exists but is a directory. + // + // The field values of |file_info| are undefined (implementation + // dependent) in error cases, and the caller should always + // check the return code. + virtual void CreateSnapshotFile( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const CreateSnapshotFileCallback& callback) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(AsyncFileUtil); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_UTIL_H_ diff --git a/chromium/webkit/browser/fileapi/async_file_util_adapter.cc b/chromium/webkit/browser/fileapi/async_file_util_adapter.cc new file mode 100644 index 00000000000..807c2e20227 --- /dev/null +++ b/chromium/webkit/browser/fileapi/async_file_util_adapter.cc @@ -0,0 +1,346 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/async_file_util_adapter.h" + +#include "base/bind.h" +#include "base/sequenced_task_runner.h" +#include "base/task_runner_util.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/common/blob/shareable_file_reference.h" +#include "webkit/common/fileapi/file_system_util.h" + +using base::Bind; +using base::Callback; +using base::Owned; +using base::PlatformFileError; +using base::Unretained; +using webkit_blob::ShareableFileReference; + +namespace fileapi { + +namespace { + +class EnsureFileExistsHelper { + public: + EnsureFileExistsHelper() : error_(base::PLATFORM_FILE_OK), created_(false) {} + + void RunWork(FileSystemFileUtil* file_util, + FileSystemOperationContext* context, + const FileSystemURL& url) { + error_ = file_util->EnsureFileExists(context, url, &created_); + } + + void Reply(const AsyncFileUtil::EnsureFileExistsCallback& callback) { + callback.Run(error_, created_); + } + + private: + base::PlatformFileError error_; + bool created_; + DISALLOW_COPY_AND_ASSIGN(EnsureFileExistsHelper); +}; + +class GetFileInfoHelper { + public: + GetFileInfoHelper() + : error_(base::PLATFORM_FILE_OK) {} + + void GetFileInfo(FileSystemFileUtil* file_util, + FileSystemOperationContext* context, + const FileSystemURL& url) { + error_ = file_util->GetFileInfo(context, url, &file_info_, &platform_path_); + } + + void CreateSnapshotFile(FileSystemFileUtil* file_util, + FileSystemOperationContext* context, + const FileSystemURL& url) { + scoped_file_ = file_util->CreateSnapshotFile( + context, url, &error_, &file_info_, &platform_path_); + } + + void ReplyFileInfo(const AsyncFileUtil::GetFileInfoCallback& callback) { + callback.Run(error_, file_info_); + } + + void ReplySnapshotFile( + const AsyncFileUtil::CreateSnapshotFileCallback& callback) { + callback.Run(error_, file_info_, platform_path_, + ShareableFileReference::GetOrCreate(scoped_file_.Pass())); + } + + private: + base::PlatformFileError error_; + base::PlatformFileInfo file_info_; + base::FilePath platform_path_; + webkit_blob::ScopedFile scoped_file_; + DISALLOW_COPY_AND_ASSIGN(GetFileInfoHelper); +}; + +class ReadDirectoryHelper { + public: + ReadDirectoryHelper() : error_(base::PLATFORM_FILE_OK) {} + + void RunWork(FileSystemFileUtil* file_util, + FileSystemOperationContext* context, + const FileSystemURL& url) { + base::PlatformFileInfo file_info; + base::FilePath platform_path; + PlatformFileError error = file_util->GetFileInfo( + context, url, &file_info, &platform_path); + if (error != base::PLATFORM_FILE_OK) { + error_ = error; + return; + } + if (!file_info.is_directory) { + error_ = base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY; + return; + } + + scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> file_enum( + file_util->CreateFileEnumerator(context, url)); + + base::FilePath current; + while (!(current = file_enum->Next()).empty()) { + DirectoryEntry entry; + entry.is_directory = file_enum->IsDirectory(); + entry.name = VirtualPath::BaseName(current).value(); + entry.size = file_enum->Size(); + entry.last_modified_time = file_enum->LastModifiedTime(); + entries_.push_back(entry); + } + error_ = base::PLATFORM_FILE_OK; + } + + void Reply(const AsyncFileUtil::ReadDirectoryCallback& callback) { + callback.Run(error_, entries_, false /* has_more */); + } + + private: + base::PlatformFileError error_; + std::vector<DirectoryEntry> entries_; + DISALLOW_COPY_AND_ASSIGN(ReadDirectoryHelper); +}; + +void RunCreateOrOpenCallback( + const AsyncFileUtil::CreateOrOpenCallback& callback, + base::PlatformFileError result, + base::PassPlatformFile file, + bool created) { + callback.Run(result, file, base::Closure()); +} + +} // namespace + +AsyncFileUtilAdapter::AsyncFileUtilAdapter( + FileSystemFileUtil* sync_file_util) + : sync_file_util_(sync_file_util) { + DCHECK(sync_file_util_.get()); +} + +AsyncFileUtilAdapter::~AsyncFileUtilAdapter() { +} + +void AsyncFileUtilAdapter::CreateOrOpen( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + int file_flags, + const CreateOrOpenCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::FileUtilProxy::RelayCreateOrOpen( + context_ptr->task_runner(), + Bind(&FileSystemFileUtil::CreateOrOpen, Unretained(sync_file_util_.get()), + context_ptr, url, file_flags), + Bind(&FileSystemFileUtil::Close, Unretained(sync_file_util_.get()), + base::Owned(context_ptr)), + Bind(&RunCreateOrOpenCallback, callback)); + DCHECK(success); +} + +void AsyncFileUtilAdapter::EnsureFileExists( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const EnsureFileExistsCallback& callback) { + EnsureFileExistsHelper* helper = new EnsureFileExistsHelper; + FileSystemOperationContext* context_ptr = context.release(); + const bool success = context_ptr->task_runner()->PostTaskAndReply( + FROM_HERE, + Bind(&EnsureFileExistsHelper::RunWork, Unretained(helper), + sync_file_util_.get(), base::Owned(context_ptr), url), + Bind(&EnsureFileExistsHelper::Reply, Owned(helper), callback)); + DCHECK(success); +} + +void AsyncFileUtilAdapter::CreateDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::CreateDirectory, + Unretained(sync_file_util_.get()), + base::Owned(context_ptr), url, exclusive, recursive), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::GetFileInfo( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const GetFileInfoCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + GetFileInfoHelper* helper = new GetFileInfoHelper; + const bool success = context_ptr->task_runner()->PostTaskAndReply( + FROM_HERE, + Bind(&GetFileInfoHelper::GetFileInfo, Unretained(helper), + sync_file_util_.get(), base::Owned(context_ptr), url), + Bind(&GetFileInfoHelper::ReplyFileInfo, Owned(helper), callback)); + DCHECK(success); +} + +void AsyncFileUtilAdapter::ReadDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const ReadDirectoryCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + ReadDirectoryHelper* helper = new ReadDirectoryHelper; + const bool success = context_ptr->task_runner()->PostTaskAndReply( + FROM_HERE, + Bind(&ReadDirectoryHelper::RunWork, Unretained(helper), + sync_file_util_.get(), base::Owned(context_ptr), url), + Bind(&ReadDirectoryHelper::Reply, Owned(helper), callback)); + DCHECK(success); +} + +void AsyncFileUtilAdapter::Touch( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::Touch, Unretained(sync_file_util_.get()), + base::Owned(context_ptr), url, + last_access_time, last_modified_time), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::Truncate( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + int64 length, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::Truncate, Unretained(sync_file_util_.get()), + base::Owned(context_ptr), url, length), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::CopyFileLocal( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::CopyOrMoveFile, + Unretained(sync_file_util_.get()), + base::Owned(context_ptr), src_url, dest_url, true /* copy */), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::MoveFileLocal( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::CopyOrMoveFile, + Unretained(sync_file_util_.get()), + base::Owned(context_ptr), src_url, dest_url, false /* copy */), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::CopyInForeignFile( + scoped_ptr<FileSystemOperationContext> context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::CopyInForeignFile, + Unretained(sync_file_util_.get()), + base::Owned(context_ptr), src_file_path, dest_url), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::DeleteFile( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::DeleteFile, + Unretained(sync_file_util_.get()), + base::Owned(context_ptr), url), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::DeleteDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + const bool success = base::PostTaskAndReplyWithResult( + context_ptr->task_runner(), FROM_HERE, + Bind(&FileSystemFileUtil::DeleteDirectory, + Unretained(sync_file_util_.get()), + base::Owned(context_ptr), url), + callback); + DCHECK(success); +} + +void AsyncFileUtilAdapter::DeleteRecursively( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) { + callback.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION); +} + +void AsyncFileUtilAdapter::CreateSnapshotFile( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const CreateSnapshotFileCallback& callback) { + FileSystemOperationContext* context_ptr = context.release(); + GetFileInfoHelper* helper = new GetFileInfoHelper; + const bool success = context_ptr->task_runner()->PostTaskAndReply( + FROM_HERE, + Bind(&GetFileInfoHelper::CreateSnapshotFile, Unretained(helper), + sync_file_util_.get(), base::Owned(context_ptr), url), + Bind(&GetFileInfoHelper::ReplySnapshotFile, Owned(helper), callback)); + DCHECK(success); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/async_file_util_adapter.h b/chromium/webkit/browser/fileapi/async_file_util_adapter.h new file mode 100644 index 00000000000..9da3db9eac7 --- /dev/null +++ b/chromium/webkit/browser/fileapi/async_file_util_adapter.h @@ -0,0 +1,115 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_UTIL_ADAPTER_H_ +#define WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_UTIL_ADAPTER_H_ + +#include "base/memory/scoped_ptr.h" +#include "webkit/browser/fileapi/async_file_util.h" + +namespace fileapi { + +class FileSystemFileUtil; + +// An adapter class for FileSystemFileUtil classes to provide asynchronous +// interface. +// +// A filesystem can do either: +// - implement a synchronous version of FileUtil by extending +// FileSystemFileUtil and atach it to this adapter, or +// - directly implement AsyncFileUtil. +// +// This instance (as thus this->sync_file_util_) is guaranteed to be alive +// as far as FileSystemOperationContext given to each operation is kept alive. +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE AsyncFileUtilAdapter + : public AsyncFileUtil { + public: + // Creates a new AsyncFileUtil for |sync_file_util|. This takes the + // ownership of |sync_file_util|. (This doesn't take scoped_ptr<> just + // to save extra make_scoped_ptr; in all use cases a new fresh FileUtil is + // created only for this adapter.) + explicit AsyncFileUtilAdapter(FileSystemFileUtil* sync_file_util); + + virtual ~AsyncFileUtilAdapter(); + + FileSystemFileUtil* sync_file_util() { + return sync_file_util_.get(); + } + + // AsyncFileUtil overrides. + virtual void CreateOrOpen( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + int file_flags, + const CreateOrOpenCallback& callback) OVERRIDE; + virtual void EnsureFileExists( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const EnsureFileExistsCallback& callback) OVERRIDE; + virtual void CreateDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback) OVERRIDE; + virtual void GetFileInfo( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const GetFileInfoCallback& callback) OVERRIDE; + virtual void ReadDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const ReadDirectoryCallback& callback) OVERRIDE; + virtual void Touch( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) OVERRIDE; + virtual void Truncate( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + int64 length, + const StatusCallback& callback) OVERRIDE; + virtual void CopyFileLocal( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) OVERRIDE; + virtual void MoveFileLocal( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) OVERRIDE; + virtual void CopyInForeignFile( + scoped_ptr<FileSystemOperationContext> context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) OVERRIDE; + virtual void DeleteFile( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void DeleteDirectory( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void DeleteRecursively( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void CreateSnapshotFile( + scoped_ptr<FileSystemOperationContext> context, + const FileSystemURL& url, + const CreateSnapshotFileCallback& callback) OVERRIDE; + + private: + scoped_ptr<FileSystemFileUtil> sync_file_util_; + + DISALLOW_COPY_AND_ASSIGN(AsyncFileUtilAdapter); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_UTIL_ADAPTER_H_ diff --git a/chromium/webkit/browser/fileapi/copy_or_move_file_validator.h b/chromium/webkit/browser/fileapi/copy_or_move_file_validator.h new file mode 100644 index 00000000000..2bd4df17b7a --- /dev/null +++ b/chromium/webkit/browser/fileapi/copy_or_move_file_validator.h @@ -0,0 +1,54 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_COPY_OR_MOVE_FILE_VALIDATOR_H_ +#define WEBKIT_BROWSER_FILEAPI_COPY_OR_MOVE_FILE_VALIDATOR_H_ + +#include "base/callback.h" +#include "base/platform_file.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace base { +class FilePath; +} + +namespace fileapi { + +class FileSystemURL; + +class WEBKIT_STORAGE_BROWSER_EXPORT CopyOrMoveFileValidator { + public: + // Callback that is invoked when validation completes. A result of + // base::PLATFORM_FILE_OK means the file validated. + typedef base::Callback<void(base::PlatformFileError result)> ResultCallback; + + virtual ~CopyOrMoveFileValidator() {} + + // Called on a source file before copying or moving to the final + // destination. + virtual void StartPreWriteValidation( + const ResultCallback& result_callback) = 0; + + // Called on a destination file after copying or moving to the final + // destination. Suitable for running Anti-Virus checks. + virtual void StartPostWriteValidation( + const base::FilePath& dest_platform_path, + const ResultCallback& result_callback) = 0; +}; + +class CopyOrMoveFileValidatorFactory { + public: + virtual ~CopyOrMoveFileValidatorFactory() {} + + // This method must always return a non-NULL validator. |src_url| is needed + // in addition to |platform_path| because in the obfuscated file system + // case, |platform_path| will be an obfuscated filename and extension. + virtual CopyOrMoveFileValidator* CreateCopyOrMoveFileValidator( + const FileSystemURL& src_url, + const base::FilePath& platform_path) = 0; +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_COPY_OR_MOVE_FILE_VALIDATOR_H_ diff --git a/chromium/webkit/browser/fileapi/copy_or_move_file_validator_unittest.cc b/chromium/webkit/browser/fileapi/copy_or_move_file_validator_unittest.cc new file mode 100644 index 00000000000..caecccfddfb --- /dev/null +++ b/chromium/webkit/browser/fileapi/copy_or_move_file_validator_unittest.cc @@ -0,0 +1,326 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/copy_or_move_file_validator.h" +#include "webkit/browser/fileapi/external_mount_points.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/isolated_context.h" +#include "webkit/browser/fileapi/mock_file_system_context.h" +#include "webkit/browser/fileapi/test_file_system_backend.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" +#include "webkit/common/blob/shareable_file_reference.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +namespace { + +const FileSystemType kNoValidatorType = kFileSystemTypeTemporary; +const FileSystemType kWithValidatorType = kFileSystemTypeTest; + +void ExpectOk(const GURL& origin_url, + const std::string& name, + base::PlatformFileError error) { + ASSERT_EQ(base::PLATFORM_FILE_OK, error); +} + +class CopyOrMoveFileValidatorTestHelper { + public: + CopyOrMoveFileValidatorTestHelper( + const GURL& origin, + FileSystemType src_type, + FileSystemType dest_type) + : origin_(origin), + src_type_(src_type), + dest_type_(dest_type) {} + + ~CopyOrMoveFileValidatorTestHelper() { + file_system_context_ = NULL; + base::MessageLoop::current()->RunUntilIdle(); + } + + void SetUp() { + ASSERT_TRUE(base_.CreateUniqueTempDir()); + base::FilePath base_dir = base_.path(); + + file_system_context_ = CreateFileSystemContextForTesting(NULL, base_dir); + + // Set up TestFileSystemBackend to require CopyOrMoveFileValidator. + FileSystemBackend* test_file_system_backend = + file_system_context_->GetFileSystemBackend(kWithValidatorType); + static_cast<TestFileSystemBackend*>(test_file_system_backend)-> + set_require_copy_or_move_validator(true); + + // Sets up source. + FileSystemBackend* src_file_system_backend = + file_system_context_->GetFileSystemBackend(src_type_); + src_file_system_backend->OpenFileSystem( + origin_, src_type_, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&ExpectOk)); + base::MessageLoop::current()->RunUntilIdle(); + ASSERT_EQ(base::PLATFORM_FILE_OK, CreateDirectory(SourceURL(""))); + + // Sets up dest. + DCHECK_EQ(kWithValidatorType, dest_type_); + ASSERT_EQ(base::PLATFORM_FILE_OK, CreateDirectory(DestURL(""))); + + copy_src_ = SourceURL("copy_src.jpg"); + move_src_ = SourceURL("move_src.jpg"); + copy_dest_ = DestURL("copy_dest.jpg"); + move_dest_ = DestURL("move_dest.jpg"); + + ASSERT_EQ(base::PLATFORM_FILE_OK, CreateFile(copy_src_, 10)); + ASSERT_EQ(base::PLATFORM_FILE_OK, CreateFile(move_src_, 10)); + + ASSERT_TRUE(FileExists(copy_src_, 10)); + ASSERT_TRUE(FileExists(move_src_, 10)); + ASSERT_FALSE(FileExists(copy_dest_, 10)); + ASSERT_FALSE(FileExists(move_dest_, 10)); + } + + void SetMediaCopyOrMoveFileValidatorFactory( + scoped_ptr<CopyOrMoveFileValidatorFactory> factory) { + TestFileSystemBackend* backend = static_cast<TestFileSystemBackend*>( + file_system_context_->GetFileSystemBackend(kWithValidatorType)); + backend->InitializeCopyOrMoveFileValidatorFactory(factory.Pass()); + } + + void CopyTest(base::PlatformFileError expected) { + ASSERT_TRUE(FileExists(copy_src_, 10)); + ASSERT_FALSE(FileExists(copy_dest_, 10)); + + EXPECT_EQ(expected, + AsyncFileTestHelper::Copy( + file_system_context_.get(), copy_src_, copy_dest_)); + + EXPECT_TRUE(FileExists(copy_src_, 10)); + if (expected == base::PLATFORM_FILE_OK) + EXPECT_TRUE(FileExists(copy_dest_, 10)); + else + EXPECT_FALSE(FileExists(copy_dest_, 10)); + }; + + void MoveTest(base::PlatformFileError expected) { + ASSERT_TRUE(FileExists(move_src_, 10)); + ASSERT_FALSE(FileExists(move_dest_, 10)); + + EXPECT_EQ(expected, + AsyncFileTestHelper::Move( + file_system_context_.get(), move_src_, move_dest_)); + + if (expected == base::PLATFORM_FILE_OK) { + EXPECT_FALSE(FileExists(move_src_, 10)); + EXPECT_TRUE(FileExists(move_dest_, 10)); + } else { + EXPECT_TRUE(FileExists(move_src_, 10)); + EXPECT_FALSE(FileExists(move_dest_, 10)); + } + }; + + private: + FileSystemURL SourceURL(const std::string& path) { + return file_system_context_->CreateCrackedFileSystemURL( + origin_, src_type_, + base::FilePath().AppendASCII("src").AppendASCII(path)); + } + + FileSystemURL DestURL(const std::string& path) { + return file_system_context_->CreateCrackedFileSystemURL( + origin_, dest_type_, + base::FilePath().AppendASCII("dest").AppendASCII(path)); + } + + base::PlatformFileError CreateFile(const FileSystemURL& url, size_t size) { + base::PlatformFileError result = + AsyncFileTestHelper::CreateFile(file_system_context_.get(), url); + if (result != base::PLATFORM_FILE_OK) + return result; + return AsyncFileTestHelper::TruncateFile( + file_system_context_.get(), url, size); + } + + base::PlatformFileError CreateDirectory(const FileSystemURL& url) { + return AsyncFileTestHelper::CreateDirectory(file_system_context_.get(), + url); + } + + bool FileExists(const FileSystemURL& url, int64 expected_size) { + return AsyncFileTestHelper::FileExists( + file_system_context_.get(), url, expected_size); + } + + base::ScopedTempDir base_; + + const GURL origin_; + + const FileSystemType src_type_; + const FileSystemType dest_type_; + std::string src_fsid_; + std::string dest_fsid_; + + base::MessageLoop message_loop_; + scoped_refptr<FileSystemContext> file_system_context_; + + FileSystemURL copy_src_; + FileSystemURL copy_dest_; + FileSystemURL move_src_; + FileSystemURL move_dest_; + + DISALLOW_COPY_AND_ASSIGN(CopyOrMoveFileValidatorTestHelper); +}; + +// For TestCopyOrMoveFileValidatorFactory +enum Validity { + VALID, + PRE_WRITE_INVALID, + POST_WRITE_INVALID +}; + +class TestCopyOrMoveFileValidatorFactory + : public CopyOrMoveFileValidatorFactory { + public: + // A factory that creates validators that accept everything or nothing. + // TODO(gbillock): switch args to enum or something + explicit TestCopyOrMoveFileValidatorFactory(Validity validity) + : validity_(validity) {} + virtual ~TestCopyOrMoveFileValidatorFactory() {} + + virtual CopyOrMoveFileValidator* CreateCopyOrMoveFileValidator( + const FileSystemURL& /*src_url*/, + const base::FilePath& /*platform_path*/) OVERRIDE { + return new TestCopyOrMoveFileValidator(validity_); + } + + private: + class TestCopyOrMoveFileValidator : public CopyOrMoveFileValidator { + public: + explicit TestCopyOrMoveFileValidator(Validity validity) + : result_(validity == VALID || validity == POST_WRITE_INVALID + ? base::PLATFORM_FILE_OK + : base::PLATFORM_FILE_ERROR_SECURITY), + write_result_(validity == VALID || validity == PRE_WRITE_INVALID + ? base::PLATFORM_FILE_OK + : base::PLATFORM_FILE_ERROR_SECURITY) { + } + virtual ~TestCopyOrMoveFileValidator() {} + + virtual void StartPreWriteValidation( + const ResultCallback& result_callback) OVERRIDE { + // Post the result since a real validator must do work asynchronously. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(result_callback, result_)); + } + + virtual void StartPostWriteValidation( + const base::FilePath& dest_platform_path, + const ResultCallback& result_callback) OVERRIDE { + // Post the result since a real validator must do work asynchronously. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(result_callback, write_result_)); + } + + private: + base::PlatformFileError result_; + base::PlatformFileError write_result_; + + DISALLOW_COPY_AND_ASSIGN(TestCopyOrMoveFileValidator); + }; + + Validity validity_; + + DISALLOW_COPY_AND_ASSIGN(TestCopyOrMoveFileValidatorFactory); +}; + +} // namespace + +TEST(CopyOrMoveFileValidatorTest, NoValidatorWithinSameFSType) { + // Within a file system type, validation is not expected, so it should + // work for kWithValidatorType without a validator set. + CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"), + kWithValidatorType, + kWithValidatorType); + helper.SetUp(); + helper.CopyTest(base::PLATFORM_FILE_OK); + helper.MoveTest(base::PLATFORM_FILE_OK); +} + +TEST(CopyOrMoveFileValidatorTest, MissingValidator) { + // Copying or moving into a kWithValidatorType requires a file + // validator. An error is expected if copy is attempted without a validator. + CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"), + kNoValidatorType, + kWithValidatorType); + helper.SetUp(); + helper.CopyTest(base::PLATFORM_FILE_ERROR_SECURITY); + helper.MoveTest(base::PLATFORM_FILE_ERROR_SECURITY); +} + +TEST(CopyOrMoveFileValidatorTest, AcceptAll) { + CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"), + kNoValidatorType, + kWithValidatorType); + helper.SetUp(); + scoped_ptr<CopyOrMoveFileValidatorFactory> factory( + new TestCopyOrMoveFileValidatorFactory(VALID)); + helper.SetMediaCopyOrMoveFileValidatorFactory(factory.Pass()); + + helper.CopyTest(base::PLATFORM_FILE_OK); + helper.MoveTest(base::PLATFORM_FILE_OK); +} + +TEST(CopyOrMoveFileValidatorTest, AcceptNone) { + CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"), + kNoValidatorType, + kWithValidatorType); + helper.SetUp(); + scoped_ptr<CopyOrMoveFileValidatorFactory> factory( + new TestCopyOrMoveFileValidatorFactory(PRE_WRITE_INVALID)); + helper.SetMediaCopyOrMoveFileValidatorFactory(factory.Pass()); + + helper.CopyTest(base::PLATFORM_FILE_ERROR_SECURITY); + helper.MoveTest(base::PLATFORM_FILE_ERROR_SECURITY); +} + +TEST(CopyOrMoveFileValidatorTest, OverrideValidator) { + // Once set, you can not override the validator. + CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"), + kNoValidatorType, + kWithValidatorType); + helper.SetUp(); + scoped_ptr<CopyOrMoveFileValidatorFactory> reject_factory( + new TestCopyOrMoveFileValidatorFactory(PRE_WRITE_INVALID)); + helper.SetMediaCopyOrMoveFileValidatorFactory(reject_factory.Pass()); + + scoped_ptr<CopyOrMoveFileValidatorFactory> accept_factory( + new TestCopyOrMoveFileValidatorFactory(VALID)); + helper.SetMediaCopyOrMoveFileValidatorFactory(accept_factory.Pass()); + + helper.CopyTest(base::PLATFORM_FILE_ERROR_SECURITY); + helper.MoveTest(base::PLATFORM_FILE_ERROR_SECURITY); +} + +TEST(CopyOrMoveFileValidatorTest, RejectPostWrite) { + CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"), + kNoValidatorType, + kWithValidatorType); + helper.SetUp(); + scoped_ptr<CopyOrMoveFileValidatorFactory> factory( + new TestCopyOrMoveFileValidatorFactory(POST_WRITE_INVALID)); + helper.SetMediaCopyOrMoveFileValidatorFactory(factory.Pass()); + + helper.CopyTest(base::PLATFORM_FILE_ERROR_SECURITY); + helper.MoveTest(base::PLATFORM_FILE_ERROR_SECURITY); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate.cc b/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate.cc new file mode 100644 index 00000000000..da1ef97b984 --- /dev/null +++ b/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate.cc @@ -0,0 +1,325 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/copy_or_move_operation_delegate.h" + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "webkit/browser/fileapi/copy_or_move_file_validator.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/recursive_operation_delegate.h" +#include "webkit/common/blob/shareable_file_reference.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +CopyOrMoveOperationDelegate::CopyOrMoveOperationDelegate( + FileSystemContext* file_system_context, + const FileSystemURL& src_root, + const FileSystemURL& dest_root, + OperationType operation_type, + const StatusCallback& callback) + : RecursiveOperationDelegate(file_system_context), + src_root_(src_root), + dest_root_(dest_root), + operation_type_(operation_type), + callback_(callback), + weak_factory_(this) { + same_file_system_ = src_root_.IsInSameFileSystem(dest_root_); +} + +CopyOrMoveOperationDelegate::~CopyOrMoveOperationDelegate() { +} + +void CopyOrMoveOperationDelegate::Run() { + // Not supported; this should never be called. + NOTREACHED(); +} + +void CopyOrMoveOperationDelegate::RunRecursively() { + // Perform light-weight checks first. + + // It is an error to try to copy/move an entry into its child. + if (same_file_system_ && src_root_.path().IsParent(dest_root_.path())) { + callback_.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION); + return; + } + + // It is an error to copy/move an entry into the same path. + if (same_file_system_ && src_root_.path() == dest_root_.path()) { + callback_.Run(base::PLATFORM_FILE_ERROR_EXISTS); + return; + } + + // First try to copy/move it as a file. + CopyOrMoveFile(URLPair(src_root_, dest_root_), + base::Bind(&CopyOrMoveOperationDelegate::DidTryCopyOrMoveFile, + weak_factory_.GetWeakPtr())); +} + +void CopyOrMoveOperationDelegate::ProcessFile(const FileSystemURL& src_url, + const StatusCallback& callback) { + CopyOrMoveFile(URLPair(src_url, CreateDestURL(src_url)), callback); +} + +void CopyOrMoveOperationDelegate::ProcessDirectory(const FileSystemURL& src_url, + const StatusCallback& callback) { + FileSystemURL dest_url = CreateDestURL(src_url); + + // If operation_type == Move we may need to record directories and + // restore directory timestamps in the end, though it may have + // negative performance impact. + // See http://crbug.com/171284 for more details. + operation_runner()->CreateDirectory( + dest_url, false /* exclusive */, false /* recursive */, callback); +} + +void CopyOrMoveOperationDelegate::DidTryCopyOrMoveFile( + base::PlatformFileError error) { + if (error == base::PLATFORM_FILE_OK || + error != base::PLATFORM_FILE_ERROR_NOT_A_FILE) { + callback_.Run(error); + return; + } + + // The src_root_ looks to be a directory. + // Try removing the dest_root_ to see if it exists and/or it is an + // empty directory. + operation_runner()->RemoveDirectory( + dest_root_, + base::Bind(&CopyOrMoveOperationDelegate::DidTryRemoveDestRoot, + weak_factory_.GetWeakPtr())); +} + +void CopyOrMoveOperationDelegate::DidTryRemoveDestRoot( + base::PlatformFileError error) { + if (error == base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY) { + callback_.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION); + return; + } + if (error != base::PLATFORM_FILE_OK && + error != base::PLATFORM_FILE_ERROR_NOT_FOUND) { + callback_.Run(error); + return; + } + + // Start to process the source directory recursively. + // TODO(kinuko): This could be too expensive for same_file_system_==true + // and operation==MOVE case, probably we can just rename the root directory. + // http://crbug.com/172187 + StartRecursiveOperation( + src_root_, + base::Bind(&CopyOrMoveOperationDelegate::DidFinishRecursiveCopyDir, + weak_factory_.GetWeakPtr(), src_root_, callback_)); +} + +void CopyOrMoveOperationDelegate::CopyOrMoveFile( + const URLPair& url_pair, + const StatusCallback& callback) { + // Same filesystem case. + if (same_file_system_) { + if (operation_type_ == OPERATION_MOVE) { + operation_runner()->MoveFileLocal(url_pair.src, url_pair.dest, callback); + } else { + operation_runner()->CopyFileLocal(url_pair.src, url_pair.dest, callback); + } + return; + } + + // Cross filesystem case. + // Perform CreateSnapshotFile, CopyInForeignFile and then calls + // copy_callback which removes the source file if operation_type == MOVE. + StatusCallback copy_callback = + base::Bind(&CopyOrMoveOperationDelegate::DidFinishCopy, + weak_factory_.GetWeakPtr(), url_pair, callback); + operation_runner()->CreateSnapshotFile( + url_pair.src, + base::Bind(&CopyOrMoveOperationDelegate::DidCreateSnapshot, + weak_factory_.GetWeakPtr(), url_pair, copy_callback)); +} + +void CopyOrMoveOperationDelegate::DidCreateSnapshot( + const URLPair& url_pair, + const StatusCallback& callback, + base::PlatformFileError error, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) { + if (error != base::PLATFORM_FILE_OK) { + callback.Run(error); + return; + } + current_file_ref_ = file_ref; + + // For now we assume CreateSnapshotFile always return a valid local file path. + // TODO(kinuko): Otherwise create a FileStreamReader to perform a copy/move. + DCHECK(!platform_path.empty()); + + CopyOrMoveFileValidatorFactory* factory = + file_system_context()->GetCopyOrMoveFileValidatorFactory( + dest_root_.type(), &error); + if (error != base::PLATFORM_FILE_OK) { + callback.Run(error); + return; + } + if (!factory) { + DidValidateFile(url_pair.dest, callback, file_info, platform_path, error); + return; + } + + validator_.reset( + factory->CreateCopyOrMoveFileValidator(url_pair.src, platform_path)); + validator_->StartPreWriteValidation( + base::Bind(&CopyOrMoveOperationDelegate::DidValidateFile, + weak_factory_.GetWeakPtr(), + url_pair.dest, callback, file_info, platform_path)); +} + +void CopyOrMoveOperationDelegate::DidValidateFile( + const FileSystemURL& dest, + const StatusCallback& callback, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + base::PlatformFileError error) { + if (error != base::PLATFORM_FILE_OK) { + callback.Run(error); + return; + } + + operation_runner()->CopyInForeignFile(platform_path, dest, callback); +} + +void CopyOrMoveOperationDelegate::DidFinishRecursiveCopyDir( + const FileSystemURL& src, + const StatusCallback& callback, + base::PlatformFileError error) { + if (error != base::PLATFORM_FILE_OK || + operation_type_ == OPERATION_COPY) { + callback.Run(error); + return; + } + + DCHECK_EQ(OPERATION_MOVE, operation_type_); + + // Remove the source for finalizing move operation. + operation_runner()->Remove( + src, true /* recursive */, + base::Bind(&CopyOrMoveOperationDelegate::DidRemoveSourceForMove, + weak_factory_.GetWeakPtr(), callback)); +} + +void CopyOrMoveOperationDelegate::DidFinishCopy( + const URLPair& url_pair, + const StatusCallback& callback, + base::PlatformFileError error) { + if (error != base::PLATFORM_FILE_OK) { + callback.Run(error); + return; + } + + // |validator_| is NULL in the same-filesystem case or when the destination + // filesystem does not do validation. + if (!validator_.get()) { + scoped_refptr<webkit_blob::ShareableFileReference> file_ref; + DidPostWriteValidation(url_pair, callback, file_ref, + base::PLATFORM_FILE_OK); + return; + } + + DCHECK(!same_file_system_); + operation_runner()->CreateSnapshotFile( + url_pair.dest, + base::Bind(&CopyOrMoveOperationDelegate::DoPostWriteValidation, + weak_factory_.GetWeakPtr(), url_pair, callback)); +} + +void CopyOrMoveOperationDelegate::DoPostWriteValidation( + const URLPair& url_pair, + const StatusCallback& callback, + base::PlatformFileError error, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) { + if (error != base::PLATFORM_FILE_OK) { + operation_runner()->Remove( + url_pair.dest, true, + base::Bind(&CopyOrMoveOperationDelegate::DidRemoveDestForError, + weak_factory_.GetWeakPtr(), error, callback)); + return; + } + + DCHECK(validator_.get()); + // Note: file_ref passed here to keep the file alive until after + // the StartPostWriteValidation operation finishes. + validator_->StartPostWriteValidation( + platform_path, + base::Bind(&CopyOrMoveOperationDelegate::DidPostWriteValidation, + weak_factory_.GetWeakPtr(), url_pair, callback, file_ref)); +} + +// |file_ref| is unused; it is passed here to make sure the reference is +// alive until after post-write validation is complete. +void CopyOrMoveOperationDelegate::DidPostWriteValidation( + const URLPair& url_pair, + const StatusCallback& callback, + const scoped_refptr<webkit_blob::ShareableFileReference>& /*file_ref*/, + base::PlatformFileError error) { + if (error != base::PLATFORM_FILE_OK) { + operation_runner()->Remove( + url_pair.dest, true, + base::Bind(&CopyOrMoveOperationDelegate::DidRemoveDestForError, + weak_factory_.GetWeakPtr(), error, callback)); + return; + } + + if (operation_type_ == OPERATION_COPY) { + callback.Run(error); + return; + } + + DCHECK_EQ(OPERATION_MOVE, operation_type_); + + // Remove the source for finalizing move operation. + operation_runner()->Remove( + url_pair.src, true /* recursive */, + base::Bind(&CopyOrMoveOperationDelegate::DidRemoveSourceForMove, + weak_factory_.GetWeakPtr(), callback)); +} + +void CopyOrMoveOperationDelegate::DidRemoveSourceForMove( + const StatusCallback& callback, + base::PlatformFileError error) { + if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) + error = base::PLATFORM_FILE_OK; + callback.Run(error); +} + +FileSystemURL CopyOrMoveOperationDelegate::CreateDestURL( + const FileSystemURL& src_url) const { + DCHECK_EQ(src_root_.type(), src_url.type()); + DCHECK_EQ(src_root_.origin(), src_url.origin()); + + base::FilePath relative = dest_root_.virtual_path(); + src_root_.virtual_path().AppendRelativePath(src_url.virtual_path(), + &relative); + return file_system_context()->CreateCrackedFileSystemURL( + dest_root_.origin(), + dest_root_.mount_type(), + relative); +} + +void CopyOrMoveOperationDelegate::DidRemoveDestForError( + base::PlatformFileError prior_error, + const StatusCallback& callback, + base::PlatformFileError error) { + if (error != base::PLATFORM_FILE_OK) { + VLOG(1) << "Error removing destination file after validation error: " + << error; + } + callback.Run(prior_error); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate.h b/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate.h new file mode 100644 index 00000000000..ceceb971367 --- /dev/null +++ b/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate.h @@ -0,0 +1,122 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_COPY_OR_MOVE_OPERATION_DELEGATE_H_ +#define WEBKIT_BROWSER_FILEAPI_COPY_OR_MOVE_OPERATION_DELEGATE_H_ + +#include <stack> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "webkit/browser/fileapi/recursive_operation_delegate.h" + +namespace webkit_blob { +class ShareableFileReference; +} + +namespace fileapi { + +class CopyOrMoveFileValidator; + +// A delegate class for recursive copy or move operations. +class CopyOrMoveOperationDelegate + : public RecursiveOperationDelegate { + public: + enum OperationType { + OPERATION_COPY, + OPERATION_MOVE + }; + + CopyOrMoveOperationDelegate( + FileSystemContext* file_system_context, + const FileSystemURL& src_root, + const FileSystemURL& dest_root, + OperationType operation_type, + const StatusCallback& callback); + virtual ~CopyOrMoveOperationDelegate(); + + // RecursiveOperationDelegate overrides: + virtual void Run() OVERRIDE; + virtual void RunRecursively() OVERRIDE; + virtual void ProcessFile(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void ProcessDirectory(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + + private: + struct URLPair { + URLPair(const FileSystemURL& src, const FileSystemURL& dest) + : src(src), + dest(dest) { + } + FileSystemURL src; + FileSystemURL dest; + }; + + void DidTryCopyOrMoveFile(base::PlatformFileError error); + void DidTryRemoveDestRoot(base::PlatformFileError error); + void CopyOrMoveFile( + const URLPair& url_pair, + const StatusCallback& callback); + void DidCreateSnapshot( + const URLPair& url_pair, + const StatusCallback& callback, + base::PlatformFileError error, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref); + void DidValidateFile( + const FileSystemURL& dest, + const StatusCallback& callback, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + base::PlatformFileError error); + void DidFinishRecursiveCopyDir( + const FileSystemURL& src, + const StatusCallback& callback, + base::PlatformFileError error); + void DidFinishCopy( + const URLPair& url_pair, + const StatusCallback& callback, + base::PlatformFileError error); + void DoPostWriteValidation( + const URLPair& url_pair, + const StatusCallback& callback, + base::PlatformFileError error, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref); + void DidPostWriteValidation( + const URLPair& url_pair, + const StatusCallback& callback, + const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref, + base::PlatformFileError error); + void DidRemoveSourceForMove( + const StatusCallback& callback, + base::PlatformFileError error); + void DidRemoveDestForError( + base::PlatformFileError prior_error, + const StatusCallback& callback, + base::PlatformFileError error); + + FileSystemURL CreateDestURL(const FileSystemURL& src_url) const; + + FileSystemURL src_root_; + FileSystemURL dest_root_; + bool same_file_system_; + OperationType operation_type_; + StatusCallback callback_; + + scoped_refptr<webkit_blob::ShareableFileReference> current_file_ref_; + + scoped_ptr<CopyOrMoveFileValidator> validator_; + + base::WeakPtrFactory<CopyOrMoveOperationDelegate> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(CopyOrMoveOperationDelegate); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_COPY_OR_MOVE_OPERATION_DELEGATE_H_ diff --git a/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate_unittest.cc b/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate_unittest.cc new file mode 100644 index 00000000000..183becd7dbf --- /dev/null +++ b/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate_unittest.cc @@ -0,0 +1,562 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <map> +#include <queue> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/stl_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/copy_or_move_file_validator.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/mock_file_system_context.h" +#include "webkit/browser/fileapi/test_file_set.h" +#include "webkit/browser/fileapi/test_file_system_backend.h" +#include "webkit/browser/quota/mock_quota_manager.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +typedef FileSystemOperation::FileEntryList FileEntryList; + +namespace { + +void ExpectOk(const GURL& origin_url, + const std::string& name, + base::PlatformFileError error) { + ASSERT_EQ(base::PLATFORM_FILE_OK, error); +} + +class TestValidatorFactory : public CopyOrMoveFileValidatorFactory { + public: + // A factory that creates validators that accept everything or nothing. + TestValidatorFactory() {} + virtual ~TestValidatorFactory() {} + + virtual CopyOrMoveFileValidator* CreateCopyOrMoveFileValidator( + const FileSystemURL& /*src_url*/, + const base::FilePath& /*platform_path*/) OVERRIDE { + // Move arg management to TestValidator? + return new TestValidator(true, true, std::string("2")); + } + + private: + class TestValidator : public CopyOrMoveFileValidator { + public: + explicit TestValidator(bool pre_copy_valid, + bool post_copy_valid, + const std::string& reject_string) + : result_(pre_copy_valid ? base::PLATFORM_FILE_OK + : base::PLATFORM_FILE_ERROR_SECURITY), + write_result_(post_copy_valid ? base::PLATFORM_FILE_OK + : base::PLATFORM_FILE_ERROR_SECURITY), + reject_string_(reject_string) { + } + virtual ~TestValidator() {} + + virtual void StartPreWriteValidation( + const ResultCallback& result_callback) OVERRIDE { + // Post the result since a real validator must do work asynchronously. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(result_callback, result_)); + } + + virtual void StartPostWriteValidation( + const base::FilePath& dest_platform_path, + const ResultCallback& result_callback) OVERRIDE { + base::PlatformFileError result = write_result_; + std::string unsafe = dest_platform_path.BaseName().AsUTF8Unsafe(); + if (unsafe.find(reject_string_) != std::string::npos) { + result = base::PLATFORM_FILE_ERROR_SECURITY; + } + // Post the result since a real validator must do work asynchronously. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(result_callback, result)); + } + + private: + base::PlatformFileError result_; + base::PlatformFileError write_result_; + std::string reject_string_; + + DISALLOW_COPY_AND_ASSIGN(TestValidator); + }; +}; + +} // namespace + +class CopyOrMoveOperationTestHelper { + public: + CopyOrMoveOperationTestHelper( + const GURL& origin, + FileSystemType src_type, + FileSystemType dest_type) + : origin_(origin), + src_type_(src_type), + dest_type_(dest_type) {} + + ~CopyOrMoveOperationTestHelper() { + file_system_context_ = NULL; + quota_manager_proxy_->SimulateQuotaManagerDestroyed(); + quota_manager_ = NULL; + quota_manager_proxy_ = NULL; + base::MessageLoop::current()->RunUntilIdle(); + } + + void SetUp() { + SetUp(true, true); + } + + void SetUpNoValidator() { + SetUp(true, false); + } + + void SetUp(bool require_copy_or_move_validator, + bool init_copy_or_move_validator) { + ASSERT_TRUE(base_.CreateUniqueTempDir()); + base::FilePath base_dir = base_.path(); + quota_manager_ = + new quota::MockQuotaManager(false /* is_incognito */, + base_dir, + base::MessageLoopProxy::current().get(), + base::MessageLoopProxy::current().get(), + NULL /* special storage policy */); + quota_manager_proxy_ = new quota::MockQuotaManagerProxy( + quota_manager_.get(), base::MessageLoopProxy::current().get()); + file_system_context_ = + CreateFileSystemContextForTesting(quota_manager_proxy_.get(), base_dir); + + // Prepare the origin's root directory. + FileSystemBackend* backend = + file_system_context_->GetFileSystemBackend(src_type_); + backend->OpenFileSystem(origin_, src_type_, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&ExpectOk)); + backend = file_system_context_->GetFileSystemBackend(dest_type_); + if (dest_type_ == kFileSystemTypeTest) { + TestFileSystemBackend* test_backend = + static_cast<TestFileSystemBackend*>(backend); + scoped_ptr<CopyOrMoveFileValidatorFactory> factory( + new TestValidatorFactory); + test_backend->set_require_copy_or_move_validator( + require_copy_or_move_validator); + if (init_copy_or_move_validator) + test_backend->InitializeCopyOrMoveFileValidatorFactory(factory.Pass()); + } + backend->OpenFileSystem(origin_, dest_type_, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&ExpectOk)); + base::MessageLoop::current()->RunUntilIdle(); + + // Grant relatively big quota initially. + quota_manager_->SetQuota(origin_, + FileSystemTypeToQuotaStorageType(src_type_), + 1024 * 1024); + quota_manager_->SetQuota(origin_, + FileSystemTypeToQuotaStorageType(dest_type_), + 1024 * 1024); + } + + int64 GetSourceUsage() { + int64 usage = 0; + GetUsageAndQuota(src_type_, &usage, NULL); + return usage; + } + + int64 GetDestUsage() { + int64 usage = 0; + GetUsageAndQuota(dest_type_, &usage, NULL); + return usage; + } + + FileSystemURL SourceURL(const std::string& path) { + return file_system_context_->CreateCrackedFileSystemURL( + origin_, src_type_, base::FilePath::FromUTF8Unsafe(path)); + } + + FileSystemURL DestURL(const std::string& path) { + return file_system_context_->CreateCrackedFileSystemURL( + origin_, dest_type_, base::FilePath::FromUTF8Unsafe(path)); + } + + base::PlatformFileError Copy(const FileSystemURL& src, + const FileSystemURL& dest) { + return AsyncFileTestHelper::Copy(file_system_context_.get(), src, dest); + } + + base::PlatformFileError Move(const FileSystemURL& src, + const FileSystemURL& dest) { + return AsyncFileTestHelper::Move(file_system_context_.get(), src, dest); + } + + base::PlatformFileError SetUpTestCaseFiles( + const FileSystemURL& root, + const test::TestCaseRecord* const test_cases, + size_t test_case_size) { + base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED; + for (size_t i = 0; i < test_case_size; ++i) { + const test::TestCaseRecord& test_case = test_cases[i]; + FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( + root.origin(), + root.mount_type(), + root.virtual_path().Append(test_case.path)); + if (test_case.is_directory) + result = CreateDirectory(url); + else + result = CreateFile(url, test_case.data_file_size); + EXPECT_EQ(base::PLATFORM_FILE_OK, result) << url.DebugString(); + if (result != base::PLATFORM_FILE_OK) + return result; + } + return result; + } + + void VerifyTestCaseFiles( + const FileSystemURL& root, + const test::TestCaseRecord* const test_cases, + size_t test_case_size) { + std::map<base::FilePath, const test::TestCaseRecord*> test_case_map; + for (size_t i = 0; i < test_case_size; ++i) { + test_case_map[ + base::FilePath(test_cases[i].path).NormalizePathSeparators()] = + &test_cases[i]; + } + + std::queue<FileSystemURL> directories; + FileEntryList entries; + directories.push(root); + while (!directories.empty()) { + FileSystemURL dir = directories.front(); + directories.pop(); + ASSERT_EQ(base::PLATFORM_FILE_OK, ReadDirectory(dir, &entries)); + for (size_t i = 0; i < entries.size(); ++i) { + FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( + dir.origin(), + dir.mount_type(), + dir.virtual_path().Append(entries[i].name)); + base::FilePath relative; + root.virtual_path().AppendRelativePath(url.virtual_path(), &relative); + relative = relative.NormalizePathSeparators(); + ASSERT_TRUE(ContainsKey(test_case_map, relative)); + if (entries[i].is_directory) { + EXPECT_TRUE(test_case_map[relative]->is_directory); + directories.push(url); + } else { + EXPECT_FALSE(test_case_map[relative]->is_directory); + EXPECT_TRUE(FileExists(url, test_case_map[relative]->data_file_size)); + } + test_case_map.erase(relative); + } + } + EXPECT_TRUE(test_case_map.empty()); + std::map<base::FilePath, const test::TestCaseRecord*>::const_iterator it; + for (it = test_case_map.begin(); it != test_case_map.end(); ++it) { + LOG(ERROR) << "Extra entry: " << it->first.LossyDisplayName(); + } + } + + base::PlatformFileError ReadDirectory(const FileSystemURL& url, + FileEntryList* entries) { + return AsyncFileTestHelper::ReadDirectory( + file_system_context_.get(), url, entries); + } + + base::PlatformFileError CreateDirectory(const FileSystemURL& url) { + return AsyncFileTestHelper::CreateDirectory(file_system_context_.get(), + url); + } + + base::PlatformFileError CreateFile(const FileSystemURL& url, size_t size) { + base::PlatformFileError result = + AsyncFileTestHelper::CreateFile(file_system_context_.get(), url); + if (result != base::PLATFORM_FILE_OK) + return result; + return AsyncFileTestHelper::TruncateFile( + file_system_context_.get(), url, size); + } + + bool FileExists(const FileSystemURL& url, int64 expected_size) { + return AsyncFileTestHelper::FileExists( + file_system_context_.get(), url, expected_size); + } + + bool DirectoryExists(const FileSystemURL& url) { + return AsyncFileTestHelper::DirectoryExists(file_system_context_.get(), + url); + } + + private: + void GetUsageAndQuota(FileSystemType type, int64* usage, int64* quota) { + quota::QuotaStatusCode status = AsyncFileTestHelper::GetUsageAndQuota( + quota_manager_.get(), origin_, type, usage, quota); + ASSERT_EQ(quota::kQuotaStatusOk, status); + } + + private: + base::ScopedTempDir base_; + + const GURL origin_; + const FileSystemType src_type_; + const FileSystemType dest_type_; + + base::MessageLoop message_loop_; + scoped_refptr<FileSystemContext> file_system_context_; + scoped_refptr<quota::MockQuotaManagerProxy> quota_manager_proxy_; + scoped_refptr<quota::MockQuotaManager> quota_manager_; + + DISALLOW_COPY_AND_ASSIGN(CopyOrMoveOperationTestHelper); +}; + +TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleFile) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypePersistent); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + int64 src_initial_usage = helper.GetSourceUsage(); + int64 dest_initial_usage = helper.GetDestUsage(); + + // Set up a source file. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateFile(src, 10)); + int64 src_increase = helper.GetSourceUsage() - src_initial_usage; + + // Copy it. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Copy(src, dest)); + + // Verify. + ASSERT_TRUE(helper.FileExists(src, 10)); + ASSERT_TRUE(helper.FileExists(dest, 10)); + + int64 src_new_usage = helper.GetSourceUsage(); + ASSERT_EQ(src_initial_usage + src_increase, src_new_usage); + + int64 dest_increase = helper.GetDestUsage() - dest_initial_usage; + ASSERT_EQ(src_increase, dest_increase); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, MoveSingleFile) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypePersistent); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + int64 src_initial_usage = helper.GetSourceUsage(); + int64 dest_initial_usage = helper.GetDestUsage(); + + // Set up a source file. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateFile(src, 10)); + int64 src_increase = helper.GetSourceUsage() - src_initial_usage; + + // Move it. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Move(src, dest)); + + // Verify. + ASSERT_FALSE(helper.FileExists(src, AsyncFileTestHelper::kDontCheckSize)); + ASSERT_TRUE(helper.FileExists(dest, 10)); + + int64 src_new_usage = helper.GetSourceUsage(); + ASSERT_EQ(src_initial_usage, src_new_usage); + + int64 dest_increase = helper.GetDestUsage() - dest_initial_usage; + ASSERT_EQ(src_increase, dest_increase); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleDirectory) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypePersistent); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + int64 src_initial_usage = helper.GetSourceUsage(); + int64 dest_initial_usage = helper.GetDestUsage(); + + // Set up a source directory. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src)); + int64 src_increase = helper.GetSourceUsage() - src_initial_usage; + + // Copy it. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Copy(src, dest)); + + // Verify. + ASSERT_TRUE(helper.DirectoryExists(src)); + ASSERT_TRUE(helper.DirectoryExists(dest)); + + int64 src_new_usage = helper.GetSourceUsage(); + ASSERT_EQ(src_initial_usage + src_increase, src_new_usage); + + int64 dest_increase = helper.GetDestUsage() - dest_initial_usage; + ASSERT_EQ(src_increase, dest_increase); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, MoveSingleDirectory) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypePersistent); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + int64 src_initial_usage = helper.GetSourceUsage(); + int64 dest_initial_usage = helper.GetDestUsage(); + + // Set up a source directory. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src)); + int64 src_increase = helper.GetSourceUsage() - src_initial_usage; + + // Move it. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Move(src, dest)); + + // Verify. + ASSERT_FALSE(helper.DirectoryExists(src)); + ASSERT_TRUE(helper.DirectoryExists(dest)); + + int64 src_new_usage = helper.GetSourceUsage(); + ASSERT_EQ(src_initial_usage, src_new_usage); + + int64 dest_increase = helper.GetDestUsage() - dest_initial_usage; + ASSERT_EQ(src_increase, dest_increase); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, CopyDirectory) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypePersistent); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + int64 src_initial_usage = helper.GetSourceUsage(); + int64 dest_initial_usage = helper.GetDestUsage(); + + // Set up a source directory. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + helper.SetUpTestCaseFiles(src, + test::kRegularTestCases, + test::kRegularTestCaseSize)); + int64 src_increase = helper.GetSourceUsage() - src_initial_usage; + + // Copy it. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Copy(src, dest)); + + // Verify. + ASSERT_TRUE(helper.DirectoryExists(src)); + ASSERT_TRUE(helper.DirectoryExists(dest)); + + helper.VerifyTestCaseFiles(dest, + test::kRegularTestCases, + test::kRegularTestCaseSize); + + int64 src_new_usage = helper.GetSourceUsage(); + ASSERT_EQ(src_initial_usage + src_increase, src_new_usage); + + int64 dest_increase = helper.GetDestUsage() - dest_initial_usage; + ASSERT_EQ(src_increase, dest_increase); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, MoveDirectory) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypePersistent); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + int64 src_initial_usage = helper.GetSourceUsage(); + int64 dest_initial_usage = helper.GetDestUsage(); + + // Set up a source directory. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + helper.SetUpTestCaseFiles(src, + test::kRegularTestCases, + test::kRegularTestCaseSize)); + int64 src_increase = helper.GetSourceUsage() - src_initial_usage; + + // Move it. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Move(src, dest)); + + // Verify. + ASSERT_FALSE(helper.DirectoryExists(src)); + ASSERT_TRUE(helper.DirectoryExists(dest)); + + helper.VerifyTestCaseFiles(dest, + test::kRegularTestCases, + test::kRegularTestCaseSize); + + int64 src_new_usage = helper.GetSourceUsage(); + ASSERT_EQ(src_initial_usage, src_new_usage); + + int64 dest_increase = helper.GetDestUsage() - dest_initial_usage; + ASSERT_EQ(src_increase, dest_increase); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, + MoveDirectoryFailPostWriteValidation) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypeTest); + helper.SetUp(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + + // Set up a source directory. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + helper.SetUpTestCaseFiles(src, + test::kRegularTestCases, + test::kRegularTestCaseSize)); + + // Move it. + helper.Move(src, dest); + + // Verify. + ASSERT_TRUE(helper.DirectoryExists(src)); + ASSERT_TRUE(helper.DirectoryExists(dest)); + + test::TestCaseRecord kMoveDirResultCases[] = { + {false, FILE_PATH_LITERAL("file 0"), 38}, + {false, FILE_PATH_LITERAL("file 3"), 0}, + }; + + helper.VerifyTestCaseFiles(dest, + kMoveDirResultCases, + arraysize(kMoveDirResultCases)); +} + +TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleFileNoValidator) { + CopyOrMoveOperationTestHelper helper(GURL("http://foo"), + kFileSystemTypeTemporary, + kFileSystemTypeTest); + helper.SetUpNoValidator(); + + FileSystemURL src = helper.SourceURL("a"); + FileSystemURL dest = helper.DestURL("b"); + + // Set up a source file. + ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateFile(src, 10)); + + // The copy attempt should fail with a security error -- getting + // the factory returns a security error, and the copy operation must + // respect that. + ASSERT_EQ(base::PLATFORM_FILE_ERROR_SECURITY, helper.Copy(src, dest)); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/dump_file_system.cc b/chromium/webkit/browser/fileapi/dump_file_system.cc new file mode 100644 index 00000000000..4dd60f26b36 --- /dev/null +++ b/chromium/webkit/browser/fileapi/dump_file_system.cc @@ -0,0 +1,205 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A tool to dump HTML5 filesystem from CUI. +// +// Usage: +// +// ./out/Release/dump_file_system [options] <filesystem dir> [origin]... +// +// If no origin is specified, this dumps all origins in the profile dir. +// +// Available options: +// +// -t : dumps temporary files instead of persistent. +// -s : dumps syncable files instead of persistent. +// -l : more information will be displayed. +// +// The format of -l option is: +// +// === ORIGIN origin_name origin_dir === +// file_name file_id file_size file_content_path +// ... +// +// where file_name has a trailing slash, file_size is the number of +// children, and file_content_path is empty if the file is a directory. +// + +#include <stdio.h> +#include <stdlib.h> + +#include <stack> +#include <string> +#include <utility> +#include <vector> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/format_macros.h" +#include "base/strings/stringprintf.h" +#include "webkit/browser/fileapi/obfuscated_file_util.h" +#include "webkit/browser/fileapi/sandbox_directory_database.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend.h" +#include "webkit/browser/fileapi/sandbox_origin_database.h" +#include "webkit/common/fileapi/file_system_types.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace { + +bool g_opt_long; +fileapi::FileSystemType g_opt_fs_type = fileapi::kFileSystemTypePersistent; + +void ShowMessageAndExit(const std::string& msg) { + fprintf(stderr, "%s\n", msg.c_str()); + exit(EXIT_FAILURE); +} + +void ShowUsageAndExit(const std::string& arg0) { + ShowMessageAndExit( + "Usage: " + arg0 + + " [-l] [-t] [-s] <filesystem dir> [origin]..."); +} + +} // namespace + +namespace fileapi { + +static void DumpDirectoryTree(const std::string& origin_name, + base::FilePath origin_dir) { + origin_dir = origin_dir.Append( + ObfuscatedFileUtil::GetDirectoryNameForType(g_opt_fs_type)); + + printf("=== ORIGIN %s %s ===\n", + origin_name.c_str(), FilePathToString(origin_dir).c_str()); + + if (!base::DirectoryExists(origin_dir)) + return; + + SandboxDirectoryDatabase directory_db(origin_dir); + SandboxDirectoryDatabase::FileId root_id; + if (!directory_db.GetFileWithPath(StringToFilePath("/"), &root_id)) + return; + + std::stack<std::pair<SandboxDirectoryDatabase::FileId, + std::string> > paths; + paths.push(std::make_pair(root_id, "")); + while (!paths.empty()) { + SandboxDirectoryDatabase::FileId id = paths.top().first; + const std::string dirname = paths.top().second; + paths.pop(); + + SandboxDirectoryDatabase::FileInfo info; + if (!directory_db.GetFileInfo(id, &info)) { + ShowMessageAndExit(base::StringPrintf("GetFileInfo failed for %"PRId64, + id)); + } + + const std::string name = + dirname + "/" + FilePathToString(base::FilePath(info.name)); + std::vector<SandboxDirectoryDatabase::FileId> children; + if (info.is_directory()) { + if (!directory_db.ListChildren(id, &children)) { + ShowMessageAndExit(base::StringPrintf( + "ListChildren failed for %s (%"PRId64")", + info.name.c_str(), id)); + } + + for (size_t j = children.size(); j; j--) + paths.push(make_pair(children[j-1], name)); + } + + // +1 for the leading extra slash. + const char* display_name = name.c_str() + 1; + const char* directory_suffix = info.is_directory() ? "/" : ""; + if (g_opt_long) { + int64 size; + if (info.is_directory()) { + size = static_cast<int64>(children.size()); + } else { + file_util::GetFileSize(origin_dir.Append(info.data_path), &size); + } + // TODO(hamaji): Modification time? + printf("%s%s %"PRId64" %"PRId64" %s\n", + display_name, + directory_suffix, + id, + size, + FilePathToString(info.data_path).c_str()); + } else { + printf("%s%s\n", display_name, directory_suffix); + } + } +} + +static void DumpOrigin(const base::FilePath& file_system_dir, + const std::string& origin_name) { + SandboxOriginDatabase origin_db(file_system_dir); + base::FilePath origin_dir; + if (!origin_db.HasOriginPath(origin_name)) { + ShowMessageAndExit("Origin " + origin_name + " is not in " + + FilePathToString(file_system_dir)); + } + + if (!origin_db.GetPathForOrigin(origin_name, &origin_dir)) { + ShowMessageAndExit("Failed to get path of origin " + origin_name + + " in " + FilePathToString(file_system_dir)); + } + DumpDirectoryTree(origin_name, file_system_dir.Append(origin_dir)); +} + +static void DumpFileSystem(const base::FilePath& file_system_dir) { + SandboxOriginDatabase origin_db(file_system_dir); + std::vector<SandboxOriginDatabase::OriginRecord> origins; + origin_db.ListAllOrigins(&origins); + for (size_t i = 0; i < origins.size(); i++) { + const SandboxOriginDatabase::OriginRecord& origin = origins[i]; + DumpDirectoryTree(origin.origin, file_system_dir.Append(origin.path)); + puts(""); + } +} + +} // namespace fileapi + +int main(int argc, char* argv[]) { + const char* arg0 = argv[0]; + std::string username = "Default"; + while (true) { + if (argc < 2) + ShowUsageAndExit(arg0); + + if (std::string(argv[1]) == "-l") { + g_opt_long = true; + argc--; + argv++; + } else if (std::string(argv[1]) == "-t") { + g_opt_fs_type = fileapi::kFileSystemTypeTemporary; + argc--; + argv++; + } else if (std::string(argv[1]) == "-s") { + g_opt_fs_type = fileapi::kFileSystemTypeSyncable; + argc--; + argv++; + } else { + break; + } + } + + if (argc < 2) + ShowUsageAndExit(arg0); + + const base::FilePath file_system_dir = fileapi::StringToFilePath(argv[1]); + if (!base::DirectoryExists(file_system_dir)) { + ShowMessageAndExit(fileapi::FilePathToString(file_system_dir) + + " is not a filesystem directory"); + } + + if (argc == 2) { + fileapi::DumpFileSystem(file_system_dir); + } else { + for (int i = 2; i < argc; i++) { + fileapi::DumpOrigin(file_system_dir, argv[i]); + } + } + return 0; +} diff --git a/chromium/webkit/browser/fileapi/external_mount_points.cc b/chromium/webkit/browser/fileapi/external_mount_points.cc new file mode 100644 index 00000000000..a3ecd4b6678 --- /dev/null +++ b/chromium/webkit/browser/fileapi/external_mount_points.cc @@ -0,0 +1,335 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/external_mount_points.h" + +#include "base/files/file_path.h" +#include "base/lazy_instance.h" +#include "base/path_service.h" +#include "base/stl_util.h" +#include "webkit/browser/fileapi/file_system_url.h" + +namespace { + +// Normalizes file path so it has normalized separators and ends with exactly +// one separator. Paths have to be normalized this way for use in +// GetVirtualPath method. Separators cannot be completely stripped, or +// GetVirtualPath could not working in some edge cases. +// For example, /a/b/c(1)/d would be erroneously resolved as c/d if the +// following mount points were registered: "/a/b/c", "/a/b/c(1)". (Note: +// "/a/b/c" < "/a/b/c(1)" < "/a/b/c/"). +base::FilePath NormalizeFilePath(const base::FilePath& path) { + if (path.empty()) + return path; + + base::FilePath::StringType path_str = path.StripTrailingSeparators().value(); + if (!base::FilePath::IsSeparator(path_str[path_str.length() - 1])) + path_str.append(FILE_PATH_LITERAL("/")); + + return base::FilePath(path_str).NormalizePathSeparators(); +} + +// Wrapper around ref-counted ExternalMountPoints that will be used to lazily +// create and initialize LazyInstance system ExternalMountPoints. +class SystemMountPointsLazyWrapper { + public: + SystemMountPointsLazyWrapper() + : system_mount_points_(fileapi::ExternalMountPoints::CreateRefCounted()) { + } + + ~SystemMountPointsLazyWrapper() {} + + fileapi::ExternalMountPoints* get() { + return system_mount_points_.get(); + } + + private: + scoped_refptr<fileapi::ExternalMountPoints> system_mount_points_; +}; + +base::LazyInstance<SystemMountPointsLazyWrapper>::Leaky + g_external_mount_points = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace fileapi { + +class ExternalMountPoints::Instance { + public: + Instance(FileSystemType type, const base::FilePath& path) + : type_(type), path_(path.StripTrailingSeparators()) {} + ~Instance() {} + + FileSystemType type() const { return type_; } + const base::FilePath& path() const { return path_; } + + private: + const FileSystemType type_; + const base::FilePath path_; + + DISALLOW_COPY_AND_ASSIGN(Instance); +}; + +//-------------------------------------------------------------------------- + +// static +ExternalMountPoints* ExternalMountPoints::GetSystemInstance() { + return g_external_mount_points.Pointer()->get(); +} + +// static +scoped_refptr<ExternalMountPoints> ExternalMountPoints::CreateRefCounted() { + return new ExternalMountPoints(); +} + +bool ExternalMountPoints::RegisterFileSystem( + const std::string& mount_name, + FileSystemType type, + const base::FilePath& path_in) { + base::AutoLock locker(lock_); + + base::FilePath path = NormalizeFilePath(path_in); + if (!ValidateNewMountPoint(mount_name, path)) + return false; + + instance_map_[mount_name] = new Instance(type, path); + if (!path.empty()) + path_to_name_map_.insert(std::make_pair(path, mount_name)); + return true; +} + +bool ExternalMountPoints::HandlesFileSystemMountType( + FileSystemType type) const { + return type == kFileSystemTypeExternal || + type == kFileSystemTypeNativeForPlatformApp; +} + +bool ExternalMountPoints::RevokeFileSystem(const std::string& mount_name) { + base::AutoLock locker(lock_); + NameToInstance::iterator found = instance_map_.find(mount_name); + if (found == instance_map_.end()) + return false; + Instance* instance = found->second; + path_to_name_map_.erase(NormalizeFilePath(instance->path())); + delete found->second; + instance_map_.erase(found); + return true; +} + +bool ExternalMountPoints::GetRegisteredPath( + const std::string& filesystem_id, base::FilePath* path) const { + DCHECK(path); + base::AutoLock locker(lock_); + NameToInstance::const_iterator found = instance_map_.find(filesystem_id); + if (found == instance_map_.end()) + return false; + *path = found->second->path(); + return true; +} + +bool ExternalMountPoints::CrackVirtualPath(const base::FilePath& virtual_path, + std::string* mount_name, + FileSystemType* type, + base::FilePath* path) const { + DCHECK(mount_name); + DCHECK(path); + + // The path should not contain any '..' references. + if (virtual_path.ReferencesParent()) + return false; + + // The virtual_path should comprise of <mount_name> and <relative_path> parts. + std::vector<base::FilePath::StringType> components; + virtual_path.GetComponents(&components); + if (components.size() < 1) + return false; + + std::vector<base::FilePath::StringType>::iterator component_iter = + components.begin(); + std::string maybe_mount_name = + base::FilePath(*component_iter++).MaybeAsASCII(); + if (maybe_mount_name.empty()) + return false; + + base::FilePath cracked_path; + { + base::AutoLock locker(lock_); + NameToInstance::const_iterator found_instance = + instance_map_.find(maybe_mount_name); + if (found_instance == instance_map_.end()) + return false; + + *mount_name = maybe_mount_name; + const Instance* instance = found_instance->second; + if (type) + *type = instance->type(); + cracked_path = instance->path(); + } + + for (; component_iter != components.end(); ++component_iter) + cracked_path = cracked_path.Append(*component_iter); + *path = cracked_path; + return true; +} + +FileSystemURL ExternalMountPoints::CrackURL(const GURL& url) const { + FileSystemURL filesystem_url = FileSystemURL(url); + if (!filesystem_url.is_valid()) + return FileSystemURL(); + return CrackFileSystemURL(filesystem_url); +} + +FileSystemURL ExternalMountPoints::CreateCrackedFileSystemURL( + const GURL& origin, + FileSystemType type, + const base::FilePath& path) const { + return CrackFileSystemURL(FileSystemURL(origin, type, path)); +} + +void ExternalMountPoints::AddMountPointInfosTo( + std::vector<MountPointInfo>* mount_points) const { + base::AutoLock locker(lock_); + DCHECK(mount_points); + for (NameToInstance::const_iterator iter = instance_map_.begin(); + iter != instance_map_.end(); ++iter) { + mount_points->push_back(MountPointInfo(iter->first, iter->second->path())); + } +} + +bool ExternalMountPoints::GetVirtualPath(const base::FilePath& path_in, + base::FilePath* virtual_path) const { + DCHECK(virtual_path); + + base::AutoLock locker(lock_); + + base::FilePath path = NormalizeFilePath(path_in); + std::map<base::FilePath, std::string>::const_reverse_iterator iter( + path_to_name_map_.upper_bound(path)); + if (iter == path_to_name_map_.rend()) + return false; + + *virtual_path = CreateVirtualRootPath(iter->second); + if (iter->first == path) + return true; + return iter->first.AppendRelativePath(path, virtual_path); +} + +base::FilePath ExternalMountPoints::CreateVirtualRootPath( + const std::string& mount_name) const { + return base::FilePath().AppendASCII(mount_name); +} + +FileSystemURL ExternalMountPoints::CreateExternalFileSystemURL( + const GURL& origin, + const std::string& mount_name, + const base::FilePath& path) const { + return CreateCrackedFileSystemURL( + origin, + fileapi::kFileSystemTypeExternal, + // Avoid using FilePath::Append as path may be an absolute path. + base::FilePath( + CreateVirtualRootPath(mount_name).value() + + base::FilePath::kSeparators[0] + path.value())); +} + +ExternalMountPoints::ExternalMountPoints() {} + +ExternalMountPoints::~ExternalMountPoints() { + STLDeleteContainerPairSecondPointers(instance_map_.begin(), + instance_map_.end()); +} + +FileSystemURL ExternalMountPoints::CrackFileSystemURL( + const FileSystemURL& url) const { + if (!HandlesFileSystemMountType(url.type())) + return FileSystemURL(); + + base::FilePath virtual_path = url.path(); + if (url.type() == kFileSystemTypeNativeForPlatformApp) { +#if defined(OS_CHROMEOS) + // On Chrome OS, find a mount point and virtual path for the external fs. + if (!GetVirtualPath(url.path(), &virtual_path)) + return FileSystemURL(); +#else + // On other OS, it is simply a native local path. + return FileSystemURL( + url.origin(), url.mount_type(), url.virtual_path(), + url.mount_filesystem_id(), kFileSystemTypeNativeLocal, + url.path(), url.filesystem_id()); +#endif + } + + std::string mount_name; + FileSystemType cracked_type; + base::FilePath cracked_path; + + if (!CrackVirtualPath(virtual_path, &mount_name, &cracked_type, + &cracked_path)) { + return FileSystemURL(); + } + + return FileSystemURL( + url.origin(), url.mount_type(), url.virtual_path(), + !url.filesystem_id().empty() ? url.filesystem_id() : mount_name, + cracked_type, cracked_path, mount_name); +} + +bool ExternalMountPoints::ValidateNewMountPoint(const std::string& mount_name, + const base::FilePath& path) { + lock_.AssertAcquired(); + + // Mount name must not be empty. + if (mount_name.empty()) + return false; + + // Verify there is no registered mount point with the same name. + NameToInstance::iterator found = instance_map_.find(mount_name); + if (found != instance_map_.end()) + return false; + + // Allow empty paths. + if (path.empty()) + return true; + + // Verify path is legal. + if (path.ReferencesParent() || !path.IsAbsolute()) + return false; + + // Check there the new path does not overlap with one of the existing ones. + std::map<base::FilePath, std::string>::reverse_iterator potential_parent( + path_to_name_map_.upper_bound(path)); + if (potential_parent != path_to_name_map_.rend()) { + if (potential_parent->first == path || + potential_parent->first.IsParent(path)) { + return false; + } + } + + std::map<base::FilePath, std::string>::iterator potential_child = + path_to_name_map_.upper_bound(path); + if (potential_child == path_to_name_map_.end()) + return true; + return !(potential_child->first == path) && + !path.IsParent(potential_child->first); +} + +ScopedExternalFileSystem::ScopedExternalFileSystem( + const std::string& mount_name, + FileSystemType type, + const base::FilePath& path) + : mount_name_(mount_name) { + ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( + mount_name, type, path); +} + +base::FilePath ScopedExternalFileSystem::GetVirtualRootPath() const { + return ExternalMountPoints::GetSystemInstance()-> + CreateVirtualRootPath(mount_name_); +} + +ScopedExternalFileSystem::~ScopedExternalFileSystem() { + ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(mount_name_); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/external_mount_points.h b/chromium/webkit/browser/fileapi/external_mount_points.h new file mode 100644 index 00000000000..9118443b7b6 --- /dev/null +++ b/chromium/webkit/browser/fileapi/external_mount_points.h @@ -0,0 +1,159 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_EXTERNAL_MOUNT_POINTS_H_ +#define WEBKIT_BROWSER_FILEAPI_EXTERNAL_MOUNT_POINTS_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "webkit/browser/fileapi/mount_points.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/fileapi/file_system_types.h" + +namespace base { +class FilePath; +} + +namespace fileapi { +class FileSystemURL; +} + +namespace fileapi { + +// Manages external filesystem namespaces that are identified by 'mount name' +// and are persisted until RevokeFileSystem is called. +// Files in an external filesystem are identified by a filesystem URL like: +// +// filesystem:<origin>/external/<mount_name>/relative/path +// +class WEBKIT_STORAGE_BROWSER_EXPORT ExternalMountPoints + : public base::RefCountedThreadSafe<ExternalMountPoints>, + public MountPoints { + public: + static ExternalMountPoints* GetSystemInstance(); + static scoped_refptr<ExternalMountPoints> CreateRefCounted(); + + // Registers a new named external filesystem. + // The |path| is registered as the root path of the mount point which + // is identified by a URL "filesystem:.../external/mount_name". + // + // For example, if the path "/media/removable" is registered with + // the mount_name "removable", a filesystem URL like + // "filesystem:.../external/removable/a/b" will be resolved as + // "/media/removable/a/b". + // + // The |mount_name| should NOT contain a path separator '/'. + // Returns false if the given name is already registered. + // + // Overlapping mount points in a single MountPoints instance are not allowed. + // Adding mount point whose path overlaps with an existing mount point will + // fail. + // + // If not empty, |path| must be absolute. It is allowed for the path to be + // empty, but |GetVirtualPath| will not work for those mount points. + // + // An external file system registered by this method can be revoked + // by calling RevokeFileSystem with |mount_name|. + bool RegisterFileSystem(const std::string& mount_name, + FileSystemType type, + const base::FilePath& path); + + // MountPoints overrides. + virtual bool HandlesFileSystemMountType(FileSystemType type) const OVERRIDE; + virtual bool RevokeFileSystem(const std::string& mount_name) OVERRIDE; + virtual bool GetRegisteredPath(const std::string& mount_name, + base::FilePath* path) const OVERRIDE; + virtual bool CrackVirtualPath(const base::FilePath& virtual_path, + std::string* mount_name, + FileSystemType* type, + base::FilePath* path) const OVERRIDE; + virtual FileSystemURL CrackURL(const GURL& url) const OVERRIDE; + virtual FileSystemURL CreateCrackedFileSystemURL( + const GURL& origin, + FileSystemType type, + const base::FilePath& path) const OVERRIDE; + + // Returns a list of registered MountPointInfos (of <mount_name, path>). + void AddMountPointInfosTo(std::vector<MountPointInfo>* mount_points) const; + + // Converts a path on a registered file system to virtual path relative to the + // file system root. E.g. if 'Downloads' file system is mapped to + // '/usr/local/home/Downloads', and |absolute| path is set to + // '/usr/local/home/Downloads/foo', the method will set |virtual_path| to + // 'Downloads/foo'. + // Returns false if the path cannot be resolved (e.g. if the path is not + // part of any registered filesystem). + // + // Returned virtual_path will have normalized path separators. + bool GetVirtualPath(const base::FilePath& absolute_path, + base::FilePath* virtual_path) const; + + // Returns the virtual root path that looks like /<mount_name>. + base::FilePath CreateVirtualRootPath(const std::string& mount_name) const; + + FileSystemURL CreateExternalFileSystemURL( + const GURL& origin, + const std::string& mount_name, + const base::FilePath& path) const; + + private: + friend class base::RefCountedThreadSafe<ExternalMountPoints>; + + // Represents each file system instance (defined in the .cc). + class Instance; + + typedef std::map<std::string, Instance*> NameToInstance; + + // Reverse map from registered path to its corresponding mount name. + typedef std::map<base::FilePath, std::string> PathToName; + + // Use |GetSystemInstance| of |CreateRefCounted| to get an instance. + ExternalMountPoints(); + virtual ~ExternalMountPoints(); + + // MountPoint overrides. + virtual FileSystemURL CrackFileSystemURL( + const FileSystemURL& url) const OVERRIDE; + + // Performs sanity checks on the new mount point. + // Checks the following: + // - there is no registered mount point with mount_name + // - path does not contain a reference to a parent + // - path is absolute + // - path does not overlap with an existing mount point path. + // + // |lock_| should be taken before calling this method. + bool ValidateNewMountPoint(const std::string& mount_name, + const base::FilePath& path); + + // This lock needs to be obtained when accessing the instance_map_. + mutable base::Lock lock_; + + NameToInstance instance_map_; + PathToName path_to_name_map_; + + DISALLOW_COPY_AND_ASSIGN(ExternalMountPoints); +}; + +// Registers a scoped external filesystem which gets revoked when it scopes out. +class WEBKIT_STORAGE_BROWSER_EXPORT ScopedExternalFileSystem { + public: + ScopedExternalFileSystem(const std::string& mount_name, + FileSystemType type, + const base::FilePath& path); + ~ScopedExternalFileSystem(); + + base::FilePath GetVirtualRootPath() const; + + private: + const std::string mount_name_; +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_EXTERNAL_MOUNT_POINTS_H_ diff --git a/chromium/webkit/browser/fileapi/external_mount_points_unittest.cc b/chromium/webkit/browser/fileapi/external_mount_points_unittest.cc new file mode 100644 index 00000000000..cc2acbe2a16 --- /dev/null +++ b/chromium/webkit/browser/fileapi/external_mount_points_unittest.cc @@ -0,0 +1,462 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/external_mount_points.h" + +#include <string> + +#include "base/files/file_path.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/file_system_url.h" + +#define FPL FILE_PATH_LITERAL + +#if defined(FILE_PATH_USES_DRIVE_LETTERS) +#define DRIVE FPL("C:") +#else +#define DRIVE +#endif + +using fileapi::FileSystemURL; + +namespace { + +TEST(ExternalMountPointsTest, AddMountPoint) { + scoped_refptr<fileapi::ExternalMountPoints> mount_points( + fileapi::ExternalMountPoints::CreateRefCounted()); + + struct TestCase { + // The mount point's name. + const char* const name; + // The mount point's path. + const base::FilePath::CharType* const path; + // Whether the mount point registration should succeed. + bool success; + // Path returned by GetRegisteredPath. NULL if the method is expected to + // fail. + const base::FilePath::CharType* const registered_path; + }; + + const TestCase kTestCases[] = { + // Valid mount point. + { "test", DRIVE FPL("/foo/test"), true, DRIVE FPL("/foo/test") }, + // Valid mount point with only one path component. + { "bbb", DRIVE FPL("/bbb"), true, DRIVE FPL("/bbb") }, + // Existing mount point path is substring of the mount points path. + { "test11", DRIVE FPL("/foo/test11"), true, DRIVE FPL("/foo/test11") }, + // Path substring of an existing path. + { "test1", DRIVE FPL("/foo/test1"), true, DRIVE FPL("/foo/test1") }, + // Empty mount point name and path. + { "", DRIVE FPL(""), false, NULL }, + // Empty mount point name. + { "", DRIVE FPL("/ddd"), false, NULL }, + // Empty mount point path. + { "empty_path", FPL(""), true, FPL("") }, + // Name different from path's base name. + { "not_base_name", DRIVE FPL("/x/y/z"), true, DRIVE FPL("/x/y/z") }, + // References parent. + { "invalid", DRIVE FPL("../foo/invalid"), false, NULL }, + // Relative path. + { "relative", DRIVE FPL("foo/relative"), false, NULL }, + // Existing mount point path. + { "path_exists", DRIVE FPL("/foo/test"), false, NULL }, + // Mount point with the same name exists. + { "test", DRIVE FPL("/foo/a/test_name_exists"), false, + DRIVE FPL("/foo/test") }, + // Child of an existing mount point. + { "a1", DRIVE FPL("/foo/test/a"), false, NULL }, + // Parent of an existing mount point. + { "foo1", DRIVE FPL("/foo"), false, NULL }, + // Bit bigger depth. + { "g", DRIVE FPL("/foo/a/b/c/d/e/f/g"), true, + DRIVE FPL("/foo/a/b/c/d/e/f/g") }, + // Sibling mount point (with similar name) exists. + { "ff", DRIVE FPL("/foo/a/b/c/d/e/ff"), true, + DRIVE FPL("/foo/a/b/c/d/e/ff") }, + // Lexicographically last among existing mount points. + { "yyy", DRIVE FPL("/zzz/yyy"), true, DRIVE FPL("/zzz/yyy") }, + // Parent of the lexicographically last mount point. + { "zzz1", DRIVE FPL("/zzz"), false, NULL }, + // Child of the lexicographically last mount point. + { "xxx1", DRIVE FPL("/zzz/yyy/xxx"), false, NULL }, + // Lexicographically first among existing mount points. + { "b", DRIVE FPL("/a/b"), true, DRIVE FPL("/a/b") }, + // Parent of lexicographically first mount point. + { "a2", DRIVE FPL("/a"), false, NULL }, + // Child of lexicographically last mount point. + { "c1", DRIVE FPL("/a/b/c"), false, NULL }, + // Parent to all of the mount points. + { "root", DRIVE FPL("/"), false, NULL }, + // Path contains .. component. + { "funky", DRIVE FPL("/tt/fun/../funky"), false, NULL }, + // Windows separators. +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { "win", DRIVE FPL("\\try\\separators\\win"), true, + DRIVE FPL("\\try\\separators\\win") }, + { "win1", DRIVE FPL("\\try/separators\\win1"), true, + DRIVE FPL("\\try/separators\\win1") }, + { "win2", DRIVE FPL("\\try/separators\\win"), false, NULL }, +#else + { "win", DRIVE FPL("\\separators\\win"), false, NULL }, + { "win1", DRIVE FPL("\\try/separators\\win1"), false, NULL }, +#endif + // Win separators, but relative path. + { "win2", DRIVE FPL("try\\separators\\win2"), false, NULL }, + }; + + // Test adding mount points. + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) { + EXPECT_EQ(kTestCases[i].success, + mount_points->RegisterFileSystem( + kTestCases[i].name, + fileapi::kFileSystemTypeNativeLocal, + base::FilePath(kTestCases[i].path))) + << "Adding mount point: " << kTestCases[i].name << " with path " + << kTestCases[i].path; + } + + // Test that final mount point presence state is as expected. + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) { + base::FilePath found_path; + EXPECT_EQ(kTestCases[i].registered_path != NULL, + mount_points->GetRegisteredPath(kTestCases[i].name, &found_path)) + << "Test case: " << i; + + if (kTestCases[i].registered_path) { + base::FilePath expected_path(kTestCases[i].registered_path); + EXPECT_EQ(expected_path.NormalizePathSeparators(), found_path); + } + } +} + +TEST(ExternalMountPointsTest, GetVirtualPath) { + scoped_refptr<fileapi::ExternalMountPoints> mount_points( + fileapi::ExternalMountPoints::CreateRefCounted()); + + mount_points->RegisterFileSystem("c", + fileapi::kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/a/b/c"))); + // Note that "/a/b/c" < "/a/b/c(1)" < "/a/b/c/". + mount_points->RegisterFileSystem("c(1)", + fileapi::kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/a/b/c(1)"))); + mount_points->RegisterFileSystem("x", + fileapi::kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/z/y/x"))); + mount_points->RegisterFileSystem("o", + fileapi::kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/m/n/o"))); + // A mount point whose name does not match its path base name. + mount_points->RegisterFileSystem("mount", + fileapi::kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/root/foo"))); + // A mount point with an empty path. + mount_points->RegisterFileSystem("empty_path", + fileapi::kFileSystemTypeNativeLocal, + base::FilePath()); + + struct TestCase { + const base::FilePath::CharType* const local_path; + bool success; + const base::FilePath::CharType* const virtual_path; + }; + + const TestCase kTestCases[] = { + // Empty path. + { FPL(""), false, FPL("") }, + // No registered mount point (but is parent to a mount point). + { DRIVE FPL("/a/b"), false, FPL("") }, + // No registered mount point (but is parent to a mount point). + { DRIVE FPL("/z/y"), false, FPL("") }, + // No registered mount point (but is parent to a mount point). + { DRIVE FPL("/m/n"), false, FPL("") }, + // No registered mount point. + { DRIVE FPL("/foo/mount"), false, FPL("") }, + // An existing mount point path is substring. + { DRIVE FPL("/a/b/c1"), false, FPL("") }, + // No leading /. + { DRIVE FPL("a/b/c"), false, FPL("") }, + // Sibling to a root path. + { DRIVE FPL("/a/b/d/e"), false, FPL("") }, + // Sibling to a root path. + { DRIVE FPL("/z/y/v/u"), false, FPL("") }, + // Sibling to a root path. + { DRIVE FPL("/m/n/p/q"), false, FPL("") }, + // Mount point root path. + { DRIVE FPL("/a/b/c"), true, FPL("c") }, + // Mount point root path. + { DRIVE FPL("/z/y/x"), true, FPL("x") }, + // Mount point root path. + { DRIVE FPL("/m/n/o"), true, FPL("o") }, + // Mount point child path. + { DRIVE FPL("/a/b/c/d/e"), true, FPL("c/d/e") }, + // Mount point child path. + { DRIVE FPL("/z/y/x/v/u"), true, FPL("x/v/u") }, + // Mount point child path. + { DRIVE FPL("/m/n/o/p/q"), true, FPL("o/p/q") }, + // Name doesn't match mount point path base name. + { DRIVE FPL("/root/foo/a/b/c"), true, FPL("mount/a/b/c") }, + { DRIVE FPL("/root/foo"), true, FPL("mount") }, + // Mount point contains character whose ASCII code is smaller than file path + // separator's. + { DRIVE FPL("/a/b/c(1)/d/e"), true, FPL("c(1)/d/e") }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + // Path with win separators mixed in. + { DRIVE FPL("/a\\b\\c/d"), true, FPL("c/d") }, +#endif + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) { + // Initialize virtual path with a value. + base::FilePath virtual_path(DRIVE FPL("/mount")); + base::FilePath local_path(kTestCases[i].local_path); + EXPECT_EQ(kTestCases[i].success, + mount_points->GetVirtualPath(local_path, &virtual_path)) + << "Resolving " << kTestCases[i].local_path; + + // There are no guarantees for |virtual_path| value if |GetVirtualPath| + // fails. + if (!kTestCases[i].success) + continue; + + base::FilePath expected_virtual_path(kTestCases[i].virtual_path); + EXPECT_EQ(expected_virtual_path.NormalizePathSeparators(), virtual_path) + << "Resolving " << kTestCases[i].local_path; + } +} + +TEST(ExternalMountPointsTest, HandlesFileSystemMountType) { + scoped_refptr<fileapi::ExternalMountPoints> mount_points( + fileapi::ExternalMountPoints::CreateRefCounted()); + + const GURL test_origin("http://chromium.org"); + const base::FilePath test_path(FPL("/mount")); + + // Should handle External File System. + EXPECT_TRUE(mount_points->HandlesFileSystemMountType( + fileapi::kFileSystemTypeExternal)); + + // Shouldn't handle the rest. + EXPECT_FALSE(mount_points->HandlesFileSystemMountType( + fileapi::kFileSystemTypeIsolated)); + EXPECT_FALSE(mount_points->HandlesFileSystemMountType( + fileapi::kFileSystemTypeTemporary)); + EXPECT_FALSE(mount_points->HandlesFileSystemMountType( + fileapi::kFileSystemTypePersistent)); + EXPECT_FALSE(mount_points->HandlesFileSystemMountType( + fileapi::kFileSystemTypeTest)); + // Not even if it's external subtype. + EXPECT_FALSE(mount_points->HandlesFileSystemMountType( + fileapi::kFileSystemTypeNativeLocal)); + EXPECT_FALSE(mount_points->HandlesFileSystemMountType( + fileapi::kFileSystemTypeRestrictedNativeLocal)); + EXPECT_FALSE(mount_points->HandlesFileSystemMountType( + fileapi::kFileSystemTypeDrive)); + EXPECT_FALSE(mount_points->HandlesFileSystemMountType( + fileapi::kFileSystemTypeSyncable)); +} + +TEST(ExternalMountPointsTest, CreateCrackedFileSystemURL) { + scoped_refptr<fileapi::ExternalMountPoints> mount_points( + fileapi::ExternalMountPoints::CreateRefCounted()); + + const GURL kTestOrigin("http://chromium.org"); + + mount_points->RegisterFileSystem("c", + fileapi::kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/a/b/c"))); + mount_points->RegisterFileSystem("c(1)", + fileapi::kFileSystemTypeDrive, + base::FilePath(DRIVE FPL("/a/b/c(1)"))); + mount_points->RegisterFileSystem("empty_path", + fileapi::kFileSystemTypeSyncable, + base::FilePath()); + mount_points->RegisterFileSystem("mount", + fileapi::kFileSystemTypeDrive, + base::FilePath(DRIVE FPL("/root"))); + + // Try cracking invalid GURL. + FileSystemURL invalid = mount_points->CrackURL(GURL("http://chromium.og")); + EXPECT_FALSE(invalid.is_valid()); + + // Try cracking isolated path. + FileSystemURL isolated = mount_points->CreateCrackedFileSystemURL( + kTestOrigin, fileapi::kFileSystemTypeIsolated, base::FilePath(FPL("c"))); + EXPECT_FALSE(isolated.is_valid()); + + // Try native local which is not cracked. + FileSystemURL native_local = mount_points->CreateCrackedFileSystemURL( + kTestOrigin, fileapi::kFileSystemTypeNativeLocal, base::FilePath(FPL("c"))); + EXPECT_FALSE(native_local.is_valid()); + + struct TestCase { + const base::FilePath::CharType* const path; + bool expect_valid; + fileapi::FileSystemType expect_type; + const base::FilePath::CharType* const expect_path; + const char* const expect_fs_id; + }; + + const TestCase kTestCases[] = { + { FPL("c/d/e"), + true, fileapi::kFileSystemTypeNativeLocal, DRIVE FPL("/a/b/c/d/e"), "c" }, + { FPL("c(1)/d/e"), + true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/a/b/c(1)/d/e"), "c(1)" }, + { FPL("c(1)"), + true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/a/b/c(1)"), "c(1)" }, + { FPL("empty_path/a"), + true, fileapi::kFileSystemTypeSyncable, FPL("a"), "empty_path" }, + { FPL("empty_path"), + true, fileapi::kFileSystemTypeSyncable, FPL(""), "empty_path" }, + { FPL("mount/a/b"), + true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/root/a/b"), "mount" }, + { FPL("mount"), + true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/root"), "mount" }, + { FPL("cc"), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, + { FPL(""), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, + { FPL(".."), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, + // Absolte paths. + { FPL("/c/d/e"), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, + { FPL("/c(1)/d/e"), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, + { FPL("/empty_path"), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, + // PAth references parent. + { FPL("c/d/../e"), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, + { FPL("/empty_path/a/../b"), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("c/d\\e"), + true, fileapi::kFileSystemTypeNativeLocal, DRIVE FPL("/a/b/c/d/e"), "c" }, + { FPL("mount\\a\\b"), + true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/root/a/b"), "mount" }, +#endif + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) { + FileSystemURL cracked = mount_points->CreateCrackedFileSystemURL( + kTestOrigin, + fileapi::kFileSystemTypeExternal, + base::FilePath(kTestCases[i].path)); + + EXPECT_EQ(kTestCases[i].expect_valid, cracked.is_valid()) + << "Test case index: " << i; + + if (!kTestCases[i].expect_valid) + continue; + + EXPECT_EQ(kTestOrigin, cracked.origin()) + << "Test case index: " << i; + EXPECT_EQ(kTestCases[i].expect_type, cracked.type()) + << "Test case index: " << i; + EXPECT_EQ(base::FilePath(kTestCases[i].expect_path).NormalizePathSeparators(), + cracked.path()) + << "Test case index: " << i; + EXPECT_EQ(base::FilePath(kTestCases[i].path).NormalizePathSeparators(), + cracked.virtual_path()) + << "Test case index: " << i; + EXPECT_EQ(kTestCases[i].expect_fs_id, cracked.filesystem_id()) + << "Test case index: " << i; + EXPECT_EQ(fileapi::kFileSystemTypeExternal, cracked.mount_type()) + << "Test case index: " << i; + } +} + +TEST(ExternalMountPointsTest, CrackVirtualPath) { + scoped_refptr<fileapi::ExternalMountPoints> mount_points( + fileapi::ExternalMountPoints::CreateRefCounted()); + + const GURL kTestOrigin("http://chromium.org"); + + mount_points->RegisterFileSystem("c", + fileapi::kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/a/b/c"))); + mount_points->RegisterFileSystem("c(1)", + fileapi::kFileSystemTypeDrive, + base::FilePath(DRIVE FPL("/a/b/c(1)"))); + mount_points->RegisterFileSystem("empty_path", + fileapi::kFileSystemTypeSyncable, + base::FilePath()); + mount_points->RegisterFileSystem("mount", + fileapi::kFileSystemTypeDrive, + base::FilePath(DRIVE FPL("/root"))); + + struct TestCase { + const base::FilePath::CharType* const path; + bool expect_valid; + fileapi::FileSystemType expect_type; + const base::FilePath::CharType* const expect_path; + const char* const expect_name; + }; + + const TestCase kTestCases[] = { + { FPL("c/d/e"), + true, fileapi::kFileSystemTypeNativeLocal, DRIVE FPL("/a/b/c/d/e"), "c" }, + { FPL("c(1)/d/e"), + true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/a/b/c(1)/d/e"), "c(1)" }, + { FPL("c(1)"), + true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/a/b/c(1)"), "c(1)" }, + { FPL("empty_path/a"), + true, fileapi::kFileSystemTypeSyncable, FPL("a"), "empty_path" }, + { FPL("empty_path"), + true, fileapi::kFileSystemTypeSyncable, FPL(""), "empty_path" }, + { FPL("mount/a/b"), + true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/root/a/b"), "mount" }, + { FPL("mount"), + true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/root"), "mount" }, + { FPL("cc"), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, + { FPL(""), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, + { FPL(".."), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, + // Absolte paths. + { FPL("/c/d/e"), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, + { FPL("/c(1)/d/e"), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, + { FPL("/empty_path"), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, + // PAth references parent. + { FPL("c/d/../e"), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, + { FPL("/empty_path/a/../b"), + false, fileapi::kFileSystemTypeUnknown, FPL(""), "" }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + { FPL("c/d\\e"), + true, fileapi::kFileSystemTypeNativeLocal, DRIVE FPL("/a/b/c/d/e"), "c" }, + { FPL("mount\\a\\b"), + true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/root/a/b"), "mount" }, +#endif + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) { + std::string cracked_name; + fileapi::FileSystemType cracked_type; + base::FilePath cracked_path; + EXPECT_EQ(kTestCases[i].expect_valid, + mount_points->CrackVirtualPath(base::FilePath(kTestCases[i].path), + &cracked_name, &cracked_type, &cracked_path)) + << "Test case index: " << i; + + if (!kTestCases[i].expect_valid) + continue; + + EXPECT_EQ(kTestCases[i].expect_type, cracked_type) + << "Test case index: " << i; + EXPECT_EQ(base::FilePath(kTestCases[i].expect_path).NormalizePathSeparators(), + cracked_path) + << "Test case index: " << i; + EXPECT_EQ(kTestCases[i].expect_name, cracked_name) + << "Test case index: " << i; + } +} + +} // namespace + diff --git a/chromium/webkit/browser/fileapi/file_observers.h b/chromium/webkit/browser/fileapi/file_observers.h new file mode 100644 index 00000000000..c791503d82f --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_observers.h @@ -0,0 +1,83 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_OBSERVERS_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_OBSERVERS_H_ + +#include "base/basictypes.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +// TODO(kinuko): Split this file into per-observer multiple files. + +namespace fileapi { + +class FileSystemURL; + +// An abstract interface to observe update operations. +// +// OnStartUpdate and OnEndUpdate are called once for each target url +// before and after following operations regardless of whether the operation +// is made recursively or not (i.e. StartUpdate() will be called only once +// for destination url regardless of whether it is recursive copy or not): +// CreateFile(), CreateDirectory(), +// Copy() (destination only), +// Move() (both for source and destination), +// Remove(), Write(), Truncate(), TouchFile() +// +// OnUpdate() is called each time the |url| is updated but works only for +// sandboxed files (where usage is tracked). +class WEBKIT_STORAGE_BROWSER_EXPORT FileUpdateObserver { + public: + FileUpdateObserver() {} + virtual ~FileUpdateObserver() {} + + virtual void OnStartUpdate(const FileSystemURL& url) = 0; + virtual void OnUpdate(const FileSystemURL& url, int64 delta) = 0; + virtual void OnEndUpdate(const FileSystemURL& url) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(FileUpdateObserver); +}; + +// An abstract interface to observe file access. +// OnAccess is called whenever an operation reads file contents or metadata. +// (It is called only once per operation regardless of whether the operation +// is recursive or not) +class WEBKIT_STORAGE_BROWSER_EXPORT FileAccessObserver { + public: + FileAccessObserver() {} + virtual ~FileAccessObserver() {} + + virtual void OnAccess(const FileSystemURL& url) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(FileAccessObserver); +}; + +// An abstract interface to observe file changes. +// Each method of this class is called once per file/directory is created, +// removed or modified. For recursive operations each method is called for +// each subdirectory/subfile. Currently ChangeObserver is only supported +// by the local sandbox file system. +class WEBKIT_STORAGE_BROWSER_EXPORT FileChangeObserver { + public: + FileChangeObserver() {} + virtual ~FileChangeObserver() {} + + virtual void OnCreateFile(const FileSystemURL& url) = 0; + virtual void OnCreateFileFrom(const FileSystemURL& url, + const FileSystemURL& src) = 0; + virtual void OnRemoveFile(const FileSystemURL& url) = 0; + virtual void OnModifyFile(const FileSystemURL& url) = 0; + + virtual void OnCreateDirectory(const FileSystemURL& url) = 0; + virtual void OnRemoveDirectory(const FileSystemURL& url) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(FileChangeObserver); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_OBSERVERS_H_ diff --git a/chromium/webkit/browser/fileapi/file_permission_policy.cc b/chromium/webkit/browser/fileapi/file_permission_policy.cc new file mode 100644 index 00000000000..164548791db --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_permission_policy.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_permission_policy.h" + +#include "base/platform_file.h" + +namespace fileapi { + +const int kReadFilePermissions = base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_EXCLUSIVE_READ | + base::PLATFORM_FILE_ASYNC; + +const int kWriteFilePermissions = base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_WRITE | + base::PLATFORM_FILE_EXCLUSIVE_WRITE | + base::PLATFORM_FILE_ASYNC | + base::PLATFORM_FILE_WRITE_ATTRIBUTES; + +const int kCreateFilePermissions = base::PLATFORM_FILE_CREATE; + +const int kOpenFilePermissions = base::PLATFORM_FILE_CREATE | + base::PLATFORM_FILE_OPEN_ALWAYS | + base::PLATFORM_FILE_CREATE_ALWAYS | + base::PLATFORM_FILE_OPEN_TRUNCATED | + base::PLATFORM_FILE_WRITE | + base::PLATFORM_FILE_EXCLUSIVE_WRITE | + base::PLATFORM_FILE_DELETE_ON_CLOSE | + base::PLATFORM_FILE_WRITE_ATTRIBUTES; + + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_permission_policy.h b/chromium/webkit/browser/fileapi/file_permission_policy.h new file mode 100644 index 00000000000..3975c3ccace --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_permission_policy.h @@ -0,0 +1,33 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_PERMISSION_POLICY_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_PERMISSION_POLICY_H_ + +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace fileapi { + +WEBKIT_STORAGE_BROWSER_EXPORT extern const int kReadFilePermissions; +WEBKIT_STORAGE_BROWSER_EXPORT extern const int kWriteFilePermissions; +WEBKIT_STORAGE_BROWSER_EXPORT extern const int kCreateFilePermissions; +WEBKIT_STORAGE_BROWSER_EXPORT extern const int kOpenFilePermissions; + +enum FilePermissionPolicy { + // Any access should be always denied. + FILE_PERMISSION_ALWAYS_DENY = 0x0, + + // Access is sandboxed, no extra permission check is necessary. + FILE_PERMISSION_SANDBOX = 1 << 0, + + // Access should be restricted to read-only. + FILE_PERMISSION_READ_ONLY = 1 << 1, + + // Access should be examined by per-file permission policy. + FILE_PERMISSION_USE_FILE_PERMISSION = 1 << 2, +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_PERMISSION_POLICY_H_ diff --git a/chromium/webkit/browser/fileapi/file_stream_writer.h b/chromium/webkit/browser/fileapi/file_stream_writer.h new file mode 100644 index 00000000000..7262cdf6ccd --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_stream_writer.h @@ -0,0 +1,72 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_STREAM_WRITER_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_STREAM_WRITER_H_ + +#include "base/basictypes.h" +#include "net/base/completion_callback.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace net { +class IOBuffer; +} + +namespace fileapi { + +// A generic interface for writing to a file-like object. +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileStreamWriter { + public: + // Closes the file. If there's an in-flight operation, it is canceled (i.e., + // the callback function associated with the operation is not called). + virtual ~FileStreamWriter() {} + + // Writes to the current cursor position asynchronously. + // + // Up to buf_len bytes will be written. (In other words, partial + // writes are allowed.) If the write completed synchronously, it returns + // the number of bytes written. If the operation could not be performed, it + // returns an error code. Otherwise, net::ERR_IO_PENDING is returned, and the + // callback will be run on the thread where Write() was called when the write + // has completed. + // + // This errors out (either synchronously or via callback) with: + // net::ERR_FILE_NOT_FOUND: When the target file is not found. + // net::ERR_ACCESS_DENIED: When the target file is a directory or + // the writer has no permission on the file. + // net::ERR_FILE_NO_SPACE: When the write will result in out of quota + // or there is not enough room left on the disk. + // + // It is invalid to call Write while there is an in-flight async operation. + virtual int Write(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) = 0; + + // Cancels an in-flight async operation. + // + // If the cancel is finished synchronously, it returns net::OK. If the + // cancel could not be performed, it returns an error code. Especially when + // there is no in-flight operation, net::ERR_UNEXPECTED is returned. + // Otherwise, net::ERR_IO_PENDING is returned, and the callback will be run on + // the thread where Cancel() was called when the cancel has completed. It is + // invalid to call Cancel() more than once on the same async operation. + // + // In either case, the callback function passed to the in-flight async + // operation is dismissed immediately when Cancel() is called, and thus + // will never be called. + virtual int Cancel(const net::CompletionCallback& callback) = 0; + + // Flushes the data written so far. + // + // If the flush finished synchronously, it return net::OK. If the flush could + // not be performed, it returns an error code. Otherwise, net::ERR_IO_PENDING + // is returned, and the callback will be run on the thread where Flush() was + // called when the flush has completed. + // + // It is invalid to call Flush while there is an in-flight async operation. + virtual int Flush(const net::CompletionCallback& callback) = 0; +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_STREAM_WRITER_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_backend.h b/chromium/webkit/browser/fileapi/file_system_backend.h new file mode 100644 index 00000000000..b368db6c928 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_backend.h @@ -0,0 +1,155 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_BACKEND_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_BACKEND_H_ + +#include <string> +#include <vector> + +#include "base/callback_forward.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/platform_file.h" +#include "webkit/browser/fileapi/file_permission_policy.h" +#include "webkit/browser/fileapi/open_file_system_mode.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/fileapi/file_system_types.h" + +class GURL; + +namespace webkit_blob { +class FileStreamReader; +} + +namespace fileapi { + +class AsyncFileUtil; +class CopyOrMoveFileValidatorFactory; +class FileSystemURL; +class FileStreamWriter; +class FileSystemContext; +class FileSystemFileUtil; +class FileSystemOperation; +class FileSystemQuotaUtil; + +// An interface for defining a file system backend. +// +// NOTE: when you implement a new FileSystemBackend for your own +// FileSystem module, please contact to kinuko@chromium.org. +// +class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemBackend { + public: + // Callback for InitializeFileSystem. + typedef base::Callback<void(const GURL& root_url, + const std::string& name, + base::PlatformFileError error)> + OpenFileSystemCallback; + virtual ~FileSystemBackend() {} + + // Returns true if this filesystem backend can handle |type|. + // One filesystem backend may be able to handle multiple filesystem types. + virtual bool CanHandleType(FileSystemType type) const = 0; + + // This method is called right after the backend is registered in the + // FileSystemContext and before any other methods are called. Each backend can + // do additional initialization which depends on FileSystemContext here. + virtual void Initialize(FileSystemContext* context) = 0; + + // Opens the filesystem for the given |origin_url| and |type|. + // This verifies if it is allowed to request (or create) the filesystem + // and if it can access (or create) the root directory. + // If |mode| is CREATE_IF_NONEXISTENT calling this may also create + // the root directory (and/or related database entries etc) for + // the filesystem if it doesn't exist. + virtual void OpenFileSystem( + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) = 0; + + // Returns the specialized FileSystemFileUtil for this backend. + // It is ok to return NULL if the filesystem doesn't support synchronous + // version of FileUtil. + virtual FileSystemFileUtil* GetFileUtil(FileSystemType type) = 0; + + // Returns the specialized AsyncFileUtil for this backend. + virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) = 0; + + // Returns the specialized CopyOrMoveFileValidatorFactory for this backend + // and |type|. If |error_code| is PLATFORM_FILE_OK and the result is NULL, + // then no validator is required. + virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory( + FileSystemType type, base::PlatformFileError* error_code) = 0; + + // Returns a new instance of the specialized FileSystemOperation for this + // backend based on the given triplet of |origin_url|, |file_system_type| + // and |virtual_path|. On failure to create a file system operation, set + // |error_code| correspondingly. + // This method is usually dispatched by + // FileSystemContext::CreateFileSystemOperation. + virtual FileSystemOperation* CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::PlatformFileError* error_code) const = 0; + + // Creates a new file stream reader for a given filesystem URL |url| with an + // offset |offset|. |expected_modification_time| specifies the expected last + // modification if the value is non-null, the reader will check the underlying + // file's actual modification time to see if the file has been modified, and + // if it does any succeeding read operations should fail with + // ERR_UPLOAD_FILE_CHANGED error. + // This method itself does *not* check if the given path exists and is a + // regular file. + virtual scoped_ptr<webkit_blob::FileStreamReader> CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const = 0; + + // Creates a new file stream writer for a given filesystem URL |url| with an + // offset |offset|. + // This method itself does *not* check if the given path exists and is a + // regular file. + virtual scoped_ptr<FileStreamWriter> CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const = 0; + + // Returns the specialized FileSystemQuotaUtil for this backend. + // This could return NULL if this backend does not support quota. + virtual FileSystemQuotaUtil* GetQuotaUtil() = 0; +}; + +// An interface to control external file system access permissions. +// TODO(satorux): Move this out of 'webkit/browser/fileapi'. crbug.com/257279 +class ExternalFileSystemBackend : public FileSystemBackend { + public: + // Returns true if |url| is allowed to be accessed. + // This is supposed to perform ExternalFileSystem-specific security + // checks. + virtual bool IsAccessAllowed(const fileapi::FileSystemURL& url) const = 0; + // Returns the list of top level directories that are exposed by this + // provider. This list is used to set appropriate child process file access + // permissions. + virtual std::vector<base::FilePath> GetRootDirectories() const = 0; + // Grants access to all external file system from extension identified with + // |extension_id|. + virtual void GrantFullAccessToExtension(const std::string& extension_id) = 0; + // Grants access to |virtual_path| from |origin_url|. + virtual void GrantFileAccessToExtension( + const std::string& extension_id, + const base::FilePath& virtual_path) = 0; + // Revokes file access from extension identified with |extension_id|. + virtual void RevokeAccessForExtension( + const std::string& extension_id) = 0; + // Gets virtual path by known filesystem path. Returns false when filesystem + // path is not exposed by this provider. + virtual bool GetVirtualPath(const base::FilePath& file_system_path, + base::FilePath* virtual_path) = 0; +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_BACKEND_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_context.cc b/chromium/webkit/browser/fileapi/file_system_context.cc new file mode 100644 index 00000000000..f2973d35fa2 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_context.cc @@ -0,0 +1,448 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_context.h" + +#include "base/bind.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/task_runner_util.h" +#include "url/gurl.h" +#include "webkit/browser/blob/file_stream_reader.h" +#include "webkit/browser/fileapi/copy_or_move_file_validator.h" +#include "webkit/browser/fileapi/external_mount_points.h" +#include "webkit/browser/fileapi/file_permission_policy.h" +#include "webkit/browser/fileapi/file_stream_writer.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" +#include "webkit/browser/fileapi/file_system_options.h" +#include "webkit/browser/fileapi/file_system_quota_client.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/isolated_context.h" +#include "webkit/browser/fileapi/isolated_file_system_backend.h" +#include "webkit/browser/fileapi/mount_points.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend.h" +#include "webkit/browser/fileapi/test_file_system_backend.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/browser/quota/special_storage_policy.h" +#include "webkit/common/fileapi/file_system_util.h" + +using quota::QuotaClient; + +namespace fileapi { + +namespace { + +QuotaClient* CreateQuotaClient( + FileSystemContext* context, + bool is_incognito) { + return new FileSystemQuotaClient(context, is_incognito); +} + +void DidOpenFileSystem( + const FileSystemContext::OpenFileSystemCallback& callback, + const GURL& filesystem_root, + const std::string& filesystem_name, + base::PlatformFileError error) { + callback.Run(error, filesystem_name, filesystem_root); +} + +} // namespace + +// static +int FileSystemContext::GetPermissionPolicy(FileSystemType type) { + switch (type) { + case kFileSystemTypeTemporary: + case kFileSystemTypePersistent: + case kFileSystemTypeSyncable: + return FILE_PERMISSION_SANDBOX; + + case kFileSystemTypeDrive: + case kFileSystemTypeNativeForPlatformApp: + case kFileSystemTypeNativeLocal: + return FILE_PERMISSION_USE_FILE_PERMISSION; + + case kFileSystemTypeRestrictedNativeLocal: + return FILE_PERMISSION_READ_ONLY | + FILE_PERMISSION_USE_FILE_PERMISSION; + + // Following types are only accessed via IsolatedFileSystem, and + // don't have their own permission policies. + case kFileSystemTypeDeviceMedia: + case kFileSystemTypeDragged: + case kFileSystemTypeForTransientFile: + case kFileSystemTypeItunes: + case kFileSystemTypeNativeMedia: + case kFileSystemTypePicasa: + return FILE_PERMISSION_ALWAYS_DENY; + + // Following types only appear as mount_type, and will not be + // queried for their permission policies. + case kFileSystemTypeIsolated: + case kFileSystemTypeExternal: + return FILE_PERMISSION_ALWAYS_DENY; + + // Following types should not be used to access files by FileAPI clients. + case kFileSystemTypeTest: + case kFileSystemTypeSyncableForInternalSync: + case kFileSystemInternalTypeEnumEnd: + case kFileSystemInternalTypeEnumStart: + case kFileSystemTypeUnknown: + return FILE_PERMISSION_ALWAYS_DENY; + } + NOTREACHED(); + return FILE_PERMISSION_ALWAYS_DENY; +} + +FileSystemContext::FileSystemContext( + base::SingleThreadTaskRunner* io_task_runner, + base::SequencedTaskRunner* file_task_runner, + ExternalMountPoints* external_mount_points, + quota::SpecialStoragePolicy* special_storage_policy, + quota::QuotaManagerProxy* quota_manager_proxy, + ScopedVector<FileSystemBackend> additional_backends, + const base::FilePath& partition_path, + const FileSystemOptions& options) + : io_task_runner_(io_task_runner), + default_file_task_runner_(file_task_runner), + quota_manager_proxy_(quota_manager_proxy), + sandbox_delegate_(new SandboxFileSystemBackendDelegate( + quota_manager_proxy, + file_task_runner, + partition_path, + special_storage_policy, + options)), + sandbox_backend_(new SandboxFileSystemBackend( + sandbox_delegate_.get())), + isolated_backend_(new IsolatedFileSystemBackend()), + additional_backends_(additional_backends.Pass()), + external_mount_points_(external_mount_points), + partition_path_(partition_path), + operation_runner_(new FileSystemOperationRunner(this)) { + if (quota_manager_proxy) { + quota_manager_proxy->RegisterClient(CreateQuotaClient( + this, options.is_incognito())); + } + + RegisterBackend(sandbox_backend_.get()); + RegisterBackend(isolated_backend_.get()); + + for (ScopedVector<FileSystemBackend>::const_iterator iter = + additional_backends_.begin(); + iter != additional_backends_.end(); ++iter) { + RegisterBackend(*iter); + } + + sandbox_backend_->Initialize(this); + isolated_backend_->Initialize(this); + for (ScopedVector<FileSystemBackend>::const_iterator iter = + additional_backends_.begin(); + iter != additional_backends_.end(); ++iter) { + (*iter)->Initialize(this); + } + + // Additional mount points must be added before regular system-wide + // mount points. + if (external_mount_points) + url_crackers_.push_back(external_mount_points); + url_crackers_.push_back(ExternalMountPoints::GetSystemInstance()); + url_crackers_.push_back(IsolatedContext::GetInstance()); +} + +bool FileSystemContext::DeleteDataForOriginOnFileThread( + const GURL& origin_url) { + DCHECK(default_file_task_runner()->RunsTasksOnCurrentThread()); + DCHECK(origin_url == origin_url.GetOrigin()); + + bool success = true; + for (FileSystemBackendMap::iterator iter = backend_map_.begin(); + iter != backend_map_.end(); + ++iter) { + FileSystemBackend* backend = iter->second; + if (!backend->GetQuotaUtil()) + continue; + if (backend->GetQuotaUtil()->DeleteOriginDataOnFileThread( + this, quota_manager_proxy(), origin_url, iter->first) + != base::PLATFORM_FILE_OK) { + // Continue the loop, but record the failure. + success = false; + } + } + + return success; +} + +void FileSystemContext::Shutdown() { + if (!io_task_runner_->RunsTasksOnCurrentThread()) { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&FileSystemContext::Shutdown, + make_scoped_refptr(this))); + return; + } + operation_runner_->Shutdown(); +} + +FileSystemQuotaUtil* +FileSystemContext::GetQuotaUtil(FileSystemType type) const { + FileSystemBackend* backend = GetFileSystemBackend(type); + if (!backend) + return NULL; + return backend->GetQuotaUtil(); +} + +AsyncFileUtil* FileSystemContext::GetAsyncFileUtil( + FileSystemType type) const { + FileSystemBackend* backend = GetFileSystemBackend(type); + if (!backend) + return NULL; + return backend->GetAsyncFileUtil(type); +} + +FileSystemFileUtil* FileSystemContext::GetFileUtil( + FileSystemType type) const { + FileSystemBackend* backend = GetFileSystemBackend(type); + if (!backend) + return NULL; + return backend->GetFileUtil(type); +} + +CopyOrMoveFileValidatorFactory* +FileSystemContext::GetCopyOrMoveFileValidatorFactory( + FileSystemType type, base::PlatformFileError* error_code) const { + DCHECK(error_code); + *error_code = base::PLATFORM_FILE_OK; + FileSystemBackend* backend = GetFileSystemBackend(type); + if (!backend) + return NULL; + return backend->GetCopyOrMoveFileValidatorFactory( + type, error_code); +} + +FileSystemBackend* FileSystemContext::GetFileSystemBackend( + FileSystemType type) const { + FileSystemBackendMap::const_iterator found = backend_map_.find(type); + if (found != backend_map_.end()) + return found->second; + NOTREACHED() << "Unknown filesystem type: " << type; + return NULL; +} + +bool FileSystemContext::IsSandboxFileSystem(FileSystemType type) const { + return GetQuotaUtil(type) != NULL; +} + +const UpdateObserverList* FileSystemContext::GetUpdateObservers( + FileSystemType type) const { + FileSystemBackend* backend = GetFileSystemBackend(type); + if (backend->GetQuotaUtil()) + return backend->GetQuotaUtil()->GetUpdateObservers(type); + return NULL; +} + +const AccessObserverList* FileSystemContext::GetAccessObservers( + FileSystemType type) const { + FileSystemBackend* backend = GetFileSystemBackend(type); + if (backend->GetQuotaUtil()) + return backend->GetQuotaUtil()->GetAccessObservers(type); + return NULL; +} + +void FileSystemContext::GetFileSystemTypes( + std::vector<FileSystemType>* types) const { + types->clear(); + for (FileSystemBackendMap::const_iterator iter = backend_map_.begin(); + iter != backend_map_.end(); ++iter) + types->push_back(iter->first); +} + +ExternalFileSystemBackend* +FileSystemContext::external_backend() const { + return static_cast<ExternalFileSystemBackend*>( + GetFileSystemBackend(kFileSystemTypeExternal)); +} + +void FileSystemContext::OpenFileSystem( + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) { + DCHECK(!callback.is_null()); + + FileSystemBackend* backend = GetFileSystemBackend(type); + if (!backend) { + callback.Run(base::PLATFORM_FILE_ERROR_SECURITY, std::string(), GURL()); + return; + } + + backend->OpenFileSystem(origin_url, type, mode, + base::Bind(&DidOpenFileSystem, callback)); +} + +void FileSystemContext::DeleteFileSystem( + const GURL& origin_url, + FileSystemType type, + const DeleteFileSystemCallback& callback) { + DCHECK(origin_url == origin_url.GetOrigin()); + FileSystemBackend* backend = GetFileSystemBackend(type); + if (!backend) { + callback.Run(base::PLATFORM_FILE_ERROR_SECURITY); + return; + } + if (!backend->GetQuotaUtil()) { + callback.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION); + return; + } + + base::PostTaskAndReplyWithResult( + default_file_task_runner(), + FROM_HERE, + // It is safe to pass Unretained(quota_util) since context owns it. + base::Bind(&FileSystemQuotaUtil::DeleteOriginDataOnFileThread, + base::Unretained(backend->GetQuotaUtil()), + make_scoped_refptr(this), + base::Unretained(quota_manager_proxy()), + origin_url, + type), + callback); +} + +scoped_ptr<webkit_blob::FileStreamReader> +FileSystemContext::CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time) { + if (!url.is_valid()) + return scoped_ptr<webkit_blob::FileStreamReader>(); + FileSystemBackend* backend = GetFileSystemBackend(url.type()); + if (!backend) + return scoped_ptr<webkit_blob::FileStreamReader>(); + return backend->CreateFileStreamReader( + url, offset, expected_modification_time, this); +} + +scoped_ptr<FileStreamWriter> FileSystemContext::CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset) { + if (!url.is_valid()) + return scoped_ptr<FileStreamWriter>(); + FileSystemBackend* backend = GetFileSystemBackend(url.type()); + if (!backend) + return scoped_ptr<FileStreamWriter>(); + return backend->CreateFileStreamWriter(url, offset, this); +} + +scoped_ptr<FileSystemOperationRunner> +FileSystemContext::CreateFileSystemOperationRunner() { + return make_scoped_ptr(new FileSystemOperationRunner(this)); +} + +FileSystemURL FileSystemContext::CrackURL(const GURL& url) const { + return CrackFileSystemURL(FileSystemURL(url)); +} + +FileSystemURL FileSystemContext::CreateCrackedFileSystemURL( + const GURL& origin, + FileSystemType type, + const base::FilePath& path) const { + return CrackFileSystemURL(FileSystemURL(origin, type, path)); +} + +#if defined(OS_CHROMEOS) && defined(GOOGLE_CHROME_BUILD) +void FileSystemContext::EnableTemporaryFileSystemInIncognito() { + sandbox_backend_->set_enable_temporary_file_system_in_incognito(true); +} +#endif + +FileSystemContext::~FileSystemContext() { +} + +void FileSystemContext::DeleteOnCorrectThread() const { + if (!io_task_runner_->RunsTasksOnCurrentThread() && + io_task_runner_->DeleteSoon(FROM_HERE, this)) { + return; + } + delete this; +} + +FileSystemOperation* FileSystemContext::CreateFileSystemOperation( + const FileSystemURL& url, base::PlatformFileError* error_code) { + if (!url.is_valid()) { + if (error_code) + *error_code = base::PLATFORM_FILE_ERROR_INVALID_URL; + return NULL; + } + + FileSystemBackend* backend = GetFileSystemBackend(url.type()); + if (!backend) { + if (error_code) + *error_code = base::PLATFORM_FILE_ERROR_FAILED; + return NULL; + } + + base::PlatformFileError fs_error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + backend->CreateFileSystemOperation(url, this, &fs_error); + + if (error_code) + *error_code = fs_error; + return operation; +} + +FileSystemURL FileSystemContext::CrackFileSystemURL( + const FileSystemURL& url) const { + if (!url.is_valid()) + return FileSystemURL(); + + // The returned value in case there is no crackers which can crack the url. + // This is valid situation for non isolated/external file systems. + FileSystemURL current = url; + + // File system may be mounted multiple times (e.g., an isolated filesystem on + // top of an external filesystem). Hence cracking needs to be iterated. + for (;;) { + FileSystemURL cracked = current; + for (size_t i = 0; i < url_crackers_.size(); ++i) { + if (!url_crackers_[i]->HandlesFileSystemMountType(current.type())) + continue; + cracked = url_crackers_[i]->CrackFileSystemURL(current); + if (cracked.is_valid()) + break; + } + if (cracked == current) + break; + current = cracked; + } + return current; +} + +void FileSystemContext::RegisterBackend( + FileSystemBackend* backend) { + const FileSystemType mount_types[] = { + kFileSystemTypeTemporary, + kFileSystemTypePersistent, + kFileSystemTypeIsolated, + kFileSystemTypeExternal, + }; + // Register file system backends for public mount types. + for (size_t j = 0; j < ARRAYSIZE_UNSAFE(mount_types); ++j) { + if (backend->CanHandleType(mount_types[j])) { + const bool inserted = backend_map_.insert( + std::make_pair(mount_types[j], backend)).second; + DCHECK(inserted); + } + } + // Register file system backends for internal types. + for (int t = kFileSystemInternalTypeEnumStart + 1; + t < kFileSystemInternalTypeEnumEnd; ++t) { + FileSystemType type = static_cast<FileSystemType>(t); + if (backend->CanHandleType(type)) { + const bool inserted = backend_map_.insert( + std::make_pair(type, backend)).second; + DCHECK(inserted); + } + } +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_context.h b/chromium/webkit/browser/fileapi/file_system_context.h new file mode 100644 index 00000000000..528f7a26021 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_context.h @@ -0,0 +1,329 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_CONTEXT_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_CONTEXT_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/platform_file.h" +#include "base/sequenced_task_runner_helpers.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/open_file_system_mode.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h" +#include "webkit/browser/fileapi/task_runner_bound_observer_list.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/fileapi/file_system_types.h" + +namespace base { +class FilePath; +class SequencedTaskRunner; +class SingleThreadTaskRunner; +} + +namespace chrome { +class NativeMediaFileUtilTest; +} + +namespace quota { +class QuotaManagerProxy; +class SpecialStoragePolicy; +} + +namespace webkit_blob { +class BlobURLRequestJobTest; +class FileStreamReader; +} + +namespace fileapi { + +class AsyncFileUtil; +class CopyOrMoveFileValidatorFactory; +class ExternalFileSystemBackend; +class ExternalMountPoints; +class FileStreamWriter; +class FileSystemFileUtil; +class FileSystemBackend; +class FileSystemOperation; +class FileSystemOperationRunner; +class FileSystemOptions; +class FileSystemQuotaUtil; +class FileSystemURL; +class IsolatedFileSystemBackend; +class MountPoints; +class SandboxFileSystemBackend; + +struct DefaultContextDeleter; + +// This class keeps and provides a file system context for FileSystem API. +// An instance of this class is created and owned by profile. +class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemContext + : public base::RefCountedThreadSafe<FileSystemContext, + DefaultContextDeleter> { + public: + // Returns file permission policy we should apply for the given |type|. + // The return value must be bitwise-or'd of FilePermissionPolicy. + // + // Note: if a part of a filesystem is returned via 'Isolated' mount point, + // its per-filesystem permission overrides the underlying filesystem's + // permission policy. + static int GetPermissionPolicy(FileSystemType type); + + // file_task_runner is used as default TaskRunner. + // Unless a FileSystemBackend is overridden in CreateFileSystemOperation, + // it is used for all file operations and file related meta operations. + // The code assumes that file_task_runner->RunsTasksOnCurrentThread() + // returns false if the current task is not running on the thread that allows + // blocking file operations (like SequencedWorkerPool implementation does). + // + // |external_mount_points| contains non-system external mount points available + // in the context. If not NULL, it will be used during URL cracking. + // |external_mount_points| may be NULL only on platforms different from + // ChromeOS (i.e. platforms that don't use external_mount_point_provider). + // + // |additional_backends| are added to the internal backend map + // to serve filesystem requests for non-regular types. + // If none is given, this context only handles HTML5 Sandbox FileSystem + // and Drag-and-drop Isolated FileSystem requests. + FileSystemContext( + base::SingleThreadTaskRunner* io_task_runner, + base::SequencedTaskRunner* file_task_runner, + ExternalMountPoints* external_mount_points, + quota::SpecialStoragePolicy* special_storage_policy, + quota::QuotaManagerProxy* quota_manager_proxy, + ScopedVector<FileSystemBackend> additional_backends, + const base::FilePath& partition_path, + const FileSystemOptions& options); + + bool DeleteDataForOriginOnFileThread(const GURL& origin_url); + + quota::QuotaManagerProxy* quota_manager_proxy() const { + return quota_manager_proxy_.get(); + } + + // Discards inflight operations in the operation runner. + void Shutdown(); + + // Returns a quota util for a given filesystem type. This may + // return NULL if the type does not support the usage tracking or + // it is not a quota-managed storage. + FileSystemQuotaUtil* GetQuotaUtil(FileSystemType type) const; + + // Returns the appropriate AsyncFileUtil instance for the given |type|. + AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) const; + + // Returns the appropriate FileUtil instance for the given |type|. + // This may return NULL if it is given an invalid type or the filesystem + // does not support synchronous file operations. + FileSystemFileUtil* GetFileUtil(FileSystemType type) const; + + // Returns the appropriate CopyOrMoveFileValidatorFactory for the given + // |type|. If |error_code| is PLATFORM_FILE_OK and the result is NULL, + // then no validator is required. + CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory( + FileSystemType type, base::PlatformFileError* error_code) const; + + // Returns the file system backend instance for the given |type|. + // This may return NULL if it is given an invalid or unsupported filesystem + // type. + FileSystemBackend* GetFileSystemBackend( + FileSystemType type) const; + + // Returns true for sandboxed filesystems. Currently this does + // the same as GetQuotaUtil(type) != NULL. (In an assumption that + // all sandboxed filesystems must cooperate with QuotaManager so that + // they can get deleted) + bool IsSandboxFileSystem(FileSystemType type) const; + + // Returns observers for the given filesystem type. + const UpdateObserverList* GetUpdateObservers(FileSystemType type) const; + const AccessObserverList* GetAccessObservers(FileSystemType type) const; + + // Returns all registered filesystem types. + void GetFileSystemTypes(std::vector<FileSystemType>* types) const; + + // Returns a FileSystemBackend instance for external filesystem + // type, which is used only by chromeos for now. This is equivalent to + // calling GetFileSystemBackend(kFileSystemTypeExternal). + ExternalFileSystemBackend* external_backend() const; + + // Used for OpenFileSystem. + typedef base::Callback<void(base::PlatformFileError result, + const std::string& name, + const GURL& root)> OpenFileSystemCallback; + + // Used for DeleteFileSystem. + typedef base::Callback<void(base::PlatformFileError result)> + DeleteFileSystemCallback; + + // Opens the filesystem for the given |origin_url| and |type|, and dispatches + // |callback| on completion. + // If |create| is true this may actually set up a filesystem instance + // (e.g. by creating the root directory or initializing the database + // entry etc). + void OpenFileSystem( + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback); + + // Deletes the filesystem for the given |origin_url| and |type|. This should + // be called on the IO Thread. + void DeleteFileSystem( + const GURL& origin_url, + FileSystemType type, + const DeleteFileSystemCallback& callback); + + // Creates new FileStreamReader instance to read a file pointed by the given + // filesystem URL |url| starting from |offset|. |expected_modification_time| + // specifies the expected last modification if the value is non-null, the + // reader will check the underlying file's actual modification time to see if + // the file has been modified, and if it does any succeeding read operations + // should fail with ERR_UPLOAD_FILE_CHANGED error. + // This method internally cracks the |url|, get an appropriate + // FileSystemBackend for the URL and call the backend's CreateFileReader. + // The resolved FileSystemBackend could perform further specialization + // depending on the filesystem type pointed by the |url|. + scoped_ptr<webkit_blob::FileStreamReader> CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time); + + // Creates new FileStreamWriter instance to write into a file pointed by + // |url| from |offset|. + scoped_ptr<FileStreamWriter> CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset); + + // Creates a new FileSystemOperationRunner. + scoped_ptr<FileSystemOperationRunner> CreateFileSystemOperationRunner(); + + base::SequencedTaskRunner* default_file_task_runner() { + return default_file_task_runner_.get(); + } + + FileSystemOperationRunner* operation_runner() { + return operation_runner_.get(); + } + + const base::FilePath& partition_path() const { return partition_path_; } + + // Same as |CrackFileSystemURL|, but cracks FileSystemURL created from |url|. + FileSystemURL CrackURL(const GURL& url) const; + // Same as |CrackFileSystemURL|, but cracks FileSystemURL created from method + // arguments. + FileSystemURL CreateCrackedFileSystemURL(const GURL& origin, + FileSystemType type, + const base::FilePath& path) const; + +#if defined(OS_CHROMEOS) && defined(GOOGLE_CHROME_BUILD) + // Used only on ChromeOS for now. + void EnableTemporaryFileSystemInIncognito(); +#endif + + SandboxFileSystemBackendDelegate* sandbox_delegate() { + return sandbox_delegate_.get(); + } + + private: + typedef std::map<FileSystemType, FileSystemBackend*> + FileSystemBackendMap; + + // For CreateFileSystemOperation. + friend class FileSystemOperationRunner; + + // For sandbox_backend(). + friend class SandboxFileSystemTestHelper; + + // Deleters. + friend struct DefaultContextDeleter; + friend class base::DeleteHelper<FileSystemContext>; + friend class base::RefCountedThreadSafe<FileSystemContext, + DefaultContextDeleter>; + ~FileSystemContext(); + + void DeleteOnCorrectThread() const; + + // Creates a new FileSystemOperation instance by getting an appropriate + // FileSystemBackend for |url| and calling the backend's corresponding + // CreateFileSystemOperation method. + // The resolved FileSystemBackend could perform further specialization + // depending on the filesystem type pointed by the |url|. + // + // Called by FileSystemOperationRunner. + FileSystemOperation* CreateFileSystemOperation( + const FileSystemURL& url, + base::PlatformFileError* error_code); + + // For non-cracked isolated and external mount points, returns a FileSystemURL + // created by cracking |url|. The url is cracked using MountPoints registered + // as |url_crackers_|. If the url cannot be cracked, returns invalid + // FileSystemURL. + // + // If the original url does not point to an isolated or external filesystem, + // returns the original url, without attempting to crack it. + FileSystemURL CrackFileSystemURL(const FileSystemURL& url) const; + + // For initial backend_map construction. This must be called only from + // the constructor. + void RegisterBackend(FileSystemBackend* backend); + + // Returns a FileSystemBackend, used only by test code. + SandboxFileSystemBackend* sandbox_backend() const { + return sandbox_backend_.get(); + } + + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; + scoped_refptr<base::SequencedTaskRunner> default_file_task_runner_; + + scoped_refptr<quota::QuotaManagerProxy> quota_manager_proxy_; + + scoped_ptr<SandboxFileSystemBackendDelegate> sandbox_delegate_; + + // Regular file system backends. + scoped_ptr<SandboxFileSystemBackend> sandbox_backend_; + scoped_ptr<IsolatedFileSystemBackend> isolated_backend_; + + // Additional file system backends. + ScopedVector<FileSystemBackend> additional_backends_; + + // Registered file system backends. + // The map must be constructed in the constructor since it can be accessed + // on multiple threads. + // This map itself doesn't retain each backend's ownership; ownerships + // of the backends are held by additional_backends_ or other scoped_ptr + // backend fields. + FileSystemBackendMap backend_map_; + + // External mount points visible in the file system context (excluding system + // external mount points). + scoped_refptr<ExternalMountPoints> external_mount_points_; + + // MountPoints used to crack FileSystemURLs. The MountPoints are ordered + // in order they should try to crack a FileSystemURL. + std::vector<MountPoints*> url_crackers_; + + // The base path of the storage partition for this context. + const base::FilePath partition_path_; + + scoped_ptr<FileSystemOperationRunner> operation_runner_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(FileSystemContext); +}; + +struct DefaultContextDeleter { + static void Destruct(const FileSystemContext* context) { + context->DeleteOnCorrectThread(); + } +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_CONTEXT_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_context_unittest.cc b/chromium/webkit/browser/fileapi/file_system_context_unittest.cc new file mode 100644 index 00000000000..92256af625b --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_context_unittest.cc @@ -0,0 +1,328 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_context.h" + +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/stringprintf.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/external_mount_points.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/isolated_context.h" +#include "webkit/browser/fileapi/mock_file_system_options.h" +#include "webkit/browser/quota/mock_quota_manager.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" + +#define FPL(x) FILE_PATH_LITERAL(x) + +#if defined(FILE_PATH_USES_DRIVE_LETTERS) +#define DRIVE FPL("C:") +#else +#define DRIVE +#endif + +namespace fileapi { + +namespace { + +const char kTestOrigin[] = "http://chromium.org/"; +const base::FilePath::CharType kVirtualPathNoRoot[] = FPL("root/file"); + +GURL CreateRawFileSystemURL(const std::string& type_str, + const std::string& fs_id) { + std::string url_str = base::StringPrintf( + "filesystem:http://chromium.org/%s/%s/root/file", + type_str.c_str(), + fs_id.c_str()); + return GURL(url_str); +} + +class FileSystemContextTest : public testing::Test { + public: + FileSystemContextTest() {} + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + + storage_policy_ = new quota::MockSpecialStoragePolicy(); + + mock_quota_manager_ = + new quota::MockQuotaManager(false /* is_incognito */, + data_dir_.path(), + base::MessageLoopProxy::current().get(), + base::MessageLoopProxy::current().get(), + storage_policy_.get()); + } + + protected: + FileSystemContext* CreateFileSystemContextForTest( + ExternalMountPoints* external_mount_points) { + return new FileSystemContext(base::MessageLoopProxy::current().get(), + base::MessageLoopProxy::current().get(), + external_mount_points, + storage_policy_.get(), + mock_quota_manager_->proxy(), + ScopedVector<FileSystemBackend>(), + data_dir_.path(), + CreateAllowFileAccessOptions()); + } + + // Verifies a *valid* filesystem url has expected values. + void ExpectFileSystemURLMatches(const FileSystemURL& url, + const GURL& expect_origin, + FileSystemType expect_mount_type, + FileSystemType expect_type, + const base::FilePath& expect_path, + const base::FilePath& expect_virtual_path, + const std::string& expect_filesystem_id) { + EXPECT_TRUE(url.is_valid()); + + EXPECT_EQ(expect_origin, url.origin()); + EXPECT_EQ(expect_mount_type, url.mount_type()); + EXPECT_EQ(expect_type, url.type()); + EXPECT_EQ(expect_path, url.path()); + EXPECT_EQ(expect_virtual_path, url.virtual_path()); + EXPECT_EQ(expect_filesystem_id, url.filesystem_id()); + } + + private: + base::ScopedTempDir data_dir_; + base::MessageLoop message_loop_; + scoped_refptr<quota::SpecialStoragePolicy> storage_policy_; + scoped_refptr<quota::MockQuotaManager> mock_quota_manager_; +}; + +// It is not valid to pass NULL ExternalMountPoints to FileSystemContext on +// ChromeOS. +#if !defined(OS_CHROMEOS) +TEST_F(FileSystemContextTest, NullExternalMountPoints) { + scoped_refptr<FileSystemContext> file_system_context( + CreateFileSystemContextForTest(NULL)); + + // Cracking system external mount and isolated mount points should work. + std::string isolated_name = "root"; + std::string isolated_id = + IsolatedContext::GetInstance()->RegisterFileSystemForPath( + kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/test/isolated/root")), + &isolated_name); + // Register system external mount point. + ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( + "system", + kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/test/sys/")))); + + FileSystemURL cracked_isolated = file_system_context->CrackURL( + CreateRawFileSystemURL("isolated", isolated_id)); + + ExpectFileSystemURLMatches( + cracked_isolated, + GURL(kTestOrigin), + kFileSystemTypeIsolated, + kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/test/isolated/root/file")).NormalizePathSeparators(), + base::FilePath::FromUTF8Unsafe(isolated_id).Append(FPL("root/file")). + NormalizePathSeparators(), + isolated_id); + + FileSystemURL cracked_external = file_system_context->CrackURL( + CreateRawFileSystemURL("external", "system")); + + ExpectFileSystemURLMatches( + cracked_external, + GURL(kTestOrigin), + kFileSystemTypeExternal, + kFileSystemTypeNativeLocal, + base::FilePath( + DRIVE FPL("/test/sys/root/file")).NormalizePathSeparators(), + base::FilePath(FPL("system/root/file")).NormalizePathSeparators(), + "system"); + + + IsolatedContext::GetInstance()->RevokeFileSystem(isolated_id); + ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("system"); +} +#endif // !defiend(OS_CHROMEOS) + +TEST_F(FileSystemContextTest, FileSystemContextKeepsMountPointsAlive) { + scoped_refptr<ExternalMountPoints> mount_points = + ExternalMountPoints::CreateRefCounted(); + + // Register system external mount point. + ASSERT_TRUE(mount_points->RegisterFileSystem( + "system", + kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/test/sys/")))); + + scoped_refptr<FileSystemContext> file_system_context( + CreateFileSystemContextForTest(mount_points.get())); + + // Release a MountPoints reference created in the test. + mount_points = NULL; + + // FileSystemContext should keep a reference to the |mount_points|, so it + // should be able to resolve the URL. + FileSystemURL cracked_external = file_system_context->CrackURL( + CreateRawFileSystemURL("external", "system")); + + ExpectFileSystemURLMatches( + cracked_external, + GURL(kTestOrigin), + kFileSystemTypeExternal, + kFileSystemTypeNativeLocal, + base::FilePath( + DRIVE FPL("/test/sys/root/file")).NormalizePathSeparators(), + base::FilePath(FPL("system/root/file")).NormalizePathSeparators(), + "system"); + + // No need to revoke the registered filesystem since |mount_points| lifetime + // is bound to this test. +} + +TEST_F(FileSystemContextTest, CrackFileSystemURL) { + scoped_refptr<ExternalMountPoints> external_mount_points( + ExternalMountPoints::CreateRefCounted()); + scoped_refptr<FileSystemContext> file_system_context( + CreateFileSystemContextForTest(external_mount_points.get())); + + // Register an isolated mount point. + std::string isolated_file_system_name = "root"; + const std::string kIsolatedFileSystemID = + IsolatedContext::GetInstance()->RegisterFileSystemForPath( + kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/test/isolated/root")), + &isolated_file_system_name); + // Register system external mount point. + ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( + "system", + kFileSystemTypeDrive, + base::FilePath(DRIVE FPL("/test/sys/")))); + ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( + "ext", + kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/test/ext")))); + // Register a system external mount point with the same name/id as the + // registered isolated mount point. + ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( + kIsolatedFileSystemID, + kFileSystemTypeRestrictedNativeLocal, + base::FilePath(DRIVE FPL("/test/system/isolated")))); + // Add a mount points with the same name as a system mount point to + // FileSystemContext's external mount points. + ASSERT_TRUE(external_mount_points->RegisterFileSystem( + "ext", + kFileSystemTypeNativeLocal, + base::FilePath(DRIVE FPL("/test/local/ext/")))); + + const GURL kTestOrigin = GURL("http://chromium.org/"); + const base::FilePath kVirtualPathNoRoot = base::FilePath(FPL("root/file")); + + struct TestCase { + // Test case values. + std::string root; + std::string type_str; + + // Expected test results. + bool expect_is_valid; + FileSystemType expect_mount_type; + FileSystemType expect_type; + const base::FilePath::CharType* expect_path; + std::string expect_filesystem_id; + }; + + const TestCase kTestCases[] = { + // Following should not be handled by the url crackers: + { + "pers_mount", "persistent", true /* is_valid */, + kFileSystemTypePersistent, kFileSystemTypePersistent, + FPL("pers_mount/root/file"), + std::string() /* filesystem id */ + }, + { + "temp_mount", "temporary", true /* is_valid */, + kFileSystemTypeTemporary, kFileSystemTypeTemporary, + FPL("temp_mount/root/file"), + std::string() /* filesystem id */ + }, + // Should be cracked by isolated mount points: + { + kIsolatedFileSystemID, "isolated", true /* is_valid */, + kFileSystemTypeIsolated, kFileSystemTypeNativeLocal, + DRIVE FPL("/test/isolated/root/file"), + kIsolatedFileSystemID + }, + // Should be cracked by system mount points: + { + "system", "external", true /* is_valid */, + kFileSystemTypeExternal, kFileSystemTypeDrive, + DRIVE FPL("/test/sys/root/file"), + "system" + }, + { + kIsolatedFileSystemID, "external", true /* is_valid */, + kFileSystemTypeExternal, kFileSystemTypeRestrictedNativeLocal, + DRIVE FPL("/test/system/isolated/root/file"), + kIsolatedFileSystemID + }, + // Should be cracked by FileSystemContext's ExternalMountPoints. + { + "ext", "external", true /* is_valid */, + kFileSystemTypeExternal, kFileSystemTypeNativeLocal, + DRIVE FPL("/test/local/ext/root/file"), + "ext" + }, + // Test for invalid filesystem url (made invalid by adding invalid + // filesystem type). + { + "sytem", "external", false /* is_valid */, + // The rest of values will be ignored. + kFileSystemTypeUnknown, kFileSystemTypeUnknown, FPL(""), + std::string() + }, + // Test for URL with non-existing filesystem id. + { + "invalid", "external", false /* is_valid */, + // The rest of values will be ignored. + kFileSystemTypeUnknown, kFileSystemTypeUnknown, FPL(""), + std::string() + }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) { + const base::FilePath virtual_path = + base::FilePath::FromUTF8Unsafe(kTestCases[i].root).Append(kVirtualPathNoRoot); + + GURL raw_url = + CreateRawFileSystemURL(kTestCases[i].type_str, kTestCases[i].root); + FileSystemURL cracked_url = file_system_context->CrackURL(raw_url); + + SCOPED_TRACE(testing::Message() << "Test case " << i << ": " + << "Cracking URL: " << raw_url); + + EXPECT_EQ(kTestCases[i].expect_is_valid, cracked_url.is_valid()); + if (!kTestCases[i].expect_is_valid) + continue; + + ExpectFileSystemURLMatches( + cracked_url, + GURL(kTestOrigin), + kTestCases[i].expect_mount_type, + kTestCases[i].expect_type, + base::FilePath(kTestCases[i].expect_path).NormalizePathSeparators(), + virtual_path.NormalizePathSeparators(), + kTestCases[i].expect_filesystem_id); + } + + IsolatedContext::GetInstance()->RevokeFileSystemByPath( + base::FilePath(DRIVE FPL("/test/isolated/root"))); + ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("system"); + ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("ext"); + ExternalMountPoints::GetSystemInstance()->RevokeFileSystem( + kIsolatedFileSystemID); +} + +} // namespace + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_dir_url_request_job.cc b/chromium/webkit/browser/fileapi/file_system_dir_url_request_job.cc new file mode 100644 index 00000000000..b91c7d00d9d --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_dir_url_request_job.cc @@ -0,0 +1,130 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_dir_url_request_job.h" + +#include <algorithm> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/message_loop/message_loop.h" +#include "base/platform_file.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "build/build_config.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 "url/gurl.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/common/fileapi/directory_entry.h" + +using net::NetworkDelegate; +using net::URLRequest; +using net::URLRequestJob; +using net::URLRequestStatus; + +namespace fileapi { + +FileSystemDirURLRequestJob::FileSystemDirURLRequestJob( + URLRequest* request, + NetworkDelegate* network_delegate, + FileSystemContext* file_system_context) + : URLRequestJob(request, network_delegate), + file_system_context_(file_system_context), + weak_factory_(this) { +} + +FileSystemDirURLRequestJob::~FileSystemDirURLRequestJob() { +} + +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; +} + +void FileSystemDirURLRequestJob::Start() { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&FileSystemDirURLRequestJob::StartAsync, + weak_factory_.GetWeakPtr())); +} + +void FileSystemDirURLRequestJob::Kill() { + URLRequestJob::Kill(); + weak_factory_.InvalidateWeakPtrs(); +} + +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() { + if (!request_) + return; + url_ = file_system_context_->CrackURL(request_->url()); + file_system_context_->operation_runner()->ReadDirectory( + url_, + base::Bind(&FileSystemDirURLRequestJob::DidReadDirectory, this)); +} + +void FileSystemDirURLRequestJob::DidReadDirectory( + base::PlatformFileError result, + const std::vector<DirectoryEntry>& entries, + bool has_more) { + if (result != base::PLATFORM_FILE_OK) { + int rv = net::ERR_FILE_NOT_FOUND; + if (result == base::PLATFORM_FILE_ERROR_INVALID_URL) + rv = net::ERR_INVALID_URL; + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); + return; + } + + if (!request_) + return; + + if (data_.empty()) { + base::FilePath relative_path = url_.path(); +#if defined(OS_POSIX) + relative_path = base::FilePath(FILE_PATH_LITERAL("/") + relative_path.value()); +#endif + const base::string16& title = relative_path.LossyDisplayName(); + data_.append(net::GetDirectoryListingHeader(title)); + } + + typedef std::vector<DirectoryEntry>::const_iterator EntryIterator; + for (EntryIterator it = entries.begin(); it != entries.end(); ++it) { + const base::string16& name = base::FilePath(it->name).LossyDisplayName(); + data_.append(net::GetDirectoryListingEntry( + name, std::string(), it->is_directory, it->size, + it->last_modified_time)); + } + + if (has_more) { + file_system_context_->operation_runner()->ReadDirectory( + url_, + base::Bind(&FileSystemDirURLRequestJob::DidReadDirectory, this)); + } else { + set_expected_content_size(data_.size()); + NotifyHeadersComplete(); + } +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_dir_url_request_job.h b/chromium/webkit/browser/fileapi/file_system_dir_url_request_job.h new file mode 100644 index 00000000000..933550ef4ac --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_dir_url_request_job.h @@ -0,0 +1,66 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_ + +#include <string> +#include <vector> + +#include "base/files/file_path.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/platform_file.h" +#include "net/url_request/url_request_job.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace fileapi { + +class FileSystemContext; +struct DirectoryEntry; + +// A request job that handles reading filesystem: URLs for directories. +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileSystemDirURLRequestJob + : public net::URLRequestJob { + public: + FileSystemDirURLRequestJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + FileSystemContext* file_system_context); + + // URLRequestJob methods: + virtual void Start() OVERRIDE; + virtual void Kill() OVERRIDE; + virtual bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int* bytes_read) OVERRIDE; + virtual bool GetCharset(std::string* charset) OVERRIDE; + + // FilterContext methods (via URLRequestJob): + virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; + // TODO(adamk): Implement GetResponseInfo and GetResponseCode to simulate + // an HTTP response. + + private: + class CallbackDispatcher; + + virtual ~FileSystemDirURLRequestJob(); + + void StartAsync(); + void DidReadDirectory(base::PlatformFileError result, + const std::vector<DirectoryEntry>& entries, + bool has_more); + + std::string data_; + FileSystemURL url_; + FileSystemContext* file_system_context_; + base::WeakPtrFactory<FileSystemDirURLRequestJob> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemDirURLRequestJob); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_dir_url_request_job_unittest.cc b/chromium/webkit/browser/fileapi/file_system_dir_url_request_job_unittest.cc new file mode 100644 index 00000000000..1c197cd294d --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_dir_url_request_job_unittest.cc @@ -0,0 +1,290 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_dir_url_request_job.h" + +#include <string> + +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/format_macros.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/platform_file.h" +#include "base/strings/string_piece.h" +#include "base/strings/utf_string_conversions.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_context.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/icu/source/i18n/unicode/regex.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/mock_file_system_context.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" + +namespace fileapi { +namespace { + +// We always use the TEMPORARY FileSystem in this test. +static const char kFileSystemURLPrefix[] = + "filesystem:http://remote/temporary/"; + +} // namespace + +class FileSystemDirURLRequestJobTest : public testing::Test { + protected: + FileSystemDirURLRequestJobTest() + : message_loop_(base::MessageLoop::TYPE_IO), // simulate an IO thread + weak_factory_(this) { + } + + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + special_storage_policy_ = new quota::MockSpecialStoragePolicy; + file_system_context_ = CreateFileSystemContextForTesting( + NULL, temp_dir_.path()); + + file_system_context_->OpenFileSystem( + GURL("http://remote/"), kFileSystemTypeTemporary, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&FileSystemDirURLRequestJobTest::OnOpenFileSystem, + weak_factory_.GetWeakPtr())); + base::MessageLoop::current()->RunUntilIdle(); + + net::URLRequest::Deprecated::RegisterProtocolFactory( + "filesystem", &FileSystemDirURLRequestJobFactory); + } + + virtual void TearDown() OVERRIDE { + // NOTE: order matters, request must die before delegate + request_.reset(NULL); + delegate_.reset(NULL); + + net::URLRequest::Deprecated::RegisterProtocolFactory("filesystem", NULL); + ClearUnusedJob(); + } + + void OnOpenFileSystem(base::PlatformFileError result, + const std::string& name, + const GURL& root_url) { + ASSERT_EQ(base::PLATFORM_FILE_OK, result); + } + + void TestRequestHelper(const GURL& url, bool run_to_completion) { + delegate_.reset(new net::TestDelegate()); + delegate_->set_quit_on_redirect(true); + request_.reset(empty_context_.CreateRequest(url, delegate_.get())); + job_ = new FileSystemDirURLRequestJob( + request_.get(), NULL, file_system_context_.get()); + + request_->Start(); + ASSERT_TRUE(request_->is_pending()); // verify that we're starting async + if (run_to_completion) + base::MessageLoop::current()->Run(); + } + + void TestRequest(const GURL& url) { + TestRequestHelper(url, true); + } + + void TestRequestNoRun(const GURL& url) { + TestRequestHelper(url, false); + } + + FileSystemURL CreateURL(const base::FilePath& file_path) { + return file_system_context_->CreateCrackedFileSystemURL( + GURL("http://remote"), + fileapi::kFileSystemTypeTemporary, + file_path); + } + + FileSystemOperationContext* NewOperationContext() { + FileSystemOperationContext* context( + new FileSystemOperationContext(file_system_context_.get())); + context->set_allowed_bytes_growth(1024); + return context; + } + + void CreateDirectory(const base::StringPiece& dir_name) { + base::FilePath path = base::FilePath().AppendASCII(dir_name); + scoped_ptr<FileSystemOperationContext> context(NewOperationContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, file_util()->CreateDirectory( + context.get(), + CreateURL(path), + false /* exclusive */, + false /* recursive */)); + } + + void EnsureFileExists(const base::StringPiece file_name) { + base::FilePath path = base::FilePath().AppendASCII(file_name); + scoped_ptr<FileSystemOperationContext> context(NewOperationContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, file_util()->EnsureFileExists( + context.get(), CreateURL(path), NULL)); + } + + void TruncateFile(const base::StringPiece file_name, int64 length) { + base::FilePath path = base::FilePath().AppendASCII(file_name); + scoped_ptr<FileSystemOperationContext> context(NewOperationContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, file_util()->Truncate( + context.get(), CreateURL(path), length)); + } + + base::PlatformFileError GetFileInfo(const base::FilePath& path, + base::PlatformFileInfo* file_info, + base::FilePath* platform_file_path) { + scoped_ptr<FileSystemOperationContext> context(NewOperationContext()); + return file_util()->GetFileInfo(context.get(), + CreateURL(path), + file_info, platform_file_path); + } + + void VerifyListingEntry(const std::string& entry_line, + const std::string& name, + const std::string& url, + bool is_directory, + int64 size) { +#define STR "([^\"]*)" + icu::UnicodeString pattern("^<script>addRow\\(\"" STR "\",\"" STR + "\",(0|1),\"" STR "\",\"" STR "\"\\);</script>"); +#undef STR + icu::UnicodeString input(entry_line.c_str()); + + UErrorCode status = U_ZERO_ERROR; + icu::RegexMatcher match(pattern, input, 0, status); + + EXPECT_TRUE(match.find()); + EXPECT_EQ(5, match.groupCount()); + EXPECT_EQ(icu::UnicodeString(name.c_str()), match.group(1, status)); + EXPECT_EQ(icu::UnicodeString(url.c_str()), match.group(2, status)); + EXPECT_EQ(icu::UnicodeString(is_directory ? "1" : "0"), + match.group(3, status)); + icu::UnicodeString size_string(FormatBytesUnlocalized(size).c_str()); + EXPECT_EQ(size_string, match.group(4, status)); + + base::Time date; + icu::UnicodeString date_ustr(match.group(5, status)); + std::string date_str; + UTF16ToUTF8(date_ustr.getBuffer(), date_ustr.length(), &date_str); + EXPECT_TRUE(base::Time::FromString(date_str.c_str(), &date)); + EXPECT_FALSE(date.is_null()); + } + + GURL CreateFileSystemURL(const std::string path) { + return GURL(kFileSystemURLPrefix + path); + } + + static net::URLRequestJob* FileSystemDirURLRequestJobFactory( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const std::string& scheme) { + DCHECK(job_); + net::URLRequestJob* temp = job_; + job_ = NULL; + return temp; + } + + static void ClearUnusedJob() { + if (job_) { + scoped_refptr<net::URLRequestJob> deleter = job_; + job_ = NULL; + } + } + + FileSystemFileUtil* file_util() { + return file_system_context_->GetFileUtil(kFileSystemTypeTemporary); + } + + // Put the message loop at the top, so that it's the last thing deleted. + // Delete all MessageLoopProxy objects before the MessageLoop, to help prevent + // leaks caused by tasks posted during shutdown. + base::MessageLoop message_loop_; + + base::ScopedTempDir temp_dir_; + net::URLRequestContext empty_context_; + scoped_ptr<net::TestDelegate> delegate_; + scoped_ptr<net::URLRequest> request_; + scoped_refptr<quota::MockSpecialStoragePolicy> special_storage_policy_; + scoped_refptr<FileSystemContext> file_system_context_; + base::WeakPtrFactory<FileSystemDirURLRequestJobTest> weak_factory_; + + static net::URLRequestJob* job_; +}; + +// static +net::URLRequestJob* FileSystemDirURLRequestJobTest::job_ = NULL; + +namespace { + +TEST_F(FileSystemDirURLRequestJobTest, DirectoryListing) { + CreateDirectory("foo"); + CreateDirectory("foo/bar"); + CreateDirectory("foo/bar/baz"); + + EnsureFileExists("foo/bar/hoge"); + TruncateFile("foo/bar/hoge", 10); + + 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); + + std::istringstream in(delegate_->data_received()); + std::string line; + EXPECT_TRUE(std::getline(in, line)); + +#if defined(OS_WIN) + EXPECT_EQ("<script>start(\"foo\\\\bar\");</script>", line); +#elif defined(OS_POSIX) + EXPECT_EQ("<script>start(\"/foo/bar\");</script>", line); +#endif + + EXPECT_TRUE(std::getline(in, line)); + VerifyListingEntry(line, "hoge", "hoge", false, 10); + + EXPECT_TRUE(std::getline(in, line)); + VerifyListingEntry(line, "baz", "baz", true, 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().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().error()); +} + +TEST_F(FileSystemDirURLRequestJobTest, NoSuchDirectory) { + TestRequest(CreateFileSystemURL("somedir/")); + ASSERT_FALSE(request_->is_pending()); + ASSERT_FALSE(request_->status().is_success()); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); +} + +TEST_F(FileSystemDirURLRequestJobTest, Cancel) { + CreateDirectory("foo"); + TestRequestNoRun(CreateFileSystemURL("foo/")); + // Run StartAsync() and only StartAsync(). + base::MessageLoop::current()->DeleteSoon(FROM_HERE, request_.release()); + base::MessageLoop::current()->RunUntilIdle(); + // If we get here, success! we didn't crash! +} + +} // namespace (anonymous) +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_file_stream_reader.cc b/chromium/webkit/browser/fileapi/file_system_file_stream_reader.cc new file mode 100644 index 00000000000..71b38278186 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_file_stream_reader.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_file_stream_reader.h" + +#include "base/files/file_util_proxy.h" +#include "base/platform_file.h" +#include "base/single_thread_task_runner.h" +#include "net/base/file_stream.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "webkit/browser/blob/local_file_stream_reader.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" + +using webkit_blob::LocalFileStreamReader; + +namespace fileapi { + +namespace { + +void ReadAdapter(base::WeakPtr<FileSystemFileStreamReader> reader, + net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + if (!reader.get()) + return; + int rv = reader->Read(buf, buf_len, callback); + if (rv != net::ERR_IO_PENDING) + callback.Run(rv); +} + +void GetLengthAdapter(base::WeakPtr<FileSystemFileStreamReader> reader, + const net::Int64CompletionCallback& callback) { + if (!reader.get()) + return; + int rv = reader->GetLength(callback); + if (rv != net::ERR_IO_PENDING) + callback.Run(rv); +} + +void Int64CallbackAdapter(const net::Int64CompletionCallback& callback, + int value) { + callback.Run(value); +} + +} // namespace + +FileSystemFileStreamReader::FileSystemFileStreamReader( + FileSystemContext* file_system_context, + const FileSystemURL& url, + int64 initial_offset, + const base::Time& expected_modification_time) + : file_system_context_(file_system_context), + url_(url), + initial_offset_(initial_offset), + expected_modification_time_(expected_modification_time), + has_pending_create_snapshot_(false), + weak_factory_(this) { +} + +FileSystemFileStreamReader::~FileSystemFileStreamReader() { +} + +int FileSystemFileStreamReader::Read( + net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + if (local_file_reader_) + return local_file_reader_->Read(buf, buf_len, callback); + return CreateSnapshot( + base::Bind(&ReadAdapter, weak_factory_.GetWeakPtr(), + make_scoped_refptr(buf), buf_len, callback), + callback); +} + +int64 FileSystemFileStreamReader::GetLength( + const net::Int64CompletionCallback& callback) { + if (local_file_reader_) + return local_file_reader_->GetLength(callback); + return CreateSnapshot( + base::Bind(&GetLengthAdapter, weak_factory_.GetWeakPtr(), callback), + base::Bind(&Int64CallbackAdapter, callback)); +} + +int FileSystemFileStreamReader::CreateSnapshot( + const base::Closure& callback, + const net::CompletionCallback& error_callback) { + DCHECK(!has_pending_create_snapshot_); + has_pending_create_snapshot_ = true; + file_system_context_->operation_runner()->CreateSnapshotFile( + url_, + base::Bind(&FileSystemFileStreamReader::DidCreateSnapshot, + weak_factory_.GetWeakPtr(), + callback, + error_callback)); + return net::ERR_IO_PENDING; +} + +void FileSystemFileStreamReader::DidCreateSnapshot( + const base::Closure& callback, + const net::CompletionCallback& error_callback, + base::PlatformFileError file_error, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) { + DCHECK(has_pending_create_snapshot_); + DCHECK(!local_file_reader_.get()); + has_pending_create_snapshot_ = false; + + if (file_error != base::PLATFORM_FILE_OK) { + error_callback.Run(net::PlatformFileErrorToNetError(file_error)); + return; + } + + // Keep the reference (if it's non-null) so that the file won't go away. + snapshot_ref_ = file_ref; + + local_file_reader_.reset( + new LocalFileStreamReader( + file_system_context_->default_file_task_runner(), + platform_path, initial_offset_, expected_modification_time_)); + + callback.Run(); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_file_stream_reader.h b/chromium/webkit/browser/fileapi/file_system_file_stream_reader.h new file mode 100644 index 00000000000..634f5684c42 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_file_stream_reader.h @@ -0,0 +1,79 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_FILE_STREAM_READER_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_FILE_STREAM_READER_H_ + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/platform_file.h" +#include "base/time/time.h" +#include "webkit/browser/blob/file_stream_reader.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/blob/shareable_file_reference.h" + +namespace base { +class FilePath; +class SequencedTaskRunner; +} + +namespace webkit_blob { +class LocalFileStreamReader; +} + +namespace fileapi { + +class FileSystemContext; + +// TODO(kinaba,satorux): This generic implementation would work for any +// filesystems but remote filesystem should implement its own reader +// rather than relying on FileSystemOperation::GetSnapshotFile() which +// may force downloading the entire contents for remote files. +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileSystemFileStreamReader + : public webkit_blob::FileStreamReader { + public: + // Creates a new FileReader for a filesystem URL |url| form |initial_offset|. + // |expected_modification_time| specifies the expected last modification if + // the value is non-null, the reader will check the underlying file's actual + // modification time to see if the file has been modified, and if it does any + // succeeding read operations should fail with ERR_UPLOAD_FILE_CHANGED error. + FileSystemFileStreamReader(FileSystemContext* file_system_context, + const FileSystemURL& url, + int64 initial_offset, + const base::Time& expected_modification_time); + virtual ~FileSystemFileStreamReader(); + + // FileStreamReader overrides. + virtual int Read(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE; + virtual int64 GetLength( + const net::Int64CompletionCallback& callback) OVERRIDE; + + private: + int CreateSnapshot(const base::Closure& callback, + const net::CompletionCallback& error_callback); + void DidCreateSnapshot( + const base::Closure& callback, + const net::CompletionCallback& error_callback, + base::PlatformFileError file_error, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref); + + scoped_refptr<FileSystemContext> file_system_context_; + FileSystemURL url_; + const int64 initial_offset_; + const base::Time expected_modification_time_; + scoped_ptr<webkit_blob::LocalFileStreamReader> local_file_reader_; + scoped_refptr<webkit_blob::ShareableFileReference> snapshot_ref_; + bool has_pending_create_snapshot_; + base::WeakPtrFactory<FileSystemFileStreamReader> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemFileStreamReader); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_FILE_STREAM_READER_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_file_stream_reader_unittest.cc b/chromium/webkit/browser/fileapi/file_system_file_stream_reader_unittest.cc new file mode 100644 index 00000000000..ca82338a5cc --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_file_stream_reader_unittest.cc @@ -0,0 +1,283 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_file_stream_reader.h" + +#include <limits> +#include <string> + +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/platform_file.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/external_mount_points.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/mock_file_system_context.h" + +namespace fileapi { + +namespace { + +const char kURLOrigin[] = "http://remote/"; +const char kTestFileName[] = "test.dat"; +const char kTestData[] = "0123456789"; +const int kTestDataSize = arraysize(kTestData) - 1; + +void ReadFromReader(FileSystemFileStreamReader* reader, + std::string* data, + size_t size, + int* result) { + ASSERT_TRUE(reader != NULL); + ASSERT_TRUE(result != NULL); + *result = net::OK; + net::TestCompletionCallback callback; + size_t total_bytes_read = 0; + while (total_bytes_read < size) { + scoped_refptr<net::IOBufferWithSize> buf( + new net::IOBufferWithSize(size - total_bytes_read)); + int rv = reader->Read(buf.get(), buf->size(), callback.callback()); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + if (rv < 0) + *result = rv; + if (rv <= 0) + break; + total_bytes_read += rv; + data->append(buf->data(), rv); + } +} + +void NeverCalled(int unused) { ADD_FAILURE(); } + +} // namespace + +class FileSystemFileStreamReaderTest : public testing::Test { + public: + FileSystemFileStreamReaderTest() + : message_loop_(base::MessageLoop::TYPE_IO) {} + + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + file_system_context_ = CreateFileSystemContextForTesting( + NULL, temp_dir_.path()); + + file_system_context_->OpenFileSystem( + GURL(kURLOrigin), kFileSystemTypeTemporary, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&OnOpenFileSystem)); + base::MessageLoop::current()->RunUntilIdle(); + + WriteFile(kTestFileName, kTestData, kTestDataSize, + &test_file_modification_time_); + } + + virtual void TearDown() OVERRIDE { + base::MessageLoop::current()->RunUntilIdle(); + } + + protected: + FileSystemFileStreamReader* CreateFileReader( + const std::string& file_name, + int64 initial_offset, + const base::Time& expected_modification_time) { + return new FileSystemFileStreamReader(file_system_context_.get(), + GetFileSystemURL(file_name), + initial_offset, + expected_modification_time); + } + + base::Time test_file_modification_time() const { + return test_file_modification_time_; + } + + void WriteFile(const std::string& file_name, + const char* buf, + int buf_size, + base::Time* modification_time) { + FileSystemFileUtil* file_util = file_system_context_->GetFileUtil( + kFileSystemTypeTemporary); + FileSystemURL url = GetFileSystemURL(file_name); + + FileSystemOperationContext context(file_system_context_.get()); + context.set_allowed_bytes_growth(1024); + + base::PlatformFile handle = base::kInvalidPlatformFileValue; + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, file_util->CreateOrOpen( + &context, + url, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, + &handle, + &created)); + EXPECT_TRUE(created); + ASSERT_NE(base::kInvalidPlatformFileValue, handle); + ASSERT_EQ(buf_size, + base::WritePlatformFile(handle, 0 /* offset */, buf, buf_size)); + base::ClosePlatformFile(handle); + + base::PlatformFileInfo file_info; + base::FilePath platform_path; + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util->GetFileInfo(&context, url, &file_info, + &platform_path)); + if (modification_time) + *modification_time = file_info.last_modified; + } + + private: + static void OnOpenFileSystem(base::PlatformFileError result, + const std::string& name, + const GURL& root_url) { + ASSERT_EQ(base::PLATFORM_FILE_OK, result); + } + + FileSystemURL GetFileSystemURL(const std::string& file_name) { + return file_system_context_->CreateCrackedFileSystemURL( + GURL(kURLOrigin), + kFileSystemTypeTemporary, + base::FilePath().AppendASCII(file_name)); + } + + base::MessageLoop message_loop_; + base::ScopedTempDir temp_dir_; + scoped_refptr<FileSystemContext> file_system_context_; + base::Time test_file_modification_time_; +}; + +TEST_F(FileSystemFileStreamReaderTest, NonExistent) { + const char kFileName[] = "nonexistent"; + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kFileName, 0, base::Time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, 10, &result); + ASSERT_EQ(net::ERR_FILE_NOT_FOUND, result); + ASSERT_EQ(0U, data.size()); +} + +TEST_F(FileSystemFileStreamReaderTest, Empty) { + const char kFileName[] = "empty"; + WriteFile(kFileName, NULL, 0, NULL); + + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kFileName, 0, base::Time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, 10, &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(0U, data.size()); + + net::TestInt64CompletionCallback callback; + int64 length_result = reader->GetLength(callback.callback()); + if (length_result == net::ERR_IO_PENDING) + length_result = callback.WaitForResult(); + ASSERT_EQ(0, length_result); +} + +TEST_F(FileSystemFileStreamReaderTest, GetLengthNormal) { + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kTestFileName, 0, test_file_modification_time())); + net::TestInt64CompletionCallback callback; + int64 result = reader->GetLength(callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + ASSERT_EQ(kTestDataSize, result); +} + +TEST_F(FileSystemFileStreamReaderTest, GetLengthAfterModified) { + // Pass a fake expected modifictaion time so that the expectation fails. + base::Time fake_expected_modification_time = + test_file_modification_time() - base::TimeDelta::FromSeconds(10); + + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kTestFileName, 0, fake_expected_modification_time)); + net::TestInt64CompletionCallback callback; + int64 result = reader->GetLength(callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); + + // With NULL expected modification time this should work. + reader.reset(CreateFileReader(kTestFileName, 0, base::Time())); + result = reader->GetLength(callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + ASSERT_EQ(kTestDataSize, result); +} + +TEST_F(FileSystemFileStreamReaderTest, GetLengthWithOffset) { + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kTestFileName, 3, base::Time())); + net::TestInt64CompletionCallback callback; + int64 result = reader->GetLength(callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + // Initial offset does not affect the result of GetLength. + ASSERT_EQ(kTestDataSize, result); +} + +TEST_F(FileSystemFileStreamReaderTest, ReadNormal) { + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kTestFileName, 0, test_file_modification_time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, kTestDataSize, &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(kTestData, data); +} + +TEST_F(FileSystemFileStreamReaderTest, ReadAfterModified) { + // Pass a fake expected modifictaion time so that the expectation fails. + base::Time fake_expected_modification_time = + test_file_modification_time() - base::TimeDelta::FromSeconds(10); + + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kTestFileName, 0, fake_expected_modification_time)); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, kTestDataSize, &result); + ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); + ASSERT_EQ(0U, data.size()); + + // With NULL expected modification time this should work. + data.clear(); + reader.reset(CreateFileReader(kTestFileName, 0, base::Time())); + ReadFromReader(reader.get(), &data, kTestDataSize, &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(kTestData, data); +} + +TEST_F(FileSystemFileStreamReaderTest, ReadWithOffset) { + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kTestFileName, 3, base::Time())); + int result = 0; + std::string data; + ReadFromReader(reader.get(), &data, kTestDataSize, &result); + ASSERT_EQ(net::OK, result); + ASSERT_EQ(&kTestData[3], data); +} + +TEST_F(FileSystemFileStreamReaderTest, DeleteWithUnfinishedRead) { + scoped_ptr<FileSystemFileStreamReader> reader( + CreateFileReader(kTestFileName, 0, base::Time())); + + net::TestCompletionCallback callback; + scoped_refptr<net::IOBufferWithSize> buf( + new net::IOBufferWithSize(kTestDataSize)); + int rv = reader->Read(buf.get(), buf->size(), base::Bind(&NeverCalled)); + ASSERT_TRUE(rv == net::ERR_IO_PENDING || rv >= 0); + + // Delete immediately. + // Should not crash; nor should NeverCalled be callback. + reader.reset(); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_file_util.cc b/chromium/webkit/browser/fileapi/file_system_file_util.cc new file mode 100644 index 00000000000..7317b8b2693 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_file_util.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_file_util.h" + +namespace fileapi { + +base::FilePath FileSystemFileUtil::EmptyFileEnumerator::Next() { + return base::FilePath(); +} + +int64 FileSystemFileUtil::EmptyFileEnumerator::Size() { + return 0; +} + +base::Time FileSystemFileUtil::EmptyFileEnumerator::LastModifiedTime() { + return base::Time(); +} + +bool FileSystemFileUtil::EmptyFileEnumerator::IsDirectory() { + return false; +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_file_util.h b/chromium/webkit/browser/fileapi/file_system_file_util.h new file mode 100644 index 00000000000..13ea0e40a4d --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_file_util.h @@ -0,0 +1,189 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_FILE_UTIL_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_FILE_UTIL_H_ + +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/platform_file.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/blob/scoped_file.h" + +namespace base { +class Time; +} + +namespace fileapi { + +class FileSystemOperationContext; +class FileSystemURL; + +// A file utility interface that provides basic file utility methods for +// FileSystem API. +// +// Layering structure of the FileSystemFileUtil was split out. +// See http://crbug.com/128136 if you need it. +class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemFileUtil { + public: + // It will be implemented by each subclass such as FileSystemFileEnumerator. + class WEBKIT_STORAGE_BROWSER_EXPORT AbstractFileEnumerator { + public: + virtual ~AbstractFileEnumerator() {} + + // Returns an empty string if there are no more results. + virtual base::FilePath Next() = 0; + + // These methods return metadata for the file most recently returned by + // Next(). If Next() has never been called, or if Next() most recently + // returned an empty string, then return the default values of 0, + // "null time", and false, respectively. + virtual int64 Size() = 0; + virtual base::Time LastModifiedTime() = 0; + virtual bool IsDirectory() = 0; + }; + + class WEBKIT_STORAGE_BROWSER_EXPORT EmptyFileEnumerator + : public AbstractFileEnumerator { + virtual base::FilePath Next() OVERRIDE; + virtual int64 Size() OVERRIDE; + virtual base::Time LastModifiedTime() OVERRIDE; + virtual bool IsDirectory() OVERRIDE; + }; + + virtual ~FileSystemFileUtil() {} + + // Creates or opens a file with the given flags. + // See header comments for AsyncFileUtil::CreateOrOpen() for more details. + // This is used only by Pepper/NaCL File API. + virtual base::PlatformFileError CreateOrOpen( + FileSystemOperationContext* context, + const FileSystemURL& url, + int file_flags, + base::PlatformFile* file_handle, + bool* created) = 0; + + // Closes the given file handle. + // This is used only for Pepper/NaCL File API. + virtual base::PlatformFileError Close( + FileSystemOperationContext* context, + base::PlatformFile file) = 0; + + // Ensures that the given |url| exist. This creates a empty new file + // at |url| if the |url| does not exist. + // See header comments for AsyncFileUtil::EnsureFileExists() for more details. + virtual base::PlatformFileError EnsureFileExists( + FileSystemOperationContext* context, + const FileSystemURL& url, bool* created) = 0; + + // Creates directory at given url. + // See header comments for AsyncFileUtil::CreateDirectory() for more details. + virtual base::PlatformFileError CreateDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool exclusive, + bool recursive) = 0; + + // Retrieves the information about a file. + // See header comments for AsyncFileUtil::GetFileInfo() for more details. + virtual base::PlatformFileError GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::PlatformFileInfo* file_info, + base::FilePath* platform_path) = 0; + + // Returns a pointer to a new instance of AbstractFileEnumerator which is + // implemented for each FileSystemFileUtil subclass. The instance needs to be + // freed by the caller, and its lifetime should not extend past when the + // current call returns to the main FILE message loop. + // + // The supplied context must remain valid at least lifetime of the enumerator + // instance. + virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url) = 0; + + // Maps |file_system_url| given |context| into |local_file_path| + // which represents physical file location on the host OS. + // This may not always make sense for all subclasses. + virtual base::PlatformFileError GetLocalFilePath( + FileSystemOperationContext* context, + const FileSystemURL& file_system_url, + base::FilePath* local_file_path) = 0; + + // Updates the file metadata information. + // See header comments for AsyncFileUtil::Touch() for more details. + virtual base::PlatformFileError Touch( + FileSystemOperationContext* context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time) = 0; + + // Truncates a file to the given length. + // See header comments for AsyncFileUtil::Truncate() for more details. + virtual base::PlatformFileError Truncate( + FileSystemOperationContext* context, + const FileSystemURL& url, + int64 length) = 0; + + // Copies or moves a single file from |src_url| to |dest_url|. + // The filesystem type of |src_url| and |dest_url| MUST be same. + // + // This returns: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_url| + // or the parent directory of |dest_url| does not exist. + // - PLATFORM_FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file. + // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + virtual base::PlatformFileError CopyOrMoveFile( + FileSystemOperationContext* context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + bool copy) = 0; + + // Copies in a single file from a different filesystem. + // See header comments for AsyncFileUtil::CopyInForeignFile() for + // more details. + virtual base::PlatformFileError CopyInForeignFile( + FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url) = 0; + + // Deletes a single file. + // See header comments for AsyncFileUtil::DeleteFile() for more details. + virtual base::PlatformFileError DeleteFile( + FileSystemOperationContext* context, + const FileSystemURL& url) = 0; + + // Deletes a single empty directory. + // See header comments for AsyncFileUtil::DeleteDirectory() for more details. + virtual base::PlatformFileError DeleteDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url) = 0; + + // Creates a local snapshot file for a given |url| and returns the + // metadata and platform path of the snapshot file via |callback|. + // + // See header comments for AsyncFileUtil::CreateSnapshotFile() for + // more details. + virtual webkit_blob::ScopedFile CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::PlatformFileError* error, + base::PlatformFileInfo* file_info, + base::FilePath* platform_path) = 0; + + protected: + FileSystemFileUtil() {} + + private: + DISALLOW_COPY_AND_ASSIGN(FileSystemFileUtil); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_FILE_UTIL_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_operation.h b/chromium/webkit/browser/fileapi/file_system_operation.h new file mode 100644 index 00000000000..f1adc191a7e --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_operation.h @@ -0,0 +1,276 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_ + +#include <vector> + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/platform_file.h" +#include "base/process/process.h" +#include "webkit/common/fileapi/directory_entry.h" + +namespace base { +class Time; +} + +namespace net { +class URLRequest; +} + +namespace webkit_blob { +class ShareableFileReference; +} + +class GURL; + +namespace fileapi { + +class FileSystemURL; +class FileWriterDelegate; +class FileSystemOperationImpl; + +// The interface class for FileSystemOperation implementations. +// +// This interface defines file system operations required to implement +// "File API: Directories and System" +// http://www.w3.org/TR/file-system-api/ +// +// DESIGN NOTES +// +// This class is designed to +// +// 1) Serve one-time file system operation per instance. Only one +// method(CreateFile, CreateDirectory, Copy, Move, DirectoryExists, +// GetMetadata, ReadDirectory and Remove) may be called during the +// lifetime of this object and it should be called no more than once. +// +// 2) Deliver the results of operations to the client via the callback function +// passed as the last parameter of the method. +// +// Note that it is valid to delete an operation while it is running. +// The callback will NOT be fired if the operation is deleted before +// it gets called. +class FileSystemOperation { + public: + virtual ~FileSystemOperation() {} + + // Used for CreateFile(), etc. |result| is the return code of the operation. + typedef base::Callback<void(base::PlatformFileError result)> StatusCallback; + + // Used for GetMetadata(). |result| is the return code of the operation, + // |file_info| is the obtained file info. + typedef base::Callback< + void(base::PlatformFileError result, + const base::PlatformFileInfo& file_info)> GetMetadataCallback; + + // Used for OpenFile(). |result| is the return code of the operation. + // |on_close_callback| will be called after the file is closed in the child + // process. It can be null, if no operation is needed on closing a file. + typedef base::Callback< + void(base::PlatformFileError result, + base::PlatformFile file, + const base::Closure& on_close_callback, + base::ProcessHandle peer_handle)> OpenFileCallback; + + // Used for ReadDirectoryCallback. + typedef std::vector<DirectoryEntry> FileEntryList; + + // Used for ReadDirectory(). |result| is the return code of the operation, + // |file_list| is the list of files read, and |has_more| is true if some files + // are yet to be read. + typedef base::Callback< + void(base::PlatformFileError result, + const FileEntryList& file_list, + bool has_more)> ReadDirectoryCallback; + + // Used for CreateSnapshotFile(). (Please see the comment at + // CreateSnapshotFile() below for how the method is called) + // |result| is the return code of the operation. + // |file_info| is the metadata of the snapshot file created. + // |platform_path| is the path to the snapshot file created. + // + // The snapshot file could simply be of the local file pointed by the given + // filesystem URL in local filesystem cases; remote filesystems + // may want to download the file into a temporary snapshot file and then + // return the metadata of the temporary file. + // + // |file_ref| is used to manage the lifetime of the returned + // snapshot file. It can be set to let the chromium backend take + // care of the life time of the snapshot file. Otherwise (if the returned + // file does not require any handling) the implementation can just + // return NULL. In a more complex case, the implementaiton can manage + // the lifetime of the snapshot file on its own (e.g. by its cache system) + // but also can be notified via the reference when the file becomes no + // longer necessary in the javascript world. + // Please see the comment for ShareableFileReference for details. + // + typedef base::Callback< + void(base::PlatformFileError result, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref)> + SnapshotFileCallback; + + // Used for Write(). + typedef base::Callback<void(base::PlatformFileError result, + int64 bytes, + bool complete)> WriteCallback; + + // Creates a file at |path|. If |exclusive| is true, an error is raised + // in case a file is already present at the URL. + virtual void CreateFile(const FileSystemURL& path, + bool exclusive, + const StatusCallback& callback) = 0; + + // Creates a directory at |path|. If |exclusive| is true, an error is + // raised in case a directory is already present at the URL. If + // |recursive| is true, create parent directories as needed just like + // mkdir -p does. + virtual void CreateDirectory(const FileSystemURL& path, + bool exclusive, + bool recursive, + const StatusCallback& callback) = 0; + + // Copies a file or directory from |src_path| to |dest_path|. If + // |src_path| is a directory, the contents of |src_path| are copied to + // |dest_path| recursively. A new file or directory is created at + // |dest_path| as needed. + virtual void Copy(const FileSystemURL& src_path, + const FileSystemURL& dest_path, + const StatusCallback& callback) = 0; + + // Moves a file or directory from |src_path| to |dest_path|. A new file + // or directory is created at |dest_path| as needed. + virtual void Move(const FileSystemURL& src_path, + const FileSystemURL& dest_path, + const StatusCallback& callback) = 0; + + // Checks if a directory is present at |path|. + virtual void DirectoryExists(const FileSystemURL& path, + const StatusCallback& callback) = 0; + + // Checks if a file is present at |path|. + virtual void FileExists(const FileSystemURL& path, + const StatusCallback& callback) = 0; + + // Gets the metadata of a file or directory at |path|. + virtual void GetMetadata(const FileSystemURL& path, + const GetMetadataCallback& callback) = 0; + + // Reads contents of a directory at |path|. + virtual void ReadDirectory(const FileSystemURL& path, + const ReadDirectoryCallback& callback) = 0; + + // Removes a file or directory at |path|. If |recursive| is true, remove + // all files and directories under the directory at |path| recursively. + virtual void Remove(const FileSystemURL& path, bool recursive, + const StatusCallback& callback) = 0; + + // Writes the data read from |blob_request| using |writer_delegate|. + virtual void Write( + const FileSystemURL& url, + scoped_ptr<FileWriterDelegate> writer_delegate, + scoped_ptr<net::URLRequest> blob_request, + const WriteCallback& callback) = 0; + + // Truncates a file at |path| to |length|. If |length| is larger than + // the original file size, the file will be extended, and the extended + // part is filled with null bytes. + virtual void Truncate(const FileSystemURL& path, int64 length, + const StatusCallback& callback) = 0; + + // Tries to cancel the current operation [we support cancelling write or + // truncate only]. Reports failure for the current operation, then reports + // success for the cancel operation itself via the |cancel_dispatcher|. + // + // E.g. a typical cancel implementation would look like: + // + // virtual void SomeOperationImpl::Cancel( + // const StatusCallback& cancel_callback) { + // // Abort the current inflight operation first. + // ... + // + // // Dispatch ABORT error for the current operation by invoking + // // the callback function for the ongoing operation, + // operation_callback.Run(base::PLATFORM_FILE_ERROR_ABORT, ...); + // + // // Dispatch 'success' for the cancel (or dispatch appropriate + // // error code with DidFail() if the cancel has somehow failed). + // cancel_callback.Run(base::PLATFORM_FILE_OK); + // } + // + // Note that, for reporting failure, the callback function passed to a + // cancellable operations are kept around with the operation instance + // (as |operation_callback_| in the code example). + virtual void Cancel(const StatusCallback& cancel_callback) = 0; + + // Modifies timestamps of a file or directory at |path| with + // |last_access_time| and |last_modified_time|. The function DOES NOT + // create a file unlike 'touch' command on Linux. + // + // This function is used only by Pepper as of writing. + virtual void TouchFile(const FileSystemURL& path, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) = 0; + + // Opens a file at |path| with |file_flags|, where flags are OR'ed + // values of base::PlatformFileFlags. + // + // |peer_handle| is the process handle of a pepper plugin process, which + // is necessary for underlying IPC calls with Pepper plugins. + // + // This function is used only by Pepper as of writing. + virtual void OpenFile(const FileSystemURL& path, + int file_flags, + base::ProcessHandle peer_handle, + const OpenFileCallback& callback) = 0; + + // For downcasting to FileSystemOperationImpl. + // TODO(kinuko): this hack should go away once appropriate upload-stream + // handling based on element types is supported. + virtual FileSystemOperationImpl* AsFileSystemOperationImpl() = 0; + + // Creates a local snapshot file for a given |path| and returns the + // metadata and platform path of the snapshot file via |callback|. + // In local filesystem cases the implementation may simply return + // the metadata of the file itself (as well as GetMetadata does), + // while in remote filesystem case the backend may want to download the file + // into a temporary snapshot file and return the metadata of the + // temporary file. Or if the implementaiton already has the local cache + // data for |path| it can simply return the path to the cache. + virtual void CreateSnapshotFile(const FileSystemURL& path, + const SnapshotFileCallback& callback) = 0; + + protected: + // Used only for internal assertions. + enum OperationType { + kOperationNone, + kOperationCreateFile, + kOperationCreateDirectory, + kOperationCreateSnapshotFile, + kOperationCopy, + kOperationCopyInForeignFile, + kOperationMove, + kOperationDirectoryExists, + kOperationFileExists, + kOperationGetMetadata, + kOperationReadDirectory, + kOperationRemove, + kOperationWrite, + kOperationTruncate, + kOperationTouchFile, + kOperationOpenFile, + kOperationCloseFile, + kOperationGetLocalPath, + kOperationCancel, + }; +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_operation_context.cc b/chromium/webkit/browser/fileapi/file_system_operation_context.cc new file mode 100644 index 00000000000..dd333ba6773 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_operation_context.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_operation_context.h" + +#include "base/sequenced_task_runner.h" +#include "webkit/browser/fileapi/file_system_context.h" + +namespace fileapi { + +FileSystemOperationContext::FileSystemOperationContext( + FileSystemContext* context) + : file_system_context_(context), + task_runner_(file_system_context_->default_file_task_runner()), + allowed_bytes_growth_(0), + quota_limit_type_(quota::kQuotaLimitTypeUnknown) {} + +FileSystemOperationContext::FileSystemOperationContext( + FileSystemContext* context, + base::SequencedTaskRunner* task_runner) + : file_system_context_(context), + task_runner_(task_runner), + allowed_bytes_growth_(0), + quota_limit_type_(quota::kQuotaLimitTypeUnknown) {} + +FileSystemOperationContext::~FileSystemOperationContext() { + DetachUserDataThread(); + setter_thread_checker_.DetachFromThread(); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_operation_context.h b/chromium/webkit/browser/fileapi/file_system_operation_context.h new file mode 100644 index 00000000000..4d175108482 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_operation_context.h @@ -0,0 +1,105 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_CONTEXT_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_CONTEXT_H_ + +#include "base/files/file_path.h" +#include "base/supports_user_data.h" +#include "base/threading/thread_checker.h" +#include "webkit/browser/fileapi/task_runner_bound_observer_list.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/quota/quota_types.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace fileapi { + +class FileSystemContext; + +// A context class which is carried around by FileSystemOperation and +// its delegated tasks. It is valid to reuse one context instance across +// multiple operations as far as those operations are supposed to share +// the same context (e.g. use the same task runner, share the quota etc). +// Note that the remaining quota bytes (allowed_bytes_growth) may be +// updated during the execution of write operations. +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileSystemOperationContext + : public base::SupportsUserData { + public: + explicit FileSystemOperationContext(FileSystemContext* context); + + // Specifies |task_runner| which the operation is performed on. + // The backend of |task_runner| must outlive the IO thread. + FileSystemOperationContext(FileSystemContext* context, + base::SequencedTaskRunner* task_runner); + + virtual ~FileSystemOperationContext(); + + FileSystemContext* file_system_context() const { + return file_system_context_.get(); + } + + // Updates the current remaining quota. + // This can be called to update the remaining quota during the operation. + void set_allowed_bytes_growth(const int64& allowed_bytes_growth) { + allowed_bytes_growth_ = allowed_bytes_growth; + } + + // Returns the current remaining quota. + int64 allowed_bytes_growth() const { return allowed_bytes_growth_; } + quota::QuotaLimitType quota_limit_type() const { return quota_limit_type_; } + base::SequencedTaskRunner* task_runner() const { return task_runner_.get(); } + const base::FilePath& root_path() const { return root_path_; } + + ChangeObserverList* change_observers() { return &change_observers_; } + UpdateObserverList* update_observers() { return &update_observers_; } + + // Following setters should be called only on the same thread as the + // FileSystemOperationContext is created (i.e. are not supposed be updated + // after the context's passed onto other task runners). + void set_change_observers(const ChangeObserverList& list) { + DCHECK(setter_thread_checker_.CalledOnValidThread()); + change_observers_ = list; + } + void set_update_observers(const UpdateObserverList& list) { + DCHECK(setter_thread_checker_.CalledOnValidThread()); + update_observers_ = list; + } + void set_quota_limit_type(quota::QuotaLimitType limit_type) { + DCHECK(setter_thread_checker_.CalledOnValidThread()); + quota_limit_type_ = limit_type; + } + void set_root_path(const base::FilePath& root_path) { + DCHECK(setter_thread_checker_.CalledOnValidThread()); + root_path_ = root_path; + } + + private: + scoped_refptr<FileSystemContext> file_system_context_; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + // The current remaining quota, used by ObfuscatedFileUtil. + int64 allowed_bytes_growth_; + + // The current quota limit type, used by ObfuscatedFileUtil. + quota::QuotaLimitType quota_limit_type_; + + // Observers attached to this context. + ChangeObserverList change_observers_; + UpdateObserverList update_observers_; + + // Root path for the operation, used by LocalFileUtil. + base::FilePath root_path_; + + // Used to check its setters are not called on arbitrary thread. + base::ThreadChecker setter_thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemOperationContext); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_CONTEXT_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_operation_impl.cc b/chromium/webkit/browser/fileapi/file_system_operation_impl.cc new file mode 100644 index 00000000000..60f79901923 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_operation_impl.cc @@ -0,0 +1,539 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_operation_impl.h" + +#include "base/bind.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "net/base/escape.h" +#include "net/url_request/url_request.h" +#include "webkit/browser/fileapi/async_file_util.h" +#include "webkit/browser/fileapi/copy_or_move_operation_delegate.h" +#include "webkit/browser/fileapi/file_observers.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/file_writer_delegate.h" +#include "webkit/browser/fileapi/remove_operation_delegate.h" +#include "webkit/browser/fileapi/sandbox_file_stream_writer.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/blob/shareable_file_reference.h" +#include "webkit/common/fileapi/file_system_types.h" +#include "webkit/common/fileapi/file_system_util.h" +#include "webkit/common/quota/quota_types.h" + +using webkit_blob::ScopedFile; + +namespace fileapi { + +FileSystemOperationImpl::FileSystemOperationImpl( + const FileSystemURL& url, + FileSystemContext* file_system_context, + scoped_ptr<FileSystemOperationContext> operation_context) + : file_system_context_(file_system_context), + operation_context_(operation_context.Pass()), + async_file_util_(NULL), + peer_handle_(base::kNullProcessHandle), + pending_operation_(kOperationNone) { + DCHECK(operation_context_.get()); + operation_context_->DetachUserDataThread(); + async_file_util_ = file_system_context_->GetAsyncFileUtil(url.type()); + DCHECK(async_file_util_); +} + +FileSystemOperationImpl::~FileSystemOperationImpl() { +} + +void FileSystemOperationImpl::CreateFile(const FileSystemURL& url, + bool exclusive, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationCreateFile)); + GetUsageAndQuotaThenRunTask( + url, + base::Bind(&FileSystemOperationImpl::DoCreateFile, + AsWeakPtr(), url, callback, exclusive), + base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED)); +} + +void FileSystemOperationImpl::CreateDirectory(const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationCreateDirectory)); + GetUsageAndQuotaThenRunTask( + url, + base::Bind(&FileSystemOperationImpl::DoCreateDirectory, + AsWeakPtr(), url, callback, exclusive, recursive), + base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED)); +} + +void FileSystemOperationImpl::Copy(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationCopy)); + DCHECK(!recursive_operation_delegate_); + recursive_operation_delegate_.reset( + new CopyOrMoveOperationDelegate( + file_system_context(), + src_url, dest_url, + CopyOrMoveOperationDelegate::OPERATION_COPY, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + AsWeakPtr(), callback))); + recursive_operation_delegate_->RunRecursively(); +} + +void FileSystemOperationImpl::Move(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationMove)); + DCHECK(!recursive_operation_delegate_); + recursive_operation_delegate_.reset( + new CopyOrMoveOperationDelegate( + file_system_context(), + src_url, dest_url, + CopyOrMoveOperationDelegate::OPERATION_MOVE, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + AsWeakPtr(), callback))); + recursive_operation_delegate_->RunRecursively(); +} + +void FileSystemOperationImpl::DirectoryExists(const FileSystemURL& url, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationDirectoryExists)); + async_file_util_->GetFileInfo( + operation_context_.Pass(), url, + base::Bind(&FileSystemOperationImpl::DidDirectoryExists, + AsWeakPtr(), callback)); +} + +void FileSystemOperationImpl::FileExists(const FileSystemURL& url, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationFileExists)); + async_file_util_->GetFileInfo( + operation_context_.Pass(), url, + base::Bind(&FileSystemOperationImpl::DidFileExists, + AsWeakPtr(), callback)); +} + +void FileSystemOperationImpl::GetMetadata( + const FileSystemURL& url, const GetMetadataCallback& callback) { + DCHECK(SetPendingOperationType(kOperationGetMetadata)); + async_file_util_->GetFileInfo(operation_context_.Pass(), url, callback); +} + +void FileSystemOperationImpl::ReadDirectory( + const FileSystemURL& url, const ReadDirectoryCallback& callback) { + DCHECK(SetPendingOperationType(kOperationReadDirectory)); + async_file_util_->ReadDirectory( + operation_context_.Pass(), url, callback); +} + +void FileSystemOperationImpl::Remove(const FileSystemURL& url, + bool recursive, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationRemove)); + DCHECK(!recursive_operation_delegate_); + + if (recursive) { + // For recursive removal, try to delegate the operation to AsyncFileUtil + // first. If not supported, it is delegated to RemoveOperationDelegate + // in DidDeleteRecursively. + async_file_util_->DeleteRecursively( + operation_context_.Pass(), url, + base::Bind(&FileSystemOperationImpl::DidDeleteRecursively, + AsWeakPtr(), url, callback)); + return; + } + + recursive_operation_delegate_.reset( + new RemoveOperationDelegate( + file_system_context(), url, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + AsWeakPtr(), callback))); + recursive_operation_delegate_->Run(); +} + +void FileSystemOperationImpl::Write( + const FileSystemURL& url, + scoped_ptr<FileWriterDelegate> writer_delegate, + scoped_ptr<net::URLRequest> blob_request, + const WriteCallback& callback) { + DCHECK(SetPendingOperationType(kOperationWrite)); + file_writer_delegate_ = writer_delegate.Pass(); + file_writer_delegate_->Start( + blob_request.Pass(), + base::Bind(&FileSystemOperationImpl::DidWrite, AsWeakPtr(), + url, callback)); +} + +void FileSystemOperationImpl::Truncate(const FileSystemURL& url, int64 length, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationTruncate)); + GetUsageAndQuotaThenRunTask( + url, + base::Bind(&FileSystemOperationImpl::DoTruncate, + AsWeakPtr(), url, callback, length), + base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED)); +} + +void FileSystemOperationImpl::TouchFile(const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationTouchFile)); + async_file_util_->Touch( + operation_context_.Pass(), url, + last_access_time, last_modified_time, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + AsWeakPtr(), callback)); +} + +void FileSystemOperationImpl::OpenFile(const FileSystemURL& url, + int file_flags, + base::ProcessHandle peer_handle, + const OpenFileCallback& callback) { + DCHECK(SetPendingOperationType(kOperationOpenFile)); + peer_handle_ = peer_handle; + + if (file_flags & ( + (base::PLATFORM_FILE_ENUMERATE | base::PLATFORM_FILE_TEMPORARY | + base::PLATFORM_FILE_HIDDEN))) { + callback.Run(base::PLATFORM_FILE_ERROR_FAILED, + base::kInvalidPlatformFileValue, + base::Closure(), + base::kNullProcessHandle); + return; + } + GetUsageAndQuotaThenRunTask( + url, + base::Bind(&FileSystemOperationImpl::DoOpenFile, + AsWeakPtr(), + url, callback, file_flags), + base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED, + base::kInvalidPlatformFileValue, + base::Closure(), + base::kNullProcessHandle)); +} + +// We can only get here on a write or truncate that's not yet completed. +// We don't support cancelling any other operation at this time. +void FileSystemOperationImpl::Cancel(const StatusCallback& cancel_callback) { + DCHECK(cancel_callback_.is_null()); + cancel_callback_ = cancel_callback; + + if (file_writer_delegate_.get()) { + DCHECK_EQ(kOperationWrite, pending_operation_); + // This will call DidWrite() with ABORT status code. + file_writer_delegate_->Cancel(); + } else { + // For truncate we have no way to cancel the inflight operation (for now). + // Let it just run and dispatch cancel callback later. + DCHECK_EQ(kOperationTruncate, pending_operation_); + } +} + +FileSystemOperationImpl* FileSystemOperationImpl::AsFileSystemOperationImpl() { + return this; +} + +base::PlatformFileError FileSystemOperationImpl::SyncGetPlatformPath( + const FileSystemURL& url, + base::FilePath* platform_path) { + DCHECK(SetPendingOperationType(kOperationGetLocalPath)); + FileSystemFileUtil* file_util = file_system_context()->GetFileUtil( + url.type()); + if (!file_util) + return base::PLATFORM_FILE_ERROR_INVALID_OPERATION; + file_util->GetLocalFilePath(operation_context_.get(), url, platform_path); + return base::PLATFORM_FILE_OK; +} + +void FileSystemOperationImpl::CreateSnapshotFile( + const FileSystemURL& url, + const SnapshotFileCallback& callback) { + DCHECK(SetPendingOperationType(kOperationCreateSnapshotFile)); + async_file_util_->CreateSnapshotFile( + operation_context_.Pass(), url, callback); +} + +void FileSystemOperationImpl::CopyInForeignFile( + const base::FilePath& src_local_disk_file_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationCopyInForeignFile)); + GetUsageAndQuotaThenRunTask( + dest_url, + base::Bind(&FileSystemOperationImpl::DoCopyInForeignFile, + AsWeakPtr(), src_local_disk_file_path, dest_url, + callback), + base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED)); +} + +void FileSystemOperationImpl::RemoveFile( + const FileSystemURL& url, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationRemove)); + async_file_util_->DeleteFile( + operation_context_.Pass(), url, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + AsWeakPtr(), callback)); +} + +void FileSystemOperationImpl::RemoveDirectory( + const FileSystemURL& url, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationRemove)); + async_file_util_->DeleteDirectory( + operation_context_.Pass(), url, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + AsWeakPtr(), callback)); +} + +void FileSystemOperationImpl::CopyFileLocal( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationCopy)); + DCHECK(src_url.IsInSameFileSystem(dest_url)); + GetUsageAndQuotaThenRunTask( + dest_url, + base::Bind(&FileSystemOperationImpl::DoCopyFileLocal, + AsWeakPtr(), src_url, dest_url, callback), + base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED)); +} + +void FileSystemOperationImpl::MoveFileLocal( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + DCHECK(SetPendingOperationType(kOperationMove)); + DCHECK(src_url.IsInSameFileSystem(dest_url)); + GetUsageAndQuotaThenRunTask( + dest_url, + base::Bind(&FileSystemOperationImpl::DoMoveFileLocal, + AsWeakPtr(), src_url, dest_url, callback), + base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED)); +} + +void FileSystemOperationImpl::GetUsageAndQuotaThenRunTask( + const FileSystemURL& url, + const base::Closure& task, + const base::Closure& error_callback) { + quota::QuotaManagerProxy* quota_manager_proxy = + file_system_context()->quota_manager_proxy(); + if (!quota_manager_proxy || + !file_system_context()->GetQuotaUtil(url.type())) { + // If we don't have the quota manager or the requested filesystem type + // does not support quota, we should be able to let it go. + operation_context_->set_allowed_bytes_growth(kint64max); + task.Run(); + return; + } + + DCHECK(quota_manager_proxy); + DCHECK(quota_manager_proxy->quota_manager()); + quota_manager_proxy->quota_manager()->GetUsageAndQuota( + url.origin(), + FileSystemTypeToQuotaStorageType(url.type()), + base::Bind(&FileSystemOperationImpl::DidGetUsageAndQuotaAndRunTask, + AsWeakPtr(), task, error_callback)); +} + +void FileSystemOperationImpl::DidGetUsageAndQuotaAndRunTask( + const base::Closure& task, + const base::Closure& error_callback, + quota::QuotaStatusCode status, + int64 usage, int64 quota) { + if (status != quota::kQuotaStatusOk) { + LOG(WARNING) << "Got unexpected quota error : " << status; + error_callback.Run(); + return; + } + + operation_context_->set_allowed_bytes_growth(quota - usage); + task.Run(); +} + +void FileSystemOperationImpl::DoCreateFile( + const FileSystemURL& url, + const StatusCallback& callback, + bool exclusive) { + async_file_util_->EnsureFileExists( + operation_context_.Pass(), url, + base::Bind( + exclusive ? + &FileSystemOperationImpl::DidEnsureFileExistsExclusive : + &FileSystemOperationImpl::DidEnsureFileExistsNonExclusive, + AsWeakPtr(), callback)); +} + +void FileSystemOperationImpl::DoCreateDirectory( + const FileSystemURL& url, + const StatusCallback& callback, + bool exclusive, bool recursive) { + async_file_util_->CreateDirectory( + operation_context_.Pass(), + url, exclusive, recursive, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + AsWeakPtr(), callback)); +} + +void FileSystemOperationImpl::DoCopyFileLocal( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + async_file_util_->CopyFileLocal( + operation_context_.Pass(), src_url, dest_url, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + AsWeakPtr(), callback)); +} + +void FileSystemOperationImpl::DoMoveFileLocal( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + async_file_util_->MoveFileLocal( + operation_context_.Pass(), src_url, dest_url, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + AsWeakPtr(), callback)); +} + +void FileSystemOperationImpl::DoCopyInForeignFile( + const base::FilePath& src_local_disk_file_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + async_file_util_->CopyInForeignFile( + operation_context_.Pass(), + src_local_disk_file_path, dest_url, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + AsWeakPtr(), callback)); +} + +void FileSystemOperationImpl::DoTruncate(const FileSystemURL& url, + const StatusCallback& callback, + int64 length) { + async_file_util_->Truncate( + operation_context_.Pass(), url, length, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + AsWeakPtr(), callback)); +} + +void FileSystemOperationImpl::DoOpenFile(const FileSystemURL& url, + const OpenFileCallback& callback, + int file_flags) { + async_file_util_->CreateOrOpen( + operation_context_.Pass(), url, file_flags, + base::Bind(&FileSystemOperationImpl::DidOpenFile, + AsWeakPtr(), callback)); +} + +void FileSystemOperationImpl::DidEnsureFileExistsExclusive( + const StatusCallback& callback, + base::PlatformFileError rv, bool created) { + if (rv == base::PLATFORM_FILE_OK && !created) { + callback.Run(base::PLATFORM_FILE_ERROR_EXISTS); + } else { + DidFinishOperation(callback, rv); + } +} + +void FileSystemOperationImpl::DidEnsureFileExistsNonExclusive( + const StatusCallback& callback, + base::PlatformFileError rv, bool /* created */) { + DidFinishOperation(callback, rv); +} + +void FileSystemOperationImpl::DidFinishOperation( + const StatusCallback& callback, + base::PlatformFileError rv) { + if (!cancel_callback_.is_null()) { + DCHECK_EQ(kOperationTruncate, pending_operation_); + + StatusCallback cancel_callback = cancel_callback_; + callback.Run(base::PLATFORM_FILE_ERROR_ABORT); + cancel_callback.Run(base::PLATFORM_FILE_OK); + } else { + callback.Run(rv); + } +} + +void FileSystemOperationImpl::DidDirectoryExists( + const StatusCallback& callback, + base::PlatformFileError rv, + const base::PlatformFileInfo& file_info) { + if (rv == base::PLATFORM_FILE_OK && !file_info.is_directory) + rv = base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY; + callback.Run(rv); +} + +void FileSystemOperationImpl::DidFileExists( + const StatusCallback& callback, + base::PlatformFileError rv, + const base::PlatformFileInfo& file_info) { + if (rv == base::PLATFORM_FILE_OK && file_info.is_directory) + rv = base::PLATFORM_FILE_ERROR_NOT_A_FILE; + callback.Run(rv); +} + +void FileSystemOperationImpl::DidDeleteRecursively( + const FileSystemURL& url, + const StatusCallback& callback, + base::PlatformFileError rv) { + if (rv == base::PLATFORM_FILE_ERROR_INVALID_OPERATION) { + // Recursive removal is not supported on this platform. + DCHECK(!recursive_operation_delegate_); + recursive_operation_delegate_.reset( + new RemoveOperationDelegate( + file_system_context(), url, + base::Bind(&FileSystemOperationImpl::DidFinishOperation, + AsWeakPtr(), callback))); + recursive_operation_delegate_->RunRecursively(); + return; + } + + callback.Run(rv); +} + +void FileSystemOperationImpl::DidWrite( + const FileSystemURL& url, + const WriteCallback& write_callback, + base::PlatformFileError rv, + int64 bytes, + FileWriterDelegate::WriteProgressStatus write_status) { + const bool complete = ( + write_status != FileWriterDelegate::SUCCESS_IO_PENDING); + if (complete && write_status != FileWriterDelegate::ERROR_WRITE_NOT_STARTED) { + DCHECK(operation_context_); + operation_context_->change_observers()->Notify( + &FileChangeObserver::OnModifyFile, MakeTuple(url)); + } + + StatusCallback cancel_callback = cancel_callback_; + write_callback.Run(rv, bytes, complete); + if (!cancel_callback.is_null()) + cancel_callback.Run(base::PLATFORM_FILE_OK); +} + +void FileSystemOperationImpl::DidOpenFile( + const OpenFileCallback& callback, + base::PlatformFileError rv, + base::PassPlatformFile file, + const base::Closure& on_close_callback) { + if (rv == base::PLATFORM_FILE_OK) + CHECK_NE(base::kNullProcessHandle, peer_handle_); + callback.Run(rv, file.ReleaseValue(), on_close_callback, peer_handle_); +} + +bool FileSystemOperationImpl::SetPendingOperationType(OperationType type) { + if (pending_operation_ != kOperationNone) + return false; + pending_operation_ = type; + return true; +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_operation_impl.h b/chromium/webkit/browser/fileapi/file_system_operation_impl.h new file mode 100644 index 00000000000..293ac5178d8 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_operation_impl.h @@ -0,0 +1,263 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_IMPL_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_IMPL_H_ + +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "webkit/browser/fileapi/file_system_operation.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/file_writer_delegate.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/blob/scoped_file.h" +#include "webkit/common/quota/quota_types.h" + +namespace fileapi { + +class AsyncFileUtil; +class FileSystemContext; +class RecursiveOperationDelegate; + +// The default implementation of FileSystemOperation for file systems. +class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemOperationImpl + : public NON_EXPORTED_BASE(FileSystemOperation), + public base::SupportsWeakPtr<FileSystemOperationImpl> { + public: + // NOTE: This constructor should not be called outside FileSystemBackends; + // instead please consider using + // file_system_context->CreateFileSystemOperation() to instantiate + // an appropriate FileSystemOperation. + FileSystemOperationImpl( + const FileSystemURL& url, + FileSystemContext* file_system_context, + scoped_ptr<FileSystemOperationContext> operation_context); + + virtual ~FileSystemOperationImpl(); + + // FileSystemOperation overrides. + virtual void CreateFile(const FileSystemURL& url, + bool exclusive, + const StatusCallback& callback) OVERRIDE; + virtual void CreateDirectory(const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback) OVERRIDE; + virtual void Copy(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) OVERRIDE; + virtual void Move(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) OVERRIDE; + virtual void DirectoryExists(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void FileExists(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void GetMetadata(const FileSystemURL& url, + const GetMetadataCallback& callback) OVERRIDE; + virtual void ReadDirectory(const FileSystemURL& url, + const ReadDirectoryCallback& callback) OVERRIDE; + virtual void Remove(const FileSystemURL& url, bool recursive, + const StatusCallback& callback) OVERRIDE; + virtual void Write(const FileSystemURL& url, + scoped_ptr<FileWriterDelegate> writer_delegate, + scoped_ptr<net::URLRequest> blob_request, + const WriteCallback& callback) OVERRIDE; + virtual void Truncate(const FileSystemURL& url, int64 length, + const StatusCallback& callback) OVERRIDE; + virtual void TouchFile(const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) OVERRIDE; + virtual void OpenFile(const FileSystemURL& url, + int file_flags, + base::ProcessHandle peer_handle, + const OpenFileCallback& callback) OVERRIDE; + virtual void Cancel(const StatusCallback& cancel_callback) OVERRIDE; + virtual FileSystemOperationImpl* AsFileSystemOperationImpl() OVERRIDE; + virtual void CreateSnapshotFile( + const FileSystemURL& path, + const SnapshotFileCallback& callback) OVERRIDE; + + // Copies in a single file from a different filesystem. + // + // This returns: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_file_path| + // or the parent directory of |dest_url| does not exist. + // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + virtual void CopyInForeignFile(const base::FilePath& src_local_disk_path, + const FileSystemURL& dest_url, + const StatusCallback& callback); + + // Removes a single file. + // + // This returns: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist. + // - PLATFORM_FILE_ERROR_NOT_A_FILE if |url| is not a file. + // + void RemoveFile(const FileSystemURL& url, + const StatusCallback& callback); + + // Removes a single empty directory. + // + // This returns: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist. + // - PLATFORM_FILE_ERROR_NOT_A_DIRECTORY if |url| is not a directory. + // - PLATFORM_FILE_ERROR_NOT_EMPTY if |url| is not empty. + // + void RemoveDirectory(const FileSystemURL& url, + const StatusCallback& callback); + + // Copies a file from |src_url| to |dest_url|. + // This must be called for files that belong to the same filesystem + // (i.e. type() and origin() of the |src_url| and |dest_url| must match). + // + // This returns: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_url| + // or the parent directory of |dest_url| does not exist. + // - PLATFORM_FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file. + // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + void CopyFileLocal(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback); + + // Moves a local file from |src_url| to |dest_url|. + // This must be called for files that belong to the same filesystem + // (i.e. type() and origin() of the |src_url| and |dest_url| must match). + // + // This returns: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_url| + // or the parent directory of |dest_url| does not exist. + // - PLATFORM_FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file. + // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + void MoveFileLocal(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback); + + // Synchronously gets the platform path for the given |url|. + // This may fail if |file_system_context| returns NULL on GetFileUtil(). + // In such a case, base::PLATFORM_FILE_ERROR_INVALID_OPERATION will be + // returned. + base::PlatformFileError SyncGetPlatformPath(const FileSystemURL& url, + base::FilePath* platform_path); + + FileSystemContext* file_system_context() const { + return file_system_context_.get(); + } + + protected: + // Queries the quota and usage and then runs the given |task|. + // If an error occurs during the quota query it runs |error_callback| instead. + void GetUsageAndQuotaThenRunTask( + const FileSystemURL& url, + const base::Closure& task, + const base::Closure& error_callback); + + // Called after the quota info is obtained from the quota manager + // (which is triggered by GetUsageAndQuotaThenRunTask). + // Sets the quota info in the operation_context_ and then runs the given + // |task| if the returned quota status is successful, otherwise runs + // |error_callback|. + void DidGetUsageAndQuotaAndRunTask( + const base::Closure& task, + const base::Closure& error_callback, + quota::QuotaStatusCode status, + int64 usage, int64 quota); + + // The 'body' methods that perform the actual work (i.e. posting the + // file task on proxy_) after the quota check. + void DoCreateFile(const FileSystemURL& url, + const StatusCallback& callback, bool exclusive); + void DoCreateDirectory(const FileSystemURL& url, + const StatusCallback& callback, + bool exclusive, + bool recursive); + void DoCopyFileLocal(const FileSystemURL& src, + const FileSystemURL& dest, + const StatusCallback& callback); + void DoMoveFileLocal(const FileSystemURL& src, + const FileSystemURL& dest, + const StatusCallback& callback); + void DoCopyInForeignFile(const base::FilePath& src_local_disk_file_path, + const FileSystemURL& dest, + const StatusCallback& callback); + void DoTruncate(const FileSystemURL& url, + const StatusCallback& callback, int64 length); + void DoOpenFile(const FileSystemURL& url, + const OpenFileCallback& callback, int file_flags); + + // Callback for CreateFile for |exclusive|=true cases. + void DidEnsureFileExistsExclusive(const StatusCallback& callback, + base::PlatformFileError rv, + bool created); + + // Callback for CreateFile for |exclusive|=false cases. + void DidEnsureFileExistsNonExclusive(const StatusCallback& callback, + base::PlatformFileError rv, + bool created); + + void DidFinishOperation(const StatusCallback& callback, + base::PlatformFileError rv); + void DidDirectoryExists(const StatusCallback& callback, + base::PlatformFileError rv, + const base::PlatformFileInfo& file_info); + void DidFileExists(const StatusCallback& callback, + base::PlatformFileError rv, + const base::PlatformFileInfo& file_info); + void DidDeleteRecursively(const FileSystemURL& url, + const StatusCallback& callback, + base::PlatformFileError rv); + void DidWrite(const FileSystemURL& url, + const WriteCallback& callback, + base::PlatformFileError rv, + int64 bytes, + FileWriterDelegate::WriteProgressStatus write_status); + void DidOpenFile(const OpenFileCallback& callback, + base::PlatformFileError rv, + base::PassPlatformFile file, + const base::Closure& on_close_callback); + + // Used only for internal assertions. + // Returns false if there's another inflight pending operation. + bool SetPendingOperationType(OperationType type); + + scoped_refptr<FileSystemContext> file_system_context_; + + scoped_ptr<FileSystemOperationContext> operation_context_; + AsyncFileUtil* async_file_util_; // Not owned. + + scoped_ptr<FileWriterDelegate> file_writer_delegate_; + scoped_ptr<RecursiveOperationDelegate> recursive_operation_delegate_; + + StatusCallback cancel_callback_; + + // Used only by OpenFile, in order to clone the file handle back to the + // requesting process. + base::ProcessHandle peer_handle_; + + // A flag to make sure we call operation only once per instance. + OperationType pending_operation_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemOperationImpl); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_IMPL_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_operation_impl_unittest.cc b/chromium/webkit/browser/fileapi/file_system_operation_impl_unittest.cc new file mode 100644 index 00000000000..083fdeb7dbd --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_operation_impl_unittest.cc @@ -0,0 +1,1187 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_operation_impl.h" + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/stringprintf.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" +#include "webkit/browser/fileapi/mock_file_change_observer.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend.h" +#include "webkit/browser/fileapi/sandbox_file_system_test_helper.h" +#include "webkit/browser/quota/mock_quota_manager.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/blob/shareable_file_reference.h" +#include "webkit/common/fileapi/file_system_util.h" + +using quota::QuotaManager; +using quota::QuotaManagerProxy; +using webkit_blob::ShareableFileReference; + +namespace fileapi { + +namespace { + +const int kFileOperationStatusNotSet = 1; + +void AssertFileErrorEq(const tracked_objects::Location& from_here, + base::PlatformFileError expected, + base::PlatformFileError actual) { + ASSERT_EQ(expected, actual) << from_here.ToString(); +} + +} // namespace (anonymous) + +// Test class for FileSystemOperationImpl. +class FileSystemOperationImplTest + : public testing::Test { + public: + FileSystemOperationImplTest() + : status_(kFileOperationStatusNotSet), + weak_factory_(this) {} + + protected: + virtual void SetUp() OVERRIDE { + EXPECT_TRUE(base_.CreateUniqueTempDir()); + change_observers_ = MockFileChangeObserver::CreateList(&change_observer_); + + base::FilePath base_dir = base_.path().AppendASCII("filesystem"); + quota_manager_ = + new quota::MockQuotaManager(false /* is_incognito */, + base_dir, + base::MessageLoopProxy::current().get(), + base::MessageLoopProxy::current().get(), + NULL /* special storage policy */); + quota_manager_proxy_ = new quota::MockQuotaManagerProxy( + quota_manager(), base::MessageLoopProxy::current().get()); + sandbox_file_system_.SetUp(base_dir, quota_manager_proxy_.get()); + sandbox_file_system_.AddFileChangeObserver(&change_observer_); + } + + virtual void TearDown() OVERRIDE { + // Let the client go away before dropping a ref of the quota manager proxy. + quota_manager_proxy()->SimulateQuotaManagerDestroyed(); + quota_manager_ = NULL; + quota_manager_proxy_ = NULL; + sandbox_file_system_.TearDown(); + } + + FileSystemOperationRunner* operation_runner() { + return sandbox_file_system_.operation_runner(); + } + + int status() const { return status_; } + const base::PlatformFileInfo& info() const { return info_; } + const base::FilePath& path() const { return path_; } + const std::vector<DirectoryEntry>& entries() const { + return entries_; + } + + const ShareableFileReference* shareable_file_ref() const { + return shareable_file_ref_.get(); + } + + quota::MockQuotaManager* quota_manager() { + return static_cast<quota::MockQuotaManager*>(quota_manager_.get()); + } + + quota::MockQuotaManagerProxy* quota_manager_proxy() { + return static_cast<quota::MockQuotaManagerProxy*>( + quota_manager_proxy_.get()); + } + + FileSystemFileUtil* file_util() { + return sandbox_file_system_.file_util(); + } + + MockFileChangeObserver* change_observer() { + return &change_observer_; + } + + scoped_ptr<FileSystemOperationContext> NewContext() { + FileSystemOperationContext* context = + sandbox_file_system_.NewOperationContext(); + // Grant enough quota for all test cases. + context->set_allowed_bytes_growth(1000000); + return make_scoped_ptr(context); + } + + FileSystemURL URLForPath(const std::string& path) const { + return sandbox_file_system_.CreateURLFromUTF8(path); + } + + base::FilePath PlatformPath(const std::string& path) { + return sandbox_file_system_.GetLocalPath( + base::FilePath::FromUTF8Unsafe(path)); + } + + bool FileExists(const std::string& path) { + return AsyncFileTestHelper::FileExists( + sandbox_file_system_.file_system_context(), URLForPath(path), + AsyncFileTestHelper::kDontCheckSize); + } + + bool DirectoryExists(const std::string& path) { + return AsyncFileTestHelper::DirectoryExists( + sandbox_file_system_.file_system_context(), URLForPath(path)); + } + + FileSystemURL CreateFile(const std::string& path) { + FileSystemURL url = URLForPath(path); + bool created = false; + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->EnsureFileExists(NewContext().get(), + url, &created)); + EXPECT_TRUE(created); + return url; + } + + FileSystemURL CreateDirectory(const std::string& path) { + FileSystemURL url = URLForPath(path); + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->CreateDirectory(NewContext().get(), url, + false /* exclusive */, true)); + return url; + } + + int64 GetFileSize(const std::string& path) { + base::PlatformFileInfo info; + EXPECT_TRUE(file_util::GetFileInfo(PlatformPath(path), &info)); + return info.size; + } + + // Callbacks for recording test results. + FileSystemOperation::StatusCallback RecordStatusCallback() { + return base::Bind(&FileSystemOperationImplTest::DidFinish, + weak_factory_.GetWeakPtr()); + } + + FileSystemOperation::ReadDirectoryCallback + RecordReadDirectoryCallback() { + return base::Bind(&FileSystemOperationImplTest::DidReadDirectory, + weak_factory_.GetWeakPtr()); + } + + FileSystemOperation::GetMetadataCallback RecordMetadataCallback() { + return base::Bind(&FileSystemOperationImplTest::DidGetMetadata, + weak_factory_.GetWeakPtr()); + } + + FileSystemOperation::SnapshotFileCallback RecordSnapshotFileCallback() { + return base::Bind(&FileSystemOperationImplTest::DidCreateSnapshotFile, + weak_factory_.GetWeakPtr()); + } + + void DidFinish(base::PlatformFileError status) { + status_ = status; + } + + void DidReadDirectory( + base::PlatformFileError status, + const std::vector<DirectoryEntry>& entries, + bool /* has_more */) { + entries_ = entries; + status_ = status; + } + + void DidGetMetadata(base::PlatformFileError status, + const base::PlatformFileInfo& info) { + info_ = info; + status_ = status; + } + + void DidCreateSnapshotFile( + base::PlatformFileError status, + const base::PlatformFileInfo& info, + const base::FilePath& platform_path, + const scoped_refptr<ShareableFileReference>& shareable_file_ref) { + info_ = info; + path_ = platform_path; + status_ = status; + shareable_file_ref_ = shareable_file_ref; + } + + int64 GetDataSizeOnDisk() { + return sandbox_file_system_.ComputeCurrentOriginUsage() - + sandbox_file_system_.ComputeCurrentDirectoryDatabaseUsage(); + } + + void GetUsageAndQuota(int64* usage, int64* quota) { + quota::QuotaStatusCode status = + AsyncFileTestHelper::GetUsageAndQuota(quota_manager_.get(), + sandbox_file_system_.origin(), + sandbox_file_system_.type(), + usage, + quota); + base::MessageLoop::current()->RunUntilIdle(); + ASSERT_EQ(quota::kQuotaStatusOk, status); + } + + int64 ComputePathCost(const FileSystemURL& url) { + int64 base_usage; + GetUsageAndQuota(&base_usage, NULL); + + AsyncFileTestHelper::CreateFile( + sandbox_file_system_.file_system_context(), url); + operation_runner()->Remove(url, false /* recursive */, + base::Bind(&AssertFileErrorEq, FROM_HERE, + base::PLATFORM_FILE_OK)); + base::MessageLoop::current()->RunUntilIdle(); + change_observer()->ResetCount(); + + int64 total_usage; + GetUsageAndQuota(&total_usage, NULL); + return total_usage - base_usage; + } + + void GrantQuotaForCurrentUsage() { + int64 usage; + GetUsageAndQuota(&usage, NULL); + quota_manager()->SetQuota(sandbox_file_system_.origin(), + sandbox_file_system_.storage_type(), + usage); + } + + int64 GetUsage() { + int64 usage = 0; + GetUsageAndQuota(&usage, NULL); + return usage; + } + + void AddQuota(int64 quota_delta) { + int64 quota; + GetUsageAndQuota(NULL, "a); + quota_manager()->SetQuota(sandbox_file_system_.origin(), + sandbox_file_system_.storage_type(), + quota + quota_delta); + } + + base::MessageLoop message_loop_; + scoped_refptr<QuotaManager> quota_manager_; + scoped_refptr<QuotaManagerProxy> quota_manager_proxy_; + + // Common temp base for nondestructive uses. + base::ScopedTempDir base_; + + SandboxFileSystemTestHelper sandbox_file_system_; + + // For post-operation status. + int status_; + base::PlatformFileInfo info_; + base::FilePath path_; + std::vector<DirectoryEntry> entries_; + scoped_refptr<ShareableFileReference> shareable_file_ref_; + + MockFileChangeObserver change_observer_; + ChangeObserverList change_observers_; + + base::WeakPtrFactory<FileSystemOperationImplTest> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemOperationImplTest); +}; + +TEST_F(FileSystemOperationImplTest, TestMoveFailureSrcDoesntExist) { + change_observer()->ResetCount(); + operation_runner()->Move(URLForPath("a"), URLForPath("b"), + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveFailureContainsPath) { + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("src/dest")); + + operation_runner()->Move(src_dir, dest_dir, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveFailureSrcDirExistsDestFile) { + // Src exists and is dir. Dest is a file. + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + FileSystemURL dest_file(CreateFile("dest/file")); + + operation_runner()->Move(src_dir, dest_file, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, + TestMoveFailureSrcFileExistsDestNonEmptyDir) { + // Src exists and is a directory. Dest is a non-empty directory. + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + FileSystemURL dest_file(CreateFile("dest/file")); + + operation_runner()->Move(src_dir, dest_dir, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveFailureSrcFileExistsDestDir) { + // Src exists and is a file. Dest is a directory. + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL src_file(CreateFile("src/file")); + FileSystemURL dest_dir(CreateDirectory("dest")); + + operation_runner()->Move(src_file, dest_dir, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveFailureDestParentDoesntExist) { + // Dest. parent path does not exist. + FileSystemURL src_dir(CreateDirectory("src")); + operation_runner()->Move(src_dir, URLForPath("nonexistent/deset"), + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcFileAndOverwrite) { + FileSystemURL src_file(CreateFile("src")); + FileSystemURL dest_file(CreateFile("dest")); + + operation_runner()->Move(src_file, dest_file, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(FileExists("dest")); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + EXPECT_EQ(1, quota_manager_proxy()->notify_storage_accessed_count()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcFileAndNew) { + FileSystemURL src_file(CreateFile("src")); + + operation_runner()->Move(src_file, URLForPath("new"), RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(FileExists("new")); + + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_from_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcDirAndOverwrite) { + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + + operation_runner()->Move(src_dir, dest_dir, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_FALSE(DirectoryExists("src")); + + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); + EXPECT_EQ(2, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Make sure we've overwritten but not moved the source under the |dest_dir|. + EXPECT_TRUE(DirectoryExists("dest")); + EXPECT_FALSE(DirectoryExists("dest/src")); +} + +TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcDirAndNew) { + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + + operation_runner()->Move(src_dir, URLForPath("dest/new"), + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_FALSE(DirectoryExists("src")); + EXPECT_TRUE(DirectoryExists("dest/new")); + + EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcDirRecursive) { + FileSystemURL src_dir(CreateDirectory("src")); + CreateDirectory("src/dir"); + CreateFile("src/dir/sub"); + + FileSystemURL dest_dir(CreateDirectory("dest")); + + operation_runner()->Move(src_dir, dest_dir, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(DirectoryExists("dest/dir")); + EXPECT_TRUE(FileExists("dest/dir/sub")); + + EXPECT_EQ(3, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_from_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopyFailureSrcDoesntExist) { + operation_runner()->Copy(URLForPath("a"), URLForPath("b"), + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopyFailureContainsPath) { + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("src/dir")); + + operation_runner()->Copy(src_dir, dest_dir, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopyFailureSrcDirExistsDestFile) { + // Src exists and is dir. Dest is a file. + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + FileSystemURL dest_file(CreateFile("dest/file")); + + operation_runner()->Copy(src_dir, dest_file, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, + TestCopyFailureSrcFileExistsDestNonEmptyDir) { + // Src exists and is a directory. Dest is a non-empty directory. + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + FileSystemURL dest_file(CreateFile("dest/file")); + + operation_runner()->Copy(src_dir, dest_dir, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopyFailureSrcFileExistsDestDir) { + // Src exists and is a file. Dest is a directory. + FileSystemURL src_file(CreateFile("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + + operation_runner()->Copy(src_file, dest_dir, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopyFailureDestParentDoesntExist) { + // Dest. parent path does not exist. + FileSystemURL src_dir(CreateDirectory("src")); + + operation_runner()->Copy(src_dir, URLForPath("nonexistent/dest"), + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopyFailureByQuota) { + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL src_file(CreateFile("src/file")); + FileSystemURL dest_dir(CreateDirectory("dest")); + operation_runner()->Truncate(src_file, 6, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_EQ(6, GetFileSize("src/file")); + + FileSystemURL dest_file(URLForPath("dest/file")); + int64 dest_path_cost = ComputePathCost(dest_file); + GrantQuotaForCurrentUsage(); + AddQuota(6 + dest_path_cost - 1); + + operation_runner()->Copy(src_file, dest_file, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, status()); + EXPECT_FALSE(FileExists("dest/file")); +} + +TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcFileAndOverwrite) { + FileSystemURL src_file(CreateFile("src")); + FileSystemURL dest_file(CreateFile("dest")); + + operation_runner()->Copy(src_file, dest_file, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(FileExists("dest")); + EXPECT_EQ(2, quota_manager_proxy()->notify_storage_accessed_count()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcFileAndNew) { + FileSystemURL src_file(CreateFile("src")); + + operation_runner()->Copy(src_file, URLForPath("new"), + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(FileExists("new")); + EXPECT_EQ(2, quota_manager_proxy()->notify_storage_accessed_count()); + + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_from_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcDirAndOverwrite) { + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir(CreateDirectory("dest")); + + operation_runner()->Copy(src_dir, dest_dir, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + + // Make sure we've overwritten but not copied the source under the |dest_dir|. + EXPECT_TRUE(DirectoryExists("dest")); + EXPECT_FALSE(DirectoryExists("dest/src")); + EXPECT_GE(quota_manager_proxy()->notify_storage_accessed_count(), 3); + + EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcDirAndNew) { + FileSystemURL src_dir(CreateDirectory("src")); + FileSystemURL dest_dir_new(URLForPath("dest")); + + operation_runner()->Copy(src_dir, dest_dir_new, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(DirectoryExists("dest")); + EXPECT_GE(quota_manager_proxy()->notify_storage_accessed_count(), 2); + + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcDirRecursive) { + FileSystemURL src_dir(CreateDirectory("src")); + CreateDirectory("src/dir"); + CreateFile("src/dir/sub"); + + FileSystemURL dest_dir(CreateDirectory("dest")); + + operation_runner()->Copy(src_dir, dest_dir, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(DirectoryExists("dest/dir")); + EXPECT_TRUE(FileExists("dest/dir/sub")); + + // For recursive copy we may record multiple read access. + EXPECT_GE(quota_manager_proxy()->notify_storage_accessed_count(), 1); + + EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_from_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCopyInForeignFileSuccess) { + base::FilePath src_local_disk_file_path; + file_util::CreateTemporaryFile(&src_local_disk_file_path); + const char test_data[] = "foo"; + int data_size = ARRAYSIZE_UNSAFE(test_data); + file_util::WriteFile(src_local_disk_file_path, test_data, data_size); + + FileSystemURL dest_dir(CreateDirectory("dest")); + + int64 before_usage; + GetUsageAndQuota(&before_usage, NULL); + + // Check that the file copied and corresponding usage increased. + operation_runner()->CopyInForeignFile(src_local_disk_file_path, + URLForPath("dest/file"), + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(1, change_observer()->create_file_count()); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(FileExists("dest/file")); + int64 after_usage; + GetUsageAndQuota(&after_usage, NULL); + EXPECT_GT(after_usage, before_usage); + + // Compare contents of src and copied file. + char buffer[100]; + EXPECT_EQ(data_size, file_util::ReadFile(PlatformPath("dest/file"), + buffer, data_size)); + for (int i = 0; i < data_size; ++i) + EXPECT_EQ(test_data[i], buffer[i]); +} + +TEST_F(FileSystemOperationImplTest, TestCopyInForeignFileFailureByQuota) { + base::FilePath src_local_disk_file_path; + file_util::CreateTemporaryFile(&src_local_disk_file_path); + const char test_data[] = "foo"; + file_util::WriteFile(src_local_disk_file_path, test_data, + ARRAYSIZE_UNSAFE(test_data)); + + FileSystemURL dest_dir(CreateDirectory("dest")); + + GrantQuotaForCurrentUsage(); + operation_runner()->CopyInForeignFile(src_local_disk_file_path, + URLForPath("dest/file"), + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_FALSE(FileExists("dest/file")); + EXPECT_EQ(0, change_observer()->create_file_count()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, status()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateFileFailure) { + // Already existing file and exclusive true. + FileSystemURL file(CreateFile("file")); + operation_runner()->CreateFile(file, true, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateFileSuccessFileExists) { + // Already existing file and exclusive false. + FileSystemURL file(CreateFile("file")); + operation_runner()->CreateFile(file, false, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(FileExists("file")); + + // The file was already there; did nothing. + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateFileSuccessExclusive) { + // File doesn't exist but exclusive is true. + operation_runner()->CreateFile(URLForPath("new"), true, + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(FileExists("new")); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateFileSuccessFileDoesntExist) { + // Non existing file. + operation_runner()->CreateFile(URLForPath("nonexistent"), false, + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count()); +} + +TEST_F(FileSystemOperationImplTest, + TestCreateDirFailureDestParentDoesntExist) { + // Dest. parent path does not exist. + operation_runner()->CreateDirectory( + URLForPath("nonexistent/dir"), false, false, + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateDirFailureDirExists) { + // Exclusive and dir existing at path. + FileSystemURL dir(CreateDirectory("dir")); + operation_runner()->CreateDirectory(dir, true, false, + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateDirFailureFileExists) { + // Exclusive true and file existing at path. + FileSystemURL file(CreateFile("file")); + operation_runner()->CreateDirectory(file, true, false, + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateDirSuccess) { + // Dir exists and exclusive is false. + FileSystemURL dir(CreateDirectory("dir")); + operation_runner()->CreateDirectory(dir, false, false, + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Dir doesn't exist. + operation_runner()->CreateDirectory(URLForPath("new"), false, false, + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(DirectoryExists("new")); + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateDirSuccessExclusive) { + // Dir doesn't exist. + operation_runner()->CreateDirectory(URLForPath("new"), true, false, + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(DirectoryExists("new")); + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestExistsAndMetadataFailure) { + operation_runner()->GetMetadata(URLForPath("nonexistent"), + RecordMetadataCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + + operation_runner()->FileExists(URLForPath("nonexistent"), + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + + operation_runner()->DirectoryExists(URLForPath("nonexistent"), + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestExistsAndMetadataSuccess) { + FileSystemURL dir(CreateDirectory("dir")); + FileSystemURL file(CreateFile("dir/file")); + int read_access = 0; + + operation_runner()->DirectoryExists(dir, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + ++read_access; + + operation_runner()->GetMetadata(dir, RecordMetadataCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(info().is_directory); + ++read_access; + + operation_runner()->FileExists(file, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + ++read_access; + + operation_runner()->GetMetadata(file, RecordMetadataCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_FALSE(info().is_directory); + ++read_access; + + EXPECT_EQ(read_access, + quota_manager_proxy()->notify_storage_accessed_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestTypeMismatchErrors) { + FileSystemURL dir(CreateDirectory("dir")); + operation_runner()->FileExists(dir, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_FILE, status()); + + FileSystemURL file(CreateFile("file")); + operation_runner()->DirectoryExists(file, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY, status()); +} + +TEST_F(FileSystemOperationImplTest, TestReadDirFailure) { + // Path doesn't exist + operation_runner()->ReadDirectory(URLForPath("nonexistent"), + RecordReadDirectoryCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + + // File exists. + FileSystemURL file(CreateFile("file")); + operation_runner()->ReadDirectory(file, RecordReadDirectoryCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestReadDirSuccess) { + // parent_dir + // | | + // child_dir child_file + // Verify reading parent_dir. + FileSystemURL parent_dir(CreateDirectory("dir")); + FileSystemURL child_dir(CreateDirectory("dir/child_dir")); + FileSystemURL child_file(CreateFile("dir/child_file")); + + operation_runner()->ReadDirectory(parent_dir, RecordReadDirectoryCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_EQ(2u, entries().size()); + + for (size_t i = 0; i < entries().size(); ++i) { + if (entries()[i].is_directory) + EXPECT_EQ(FILE_PATH_LITERAL("child_dir"), entries()[i].name); + else + EXPECT_EQ(FILE_PATH_LITERAL("child_file"), entries()[i].name); + } + EXPECT_EQ(1, quota_manager_proxy()->notify_storage_accessed_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestRemoveFailure) { + // Path doesn't exist. + operation_runner()->Remove(URLForPath("nonexistent"), false /* recursive */, + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + + // It's an error to try to remove a non-empty directory if recursive flag + // is false. + // parent_dir + // | | + // child_dir child_file + // Verify deleting parent_dir. + FileSystemURL parent_dir(CreateDirectory("dir")); + FileSystemURL child_dir(CreateDirectory("dir/child_dir")); + FileSystemURL child_file(CreateFile("dir/child_file")); + + operation_runner()->Remove(parent_dir, false /* recursive */, + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestRemoveSuccess) { + FileSystemURL empty_dir(CreateDirectory("empty_dir")); + EXPECT_TRUE(DirectoryExists("empty_dir")); + operation_runner()->Remove(empty_dir, false /* recursive */, + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_FALSE(DirectoryExists("empty_dir")); + + EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestRemoveSuccessRecursive) { + // Removing a non-empty directory with recursive flag == true should be ok. + // parent_dir + // | | + // child_dir child_files + // | + // child_files + // + // Verify deleting parent_dir. + FileSystemURL parent_dir(CreateDirectory("dir")); + for (int i = 0; i < 8; ++i) + CreateFile(base::StringPrintf("dir/file-%d", i)); + FileSystemURL child_dir(CreateDirectory("dir/child_dir")); + for (int i = 0; i < 8; ++i) + CreateFile(base::StringPrintf("dir/child_dir/file-%d", i)); + + operation_runner()->Remove(parent_dir, true /* recursive */, + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_FALSE(DirectoryExists("parent_dir")); + + EXPECT_EQ(2, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_EQ(16, change_observer()->get_and_reset_remove_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(FileSystemOperationImplTest, TestTruncate) { + FileSystemURL file(CreateFile("file")); + base::FilePath platform_path = PlatformPath("file"); + + char test_data[] = "test data"; + int data_size = static_cast<int>(sizeof(test_data)); + EXPECT_EQ(data_size, + file_util::WriteFile(platform_path, test_data, data_size)); + + // Check that its length is the size of the data written. + operation_runner()->GetMetadata(file, RecordMetadataCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_FALSE(info().is_directory); + EXPECT_EQ(data_size, info().size); + + // Extend the file by truncating it. + int length = 17; + operation_runner()->Truncate(file, length, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Check that its length is now 17 and that it's all zeroes after the test + // data. + EXPECT_EQ(length, GetFileSize("file")); + char data[100]; + EXPECT_EQ(length, file_util::ReadFile(platform_path, data, length)); + for (int i = 0; i < length; ++i) { + if (i < static_cast<int>(sizeof(test_data))) + EXPECT_EQ(test_data[i], data[i]); + else + EXPECT_EQ(0, data[i]); + } + + // Shorten the file by truncating it. + length = 3; + operation_runner()->Truncate(file, length, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Check that its length is now 3 and that it contains only bits of test data. + EXPECT_EQ(length, GetFileSize("file")); + EXPECT_EQ(length, file_util::ReadFile(platform_path, data, length)); + for (int i = 0; i < length; ++i) + EXPECT_EQ(test_data[i], data[i]); + + // Truncate is not a 'read' access. (Here expected access count is 1 + // since we made 1 read access for GetMetadata.) + EXPECT_EQ(1, quota_manager_proxy()->notify_storage_accessed_count()); +} + +TEST_F(FileSystemOperationImplTest, TestTruncateFailureByQuota) { + FileSystemURL dir(CreateDirectory("dir")); + FileSystemURL file(CreateFile("dir/file")); + + GrantQuotaForCurrentUsage(); + AddQuota(10); + + operation_runner()->Truncate(file, 10, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + EXPECT_EQ(10, GetFileSize("dir/file")); + + operation_runner()->Truncate(file, 11, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + EXPECT_EQ(10, GetFileSize("dir/file")); +} + +TEST_F(FileSystemOperationImplTest, TestTouchFile) { + FileSystemURL file(CreateFile("file")); + base::FilePath platform_path = PlatformPath("file"); + + base::PlatformFileInfo info; + EXPECT_TRUE(file_util::GetFileInfo(platform_path, &info)); + EXPECT_FALSE(info.is_directory); + EXPECT_EQ(0, info.size); + const base::Time last_modified = info.last_modified; + const base::Time last_accessed = info.last_accessed; + + const base::Time new_modified_time = base::Time::UnixEpoch(); + const base::Time new_accessed_time = new_modified_time + + base::TimeDelta::FromHours(77); + ASSERT_NE(last_modified, new_modified_time); + ASSERT_NE(last_accessed, new_accessed_time); + + operation_runner()->TouchFile(file, new_accessed_time, new_modified_time, + RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + EXPECT_TRUE(file_util::GetFileInfo(platform_path, &info)); + // We compare as time_t here to lower our resolution, to avoid false + // negatives caused by conversion to the local filesystem's native + // representation and back. + EXPECT_EQ(new_modified_time.ToTimeT(), info.last_modified.ToTimeT()); + EXPECT_EQ(new_accessed_time.ToTimeT(), info.last_accessed.ToTimeT()); +} + +TEST_F(FileSystemOperationImplTest, TestCreateSnapshotFile) { + FileSystemURL dir(CreateDirectory("dir")); + + // Create a file for the testing. + operation_runner()->DirectoryExists(dir, RecordStatusCallback()); + FileSystemURL file(CreateFile("dir/file")); + operation_runner()->FileExists(file, RecordStatusCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + + // See if we can get a 'snapshot' file info for the file. + // Since FileSystemOperationImpl assumes the file exists in the local + // directory it should just returns the same metadata and platform_path + // as the file itself. + operation_runner()->CreateSnapshotFile(file, RecordSnapshotFileCallback()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_FALSE(info().is_directory); + EXPECT_EQ(PlatformPath("dir/file"), path()); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // The FileSystemOpration implementation does not create a + // shareable file reference. + EXPECT_EQ(NULL, shareable_file_ref()); +} + +TEST_F(FileSystemOperationImplTest, + TestMoveSuccessSrcDirRecursiveWithQuota) { + FileSystemURL src(CreateDirectory("src")); + int src_path_cost = GetUsage(); + + FileSystemURL dest(CreateDirectory("dest")); + FileSystemURL child_file1(CreateFile("src/file1")); + FileSystemURL child_file2(CreateFile("src/file2")); + FileSystemURL child_dir(CreateDirectory("src/dir")); + FileSystemURL grandchild_file1(CreateFile("src/dir/file1")); + FileSystemURL grandchild_file2(CreateFile("src/dir/file2")); + + int total_path_cost = GetUsage(); + EXPECT_EQ(0, GetDataSizeOnDisk()); + + operation_runner()->Truncate( + child_file1, 5000, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + operation_runner()->Truncate( + child_file2, 400, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + operation_runner()->Truncate( + grandchild_file1, 30, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + operation_runner()->Truncate( + grandchild_file2, 2, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + base::MessageLoop::current()->RunUntilIdle(); + + const int64 all_file_size = 5000 + 400 + 30 + 2; + EXPECT_EQ(all_file_size, GetDataSizeOnDisk()); + EXPECT_EQ(all_file_size + total_path_cost, GetUsage()); + + operation_runner()->Move( + src, dest, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_FALSE(DirectoryExists("src/dir")); + EXPECT_FALSE(FileExists("src/dir/file2")); + EXPECT_TRUE(DirectoryExists("dest/dir")); + EXPECT_TRUE(FileExists("dest/dir/file2")); + + EXPECT_EQ(all_file_size, GetDataSizeOnDisk()); + EXPECT_EQ(all_file_size + total_path_cost - src_path_cost, + GetUsage()); +} + +TEST_F(FileSystemOperationImplTest, + TestCopySuccessSrcDirRecursiveWithQuota) { + FileSystemURL src(CreateDirectory("src")); + FileSystemURL dest1(CreateDirectory("dest1")); + FileSystemURL dest2(CreateDirectory("dest2")); + + int64 usage = GetUsage(); + FileSystemURL child_file1(CreateFile("src/file1")); + FileSystemURL child_file2(CreateFile("src/file2")); + FileSystemURL child_dir(CreateDirectory("src/dir")); + int64 child_path_cost = GetUsage() - usage; + usage += child_path_cost; + + FileSystemURL grandchild_file1(CreateFile("src/dir/file1")); + FileSystemURL grandchild_file2(CreateFile("src/dir/file2")); + int64 total_path_cost = GetUsage(); + int64 grandchild_path_cost = total_path_cost - usage; + + EXPECT_EQ(0, GetDataSizeOnDisk()); + + operation_runner()->Truncate( + child_file1, 8000, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + operation_runner()->Truncate( + child_file2, 700, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + operation_runner()->Truncate( + grandchild_file1, 60, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + operation_runner()->Truncate( + grandchild_file2, 5, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + base::MessageLoop::current()->RunUntilIdle(); + + const int64 child_file_size = 8000 + 700; + const int64 grandchild_file_size = 60 + 5; + const int64 all_file_size = child_file_size + grandchild_file_size; + int64 expected_usage = all_file_size + total_path_cost; + + usage = GetUsage(); + EXPECT_EQ(all_file_size, GetDataSizeOnDisk()); + EXPECT_EQ(expected_usage, usage); + + // Copy src to dest1. + operation_runner()->Copy( + src, dest1, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + base::MessageLoop::current()->RunUntilIdle(); + + expected_usage += all_file_size + child_path_cost + grandchild_path_cost; + EXPECT_TRUE(DirectoryExists("src/dir")); + EXPECT_TRUE(FileExists("src/dir/file2")); + EXPECT_TRUE(DirectoryExists("dest1/dir")); + EXPECT_TRUE(FileExists("dest1/dir/file2")); + + EXPECT_EQ(2 * all_file_size, GetDataSizeOnDisk()); + EXPECT_EQ(expected_usage, GetUsage()); + + // Copy src/dir to dest2. + operation_runner()->Copy( + child_dir, dest2, + base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK)); + base::MessageLoop::current()->RunUntilIdle(); + + expected_usage += grandchild_file_size + grandchild_path_cost; + usage = GetUsage(); + EXPECT_EQ(2 * child_file_size + 3 * grandchild_file_size, + GetDataSizeOnDisk()); + EXPECT_EQ(expected_usage, usage); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_operation_impl_write_unittest.cc b/chromium/webkit/browser/fileapi/file_system_operation_impl_write_unittest.cc new file mode 100644 index 00000000000..d8ecc280fc8 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_operation_impl_write_unittest.cc @@ -0,0 +1,341 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <vector> + +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_job.h" +#include "net/url_request/url_request_job_factory_impl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" +#include "webkit/browser/blob/blob_storage_controller.h" +#include "webkit/browser/blob/blob_url_request_job.h" +#include "webkit/browser/blob/mock_blob_url_request_context.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" +#include "webkit/browser/fileapi/local_file_util.h" +#include "webkit/browser/fileapi/mock_file_change_observer.h" +#include "webkit/browser/fileapi/mock_file_system_context.h" +#include "webkit/browser/fileapi/test_file_system_backend.h" +#include "webkit/browser/quota/mock_quota_manager.h" +#include "webkit/common/blob/blob_data.h" +#include "webkit/common/fileapi/file_system_util.h" + +using webkit_blob::MockBlobURLRequestContext; +using webkit_blob::ScopedTextBlob; + +namespace fileapi { + +namespace { + +const GURL kOrigin("http://example.com"); +const FileSystemType kFileSystemType = kFileSystemTypeTest; + +void AssertStatusEq(base::PlatformFileError expected, + base::PlatformFileError actual) { + ASSERT_EQ(expected, actual); +} + +} // namespace + +class FileSystemOperationImplWriteTest + : public testing::Test { + public: + FileSystemOperationImplWriteTest() + : loop_(base::MessageLoop::TYPE_IO), + status_(base::PLATFORM_FILE_OK), + cancel_status_(base::PLATFORM_FILE_ERROR_FAILED), + bytes_written_(0), + complete_(false), + weak_factory_(this) { + change_observers_ = MockFileChangeObserver::CreateList(&change_observer_); + } + + virtual void SetUp() { + ASSERT_TRUE(dir_.CreateUniqueTempDir()); + + quota_manager_ = + new quota::MockQuotaManager(false /* is_incognito */, + dir_.path(), + base::MessageLoopProxy::current().get(), + base::MessageLoopProxy::current().get(), + NULL /* special storage policy */); + virtual_path_ = base::FilePath(FILE_PATH_LITERAL("temporary file")); + + file_system_context_ = CreateFileSystemContextForTesting( + quota_manager_->proxy(), dir_.path()); + url_request_context_.reset( + new MockBlobURLRequestContext(file_system_context_.get())); + + file_system_context_->operation_runner()->CreateFile( + URLForPath(virtual_path_), true /* exclusive */, + base::Bind(&AssertStatusEq, base::PLATFORM_FILE_OK)); + + static_cast<TestFileSystemBackend*>( + file_system_context_->GetFileSystemBackend(kFileSystemType)) + ->AddFileChangeObserver(change_observer()); + } + + virtual void TearDown() { + quota_manager_ = NULL; + file_system_context_ = NULL; + base::MessageLoop::current()->RunUntilIdle(); + } + + base::PlatformFileError status() const { return status_; } + base::PlatformFileError cancel_status() const { return cancel_status_; } + void add_bytes_written(int64 bytes, bool complete) { + bytes_written_ += bytes; + EXPECT_FALSE(complete_); + complete_ = complete; + } + int64 bytes_written() const { return bytes_written_; } + bool complete() const { return complete_; } + + protected: + const ChangeObserverList& change_observers() const { + return change_observers_; + } + + MockFileChangeObserver* change_observer() { + return &change_observer_; + } + + FileSystemURL URLForPath(const base::FilePath& path) const { + return file_system_context_->CreateCrackedFileSystemURL( + kOrigin, kFileSystemType, path); + } + + // Callback function for recording test results. + FileSystemOperation::WriteCallback RecordWriteCallback() { + return base::Bind(&FileSystemOperationImplWriteTest::DidWrite, + weak_factory_.GetWeakPtr()); + } + + FileSystemOperation::StatusCallback RecordCancelCallback() { + return base::Bind(&FileSystemOperationImplWriteTest::DidCancel, + weak_factory_.GetWeakPtr()); + } + + void DidWrite(base::PlatformFileError status, int64 bytes, bool complete) { + if (status == base::PLATFORM_FILE_OK) { + add_bytes_written(bytes, complete); + if (complete) + base::MessageLoop::current()->Quit(); + } else { + EXPECT_FALSE(complete_); + EXPECT_EQ(status_, base::PLATFORM_FILE_OK); + complete_ = true; + status_ = status; + if (base::MessageLoop::current()->is_running()) + base::MessageLoop::current()->Quit(); + } + } + + void DidCancel(base::PlatformFileError status) { + cancel_status_ = status; + } + + const MockBlobURLRequestContext& url_request_context() const { + return *url_request_context_; + } + + scoped_refptr<FileSystemContext> file_system_context_; + scoped_refptr<quota::MockQuotaManager> quota_manager_; + + base::MessageLoop loop_; + + base::ScopedTempDir dir_; + base::FilePath virtual_path_; + + // For post-operation status. + base::PlatformFileError status_; + base::PlatformFileError cancel_status_; + int64 bytes_written_; + bool complete_; + + scoped_ptr<MockBlobURLRequestContext> url_request_context_; + + MockFileChangeObserver change_observer_; + ChangeObserverList change_observers_; + + base::WeakPtrFactory<FileSystemOperationImplWriteTest> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemOperationImplWriteTest); +}; + +TEST_F(FileSystemOperationImplWriteTest, TestWriteSuccess) { + const GURL blob_url("blob:success"); + ScopedTextBlob blob(url_request_context(), blob_url, "Hello, world!\n"); + + file_system_context_->operation_runner()->Write( + &url_request_context(), URLForPath(virtual_path_), blob_url, + 0, RecordWriteCallback()); + base::MessageLoop::current()->Run(); + + EXPECT_EQ(14, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(complete()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); +} + +TEST_F(FileSystemOperationImplWriteTest, TestWriteZero) { + GURL blob_url("blob:zero"); + scoped_refptr<webkit_blob::BlobData> blob_data(new webkit_blob::BlobData()); + + url_request_context().blob_storage_controller() + ->AddFinishedBlob(blob_url, blob_data.get()); + + file_system_context_->operation_runner()->Write( + &url_request_context(), URLForPath(virtual_path_), + blob_url, 0, RecordWriteCallback()); + base::MessageLoop::current()->Run(); + + url_request_context().blob_storage_controller()->RemoveBlob(blob_url); + + EXPECT_EQ(0, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, status()); + EXPECT_TRUE(complete()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); +} + +TEST_F(FileSystemOperationImplWriteTest, TestWriteInvalidBlobUrl) { + file_system_context_->operation_runner()->Write( + &url_request_context(), URLForPath(virtual_path_), + GURL("blob:invalid"), 0, RecordWriteCallback()); + base::MessageLoop::current()->Run(); + + EXPECT_EQ(0, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_FAILED, status()); + EXPECT_TRUE(complete()); + + EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count()); +} + +TEST_F(FileSystemOperationImplWriteTest, TestWriteInvalidFile) { + GURL blob_url("blob:writeinvalidfile"); + ScopedTextBlob blob(url_request_context(), blob_url, + "It\'ll not be written."); + + file_system_context_->operation_runner()->Write( + &url_request_context(), + URLForPath(base::FilePath(FILE_PATH_LITERAL("nonexist"))), + blob_url, 0, RecordWriteCallback()); + base::MessageLoop::current()->Run(); + + EXPECT_EQ(0, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status()); + EXPECT_TRUE(complete()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); +} + +TEST_F(FileSystemOperationImplWriteTest, TestWriteDir) { + base::FilePath virtual_dir_path(FILE_PATH_LITERAL("d")); + file_system_context_->operation_runner()->CreateDirectory( + URLForPath(virtual_dir_path), + true /* exclusive */, false /* recursive */, + base::Bind(&AssertStatusEq, base::PLATFORM_FILE_OK)); + + GURL blob_url("blob:writedir"); + ScopedTextBlob blob(url_request_context(), blob_url, + "It\'ll not be written, too."); + + file_system_context_->operation_runner()->Write( + &url_request_context(), URLForPath(virtual_dir_path), + blob_url, 0, RecordWriteCallback()); + base::MessageLoop::current()->Run(); + + EXPECT_EQ(0, bytes_written()); + // TODO(kinuko): This error code is platform- or fileutil- dependent + // right now. Make it return PLATFORM_FILE_ERROR_NOT_A_FILE in every case. + EXPECT_TRUE(status() == base::PLATFORM_FILE_ERROR_NOT_A_FILE || + status() == base::PLATFORM_FILE_ERROR_ACCESS_DENIED || + status() == base::PLATFORM_FILE_ERROR_FAILED); + EXPECT_TRUE(complete()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); +} + +TEST_F(FileSystemOperationImplWriteTest, TestWriteFailureByQuota) { + GURL blob_url("blob:success"); + ScopedTextBlob blob(url_request_context(), blob_url, "Hello, world!\n"); + + quota_manager_->SetQuota( + kOrigin, FileSystemTypeToQuotaStorageType(kFileSystemType), 10); + file_system_context_->operation_runner()->Write( + &url_request_context(), URLForPath(virtual_path_), blob_url, + 0, RecordWriteCallback()); + base::MessageLoop::current()->Run(); + + EXPECT_EQ(10, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, status()); + EXPECT_TRUE(complete()); + + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); +} + +TEST_F(FileSystemOperationImplWriteTest, TestImmediateCancelSuccessfulWrite) { + GURL blob_url("blob:success"); + ScopedTextBlob blob(url_request_context(), blob_url, "Hello, world!\n"); + + FileSystemOperationRunner::OperationID id = + file_system_context_->operation_runner()->Write( + &url_request_context(), URLForPath(virtual_path_), + blob_url, 0, RecordWriteCallback()); + file_system_context_->operation_runner()->Cancel(id, RecordCancelCallback()); + // We use RunAllPendings() instead of Run() here, because we won't dispatch + // callbacks after Cancel() is issued (so no chance to Quit) nor do we need + // to run another write cycle. + base::MessageLoop::current()->RunUntilIdle(); + + // Issued Cancel() before receiving any response from Write(), + // so nothing should have happen. + EXPECT_EQ(0, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_ABORT, status()); + EXPECT_EQ(base::PLATFORM_FILE_OK, cancel_status()); + EXPECT_TRUE(complete()); + + EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count()); +} + +TEST_F(FileSystemOperationImplWriteTest, TestImmediateCancelFailingWrite) { + GURL blob_url("blob:writeinvalidfile"); + ScopedTextBlob blob(url_request_context(), blob_url, + "It\'ll not be written."); + + FileSystemOperationRunner::OperationID id = + file_system_context_->operation_runner()->Write( + &url_request_context(), + URLForPath(base::FilePath(FILE_PATH_LITERAL("nonexist"))), + blob_url, 0, RecordWriteCallback()); + file_system_context_->operation_runner()->Cancel(id, RecordCancelCallback()); + // We use RunAllPendings() instead of Run() here, because we won't dispatch + // callbacks after Cancel() is issued (so no chance to Quit) nor do we need + // to run another write cycle. + base::MessageLoop::current()->RunUntilIdle(); + + // Issued Cancel() before receiving any response from Write(), + // so nothing should have happen. + EXPECT_EQ(0, bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_ABORT, status()); + EXPECT_EQ(base::PLATFORM_FILE_OK, cancel_status()); + EXPECT_TRUE(complete()); + + EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count()); +} + +// TODO(ericu,dmikurube,kinuko): Add more tests for cancel cases. + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_operation_runner.cc b/chromium/webkit/browser/fileapi/file_system_operation_runner.cc new file mode 100644 index 00000000000..351536b6226 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_operation_runner.cc @@ -0,0 +1,563 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_operation_runner.h" + +#include "base/bind.h" +#include "net/url_request/url_request_context.h" +#include "webkit/browser/fileapi/file_observers.h" +#include "webkit/browser/fileapi/file_stream_writer.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_impl.h" +#include "webkit/browser/fileapi/file_writer_delegate.h" +#include "webkit/common/blob/shareable_file_reference.h" + +namespace fileapi { + +typedef FileSystemOperationRunner::OperationID OperationID; + +const OperationID FileSystemOperationRunner::kErrorOperationID = -1; + +FileSystemOperationRunner::~FileSystemOperationRunner() { +} + +void FileSystemOperationRunner::Shutdown() { + operations_.Clear(); +} + +OperationID FileSystemOperationRunner::CreateFile( + const FileSystemURL& url, + bool exclusive, + const StatusCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + if (!operation) { + callback.Run(error); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + PrepareForWrite(id, url); + operation->CreateFile( + url, exclusive, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::CreateDirectory( + const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + if (!operation) { + callback.Run(error); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + PrepareForWrite(id, url); + operation->CreateDirectory( + url, exclusive, recursive, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::Copy( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(dest_url, &error); + if (!operation) { + callback.Run(error); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + PrepareForWrite(id, dest_url); + PrepareForRead(id, src_url); + operation->Copy( + src_url, dest_url, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::Move( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(dest_url, &error); + if (!operation) { + callback.Run(error); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + PrepareForWrite(id, dest_url); + PrepareForWrite(id, src_url); + operation->Move( + src_url, dest_url, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::DirectoryExists( + const FileSystemURL& url, + const StatusCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + if (!operation) { + callback.Run(error); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + PrepareForRead(id, url); + operation->DirectoryExists( + url, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::FileExists( + const FileSystemURL& url, + const StatusCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + if (!operation) { + callback.Run(error); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + PrepareForRead(id, url); + operation->FileExists( + url, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::GetMetadata( + const FileSystemURL& url, + const GetMetadataCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + if (!operation) { + callback.Run(error, base::PlatformFileInfo()); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + PrepareForRead(id, url); + operation->GetMetadata( + url, + base::Bind(&FileSystemOperationRunner::DidGetMetadata, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::ReadDirectory( + const FileSystemURL& url, + const ReadDirectoryCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + if (!operation) { + callback.Run(error, std::vector<DirectoryEntry>(), false); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + PrepareForRead(id, url); + operation->ReadDirectory( + url, + base::Bind(&FileSystemOperationRunner::DidReadDirectory, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::Remove( + const FileSystemURL& url, bool recursive, + const StatusCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + if (!operation) { + callback.Run(error); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + PrepareForWrite(id, url); + operation->Remove( + url, recursive, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::Write( + const net::URLRequestContext* url_request_context, + const FileSystemURL& url, + const GURL& blob_url, + int64 offset, + const WriteCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + if (!operation) { + callback.Run(error, 0, true); + return kErrorOperationID; + } + + scoped_ptr<FileStreamWriter> writer( + file_system_context_->CreateFileStreamWriter(url, offset)); + if (!writer) { + // Write is not supported. + callback.Run(base::PLATFORM_FILE_ERROR_SECURITY, 0, true); + return kErrorOperationID; + } + + DCHECK(blob_url.is_valid()); + scoped_ptr<FileWriterDelegate> writer_delegate( + new FileWriterDelegate(writer.Pass())); + scoped_ptr<net::URLRequest> blob_request(url_request_context->CreateRequest( + blob_url, writer_delegate.get())); + + OperationID id = operations_.Add(operation); + PrepareForWrite(id, url); + operation->Write( + url, writer_delegate.Pass(), blob_request.Pass(), + base::Bind(&FileSystemOperationRunner::DidWrite, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::Truncate( + const FileSystemURL& url, int64 length, + const StatusCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + if (!operation) { + callback.Run(error); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + PrepareForWrite(id, url); + operation->Truncate( + url, length, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + id, callback)); + return id; +} + +void FileSystemOperationRunner::Cancel( + OperationID id, + const StatusCallback& callback) { + FileSystemOperation* operation = operations_.Lookup(id); + if (!operation) { + // The operation is already finished; report that we failed to stop it. + callback.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION); + return; + } + operation->Cancel(callback); +} + +OperationID FileSystemOperationRunner::TouchFile( + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + if (!operation) { + callback.Run(error); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + PrepareForWrite(id, url); + operation->TouchFile( + url, last_access_time, last_modified_time, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::OpenFile( + const FileSystemURL& url, + int file_flags, + base::ProcessHandle peer_handle, + const OpenFileCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + if (!operation) { + callback.Run(error, base::kInvalidPlatformFileValue, + base::Closure(), base::ProcessHandle()); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + if (file_flags & + (base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_OPEN_ALWAYS | + base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_OPEN_TRUNCATED | + base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_EXCLUSIVE_WRITE | + base::PLATFORM_FILE_DELETE_ON_CLOSE | + base::PLATFORM_FILE_WRITE_ATTRIBUTES)) { + PrepareForWrite(id, url); + } else { + PrepareForRead(id, url); + } + operation->OpenFile( + url, file_flags, peer_handle, + base::Bind(&FileSystemOperationRunner::DidOpenFile, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::CreateSnapshotFile( + const FileSystemURL& url, + const SnapshotFileCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, &error); + if (!operation) { + callback.Run(error, base::PlatformFileInfo(), base::FilePath(), NULL); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + PrepareForRead(id, url); + operation->CreateSnapshotFile( + url, + base::Bind(&FileSystemOperationRunner::DidCreateSnapshot, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::CopyInForeignFile( + const base::FilePath& src_local_disk_path, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = CreateFileSystemOperationImpl( + dest_url, &error); + if (!operation) { + callback.Run(error); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + operation->AsFileSystemOperationImpl()->CopyInForeignFile( + src_local_disk_path, dest_url, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::RemoveFile( + const FileSystemURL& url, + const StatusCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = CreateFileSystemOperationImpl(url, &error); + if (!operation) { + callback.Run(error); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + operation->AsFileSystemOperationImpl()->RemoveFile( + url, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::RemoveDirectory( + const FileSystemURL& url, + const StatusCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = CreateFileSystemOperationImpl(url, &error); + if (!operation) { + callback.Run(error); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + operation->AsFileSystemOperationImpl()->RemoveDirectory( + url, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::CopyFileLocal( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = CreateFileSystemOperationImpl( + src_url, &error); + if (!operation) { + callback.Run(error); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + operation->AsFileSystemOperationImpl()->CopyFileLocal( + src_url, dest_url, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + id, callback)); + return id; +} + +OperationID FileSystemOperationRunner::MoveFileLocal( + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = CreateFileSystemOperationImpl( + src_url, &error); + if (!operation) { + callback.Run(error); + return kErrorOperationID; + } + OperationID id = operations_.Add(operation); + operation->AsFileSystemOperationImpl()->MoveFileLocal( + src_url, dest_url, + base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(), + id, callback)); + return id; +} + +base::PlatformFileError FileSystemOperationRunner::SyncGetPlatformPath( + const FileSystemURL& url, + base::FilePath* platform_path) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + FileSystemOperation* operation = CreateFileSystemOperationImpl(url, &error); + if (!operation) + return error; + + return operation->AsFileSystemOperationImpl()->SyncGetPlatformPath( + url, platform_path); +} + +FileSystemOperationRunner::FileSystemOperationRunner( + FileSystemContext* file_system_context) + : file_system_context_(file_system_context) {} + +void FileSystemOperationRunner::DidFinish( + OperationID id, + const StatusCallback& callback, + base::PlatformFileError rv) { + callback.Run(rv); + FinishOperation(id); +} + +void FileSystemOperationRunner::DidGetMetadata( + OperationID id, + const GetMetadataCallback& callback, + base::PlatformFileError rv, + const base::PlatformFileInfo& file_info) { + callback.Run(rv, file_info); + FinishOperation(id); +} + +void FileSystemOperationRunner::DidReadDirectory( + OperationID id, + const ReadDirectoryCallback& callback, + base::PlatformFileError rv, + const std::vector<DirectoryEntry>& entries, + bool has_more) { + callback.Run(rv, entries, has_more); + if (rv != base::PLATFORM_FILE_OK || !has_more) + FinishOperation(id); +} + +void FileSystemOperationRunner::DidWrite( + OperationID id, + const WriteCallback& callback, + base::PlatformFileError rv, + int64 bytes, + bool complete) { + callback.Run(rv, bytes, complete); + if (rv != base::PLATFORM_FILE_OK || complete) + FinishOperation(id); +} + +void FileSystemOperationRunner::DidOpenFile( + OperationID id, + const OpenFileCallback& callback, + base::PlatformFileError rv, + base::PlatformFile file, + const base::Closure& on_close_callback, + base::ProcessHandle peer_handle) { + callback.Run(rv, file, on_close_callback, peer_handle); + FinishOperation(id); +} + +void FileSystemOperationRunner::DidCreateSnapshot( + OperationID id, + const SnapshotFileCallback& callback, + base::PlatformFileError rv, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) { + callback.Run(rv, file_info, platform_path, file_ref); + FinishOperation(id); +} + +FileSystemOperation* +FileSystemOperationRunner::CreateFileSystemOperationImpl( + const FileSystemURL& url, base::PlatformFileError* error) { + FileSystemOperation* operation = + file_system_context_->CreateFileSystemOperation(url, error); + if (!operation) + return NULL; + if (!operation->AsFileSystemOperationImpl()) { + *error = base::PLATFORM_FILE_ERROR_INVALID_OPERATION; + delete operation; + return NULL; + } + return operation; +} + +void FileSystemOperationRunner::PrepareForWrite(OperationID id, + const FileSystemURL& url) { + if (file_system_context_->GetUpdateObservers(url.type())) { + file_system_context_->GetUpdateObservers(url.type())->Notify( + &FileUpdateObserver::OnStartUpdate, MakeTuple(url)); + } + write_target_urls_[id].insert(url); +} + +void FileSystemOperationRunner::PrepareForRead(OperationID id, + const FileSystemURL& url) { + if (file_system_context_->GetAccessObservers(url.type())) { + file_system_context_->GetAccessObservers(url.type())->Notify( + &FileAccessObserver::OnAccess, MakeTuple(url)); + } +} + +void FileSystemOperationRunner::FinishOperation(OperationID id) { + OperationToURLSet::iterator found = write_target_urls_.find(id); + if (found != write_target_urls_.end()) { + const FileSystemURLSet& urls = found->second; + for (FileSystemURLSet::const_iterator iter = urls.begin(); + iter != urls.end(); ++iter) { + if (file_system_context_->GetUpdateObservers(iter->type())) { + file_system_context_->GetUpdateObservers(iter->type())->Notify( + &FileUpdateObserver::OnEndUpdate, MakeTuple(*iter)); + } + } + write_target_urls_.erase(found); + } + DCHECK(operations_.Lookup(id)); + operations_.Remove(id); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_operation_runner.h b/chromium/webkit/browser/fileapi/file_system_operation_runner.h new file mode 100644 index 00000000000..67f30b89b17 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_operation_runner.h @@ -0,0 +1,297 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_RUNNER_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_RUNNER_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/id_map.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "webkit/browser/fileapi/file_system_operation.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace net { +class URLRequestContext; +} + +namespace fileapi { + +class FileSystemURL; +class FileSystemContext; + +// A central interface for running FileSystem API operations. +// All operation methods take callback and returns OperationID, which is +// an integer value which can be used for cancelling an operation. +// All operation methods return kErrorOperationID if running (posting) an +// operation fails, in addition to dispatching the callback with an error +// code (therefore in most cases the caller does not need to check the +// returned operation ID). +// +// Some operations (e.g. CopyInForeignFile, RemoveFile, RemoveDirectory, +// CopyFileLocal, MoveFileLocal and SyncGetPlatformPath) are only supported +// by filesystems which implement FileSystemOperationImpl. +// If they are called on other filesystems +// base::PLATFORM_FILE_ERROR_INVALID_OPERATION is returned via callback. +class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemOperationRunner + : public base::SupportsWeakPtr<FileSystemOperationRunner> { + public: + typedef FileSystemOperation::GetMetadataCallback GetMetadataCallback; + typedef FileSystemOperation::ReadDirectoryCallback ReadDirectoryCallback; + typedef FileSystemOperation::SnapshotFileCallback SnapshotFileCallback; + typedef FileSystemOperation::StatusCallback StatusCallback; + typedef FileSystemOperation::WriteCallback WriteCallback; + typedef FileSystemOperation::OpenFileCallback OpenFileCallback; + + typedef int OperationID; + + static const OperationID kErrorOperationID; + + virtual ~FileSystemOperationRunner(); + + // Cancels all inflight operations. + void Shutdown(); + + // Creates a file at |url|. If |exclusive| is true, an error is raised + // in case a file is already present at the URL. + OperationID CreateFile(const FileSystemURL& url, + bool exclusive, + const StatusCallback& callback); + + OperationID CreateDirectory(const FileSystemURL& url, + bool exclusive, + bool recursive, + const StatusCallback& callback); + + // Copies a file or directory from |src_url| to |dest_url|. If + // |src_url| is a directory, the contents of |src_url| are copied to + // |dest_url| recursively. A new file or directory is created at + // |dest_url| as needed. + OperationID Copy(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback); + + // Moves a file or directory from |src_url| to |dest_url|. A new file + // or directory is created at |dest_url| as needed. + OperationID Move(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback); + + // Checks if a directory is present at |url|. + OperationID DirectoryExists(const FileSystemURL& url, + const StatusCallback& callback); + + // Checks if a file is present at |url|. + OperationID FileExists(const FileSystemURL& url, + const StatusCallback& callback); + + // Gets the metadata of a file or directory at |url|. + OperationID GetMetadata(const FileSystemURL& url, + const GetMetadataCallback& callback); + + // Reads contents of a directory at |url|. + OperationID ReadDirectory(const FileSystemURL& url, + const ReadDirectoryCallback& callback); + + // Removes a file or directory at |url|. If |recursive| is true, remove + // all files and directories under the directory at |url| recursively. + OperationID Remove(const FileSystemURL& url, bool recursive, + const StatusCallback& callback); + + // Writes contents of |blob_url| to |url| at |offset|. + // |url_request_context| is used to read contents in |blob_url|. + OperationID Write(const net::URLRequestContext* url_request_context, + const FileSystemURL& url, + const GURL& blob_url, + int64 offset, + const WriteCallback& callback); + + // Truncates a file at |url| to |length|. If |length| is larger than + // the original file size, the file will be extended, and the extended + // part is filled with null bytes. + OperationID Truncate(const FileSystemURL& url, int64 length, + const StatusCallback& callback); + + // Tries to cancel the operation |id| [we support cancelling write or + // truncate only]. Reports failure for the current operation, then reports + // success for the cancel operation itself via the |callback|. + void Cancel(OperationID id, const StatusCallback& callback); + + // Modifies timestamps of a file or directory at |url| with + // |last_access_time| and |last_modified_time|. The function DOES NOT + // create a file unlike 'touch' command on Linux. + // + // This function is used only by Pepper as of writing. + OperationID TouchFile(const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time, + const StatusCallback& callback); + + // Opens a file at |url| with |file_flags|, where flags are OR'ed + // values of base::PlatformFileFlags. + // + // |peer_handle| is the process handle of a pepper plugin process, which + // is necessary for underlying IPC calls with Pepper plugins. + // + // This function is used only by Pepper as of writing. + OperationID OpenFile(const FileSystemURL& url, + int file_flags, + base::ProcessHandle peer_handle, + const OpenFileCallback& callback); + + // Creates a local snapshot file for a given |url| and returns the + // metadata and platform url of the snapshot file via |callback|. + // In local filesystem cases the implementation may simply return + // the metadata of the file itself (as well as GetMetadata does), + // while in remote filesystem case the backend may want to download the file + // into a temporary snapshot file and return the metadata of the + // temporary file. Or if the implementaiton already has the local cache + // data for |url| it can simply return the url to the cache. + OperationID CreateSnapshotFile(const FileSystemURL& url, + const SnapshotFileCallback& callback); + + // Copies in a single file from a different filesystem. + // + // This returns: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_file_path| + // or the parent directory of |dest_url| does not exist. + // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + OperationID CopyInForeignFile(const base::FilePath& src_local_disk_path, + const FileSystemURL& dest_url, + const StatusCallback& callback); + + // Removes a single file. + // + // This returns: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist. + // - PLATFORM_FILE_ERROR_NOT_A_FILE if |url| is not a file. + // + OperationID RemoveFile(const FileSystemURL& url, + const StatusCallback& callback); + + // Removes a single empty directory. + // + // This returns: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist. + // - PLATFORM_FILE_ERROR_NOT_A_DIRECTORY if |url| is not a directory. + // - PLATFORM_FILE_ERROR_NOT_EMPTY if |url| is not empty. + // + OperationID RemoveDirectory(const FileSystemURL& url, + const StatusCallback& callback); + + // Copies a file from |src_url| to |dest_url|. + // This must be called for files that belong to the same filesystem + // (i.e. type() and origin() of the |src_url| and |dest_url| must match). + // + // This returns: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_url| + // or the parent directory of |dest_url| does not exist. + // - PLATFORM_FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file. + // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + OperationID CopyFileLocal(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback); + + // Moves a local file from |src_url| to |dest_url|. + // This must be called for files that belong to the same filesystem + // (i.e. type() and origin() of the |src_url| and |dest_url| must match). + // + // This returns: + // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_url| + // or the parent directory of |dest_url| does not exist. + // - PLATFORM_FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file. + // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and + // is not a file. + // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and + // its parent path is a file. + // + OperationID MoveFileLocal(const FileSystemURL& src_url, + const FileSystemURL& dest_url, + const StatusCallback& callback); + + // This is called only by pepper plugin as of writing to synchronously get + // the underlying platform path to upload a file in the sandboxed filesystem + // (e.g. TEMPORARY or PERSISTENT). + base::PlatformFileError SyncGetPlatformPath(const FileSystemURL& url, + base::FilePath* platform_path); + + private: + friend class FileSystemContext; + explicit FileSystemOperationRunner(FileSystemContext* file_system_context); + + void DidFinish(OperationID id, + const StatusCallback& callback, + base::PlatformFileError rv); + void DidGetMetadata(OperationID id, + const GetMetadataCallback& callback, + base::PlatformFileError rv, + const base::PlatformFileInfo& file_info); + void DidReadDirectory(OperationID id, + const ReadDirectoryCallback& callback, + base::PlatformFileError rv, + const std::vector<DirectoryEntry>& entries, + bool has_more); + void DidWrite(OperationID id, + const WriteCallback& callback, + base::PlatformFileError rv, + int64 bytes, + bool complete); + void DidOpenFile( + OperationID id, + const OpenFileCallback& callback, + base::PlatformFileError rv, + base::PlatformFile file, + const base::Closure& on_close_callback, + base::ProcessHandle peer_handle); + void DidCreateSnapshot( + OperationID id, + const SnapshotFileCallback& callback, + base::PlatformFileError rv, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref); + + // A helper method for creating FileSystemOperationImpl for operations + // that are supported only in FileSystemOperationImpl. + // Note that this returns FileSystemOperation, so the caller needs to + // call AsFileSystemOperationImpl() (which is guaranteed to be non-null + // if this method returns without error). + FileSystemOperation* CreateFileSystemOperationImpl( + const FileSystemURL& url, + base::PlatformFileError* error); + + void PrepareForWrite(OperationID id, const FileSystemURL& url); + void PrepareForRead(OperationID id, const FileSystemURL& url); + + // This must be called at the end of any async operations. + void FinishOperation(OperationID id); + + // Not owned; file_system_context owns this. + FileSystemContext* file_system_context_; + + // IDMap<FileSystemOperation, IDMapOwnPointer> operations_; + IDMap<FileSystemOperation, IDMapOwnPointer> operations_; + + // We keep track of the file to be modified by each operation so that + // we can notify observers when we're done. + typedef std::map<OperationID, FileSystemURLSet> OperationToURLSet; + OperationToURLSet write_target_urls_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemOperationRunner); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_RUNNER_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_options.cc b/chromium/webkit/browser/fileapi/file_system_options.cc new file mode 100644 index 00000000000..4100b5d1e0e --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_options.cc @@ -0,0 +1,19 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_options.h" + +namespace fileapi { + +FileSystemOptions::FileSystemOptions( + ProfileMode profile_mode, + const std::vector<std::string>& additional_allowed_schemes) + : profile_mode_(profile_mode), + additional_allowed_schemes_(additional_allowed_schemes) { +} + +FileSystemOptions::~FileSystemOptions() { +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_options.h b/chromium/webkit/browser/fileapi/file_system_options.h new file mode 100644 index 00000000000..b6e78efce61 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_options.h @@ -0,0 +1,53 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPTIONS_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPTIONS_H_ + +#include <string> +#include <vector> + +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace fileapi { + +// Provides runtime options that may change FileSystem API behavior. +// This object is copyable. +class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemOptions { + public: + enum ProfileMode { + PROFILE_MODE_NORMAL = 0, + PROFILE_MODE_INCOGNITO + }; + + // |profile_mode| specifies if the profile (for this filesystem) + // is running in incognito mode (PROFILE_MODE_INCOGNITO) or no + // (PROFILE_MODE_NORMAL). + // |additional_allowed_schemes| specifies schemes that are allowed + // to access FileSystem API in addition to "http" and "https". + FileSystemOptions( + ProfileMode profile_mode, + const std::vector<std::string>& additional_allowed_schemes); + + ~FileSystemOptions(); + + // Returns true if it is running in the incognito mode. + bool is_incognito() const { return profile_mode_ == PROFILE_MODE_INCOGNITO; } + + // Returns the schemes that must be allowed to access FileSystem API + // in addition to standard "http" and "https". + // (e.g. If the --allow-file-access-from-files option is given in chrome + // "file" scheme will also need to be allowed). + const std::vector<std::string>& additional_allowed_schemes() const { + return additional_allowed_schemes_; + } + + private: + const ProfileMode profile_mode_; + const std::vector<std::string> additional_allowed_schemes_; +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPTIONS_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_quota_client.cc b/chromium/webkit/browser/fileapi/file_system_quota_client.cc new file mode 100644 index 00000000000..343a6e6ee5c --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_quota_client.cc @@ -0,0 +1,204 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_quota_client.h" + +#include <algorithm> +#include <set> + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" +#include "base/task_runner_util.h" +#include "net/base/net_util.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_quota_util.h" +#include "webkit/browser/fileapi/file_system_usage_cache.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend.h" +#include "webkit/common/fileapi/file_system_util.h" + +using quota::StorageType; + +namespace fileapi { + +namespace { + +void GetOriginsForTypeOnFileThread( + FileSystemContext* context, + StorageType storage_type, + std::set<GURL>* origins_ptr) { + FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); + DCHECK(type != kFileSystemTypeUnknown); + + FileSystemQuotaUtil* quota_util = context->GetQuotaUtil(type); + if (!quota_util) + return; + quota_util->GetOriginsForTypeOnFileThread(type, origins_ptr); +} + +void GetOriginsForHostOnFileThread( + FileSystemContext* context, + StorageType storage_type, + const std::string& host, + std::set<GURL>* origins_ptr) { + FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); + DCHECK(type != kFileSystemTypeUnknown); + + FileSystemQuotaUtil* quota_util = context->GetQuotaUtil(type); + if (!quota_util) + return; + quota_util->GetOriginsForHostOnFileThread(type, host, origins_ptr); +} + +void DidGetOrigins( + const quota::QuotaClient::GetOriginsCallback& callback, + std::set<GURL>* origins_ptr) { + callback.Run(*origins_ptr); +} + +quota::QuotaStatusCode DeleteOriginOnFileThread( + FileSystemContext* context, + const GURL& origin, + FileSystemType type) { + FileSystemBackend* provider = context->GetFileSystemBackend(type); + if (!provider || !provider->GetQuotaUtil()) + return quota::kQuotaErrorNotSupported; + base::PlatformFileError result = + provider->GetQuotaUtil()->DeleteOriginDataOnFileThread( + context, context->quota_manager_proxy(), origin, type); + if (result == base::PLATFORM_FILE_OK) + return quota::kQuotaStatusOk; + return quota::kQuotaErrorInvalidModification; +} + +} // namespace + +FileSystemQuotaClient::FileSystemQuotaClient( + FileSystemContext* file_system_context, + bool is_incognito) + : file_system_context_(file_system_context), + is_incognito_(is_incognito) { +} + +FileSystemQuotaClient::~FileSystemQuotaClient() {} + +quota::QuotaClient::ID FileSystemQuotaClient::id() const { + return quota::QuotaClient::kFileSystem; +} + +void FileSystemQuotaClient::OnQuotaManagerDestroyed() { + delete this; +} + +void FileSystemQuotaClient::GetOriginUsage( + const GURL& origin_url, + StorageType storage_type, + const GetUsageCallback& callback) { + DCHECK(!callback.is_null()); + + if (is_incognito_) { + // We don't support FileSystem in incognito mode yet. + callback.Run(0); + return; + } + + FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); + DCHECK(type != kFileSystemTypeUnknown); + + FileSystemQuotaUtil* quota_util = file_system_context_->GetQuotaUtil(type); + if (!quota_util) { + callback.Run(0); + return; + } + + base::PostTaskAndReplyWithResult( + file_task_runner(), + FROM_HERE, + // It is safe to pass Unretained(quota_util) since context owns it. + base::Bind(&FileSystemQuotaUtil::GetOriginUsageOnFileThread, + base::Unretained(quota_util), + file_system_context_, + origin_url, + type), + callback); +} + +void FileSystemQuotaClient::GetOriginsForType( + StorageType storage_type, + const GetOriginsCallback& callback) { + DCHECK(!callback.is_null()); + + if (is_incognito_) { + // We don't support FileSystem in incognito mode yet. + std::set<GURL> origins; + callback.Run(origins); + return; + } + + std::set<GURL>* origins_ptr = new std::set<GURL>(); + file_task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&GetOriginsForTypeOnFileThread, + file_system_context_, + storage_type, + base::Unretained(origins_ptr)), + base::Bind(&DidGetOrigins, + callback, + base::Owned(origins_ptr))); +} + +void FileSystemQuotaClient::GetOriginsForHost( + StorageType storage_type, + const std::string& host, + const GetOriginsCallback& callback) { + DCHECK(!callback.is_null()); + + if (is_incognito_) { + // We don't support FileSystem in incognito mode yet. + std::set<GURL> origins; + callback.Run(origins); + return; + } + + std::set<GURL>* origins_ptr = new std::set<GURL>(); + file_task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&GetOriginsForHostOnFileThread, + file_system_context_, + storage_type, + host, + base::Unretained(origins_ptr)), + base::Bind(&DidGetOrigins, + callback, + base::Owned(origins_ptr))); +} + +void FileSystemQuotaClient::DeleteOriginData( + const GURL& origin, + StorageType type, + const DeletionCallback& callback) { + FileSystemType fs_type = QuotaStorageTypeToFileSystemType(type); + DCHECK(fs_type != kFileSystemTypeUnknown); + + base::PostTaskAndReplyWithResult( + file_task_runner(), + FROM_HERE, + base::Bind(&DeleteOriginOnFileThread, + file_system_context_, + origin, + fs_type), + callback); +} + +base::SequencedTaskRunner* FileSystemQuotaClient::file_task_runner() const { + return file_system_context_->default_file_task_runner(); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_quota_client.h b/chromium/webkit/browser/fileapi/file_system_quota_client.h new file mode 100644 index 00000000000..f2168043f1b --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_quota_client.h @@ -0,0 +1,72 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_CLIENT_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_CLIENT_H_ + +#include <set> +#include <string> +#include <utility> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "webkit/browser/fileapi/file_system_quota_util.h" +#include "webkit/browser/quota/quota_client.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/fileapi/file_system_types.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace fileapi { + +class FileSystemContext; + +// An instance of this class is created per-profile. This class +// is self-destructed and will delete itself when OnQuotaManagerDestroyed +// is called. +// All of the public methods of this class are called by the quota manager +// (except for the constructor/destructor). +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileSystemQuotaClient + : public NON_EXPORTED_BASE(quota::QuotaClient) { + public: + FileSystemQuotaClient( + FileSystemContext* file_system_context, + bool is_incognito); + virtual ~FileSystemQuotaClient(); + + // QuotaClient methods. + virtual quota::QuotaClient::ID id() const OVERRIDE; + virtual void OnQuotaManagerDestroyed() OVERRIDE; + virtual void GetOriginUsage(const GURL& origin_url, + quota::StorageType type, + const GetUsageCallback& callback) OVERRIDE; + virtual void GetOriginsForType( + quota::StorageType type, + const GetOriginsCallback& callback) OVERRIDE; + virtual void GetOriginsForHost( + quota::StorageType type, + const std::string& host, + const GetOriginsCallback& callback) OVERRIDE; + virtual void DeleteOriginData( + const GURL& origin, + quota::StorageType type, + const DeletionCallback& callback) OVERRIDE; + + private: + base::SequencedTaskRunner* file_task_runner() const; + + scoped_refptr<FileSystemContext> file_system_context_; + + bool is_incognito_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemQuotaClient); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_CLIENT_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_quota_client_unittest.cc b/chromium/webkit/browser/fileapi/file_system_quota_client_unittest.cc new file mode 100644 index 00000000000..477bb77e95e --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_quota_client_unittest.cc @@ -0,0 +1,582 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/platform_file.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_quota_client.h" +#include "webkit/browser/fileapi/file_system_usage_cache.h" +#include "webkit/browser/fileapi/mock_file_system_context.h" +#include "webkit/browser/fileapi/obfuscated_file_util.h" +#include "webkit/common/fileapi/file_system_types.h" +#include "webkit/common/fileapi/file_system_util.h" +#include "webkit/common/quota/quota_types.h" + +namespace fileapi { +namespace { + +const char kDummyURL1[] = "http://www.dummy.org"; +const char kDummyURL2[] = "http://www.example.com"; +const char kDummyURL3[] = "http://www.bleh"; + +// Declared to shorten the variable names. +const quota::StorageType kTemporary = quota::kStorageTypeTemporary; +const quota::StorageType kPersistent = quota::kStorageTypePersistent; + +} // namespace + +class FileSystemQuotaClientTest : public testing::Test { + public: + FileSystemQuotaClientTest() + : weak_factory_(this), + additional_callback_count_(0), + deletion_status_(quota::kQuotaStatusUnknown) { + } + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + file_system_context_ = CreateFileSystemContextForTesting( + NULL, data_dir_.path()); + } + + struct TestFile { + bool isDirectory; + const char* name; + int64 size; + const char* origin_url; + quota::StorageType type; + }; + + protected: + FileSystemQuotaClient* NewQuotaClient(bool is_incognito) { + return new FileSystemQuotaClient(file_system_context_.get(), is_incognito); + } + + void GetOriginUsageAsync(FileSystemQuotaClient* quota_client, + const std::string& origin_url, + quota::StorageType type) { + quota_client->GetOriginUsage( + GURL(origin_url), type, + base::Bind(&FileSystemQuotaClientTest::OnGetUsage, + weak_factory_.GetWeakPtr())); + } + + int64 GetOriginUsage(FileSystemQuotaClient* quota_client, + const std::string& origin_url, + quota::StorageType type) { + GetOriginUsageAsync(quota_client, origin_url, type); + base::MessageLoop::current()->RunUntilIdle(); + return usage_; + } + + const std::set<GURL>& GetOriginsForType(FileSystemQuotaClient* quota_client, + quota::StorageType type) { + origins_.clear(); + quota_client->GetOriginsForType( + type, + base::Bind(&FileSystemQuotaClientTest::OnGetOrigins, + weak_factory_.GetWeakPtr())); + base::MessageLoop::current()->RunUntilIdle(); + return origins_; + } + + const std::set<GURL>& GetOriginsForHost(FileSystemQuotaClient* quota_client, + quota::StorageType type, + const std::string& host) { + origins_.clear(); + quota_client->GetOriginsForHost( + type, host, + base::Bind(&FileSystemQuotaClientTest::OnGetOrigins, + weak_factory_.GetWeakPtr())); + base::MessageLoop::current()->RunUntilIdle(); + return origins_; + } + + void RunAdditionalOriginUsageTask(FileSystemQuotaClient* quota_client, + const std::string& origin_url, + quota::StorageType type) { + quota_client->GetOriginUsage( + GURL(origin_url), type, + base::Bind(&FileSystemQuotaClientTest::OnGetAdditionalUsage, + weak_factory_.GetWeakPtr())); + } + + FileSystemOperationContext* CreateFileSystemOperationContext( + FileSystemType type) { + FileSystemOperationContext* context = + new FileSystemOperationContext(file_system_context_.get()); + context->set_allowed_bytes_growth(100000000); + context->set_update_observers( + *file_system_context_->GetUpdateObservers(type)); + return context; + } + + bool CreateFileSystemDirectory(const base::FilePath& file_path, + const std::string& origin_url, + quota::StorageType storage_type) { + FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); + FileSystemFileUtil* file_util = file_system_context_->GetFileUtil(type); + + FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( + GURL(origin_url), type, file_path); + scoped_ptr<FileSystemOperationContext> context( + CreateFileSystemOperationContext(type)); + + base::PlatformFileError result = + file_util->CreateDirectory(context.get(), url, false, false); + if (result != base::PLATFORM_FILE_OK) + return false; + return true; + } + + bool CreateFileSystemFile(const base::FilePath& file_path, + int64 file_size, + const std::string& origin_url, + quota::StorageType storage_type) { + if (file_path.empty()) + return false; + + FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); + FileSystemFileUtil* file_util = file_system_context_->GetFileUtil(type); + + FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( + GURL(origin_url), type, file_path); + scoped_ptr<FileSystemOperationContext> context( + CreateFileSystemOperationContext(type)); + + bool created = false; + if (base::PLATFORM_FILE_OK != + file_util->EnsureFileExists(context.get(), url, &created)) + return false; + EXPECT_TRUE(created); + if (base::PLATFORM_FILE_OK != + file_util->Truncate(context.get(), url, file_size)) + return false; + return true; + } + + void InitializeOriginFiles(FileSystemQuotaClient* quota_client, + const TestFile* files, + int num_files) { + for (int i = 0; i < num_files; i++) { + base::FilePath path = base::FilePath().AppendASCII(files[i].name); + if (files[i].isDirectory) { + ASSERT_TRUE(CreateFileSystemDirectory( + path, files[i].origin_url, files[i].type)); + if (path.empty()) { + // Create the usage cache. + // HACK--we always create the root [an empty path] first. If we + // create it later, this will fail due to a quota mismatch. If we + // call this before we create the root, it succeeds, but hasn't + // actually created the cache. + ASSERT_EQ(0, GetOriginUsage( + quota_client, files[i].origin_url, files[i].type)); + } + } else { + ASSERT_TRUE(CreateFileSystemFile( + path, files[i].size, files[i].origin_url, files[i].type)); + } + } + } + + // This is a bit fragile--it depends on the test data always creating a + // directory before adding a file or directory to it, so that we can just + // count the basename of each addition. A recursive creation of a path, which + // created more than one directory in a single shot, would break this. + int64 ComputeFilePathsCostForOriginAndType(const TestFile* files, + int num_files, + const std::string& origin_url, + quota::StorageType type) { + int64 file_paths_cost = 0; + for (int i = 0; i < num_files; i++) { + if (files[i].type == type && + GURL(files[i].origin_url) == GURL(origin_url)) { + base::FilePath path = base::FilePath().AppendASCII(files[i].name); + if (!path.empty()) { + file_paths_cost += ObfuscatedFileUtil::ComputeFilePathCost(path); + } + } + } + return file_paths_cost; + } + + void DeleteOriginData(FileSystemQuotaClient* quota_client, + const std::string& origin, + quota::StorageType type) { + deletion_status_ = quota::kQuotaStatusUnknown; + quota_client->DeleteOriginData( + GURL(origin), type, + base::Bind(&FileSystemQuotaClientTest::OnDeleteOrigin, + weak_factory_.GetWeakPtr())); + } + + int64 usage() const { return usage_; } + quota::QuotaStatusCode status() { return deletion_status_; } + int additional_callback_count() const { return additional_callback_count_; } + void set_additional_callback_count(int count) { + additional_callback_count_ = count; + } + + private: + void OnGetUsage(int64 usage) { + usage_ = usage; + } + + void OnGetOrigins(const std::set<GURL>& origins) { + origins_ = origins; + } + + void OnGetAdditionalUsage(int64 usage_unused) { + ++additional_callback_count_; + } + + void OnDeleteOrigin(quota::QuotaStatusCode status) { + deletion_status_ = status; + } + + base::ScopedTempDir data_dir_; + base::MessageLoop message_loop_; + scoped_refptr<FileSystemContext> file_system_context_; + base::WeakPtrFactory<FileSystemQuotaClientTest> weak_factory_; + int64 usage_; + int additional_callback_count_; + std::set<GURL> origins_; + quota::QuotaStatusCode deletion_status_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemQuotaClientTest); +}; + +TEST_F(FileSystemQuotaClientTest, NoFileSystemTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + + EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); +} + +TEST_F(FileSystemQuotaClientTest, NoFileTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + + for (int i = 0; i < 2; i++) { + EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + } +} + +TEST_F(FileSystemQuotaClientTest, OneFileTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {false, "foo", 4921, kDummyURL1, kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + + for (int i = 0; i < 2; i++) { + EXPECT_EQ(4921 + file_paths_cost, + GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + } +} + +TEST_F(FileSystemQuotaClientTest, TwoFilesTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {false, "foo", 10310, kDummyURL1, kTemporary}, + {false, "bar", 41, kDummyURL1, kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + + for (int i = 0; i < 2; i++) { + EXPECT_EQ(10310 + 41 + file_paths_cost, + GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + } +} + +TEST_F(FileSystemQuotaClientTest, EmptyFilesTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {false, "foo", 0, kDummyURL1, kTemporary}, + {false, "bar", 0, kDummyURL1, kTemporary}, + {false, "baz", 0, kDummyURL1, kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + + for (int i = 0; i < 2; i++) { + EXPECT_EQ(file_paths_cost, + GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + } +} + +TEST_F(FileSystemQuotaClientTest, SubDirectoryTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {true, "dirtest", 0, kDummyURL1, kTemporary}, + {false, "dirtest/foo", 11921, kDummyURL1, kTemporary}, + {false, "bar", 4814, kDummyURL1, kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + + for (int i = 0; i < 2; i++) { + EXPECT_EQ(11921 + 4814 + file_paths_cost, + GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + } +} + +TEST_F(FileSystemQuotaClientTest, MultiTypeTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {true, "dirtest", 0, kDummyURL1, kTemporary}, + {false, "dirtest/foo", 133, kDummyURL1, kTemporary}, + {false, "bar", 14, kDummyURL1, kTemporary}, + {true, NULL, 0, kDummyURL1, kPersistent}, + {true, "dirtest", 0, kDummyURL1, kPersistent}, + {false, "dirtest/foo", 193, kDummyURL1, kPersistent}, + {false, "bar", 9, kDummyURL1, kPersistent}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost_temporary = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + const int64 file_paths_cost_persistent = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + + for (int i = 0; i < 2; i++) { + EXPECT_EQ(133 + 14 + file_paths_cost_temporary, + GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + EXPECT_EQ(193 + 9 + file_paths_cost_persistent, + GetOriginUsage(quota_client.get(), kDummyURL1, kPersistent)); + } +} + +TEST_F(FileSystemQuotaClientTest, MultiDomainTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {true, "dir1", 0, kDummyURL1, kTemporary}, + {false, "dir1/foo", 1331, kDummyURL1, kTemporary}, + {false, "bar", 134, kDummyURL1, kTemporary}, + {true, NULL, 0, kDummyURL1, kPersistent}, + {true, "dir2", 0, kDummyURL1, kPersistent}, + {false, "dir2/foo", 1903, kDummyURL1, kPersistent}, + {false, "bar", 19, kDummyURL1, kPersistent}, + {true, NULL, 0, kDummyURL2, kTemporary}, + {true, "dom", 0, kDummyURL2, kTemporary}, + {false, "dom/fan", 1319, kDummyURL2, kTemporary}, + {false, "bar", 113, kDummyURL2, kTemporary}, + {true, NULL, 0, kDummyURL2, kPersistent}, + {true, "dom", 0, kDummyURL2, kPersistent}, + {false, "dom/fan", 2013, kDummyURL2, kPersistent}, + {false, "baz", 18, kDummyURL2, kPersistent}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost_temporary1 = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + const int64 file_paths_cost_persistent1 = + ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles), + kDummyURL1, kPersistent); + const int64 file_paths_cost_temporary2 = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL2, kTemporary); + const int64 file_paths_cost_persistent2 = + ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles), + kDummyURL2, kPersistent); + + for (int i = 0; i < 2; i++) { + EXPECT_EQ(1331 + 134 + file_paths_cost_temporary1, + GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + EXPECT_EQ(1903 + 19 + file_paths_cost_persistent1, + GetOriginUsage(quota_client.get(), kDummyURL1, kPersistent)); + EXPECT_EQ(1319 + 113 + file_paths_cost_temporary2, + GetOriginUsage(quota_client.get(), kDummyURL2, kTemporary)); + EXPECT_EQ(2013 + 18 + file_paths_cost_persistent2, + GetOriginUsage(quota_client.get(), kDummyURL2, kPersistent)); + } +} + +TEST_F(FileSystemQuotaClientTest, GetUsage_MultipleTasks) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {false, "foo", 11, kDummyURL1, kTemporary}, + {false, "bar", 22, kDummyURL1, kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType( + kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary); + + // Dispatching three GetUsage tasks. + set_additional_callback_count(0); + GetOriginUsageAsync(quota_client.get(), kDummyURL1, kTemporary); + RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary); + RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(11 + 22 + file_paths_cost, usage()); + EXPECT_EQ(2, additional_callback_count()); + + // Once more, in a different order. + set_additional_callback_count(0); + RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary); + GetOriginUsageAsync(quota_client.get(), kDummyURL1, kTemporary); + RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(11 + 22 + file_paths_cost, usage()); + EXPECT_EQ(2, additional_callback_count()); +} + +TEST_F(FileSystemQuotaClientTest, GetOriginsForType) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {true, NULL, 0, kDummyURL2, kTemporary}, + {true, NULL, 0, kDummyURL3, kPersistent}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + + std::set<GURL> origins = GetOriginsForType(quota_client.get(), kTemporary); + EXPECT_EQ(2U, origins.size()); + EXPECT_TRUE(origins.find(GURL(kDummyURL1)) != origins.end()); + EXPECT_TRUE(origins.find(GURL(kDummyURL2)) != origins.end()); + EXPECT_TRUE(origins.find(GURL(kDummyURL3)) == origins.end()); +} + +TEST_F(FileSystemQuotaClientTest, GetOriginsForHost) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const char* kURL1 = "http://foo.com/"; + const char* kURL2 = "https://foo.com/"; + const char* kURL3 = "http://foo.com:1/"; + const char* kURL4 = "http://foo2.com/"; + const char* kURL5 = "http://foo.com:2/"; + const TestFile kFiles[] = { + {true, NULL, 0, kURL1, kTemporary}, + {true, NULL, 0, kURL2, kTemporary}, + {true, NULL, 0, kURL3, kTemporary}, + {true, NULL, 0, kURL4, kTemporary}, + {true, NULL, 0, kURL5, kPersistent}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + + std::set<GURL> origins = GetOriginsForHost( + quota_client.get(), kTemporary, "foo.com"); + EXPECT_EQ(3U, origins.size()); + EXPECT_TRUE(origins.find(GURL(kURL1)) != origins.end()); + EXPECT_TRUE(origins.find(GURL(kURL2)) != origins.end()); + EXPECT_TRUE(origins.find(GURL(kURL3)) != origins.end()); + EXPECT_TRUE(origins.find(GURL(kURL4)) == origins.end()); // Different host. + EXPECT_TRUE(origins.find(GURL(kURL5)) == origins.end()); // Different type. +} + +TEST_F(FileSystemQuotaClientTest, IncognitoTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(true)); + const TestFile kFiles[] = { + {true, NULL, 0, kDummyURL1, kTemporary}, + {false, "foo", 10, kDummyURL1, kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + + // Having files in the usual directory wouldn't affect the result + // queried in incognito mode. + EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary)); + EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kPersistent)); + + std::set<GURL> origins = GetOriginsForType(quota_client.get(), kTemporary); + EXPECT_EQ(0U, origins.size()); + origins = GetOriginsForHost(quota_client.get(), kTemporary, "www.dummy.org"); + EXPECT_EQ(0U, origins.size()); +} + +TEST_F(FileSystemQuotaClientTest, DeleteOriginTest) { + scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false)); + const TestFile kFiles[] = { + {true, NULL, 0, "http://foo.com/", kTemporary}, + {false, "a", 1, "http://foo.com/", kTemporary}, + {true, NULL, 0, "https://foo.com/", kTemporary}, + {false, "b", 2, "https://foo.com/", kTemporary}, + {true, NULL, 0, "http://foo.com/", kPersistent}, + {false, "c", 4, "http://foo.com/", kPersistent}, + {true, NULL, 0, "http://bar.com/", kTemporary}, + {false, "d", 8, "http://bar.com/", kTemporary}, + {true, NULL, 0, "http://bar.com/", kPersistent}, + {false, "e", 16, "http://bar.com/", kPersistent}, + {true, NULL, 0, "https://bar.com/", kPersistent}, + {false, "f", 32, "https://bar.com/", kPersistent}, + {true, NULL, 0, "https://bar.com/", kTemporary}, + {false, "g", 64, "https://bar.com/", kTemporary}, + }; + InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles)); + const int64 file_paths_cost_temporary_foo_https = + ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles), + "https://foo.com/", kTemporary); + const int64 file_paths_cost_persistent_foo = + ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles), + "http://foo.com/", kPersistent); + const int64 file_paths_cost_temporary_bar = + ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles), + "http://bar.com/", kTemporary); + const int64 file_paths_cost_temporary_bar_https = + ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles), + "https://bar.com/", kTemporary); + const int64 file_paths_cost_persistent_bar_https = + ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles), + "https://bar.com/", kPersistent); + + DeleteOriginData(quota_client.get(), "http://foo.com/", kTemporary); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(quota::kQuotaStatusOk, status()); + + DeleteOriginData(quota_client.get(), "http://bar.com/", kPersistent); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(quota::kQuotaStatusOk, status()); + + DeleteOriginData(quota_client.get(), "http://buz.com/", kTemporary); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(quota::kQuotaStatusOk, status()); + + EXPECT_EQ(0, GetOriginUsage( + quota_client.get(), "http://foo.com/", kTemporary)); + EXPECT_EQ(0, GetOriginUsage( + quota_client.get(), "http://bar.com/", kPersistent)); + EXPECT_EQ(0, GetOriginUsage( + quota_client.get(), "http://buz.com/", kTemporary)); + + EXPECT_EQ(2 + file_paths_cost_temporary_foo_https, + GetOriginUsage(quota_client.get(), + "https://foo.com/", + kTemporary)); + EXPECT_EQ(4 + file_paths_cost_persistent_foo, + GetOriginUsage(quota_client.get(), + "http://foo.com/", + kPersistent)); + EXPECT_EQ(8 + file_paths_cost_temporary_bar, + GetOriginUsage(quota_client.get(), + "http://bar.com/", + kTemporary)); + EXPECT_EQ(32 + file_paths_cost_persistent_bar_https, + GetOriginUsage(quota_client.get(), + "https://bar.com/", + kPersistent)); + EXPECT_EQ(64 + file_paths_cost_temporary_bar_https, + GetOriginUsage(quota_client.get(), + "https://bar.com/", + kTemporary)); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_quota_util.h b/chromium/webkit/browser/fileapi/file_system_quota_util.h new file mode 100644 index 00000000000..5655c990d4d --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_quota_util.h @@ -0,0 +1,81 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_UTIL_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_UTIL_H_ + +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/platform_file.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/task_runner_bound_observer_list.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/fileapi/file_system_types.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace quota { +class QuotaManagerProxy; +} + +namespace fileapi { + +class FileSystemContext; + +// An abstract interface that provides common quota-related utility functions +// for file_system_quota_client. +// All the methods of this class are synchronous and need to be called on +// the thread that the method name implies. +class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemQuotaUtil { + public: + virtual ~FileSystemQuotaUtil() {} + + // Deletes the data on the origin and reports the amount of deleted data + // to the quota manager via |proxy|. + virtual base::PlatformFileError DeleteOriginDataOnFileThread( + FileSystemContext* context, + quota::QuotaManagerProxy* proxy, + const GURL& origin_url, + FileSystemType type) = 0; + + virtual void GetOriginsForTypeOnFileThread(fileapi::FileSystemType type, + std::set<GURL>* origins) = 0; + + virtual void GetOriginsForHostOnFileThread(fileapi::FileSystemType type, + const std::string& host, + std::set<GURL>* origins) = 0; + + // Returns the amount of data used for the origin for usage tracking. + virtual int64 GetOriginUsageOnFileThread( + fileapi::FileSystemContext* file_system_context, + const GURL& origin_url, + fileapi::FileSystemType type) = 0; + + virtual void AddFileUpdateObserver( + FileSystemType type, + FileUpdateObserver* observer, + base::SequencedTaskRunner* task_runner) = 0; + virtual void AddFileChangeObserver( + FileSystemType type, + FileChangeObserver* observer, + base::SequencedTaskRunner* task_runner) = 0; + virtual void AddFileAccessObserver( + FileSystemType type, + FileAccessObserver* observer, + base::SequencedTaskRunner* task_runner) = 0; + virtual const UpdateObserverList* GetUpdateObservers( + FileSystemType type) const = 0; + virtual const ChangeObserverList* GetChangeObservers( + FileSystemType type) const = 0; + virtual const AccessObserverList* GetAccessObservers( + FileSystemType type) const = 0; +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_UTIL_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_url.cc b/chromium/webkit/browser/fileapi/file_system_url.cc new file mode 100644 index 00000000000..6269296a684 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_url.cc @@ -0,0 +1,188 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_url.h" + +#include <sstream> + +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "net/base/escape.h" +#include "webkit/common/fileapi/file_system_types.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +namespace { + +} // namespace + +FileSystemURL::FileSystemURL() + : is_valid_(false), + mount_type_(kFileSystemTypeUnknown), + type_(kFileSystemTypeUnknown) { +} + +// static +FileSystemURL FileSystemURL::CreateForTest(const GURL& url) { + return FileSystemURL(url); +} + +FileSystemURL FileSystemURL::CreateForTest(const GURL& origin, + FileSystemType mount_type, + const base::FilePath& virtual_path) { + return FileSystemURL(origin, mount_type, virtual_path); +} + +// static +bool FileSystemURL::ParseFileSystemSchemeURL( + const GURL& url, + GURL* origin_url, + FileSystemType* mount_type, + base::FilePath* virtual_path) { + GURL origin; + FileSystemType file_system_type = kFileSystemTypeUnknown; + + if (!url.is_valid() || !url.SchemeIsFileSystem()) + return false; + DCHECK(url.inner_url()); + + std::string inner_path = url.inner_url()->path(); + + const struct { + FileSystemType type; + const char* dir; + } kValidTypes[] = { + { kFileSystemTypePersistent, kPersistentDir }, + { kFileSystemTypeTemporary, kTemporaryDir }, + { kFileSystemTypeIsolated, kIsolatedDir }, + { kFileSystemTypeExternal, kExternalDir }, + { kFileSystemTypeTest, kTestDir }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kValidTypes); ++i) { + if (StartsWithASCII(inner_path, kValidTypes[i].dir, true)) { + file_system_type = kValidTypes[i].type; + break; + } + } + + if (file_system_type == kFileSystemTypeUnknown) + return false; + + std::string path = net::UnescapeURLComponent(url.path(), + net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS | + net::UnescapeRule::CONTROL_CHARS); + + // Ensure the path is relative. + while (!path.empty() && path[0] == '/') + path.erase(0, 1); + + base::FilePath converted_path = base::FilePath::FromUTF8Unsafe(path); + + // All parent references should have been resolved in the renderer. + if (converted_path.ReferencesParent()) + return false; + + if (origin_url) + *origin_url = url.GetOrigin(); + if (mount_type) + *mount_type = file_system_type; + if (virtual_path) + *virtual_path = converted_path.NormalizePathSeparators(). + StripTrailingSeparators(); + + return true; +} + +FileSystemURL::FileSystemURL(const GURL& url) + : mount_type_(kFileSystemTypeUnknown), + type_(kFileSystemTypeUnknown) { + is_valid_ = ParseFileSystemSchemeURL(url, &origin_, &mount_type_, + &virtual_path_); + path_ = virtual_path_; + type_ = mount_type_; +} + +FileSystemURL::FileSystemURL(const GURL& origin, + FileSystemType mount_type, + const base::FilePath& virtual_path) + : is_valid_(true), + origin_(origin), + mount_type_(mount_type), + virtual_path_(virtual_path.NormalizePathSeparators()), + type_(mount_type), + path_(virtual_path.NormalizePathSeparators()) { +} + +FileSystemURL::FileSystemURL(const GURL& origin, + FileSystemType mount_type, + const base::FilePath& virtual_path, + const std::string& mount_filesystem_id, + FileSystemType cracked_type, + const base::FilePath& cracked_path, + const std::string& filesystem_id) + : is_valid_(true), + origin_(origin), + mount_type_(mount_type), + virtual_path_(virtual_path.NormalizePathSeparators()), + mount_filesystem_id_(mount_filesystem_id), + type_(cracked_type), + path_(cracked_path.NormalizePathSeparators()), + filesystem_id_(filesystem_id) { +} + +FileSystemURL::~FileSystemURL() {} + +std::string FileSystemURL::DebugString() const { + if (!is_valid_) + return "invalid filesystem: URL"; + std::ostringstream ss; + ss << GetFileSystemRootURI(origin_, mount_type_); + + // filesystem_id_ will be non empty for (and only for) cracked URLs. + if (!filesystem_id_.empty()) { + ss << virtual_path_.value(); + ss << " ("; + ss << GetFileSystemTypeString(type_) << "@" << filesystem_id_ << ":"; + ss << path_.value(); + ss << ")"; + } else { + ss << path_.value(); + } + return ss.str(); +} + +bool FileSystemURL::IsParent(const FileSystemURL& child) const { + return IsInSameFileSystem(child) && + path().IsParent(child.path()); +} + +bool FileSystemURL::IsInSameFileSystem(const FileSystemURL& other) const { + return origin() == other.origin() && + type() == other.type() && + filesystem_id() == other.filesystem_id(); +} + +bool FileSystemURL::operator==(const FileSystemURL& that) const { + return origin_ == that.origin_ && + type_ == that.type_ && + path_ == that.path_ && + filesystem_id_ == that.filesystem_id_ && + is_valid_ == that.is_valid_; +} + +bool FileSystemURL::Comparator::operator()(const FileSystemURL& lhs, + const FileSystemURL& rhs) const { + DCHECK(lhs.is_valid_ && rhs.is_valid_); + if (lhs.origin_ != rhs.origin_) + return lhs.origin_ < rhs.origin_; + if (lhs.type_ != rhs.type_) + return lhs.type_ < rhs.type_; + if (lhs.filesystem_id_ != rhs.filesystem_id_) + return lhs.filesystem_id_ < rhs.filesystem_id_; + return lhs.path_ < rhs.path_; +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_url.h b/chromium/webkit/browser/fileapi/file_system_url.h new file mode 100644 index 00000000000..6ac875d6310 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_url.h @@ -0,0 +1,174 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_H_ + +#include <set> +#include <string> + +#include "base/platform_file.h" +#include "url/gurl.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/fileapi/file_system_types.h" + +namespace fileapi { + +// A class representing a filesystem URL which consists of origin URL, +// type and an internal path used inside the filesystem. +// +// When a FileSystemURL instance is created for a GURL (for filesystem: scheme), +// each accessor method would return following values: +// +// Example: For a URL 'filesystem:http://foo.com/temporary/foo/bar': +// origin() returns 'http://foo.com', +// mount_type() returns kFileSystemTypeTemporary, +// virtual_path() returns 'foo/bar', +// type() returns the same value as mount_type(), +// path() returns the same value as virtual_path(), +// +// All other accessors return empty or invalid value. +// +// FileSystemURL can also be created to represent a 'cracked' filesystem URL if +// the original URL's type/path is pointing to a mount point which can be +// further resolved to a lower filesystem type/path. +// +// Example: Assume a path '/media/removable' is mounted at mount name +// 'mount_name' with type kFileSystemTypeFoo as an external file system. +// +// The original URL would look like: +// 'filesystem:http://bar.com/external/mount_name/foo/bar': +// +// FileSystemURL('http://bar.com', +// kFileSystemTypeExternal, +// 'mount_name/foo/bar' +// 'mount_name', +// kFileSystemTypeFoo, +// '/media/removable/foo/bar'); +// would create a FileSystemURL whose accessors return: +// +// origin() returns 'http://bar.com', +// mount_type() returns kFileSystemTypeExternal, +// virtual_path() returns 'mount_name/foo/bar', +// type() returns the kFileSystemTypeFoo, +// path() returns '/media/removable/foo/bar', +// +// Note that in either case virtual_path() always returns the path part after +// 'type' part in the original URL, and mount_type() always returns the 'type' +// part in the original URL. +// +// Additionally, following accessors would return valid values: +// filesystem_id() returns 'mount_name'. +// +// It is impossible to directly create a valid FileSystemURL instance (except by +// using CreatedForTest methods, which should not be used in production code). +// To get a valid FileSystemURL, one of the following methods can be used: +// <Friend>::CrackURL, <Friend>::CreateCrackedFileSystemURL, where <Friend> is +// one of the friended classes. +// +// TODO(ericu): Look into making virtual_path() [and all FileSystem API virtual +// paths] just an std::string, to prevent platform-specific base::FilePath +// behavior from getting invoked by accident. Currently the base::FilePath +// returned here needs special treatment, as it may contain paths that are +// illegal on the current platform. +// To avoid problems, use VirtualPath::BaseName and +// VirtualPath::GetComponents instead of the base::FilePath methods. +class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemURL { + public: + FileSystemURL(); + ~FileSystemURL(); + + // Methods for creating FileSystemURL without attempting to crack them. + // Should be used only in tests. + static FileSystemURL CreateForTest(const GURL& url); + static FileSystemURL CreateForTest(const GURL& origin, + FileSystemType mount_type, + const base::FilePath& virtual_path); + + // Parses filesystem scheme |url| into uncracked FileSystemURL components. + static bool ParseFileSystemSchemeURL(const GURL& url, + GURL* origin, + FileSystemType* mount_type, + base::FilePath* virtual_path); + + // Returns true if this instance represents a valid FileSystem URL. + bool is_valid() const { return is_valid_; } + + // Returns the origin part of this URL. See the class comment for details. + const GURL& origin() const { return origin_; } + + // Returns the type part of this URL. See the class comment for details. + FileSystemType type() const { return type_; } + + // Returns the cracked path of this URL. See the class comment for details. + const base::FilePath& path() const { return path_; } + + // Returns the original path part of this URL. + // See the class comment for details. + // TODO(kinuko): this must return std::string. + const base::FilePath& virtual_path() const { return virtual_path_; } + + // Returns the filesystem ID/mount name for isolated/external filesystem URLs. + // See the class comment for details. + const std::string& filesystem_id() const { return filesystem_id_; } + const std::string& mount_filesystem_id() const { + return mount_filesystem_id_; + } + + FileSystemType mount_type() const { return mount_type_; } + + std::string DebugString() const; + + // Returns true if this URL is a strict parent of the |child|. + bool IsParent(const FileSystemURL& child) const; + + bool IsInSameFileSystem(const FileSystemURL& other) const; + + bool operator==(const FileSystemURL& that) const; + + struct WEBKIT_STORAGE_BROWSER_EXPORT Comparator { + bool operator() (const FileSystemURL& lhs, const FileSystemURL& rhs) const; + }; + + private: + friend class FileSystemContext; + friend class ExternalMountPoints; + friend class IsolatedContext; + + explicit FileSystemURL(const GURL& filesystem_url); + FileSystemURL(const GURL& origin, + FileSystemType mount_type, + const base::FilePath& virtual_path); + // Creates a cracked FileSystemURL. + FileSystemURL(const GURL& origin, + FileSystemType mount_type, + const base::FilePath& virtual_path, + const std::string& mount_filesystem_id, + FileSystemType cracked_type, + const base::FilePath& cracked_path, + const std::string& filesystem_id); + + bool is_valid_; + + // Values parsed from the original URL. + GURL origin_; + FileSystemType mount_type_; + base::FilePath virtual_path_; + + // Values obtained by cracking URLs. + // |mount_filesystem_id_| is retrieved from the first round of cracking, + // and the rest of the fields are from recursive cracking. Permission + // checking on the top-level mount information should be done with the former, + // and the low-level file operation should be implemented with the latter. + std::string mount_filesystem_id_; + FileSystemType type_; + base::FilePath path_; + std::string filesystem_id_; +}; + +typedef std::set<FileSystemURL, FileSystemURL::Comparator> FileSystemURLSet; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_url_request_job.cc b/chromium/webkit/browser/fileapi/file_system_url_request_job.cc new file mode 100644 index 00000000000..d93c14089f2 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_url_request_job.cc @@ -0,0 +1,240 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_url_request_job.h" + +#include <vector> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/files/file_util_proxy.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/platform_file.h" +#include "base/threading/thread_restrictions.h" +#include "base/time/time.h" +#include "build/build_config.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_response_headers.h" +#include "net/http/http_response_info.h" +#include "net/http/http_util.h" +#include "net/url_request/url_request.h" +#include "url/gurl.h" +#include "webkit/browser/blob/file_stream_reader.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" +#include "webkit/common/fileapi/file_system_util.h" + +using net::NetworkDelegate; +using net::URLRequest; +using net::URLRequestJob; +using net::URLRequestStatus; + +namespace fileapi { + +static net::HttpResponseHeaders* CreateHttpResponseHeaders() { + // HttpResponseHeaders expects its input string to be terminated by two NULs. + static const char kStatus[] = "HTTP/1.1 200 OK\0"; + static const size_t kStatusLen = arraysize(kStatus); + + net::HttpResponseHeaders* headers = + new net::HttpResponseHeaders(std::string(kStatus, kStatusLen)); + + // Tell WebKit never to cache this content. + std::string cache_control(net::HttpRequestHeaders::kCacheControl); + cache_control.append(": no-cache"); + headers->AddHeader(cache_control); + + return headers; +} + +FileSystemURLRequestJob::FileSystemURLRequestJob( + URLRequest* request, + NetworkDelegate* network_delegate, + FileSystemContext* file_system_context) + : URLRequestJob(request, network_delegate), + file_system_context_(file_system_context), + weak_factory_(this), + is_directory_(false), + remaining_bytes_(0) { +} + +FileSystemURLRequestJob::~FileSystemURLRequestJob() {} + +void FileSystemURLRequestJob::Start() { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&FileSystemURLRequestJob::StartAsync, + weak_factory_.GetWeakPtr())); +} + +void FileSystemURLRequestJob::Kill() { + reader_.reset(); + URLRequestJob::Kill(); + weak_factory_.InvalidateWeakPtrs(); +} + +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 (reader_.get() == NULL) + return false; + + if (remaining_bytes_ < dest_size) + dest_size = static_cast<int>(remaining_bytes_); + + if (!dest_size) { + *bytes_read = 0; + return true; + } + + const int rv = reader_->Read(dest, dest_size, + base::Bind(&FileSystemURLRequestJob::DidRead, + weak_factory_.GetWeakPtr())); + if (rv >= 0) { + // Data is immediately available. + *bytes_read = rv; + remaining_bytes_ -= rv; + DCHECK_GE(remaining_bytes_, 0); + return true; + } + 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 { + DCHECK(request_); + DCHECK(url_.is_valid()); + base::FilePath::StringType extension = url_.path().Extension(); + if (!extension.empty()) + extension = extension.substr(1); + return net::GetWellKnownMimeTypeFromExtension(extension, 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. + NotifyFailed(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); + } + } + } +} + +void FileSystemURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) { + if (response_info_) + *info = *response_info_; +} + +int FileSystemURLRequestJob::GetResponseCode() const { + if (response_info_) + return 200; + return URLRequestJob::GetResponseCode(); +} + +void FileSystemURLRequestJob::StartAsync() { + if (!request_) + return; + DCHECK(!reader_.get()); + url_ = file_system_context_->CrackURL(request_->url()); + file_system_context_->operation_runner()->GetMetadata( + url_, + base::Bind(&FileSystemURLRequestJob::DidGetMetadata, + weak_factory_.GetWeakPtr())); +} + +void FileSystemURLRequestJob::DidGetMetadata( + base::PlatformFileError error_code, + const base::PlatformFileInfo& file_info) { + if (error_code != base::PLATFORM_FILE_OK) { + NotifyFailed(error_code == base::PLATFORM_FILE_ERROR_INVALID_URL ? + net::ERR_INVALID_URL : net::ERR_FILE_NOT_FOUND); + return; + } + + // We may have been orphaned... + if (!request_) + 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_) { + NotifyHeadersComplete(); + return; + } + + remaining_bytes_ = byte_range_.last_byte_position() - + byte_range_.first_byte_position() + 1; + DCHECK_GE(remaining_bytes_, 0); + + DCHECK(!reader_.get()); + reader_ = file_system_context_->CreateFileStreamReader( + url_, byte_range_.first_byte_position(), base::Time()); + + set_expected_content_size(remaining_bytes_); + response_info_.reset(new net::HttpResponseInfo()); + response_info_->headers = CreateHttpResponseHeaders(); + 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/chromium/webkit/browser/fileapi/file_system_url_request_job.h b/chromium/webkit/browser/fileapi/file_system_url_request_job.h new file mode 100644 index 00000000000..6b829e6e861 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_url_request_job.h @@ -0,0 +1,83 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_ + +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/platform_file.h" +#include "net/http/http_byte_range.h" +#include "net/url_request/url_request_job.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +class GURL; + +namespace base { +class FilePath; +} + +namespace webkit_blob { +class FileStreamReader; +} + +namespace fileapi { +class FileSystemContext; + +// A request job that handles reading filesystem: URLs +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileSystemURLRequestJob + : public net::URLRequestJob { + public: + FileSystemURLRequestJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + FileSystemContext* file_system_context); + + // URLRequestJob methods: + virtual void Start() OVERRIDE; + virtual void Kill() OVERRIDE; + virtual bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int* bytes_read) OVERRIDE; + virtual bool IsRedirectResponse(GURL* location, + int* http_status_code) OVERRIDE; + virtual void SetExtraRequestHeaders( + const net::HttpRequestHeaders& headers) OVERRIDE; + virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE; + virtual int GetResponseCode() const OVERRIDE; + + // FilterContext methods (via URLRequestJob): + virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; + + private: + class CallbackDispatcher; + + virtual ~FileSystemURLRequestJob(); + + void StartAsync(); + void DidGetMetadata( + base::PlatformFileError error_code, + const base::PlatformFileInfo& file_info); + void DidRead(int result); + void NotifyFailed(int rv); + + FileSystemContext* file_system_context_; + base::WeakPtrFactory<FileSystemURLRequestJob> weak_factory_; + scoped_ptr<webkit_blob::FileStreamReader> reader_; + FileSystemURL url_; + bool is_directory_; + scoped_ptr<net::HttpResponseInfo> response_info_; + int64 remaining_bytes_; + net::HttpByteRange byte_range_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemURLRequestJob); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_url_request_job_factory.cc b/chromium/webkit/browser/fileapi/file_system_url_request_job_factory.cc new file mode 100644 index 00000000000..76ae7c9ac05 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_url_request_job_factory.cc @@ -0,0 +1,68 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_url_request_job_factory.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "net/url_request/url_request.h" +#include "webkit/browser/fileapi/file_system_dir_url_request_job.h" +#include "webkit/browser/fileapi/file_system_url_request_job.h" + +namespace fileapi { + +namespace { + +class FileSystemProtocolHandler + : public net::URLRequestJobFactory::ProtocolHandler { + public: + explicit FileSystemProtocolHandler(FileSystemContext* context); + virtual ~FileSystemProtocolHandler(); + + virtual net::URLRequestJob* MaybeCreateJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate) const OVERRIDE; + + private: + // No scoped_refptr because |file_system_context_| is owned by the + // ProfileIOData, which also owns this ProtocolHandler. + FileSystemContext* const file_system_context_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemProtocolHandler); +}; + +FileSystemProtocolHandler::FileSystemProtocolHandler( + FileSystemContext* context) + : file_system_context_(context) { + DCHECK(file_system_context_); +} + +FileSystemProtocolHandler::~FileSystemProtocolHandler() {} + +net::URLRequestJob* FileSystemProtocolHandler::MaybeCreateJob( + net::URLRequest* request, net::NetworkDelegate* network_delegate) const { + const std::string path = request->url().path(); + + // If the path ends with a /, we know it's a directory. If the path refers + // to a directory and gets dispatched to FileSystemURLRequestJob, that class + // redirects back here, by adding a / to the URL. + if (!path.empty() && path[path.size() - 1] == '/') { + return new FileSystemDirURLRequestJob( + request, network_delegate, file_system_context_); + } + return new FileSystemURLRequestJob( + request, network_delegate, file_system_context_); +} + +} // anonymous namespace + +net::URLRequestJobFactory::ProtocolHandler* +CreateFileSystemProtocolHandler(FileSystemContext* context) { + DCHECK(context); + return new FileSystemProtocolHandler(context); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_url_request_job_factory.h b/chromium/webkit/browser/fileapi/file_system_url_request_job_factory.h new file mode 100644 index 00000000000..5bd3c0f91a6 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_url_request_job_factory.h @@ -0,0 +1,28 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_FACTORY_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_FACTORY_H_ + +#include "net/url_request/url_request_job_factory.h" + +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace base { +class MessageLoopProxy; +} // namespace base + +namespace fileapi { + +class FileSystemContext; + +// |context|'s lifetime should exceed the lifetime of the ProtocolHandler. +// Currently, this is only used by ProfileIOData which owns |context| and the +// ProtocolHandler. +WEBKIT_STORAGE_BROWSER_EXPORT net::URLRequestJobFactory::ProtocolHandler* + CreateFileSystemProtocolHandler(FileSystemContext* context); + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_FACTORY_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_url_request_job_unittest.cc b/chromium/webkit/browser/fileapi/file_system_url_request_job_unittest.cc new file mode 100644 index 00000000000..a8f6cabbffe --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_url_request_job_unittest.cc @@ -0,0 +1,361 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_url_request_job.h" + +#include <string> + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/format_macros.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/platform_file.h" +#include "base/rand_util.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "net/base/load_flags.h" +#include "net/base/mime_util.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_context.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/mock_file_system_context.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) { + base::RandBytes(buffer, len); +} + +} // namespace + +class FileSystemURLRequestJobTest : public testing::Test { + protected: + FileSystemURLRequestJobTest() + : message_loop_(base::MessageLoop::TYPE_IO), // simulate an IO thread + weak_factory_(this) { + } + + virtual void SetUp() OVERRIDE { + 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. + file_system_context_ = + CreateFileSystemContextForTesting(NULL, temp_dir_.path()); + + file_system_context_->OpenFileSystem( + GURL("http://remote/"), kFileSystemTypeTemporary, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&FileSystemURLRequestJobTest::OnOpenFileSystem, + weak_factory_.GetWeakPtr())); + base::MessageLoop::current()->RunUntilIdle(); + + net::URLRequest::Deprecated::RegisterProtocolFactory( + "filesystem", &FileSystemURLRequestJobFactory); + } + + virtual void TearDown() OVERRIDE { + net::URLRequest::Deprecated::RegisterProtocolFactory("filesystem", NULL); + ClearUnusedJob(); + if (pending_job_.get()) { + pending_job_->Kill(); + pending_job_ = NULL; + } + // FileReader posts a task to close the file in destructor. + base::MessageLoop::current()->RunUntilIdle(); + } + + void OnOpenFileSystem(base::PlatformFileError result, + const std::string& name, + const GURL& root_url) { + ASSERT_EQ(base::PLATFORM_FILE_OK, result); + } + + void TestRequestHelper(const GURL& url, + const net::HttpRequestHeaders* headers, + bool run_to_completion) { + delegate_.reset(new net::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(empty_context_.CreateRequest(url, delegate_.get())); + if (headers) + request_->SetExtraRequestHeaders(*headers); + ASSERT_TRUE(!job_); + job_ = new FileSystemURLRequestJob( + request_.get(), NULL, file_system_context_.get()); + pending_job_ = job_; + + request_->Start(); + ASSERT_TRUE(request_->is_pending()); // verify that we're starting async + if (run_to_completion) + base::MessageLoop::current()->Run(); + } + + void TestRequest(const GURL& url) { + TestRequestHelper(url, NULL, true); + } + + void TestRequestWithHeaders(const GURL& url, + const net::HttpRequestHeaders* headers) { + TestRequestHelper(url, headers, true); + } + + void TestRequestNoRun(const GURL& url) { + TestRequestHelper(url, NULL, false); + } + + void CreateDirectory(const base::StringPiece& dir_name) { + FileSystemFileUtil* file_util = file_system_context_->GetFileUtil( + kFileSystemTypeTemporary); + FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( + GURL("http://remote"), + kFileSystemTypeTemporary, + base::FilePath().AppendASCII(dir_name)); + + FileSystemOperationContext context(file_system_context_.get()); + context.set_allowed_bytes_growth(1024); + + ASSERT_EQ(base::PLATFORM_FILE_OK, file_util->CreateDirectory( + &context, + url, + false /* exclusive */, + false /* recursive */)); + } + + void WriteFile(const base::StringPiece& file_name, + const char* buf, int buf_size) { + FileSystemFileUtil* file_util = file_system_context_->GetFileUtil( + kFileSystemTypeTemporary); + FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( + GURL("http://remote"), + kFileSystemTypeTemporary, + base::FilePath().AppendASCII(file_name)); + + FileSystemOperationContext context(file_system_context_.get()); + context.set_allowed_bytes_growth(1024); + + base::PlatformFile handle = base::kInvalidPlatformFileValue; + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, file_util->CreateOrOpen( + &context, + url, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, + &handle, + &created)); + EXPECT_TRUE(created); + ASSERT_NE(base::kInvalidPlatformFileValue, handle); + ASSERT_EQ(buf_size, + base::WritePlatformFile(handle, 0 /* offset */, buf, buf_size)); + base::ClosePlatformFile(handle); + } + + GURL CreateFileSystemURL(const std::string& path) { + return GURL(kFileSystemURLPrefix + path); + } + + static net::URLRequestJob* FileSystemURLRequestJobFactory( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const std::string& scheme) { + DCHECK(job_); + net::URLRequestJob* temp = job_; + job_ = NULL; + return temp; + } + + static void ClearUnusedJob() { + if (job_) { + scoped_refptr<net::URLRequestJob> deleter = job_; + job_ = NULL; + } + } + + // Put the message loop at the top, so that it's the last thing deleted. + base::MessageLoop message_loop_; + + base::ScopedTempDir temp_dir_; + scoped_refptr<FileSystemContext> file_system_context_; + base::WeakPtrFactory<FileSystemURLRequestJobTest> weak_factory_; + + net::URLRequestContext empty_context_; + + // NOTE: order matters, request must die before delegate + scoped_ptr<net::TestDelegate> delegate_; + scoped_ptr<net::URLRequest> request_; + + scoped_refptr<net::URLRequestJob> pending_job_; + static net::URLRequestJob* job_; +}; + +// static +net::URLRequestJob* FileSystemURLRequestJobTest::job_ = NULL; + +namespace { + +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()); + EXPECT_EQ(200, request_->GetResponseCode()); + std::string cache_control; + request_->GetResponseHeaderByName("cache-control", &cache_control); + EXPECT_EQ("no-cache", cache_control); +} + +TEST_F(FileSystemURLRequestJobTest, FileTestFullSpecifiedRange) { + const size_t buffer_size = 4000; + scoped_ptr<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_ptr<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); + EXPECT_TRUE(delegate_->request_failed()); + EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, + request_->status().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().error()); +} + +TEST_F(FileSystemURLRequestJobTest, FileDirRedirect) { + CreateDirectory("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(); + base::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().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().error()); +} + +TEST_F(FileSystemURLRequestJobTest, NoSuchFile) { + TestRequest(CreateFileSystemURL("somefile")); + ASSERT_FALSE(request_->is_pending()); + EXPECT_TRUE(delegate_->request_failed()); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error()); +} + +TEST_F(FileSystemURLRequestJobTest, Cancel) { + WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1); + TestRequestNoRun(CreateFileSystemURL("file1.dat")); + + // Run StartAsync() and only StartAsync(). + base::MessageLoop::current()->DeleteSoon(FROM_HERE, request_.release()); + base::MessageLoop::current()->RunUntilIdle(); + // If we get here, success! we didn't crash! +} + +TEST_F(FileSystemURLRequestJobTest, GetMimeType) { + const char kFilename[] = "hoge.html"; + + std::string mime_type_direct; + base::FilePath::StringType extension = + base::FilePath().AppendASCII(kFilename).Extension(); + if (!extension.empty()) + extension = extension.substr(1); + EXPECT_TRUE(net::GetWellKnownMimeTypeFromExtension( + extension, &mime_type_direct)); + + TestRequest(CreateFileSystemURL(kFilename)); + + std::string mime_type_from_job; + request_->GetMimeType(&mime_type_from_job); + EXPECT_EQ(mime_type_direct, mime_type_from_job); +} + +} // namespace +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_url_unittest.cc b/chromium/webkit/browser/fileapi/file_system_url_unittest.cc new file mode 100644 index 00000000000..ace67007cb3 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_url_unittest.cc @@ -0,0 +1,194 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_url.h" + +#include "base/files/file_path.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" +#include "webkit/common/fileapi/file_system_types.h" +#include "webkit/common/fileapi/file_system_util.h" + +#define FPL FILE_PATH_LITERAL + +#if defined(FILE_PATH_USES_DRIVE_LETTERS) +#define DRIVE FPL("C:") +#else +#define DRIVE FPL("/a/") +#endif + +namespace fileapi { + +namespace { + +FileSystemURL CreateFileSystemURL(const std::string& url_string) { + FileSystemURL url = FileSystemURL::CreateForTest(GURL(url_string)); + EXPECT_TRUE(url.type() != kFileSystemTypeExternal && + url.type() != kFileSystemTypeIsolated); + return url; +} + +std::string NormalizedUTF8Path(const base::FilePath& path) { + return path.NormalizePathSeparators().AsUTF8Unsafe(); +} + +} // namespace + +TEST(FileSystemURLTest, ParsePersistent) { + FileSystemURL url = CreateFileSystemURL( + "filesystem:http://chromium.org/persistent/directory/file"); + ASSERT_TRUE(url.is_valid()); + EXPECT_EQ("http://chromium.org/", url.origin().spec()); + EXPECT_EQ(kFileSystemTypePersistent, url.type()); + EXPECT_EQ(FPL("file"), VirtualPath::BaseName(url.path()).value()); + EXPECT_EQ(FPL("directory"), url.path().DirName().value()); +} + +TEST(FileSystemURLTest, ParseTemporary) { + FileSystemURL url = CreateFileSystemURL( + "filesystem:http://chromium.org/temporary/directory/file"); + ASSERT_TRUE(url.is_valid()); + EXPECT_EQ("http://chromium.org/", url.origin().spec()); + EXPECT_EQ(kFileSystemTypeTemporary, url.type()); + EXPECT_EQ(FPL("file"), VirtualPath::BaseName(url.path()).value()); + EXPECT_EQ(FPL("directory"), url.path().DirName().value()); +} + +TEST(FileSystemURLTest, EnsureFilePathIsRelative) { + FileSystemURL url = CreateFileSystemURL( + "filesystem:http://chromium.org/temporary/////directory/file"); + ASSERT_TRUE(url.is_valid()); + EXPECT_EQ("http://chromium.org/", url.origin().spec()); + EXPECT_EQ(kFileSystemTypeTemporary, url.type()); + EXPECT_EQ(FPL("file"), VirtualPath::BaseName(url.path()).value()); + EXPECT_EQ(FPL("directory"), url.path().DirName().value()); + EXPECT_FALSE(url.path().IsAbsolute()); +} + +TEST(FileSystemURLTest, RejectBadSchemes) { + EXPECT_FALSE(CreateFileSystemURL("http://chromium.org/").is_valid()); + EXPECT_FALSE(CreateFileSystemURL("https://chromium.org/").is_valid()); + EXPECT_FALSE(CreateFileSystemURL("file:///foo/bar").is_valid()); + EXPECT_FALSE(CreateFileSystemURL("foobar:///foo/bar").is_valid()); +} + +TEST(FileSystemURLTest, UnescapePath) { + FileSystemURL url = CreateFileSystemURL( + "filesystem:http://chromium.org/persistent/%7Echromium/space%20bar"); + ASSERT_TRUE(url.is_valid()); + EXPECT_EQ(FPL("space bar"), VirtualPath::BaseName(url.path()).value()); + EXPECT_EQ(FPL("~chromium"), url.path().DirName().value()); +} + +TEST(FileSystemURLTest, RejectBadType) { + EXPECT_FALSE(CreateFileSystemURL( + "filesystem:http://c.org/foobar/file").is_valid()); +} + +TEST(FileSystemURLTest, RejectMalformedURL) { + EXPECT_FALSE(CreateFileSystemURL("filesystem:///foobar/file").is_valid()); + EXPECT_FALSE(CreateFileSystemURL("filesystem:foobar/file").is_valid()); +} + +TEST(FileSystemURLTest, CompareURLs) { + const GURL urls[] = { + GURL("filesystem:http://chromium.org/temporary/dir a/file a"), + GURL("filesystem:http://chromium.org/temporary/dir a/file a"), + GURL("filesystem:http://chromium.org/temporary/dir a/file b"), + GURL("filesystem:http://chromium.org/temporary/dir a/file aa"), + GURL("filesystem:http://chromium.org/temporary/dir b/file a"), + GURL("filesystem:http://chromium.org/temporary/dir aa/file b"), + GURL("filesystem:http://chromium.com/temporary/dir a/file a"), + GURL("filesystem:https://chromium.org/temporary/dir a/file a") + }; + + FileSystemURL::Comparator compare; + for (size_t i = 0; i < arraysize(urls); ++i) { + for (size_t j = 0; j < arraysize(urls); ++j) { + SCOPED_TRACE(testing::Message() << i << " < " << j); + EXPECT_EQ(urls[i] < urls[j], + compare(FileSystemURL::CreateForTest(urls[i]), + FileSystemURL::CreateForTest(urls[j]))); + } + } + + const FileSystemURL a = CreateFileSystemURL( + "filesystem:http://chromium.org/temporary/dir a/file a"); + const FileSystemURL b = CreateFileSystemURL( + "filesystem:http://chromium.org/persistent/dir a/file a"); + EXPECT_EQ(a.type() < b.type(), compare(a, b)); + EXPECT_EQ(b.type() < a.type(), compare(b, a)); +} + +TEST(FileSystemURLTest, IsParent) { + const std::string root1 = GetFileSystemRootURI( + GURL("http://example.com"), kFileSystemTypeTemporary).spec(); + const std::string root2 = GetFileSystemRootURI( + GURL("http://example.com"), kFileSystemTypePersistent).spec(); + const std::string root3 = GetFileSystemRootURI( + GURL("http://chromium.org"), kFileSystemTypeTemporary).spec(); + + const std::string parent("dir"); + const std::string child("dir/child"); + const std::string other("other"); + + // True cases. + EXPECT_TRUE(CreateFileSystemURL(root1 + parent).IsParent( + CreateFileSystemURL(root1 + child))); + EXPECT_TRUE(CreateFileSystemURL(root2 + parent).IsParent( + CreateFileSystemURL(root2 + child))); + + // False cases: the path is not a child. + EXPECT_FALSE(CreateFileSystemURL(root1 + parent).IsParent( + CreateFileSystemURL(root1 + other))); + EXPECT_FALSE(CreateFileSystemURL(root1 + parent).IsParent( + CreateFileSystemURL(root1 + parent))); + EXPECT_FALSE(CreateFileSystemURL(root1 + child).IsParent( + CreateFileSystemURL(root1 + parent))); + + // False case: different types. + EXPECT_FALSE(CreateFileSystemURL(root1 + parent).IsParent( + CreateFileSystemURL(root2 + child))); + + // False case: different origins. + EXPECT_FALSE(CreateFileSystemURL(root1 + parent).IsParent( + CreateFileSystemURL(root3 + child))); +} + +TEST(FileSystemURLTest, DebugString) { + const GURL kOrigin("http://example.com"); + const base::FilePath kPath(FPL("dir/file")); + + const FileSystemURL kURL1 = FileSystemURL::CreateForTest( + kOrigin, kFileSystemTypeTemporary, kPath); + EXPECT_EQ("filesystem:http://example.com/temporary/" + + NormalizedUTF8Path(kPath), + kURL1.DebugString()); +} + +TEST(FileSystemURLTest, IsInSameFileSystem) { + FileSystemURL url_foo_temp_a = FileSystemURL::CreateForTest( + GURL("http://foo"), kFileSystemTypeTemporary, + base::FilePath::FromUTF8Unsafe("a")); + FileSystemURL url_foo_temp_b = FileSystemURL::CreateForTest( + GURL("http://foo"), kFileSystemTypeTemporary, + base::FilePath::FromUTF8Unsafe("b")); + FileSystemURL url_foo_perm_a = FileSystemURL::CreateForTest( + GURL("http://foo"), kFileSystemTypePersistent, + base::FilePath::FromUTF8Unsafe("a")); + FileSystemURL url_bar_temp_a = FileSystemURL::CreateForTest( + GURL("http://bar"), kFileSystemTypeTemporary, + base::FilePath::FromUTF8Unsafe("a")); + FileSystemURL url_bar_perm_a = FileSystemURL::CreateForTest( + GURL("http://bar"), kFileSystemTypePersistent, + base::FilePath::FromUTF8Unsafe("a")); + + EXPECT_TRUE(url_foo_temp_a.IsInSameFileSystem(url_foo_temp_a)); + EXPECT_TRUE(url_foo_temp_a.IsInSameFileSystem(url_foo_temp_b)); + EXPECT_FALSE(url_foo_temp_a.IsInSameFileSystem(url_foo_perm_a)); + EXPECT_FALSE(url_foo_temp_a.IsInSameFileSystem(url_bar_temp_a)); + EXPECT_FALSE(url_foo_temp_a.IsInSameFileSystem(url_bar_perm_a)); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_usage_cache.cc b/chromium/webkit/browser/fileapi/file_system_usage_cache.cc new file mode 100644 index 00000000000..c1896fb1f95 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_usage_cache.cc @@ -0,0 +1,316 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_usage_cache.h" + +#include <utility> + +#include "base/bind.h" +#include "base/debug/trace_event.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/pickle.h" +#include "base/stl_util.h" +#include "webkit/browser/fileapi/timed_task_helper.h" + +namespace fileapi { + +namespace { +const int64 kCloseDelaySeconds = 5; +const size_t kMaxHandleCacheSize = 2; +} // namespace + +FileSystemUsageCache::FileSystemUsageCache( + base::SequencedTaskRunner* task_runner) + : weak_factory_(this), + task_runner_(task_runner) { +} + +FileSystemUsageCache::~FileSystemUsageCache() { + task_runner_ = NULL; + CloseCacheFiles(); +} + +const base::FilePath::CharType FileSystemUsageCache::kUsageFileName[] = + FILE_PATH_LITERAL(".usage"); +const char FileSystemUsageCache::kUsageFileHeader[] = "FSU5"; +const int FileSystemUsageCache::kUsageFileHeaderSize = 4; + +// Pickle::{Read,Write}Bool treat bool as int +const int FileSystemUsageCache::kUsageFileSize = + sizeof(Pickle::Header) + + FileSystemUsageCache::kUsageFileHeaderSize + + sizeof(int) + sizeof(int32) + sizeof(int64); // NOLINT + +bool FileSystemUsageCache::GetUsage(const base::FilePath& usage_file_path, + int64* usage_out) { + TRACE_EVENT0("FileSystem", "UsageCache::GetUsage"); + DCHECK(CalledOnValidThread()); + DCHECK(usage_out); + bool is_valid = true; + uint32 dirty = 0; + int64 usage = 0; + if (!Read(usage_file_path, &is_valid, &dirty, &usage)) + return false; + *usage_out = usage; + return true; +} + +bool FileSystemUsageCache::GetDirty(const base::FilePath& usage_file_path, + uint32* dirty_out) { + TRACE_EVENT0("FileSystem", "UsageCache::GetDirty"); + DCHECK(CalledOnValidThread()); + DCHECK(dirty_out); + bool is_valid = true; + uint32 dirty = 0; + int64 usage = 0; + if (!Read(usage_file_path, &is_valid, &dirty, &usage)) + return false; + *dirty_out = dirty; + return true; +} + +bool FileSystemUsageCache::IncrementDirty( + const base::FilePath& usage_file_path) { + TRACE_EVENT0("FileSystem", "UsageCache::IncrementDirty"); + DCHECK(CalledOnValidThread()); + bool is_valid = true; + uint32 dirty = 0; + int64 usage = 0; + bool new_handle = !HasCacheFileHandle(usage_file_path); + if (!Read(usage_file_path, &is_valid, &dirty, &usage)) + return false; + + bool success = Write(usage_file_path, is_valid, dirty + 1, usage); + if (success && dirty == 0 && new_handle) + FlushFile(usage_file_path); + return success; +} + +bool FileSystemUsageCache::DecrementDirty( + const base::FilePath& usage_file_path) { + TRACE_EVENT0("FileSystem", "UsageCache::DecrementDirty"); + DCHECK(CalledOnValidThread()); + bool is_valid = true; + uint32 dirty = 0; + int64 usage = 0;; + if (!Read(usage_file_path, &is_valid, &dirty, &usage) || dirty <= 0) + return false; + + if (dirty <= 0) + return false; + + return Write(usage_file_path, is_valid, dirty - 1, usage); +} + +bool FileSystemUsageCache::Invalidate(const base::FilePath& usage_file_path) { + TRACE_EVENT0("FileSystem", "UsageCache::Invalidate"); + DCHECK(CalledOnValidThread()); + bool is_valid = true; + uint32 dirty = 0; + int64 usage = 0; + if (!Read(usage_file_path, &is_valid, &dirty, &usage)) + return false; + + return Write(usage_file_path, false, dirty, usage); +} + +bool FileSystemUsageCache::IsValid(const base::FilePath& usage_file_path) { + TRACE_EVENT0("FileSystem", "UsageCache::IsValid"); + DCHECK(CalledOnValidThread()); + bool is_valid = true; + uint32 dirty = 0; + int64 usage = 0; + if (!Read(usage_file_path, &is_valid, &dirty, &usage)) + return false; + return is_valid; +} + +bool FileSystemUsageCache::AtomicUpdateUsageByDelta( + const base::FilePath& usage_file_path, int64 delta) { + TRACE_EVENT0("FileSystem", "UsageCache::AtomicUpdateUsageByDelta"); + DCHECK(CalledOnValidThread()); + bool is_valid = true; + uint32 dirty = 0; + int64 usage = 0;; + if (!Read(usage_file_path, &is_valid, &dirty, &usage)) + return false; + return Write(usage_file_path, is_valid, dirty, usage + delta); +} + +bool FileSystemUsageCache::UpdateUsage(const base::FilePath& usage_file_path, + int64 fs_usage) { + TRACE_EVENT0("FileSystem", "UsageCache::UpdateUsage"); + DCHECK(CalledOnValidThread()); + return Write(usage_file_path, true, 0, fs_usage); +} + +bool FileSystemUsageCache::Exists(const base::FilePath& usage_file_path) { + TRACE_EVENT0("FileSystem", "UsageCache::Exists"); + DCHECK(CalledOnValidThread()); + return base::PathExists(usage_file_path); +} + +bool FileSystemUsageCache::Delete(const base::FilePath& usage_file_path) { + TRACE_EVENT0("FileSystem", "UsageCache::Delete"); + DCHECK(CalledOnValidThread()); + CloseCacheFiles(); + return base::DeleteFile(usage_file_path, true); +} + +void FileSystemUsageCache::CloseCacheFiles() { + TRACE_EVENT0("FileSystem", "UsageCache::CloseCacheFiles"); + DCHECK(CalledOnValidThread()); + for (CacheFiles::iterator itr = cache_files_.begin(); + itr != cache_files_.end(); ++itr) { + if (itr->second != base::kInvalidPlatformFileValue) + base::ClosePlatformFile(itr->second); + } + cache_files_.clear(); + timer_.reset(); +} + +bool FileSystemUsageCache::Read(const base::FilePath& usage_file_path, + bool* is_valid, + uint32* dirty_out, + int64* usage_out) { + TRACE_EVENT0("FileSystem", "UsageCache::Read"); + DCHECK(CalledOnValidThread()); + DCHECK(is_valid); + DCHECK(dirty_out); + DCHECK(usage_out); + char buffer[kUsageFileSize]; + const char *header; + if (usage_file_path.empty() || + !ReadBytes(usage_file_path, buffer, kUsageFileSize)) + return false; + Pickle read_pickle(buffer, kUsageFileSize); + PickleIterator iter(read_pickle); + uint32 dirty = 0; + int64 usage = 0; + + if (!read_pickle.ReadBytes(&iter, &header, kUsageFileHeaderSize) || + !read_pickle.ReadBool(&iter, is_valid) || + !read_pickle.ReadUInt32(&iter, &dirty) || + !read_pickle.ReadInt64(&iter, &usage)) + return false; + + if (header[0] != kUsageFileHeader[0] || + header[1] != kUsageFileHeader[1] || + header[2] != kUsageFileHeader[2] || + header[3] != kUsageFileHeader[3]) + return false; + + *dirty_out = dirty; + *usage_out = usage; + return true; +} + +bool FileSystemUsageCache::Write(const base::FilePath& usage_file_path, + bool is_valid, + int32 dirty, + int64 usage) { + TRACE_EVENT0("FileSystem", "UsageCache::Write"); + DCHECK(CalledOnValidThread()); + Pickle write_pickle; + write_pickle.WriteBytes(kUsageFileHeader, kUsageFileHeaderSize); + write_pickle.WriteBool(is_valid); + write_pickle.WriteUInt32(dirty); + write_pickle.WriteInt64(usage); + + if (!WriteBytes(usage_file_path, + static_cast<const char*>(write_pickle.data()), + write_pickle.size())) { + Delete(usage_file_path); + return false; + } + return true; +} + +bool FileSystemUsageCache::GetPlatformFile(const base::FilePath& file_path, + base::PlatformFile* file) { + DCHECK(CalledOnValidThread()); + if (cache_files_.size() >= kMaxHandleCacheSize) + CloseCacheFiles(); + ScheduleCloseTimer(); + + std::pair<CacheFiles::iterator, bool> inserted = + cache_files_.insert( + std::make_pair(file_path, base::kInvalidPlatformFileValue)); + if (!inserted.second) { + *file = inserted.first->second; + return true; + } + + base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + base::PlatformFile platform_file = + base::CreatePlatformFile(file_path, + base::PLATFORM_FILE_OPEN_ALWAYS | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE, + NULL, &error); + if (error != base::PLATFORM_FILE_OK) { + cache_files_.erase(inserted.first); + return false; + } + + inserted.first->second = platform_file; + *file = platform_file; + return true; +} + +bool FileSystemUsageCache::ReadBytes(const base::FilePath& file_path, + char* buffer, + int64 buffer_size) { + DCHECK(CalledOnValidThread()); + base::PlatformFile file; + if (!GetPlatformFile(file_path, &file)) + return false; + return base::ReadPlatformFile(file, 0, buffer, buffer_size) == buffer_size; +} + +bool FileSystemUsageCache::WriteBytes(const base::FilePath& file_path, + const char* buffer, + int64 buffer_size) { + DCHECK(CalledOnValidThread()); + base::PlatformFile file; + if (!GetPlatformFile(file_path, &file)) + return false; + return base::WritePlatformFile(file, 0, buffer, buffer_size) == buffer_size; +} + +bool FileSystemUsageCache::FlushFile(const base::FilePath& file_path) { + TRACE_EVENT0("FileSystem", "UsageCache::FlushFile"); + DCHECK(CalledOnValidThread()); + base::PlatformFile file = base::kInvalidPlatformFileValue; + return GetPlatformFile(file_path, &file) && base::FlushPlatformFile(file); +} + +void FileSystemUsageCache::ScheduleCloseTimer() { + DCHECK(CalledOnValidThread()); + if (!timer_) + timer_.reset(new TimedTaskHelper(task_runner_.get())); + + if (timer_->IsRunning()) { + timer_->Reset(); + return; + } + + timer_->Start(FROM_HERE, + base::TimeDelta::FromSeconds(kCloseDelaySeconds), + base::Bind(&FileSystemUsageCache::CloseCacheFiles, + weak_factory_.GetWeakPtr())); +} + +bool FileSystemUsageCache::CalledOnValidThread() { + return !task_runner_.get() || task_runner_->RunsTasksOnCurrentThread(); +} + +bool FileSystemUsageCache::HasCacheFileHandle(const base::FilePath& file_path) { + DCHECK(CalledOnValidThread()); + DCHECK_LE(cache_files_.size(), kMaxHandleCacheSize); + return ContainsKey(cache_files_, file_path); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_system_usage_cache.h b/chromium/webkit/browser/fileapi/file_system_usage_cache.h new file mode 100644 index 00000000000..b4d8464884b --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_usage_cache.h @@ -0,0 +1,105 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_USAGE_CACHE_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_USAGE_CACHE_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/platform_file.h" +#include "base/sequenced_task_runner.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace fileapi { + +class TimedTaskHelper; + +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileSystemUsageCache { + public: + explicit FileSystemUsageCache(base::SequencedTaskRunner* task_runner); + ~FileSystemUsageCache(); + + // Gets the size described in the .usage file even if dirty > 0 or + // is_valid == false. Returns true if the .usage file is available. + bool GetUsage(const base::FilePath& usage_file_path, int64* usage); + + // Gets the dirty count in the .usage file. + // Returns true if the .usage file is available. + bool GetDirty(const base::FilePath& usage_file_path, uint32* dirty); + + // Increments or decrements the "dirty" entry in the .usage file. + // Returns false if no .usage is available. + bool IncrementDirty(const base::FilePath& usage_file_path); + bool DecrementDirty(const base::FilePath& usage_file_path); + + // Notifies quota system that it needs to recalculate the usage cache of the + // origin. Returns false if no .usage is available. + bool Invalidate(const base::FilePath& usage_file_path); + bool IsValid(const base::FilePath& usage_file_path); + + // Updates the size described in the .usage file. + bool UpdateUsage(const base::FilePath& usage_file_path, int64 fs_usage); + + // Updates the size described in the .usage file by delta with keeping dirty + // even if dirty > 0. + bool AtomicUpdateUsageByDelta(const base::FilePath& usage_file_path, + int64 delta); + + bool Exists(const base::FilePath& usage_file_path); + bool Delete(const base::FilePath& usage_file_path); + + void CloseCacheFiles(); + + static const base::FilePath::CharType kUsageFileName[]; + static const char kUsageFileHeader[]; + static const int kUsageFileSize; + static const int kUsageFileHeaderSize; + + private: + typedef std::map<base::FilePath, base::PlatformFile> CacheFiles; + + // Read the size, validity and the "dirty" entry described in the .usage file. + // Returns less than zero if no .usage file is available. + bool Read(const base::FilePath& usage_file_path, + bool* is_valid, + uint32* dirty, + int64* usage); + + bool Write(const base::FilePath& usage_file_path, + bool is_valid, + int32 dirty, + int64 fs_usage); + + bool GetPlatformFile(const base::FilePath& file_path, + base::PlatformFile* file); + + bool ReadBytes(const base::FilePath& file_path, + char* buffer, + int64 buffer_size); + bool WriteBytes(const base::FilePath& file_path, + const char* buffer, + int64 buffer_size); + bool FlushFile(const base::FilePath& file_path); + void ScheduleCloseTimer(); + + bool HasCacheFileHandle(const base::FilePath& file_path); + + bool CalledOnValidThread(); + + scoped_ptr<TimedTaskHelper> timer_; + std::map<base::FilePath, base::PlatformFile> cache_files_; + base::WeakPtrFactory<FileSystemUsageCache> weak_factory_; + + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemUsageCache); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_USAGE_CACHE_H_ diff --git a/chromium/webkit/browser/fileapi/file_system_usage_cache_unittest.cc b/chromium/webkit/browser/fileapi/file_system_usage_cache_unittest.cc new file mode 100644 index 00000000000..1b99d9c10d2 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_system_usage_cache_unittest.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_system_usage_cache.h" + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace fileapi { + +class FileSystemUsageCacheTest : public testing::Test { + public: + FileSystemUsageCacheTest() + : usage_cache_(base::MessageLoopProxy::current().get()) {} + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + } + + protected: + base::FilePath GetUsageFilePath() { + return data_dir_.path().Append(FileSystemUsageCache::kUsageFileName); + } + + FileSystemUsageCache* usage_cache() { + return &usage_cache_; + } + + private: + base::MessageLoop message_loop_; + base::ScopedTempDir data_dir_; + FileSystemUsageCache usage_cache_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemUsageCacheTest); +}; + +TEST_F(FileSystemUsageCacheTest, CreateTest) { + base::FilePath usage_file_path = GetUsageFilePath(); + EXPECT_TRUE(usage_cache()->UpdateUsage(usage_file_path, 0)); +} + +TEST_F(FileSystemUsageCacheTest, SetSizeTest) { + static const int64 size = 240122; + base::FilePath usage_file_path = GetUsageFilePath(); + int64 usage = 0; + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size)); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(size, usage); +} + +TEST_F(FileSystemUsageCacheTest, SetLargeSizeTest) { + static const int64 size = kint64max; + base::FilePath usage_file_path = GetUsageFilePath(); + int64 usage = 0; + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size)); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(size, usage); +} + +TEST_F(FileSystemUsageCacheTest, IncAndGetSizeTest) { + base::FilePath usage_file_path = GetUsageFilePath(); + uint32 dirty = 0; + int64 usage = 0; + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, 98214)); + ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path)); + EXPECT_TRUE(usage_cache()->GetDirty(usage_file_path, &dirty)); + EXPECT_EQ(1u, dirty); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(98214, usage); +} + +TEST_F(FileSystemUsageCacheTest, DecAndGetSizeTest) { + static const int64 size = 71839; + base::FilePath usage_file_path = GetUsageFilePath(); + int64 usage = 0; + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size)); + // DecrementDirty for dirty = 0 is invalid. It returns false. + ASSERT_FALSE(usage_cache()->DecrementDirty(usage_file_path)); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(size, usage); +} + +TEST_F(FileSystemUsageCacheTest, IncDecAndGetSizeTest) { + static const int64 size = 198491; + base::FilePath usage_file_path = GetUsageFilePath(); + int64 usage = 0; + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size)); + ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path)); + ASSERT_TRUE(usage_cache()->DecrementDirty(usage_file_path)); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(size, usage); +} + +TEST_F(FileSystemUsageCacheTest, DecIncAndGetSizeTest) { + base::FilePath usage_file_path = GetUsageFilePath(); + uint32 dirty = 0; + int64 usage = 0; + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, 854238)); + // DecrementDirty for dirty = 0 is invalid. It returns false. + ASSERT_FALSE(usage_cache()->DecrementDirty(usage_file_path)); + ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path)); + // It tests DecrementDirty (which returns false) has no effect, i.e + // does not make dirty = -1 after DecrementDirty. + EXPECT_TRUE(usage_cache()->GetDirty(usage_file_path, &dirty)); + EXPECT_EQ(1u, dirty); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(854238, usage); +} + +TEST_F(FileSystemUsageCacheTest, ManyIncsSameDecsAndGetSizeTest) { + static const int64 size = 82412; + base::FilePath usage_file_path = GetUsageFilePath(); + int64 usage = 0; + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size)); + for (int i = 0; i < 20; i++) + ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path)); + for (int i = 0; i < 20; i++) + ASSERT_TRUE(usage_cache()->DecrementDirty(usage_file_path)); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(size, usage); +} + +TEST_F(FileSystemUsageCacheTest, ManyIncsLessDecsAndGetSizeTest) { + uint32 dirty = 0; + int64 usage = 0; + base::FilePath usage_file_path = GetUsageFilePath(); + ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, 19319)); + for (int i = 0; i < 20; i++) + ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path)); + for (int i = 0; i < 19; i++) + ASSERT_TRUE(usage_cache()->DecrementDirty(usage_file_path)); + EXPECT_TRUE(usage_cache()->GetDirty(usage_file_path, &dirty)); + EXPECT_EQ(1u, dirty); + EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage)); + EXPECT_EQ(19319, usage); +} + +TEST_F(FileSystemUsageCacheTest, GetSizeWithoutCacheFileTest) { + int64 usage = 0; + base::FilePath usage_file_path = GetUsageFilePath(); + EXPECT_FALSE(usage_cache()->GetUsage(usage_file_path, &usage)); +} + +TEST_F(FileSystemUsageCacheTest, IncrementDirtyWithoutCacheFileTest) { + base::FilePath usage_file_path = GetUsageFilePath(); + EXPECT_FALSE(usage_cache()->IncrementDirty(usage_file_path)); +} + +TEST_F(FileSystemUsageCacheTest, DecrementDirtyWithoutCacheFileTest) { + base::FilePath usage_file_path = GetUsageFilePath(); + EXPECT_FALSE(usage_cache()->IncrementDirty(usage_file_path)); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_writer_delegate.cc b/chromium/webkit/browser/fileapi/file_writer_delegate.cc new file mode 100644 index 00000000000..7a0094ee4d5 --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_writer_delegate.cc @@ -0,0 +1,254 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/file_writer_delegate.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_util_proxy.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/sequenced_task_runner.h" +#include "base/threading/thread_restrictions.h" +#include "net/base/net_errors.h" +#include "webkit/browser/fileapi/file_stream_writer.h" +#include "webkit/browser/fileapi/file_system_context.h" + +namespace fileapi { + +static const int kReadBufSize = 32768; + +namespace { + +base::PlatformFileError NetErrorToPlatformFileError(int error) { +// TODO(kinuko): Move this static method to more convenient place. + switch (error) { + case net::OK: + return base::PLATFORM_FILE_OK; + case net::ERR_FILE_NO_SPACE: + return base::PLATFORM_FILE_ERROR_NO_SPACE; + case net::ERR_FILE_NOT_FOUND: + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + case net::ERR_ACCESS_DENIED: + return base::PLATFORM_FILE_ERROR_ACCESS_DENIED; + default: + return base::PLATFORM_FILE_ERROR_FAILED; + } +} + +} // namespace + +FileWriterDelegate::FileWriterDelegate( + scoped_ptr<FileStreamWriter> file_stream_writer) + : file_stream_writer_(file_stream_writer.Pass()), + writing_started_(false), + bytes_written_backlog_(0), + bytes_written_(0), + bytes_read_(0), + io_buffer_(new net::IOBufferWithSize(kReadBufSize)), + weak_factory_(this) { +} + +FileWriterDelegate::~FileWriterDelegate() { +} + +void FileWriterDelegate::Start(scoped_ptr<net::URLRequest> request, + const DelegateWriteCallback& write_callback) { + write_callback_ = write_callback; + request_ = request.Pass(); + request_->Start(); +} + +void FileWriterDelegate::Cancel() { + if (request_) { + // This halts any callbacks on this delegate. + request_->set_delegate(NULL); + request_->Cancel(); + } + + const int status = file_stream_writer_->Cancel( + base::Bind(&FileWriterDelegate::OnWriteCancelled, + weak_factory_.GetWeakPtr())); + // Return true to finish immediately if we have no pending writes. + // Otherwise we'll do the final cleanup in the Cancel callback. + if (status != net::ERR_IO_PENDING) { + write_callback_.Run(base::PLATFORM_FILE_ERROR_ABORT, 0, + GetCompletionStatusOnError()); + } +} + +void FileWriterDelegate::OnReceivedRedirect(net::URLRequest* request, + const GURL& new_url, + bool* defer_redirect) { + NOTREACHED(); + OnError(base::PLATFORM_FILE_ERROR_SECURITY); +} + +void FileWriterDelegate::OnAuthRequired(net::URLRequest* request, + net::AuthChallengeInfo* auth_info) { + NOTREACHED(); + OnError(base::PLATFORM_FILE_ERROR_SECURITY); +} + +void FileWriterDelegate::OnCertificateRequested( + net::URLRequest* request, + net::SSLCertRequestInfo* cert_request_info) { + NOTREACHED(); + OnError(base::PLATFORM_FILE_ERROR_SECURITY); +} + +void FileWriterDelegate::OnSSLCertificateError(net::URLRequest* request, + const net::SSLInfo& ssl_info, + bool fatal) { + NOTREACHED(); + OnError(base::PLATFORM_FILE_ERROR_SECURITY); +} + +void FileWriterDelegate::OnResponseStarted(net::URLRequest* request) { + DCHECK_EQ(request_.get(), request); + if (!request->status().is_success() || request->GetResponseCode() != 200) { + OnError(base::PLATFORM_FILE_ERROR_FAILED); + return; + } + Read(); +} + +void FileWriterDelegate::OnReadCompleted(net::URLRequest* request, + int bytes_read) { + DCHECK_EQ(request_.get(), request); + if (!request->status().is_success()) { + OnError(base::PLATFORM_FILE_ERROR_FAILED); + return; + } + OnDataReceived(bytes_read); +} + +void FileWriterDelegate::Read() { + bytes_written_ = 0; + bytes_read_ = 0; + if (request_->Read(io_buffer_.get(), io_buffer_->size(), &bytes_read_)) { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&FileWriterDelegate::OnDataReceived, + weak_factory_.GetWeakPtr(), bytes_read_)); + } else if (!request_->status().is_io_pending()) { + OnError(base::PLATFORM_FILE_ERROR_FAILED); + } +} + +void FileWriterDelegate::OnDataReceived(int bytes_read) { + bytes_read_ = bytes_read; + if (!bytes_read_) { // We're done. + OnProgress(0, true); + } else { + // This could easily be optimized to rotate between a pool of buffers, so + // that we could read and write at the same time. It's not yet clear that + // it's necessary. + cursor_ = new net::DrainableIOBuffer(io_buffer_.get(), bytes_read_); + Write(); + } +} + +void FileWriterDelegate::Write() { + writing_started_ = true; + int64 bytes_to_write = bytes_read_ - bytes_written_; + int write_response = + file_stream_writer_->Write(cursor_.get(), + static_cast<int>(bytes_to_write), + base::Bind(&FileWriterDelegate::OnDataWritten, + weak_factory_.GetWeakPtr())); + if (write_response > 0) { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&FileWriterDelegate::OnDataWritten, + weak_factory_.GetWeakPtr(), write_response)); + } else if (net::ERR_IO_PENDING != write_response) { + OnError(NetErrorToPlatformFileError(write_response)); + } +} + +void FileWriterDelegate::OnDataWritten(int write_response) { + if (write_response > 0) { + OnProgress(write_response, false); + cursor_->DidConsume(write_response); + bytes_written_ += write_response; + if (bytes_written_ == bytes_read_) + Read(); + else + Write(); + } else { + OnError(NetErrorToPlatformFileError(write_response)); + } +} + +FileWriterDelegate::WriteProgressStatus +FileWriterDelegate::GetCompletionStatusOnError() const { + return writing_started_ ? ERROR_WRITE_STARTED : ERROR_WRITE_NOT_STARTED; +} + +void FileWriterDelegate::OnError(base::PlatformFileError error) { + if (request_) { + request_->set_delegate(NULL); + request_->Cancel(); + } + + if (writing_started_) + FlushForCompletion(error, 0, ERROR_WRITE_STARTED); + else + write_callback_.Run(error, 0, ERROR_WRITE_NOT_STARTED); +} + +void FileWriterDelegate::OnProgress(int bytes_written, bool done) { + DCHECK(bytes_written + bytes_written_backlog_ >= bytes_written_backlog_); + static const int kMinProgressDelayMS = 200; + base::Time currentTime = base::Time::Now(); + if (done || last_progress_event_time_.is_null() || + (currentTime - last_progress_event_time_).InMilliseconds() > + kMinProgressDelayMS) { + bytes_written += bytes_written_backlog_; + last_progress_event_time_ = currentTime; + bytes_written_backlog_ = 0; + + if (done) { + FlushForCompletion(base::PLATFORM_FILE_OK, bytes_written, + SUCCESS_COMPLETED); + } else { + write_callback_.Run(base::PLATFORM_FILE_OK, bytes_written, + SUCCESS_IO_PENDING); + } + return; + } + bytes_written_backlog_ += bytes_written; +} + +void FileWriterDelegate::OnWriteCancelled(int status) { + write_callback_.Run(base::PLATFORM_FILE_ERROR_ABORT, 0, + GetCompletionStatusOnError()); +} + +void FileWriterDelegate::FlushForCompletion( + base::PlatformFileError error, + int bytes_written, + WriteProgressStatus progress_status) { + int flush_error = file_stream_writer_->Flush( + base::Bind(&FileWriterDelegate::OnFlushed, weak_factory_.GetWeakPtr(), + error, bytes_written, progress_status)); + if (flush_error != net::ERR_IO_PENDING) + OnFlushed(error, bytes_written, progress_status, flush_error); +} + +void FileWriterDelegate::OnFlushed(base::PlatformFileError error, + int bytes_written, + WriteProgressStatus progress_status, + int flush_error) { + if (error == base::PLATFORM_FILE_OK && flush_error != net::OK) { + // If the Flush introduced an error, overwrite the status. + // Otherwise, keep the original error status. + error = NetErrorToPlatformFileError(flush_error); + progress_status = GetCompletionStatusOnError(); + } + write_callback_.Run(error, bytes_written, progress_status); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/file_writer_delegate.h b/chromium/webkit/browser/fileapi/file_writer_delegate.h new file mode 100644 index 00000000000..ae63e7f01de --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_writer_delegate.h @@ -0,0 +1,103 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_FILE_WRITER_DELEGATE_H_ +#define WEBKIT_BROWSER_FILEAPI_FILE_WRITER_DELEGATE_H_ + +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/platform_file.h" +#include "base/time/time.h" +#include "net/base/file_stream.h" +#include "net/base/io_buffer.h" +#include "net/url_request/url_request.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace fileapi { + +class FileStreamWriter; + +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileWriterDelegate + : public net::URLRequest::Delegate { + public: + enum WriteProgressStatus { + SUCCESS_IO_PENDING, + SUCCESS_COMPLETED, + ERROR_WRITE_STARTED, + ERROR_WRITE_NOT_STARTED, + }; + + typedef base::Callback<void(base::PlatformFileError result, + int64 bytes, + WriteProgressStatus write_status)> + DelegateWriteCallback; + + FileWriterDelegate(scoped_ptr<FileStreamWriter> file_writer); + virtual ~FileWriterDelegate(); + + void Start(scoped_ptr<net::URLRequest> request, + const DelegateWriteCallback& write_callback); + + // Cancels the current write operation. This will synchronously or + // asynchronously call the given write callback (which may result in + // deleting this). + void Cancel(); + + virtual void OnReceivedRedirect(net::URLRequest* request, + const GURL& new_url, + bool* defer_redirect) OVERRIDE; + virtual void OnAuthRequired(net::URLRequest* request, + net::AuthChallengeInfo* auth_info) OVERRIDE; + virtual void OnCertificateRequested( + net::URLRequest* request, + net::SSLCertRequestInfo* cert_request_info) OVERRIDE; + virtual void OnSSLCertificateError(net::URLRequest* request, + const net::SSLInfo& ssl_info, + bool fatal) OVERRIDE; + virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE; + virtual void OnReadCompleted(net::URLRequest* request, + int bytes_read) OVERRIDE; + + private: + void OnGetFileInfoAndStartRequest( + scoped_ptr<net::URLRequest> request, + base::PlatformFileError error, + const base::PlatformFileInfo& file_info); + void Read(); + void OnDataReceived(int bytes_read); + void Write(); + void OnDataWritten(int write_response); + void OnError(base::PlatformFileError error); + void OnProgress(int bytes_read, bool done); + void OnWriteCancelled(int status); + void FlushForCompletion(base::PlatformFileError error, + int bytes_written, + WriteProgressStatus progress_status); + void OnFlushed(base::PlatformFileError error, + int bytes_written, + WriteProgressStatus progress_status, + int flush_error); + + WriteProgressStatus GetCompletionStatusOnError() const; + + DelegateWriteCallback write_callback_; + scoped_ptr<FileStreamWriter> file_stream_writer_; + base::Time last_progress_event_time_; + bool writing_started_; + int bytes_written_backlog_; + int bytes_written_; + int bytes_read_; + scoped_refptr<net::IOBufferWithSize> io_buffer_; + scoped_refptr<net::DrainableIOBuffer> cursor_; + scoped_ptr<net::URLRequest> request_; + + base::WeakPtrFactory<FileWriterDelegate> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(FileWriterDelegate); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_FILE_WRITER_DELEGATE_H_ diff --git a/chromium/webkit/browser/fileapi/file_writer_delegate_unittest.cc b/chromium/webkit/browser/fileapi/file_writer_delegate_unittest.cc new file mode 100644 index 00000000000..340fcb0120e --- /dev/null +++ b/chromium/webkit/browser/fileapi/file_writer_delegate_unittest.cc @@ -0,0 +1,478 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "net/base/io_buffer.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_job.h" +#include "net/url_request/url_request_status.h" +#include "testing/platform_test.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_quota_util.h" +#include "webkit/browser/fileapi/file_writer_delegate.h" +#include "webkit/browser/fileapi/mock_file_system_context.h" +#include "webkit/browser/fileapi/sandbox_file_stream_writer.h" + +namespace fileapi { + +namespace { + +const GURL kOrigin("http://example.com"); +const FileSystemType kFileSystemType = kFileSystemTypeTest; + +const char kData[] = "The quick brown fox jumps over the lazy dog.\n"; +const int kDataSize = ARRAYSIZE_UNSAFE(kData) - 1; + +class Result { + public: + Result() + : status_(base::PLATFORM_FILE_OK), + bytes_written_(0), + write_status_(FileWriterDelegate::SUCCESS_IO_PENDING) {} + + base::PlatformFileError status() const { return status_; } + int64 bytes_written() const { return bytes_written_; } + FileWriterDelegate::WriteProgressStatus write_status() const { + return write_status_; + } + + void DidWrite(base::PlatformFileError status, int64 bytes, + FileWriterDelegate::WriteProgressStatus write_status) { + write_status_ = write_status; + if (status == base::PLATFORM_FILE_OK) { + bytes_written_ += bytes; + if (write_status_ != FileWriterDelegate::SUCCESS_IO_PENDING) + base::MessageLoop::current()->Quit(); + } else { + EXPECT_EQ(base::PLATFORM_FILE_OK, status_); + status_ = status; + base::MessageLoop::current()->Quit(); + } + } + + private: + // For post-operation status. + base::PlatformFileError status_; + int64 bytes_written_; + FileWriterDelegate::WriteProgressStatus write_status_; +}; + +} // namespace (anonymous) + +class FileWriterDelegateTest : public PlatformTest { + public: + FileWriterDelegateTest() + : loop_(base::MessageLoop::TYPE_IO) {} + + protected: + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + FileSystemFileUtil* file_util() { + return file_system_context_->GetFileUtil(kFileSystemType); + } + + int64 usage() { + return file_system_context_->GetQuotaUtil(kFileSystemType) + ->GetOriginUsageOnFileThread( + file_system_context_.get(), kOrigin, kFileSystemType); + } + + int64 GetFileSizeOnDisk(const char* test_file_path) { + // There might be in-flight flush/write. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&base::DoNothing)); + base::MessageLoop::current()->RunUntilIdle(); + + FileSystemURL url = GetFileSystemURL(test_file_path); + base::PlatformFileInfo file_info; + base::FilePath platform_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->GetFileInfo(NewOperationContext().get(), url, + &file_info, &platform_path)); + return file_info.size; + } + + FileSystemURL GetFileSystemURL(const char* file_name) const { + return file_system_context_->CreateCrackedFileSystemURL( + kOrigin, kFileSystemType, base::FilePath().FromUTF8Unsafe(file_name)); + } + + scoped_ptr<FileSystemOperationContext> NewOperationContext() { + FileSystemOperationContext* context = + new FileSystemOperationContext(file_system_context_.get()); + context->set_update_observers( + *file_system_context_->GetUpdateObservers(kFileSystemType)); + context->set_root_path(dir_.path()); + return make_scoped_ptr(context); + } + + FileWriterDelegate* CreateWriterDelegate( + const char* test_file_path, + int64 offset, + int64 allowed_growth) { + SandboxFileStreamWriter* writer = new SandboxFileStreamWriter( + file_system_context_.get(), + GetFileSystemURL(test_file_path), + offset, + *file_system_context_->GetUpdateObservers(kFileSystemType)); + writer->set_default_quota(allowed_growth); + return new FileWriterDelegate(scoped_ptr<FileStreamWriter>(writer)); + } + + FileWriterDelegate::DelegateWriteCallback GetWriteCallback(Result* result) { + return base::Bind(&Result::DidWrite, base::Unretained(result)); + } + + // Creates and sets up a FileWriterDelegate for writing the given |blob_url|, + // and creates a new FileWriterDelegate for the file. + void PrepareForWrite(const char* test_file_path, + const GURL& blob_url, + int64 offset, + int64 allowed_growth) { + file_writer_delegate_.reset( + CreateWriterDelegate(test_file_path, offset, allowed_growth)); + request_.reset(empty_context_.CreateRequest( + blob_url, file_writer_delegate_.get())); + } + + static net::URLRequest::ProtocolFactory Factory; + + // This should be alive until the very end of this instance. + base::MessageLoop loop_; + + scoped_refptr<FileSystemContext> file_system_context_; + + net::URLRequestContext empty_context_; + scoped_ptr<FileWriterDelegate> file_writer_delegate_; + scoped_ptr<net::URLRequest> request_; + + base::ScopedTempDir dir_; + + static const char* content_; +}; + +const char* FileWriterDelegateTest::content_ = NULL; + +namespace { + +static std::string g_content; + +class FileWriterDelegateTestJob : public net::URLRequestJob { + public: + FileWriterDelegateTestJob(net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const std::string& content) + : net::URLRequestJob(request, network_delegate), + content_(content), + remaining_bytes_(content.length()), + cursor_(0) { + } + + virtual void Start() OVERRIDE { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&FileWriterDelegateTestJob::NotifyHeadersComplete, this)); + } + + virtual bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int *bytes_read) OVERRIDE { + if (remaining_bytes_ < buf_size) + buf_size = static_cast<int>(remaining_bytes_); + + for (int i = 0; i < buf_size; ++i) + buf->data()[i] = content_[cursor_++]; + remaining_bytes_ -= buf_size; + + SetStatus(net::URLRequestStatus()); + *bytes_read = buf_size; + return true; + } + + virtual int GetResponseCode() const OVERRIDE { + return 200; + } + + protected: + virtual ~FileWriterDelegateTestJob() {} + + private: + std::string content_; + int remaining_bytes_; + int cursor_; +}; + +} // namespace (anonymous) + +// static +net::URLRequestJob* FileWriterDelegateTest::Factory( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const std::string& scheme) { + return new FileWriterDelegateTestJob( + request, network_delegate, FileWriterDelegateTest::content_); +} + +void FileWriterDelegateTest::SetUp() { + ASSERT_TRUE(dir_.CreateUniqueTempDir()); + + file_system_context_ = CreateFileSystemContextForTesting( + NULL, dir_.path()); + + bool created = false; + scoped_ptr<FileSystemOperationContext> context = NewOperationContext(); + context->set_allowed_bytes_growth(kint64max); + base::PlatformFileError error = file_util()->EnsureFileExists( + context.get(), + GetFileSystemURL("test"), + &created); + ASSERT_EQ(base::PLATFORM_FILE_OK, error); + ASSERT_TRUE(created); + net::URLRequest::Deprecated::RegisterProtocolFactory("blob", &Factory); +} + +void FileWriterDelegateTest::TearDown() { + net::URLRequest::Deprecated::RegisterProtocolFactory("blob", NULL); + file_system_context_ = NULL; + base::MessageLoop::current()->RunUntilIdle(); +} + +TEST_F(FileWriterDelegateTest, WriteSuccessWithoutQuotaLimit) { + const GURL kBlobURL("blob:nolimit"); + content_ = kData; + + PrepareForWrite("test", kBlobURL, 0, kint64max); + + Result result; + ASSERT_EQ(0, usage()); + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + file_writer_delegate_.reset(); + + ASSERT_EQ(kDataSize, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + EXPECT_EQ(kDataSize, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); +} + +TEST_F(FileWriterDelegateTest, WriteSuccessWithJustQuota) { + const GURL kBlobURL("blob:just"); + content_ = kData; + const int64 kAllowedGrowth = kDataSize; + PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth); + + Result result; + ASSERT_EQ(0, usage()); + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + file_writer_delegate_.reset(); + + ASSERT_EQ(kAllowedGrowth, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + + EXPECT_EQ(kAllowedGrowth, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); +} + +TEST_F(FileWriterDelegateTest, DISABLED_WriteFailureByQuota) { + const GURL kBlobURL("blob:failure"); + content_ = kData; + const int64 kAllowedGrowth = kDataSize - 1; + PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth); + + Result result; + ASSERT_EQ(0, usage()); + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status()); + file_writer_delegate_.reset(); + + ASSERT_EQ(kAllowedGrowth, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + + EXPECT_EQ(kAllowedGrowth, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, result.status()); + ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status()); +} + +TEST_F(FileWriterDelegateTest, WriteZeroBytesSuccessfullyWithZeroQuota) { + const GURL kBlobURL("blob:zero"); + content_ = ""; + int64 kAllowedGrowth = 0; + PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth); + + Result result; + ASSERT_EQ(0, usage()); + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + file_writer_delegate_.reset(); + + ASSERT_EQ(kAllowedGrowth, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + + EXPECT_EQ(kAllowedGrowth, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); +} + +TEST_F(FileWriterDelegateTest, WriteSuccessWithoutQuotaLimitConcurrent) { + scoped_ptr<FileWriterDelegate> file_writer_delegate2; + scoped_ptr<net::URLRequest> request2; + + bool created = false; + file_util()->EnsureFileExists(NewOperationContext().get(), + GetFileSystemURL("test2"), + &created); + ASSERT_TRUE(created); + + const GURL kBlobURL("blob:nolimitconcurrent"); + const GURL kBlobURL2("blob:nolimitconcurrent2"); + content_ = kData; + + PrepareForWrite("test", kBlobURL, 0, kint64max); + + // Credate another FileWriterDelegate for concurrent write. + file_writer_delegate2.reset(CreateWriterDelegate("test2", 0, kint64max)); + request2.reset(empty_context_.CreateRequest( + kBlobURL2, file_writer_delegate2.get())); + + Result result, result2; + ASSERT_EQ(0, usage()); + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + file_writer_delegate2->Start(request2.Pass(), GetWriteCallback(&result2)); + base::MessageLoop::current()->Run(); + if (result.write_status() == FileWriterDelegate::SUCCESS_IO_PENDING || + result2.write_status() == FileWriterDelegate::SUCCESS_IO_PENDING) + base::MessageLoop::current()->Run(); + + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result2.write_status()); + file_writer_delegate_.reset(); + file_writer_delegate2.reset(); + + ASSERT_EQ(kDataSize * 2, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test") + GetFileSizeOnDisk("test2"), usage()); + + EXPECT_EQ(kDataSize, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); + EXPECT_EQ(kDataSize, result2.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result2.status()); +} + +TEST_F(FileWriterDelegateTest, WritesWithQuotaAndOffset) { + const GURL kBlobURL("blob:failure-with-updated-quota"); + content_ = kData; + + // Writing kDataSize (=45) bytes data while allowed_growth is 100. + int64 offset = 0; + int64 allowed_growth = 100; + ASSERT_LT(kDataSize, allowed_growth); + PrepareForWrite("test", kBlobURL, offset, allowed_growth); + + { + Result result; + ASSERT_EQ(0, usage()); + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + file_writer_delegate_.reset(); + + ASSERT_EQ(kDataSize, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + EXPECT_EQ(kDataSize, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); + } + + // Trying to overwrite kDataSize bytes data while allowed_growth is 20. + offset = 0; + allowed_growth = 20; + PrepareForWrite("test", kBlobURL, offset, allowed_growth); + + { + Result result; + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + EXPECT_EQ(kDataSize, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + EXPECT_EQ(kDataSize, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + } + + // Trying to write kDataSize bytes data from offset 25 while + // allowed_growth is 55. + offset = 25; + allowed_growth = 55; + PrepareForWrite("test", kBlobURL, offset, allowed_growth); + + { + Result result; + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + file_writer_delegate_.reset(); + + EXPECT_EQ(offset + kDataSize, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + EXPECT_EQ(kDataSize, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); + } + + // Trying to overwrite 45 bytes data while allowed_growth is -20. + offset = 0; + allowed_growth = -20; + PrepareForWrite("test", kBlobURL, offset, allowed_growth); + int64 pre_write_usage = GetFileSizeOnDisk("test"); + + { + Result result; + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status()); + file_writer_delegate_.reset(); + + EXPECT_EQ(pre_write_usage, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + EXPECT_EQ(kDataSize, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_OK, result.status()); + } + + // Trying to overwrite 45 bytes data with offset pre_write_usage - 20, + // while allowed_growth is 10. + const int kOverlap = 20; + offset = pre_write_usage - kOverlap; + allowed_growth = 10; + PrepareForWrite("test", kBlobURL, offset, allowed_growth); + + { + Result result; + file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result)); + base::MessageLoop::current()->Run(); + ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status()); + file_writer_delegate_.reset(); + + EXPECT_EQ(pre_write_usage + allowed_growth, usage()); + EXPECT_EQ(GetFileSizeOnDisk("test"), usage()); + EXPECT_EQ(kOverlap + allowed_growth, result.bytes_written()); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, result.status()); + } +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/isolated_context.cc b/chromium/webkit/browser/fileapi/isolated_context.cc new file mode 100644 index 00000000000..d10438aa046 --- /dev/null +++ b/chromium/webkit/browser/fileapi/isolated_context.cc @@ -0,0 +1,469 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/isolated_context.h" + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "webkit/browser/fileapi/file_system_url.h" + +namespace fileapi { + +namespace { + +base::FilePath::StringType GetRegisterNameForPath(const base::FilePath& path) { + // If it's not a root path simply return a base name. + if (path.DirName() != path) + return path.BaseName().value(); + +#if defined(FILE_PATH_USES_DRIVE_LETTERS) + base::FilePath::StringType name; + for (size_t i = 0; + i < path.value().size() && !base::FilePath::IsSeparator(path.value()[i]); + ++i) { + if (path.value()[i] == L':') { + name.append(L"_drive"); + break; + } + name.append(1, path.value()[i]); + } + return name; +#else + return FILE_PATH_LITERAL("<root>"); +#endif +} + +bool IsSinglePathIsolatedFileSystem(FileSystemType type) { + switch (type) { + // As of writing dragged file system is the only filesystem + // which could have multiple top-level paths. + case kFileSystemTypeDragged: + return false; + + case kFileSystemTypeUnknown: + NOTREACHED(); + return true; + + default: + return true; + } + NOTREACHED(); + return true; +} + +static base::LazyInstance<IsolatedContext>::Leaky g_isolated_context = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +IsolatedContext::FileInfoSet::FileInfoSet() {} +IsolatedContext::FileInfoSet::~FileInfoSet() {} + +bool IsolatedContext::FileInfoSet::AddPath( + const base::FilePath& path, std::string* registered_name) { + // The given path should not contain any '..' and should be absolute. + if (path.ReferencesParent() || !path.IsAbsolute()) + return false; + base::FilePath::StringType name = GetRegisterNameForPath(path); + std::string utf8name = base::FilePath(name).AsUTF8Unsafe(); + base::FilePath normalized_path = path.NormalizePathSeparators(); + bool inserted = + fileset_.insert(MountPointInfo(utf8name, normalized_path)).second; + if (!inserted) { + int suffix = 1; + std::string basepart = base::FilePath(name).RemoveExtension().AsUTF8Unsafe(); + std::string ext = base::FilePath(base::FilePath(name).Extension()).AsUTF8Unsafe(); + while (!inserted) { + utf8name = base::StringPrintf("%s (%d)", basepart.c_str(), suffix++); + if (!ext.empty()) + utf8name.append(ext); + inserted = + fileset_.insert(MountPointInfo(utf8name, normalized_path)).second; + } + } + if (registered_name) + *registered_name = utf8name; + return true; +} + +bool IsolatedContext::FileInfoSet::AddPathWithName( + const base::FilePath& path, const std::string& name) { + // The given path should not contain any '..' and should be absolute. + if (path.ReferencesParent() || !path.IsAbsolute()) + return false; + return fileset_.insert( + MountPointInfo(name, path.NormalizePathSeparators())).second; +} + +//-------------------------------------------------------------------------- + +class IsolatedContext::Instance { + public: + enum PathType { + PLATFORM_PATH, + VIRTUAL_PATH + }; + + // For a single-path isolated file system, which could be registered by + // IsolatedContext::RegisterFileSystemForPath() or + // IsolatedContext::RegisterFileSystemForVirtualPath(). + // Most of isolated file system contexts should be of this type. + Instance(FileSystemType type, const MountPointInfo& file_info, + PathType path_type); + + // For a multi-paths isolated file system. As of writing only file system + // type which could have multi-paths is Dragged file system, and + // could be registered by IsolatedContext::RegisterDraggedFileSystem(). + Instance(FileSystemType type, const std::set<MountPointInfo>& files); + + ~Instance(); + + FileSystemType type() const { return type_; } + const MountPointInfo& file_info() const { return file_info_; } + const std::set<MountPointInfo>& files() const { return files_; } + int ref_counts() const { return ref_counts_; } + + void AddRef() { ++ref_counts_; } + void RemoveRef() { --ref_counts_; } + + bool ResolvePathForName(const std::string& name, base::FilePath* path) const; + + // Returns true if the instance is a single-path instance. + bool IsSinglePathInstance() const; + + private: + const FileSystemType type_; + + // For single-path instance. + const MountPointInfo file_info_; + const PathType path_type_; + + // For multiple-path instance (e.g. dragged file system). + const std::set<MountPointInfo> files_; + + // Reference counts. Note that an isolated filesystem is created with ref==0 + // and will get deleted when the ref count reaches <=0. + int ref_counts_; + + DISALLOW_COPY_AND_ASSIGN(Instance); +}; + +IsolatedContext::Instance::Instance(FileSystemType type, + const MountPointInfo& file_info, + Instance::PathType path_type) + : type_(type), + file_info_(file_info), + path_type_(path_type), + ref_counts_(0) { + DCHECK(IsSinglePathIsolatedFileSystem(type_)); +} + +IsolatedContext::Instance::Instance(FileSystemType type, + const std::set<MountPointInfo>& files) + : type_(type), + path_type_(PLATFORM_PATH), + files_(files), + ref_counts_(0) { + DCHECK(!IsSinglePathIsolatedFileSystem(type_)); +} + +IsolatedContext::Instance::~Instance() {} + +bool IsolatedContext::Instance::ResolvePathForName(const std::string& name, + base::FilePath* path) const { + if (IsSinglePathIsolatedFileSystem(type_)) { + switch (path_type_) { + case PLATFORM_PATH: + *path = file_info_.path; + break; + case VIRTUAL_PATH: + *path = base::FilePath(); + break; + default: + NOTREACHED(); + } + + return file_info_.name == name; + } + std::set<MountPointInfo>::const_iterator found = files_.find( + MountPointInfo(name, base::FilePath())); + if (found == files_.end()) + return false; + *path = found->path; + return true; +} + +bool IsolatedContext::Instance::IsSinglePathInstance() const { + return IsSinglePathIsolatedFileSystem(type_); +} + +//-------------------------------------------------------------------------- + +// static +IsolatedContext* IsolatedContext::GetInstance() { + return g_isolated_context.Pointer(); +} + +// static +bool IsolatedContext::IsIsolatedType(FileSystemType type) { + return type == kFileSystemTypeIsolated || type == kFileSystemTypeExternal; +} + +std::string IsolatedContext::RegisterDraggedFileSystem( + const FileInfoSet& files) { + base::AutoLock locker(lock_); + std::string filesystem_id = GetNewFileSystemId(); + instance_map_[filesystem_id] = new Instance( + kFileSystemTypeDragged, files.fileset()); + return filesystem_id; +} + +std::string IsolatedContext::RegisterFileSystemForPath( + FileSystemType type, + const base::FilePath& path_in, + std::string* register_name) { + base::FilePath path(path_in.NormalizePathSeparators()); + if (path.ReferencesParent() || !path.IsAbsolute()) + return std::string(); + std::string name; + if (register_name && !register_name->empty()) { + name = *register_name; + } else { + name = base::FilePath(GetRegisterNameForPath(path)).AsUTF8Unsafe(); + if (register_name) + register_name->assign(name); + } + + base::AutoLock locker(lock_); + std::string filesystem_id = GetNewFileSystemId(); + instance_map_[filesystem_id] = new Instance(type, MountPointInfo(name, path), + Instance::PLATFORM_PATH); + path_to_id_map_[path].insert(filesystem_id); + return filesystem_id; +} + +std::string IsolatedContext::RegisterFileSystemForVirtualPath( + FileSystemType type, + const std::string& register_name, + const base::FilePath& cracked_path_prefix) { + base::AutoLock locker(lock_); + base::FilePath path(cracked_path_prefix.NormalizePathSeparators()); + if (path.ReferencesParent()) + return std::string(); + std::string filesystem_id = GetNewFileSystemId(); + instance_map_[filesystem_id] = new Instance( + type, + MountPointInfo(register_name, cracked_path_prefix), + Instance::VIRTUAL_PATH); + path_to_id_map_[path].insert(filesystem_id); + return filesystem_id; +} + +bool IsolatedContext::HandlesFileSystemMountType(FileSystemType type) const { + return type == kFileSystemTypeIsolated; +} + +bool IsolatedContext::RevokeFileSystem(const std::string& filesystem_id) { + base::AutoLock locker(lock_); + return UnregisterFileSystem(filesystem_id); +} + +bool IsolatedContext::GetRegisteredPath( + const std::string& filesystem_id, base::FilePath* path) const { + DCHECK(path); + base::AutoLock locker(lock_); + IDToInstance::const_iterator found = instance_map_.find(filesystem_id); + if (found == instance_map_.end() || !found->second->IsSinglePathInstance()) + return false; + *path = found->second->file_info().path; + return true; +} + +bool IsolatedContext::CrackVirtualPath(const base::FilePath& virtual_path, + std::string* id_or_name, + FileSystemType* type, + base::FilePath* path) const { + DCHECK(id_or_name); + DCHECK(path); + + // This should not contain any '..' references. + if (virtual_path.ReferencesParent()) + return false; + + // The virtual_path should comprise <id_or_name> and <relative_path> parts. + std::vector<base::FilePath::StringType> components; + virtual_path.GetComponents(&components); + if (components.size() < 1) + return false; + std::vector<base::FilePath::StringType>::iterator component_iter = + components.begin(); + std::string fsid = base::FilePath(*component_iter++).MaybeAsASCII(); + if (fsid.empty()) + return false; + + base::FilePath cracked_path; + { + base::AutoLock locker(lock_); + IDToInstance::const_iterator found_instance = instance_map_.find(fsid); + if (found_instance == instance_map_.end()) + return false; + *id_or_name = fsid; + const Instance* instance = found_instance->second; + if (type) + *type = instance->type(); + + if (component_iter == components.end()) { + // The virtual root case. + path->clear(); + return true; + } + + // *component_iter should be a name of the registered path. + std::string name = base::FilePath(*component_iter++).AsUTF8Unsafe(); + if (!instance->ResolvePathForName(name, &cracked_path)) + return false; + } + + for (; component_iter != components.end(); ++component_iter) + cracked_path = cracked_path.Append(*component_iter); + *path = cracked_path; + return true; +} + +FileSystemURL IsolatedContext::CrackURL(const GURL& url) const { + FileSystemURL filesystem_url = FileSystemURL(url); + if (!filesystem_url.is_valid()) + return FileSystemURL(); + return CrackFileSystemURL(filesystem_url); +} + +FileSystemURL IsolatedContext::CreateCrackedFileSystemURL( + const GURL& origin, + FileSystemType type, + const base::FilePath& path) const { + return CrackFileSystemURL(FileSystemURL(origin, type, path)); +} + +void IsolatedContext::RevokeFileSystemByPath(const base::FilePath& path_in) { + base::AutoLock locker(lock_); + base::FilePath path(path_in.NormalizePathSeparators()); + PathToID::iterator ids_iter = path_to_id_map_.find(path); + if (ids_iter == path_to_id_map_.end()) + return; + std::set<std::string>& ids = ids_iter->second; + for (std::set<std::string>::iterator iter = ids.begin(); + iter != ids.end(); ++iter) { + IDToInstance::iterator found = instance_map_.find(*iter); + if (found != instance_map_.end()) { + delete found->second; + instance_map_.erase(found); + } + } + path_to_id_map_.erase(ids_iter); +} + +void IsolatedContext::AddReference(const std::string& filesystem_id) { + base::AutoLock locker(lock_); + DCHECK(instance_map_.find(filesystem_id) != instance_map_.end()); + instance_map_[filesystem_id]->AddRef(); +} + +void IsolatedContext::RemoveReference(const std::string& filesystem_id) { + base::AutoLock locker(lock_); + // This could get called for non-existent filesystem if it has been + // already deleted by RevokeFileSystemByPath. + IDToInstance::iterator found = instance_map_.find(filesystem_id); + if (found == instance_map_.end()) + return; + Instance* instance = found->second; + DCHECK_GT(instance->ref_counts(), 0); + instance->RemoveRef(); + if (instance->ref_counts() == 0) { + bool deleted = UnregisterFileSystem(filesystem_id); + DCHECK(deleted); + } +} + +bool IsolatedContext::GetDraggedFileInfo( + const std::string& filesystem_id, + std::vector<MountPointInfo>* files) const { + DCHECK(files); + base::AutoLock locker(lock_); + IDToInstance::const_iterator found = instance_map_.find(filesystem_id); + if (found == instance_map_.end() || + found->second->type() != kFileSystemTypeDragged) + return false; + files->assign(found->second->files().begin(), + found->second->files().end()); + return true; +} + +base::FilePath IsolatedContext::CreateVirtualRootPath( + const std::string& filesystem_id) const { + return base::FilePath().AppendASCII(filesystem_id); +} + +IsolatedContext::IsolatedContext() { +} + +IsolatedContext::~IsolatedContext() { + STLDeleteContainerPairSecondPointers(instance_map_.begin(), + instance_map_.end()); +} + +FileSystemURL IsolatedContext::CrackFileSystemURL( + const FileSystemURL& url) const { + if (!HandlesFileSystemMountType(url.type())) + return FileSystemURL(); + + std::string mount_name; + FileSystemType cracked_type; + base::FilePath cracked_path; + if (!CrackVirtualPath(url.path(), &mount_name, &cracked_type, &cracked_path)) + return FileSystemURL(); + + return FileSystemURL( + url.origin(), url.mount_type(), url.virtual_path(), + !url.filesystem_id().empty() ? url.filesystem_id() : mount_name, + cracked_type, cracked_path, mount_name); +} + +bool IsolatedContext::UnregisterFileSystem(const std::string& filesystem_id) { + lock_.AssertAcquired(); + IDToInstance::iterator found = instance_map_.find(filesystem_id); + if (found == instance_map_.end()) + return false; + Instance* instance = found->second; + if (instance->IsSinglePathInstance()) { + PathToID::iterator ids_iter = path_to_id_map_.find( + instance->file_info().path); + DCHECK(ids_iter != path_to_id_map_.end()); + ids_iter->second.erase(filesystem_id); + if (ids_iter->second.empty()) + path_to_id_map_.erase(ids_iter); + } + delete found->second; + instance_map_.erase(found); + return true; +} + +std::string IsolatedContext::GetNewFileSystemId() const { + // Returns an arbitrary random string which must be unique in the map. + lock_.AssertAcquired(); + uint32 random_data[4]; + std::string id; + do { + base::RandBytes(random_data, sizeof(random_data)); + id = base::HexEncode(random_data, sizeof(random_data)); + } while (instance_map_.find(id) != instance_map_.end()); + return id; +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/isolated_context.h b/chromium/webkit/browser/fileapi/isolated_context.h new file mode 100644 index 00000000000..6b3f85ad98e --- /dev/null +++ b/chromium/webkit/browser/fileapi/isolated_context.h @@ -0,0 +1,197 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_ISOLATED_CONTEXT_H_ +#define WEBKIT_BROWSER_FILEAPI_ISOLATED_CONTEXT_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/lazy_instance.h" +#include "base/memory/singleton.h" +#include "base/synchronization/lock.h" +#include "webkit/browser/fileapi/mount_points.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/fileapi/file_system_types.h" + +namespace fileapi { +class FileSystemURL; +} + +namespace fileapi { + +// Manages isolated filesystem mount points which have no well-known names +// and are identified by a string 'filesystem ID', which usually just looks +// like random value. +// This type of filesystem can be created on the fly and may go away when it has +// no references from renderers. +// Files in an isolated filesystem are registered with corresponding names and +// identified by a filesystem URL like: +// +// filesystem:<origin>/isolated/<filesystem_id>/<name>/relative/path +// +// Some methods of this class are virtual just for mocking. +// +class WEBKIT_STORAGE_BROWSER_EXPORT IsolatedContext : public MountPoints { + public: + class WEBKIT_STORAGE_BROWSER_EXPORT FileInfoSet { + public: + FileInfoSet(); + ~FileInfoSet(); + + // Add the given |path| to the set and populates |registered_name| with + // the registered name assigned for the path. |path| needs to be + // absolute and should not contain parent references. + // Return false if the |path| is not valid and could not be added. + bool AddPath(const base::FilePath& path, std::string* registered_name); + + // Add the given |path| with the |name|. + // Return false if the |name| is already registered in the set or + // is not valid and could not be added. + bool AddPathWithName(const base::FilePath& path, const std::string& name); + + const std::set<MountPointInfo>& fileset() const { return fileset_; } + + private: + std::set<MountPointInfo> fileset_; + }; + + // The instance is lazily created per browser process. + static IsolatedContext* GetInstance(); + + // Returns true if the given filesystem type is managed by IsolatedContext + // (i.e. if the given |type| is Isolated or External). + // TODO(kinuko): needs a better function name. + static bool IsIsolatedType(FileSystemType type); + + // Registers a new isolated filesystem with the given FileInfoSet |files| + // and returns the new filesystem_id. The files are registered with their + // register_name as their keys so that later we can resolve the full paths + // for the given name. We only expose the name and the ID for the + // newly created filesystem to the renderer for the sake of security. + // + // The renderer will be sending filesystem requests with a virtual path like + // '/<filesystem_id>/<registered_name>/<relative_path_from_the_dropped_path>' + // for which we could crack in the browser process by calling + // CrackIsolatedPath to get the full path. + // + // For example: if a dropped file has a path like '/a/b/foo' and we register + // the path with the name 'foo' in the newly created filesystem. + // Later if the context is asked to crack a virtual path like '/<fsid>/foo' + // it can properly return the original path '/a/b/foo' by looking up the + // internal mapping. Similarly if a dropped entry is a directory and its + // path is like '/a/b/dir' a virtual path like '/<fsid>/dir/foo' can be + // cracked into '/a/b/dir/foo'. + // + // Note that the path in |fileset| that contains '..' or is not an + // absolute path is skipped and is not registered. + std::string RegisterDraggedFileSystem(const FileInfoSet& files); + + // Registers a new isolated filesystem for a given |path| of filesystem + // |type| filesystem and returns a new filesystem ID. + // |path| must be an absolute path which has no parent references ('..'). + // If |register_name| is non-null and has non-empty string the path is + // registered as the given |register_name|, otherwise it is populated + // with the name internally assigned to the path. + std::string RegisterFileSystemForPath(FileSystemType type, + const base::FilePath& path, + std::string* register_name); + + // Registers a virtual filesystem. This is different from + // RegisterFileSystemForPath because register_name is required, and + // cracked_path_prefix is allowed to be non-absolute. + // |register_name| is required, since we cannot infer one from the path. + // |cracked_path_prefix| has no parent references, but can be relative. + std::string RegisterFileSystemForVirtualPath( + FileSystemType type, + const std::string& register_name, + const base::FilePath& cracked_path_prefix); + + // Revokes all filesystem(s) registered for the given path. + // This is assumed to be called when the registered path becomes + // globally invalid, e.g. when a device for the path is detached. + // + // Note that this revokes the filesystem no matter how many references it has. + // It is ok to call this for the path that has no associated filesystems. + // Note that this only works for the filesystems registered by + // |RegisterFileSystemForPath|. + void RevokeFileSystemByPath(const base::FilePath& path); + + // Adds a reference to a filesystem specified by the given filesystem_id. + void AddReference(const std::string& filesystem_id); + + // Removes a reference to a filesystem specified by the given filesystem_id. + // If the reference count reaches 0 the isolated context gets destroyed. + // It is OK to call this on the filesystem that has been already deleted + // (e.g. by RevokeFileSystemByPath). + void RemoveReference(const std::string& filesystem_id); + + // Returns a set of dragged MountPointInfos registered for the + // |filesystem_id|. + // The filesystem_id must be pointing to a dragged file system + // (i.e. must be the one registered by RegisterDraggedFileSystem). + // Returns false if the |filesystem_id| is not valid. + bool GetDraggedFileInfo(const std::string& filesystem_id, + std::vector<MountPointInfo>* files) const; + + // MountPoints overrides. + virtual bool HandlesFileSystemMountType(FileSystemType type) const OVERRIDE; + virtual bool RevokeFileSystem(const std::string& filesystem_id) OVERRIDE; + virtual bool GetRegisteredPath(const std::string& filesystem_id, + base::FilePath* path) const OVERRIDE; + virtual bool CrackVirtualPath(const base::FilePath& virtual_path, + std::string* filesystem_id, + FileSystemType* type, + base::FilePath* path) const OVERRIDE; + virtual FileSystemURL CrackURL(const GURL& url) const OVERRIDE; + virtual FileSystemURL CreateCrackedFileSystemURL( + const GURL& origin, + FileSystemType type, + const base::FilePath& path) const OVERRIDE; + + // Returns the virtual root path that looks like /<filesystem_id>. + base::FilePath CreateVirtualRootPath(const std::string& filesystem_id) const; + + private: + friend struct base::DefaultLazyInstanceTraits<IsolatedContext>; + + // Represents each file system instance (defined in the .cc). + class Instance; + + typedef std::map<std::string, Instance*> IDToInstance; + + // Reverse map from registered path to IDs. + typedef std::map<base::FilePath, std::set<std::string> > PathToID; + + // Obtain an instance of this class via GetInstance(). + IsolatedContext(); + virtual ~IsolatedContext(); + + // MountPoints overrides. + virtual FileSystemURL CrackFileSystemURL( + const FileSystemURL& url) const OVERRIDE; + + // Unregisters a file system of given |filesystem_id|. Must be called with + // lock_ held. Returns true if the file system is unregistered. + bool UnregisterFileSystem(const std::string& filesystem_id); + + // Returns a new filesystem_id. Called with lock. + std::string GetNewFileSystemId() const; + + // This lock needs to be obtained when accessing the instance_map_. + mutable base::Lock lock_; + + IDToInstance instance_map_; + PathToID path_to_id_map_; + + DISALLOW_COPY_AND_ASSIGN(IsolatedContext); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_ISOLATED_CONTEXT_H_ diff --git a/chromium/webkit/browser/fileapi/isolated_context_unittest.cc b/chromium/webkit/browser/fileapi/isolated_context_unittest.cc new file mode 100644 index 00000000000..36073ca5f81 --- /dev/null +++ b/chromium/webkit/browser/fileapi/isolated_context_unittest.cc @@ -0,0 +1,336 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/isolated_context.h" + +#define FPL(x) FILE_PATH_LITERAL(x) + +#if defined(FILE_PATH_USES_DRIVE_LETTERS) +#define DRIVE FPL("C:") +#else +#define DRIVE +#endif + +namespace fileapi { + +typedef IsolatedContext::MountPointInfo FileInfo; + +namespace { + +const base::FilePath kTestPaths[] = { + base::FilePath(DRIVE FPL("/a/b.txt")), + base::FilePath(DRIVE FPL("/c/d/e")), + base::FilePath(DRIVE FPL("/h/")), + base::FilePath(DRIVE FPL("/")), +#if defined(FILE_PATH_USES_WIN_SEPARATORS) + base::FilePath(DRIVE FPL("\\foo\\bar")), + base::FilePath(DRIVE FPL("\\")), +#endif + // For duplicated base name test. + base::FilePath(DRIVE FPL("/")), + base::FilePath(DRIVE FPL("/f/e")), + base::FilePath(DRIVE FPL("/f/b.txt")), +}; + +} // namespace + +class IsolatedContextTest : public testing::Test { + public: + IsolatedContextTest() { + for (size_t i = 0; i < arraysize(kTestPaths); ++i) + fileset_.insert(kTestPaths[i].NormalizePathSeparators()); + } + + virtual void SetUp() { + IsolatedContext::FileInfoSet files; + for (size_t i = 0; i < arraysize(kTestPaths); ++i) { + std::string name; + ASSERT_TRUE( + files.AddPath(kTestPaths[i].NormalizePathSeparators(), &name)); + names_.push_back(name); + } + id_ = IsolatedContext::GetInstance()->RegisterDraggedFileSystem(files); + IsolatedContext::GetInstance()->AddReference(id_); + ASSERT_FALSE(id_.empty()); + } + + virtual void TearDown() { + IsolatedContext::GetInstance()->RemoveReference(id_); + } + + IsolatedContext* isolated_context() const { + return IsolatedContext::GetInstance(); + } + + protected: + std::string id_; + std::multiset<base::FilePath> fileset_; + std::vector<std::string> names_; + + private: + DISALLOW_COPY_AND_ASSIGN(IsolatedContextTest); +}; + +TEST_F(IsolatedContextTest, RegisterAndRevokeTest) { + // See if the returned top-level entries match with what we registered. + std::vector<FileInfo> toplevels; + ASSERT_TRUE(isolated_context()->GetDraggedFileInfo(id_, &toplevels)); + ASSERT_EQ(fileset_.size(), toplevels.size()); + for (size_t i = 0; i < toplevels.size(); ++i) { + ASSERT_TRUE(fileset_.find(toplevels[i].path) != fileset_.end()); + } + + // See if the name of each registered kTestPaths (that is what we + // register in SetUp() by RegisterDraggedFileSystem) is properly cracked as + // a valid virtual path in the isolated filesystem. + for (size_t i = 0; i < arraysize(kTestPaths); ++i) { + base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath(id_) + .AppendASCII(names_[i]); + std::string cracked_id; + base::FilePath cracked_path; + FileSystemType cracked_type; + ASSERT_TRUE(isolated_context()->CrackVirtualPath( + virtual_path, &cracked_id, &cracked_type, &cracked_path)); + ASSERT_EQ(kTestPaths[i].NormalizePathSeparators().value(), + cracked_path.value()); + ASSERT_EQ(id_, cracked_id); + ASSERT_EQ(kFileSystemTypeDragged, cracked_type); + } + + // Make sure GetRegisteredPath returns false for id_ since it is + // registered for dragged files. + base::FilePath path; + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id_, &path)); + + // Deref the current one and registering a new one. + isolated_context()->RemoveReference(id_); + + std::string id2 = isolated_context()->RegisterFileSystemForPath( + kFileSystemTypeNativeLocal, base::FilePath(DRIVE FPL("/foo")), NULL); + + // Make sure the GetDraggedFileInfo returns false for both ones. + ASSERT_FALSE(isolated_context()->GetDraggedFileInfo(id2, &toplevels)); + ASSERT_FALSE(isolated_context()->GetDraggedFileInfo(id_, &toplevels)); + + // Make sure the GetRegisteredPath returns true only for the new one. + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id_, &path)); + ASSERT_TRUE(isolated_context()->GetRegisteredPath(id2, &path)); + + // Try registering three more file systems for the same path as id2. + std::string id3 = isolated_context()->RegisterFileSystemForPath( + kFileSystemTypeNativeLocal, path, NULL); + std::string id4 = isolated_context()->RegisterFileSystemForPath( + kFileSystemTypeNativeLocal, path, NULL); + std::string id5 = isolated_context()->RegisterFileSystemForPath( + kFileSystemTypeNativeLocal, path, NULL); + + // Remove file system for id4. + isolated_context()->AddReference(id4); + isolated_context()->RemoveReference(id4); + + // Only id4 should become invalid now. + ASSERT_TRUE(isolated_context()->GetRegisteredPath(id2, &path)); + ASSERT_TRUE(isolated_context()->GetRegisteredPath(id3, &path)); + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id4, &path)); + ASSERT_TRUE(isolated_context()->GetRegisteredPath(id5, &path)); + + // Revoke file system id5, after adding multiple references. + isolated_context()->AddReference(id5); + isolated_context()->AddReference(id5); + isolated_context()->AddReference(id5); + isolated_context()->RevokeFileSystem(id5); + + // No matter how many references we add id5 must be invalid now. + ASSERT_TRUE(isolated_context()->GetRegisteredPath(id2, &path)); + ASSERT_TRUE(isolated_context()->GetRegisteredPath(id3, &path)); + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id4, &path)); + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id5, &path)); + + // Revoke the file systems by path. + isolated_context()->RevokeFileSystemByPath(path); + + // Now all the file systems associated to the path must be invalid. + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id2, &path)); + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id3, &path)); + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id4, &path)); + ASSERT_FALSE(isolated_context()->GetRegisteredPath(id5, &path)); +} + +TEST_F(IsolatedContextTest, CrackWithRelativePaths) { + const struct { + base::FilePath::StringType path; + bool valid; + } relatives[] = { + { FPL("foo"), true }, + { FPL("foo/bar"), true }, + { FPL(".."), false }, + { FPL("foo/.."), false }, + { FPL("foo/../bar"), false }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) +# define SHOULD_FAIL_WITH_WIN_SEPARATORS false +#else +# define SHOULD_FAIL_WITH_WIN_SEPARATORS true +#endif + { FPL("foo\\..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS }, + { FPL("foo/..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS }, + }; + + for (size_t i = 0; i < arraysize(kTestPaths); ++i) { + for (size_t j = 0; j < ARRAYSIZE_UNSAFE(relatives); ++j) { + SCOPED_TRACE(testing::Message() << "Testing " + << kTestPaths[i].value() << " " << relatives[j].path); + base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath(id_) + .AppendASCII(names_[i]).Append(relatives[j].path); + std::string cracked_id; + base::FilePath cracked_path; + FileSystemType cracked_type; + if (!relatives[j].valid) { + ASSERT_FALSE(isolated_context()->CrackVirtualPath( + virtual_path, &cracked_id, &cracked_type, &cracked_path)); + continue; + } + ASSERT_TRUE(isolated_context()->CrackVirtualPath( + virtual_path, &cracked_id, &cracked_type, &cracked_path)); + ASSERT_EQ(kTestPaths[i].Append(relatives[j].path) + .NormalizePathSeparators().value(), + cracked_path.value()); + ASSERT_EQ(id_, cracked_id); + ASSERT_EQ(kFileSystemTypeDragged, cracked_type); + } + } +} + +TEST_F(IsolatedContextTest, CrackURLWithRelativePaths) { + const struct { + base::FilePath::StringType path; + bool valid; + } relatives[] = { + { FPL("foo"), true }, + { FPL("foo/bar"), true }, + { FPL(".."), false }, + { FPL("foo/.."), false }, + { FPL("foo/../bar"), false }, +#if defined(FILE_PATH_USES_WIN_SEPARATORS) +# define SHOULD_FAIL_WITH_WIN_SEPARATORS false +#else +# define SHOULD_FAIL_WITH_WIN_SEPARATORS true +#endif + { FPL("foo\\..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS }, + { FPL("foo/..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS }, + }; + + for (size_t i = 0; i < arraysize(kTestPaths); ++i) { + for (size_t j = 0; j < ARRAYSIZE_UNSAFE(relatives); ++j) { + SCOPED_TRACE(testing::Message() << "Testing " + << kTestPaths[i].value() << " " << relatives[j].path); + base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath(id_) + .AppendASCII(names_[i]).Append(relatives[j].path); + + FileSystemURL cracked = isolated_context()->CreateCrackedFileSystemURL( + GURL("http://chromium.org"), kFileSystemTypeIsolated, virtual_path); + + ASSERT_EQ(relatives[j].valid, cracked.is_valid()); + + if (!relatives[j].valid) + continue; + ASSERT_EQ(GURL("http://chromium.org"), cracked.origin()); + ASSERT_EQ(kTestPaths[i].Append(relatives[j].path) + .NormalizePathSeparators().value(), + cracked.path().value()); + ASSERT_EQ(virtual_path.NormalizePathSeparators(), cracked.virtual_path()); + ASSERT_EQ(id_, cracked.filesystem_id()); + ASSERT_EQ(kFileSystemTypeDragged, cracked.type()); + ASSERT_EQ(kFileSystemTypeIsolated, cracked.mount_type()); + } + } +} + +TEST_F(IsolatedContextTest, TestWithVirtualRoot) { + std::string cracked_id; + base::FilePath cracked_path; + + // Trying to crack virtual root "/" returns true but with empty cracked path + // as "/" of the isolated filesystem is a pure virtual directory + // that has no corresponding platform directory. + base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath(id_); + ASSERT_TRUE(isolated_context()->CrackVirtualPath( + virtual_path, &cracked_id, NULL, &cracked_path)); + ASSERT_EQ(FPL(""), cracked_path.value()); + ASSERT_EQ(id_, cracked_id); + + // Trying to crack "/foo" should fail (because "foo" is not the one + // included in the kTestPaths). + virtual_path = isolated_context()->CreateVirtualRootPath( + id_).AppendASCII("foo"); + ASSERT_FALSE(isolated_context()->CrackVirtualPath( + virtual_path, &cracked_id, NULL, &cracked_path)); +} + +TEST_F(IsolatedContextTest, CanHandleURL) { + const GURL test_origin("http://chromium.org"); + const base::FilePath test_path(FPL("/mount")); + + // Should handle isolated file system. + EXPECT_TRUE(isolated_context()->HandlesFileSystemMountType( + fileapi::kFileSystemTypeIsolated)); + + // Shouldn't handle the rest. + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + fileapi::kFileSystemTypeExternal)); + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + fileapi::kFileSystemTypeTemporary)); + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + fileapi::kFileSystemTypePersistent)); + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + fileapi::kFileSystemTypeTest)); + // Not even if it's isolated subtype. + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + fileapi::kFileSystemTypeNativeLocal)); + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + fileapi::kFileSystemTypeDragged)); + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + fileapi::kFileSystemTypeNativeMedia)); + EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType( + fileapi::kFileSystemTypeDeviceMedia)); +} + +TEST_F(IsolatedContextTest, VirtualFileSystemTests) { + // Should be able to register empty and non-absolute paths + std::string empty_fsid = isolated_context()->RegisterFileSystemForVirtualPath( + fileapi::kFileSystemTypeIsolated, "_", base::FilePath()); + std::string relative_fsid = + isolated_context()->RegisterFileSystemForVirtualPath( + fileapi::kFileSystemTypeIsolated, "_", + base::FilePath(FPL("relpath"))); + ASSERT_FALSE(empty_fsid.empty()); + ASSERT_FALSE(relative_fsid.empty()); + + // Make sure that filesystem root is not prepended to cracked virtual paths. + base::FilePath database_root = base::FilePath(DRIVE FPL("/database_path")); + std::string database_fsid = + isolated_context()->RegisterFileSystemForVirtualPath( + fileapi::kFileSystemTypeIsolated, "_", database_root); + + base::FilePath test_virtual_path = + base::FilePath().AppendASCII("virtualdir").AppendASCII("virtualfile.txt"); + + base::FilePath whole_virtual_path = + isolated_context()->CreateVirtualRootPath(database_fsid) + .AppendASCII("_").Append(test_virtual_path); + + std::string cracked_id; + base::FilePath cracked_path; + ASSERT_TRUE(isolated_context()->CrackVirtualPath( + whole_virtual_path, &cracked_id, NULL, &cracked_path)); + ASSERT_EQ(database_fsid, cracked_id); + ASSERT_EQ(test_virtual_path, cracked_path); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/isolated_file_system_backend.cc b/chromium/webkit/browser/fileapi/isolated_file_system_backend.cc new file mode 100644 index 00000000000..db59e35c857 --- /dev/null +++ b/chromium/webkit/browser/fileapi/isolated_file_system_backend.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/isolated_file_system_backend.h" + +#include <string> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/files/file_util_proxy.h" +#include "base/logging.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/platform_file.h" +#include "base/sequenced_task_runner.h" +#include "webkit/browser/blob/local_file_stream_reader.h" +#include "webkit/browser/fileapi/async_file_util_adapter.h" +#include "webkit/browser/fileapi/copy_or_move_file_validator.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_stream_reader.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_operation_impl.h" +#include "webkit/browser/fileapi/isolated_context.h" +#include "webkit/browser/fileapi/isolated_file_util.h" +#include "webkit/browser/fileapi/local_file_stream_writer.h" +#include "webkit/browser/fileapi/native_file_util.h" +#include "webkit/browser/fileapi/transient_file_util.h" +#include "webkit/common/fileapi/file_system_types.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +IsolatedFileSystemBackend::IsolatedFileSystemBackend() + : isolated_file_util_(new AsyncFileUtilAdapter(new IsolatedFileUtil())), + dragged_file_util_(new AsyncFileUtilAdapter(new DraggedFileUtil())), + transient_file_util_(new AsyncFileUtilAdapter(new TransientFileUtil())) { +} + +IsolatedFileSystemBackend::~IsolatedFileSystemBackend() { +} + +bool IsolatedFileSystemBackend::CanHandleType(FileSystemType type) const { + switch (type) { + case kFileSystemTypeIsolated: + case kFileSystemTypeDragged: + case kFileSystemTypeForTransientFile: + return true; +#if !defined(OS_CHROMEOS) + case kFileSystemTypeNativeLocal: + case kFileSystemTypeNativeForPlatformApp: + return true; +#endif + default: + return false; + } +} + +void IsolatedFileSystemBackend::Initialize(FileSystemContext* context) { +} + +void IsolatedFileSystemBackend::OpenFileSystem( + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) { + // We never allow opening a new isolated FileSystem via usual OpenFileSystem. + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(callback, + GetFileSystemRootURI(origin_url, type), + GetFileSystemName(origin_url, type), + base::PLATFORM_FILE_ERROR_SECURITY)); +} + +FileSystemFileUtil* IsolatedFileSystemBackend::GetFileUtil( + FileSystemType type) { + switch (type) { + case kFileSystemTypeNativeLocal: + return isolated_file_util_->sync_file_util(); + case kFileSystemTypeDragged: + return dragged_file_util_->sync_file_util(); + case kFileSystemTypeForTransientFile: + return transient_file_util_->sync_file_util(); + default: + NOTREACHED(); + } + return NULL; +} + +AsyncFileUtil* IsolatedFileSystemBackend::GetAsyncFileUtil( + FileSystemType type) { + switch (type) { + case kFileSystemTypeNativeLocal: + return isolated_file_util_.get(); + case kFileSystemTypeDragged: + return dragged_file_util_.get(); + case kFileSystemTypeForTransientFile: + return transient_file_util_.get(); + default: + NOTREACHED(); + } + return NULL; +} + +CopyOrMoveFileValidatorFactory* +IsolatedFileSystemBackend::GetCopyOrMoveFileValidatorFactory( + FileSystemType type, base::PlatformFileError* error_code) { + DCHECK(error_code); + *error_code = base::PLATFORM_FILE_OK; + return NULL; +} + +FileSystemOperation* IsolatedFileSystemBackend::CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::PlatformFileError* error_code) const { + return new FileSystemOperationImpl( + url, context, make_scoped_ptr(new FileSystemOperationContext(context))); +} + +scoped_ptr<webkit_blob::FileStreamReader> +IsolatedFileSystemBackend::CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const { + return scoped_ptr<webkit_blob::FileStreamReader>( + new webkit_blob::LocalFileStreamReader( + context->default_file_task_runner(), + url.path(), offset, expected_modification_time)); +} + +scoped_ptr<FileStreamWriter> IsolatedFileSystemBackend::CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const { + return scoped_ptr<FileStreamWriter>(new LocalFileStreamWriter( + context->default_file_task_runner(), url.path(), offset)); +} + +FileSystemQuotaUtil* IsolatedFileSystemBackend::GetQuotaUtil() { + // No quota support. + return NULL; +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/isolated_file_system_backend.h b/chromium/webkit/browser/fileapi/isolated_file_system_backend.h new file mode 100644 index 00000000000..cdc70969214 --- /dev/null +++ b/chromium/webkit/browser/fileapi/isolated_file_system_backend.h @@ -0,0 +1,56 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_ISOLATED_FILE_SYSTEM_BACKEND_H_ +#define WEBKIT_BROWSER_FILEAPI_ISOLATED_FILE_SYSTEM_BACKEND_H_ + +#include "base/memory/scoped_ptr.h" +#include "webkit/browser/fileapi/file_system_backend.h" + +namespace fileapi { + +class AsyncFileUtilAdapter; + +class IsolatedFileSystemBackend : public FileSystemBackend { + public: + IsolatedFileSystemBackend(); + virtual ~IsolatedFileSystemBackend(); + + // FileSystemBackend implementation. + virtual bool CanHandleType(FileSystemType type) const OVERRIDE; + virtual void Initialize(FileSystemContext* context) OVERRIDE; + virtual void OpenFileSystem( + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) OVERRIDE; + virtual FileSystemFileUtil* GetFileUtil(FileSystemType type) OVERRIDE; + virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) OVERRIDE; + virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory( + FileSystemType type, + base::PlatformFileError* error_code) OVERRIDE; + virtual FileSystemOperation* CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::PlatformFileError* error_code) const OVERRIDE; + virtual scoped_ptr<webkit_blob::FileStreamReader> CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const OVERRIDE; + virtual scoped_ptr<FileStreamWriter> CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const OVERRIDE; + virtual FileSystemQuotaUtil* GetQuotaUtil() OVERRIDE; + + private: + scoped_ptr<AsyncFileUtilAdapter> isolated_file_util_; + scoped_ptr<AsyncFileUtilAdapter> dragged_file_util_; + scoped_ptr<AsyncFileUtilAdapter> transient_file_util_; +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_ISOLATED_FILE_SYSTEM_BACKEND_H_ diff --git a/chromium/webkit/browser/fileapi/isolated_file_util.cc b/chromium/webkit/browser/fileapi/isolated_file_util.cc new file mode 100644 index 00000000000..bd2dffc009e --- /dev/null +++ b/chromium/webkit/browser/fileapi/isolated_file_util.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/isolated_file_util.h" + +#include <string> +#include <vector> + +#include "base/file_util.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/isolated_context.h" +#include "webkit/browser/fileapi/native_file_util.h" +#include "webkit/common/blob/shareable_file_reference.h" + +using base::PlatformFileError; +using base::PlatformFileInfo; + +namespace fileapi { + +typedef IsolatedContext::MountPointInfo FileInfo; + +namespace { + +// Simply enumerate each path from a given fileinfo set. +// Used to enumerate top-level paths of an isolated filesystem. +class SetFileEnumerator : public FileSystemFileUtil::AbstractFileEnumerator { + public: + explicit SetFileEnumerator(const std::vector<FileInfo>& files) + : files_(files) { + file_iter_ = files_.begin(); + } + virtual ~SetFileEnumerator() {} + + // AbstractFileEnumerator overrides. + virtual base::FilePath Next() OVERRIDE { + if (file_iter_ == files_.end()) + return base::FilePath(); + base::FilePath platform_file = (file_iter_++)->path; + NativeFileUtil::GetFileInfo(platform_file, &file_info_); + return platform_file; + } + virtual int64 Size() OVERRIDE { return file_info_.size; } + virtual bool IsDirectory() OVERRIDE { return file_info_.is_directory; } + virtual base::Time LastModifiedTime() OVERRIDE { + return file_info_.last_modified; + } + + private: + std::vector<FileInfo> files_; + std::vector<FileInfo>::const_iterator file_iter_; + base::PlatformFileInfo file_info_; +}; + +} // namespace + +//------------------------------------------------------------------------- + +IsolatedFileUtil::IsolatedFileUtil() {} + +PlatformFileError IsolatedFileUtil::GetLocalFilePath( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::FilePath* local_file_path) { + DCHECK(local_file_path); + DCHECK(url.is_valid()); + if (url.path().empty()) { + // Root direcory case, which should not be accessed. + return base::PLATFORM_FILE_ERROR_ACCESS_DENIED; + } + *local_file_path = url.path(); + return base::PLATFORM_FILE_OK; +} + +//------------------------------------------------------------------------- + +DraggedFileUtil::DraggedFileUtil() {} + +PlatformFileError DraggedFileUtil::GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + PlatformFileInfo* file_info, + base::FilePath* platform_path) { + DCHECK(file_info); + std::string filesystem_id; + DCHECK(url.is_valid()); + if (url.path().empty()) { + // The root directory case. + // For now we leave three time fields (modified/accessed/creation time) + // NULL as it is not really clear what to be set for this virtual directory. + // TODO(kinuko): Maybe we want to set the time when this filesystem is + // created (i.e. when the files/directories are dropped). + file_info->is_directory = true; + file_info->is_symbolic_link = false; + file_info->size = 0; + return base::PLATFORM_FILE_OK; + } + base::PlatformFileError error = + NativeFileUtil::GetFileInfo(url.path(), file_info); + if (file_util::IsLink(url.path()) && !base::FilePath().IsParent(url.path())) { + // Don't follow symlinks unless it's the one that are selected by the user. + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + } + if (error == base::PLATFORM_FILE_OK) + *platform_path = url.path(); + return error; +} + +scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> + DraggedFileUtil::CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root) { + DCHECK(root.is_valid()); + if (!root.path().empty()) + return LocalFileUtil::CreateFileEnumerator(context, root); + + // Root path case. + std::vector<FileInfo> toplevels; + IsolatedContext::GetInstance()->GetDraggedFileInfo( + root.filesystem_id(), &toplevels); + return scoped_ptr<AbstractFileEnumerator>(new SetFileEnumerator(toplevels)); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/isolated_file_util.h b/chromium/webkit/browser/fileapi/isolated_file_util.h new file mode 100644 index 00000000000..e6f595572c4 --- /dev/null +++ b/chromium/webkit/browser/fileapi/isolated_file_util.h @@ -0,0 +1,54 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_ISOLATED_FILE_UTIL_H_ +#define WEBKIT_BROWSER_FILEAPI_ISOLATED_FILE_UTIL_H_ + +#include "base/memory/scoped_ptr.h" +#include "webkit/browser/fileapi/local_file_util.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace fileapi { + +class FileSystemOperationContext; + +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE IsolatedFileUtil + : public LocalFileUtil { + public: + IsolatedFileUtil(); + virtual ~IsolatedFileUtil() {} + + // LocalFileUtil overrides. + virtual base::PlatformFileError GetLocalFilePath( + FileSystemOperationContext* context, + const FileSystemURL& file_system_url, + base::FilePath* local_file_path) OVERRIDE; +}; + +// Dragged file system is a specialized IsolatedFileUtil where read access to +// the virtual root directory (i.e. empty cracked path case) is allowed +// and single isolated context may be associated with multiple file paths. +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE DraggedFileUtil + : public IsolatedFileUtil { + public: + DraggedFileUtil(); + virtual ~DraggedFileUtil() {} + + // FileSystemFileUtil overrides. + virtual base::PlatformFileError GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::PlatformFileInfo* file_info, + base::FilePath* platform_path) OVERRIDE; + virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(DraggedFileUtil); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_ISOLATED_FILE_UTIL_H_ diff --git a/chromium/webkit/browser/fileapi/isolated_file_util_unittest.cc b/chromium/webkit/browser/fileapi/isolated_file_util_unittest.cc new file mode 100644 index 00000000000..fee8ed1497d --- /dev/null +++ b/chromium/webkit/browser/fileapi/isolated_file_util_unittest.cc @@ -0,0 +1,547 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <map> +#include <queue> +#include <set> +#include <string> +#include <vector> + +#include "base/file_util.h" +#include "base/files/file_enumerator.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/isolated_context.h" +#include "webkit/browser/fileapi/isolated_file_util.h" +#include "webkit/browser/fileapi/local_file_util.h" +#include "webkit/browser/fileapi/mock_file_system_context.h" +#include "webkit/browser/fileapi/native_file_util.h" +#include "webkit/browser/fileapi/test_file_set.h" + +namespace fileapi { + +namespace { + +typedef AsyncFileTestHelper::FileEntryList FileEntryList; + +// Used in IsolatedFileUtilTest::SimulateDropFiles(). +// Random root paths in which we create each file/directory of the +// RegularTestCases (so that we can simulate a drop with files/directories +// from multiple directories). +static const base::FilePath::CharType* kRootPaths[] = { + FILE_PATH_LITERAL("a"), + FILE_PATH_LITERAL("b/c"), + FILE_PATH_LITERAL("etc"), +}; + +base::FilePath GetTopLevelPath(const base::FilePath& path) { + std::vector<base::FilePath::StringType> components; + path.GetComponents(&components); + return base::FilePath(components[0]); +} + +bool IsDirectoryEmpty(FileSystemContext* context, const FileSystemURL& url) { + FileEntryList entries; + EXPECT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory(context, url, &entries)); + return entries.empty(); +} + +FileSystemURL GetEntryURL(FileSystemContext* file_system_context, + const FileSystemURL& dir, + const base::FilePath::StringType& name) { + return file_system_context->CreateCrackedFileSystemURL( + dir.origin(), + dir.mount_type(), + dir.virtual_path().Append(name)); +} + +base::FilePath GetRelativeVirtualPath(const FileSystemURL& root, + const FileSystemURL& url) { + if (root.virtual_path().empty()) + return url.virtual_path(); + base::FilePath relative; + const bool success = root.virtual_path().AppendRelativePath( + url.virtual_path(), &relative); + DCHECK(success); + return relative; +} + +FileSystemURL GetOtherURL(FileSystemContext* file_system_context, + const FileSystemURL& root, + const FileSystemURL& other_root, + const FileSystemURL& url) { + return file_system_context->CreateCrackedFileSystemURL( + other_root.origin(), + other_root.mount_type(), + other_root.virtual_path().Append(GetRelativeVirtualPath(root, url))); +} + +} // namespace + +// TODO(kinuko): we should have separate tests for DraggedFileUtil and +// IsolatedFileUtil. +class IsolatedFileUtilTest : public testing::Test { + public: + IsolatedFileUtilTest() {} + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + ASSERT_TRUE(partition_dir_.CreateUniqueTempDir()); + file_util_.reset(new DraggedFileUtil()); + + // Register the files/directories of RegularTestCases (with random + // root paths) as dropped files. + SimulateDropFiles(); + + file_system_context_ = CreateFileSystemContextForTesting( + NULL /* quota_manager */, + partition_dir_.path()); + + isolated_context()->AddReference(filesystem_id_); + } + + virtual void TearDown() { + isolated_context()->RemoveReference(filesystem_id_); + } + + protected: + IsolatedContext* isolated_context() const { + return IsolatedContext::GetInstance(); + } + const base::FilePath& root_path() const { + return data_dir_.path(); + } + FileSystemContext* file_system_context() const { + return file_system_context_.get(); + } + FileSystemFileUtil* file_util() const { return file_util_.get(); } + std::string filesystem_id() const { return filesystem_id_; } + + base::FilePath GetTestCasePlatformPath( + const base::FilePath::StringType& path) { + return toplevel_root_map_[GetTopLevelPath(base::FilePath(path))] + .Append(path).NormalizePathSeparators(); + } + + base::FilePath GetTestCaseLocalPath(const base::FilePath& path) { + base::FilePath relative; + if (data_dir_.path().AppendRelativePath(path, &relative)) + return relative; + return path; + } + + FileSystemURL GetFileSystemURL(const base::FilePath& path) const { + base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath( + filesystem_id()).Append(path); + return file_system_context_->CreateCrackedFileSystemURL( + GURL("http://example.com"), + kFileSystemTypeIsolated, + virtual_path); + } + + FileSystemURL GetOtherFileSystemURL(const base::FilePath& path) const { + return file_system_context()->CreateCrackedFileSystemURL( + GURL("http://example.com"), + kFileSystemTypeTemporary, + base::FilePath().AppendASCII("dest").Append(path)); + } + + void VerifyFilesHaveSameContent(const FileSystemURL& url1, + const FileSystemURL& url2) { + // Get the file info and the platform path for url1. + base::PlatformFileInfo info1; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::GetMetadata( + file_system_context(), url1, &info1)); + base::FilePath platform_path1; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::GetPlatformPath( + file_system_context(), url1, &platform_path1)); + + // Get the file info and the platform path for url2. + base::PlatformFileInfo info2; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::GetMetadata( + file_system_context(), url2, &info2)); + base::FilePath platform_path2; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::GetPlatformPath( + file_system_context(), url2, &platform_path2)); + + // See if file info matches with the other one. + EXPECT_EQ(info1.is_directory, info2.is_directory); + EXPECT_EQ(info1.size, info2.size); + EXPECT_EQ(info1.is_symbolic_link, info2.is_symbolic_link); + EXPECT_NE(platform_path1, platform_path2); + + std::string content1, content2; + EXPECT_TRUE(file_util::ReadFileToString(platform_path1, &content1)); + EXPECT_TRUE(file_util::ReadFileToString(platform_path2, &content2)); + EXPECT_EQ(content1, content2); + } + + void VerifyDirectoriesHaveSameContent(const FileSystemURL& root1, + const FileSystemURL& root2) { + base::FilePath root_path1 = root1.path(); + base::FilePath root_path2 = root2.path(); + + FileEntryList entries; + std::queue<FileSystemURL> directories; + + directories.push(root1); + std::set<base::FilePath> file_set1; + while (!directories.empty()) { + FileSystemURL dir = directories.front(); + directories.pop(); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), dir, &entries)); + for (size_t i = 0; i < entries.size(); ++i) { + FileSystemURL url = GetEntryURL(file_system_context(), + dir, entries[i].name); + if (entries[i].is_directory) { + directories.push(url); + continue; + } + file_set1.insert(GetRelativeVirtualPath(root1, url)); + } + } + + directories.push(root2); + while (!directories.empty()) { + FileSystemURL dir = directories.front(); + directories.pop(); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), dir, &entries)); + for (size_t i = 0; i < entries.size(); ++i) { + FileSystemURL url2 = GetEntryURL(file_system_context(), + dir, entries[i].name); + FileSystemURL url1 = GetOtherURL(file_system_context(), + root2, root1, url2); + if (entries[i].is_directory) { + directories.push(url2); + EXPECT_EQ(IsDirectoryEmpty(file_system_context(), url1), + IsDirectoryEmpty(file_system_context(), url2)); + continue; + } + base::FilePath relative = GetRelativeVirtualPath(root2, url2); + EXPECT_TRUE(file_set1.find(relative) != file_set1.end()); + VerifyFilesHaveSameContent(url1, url2); + } + } + } + + scoped_ptr<FileSystemOperationContext> GetOperationContext() { + return make_scoped_ptr( + new FileSystemOperationContext(file_system_context())).Pass(); + } + + + private: + void SimulateDropFiles() { + size_t root_path_index = 0; + + IsolatedContext::FileInfoSet toplevels; + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + base::FilePath path(test_case.path); + base::FilePath toplevel = GetTopLevelPath(path); + + // We create the test case files under one of the kRootPaths + // to simulate a drop with multiple directories. + if (toplevel_root_map_.find(toplevel) == toplevel_root_map_.end()) { + base::FilePath root = root_path().Append( + kRootPaths[(root_path_index++) % arraysize(kRootPaths)]); + toplevel_root_map_[toplevel] = root; + toplevels.AddPath(root.Append(path), NULL); + } + + test::SetUpOneTestCase(toplevel_root_map_[toplevel], test_case); + } + + // Register the toplevel entries. + filesystem_id_ = isolated_context()->RegisterDraggedFileSystem(toplevels); + } + + base::ScopedTempDir data_dir_; + base::ScopedTempDir partition_dir_; + base::MessageLoop message_loop_; + std::string filesystem_id_; + scoped_refptr<FileSystemContext> file_system_context_; + std::map<base::FilePath, base::FilePath> toplevel_root_map_; + scoped_ptr<IsolatedFileUtil> file_util_; + DISALLOW_COPY_AND_ASSIGN(IsolatedFileUtilTest); +}; + +TEST_F(IsolatedFileUtilTest, BasicTest) { + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + SCOPED_TRACE(testing::Message() << "Testing RegularTestCases " << i); + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + + FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); + + // See if we can query the file info via the isolated FileUtil. + // (This should succeed since we have registered all the top-level + // entries of the test cases in SetUp()) + base::PlatformFileInfo info; + base::FilePath platform_path; + FileSystemOperationContext context(file_system_context()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->GetFileInfo(&context, url, &info, &platform_path)); + + // See if the obtained file info is correct. + if (!test_case.is_directory) + ASSERT_EQ(test_case.data_file_size, info.size); + ASSERT_EQ(test_case.is_directory, info.is_directory); + ASSERT_EQ(GetTestCasePlatformPath(test_case.path), + platform_path.NormalizePathSeparators()); + } +} + +TEST_F(IsolatedFileUtilTest, UnregisteredPathsTest) { + static const fileapi::test::TestCaseRecord kUnregisteredCases[] = { + {true, FILE_PATH_LITERAL("nonexistent"), 0}, + {true, FILE_PATH_LITERAL("nonexistent/dir foo"), 0}, + {false, FILE_PATH_LITERAL("nonexistent/false"), 0}, + {false, FILE_PATH_LITERAL("foo"), 30}, + {false, FILE_PATH_LITERAL("bar"), 20}, + }; + + for (size_t i = 0; i < arraysize(kUnregisteredCases); ++i) { + SCOPED_TRACE(testing::Message() << "Creating kUnregisteredCases " << i); + const test::TestCaseRecord& test_case = kUnregisteredCases[i]; + + // Prepare the test file/directory. + SetUpOneTestCase(root_path(), test_case); + + // Make sure regular GetFileInfo succeeds. + base::PlatformFileInfo info; + ASSERT_TRUE(file_util::GetFileInfo( + root_path().Append(test_case.path), &info)); + if (!test_case.is_directory) + ASSERT_EQ(test_case.data_file_size, info.size); + ASSERT_EQ(test_case.is_directory, info.is_directory); + } + + for (size_t i = 0; i < arraysize(kUnregisteredCases); ++i) { + SCOPED_TRACE(testing::Message() << "Creating kUnregisteredCases " << i); + const test::TestCaseRecord& test_case = kUnregisteredCases[i]; + FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); + + // We should not be able to get the valid URL for unregistered files. + ASSERT_FALSE(url.is_valid()); + } +} + +TEST_F(IsolatedFileUtilTest, ReadDirectoryTest) { + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + if (!test_case.is_directory) + continue; + + SCOPED_TRACE(testing::Message() << "Testing RegularTestCases " << i + << ": " << test_case.path); + + // Read entries in the directory to construct the expected results map. + typedef std::map<base::FilePath::StringType, DirectoryEntry> EntryMap; + EntryMap expected_entry_map; + + base::FilePath dir_path = GetTestCasePlatformPath(test_case.path); + base::FileEnumerator file_enum( + dir_path, false /* not recursive */, + base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES); + base::FilePath current; + while (!(current = file_enum.Next()).empty()) { + base::FileEnumerator::FileInfo file_info = file_enum.GetInfo(); + DirectoryEntry entry; + entry.is_directory = file_info.IsDirectory(); + entry.name = current.BaseName().value(); + entry.size = file_info.GetSize(); + entry.last_modified_time = file_info.GetLastModifiedTime(); + expected_entry_map[entry.name] = entry; + +#if defined(OS_POSIX) + // Creates a symlink for each file/directory. + // They should be ignored by ReadDirectory, so we don't add them + // to expected_entry_map. + file_util::CreateSymbolicLink( + current, + dir_path.Append(current.BaseName().AddExtension( + FILE_PATH_LITERAL("link")))); +#endif + } + + // Perform ReadDirectory in the isolated filesystem. + FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); + FileEntryList entries; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), url, &entries)); + + EXPECT_EQ(expected_entry_map.size(), entries.size()); + for (size_t i = 0; i < entries.size(); ++i) { + const DirectoryEntry& entry = entries[i]; + EntryMap::iterator found = expected_entry_map.find(entry.name); + EXPECT_TRUE(found != expected_entry_map.end()); + EXPECT_EQ(found->second.name, entry.name); + EXPECT_EQ(found->second.is_directory, entry.is_directory); + EXPECT_EQ(found->second.size, entry.size); + EXPECT_EQ(found->second.last_modified_time.ToDoubleT(), + entry.last_modified_time.ToDoubleT()); + } + } +} + +TEST_F(IsolatedFileUtilTest, GetLocalFilePathTest) { + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); + + FileSystemOperationContext context(file_system_context()); + + base::FilePath local_file_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->GetLocalFilePath(&context, url, &local_file_path)); + EXPECT_EQ(GetTestCasePlatformPath(test_case.path).value(), + local_file_path.value()); + } +} + +TEST_F(IsolatedFileUtilTest, CopyOutFileTest) { + FileSystemURL src_root = GetFileSystemURL(base::FilePath()); + FileSystemURL dest_root = GetOtherFileSystemURL(base::FilePath()); + + FileEntryList entries; + std::queue<FileSystemURL> directories; + directories.push(src_root); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::CreateDirectory(file_system_context(), + dest_root)); + + while (!directories.empty()) { + FileSystemURL dir = directories.front(); + directories.pop(); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory(file_system_context(), + dir, &entries)); + for (size_t i = 0; i < entries.size(); ++i) { + FileSystemURL src_url = GetEntryURL(file_system_context(), + dir, entries[i].name); + FileSystemURL dest_url = GetOtherURL(file_system_context(), + src_root, dest_root, src_url); + + if (entries[i].is_directory) { + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::CreateDirectory(file_system_context(), + dest_url)); + directories.push(src_url); + continue; + } + SCOPED_TRACE(testing::Message() << "Testing file copy " + << src_url.path().value()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Copy(file_system_context(), + src_url, dest_url)); + VerifyFilesHaveSameContent(src_url, dest_url); + } + } +} + +TEST_F(IsolatedFileUtilTest, CopyOutDirectoryTest) { + FileSystemURL src_root = GetFileSystemURL(base::FilePath()); + FileSystemURL dest_root = GetOtherFileSystemURL(base::FilePath()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::CreateDirectory(file_system_context(), + dest_root)); + + FileEntryList entries; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory(file_system_context(), + src_root, &entries)); + for (size_t i = 0; i < entries.size(); ++i) { + if (!entries[i].is_directory) + continue; + FileSystemURL src_url = GetEntryURL(file_system_context(), + src_root, entries[i].name); + FileSystemURL dest_url = GetOtherURL(file_system_context(), + src_root, dest_root, src_url); + SCOPED_TRACE(testing::Message() << "Testing file copy " + << src_url.path().value()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Copy(file_system_context(), + src_url, dest_url)); + VerifyDirectoriesHaveSameContent(src_url, dest_url); + } +} + +TEST_F(IsolatedFileUtilTest, TouchTest) { + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + if (test_case.is_directory) + continue; + SCOPED_TRACE(testing::Message() << test_case.path); + FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); + + base::Time last_access_time = base::Time::FromTimeT(1000); + base::Time last_modified_time = base::Time::FromTimeT(2000); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->Touch(GetOperationContext().get(), url, + last_access_time, + last_modified_time)); + + // Verification. + base::PlatformFileInfo info; + base::FilePath platform_path; + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->GetFileInfo(GetOperationContext().get(), url, + &info, &platform_path)); + EXPECT_EQ(last_access_time.ToTimeT(), info.last_accessed.ToTimeT()); + EXPECT_EQ(last_modified_time.ToTimeT(), info.last_modified.ToTimeT()); + } +} + +TEST_F(IsolatedFileUtilTest, TruncateTest) { + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + if (test_case.is_directory) + continue; + + SCOPED_TRACE(testing::Message() << test_case.path); + FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path)); + + // Truncate to 0. + base::PlatformFileInfo info; + base::FilePath platform_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->Truncate(GetOperationContext().get(), url, 0)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->GetFileInfo(GetOperationContext().get(), url, + &info, &platform_path)); + EXPECT_EQ(0, info.size); + + // Truncate (extend) to 999. + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->Truncate(GetOperationContext().get(), url, 999)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->GetFileInfo(GetOperationContext().get(), url, + &info, &platform_path)); + EXPECT_EQ(999, info.size); + } +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/local_file_stream_writer.cc b/chromium/webkit/browser/fileapi/local_file_stream_writer.cc new file mode 100644 index 00000000000..3f779c969ed --- /dev/null +++ b/chromium/webkit/browser/fileapi/local_file_stream_writer.cc @@ -0,0 +1,233 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/local_file_stream_writer.h" + +#include "base/callback.h" +#include "base/message_loop/message_loop.h" +#include "net/base/file_stream.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" + +namespace fileapi { + +namespace { + +const int kOpenFlagsForWrite = base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_WRITE | + base::PLATFORM_FILE_ASYNC; + +} // namespace + +LocalFileStreamWriter::LocalFileStreamWriter(base::TaskRunner* task_runner, + const base::FilePath& file_path, + int64 initial_offset) + : file_path_(file_path), + initial_offset_(initial_offset), + has_pending_operation_(false), + weak_factory_(this), + task_runner_(task_runner) {} + +LocalFileStreamWriter::~LocalFileStreamWriter() { + // Invalidate weak pointers so that we won't receive any callbacks from + // in-flight stream operations, which might be triggered during the file close + // in the FileStream destructor. + weak_factory_.InvalidateWeakPtrs(); + + // FileStream's destructor closes the file safely, since we opened the file + // by its Open() method. +} + +int LocalFileStreamWriter::Write(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + DCHECK(!has_pending_operation_); + DCHECK(cancel_callback_.is_null()); + + has_pending_operation_ = true; + if (stream_impl_) { + int result = InitiateWrite(buf, buf_len, callback); + if (result != net::ERR_IO_PENDING) + has_pending_operation_ = false; + return result; + } + return InitiateOpen(callback, + base::Bind(&LocalFileStreamWriter::ReadyToWrite, + weak_factory_.GetWeakPtr(), + make_scoped_refptr(buf), buf_len, callback)); +} + +int LocalFileStreamWriter::Cancel(const net::CompletionCallback& callback) { + if (!has_pending_operation_) + return net::ERR_UNEXPECTED; + + DCHECK(!callback.is_null()); + cancel_callback_ = callback; + return net::ERR_IO_PENDING; +} + +int LocalFileStreamWriter::Flush(const net::CompletionCallback& callback) { + DCHECK(!has_pending_operation_); + DCHECK(cancel_callback_.is_null()); + + // Write() is not called yet, so there's nothing to flush. + if (!stream_impl_) + return net::OK; + + has_pending_operation_ = true; + int result = InitiateFlush(callback); + if (result != net::ERR_IO_PENDING) + has_pending_operation_ = false; + return result; +} + +int LocalFileStreamWriter::InitiateOpen( + const net::CompletionCallback& error_callback, + const base::Closure& main_operation) { + DCHECK(has_pending_operation_); + DCHECK(!stream_impl_.get()); + + stream_impl_.reset(new net::FileStream(NULL, task_runner_)); + + return stream_impl_->Open(file_path_, + kOpenFlagsForWrite, + base::Bind(&LocalFileStreamWriter::DidOpen, + weak_factory_.GetWeakPtr(), + error_callback, + main_operation)); +} + +void LocalFileStreamWriter::DidOpen( + const net::CompletionCallback& error_callback, + const base::Closure& main_operation, + int result) { + DCHECK(has_pending_operation_); + DCHECK(stream_impl_.get()); + + if (CancelIfRequested()) + return; + + if (result != net::OK) { + has_pending_operation_ = false; + stream_impl_.reset(NULL); + error_callback.Run(result); + return; + } + + InitiateSeek(error_callback, main_operation); +} + +void LocalFileStreamWriter::InitiateSeek( + const net::CompletionCallback& error_callback, + const base::Closure& main_operation) { + DCHECK(has_pending_operation_); + DCHECK(stream_impl_.get()); + + if (initial_offset_ == 0) { + // No need to seek. + main_operation.Run(); + return; + } + + int result = stream_impl_->Seek(net::FROM_BEGIN, initial_offset_, + base::Bind(&LocalFileStreamWriter::DidSeek, + weak_factory_.GetWeakPtr(), + error_callback, + main_operation)); + if (result != net::ERR_IO_PENDING) { + has_pending_operation_ = false; + error_callback.Run(result); + } +} + +void LocalFileStreamWriter::DidSeek( + const net::CompletionCallback& error_callback, + const base::Closure& main_operation, + int64 result) { + DCHECK(has_pending_operation_); + + if (CancelIfRequested()) + return; + + if (result != initial_offset_) { + // TODO(kinaba) add a more specific error code. + result = net::ERR_FAILED; + } + + if (result < 0) { + has_pending_operation_ = false; + error_callback.Run(static_cast<int>(result)); + return; + } + + main_operation.Run(); +} + +void LocalFileStreamWriter::ReadyToWrite( + net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + DCHECK(has_pending_operation_); + + int result = InitiateWrite(buf, buf_len, callback); + if (result != net::ERR_IO_PENDING) { + has_pending_operation_ = false; + callback.Run(result); + } +} + +int LocalFileStreamWriter::InitiateWrite( + net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + DCHECK(has_pending_operation_); + DCHECK(stream_impl_.get()); + + return stream_impl_->Write(buf, buf_len, + base::Bind(&LocalFileStreamWriter::DidWrite, + weak_factory_.GetWeakPtr(), + callback)); +} + +void LocalFileStreamWriter::DidWrite(const net::CompletionCallback& callback, + int result) { + DCHECK(has_pending_operation_); + + if (CancelIfRequested()) + return; + has_pending_operation_ = false; + callback.Run(result); +} + +int LocalFileStreamWriter::InitiateFlush( + const net::CompletionCallback& callback) { + DCHECK(has_pending_operation_); + DCHECK(stream_impl_.get()); + + return stream_impl_->Flush(base::Bind(&LocalFileStreamWriter::DidFlush, + weak_factory_.GetWeakPtr(), + callback)); +} + +void LocalFileStreamWriter::DidFlush(const net::CompletionCallback& callback, + int result) { + DCHECK(has_pending_operation_); + + if (CancelIfRequested()) + return; + has_pending_operation_ = false; + callback.Run(result); +} + +bool LocalFileStreamWriter::CancelIfRequested() { + DCHECK(has_pending_operation_); + + if (cancel_callback_.is_null()) + return false; + + net::CompletionCallback pending_cancel = cancel_callback_; + has_pending_operation_ = false; + cancel_callback_.Reset(); + pending_cancel.Run(net::OK); + return true; +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/local_file_stream_writer.h b/chromium/webkit/browser/fileapi/local_file_stream_writer.h new file mode 100644 index 00000000000..08e12e80e41 --- /dev/null +++ b/chromium/webkit/browser/fileapi/local_file_stream_writer.h @@ -0,0 +1,92 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_LOCAL_FILE_STREAM_WRITER_H_ +#define WEBKIT_BROWSER_FILEAPI_LOCAL_FILE_STREAM_WRITER_H_ + +#include <utility> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/platform_file.h" +#include "base/task_runner.h" +#include "webkit/browser/fileapi/file_stream_writer.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace net { +class FileStream; +} + +namespace fileapi { + +// This class is a thin wrapper around net::FileStream for writing local files. +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE LocalFileStreamWriter + : public FileStreamWriter { + public: + // Creates a writer for the existing file in the path |file_path| starting + // from |initial_offset|. Uses |task_runner| for async file operations. + LocalFileStreamWriter(base::TaskRunner* task_runner, + const base::FilePath& file_path, int64 initial_offset); + virtual ~LocalFileStreamWriter(); + + // FileStreamWriter overrides. + virtual int Write(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE; + virtual int Cancel(const net::CompletionCallback& callback) OVERRIDE; + virtual int Flush(const net::CompletionCallback& callback) OVERRIDE; + + private: + // Opens |file_path_| and if it succeeds, proceeds to InitiateSeek(). + // If failed, the error code is returned by calling |error_callback|. + int InitiateOpen(const net::CompletionCallback& error_callback, + const base::Closure& main_operation); + void DidOpen(const net::CompletionCallback& error_callback, + const base::Closure& main_operation, + int result); + + // Seeks to |initial_offset_| and proceeds to |main_operation| if it succeeds. + // If failed, the error code is returned by calling |error_callback|. + void InitiateSeek(const net::CompletionCallback& error_callback, + const base::Closure& main_operation); + void DidSeek(const net::CompletionCallback& error_callback, + const base::Closure& main_operation, + int64 result); + + // Passed as the |main_operation| of InitiateOpen() function. + void ReadyToWrite(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback); + + // Writes asynchronously to the file. + int InitiateWrite(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback); + void DidWrite(const net::CompletionCallback& callback, int result); + + // Flushes asynchronously to the file. + int InitiateFlush(const net::CompletionCallback& callback); + void DidFlush(const net::CompletionCallback& callback, int result); + + // Stops the in-flight operation and calls |cancel_callback_| if it has been + // set by Cancel() for the current operation. + bool CancelIfRequested(); + + // Initialization parameters. + const base::FilePath file_path_; + const int64 initial_offset_; + scoped_refptr<base::TaskRunner> task_runner_; + + // Current states of the operation. + bool has_pending_operation_; + scoped_ptr<net::FileStream> stream_impl_; + net::CompletionCallback cancel_callback_; + + base::WeakPtrFactory<LocalFileStreamWriter> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(LocalFileStreamWriter); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_LOCAL_FILE_STREAM_WRITER_H_ diff --git a/chromium/webkit/browser/fileapi/local_file_stream_writer_unittest.cc b/chromium/webkit/browser/fileapi/local_file_stream_writer_unittest.cc new file mode 100644 index 00000000000..bed037d7b09 --- /dev/null +++ b/chromium/webkit/browser/fileapi/local_file_stream_writer_unittest.cc @@ -0,0 +1,180 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/local_file_stream_writer.h" + +#include <string> + +#include "base/callback.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread.h" +#include "net/base/io_buffer.h" +#include "net/base/test_completion_callback.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using fileapi::LocalFileStreamWriter; + +class LocalFileStreamWriterTest : public testing::Test { + public: + LocalFileStreamWriterTest() + : message_loop_(base::MessageLoop::TYPE_IO), + file_thread_("FileUtilProxyTestFileThread") {} + + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(file_thread_.Start()); + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + } + + virtual void TearDown() OVERRIDE { + // Give another chance for deleted streams to perform Close. + base::MessageLoop::current()->RunUntilIdle(); + file_thread_.Stop(); + base::MessageLoop::current()->RunUntilIdle(); + } + + protected: + base::FilePath Path(const std::string& name) { + return temp_dir_.path().AppendASCII(name); + } + + int WriteStringToWriter(LocalFileStreamWriter* writer, + const std::string& data) { + scoped_refptr<net::StringIOBuffer> buffer(new net::StringIOBuffer(data)); + scoped_refptr<net::DrainableIOBuffer> drainable( + new net::DrainableIOBuffer(buffer.get(), buffer->size())); + + while (drainable->BytesRemaining() > 0) { + net::TestCompletionCallback callback; + int result = writer->Write( + drainable.get(), drainable->BytesRemaining(), callback.callback()); + if (result == net::ERR_IO_PENDING) + result = callback.WaitForResult(); + if (result <= 0) + return result; + drainable->DidConsume(result); + } + return net::OK; + } + + std::string GetFileContent(const base::FilePath& path) { + std::string content; + file_util::ReadFileToString(path, &content); + return content; + } + + base::FilePath CreateFileWithContent(const std::string& name, + const std::string& data) { + base::FilePath path = Path(name); + file_util::WriteFile(path, data.c_str(), data.size()); + return path; + } + + base::MessageLoopProxy* file_task_runner() const { + return file_thread_.message_loop_proxy().get(); + } + + private: + base::MessageLoop message_loop_; + base::Thread file_thread_; + base::ScopedTempDir temp_dir_; +}; + +void NeverCalled(int unused) { + ADD_FAILURE(); +} + +} // namespace + +TEST_F(LocalFileStreamWriterTest, Write) { + base::FilePath path = CreateFileWithContent("file_a", std::string()); + scoped_ptr<LocalFileStreamWriter> writer( + new LocalFileStreamWriter(file_task_runner(), path, 0)); + EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "foo")); + EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "bar")); + writer.reset(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_TRUE(base::PathExists(path)); + EXPECT_EQ("foobar", GetFileContent(path)); +} + +TEST_F(LocalFileStreamWriterTest, WriteMiddle) { + base::FilePath path = CreateFileWithContent("file_a", "foobar"); + scoped_ptr<LocalFileStreamWriter> writer( + new LocalFileStreamWriter(file_task_runner(), path, 2)); + EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "xxx")); + writer.reset(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_TRUE(base::PathExists(path)); + EXPECT_EQ("foxxxr", GetFileContent(path)); +} + +TEST_F(LocalFileStreamWriterTest, WriteEnd) { + base::FilePath path = CreateFileWithContent("file_a", "foobar"); + scoped_ptr<LocalFileStreamWriter> writer( + new LocalFileStreamWriter(file_task_runner(), path, 6)); + EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "xxx")); + writer.reset(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_TRUE(base::PathExists(path)); + EXPECT_EQ("foobarxxx", GetFileContent(path)); +} + +TEST_F(LocalFileStreamWriterTest, WriteFailForNonexistingFile) { + base::FilePath path = Path("file_a"); + ASSERT_FALSE(base::PathExists(path)); + scoped_ptr<LocalFileStreamWriter> writer( + new LocalFileStreamWriter(file_task_runner(), path, 0)); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, WriteStringToWriter(writer.get(), "foo")); + writer.reset(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_FALSE(base::PathExists(path)); +} + +TEST_F(LocalFileStreamWriterTest, CancelBeforeOperation) { + base::FilePath path = Path("file_a"); + scoped_ptr<LocalFileStreamWriter> writer( + new LocalFileStreamWriter(file_task_runner(), path, 0)); + // Cancel immediately fails when there's no in-flight operation. + int cancel_result = writer->Cancel(base::Bind(&NeverCalled)); + EXPECT_EQ(net::ERR_UNEXPECTED, cancel_result); +} + +TEST_F(LocalFileStreamWriterTest, CancelAfterFinishedOperation) { + base::FilePath path = CreateFileWithContent("file_a", std::string()); + scoped_ptr<LocalFileStreamWriter> writer( + new LocalFileStreamWriter(file_task_runner(), path, 0)); + EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "foo")); + + // Cancel immediately fails when there's no in-flight operation. + int cancel_result = writer->Cancel(base::Bind(&NeverCalled)); + EXPECT_EQ(net::ERR_UNEXPECTED, cancel_result); + + writer.reset(); + base::MessageLoop::current()->RunUntilIdle(); + // Write operation is already completed. + EXPECT_TRUE(base::PathExists(path)); + EXPECT_EQ("foo", GetFileContent(path)); +} + +TEST_F(LocalFileStreamWriterTest, CancelWrite) { + base::FilePath path = CreateFileWithContent("file_a", "foobar"); + scoped_ptr<LocalFileStreamWriter> writer( + new LocalFileStreamWriter(file_task_runner(), path, 0)); + + scoped_refptr<net::StringIOBuffer> buffer(new net::StringIOBuffer("xxx")); + int result = + writer->Write(buffer.get(), buffer->size(), base::Bind(&NeverCalled)); + ASSERT_EQ(net::ERR_IO_PENDING, result); + + net::TestCompletionCallback callback; + writer->Cancel(callback.callback()); + int cancel_result = callback.WaitForResult(); + EXPECT_EQ(net::OK, cancel_result); +} diff --git a/chromium/webkit/browser/fileapi/local_file_util.cc b/chromium/webkit/browser/fileapi/local_file_util.cc new file mode 100644 index 00000000000..f13af8d207f --- /dev/null +++ b/chromium/webkit/browser/fileapi/local_file_util.cc @@ -0,0 +1,257 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/local_file_util.h" + +#include "base/file_util.h" +#include "base/files/file_enumerator.h" +#include "base/files/file_util_proxy.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/native_file_util.h" +#include "webkit/common/fileapi/file_system_types.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +using base::PlatformFileError; + +class LocalFileEnumerator : public FileSystemFileUtil::AbstractFileEnumerator { + public: + LocalFileEnumerator(const base::FilePath& platform_root_path, + const base::FilePath& virtual_root_path, + int file_type) + : file_enum_(platform_root_path, false /* recursive */, file_type), + platform_root_path_(platform_root_path), + virtual_root_path_(virtual_root_path) { + } + + virtual ~LocalFileEnumerator() {} + + virtual base::FilePath Next() OVERRIDE; + virtual int64 Size() OVERRIDE; + virtual base::Time LastModifiedTime() OVERRIDE; + virtual bool IsDirectory() OVERRIDE; + + private: + base::FileEnumerator file_enum_; + base::FileEnumerator::FileInfo file_util_info_; + base::FilePath platform_root_path_; + base::FilePath virtual_root_path_; +}; + +base::FilePath LocalFileEnumerator::Next() { + base::FilePath next = file_enum_.Next(); + // Don't return symlinks. + while (!next.empty() && file_util::IsLink(next)) + next = file_enum_.Next(); + if (next.empty()) + return next; + file_util_info_ = file_enum_.GetInfo(); + + base::FilePath path; + platform_root_path_.AppendRelativePath(next, &path); + return virtual_root_path_.Append(path); +} + +int64 LocalFileEnumerator::Size() { + return file_util_info_.GetSize(); +} + +base::Time LocalFileEnumerator::LastModifiedTime() { + return file_util_info_.GetLastModifiedTime(); +} + +bool LocalFileEnumerator::IsDirectory() { + return file_util_info_.IsDirectory(); +} + +LocalFileUtil::LocalFileUtil() { +} + +LocalFileUtil::~LocalFileUtil() { +} + +PlatformFileError LocalFileUtil::CreateOrOpen( + FileSystemOperationContext* context, + const FileSystemURL& url, int file_flags, + base::PlatformFile* file_handle, bool* created) { + *created = false; + base::FilePath file_path; + PlatformFileError error = GetLocalFilePath(context, url, &file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + // Disallow opening files in symlinked paths. + if (file_util::IsLink(file_path)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + return NativeFileUtil::CreateOrOpen( + file_path, file_flags, file_handle, created); +} + +PlatformFileError LocalFileUtil::Close(FileSystemOperationContext* context, + base::PlatformFile file) { + return NativeFileUtil::Close(file); +} + +PlatformFileError LocalFileUtil::EnsureFileExists( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool* created) { + base::FilePath file_path; + PlatformFileError error = GetLocalFilePath(context, url, &file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + return NativeFileUtil::EnsureFileExists(file_path, created); +} + +PlatformFileError LocalFileUtil::CreateDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool exclusive, + bool recursive) { + base::FilePath file_path; + PlatformFileError error = GetLocalFilePath(context, url, &file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + return NativeFileUtil::CreateDirectory(file_path, exclusive, recursive); +} + +PlatformFileError LocalFileUtil::GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::PlatformFileInfo* file_info, + base::FilePath* platform_file_path) { + base::FilePath file_path; + PlatformFileError error = GetLocalFilePath(context, url, &file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + // We should not follow symbolic links in sandboxed file system. + if (file_util::IsLink(file_path)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + error = NativeFileUtil::GetFileInfo(file_path, file_info); + if (error == base::PLATFORM_FILE_OK) + *platform_file_path = file_path; + return error; +} + +scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> LocalFileUtil:: + CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url) { + base::FilePath file_path; + if (GetLocalFilePath(context, root_url, &file_path) != + base::PLATFORM_FILE_OK) { + return make_scoped_ptr(new EmptyFileEnumerator) + .PassAs<FileSystemFileUtil::AbstractFileEnumerator>(); + } + return make_scoped_ptr(new LocalFileEnumerator( + file_path, root_url.path(), + base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES)) + .PassAs<FileSystemFileUtil::AbstractFileEnumerator>(); +} + +PlatformFileError LocalFileUtil::GetLocalFilePath( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::FilePath* local_file_path) { + base::FilePath root = context->root_path(); + if (root.empty()) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + *local_file_path = root.Append(url.path()); + return base::PLATFORM_FILE_OK; +} + +PlatformFileError LocalFileUtil::Touch( + FileSystemOperationContext* context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time) { + base::FilePath file_path; + PlatformFileError error = GetLocalFilePath(context, url, &file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + return NativeFileUtil::Touch(file_path, last_access_time, last_modified_time); +} + +PlatformFileError LocalFileUtil::Truncate( + FileSystemOperationContext* context, + const FileSystemURL& url, + int64 length) { + base::FilePath file_path; + PlatformFileError error = GetLocalFilePath(context, url, &file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + return NativeFileUtil::Truncate(file_path, length); +} + +PlatformFileError LocalFileUtil::CopyOrMoveFile( + FileSystemOperationContext* context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + bool copy) { + base::FilePath src_file_path; + PlatformFileError error = GetLocalFilePath(context, src_url, &src_file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + + base::FilePath dest_file_path; + error = GetLocalFilePath(context, dest_url, &dest_file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + + return NativeFileUtil::CopyOrMoveFile(src_file_path, dest_file_path, copy); +} + +PlatformFileError LocalFileUtil::CopyInForeignFile( + FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url) { + if (src_file_path.empty()) + return base::PLATFORM_FILE_ERROR_INVALID_OPERATION; + + base::FilePath dest_file_path; + PlatformFileError error = + GetLocalFilePath(context, dest_url, &dest_file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + return NativeFileUtil::CopyOrMoveFile(src_file_path, dest_file_path, true); +} + +PlatformFileError LocalFileUtil::DeleteFile( + FileSystemOperationContext* context, + const FileSystemURL& url) { + base::FilePath file_path; + PlatformFileError error = GetLocalFilePath(context, url, &file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + return NativeFileUtil::DeleteFile(file_path); +} + +PlatformFileError LocalFileUtil::DeleteDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url) { + base::FilePath file_path; + PlatformFileError error = GetLocalFilePath(context, url, &file_path); + if (error != base::PLATFORM_FILE_OK) + return error; + return NativeFileUtil::DeleteDirectory(file_path); +} + +webkit_blob::ScopedFile LocalFileUtil::CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::PlatformFileError* error, + base::PlatformFileInfo* file_info, + base::FilePath* platform_path) { + DCHECK(file_info); + // We're just returning the local file information. + *error = GetFileInfo(context, url, file_info, platform_path); + if (*error == base::PLATFORM_FILE_OK && file_info->is_directory) + *error = base::PLATFORM_FILE_ERROR_NOT_A_FILE; + return webkit_blob::ScopedFile(); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/local_file_util.h b/chromium/webkit/browser/fileapi/local_file_util.h new file mode 100644 index 00000000000..abd6b54e1c3 --- /dev/null +++ b/chromium/webkit/browser/fileapi/local_file_util.h @@ -0,0 +1,99 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_LOCAL_FILE_UTIL_H_ +#define WEBKIT_BROWSER_FILEAPI_LOCAL_FILE_UTIL_H_ + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/platform_file.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace base { +class FilePath; +class Time; +} + +class GURL; + +namespace fileapi { + +class FileSystemOperationContext; +class FileSystemURL; + +// An instance of this class is created and owned by *FileSystemBackend. +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE LocalFileUtil + : public FileSystemFileUtil { + public: + LocalFileUtil(); + virtual ~LocalFileUtil(); + + virtual base::PlatformFileError CreateOrOpen( + FileSystemOperationContext* context, + const FileSystemURL& url, + int file_flags, + base::PlatformFile* file_handle, + bool* created) OVERRIDE; + virtual base::PlatformFileError Close( + FileSystemOperationContext* context, + base::PlatformFile file) OVERRIDE; + virtual base::PlatformFileError EnsureFileExists( + FileSystemOperationContext* context, + const FileSystemURL& url, bool* created) OVERRIDE; + virtual base::PlatformFileError CreateDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool exclusive, + bool recursive) OVERRIDE; + virtual base::PlatformFileError GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::PlatformFileInfo* file_info, + base::FilePath* platform_file) OVERRIDE; + virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url) OVERRIDE; + virtual base::PlatformFileError GetLocalFilePath( + FileSystemOperationContext* context, + const FileSystemURL& file_system_url, + base::FilePath* local_file_path) OVERRIDE; + virtual base::PlatformFileError Touch( + FileSystemOperationContext* context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time) OVERRIDE; + virtual base::PlatformFileError Truncate( + FileSystemOperationContext* context, + const FileSystemURL& url, + int64 length) OVERRIDE; + virtual base::PlatformFileError CopyOrMoveFile( + FileSystemOperationContext* context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + bool copy) OVERRIDE; + virtual base::PlatformFileError CopyInForeignFile( + FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url) OVERRIDE; + virtual base::PlatformFileError DeleteFile( + FileSystemOperationContext* context, + const FileSystemURL& url) OVERRIDE; + virtual base::PlatformFileError DeleteDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url) OVERRIDE; + virtual webkit_blob::ScopedFile CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::PlatformFileError* error, + base::PlatformFileInfo* file_info, + base::FilePath* platform_path) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(LocalFileUtil); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_LOCAL_FILE_UTIL_H_ diff --git a/chromium/webkit/browser/fileapi/local_file_util_unittest.cc b/chromium/webkit/browser/fileapi/local_file_util_unittest.cc new file mode 100644 index 00000000000..b0b52e718db --- /dev/null +++ b/chromium/webkit/browser/fileapi/local_file_util_unittest.cc @@ -0,0 +1,387 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/platform_file.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/local_file_util.h" +#include "webkit/browser/fileapi/mock_file_system_context.h" +#include "webkit/browser/fileapi/native_file_util.h" +#include "webkit/common/fileapi/file_system_types.h" + +namespace fileapi { + +namespace { + +const GURL kOrigin("http://foo/"); +const FileSystemType kFileSystemType = kFileSystemTypeTest; + +} // namespace + +class LocalFileUtilTest : public testing::Test { + public: + LocalFileUtilTest() {} + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + file_system_context_ = CreateFileSystemContextForTesting( + NULL, data_dir_.path()); + } + + virtual void TearDown() { + file_system_context_ = NULL; + base::MessageLoop::current()->RunUntilIdle(); + } + + protected: + FileSystemOperationContext* NewContext() { + FileSystemOperationContext* context = + new FileSystemOperationContext(file_system_context_.get()); + context->set_update_observers( + *file_system_context_->GetUpdateObservers(kFileSystemType)); + context->set_root_path(data_dir_.path()); + return context; + } + + LocalFileUtil* file_util() { + return static_cast<LocalFileUtil*>( + file_system_context_->GetFileUtil(kFileSystemType)); + } + + FileSystemURL CreateURL(const std::string& file_name) { + return file_system_context_->CreateCrackedFileSystemURL( + kOrigin, kFileSystemType, base::FilePath().FromUTF8Unsafe(file_name)); + } + + base::FilePath LocalPath(const char *file_name) { + base::FilePath path; + scoped_ptr<FileSystemOperationContext> context(NewContext()); + file_util()->GetLocalFilePath(context.get(), CreateURL(file_name), &path); + return path; + } + + bool FileExists(const char *file_name) { + return base::PathExists(LocalPath(file_name)) && + !base::DirectoryExists(LocalPath(file_name)); + } + + bool DirectoryExists(const char *file_name) { + return base::DirectoryExists(LocalPath(file_name)); + } + + int64 GetSize(const char *file_name) { + base::PlatformFileInfo info; + file_util::GetFileInfo(LocalPath(file_name), &info); + return info.size; + } + + base::PlatformFileError CreateFile(const char* file_name, + base::PlatformFile* file_handle, + bool* created) { + int file_flags = base::PLATFORM_FILE_CREATE | + base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC; + + scoped_ptr<FileSystemOperationContext> context(NewContext()); + return file_util()->CreateOrOpen( + context.get(), + CreateURL(file_name), + file_flags, file_handle, created); + } + + base::PlatformFileError EnsureFileExists(const char* file_name, + bool* created) { + scoped_ptr<FileSystemOperationContext> context(NewContext()); + return file_util()->EnsureFileExists( + context.get(), + CreateURL(file_name), created); + } + + FileSystemContext* file_system_context() { + return file_system_context_.get(); + } + + private: + base::MessageLoop message_loop_; + scoped_refptr<FileSystemContext> file_system_context_; + base::ScopedTempDir data_dir_; + + DISALLOW_COPY_AND_ASSIGN(LocalFileUtilTest); +}; + +TEST_F(LocalFileUtilTest, CreateAndClose) { + const char *file_name = "test_file"; + base::PlatformFile file_handle; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, + CreateFile(file_name, &file_handle, &created)); + ASSERT_TRUE(created); + + EXPECT_TRUE(FileExists(file_name)); + EXPECT_EQ(0, GetSize(file_name)); + + scoped_ptr<FileSystemOperationContext> context(NewContext()); + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->Close(context.get(), file_handle)); +} + +// file_util::CreateSymbolicLink is only supported on POSIX. +#if defined(OS_POSIX) +TEST_F(LocalFileUtilTest, CreateFailForSymlink) { + // Create symlink target file. + const char *target_name = "symlink_target"; + base::PlatformFile target_handle; + bool symlink_target_created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + CreateFile(target_name, &target_handle, &symlink_target_created)); + ASSERT_TRUE(symlink_target_created); + base::FilePath target_path = LocalPath(target_name); + + // Create symlink where target must be real file. + const char *symlink_name = "symlink_file"; + base::FilePath symlink_path = LocalPath(symlink_name); + ASSERT_TRUE(file_util::CreateSymbolicLink(target_path, symlink_path)); + ASSERT_TRUE(FileExists(symlink_name)); + + // Try to open the symlink file which should fail. + scoped_ptr<FileSystemOperationContext> context(NewContext()); + FileSystemURL url = CreateURL(symlink_name); + int file_flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ; + base::PlatformFile file_handle; + bool created = false; + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, file_util()->CreateOrOpen( + context.get(), url, file_flags, &file_handle, &created)); + EXPECT_FALSE(created); +} +#endif + +TEST_F(LocalFileUtilTest, EnsureFileExists) { + const char *file_name = "foobar"; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(file_name, &created)); + ASSERT_TRUE(created); + + EXPECT_TRUE(FileExists(file_name)); + EXPECT_EQ(0, GetSize(file_name)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(file_name, &created)); + EXPECT_FALSE(created); +} + +TEST_F(LocalFileUtilTest, TouchFile) { + const char *file_name = "test_file"; + base::PlatformFile file_handle; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, + CreateFile(file_name, &file_handle, &created)); + ASSERT_TRUE(created); + + scoped_ptr<FileSystemOperationContext> context(NewContext()); + + base::PlatformFileInfo info; + ASSERT_TRUE(file_util::GetFileInfo(LocalPath(file_name), &info)); + const base::Time new_accessed = + info.last_accessed + base::TimeDelta::FromHours(10); + const base::Time new_modified = + info.last_modified + base::TimeDelta::FromHours(5); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->Touch(context.get(), CreateURL(file_name), + new_accessed, new_modified)); + + ASSERT_TRUE(file_util::GetFileInfo(LocalPath(file_name), &info)); + EXPECT_EQ(new_accessed, info.last_accessed); + EXPECT_EQ(new_modified, info.last_modified); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->Close(context.get(), file_handle)); +} + +TEST_F(LocalFileUtilTest, TouchDirectory) { + const char *dir_name = "test_dir"; + scoped_ptr<FileSystemOperationContext> context(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->CreateDirectory(context.get(), + CreateURL(dir_name), + false /* exclusive */, + false /* recursive */)); + + base::PlatformFileInfo info; + ASSERT_TRUE(file_util::GetFileInfo(LocalPath(dir_name), &info)); + const base::Time new_accessed = + info.last_accessed + base::TimeDelta::FromHours(10); + const base::Time new_modified = + info.last_modified + base::TimeDelta::FromHours(5); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + file_util()->Touch(context.get(), CreateURL(dir_name), + new_accessed, new_modified)); + + ASSERT_TRUE(file_util::GetFileInfo(LocalPath(dir_name), &info)); + EXPECT_EQ(new_accessed, info.last_accessed); + EXPECT_EQ(new_modified, info.last_modified); +} + +TEST_F(LocalFileUtilTest, Truncate) { + const char *file_name = "truncated"; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(file_name, &created)); + ASSERT_TRUE(created); + + scoped_ptr<FileSystemOperationContext> context; + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->Truncate(context.get(), CreateURL(file_name), 1020)); + + EXPECT_TRUE(FileExists(file_name)); + EXPECT_EQ(1020, GetSize(file_name)); +} + +TEST_F(LocalFileUtilTest, CopyFile) { + const char *from_file = "fromfile"; + const char *to_file1 = "tofile1"; + const char *to_file2 = "tofile2"; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + + scoped_ptr<FileSystemOperationContext> context; + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->Truncate(context.get(), CreateURL(from_file), 1020)); + + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Copy(file_system_context(), + CreateURL(from_file), + CreateURL(to_file1))); + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Copy(file_system_context(), + CreateURL(from_file), + CreateURL(to_file2))); + + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + EXPECT_TRUE(FileExists(to_file1)); + EXPECT_EQ(1020, GetSize(to_file1)); + EXPECT_TRUE(FileExists(to_file2)); + EXPECT_EQ(1020, GetSize(to_file2)); +} + +TEST_F(LocalFileUtilTest, CopyDirectory) { + const char *from_dir = "fromdir"; + const char *from_file = "fromdir/fromfile"; + const char *to_dir = "todir"; + const char *to_file = "todir/fromfile"; + bool created; + scoped_ptr<FileSystemOperationContext> context; + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->CreateDirectory(context.get(), CreateURL(from_dir), + false, false)); + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->Truncate(context.get(), CreateURL(from_file), 1020)); + + EXPECT_TRUE(DirectoryExists(from_dir)); + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + EXPECT_FALSE(DirectoryExists(to_dir)); + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Copy(file_system_context(), + CreateURL(from_dir), CreateURL(to_dir))); + + EXPECT_TRUE(DirectoryExists(from_dir)); + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + EXPECT_TRUE(DirectoryExists(to_dir)); + EXPECT_TRUE(FileExists(to_file)); + EXPECT_EQ(1020, GetSize(to_file)); +} + +TEST_F(LocalFileUtilTest, MoveFile) { + const char *from_file = "fromfile"; + const char *to_file = "tofile"; + bool created; + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + scoped_ptr<FileSystemOperationContext> context; + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->Truncate(context.get(), CreateURL(from_file), 1020)); + + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Move(file_system_context(), + CreateURL(from_file), + CreateURL(to_file))); + + EXPECT_FALSE(FileExists(from_file)); + EXPECT_TRUE(FileExists(to_file)); + EXPECT_EQ(1020, GetSize(to_file)); +} + +TEST_F(LocalFileUtilTest, MoveDirectory) { + const char *from_dir = "fromdir"; + const char *from_file = "fromdir/fromfile"; + const char *to_dir = "todir"; + const char *to_file = "todir/fromfile"; + bool created; + scoped_ptr<FileSystemOperationContext> context; + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->CreateDirectory(context.get(), CreateURL(from_dir), + false, false)); + ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->Truncate(context.get(), CreateURL(from_file), 1020)); + + EXPECT_TRUE(DirectoryExists(from_dir)); + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + EXPECT_FALSE(DirectoryExists(to_dir)); + + context.reset(NewContext()); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Move(file_system_context(), + CreateURL(from_dir), + CreateURL(to_dir))); + + EXPECT_FALSE(DirectoryExists(from_dir)); + EXPECT_TRUE(DirectoryExists(to_dir)); + EXPECT_TRUE(FileExists(to_file)); + EXPECT_EQ(1020, GetSize(to_file)); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/mock_file_change_observer.cc b/chromium/webkit/browser/fileapi/mock_file_change_observer.cc new file mode 100644 index 00000000000..74791f5ebd2 --- /dev/null +++ b/chromium/webkit/browser/fileapi/mock_file_change_observer.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/mock_file_change_observer.h" + +namespace fileapi { + +MockFileChangeObserver::MockFileChangeObserver() + : create_file_count_(0), + create_file_from_count_(0), + remove_file_count_(0), + modify_file_count_(0), + create_directory_count_(0), + remove_directory_count_(0) {} + +MockFileChangeObserver::~MockFileChangeObserver() {} + +// static +ChangeObserverList MockFileChangeObserver::CreateList( + MockFileChangeObserver* observer) { + ChangeObserverList list; + return list.AddObserver(observer, base::MessageLoopProxy::current().get()); +} + +void MockFileChangeObserver::OnCreateFile(const FileSystemURL& url) { + create_file_count_++; +} + +void MockFileChangeObserver::OnCreateFileFrom(const FileSystemURL& url, + const FileSystemURL& src) { + create_file_from_count_++; +} + +void MockFileChangeObserver::OnRemoveFile(const FileSystemURL& url) { + remove_file_count_++; +} + +void MockFileChangeObserver::OnModifyFile(const FileSystemURL& url) { + modify_file_count_++; +} + +void MockFileChangeObserver::OnCreateDirectory(const FileSystemURL& url) { + create_directory_count_++; +} + +void MockFileChangeObserver::OnRemoveDirectory(const FileSystemURL& url) { + remove_directory_count_++; +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/mock_file_change_observer.h b/chromium/webkit/browser/fileapi/mock_file_change_observer.h new file mode 100644 index 00000000000..3835323d2a5 --- /dev/null +++ b/chromium/webkit/browser/fileapi/mock_file_change_observer.h @@ -0,0 +1,103 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_MOCK_FILE_CHANGE_OBSERVER_H_ +#define WEBKIT_BROWSER_FILEAPI_MOCK_FILE_CHANGE_OBSERVER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "webkit/browser/fileapi/file_observers.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/task_runner_bound_observer_list.h" + +namespace fileapi { + +// Mock file change observer. +class MockFileChangeObserver : public FileChangeObserver { + public: + MockFileChangeObserver(); + virtual ~MockFileChangeObserver(); + + // Creates a ChangeObserverList which only contains given |observer|. + static ChangeObserverList CreateList(MockFileChangeObserver* observer); + + // FileChangeObserver overrides. + virtual void OnCreateFile(const FileSystemURL& url) OVERRIDE; + virtual void OnCreateFileFrom(const FileSystemURL& url, + const FileSystemURL& src) OVERRIDE; + virtual void OnRemoveFile(const FileSystemURL& url) OVERRIDE; + virtual void OnModifyFile(const FileSystemURL& url) OVERRIDE; + virtual void OnCreateDirectory(const FileSystemURL& url) OVERRIDE; + virtual void OnRemoveDirectory(const FileSystemURL& url) OVERRIDE; + + void ResetCount() { + create_file_count_ = 0; + create_file_from_count_ = 0; + remove_file_count_ = 0; + modify_file_count_ = 0; + create_directory_count_ = 0; + remove_directory_count_ = 0; + } + + bool HasNoChange() const { + return create_file_count_ == 0 && + create_file_from_count_ == 0 && + remove_file_count_ == 0 && + modify_file_count_ == 0 && + create_directory_count_ == 0 && + remove_directory_count_ == 0; + } + + int create_file_count() const { return create_file_count_; } + int create_file_from_count() const { return create_file_from_count_; } + int remove_file_count() const { return remove_file_count_; } + int modify_file_count() const { return modify_file_count_; } + int create_directory_count() const { return create_directory_count_; } + int remove_directory_count() const { return remove_directory_count_; } + + int get_and_reset_create_file_count() { + int tmp = create_file_count_; + create_file_count_ = 0; + return tmp; + } + int get_and_reset_create_file_from_count() { + int tmp = create_file_from_count_; + create_file_from_count_ = 0; + return tmp; + } + int get_and_reset_remove_file_count() { + int tmp = remove_file_count_; + remove_file_count_ = 0; + return tmp; + } + int get_and_reset_modify_file_count() { + int tmp = modify_file_count_; + modify_file_count_ = 0; + return tmp; + } + int get_and_reset_create_directory_count() { + int tmp = create_directory_count_; + create_directory_count_ = 0; + return tmp; + } + int get_and_reset_remove_directory_count() { + int tmp = remove_directory_count_; + remove_directory_count_ = 0; + return tmp; + } + + private: + int create_file_count_; + int create_file_from_count_; + int remove_file_count_; + int modify_file_count_; + int create_directory_count_; + int remove_directory_count_; + + DISALLOW_COPY_AND_ASSIGN(MockFileChangeObserver); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_MOCK_FILE_CHANGE_OBSERVER_H_ diff --git a/chromium/webkit/browser/fileapi/mock_file_system_context.cc b/chromium/webkit/browser/fileapi/mock_file_system_context.cc new file mode 100644 index 00000000000..e2556d5da13 --- /dev/null +++ b/chromium/webkit/browser/fileapi/mock_file_system_context.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/mock_file_system_context.h" + +#include "base/memory/scoped_vector.h" +#include "webkit/browser/fileapi/external_mount_points.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/mock_file_system_options.h" +#include "webkit/browser/fileapi/test_file_system_backend.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" + +namespace fileapi { + +FileSystemContext* CreateFileSystemContextForTesting( + quota::QuotaManagerProxy* quota_manager_proxy, + const base::FilePath& base_path) { + ScopedVector<FileSystemBackend> additional_providers; + additional_providers.push_back(new TestFileSystemBackend( + base::MessageLoopProxy::current().get(), base_path)); + return CreateFileSystemContextWithAdditionalProvidersForTesting( + quota_manager_proxy, additional_providers.Pass(), base_path); +} + +FileSystemContext* CreateFileSystemContextWithAdditionalProvidersForTesting( + quota::QuotaManagerProxy* quota_manager_proxy, + ScopedVector<FileSystemBackend> additional_providers, + const base::FilePath& base_path) { + return new FileSystemContext( + base::MessageLoopProxy::current().get(), + base::MessageLoopProxy::current().get(), + ExternalMountPoints::CreateRefCounted().get(), + make_scoped_refptr(new quota::MockSpecialStoragePolicy()).get(), + quota_manager_proxy, + additional_providers.Pass(), + base_path, + CreateAllowFileAccessOptions()); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/mock_file_system_context.h b/chromium/webkit/browser/fileapi/mock_file_system_context.h new file mode 100644 index 00000000000..bf01ba1594d --- /dev/null +++ b/chromium/webkit/browser/fileapi/mock_file_system_context.h @@ -0,0 +1,34 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_MOCK_FILE_SYSTEM_CONTEXT_H_ +#define WEBKIT_BROWSER_FILEAPI_MOCK_FILE_SYSTEM_CONTEXT_H_ + +#include "base/files/file_path.h" +#include "base/memory/scoped_vector.h" + +namespace quota { +class QuotaManagerProxy; +class SpecialStoragePolicy; +} + +namespace fileapi { + +class FileSystemContext; +class FileSystemBackend; + +FileSystemContext* CreateFileSystemContextForTesting( + quota::QuotaManagerProxy* quota_manager_proxy, + const base::FilePath& base_path); + +// The caller is responsible for including TestFileSystemBackend in +// |additional_providers| if needed. +FileSystemContext* CreateFileSystemContextWithAdditionalProvidersForTesting( + quota::QuotaManagerProxy* quota_manager_proxy, + ScopedVector<FileSystemBackend> additional_providers, + const base::FilePath& base_path); + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_MOCK_FILE_SYSTEM_CONTEXT_H_ diff --git a/chromium/webkit/browser/fileapi/mock_file_system_options.cc b/chromium/webkit/browser/fileapi/mock_file_system_options.cc new file mode 100644 index 00000000000..06fbc27c20d --- /dev/null +++ b/chromium/webkit/browser/fileapi/mock_file_system_options.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/mock_file_system_options.h" + +#include <string> +#include <vector> + +namespace fileapi { + +FileSystemOptions CreateIncognitoFileSystemOptions() { + std::vector<std::string> additional_allowed_schemes; +#if defined(OS_CHROMEOS) + additional_allowed_schemes.push_back("chrome-extension"); +#endif + return FileSystemOptions(FileSystemOptions::PROFILE_MODE_INCOGNITO, + additional_allowed_schemes); +}; + +FileSystemOptions CreateAllowFileAccessOptions() { + std::vector<std::string> additional_allowed_schemes; + additional_allowed_schemes.push_back("file"); +#if defined(OS_CHROMEOS) + additional_allowed_schemes.push_back("chrome-extension"); +#endif + return FileSystemOptions(FileSystemOptions::PROFILE_MODE_NORMAL, + additional_allowed_schemes); +}; + +FileSystemOptions CreateDisallowFileAccessOptions() { + std::vector<std::string> additional_allowed_schemes; +#if defined(OS_CHROMEOS) + additional_allowed_schemes.push_back("chrome-extension"); +#endif + return FileSystemOptions(FileSystemOptions::PROFILE_MODE_NORMAL, + additional_allowed_schemes); +}; + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/mock_file_system_options.h b/chromium/webkit/browser/fileapi/mock_file_system_options.h new file mode 100644 index 00000000000..f46f6354611 --- /dev/null +++ b/chromium/webkit/browser/fileapi/mock_file_system_options.h @@ -0,0 +1,23 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_MOCK_FILE_SYSTEM_OPTIONS_H_ +#define WEBKIT_BROWSER_FILEAPI_MOCK_FILE_SYSTEM_OPTIONS_H_ + +#include "webkit/browser/fileapi/file_system_options.h" + +namespace fileapi { + +// Returns Filesystem options for incognito mode. +FileSystemOptions CreateIncognitoFileSystemOptions(); + +// Returns Filesystem options that allow file access. +FileSystemOptions CreateAllowFileAccessOptions(); + +// Returns Filesystem options that disallow file access. +FileSystemOptions CreateDisallowFileAccessOptions(); + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_MOCK_FILE_SYSTEM_OPTIONS_H_ diff --git a/chromium/webkit/browser/fileapi/mount_points.cc b/chromium/webkit/browser/fileapi/mount_points.cc new file mode 100644 index 00000000000..cd7c7b23c76 --- /dev/null +++ b/chromium/webkit/browser/fileapi/mount_points.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/mount_points.h" + +namespace fileapi { + +MountPoints::MountPointInfo::MountPointInfo() {} +MountPoints::MountPointInfo::MountPointInfo( + const std::string& name, const base::FilePath& path) + : name(name), path(path) {} + +} // namespace fileapi + diff --git a/chromium/webkit/browser/fileapi/mount_points.h b/chromium/webkit/browser/fileapi/mount_points.h new file mode 100644 index 00000000000..5ec67b95a9c --- /dev/null +++ b/chromium/webkit/browser/fileapi/mount_points.h @@ -0,0 +1,105 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_MOUNT_POINTS_H_ +#define WEBKIT_BROWSER_FILEAPI_MOUNT_POINTS_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/fileapi/file_system_util.h" + +class GURL; + +namespace fileapi { +class FileSystemURL; +} + +namespace fileapi { + +// Represents a set of mount points for File API. +class WEBKIT_STORAGE_BROWSER_EXPORT MountPoints { + public: + struct WEBKIT_STORAGE_BROWSER_EXPORT MountPointInfo { + MountPointInfo(); + MountPointInfo(const std::string& name, const base::FilePath& path); + + // The name to be used to register the path. The registered file can + // be referred by a virtual path /<filesystem_id>/<name>. + // The name should NOT contain a path separator '/'. + std::string name; + + // The path of the file. + base::FilePath path; + + // For STL operation. + bool operator<(const MountPointInfo& that) const { + return name < that.name; + } + }; + + MountPoints() {} + virtual ~MountPoints() {} + + // Revokes a mount point identified by |mount_name|. + // Returns false if the |mount_name| is not (no longer) registered. + // TODO(kinuko): Probably this should be rather named RevokeMountPoint. + virtual bool RevokeFileSystem(const std::string& mount_name) = 0; + + // Returns true if the MountPoints implementation handles filesystems with + // the given mount type. + virtual bool HandlesFileSystemMountType(FileSystemType type) const = 0; + + // Same as CreateCrackedFileSystemURL, but cracks FileSystemURL created + // from |url|. + virtual FileSystemURL CrackURL(const GURL& url) const = 0; + + // Creates a FileSystemURL with the given origin, type and path and tries to + // crack it as a part of one of the registered mount points. + // If the the URL is not valid or does not belong to any of the mount points + // registered in this context, returns empty, invalid FileSystemURL. + virtual FileSystemURL CreateCrackedFileSystemURL( + const GURL& origin, + fileapi::FileSystemType type, + const base::FilePath& path) const = 0; + + // Returns the mount point root path registered for a given |mount_name|. + // Returns false if the given |mount_name| is not valid. + virtual bool GetRegisteredPath(const std::string& mount_name, + base::FilePath* path) const = 0; + + // Cracks the given |virtual_path| (which is the path part of a filesystem URL + // without '/external' or '/isolated' prefix part) and populates the + // |mount_name|, |type|, and |path| if the <mount_name> part embedded in + // the |virtual_path| (i.e. the first component of the |virtual_path|) is a + // valid registered filesystem ID or mount name for an existing mount point. + // + // Returns false if the given virtual_path cannot be cracked. + // + // Note that |path| is set to empty paths if the filesystem type is isolated + // and |virtual_path| has no <relative_path> part (i.e. pointing to the + // virtual root). + virtual bool CrackVirtualPath(const base::FilePath& virtual_path, + std::string* mount_name, + FileSystemType* type, + base::FilePath* path) const = 0; + + protected: + friend class FileSystemContext; + + // Same as CrackURL and CreateCrackedFileSystemURL, but cracks the url already + // instantiated as the FileSystemURL class. This is internally used for nested + // URL cracking in FileSystemContext. + virtual FileSystemURL CrackFileSystemURL(const FileSystemURL& url) const = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(MountPoints); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_MOUNT_POINTS_H_ diff --git a/chromium/webkit/browser/fileapi/native_file_util.cc b/chromium/webkit/browser/fileapi/native_file_util.cc new file mode 100644 index 00000000000..32e9467091c --- /dev/null +++ b/chromium/webkit/browser/fileapi/native_file_util.cc @@ -0,0 +1,262 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/native_file_util.h" + +#include "base/file_util.h" +#include "base/files/file_enumerator.h" +#include "base/memory/scoped_ptr.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" + +namespace fileapi { + +namespace { + +// Sets permissions on directory at |dir_path| based on the target platform. +// Returns true on success, or false otherwise. +// +// TODO(benchan): Find a better place outside webkit to host this function. +bool SetPlatformSpecificDirectoryPermissions(const base::FilePath& dir_path) { +#if defined(OS_CHROMEOS) + // System daemons on Chrome OS may run as a user different than the Chrome + // process but need to access files under the directories created here. + // Because of that, grant the execute permission on the created directory + // to group and other users. + if (HANDLE_EINTR(chmod(dir_path.value().c_str(), + S_IRWXU | S_IXGRP | S_IXOTH)) != 0) { + return false; + } +#endif + // Keep the directory permissions unchanged on non-Chrome OS platforms. + return true; +} + +} // namespace + +using base::PlatformFile; +using base::PlatformFileError; + +class NativeFileEnumerator : public FileSystemFileUtil::AbstractFileEnumerator { + public: + NativeFileEnumerator(const base::FilePath& root_path, + bool recursive, + int file_type) + : file_enum_(root_path, recursive, file_type) { + } + + virtual ~NativeFileEnumerator() {} + + virtual base::FilePath Next() OVERRIDE; + virtual int64 Size() OVERRIDE; + virtual base::Time LastModifiedTime() OVERRIDE; + virtual bool IsDirectory() OVERRIDE; + + private: + base::FileEnumerator file_enum_; + base::FileEnumerator::FileInfo file_util_info_; +}; + +base::FilePath NativeFileEnumerator::Next() { + base::FilePath rv = file_enum_.Next(); + if (!rv.empty()) + file_util_info_ = file_enum_.GetInfo(); + return rv; +} + +int64 NativeFileEnumerator::Size() { + return file_util_info_.GetSize(); +} + +base::Time NativeFileEnumerator::LastModifiedTime() { + return file_util_info_.GetLastModifiedTime(); +} + +bool NativeFileEnumerator::IsDirectory() { + return file_util_info_.IsDirectory(); +} + +PlatformFileError NativeFileUtil::CreateOrOpen( + const base::FilePath& path, int file_flags, + PlatformFile* file_handle, bool* created) { + if (!base::DirectoryExists(path.DirName())) { + // If its parent does not exist, should return NOT_FOUND error. + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + } + if (base::DirectoryExists(path)) + return base::PLATFORM_FILE_ERROR_NOT_A_FILE; + PlatformFileError error_code = base::PLATFORM_FILE_OK; + *file_handle = base::CreatePlatformFile(path, file_flags, + created, &error_code); + return error_code; +} + +PlatformFileError NativeFileUtil::Close(PlatformFile file_handle) { + if (!base::ClosePlatformFile(file_handle)) + return base::PLATFORM_FILE_ERROR_FAILED; + return base::PLATFORM_FILE_OK; +} + +PlatformFileError NativeFileUtil::EnsureFileExists( + const base::FilePath& path, + bool* created) { + if (!base::DirectoryExists(path.DirName())) + // If its parent does not exist, should return NOT_FOUND error. + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + PlatformFileError error_code = base::PLATFORM_FILE_OK; + // Tries to create the |path| exclusively. This should fail + // with base::PLATFORM_FILE_ERROR_EXISTS if the path already exists. + PlatformFile handle = base::CreatePlatformFile( + path, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_READ, + created, &error_code); + if (error_code == base::PLATFORM_FILE_ERROR_EXISTS) { + // Make sure created_ is false. + if (created) + *created = false; + error_code = base::PLATFORM_FILE_OK; + } + if (handle != base::kInvalidPlatformFileValue) + base::ClosePlatformFile(handle); + return error_code; +} + +PlatformFileError NativeFileUtil::CreateDirectory( + const base::FilePath& path, + bool exclusive, + bool recursive) { + // If parent dir of file doesn't exist. + if (!recursive && !base::PathExists(path.DirName())) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + + bool path_exists = base::PathExists(path); + if (exclusive && path_exists) + return base::PLATFORM_FILE_ERROR_EXISTS; + + // If file exists at the path. + if (path_exists && !base::DirectoryExists(path)) + return base::PLATFORM_FILE_ERROR_EXISTS; + + if (!file_util::CreateDirectory(path)) + return base::PLATFORM_FILE_ERROR_FAILED; + + if (!SetPlatformSpecificDirectoryPermissions(path)) + return base::PLATFORM_FILE_ERROR_FAILED; + + return base::PLATFORM_FILE_OK; +} + +PlatformFileError NativeFileUtil::GetFileInfo( + const base::FilePath& path, + base::PlatformFileInfo* file_info) { + if (!base::PathExists(path)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + if (!file_util::GetFileInfo(path, file_info)) + return base::PLATFORM_FILE_ERROR_FAILED; + return base::PLATFORM_FILE_OK; +} + +scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> + NativeFileUtil::CreateFileEnumerator(const base::FilePath& root_path, + bool recursive) { + return make_scoped_ptr(new NativeFileEnumerator( + root_path, recursive, + base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES)) + .PassAs<FileSystemFileUtil::AbstractFileEnumerator>(); +} + +PlatformFileError NativeFileUtil::Touch( + const base::FilePath& path, + const base::Time& last_access_time, + const base::Time& last_modified_time) { + if (!file_util::TouchFile( + path, last_access_time, last_modified_time)) + return base::PLATFORM_FILE_ERROR_FAILED; + return base::PLATFORM_FILE_OK; +} + +PlatformFileError NativeFileUtil::Truncate( + const base::FilePath& path, int64 length) { + PlatformFileError error_code(base::PLATFORM_FILE_ERROR_FAILED); + PlatformFile file = + base::CreatePlatformFile( + path, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE, + NULL, + &error_code); + if (error_code != base::PLATFORM_FILE_OK) { + return error_code; + } + DCHECK_NE(base::kInvalidPlatformFileValue, file); + if (!base::TruncatePlatformFile(file, length)) + error_code = base::PLATFORM_FILE_ERROR_FAILED; + base::ClosePlatformFile(file); + return error_code; +} + +bool NativeFileUtil::PathExists(const base::FilePath& path) { + return base::PathExists(path); +} + +bool NativeFileUtil::DirectoryExists(const base::FilePath& path) { + return base::DirectoryExists(path); +} + +PlatformFileError NativeFileUtil::CopyOrMoveFile( + const base::FilePath& src_path, + const base::FilePath& dest_path, + bool copy) { + base::PlatformFileInfo info; + base::PlatformFileError error = NativeFileUtil::GetFileInfo(src_path, &info); + if (error != base::PLATFORM_FILE_OK) + return error; + if (info.is_directory) + return base::PLATFORM_FILE_ERROR_NOT_A_FILE; + + error = NativeFileUtil::GetFileInfo(dest_path, &info); + if (error != base::PLATFORM_FILE_OK && + error != base::PLATFORM_FILE_ERROR_NOT_FOUND) + return error; + if (info.is_directory) + return base::PLATFORM_FILE_ERROR_INVALID_OPERATION; + if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) { + error = NativeFileUtil::GetFileInfo(dest_path.DirName(), &info); + if (error != base::PLATFORM_FILE_OK) + return error; + if (!info.is_directory) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + } + + if (copy) { + if (base::CopyFile(src_path, dest_path)) + return base::PLATFORM_FILE_OK; + } else { + if (base::Move(src_path, dest_path)) + return base::PLATFORM_FILE_OK; + } + return base::PLATFORM_FILE_ERROR_FAILED; +} + +PlatformFileError NativeFileUtil::DeleteFile(const base::FilePath& path) { + if (!base::PathExists(path)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + if (base::DirectoryExists(path)) + return base::PLATFORM_FILE_ERROR_NOT_A_FILE; + if (!base::DeleteFile(path, false)) + return base::PLATFORM_FILE_ERROR_FAILED; + return base::PLATFORM_FILE_OK; +} + +PlatformFileError NativeFileUtil::DeleteDirectory(const base::FilePath& path) { + if (!base::PathExists(path)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + if (!base::DirectoryExists(path)) + return base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY; + if (!file_util::IsDirectoryEmpty(path)) + return base::PLATFORM_FILE_ERROR_NOT_EMPTY; + if (!base::DeleteFile(path, false)) + return base::PLATFORM_FILE_ERROR_FAILED; + return base::PLATFORM_FILE_OK; +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/native_file_util.h b/chromium/webkit/browser/fileapi/native_file_util.h new file mode 100644 index 00000000000..425e7421597 --- /dev/null +++ b/chromium/webkit/browser/fileapi/native_file_util.h @@ -0,0 +1,68 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_NATIVE_FILE_UTIL_H_ +#define WEBKIT_BROWSER_FILEAPI_NATIVE_FILE_UTIL_H_ + +#include "base/files/file_path.h" +#include "base/files/file_util_proxy.h" +#include "base/memory/scoped_ptr.h" +#include "base/platform_file.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace base { +class Time; +} + +namespace fileapi { + +// A thin wrapper class for accessing the OS native filesystem. +// This performs common error checks necessary to implement FileUtil family +// in addition to perform native filesystem operations. +// +// For the error checks it performs please see the comment for +// FileSystemFileUtil interface +// (webkit/browser/fileapi/file_system_file_util.h). +// +// Note that all the methods of this class are static and this does NOT +// inherit from FileSystemFileUtil. +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE NativeFileUtil { + public: + static base::PlatformFileError CreateOrOpen( + const base::FilePath& path, + int file_flags, + base::PlatformFile* file_handle, + bool* created); + static base::PlatformFileError Close(base::PlatformFile file); + static base::PlatformFileError EnsureFileExists(const base::FilePath& path, + bool* created); + static base::PlatformFileError CreateDirectory(const base::FilePath& path, + bool exclusive, + bool recursive); + static base::PlatformFileError GetFileInfo(const base::FilePath& path, + base::PlatformFileInfo* file_info); + static scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> + CreateFileEnumerator(const base::FilePath& root_path, + bool recursive); + static base::PlatformFileError Touch(const base::FilePath& path, + const base::Time& last_access_time, + const base::Time& last_modified_time); + static base::PlatformFileError Truncate(const base::FilePath& path, + int64 length); + static bool PathExists(const base::FilePath& path); + static bool DirectoryExists(const base::FilePath& path); + static base::PlatformFileError CopyOrMoveFile(const base::FilePath& src_path, + const base::FilePath& dest_path, + bool copy); + static base::PlatformFileError DeleteFile(const base::FilePath& path); + static base::PlatformFileError DeleteDirectory(const base::FilePath& path); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(NativeFileUtil); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_NATIVE_FILE_UTIL_H_ diff --git a/chromium/webkit/browser/fileapi/native_file_util_unittest.cc b/chromium/webkit/browser/fileapi/native_file_util_unittest.cc new file mode 100644 index 00000000000..1ed699e2e44 --- /dev/null +++ b/chromium/webkit/browser/fileapi/native_file_util_unittest.cc @@ -0,0 +1,341 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/platform_file.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/native_file_util.h" + +namespace fileapi { + +class NativeFileUtilTest : public testing::Test { + public: + NativeFileUtilTest() {} + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + } + + protected: + base::FilePath Path() { + return data_dir_.path(); + } + + base::FilePath Path(const char* file_name) { + return data_dir_.path().AppendASCII(file_name); + } + + bool FileExists(const base::FilePath& path) { + return base::PathExists(path) && + !base::DirectoryExists(path); + } + + int64 GetSize(const base::FilePath& path) { + base::PlatformFileInfo info; + file_util::GetFileInfo(path, &info); + return info.size; + } + + private: + base::ScopedTempDir data_dir_; + + DISALLOW_COPY_AND_ASSIGN(NativeFileUtilTest); +}; + +TEST_F(NativeFileUtilTest, CreateCloseAndDeleteFile) { + base::FilePath file_name = Path("test_file"); + base::PlatformFile file_handle; + bool created = false; + int flags = base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC; + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::CreateOrOpen(file_name, + base::PLATFORM_FILE_CREATE | flags, + &file_handle, &created)); + ASSERT_TRUE(created); + + EXPECT_TRUE(base::PathExists(file_name)); + EXPECT_TRUE(NativeFileUtil::PathExists(file_name)); + EXPECT_EQ(0, GetSize(file_name)); + EXPECT_NE(base::kInvalidPlatformFileValue, file_handle); + + ASSERT_EQ(base::PLATFORM_FILE_OK, NativeFileUtil::Close(file_handle)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::CreateOrOpen(file_name, + base::PLATFORM_FILE_OPEN | flags, + &file_handle, &created)); + ASSERT_FALSE(created); + ASSERT_EQ(base::PLATFORM_FILE_OK, NativeFileUtil::Close(file_handle)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::DeleteFile(file_name)); + EXPECT_FALSE(base::PathExists(file_name)); + EXPECT_FALSE(NativeFileUtil::PathExists(file_name)); +} + +TEST_F(NativeFileUtilTest, EnsureFileExists) { + base::FilePath file_name = Path("foobar"); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::EnsureFileExists(file_name, &created)); + ASSERT_TRUE(created); + + EXPECT_TRUE(FileExists(file_name)); + EXPECT_EQ(0, GetSize(file_name)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::EnsureFileExists(file_name, &created)); + EXPECT_FALSE(created); +} + +TEST_F(NativeFileUtilTest, CreateAndDeleteDirectory) { + base::FilePath dir_name = Path("test_dir"); + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::CreateDirectory(dir_name, + false /* exclusive */, + false /* recursive */)); + + EXPECT_TRUE(NativeFileUtil::DirectoryExists(dir_name)); + EXPECT_TRUE(base::DirectoryExists(dir_name)); + + ASSERT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, + NativeFileUtil::CreateDirectory(dir_name, + true /* exclusive */, + false /* recursive */)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::DeleteDirectory(dir_name)); + EXPECT_FALSE(base::DirectoryExists(dir_name)); + EXPECT_FALSE(NativeFileUtil::DirectoryExists(dir_name)); +} + +TEST_F(NativeFileUtilTest, TouchFileAndGetFileInfo) { + base::FilePath file_name = Path("test_file"); + base::PlatformFileInfo native_info; + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + NativeFileUtil::GetFileInfo(file_name, &native_info)); + + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::EnsureFileExists(file_name, &created)); + ASSERT_TRUE(created); + + base::PlatformFileInfo info; + ASSERT_TRUE(file_util::GetFileInfo(file_name, &info)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::GetFileInfo(file_name, &native_info)); + ASSERT_EQ(info.size, native_info.size); + ASSERT_EQ(info.is_directory, native_info.is_directory); + ASSERT_EQ(info.is_symbolic_link, native_info.is_symbolic_link); + ASSERT_EQ(info.last_modified, native_info.last_modified); + ASSERT_EQ(info.last_accessed, native_info.last_accessed); + ASSERT_EQ(info.creation_time, native_info.creation_time); + + const base::Time new_accessed = + info.last_accessed + base::TimeDelta::FromHours(10); + const base::Time new_modified = + info.last_modified + base::TimeDelta::FromHours(5); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::Touch(file_name, + new_accessed, new_modified)); + + ASSERT_TRUE(file_util::GetFileInfo(file_name, &info)); + EXPECT_EQ(new_accessed, info.last_accessed); + EXPECT_EQ(new_modified, info.last_modified); +} + +TEST_F(NativeFileUtilTest, CreateFileEnumerator) { + base::FilePath path_1 = Path("dir1"); + base::FilePath path_2 = Path("file1"); + base::FilePath path_11 = Path("dir1").AppendASCII("file11"); + base::FilePath path_12 = Path("dir1").AppendASCII("dir12"); + base::FilePath path_121 = + Path("dir1").AppendASCII("dir12").AppendASCII("file121"); + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::CreateDirectory(path_1, false, false)); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::EnsureFileExists(path_2, &created)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::EnsureFileExists(path_11, &created)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::CreateDirectory(path_12, false, false)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::EnsureFileExists(path_121, &created)); + + { + scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator = + NativeFileUtil::CreateFileEnumerator(Path(), false); + std::set<base::FilePath> set; + set.insert(path_1); + set.insert(path_2); + for (base::FilePath path = enumerator->Next(); !path.empty(); + path = enumerator->Next()) + EXPECT_EQ(1U, set.erase(path)); + EXPECT_TRUE(set.empty()); + } + + { + scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator = + NativeFileUtil::CreateFileEnumerator(Path(), true); + std::set<base::FilePath> set; + set.insert(path_1); + set.insert(path_2); + set.insert(path_11); + set.insert(path_12); + set.insert(path_121); + for (base::FilePath path = enumerator->Next(); !path.empty(); + path = enumerator->Next()) + EXPECT_EQ(1U, set.erase(path)); + EXPECT_TRUE(set.empty()); + } +} + +TEST_F(NativeFileUtilTest, Truncate) { + base::FilePath file_name = Path("truncated"); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::EnsureFileExists(file_name, &created)); + ASSERT_TRUE(created); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::Truncate(file_name, 1020)); + + EXPECT_TRUE(FileExists(file_name)); + EXPECT_EQ(1020, GetSize(file_name)); +} + +TEST_F(NativeFileUtilTest, CopyFile) { + base::FilePath from_file = Path("fromfile"); + base::FilePath to_file1 = Path("tofile1"); + base::FilePath to_file2 = Path("tofile2"); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::Truncate(from_file, 1020)); + + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::CopyOrMoveFile(from_file, to_file1, true)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::CopyOrMoveFile(from_file, to_file2, true)); + + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + EXPECT_TRUE(FileExists(to_file1)); + EXPECT_EQ(1020, GetSize(to_file1)); + EXPECT_TRUE(FileExists(to_file2)); + EXPECT_EQ(1020, GetSize(to_file2)); + + base::FilePath dir = Path("dir"); + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::CreateDirectory(dir, false, false)); + ASSERT_TRUE(base::DirectoryExists(dir)); + base::FilePath to_dir_file = dir.AppendASCII("file"); + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::CopyOrMoveFile(from_file, to_dir_file, true)); + EXPECT_TRUE(FileExists(to_dir_file)); + EXPECT_EQ(1020, GetSize(to_dir_file)); + + // Following tests are error checking. + // Source doesn't exist. + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + NativeFileUtil::CopyOrMoveFile(Path("nonexists"), Path("file"), + true)); + + // Source is not a file. + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_FILE, + NativeFileUtil::CopyOrMoveFile(dir, Path("file"), true)); + // Destination is not a file. + EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, + NativeFileUtil::CopyOrMoveFile(from_file, dir, true)); + // Destination's parent doesn't exist. + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + NativeFileUtil::CopyOrMoveFile(from_file, + Path("nodir").AppendASCII("file"), + true)); + // Destination's parent is a file. + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + NativeFileUtil::CopyOrMoveFile(from_file, + Path("tofile1").AppendASCII("file"), + true)); +} + +TEST_F(NativeFileUtilTest, MoveFile) { + base::FilePath from_file = Path("fromfile"); + base::FilePath to_file = Path("tofile"); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::EnsureFileExists(from_file, &created)); + ASSERT_TRUE(created); + + ASSERT_EQ(base::PLATFORM_FILE_OK, NativeFileUtil::Truncate(from_file, 1020)); + + EXPECT_TRUE(FileExists(from_file)); + EXPECT_EQ(1020, GetSize(from_file)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::CopyOrMoveFile(from_file, to_file, false)); + + EXPECT_FALSE(FileExists(from_file)); + EXPECT_TRUE(FileExists(to_file)); + EXPECT_EQ(1020, GetSize(to_file)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::EnsureFileExists(from_file, &created)); + ASSERT_TRUE(FileExists(from_file)); + ASSERT_EQ(base::PLATFORM_FILE_OK, NativeFileUtil::Truncate(from_file, 1020)); + + base::FilePath dir = Path("dir"); + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::CreateDirectory(dir, false, false)); + ASSERT_TRUE(base::DirectoryExists(dir)); + base::FilePath to_dir_file = dir.AppendASCII("file"); + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::CopyOrMoveFile(from_file, to_dir_file, false)); + EXPECT_FALSE(FileExists(from_file)); + EXPECT_TRUE(FileExists(to_dir_file)); + EXPECT_EQ(1020, GetSize(to_dir_file)); + + // Following is error checking. + // Source doesn't exist. + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + NativeFileUtil::CopyOrMoveFile(Path("nonexists"), Path("file"), + false)); + + // Source is not a file. + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_FILE, + NativeFileUtil::CopyOrMoveFile(dir, Path("file"), false)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::EnsureFileExists(from_file, &created)); + ASSERT_TRUE(FileExists(from_file)); + // Destination is not a file. + EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, + NativeFileUtil::CopyOrMoveFile(from_file, dir, false)); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + NativeFileUtil::EnsureFileExists(from_file, &created)); + ASSERT_TRUE(FileExists(from_file)); + // Destination's parent doesn't exist. + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + NativeFileUtil::CopyOrMoveFile(from_file, + Path("nodir").AppendASCII("file"), + false)); + // Destination's parent is a file. + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + NativeFileUtil::CopyOrMoveFile(from_file, + Path("tofile1").AppendASCII("file"), + false)); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/obfuscated_file_util.cc b/chromium/webkit/browser/fileapi/obfuscated_file_util.cc new file mode 100644 index 00000000000..7e2f7df12a8 --- /dev/null +++ b/chromium/webkit/browser/fileapi/obfuscated_file_util.cc @@ -0,0 +1,1444 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/obfuscated_file_util.h" + +#include <queue> +#include <string> +#include <vector> + +#include "base/file_util.h" +#include "base/format_macros.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/sys_string_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/file_observers.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/native_file_util.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend.h" +#include "webkit/browser/fileapi/sandbox_isolated_origin_database.h" +#include "webkit/browser/fileapi/sandbox_origin_database.h" +#include "webkit/browser/fileapi/timed_task_helper.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/database/database_identifier.h" +#include "webkit/common/fileapi/file_system_util.h" + +// Example of various paths: +// void ObfuscatedFileUtil::DoSomething(const FileSystemURL& url) { +// base::FilePath virtual_path = url.path(); +// base::FilePath local_path = GetLocalFilePath(url); +// +// NativeFileUtil::DoSomething(local_path); +// file_util::DoAnother(local_path); +// } + +namespace fileapi { + +namespace { + +typedef SandboxDirectoryDatabase::FileId FileId; +typedef SandboxDirectoryDatabase::FileInfo FileInfo; + +void InitFileInfo( + SandboxDirectoryDatabase::FileInfo* file_info, + SandboxDirectoryDatabase::FileId parent_id, + const base::FilePath::StringType& file_name) { + DCHECK(file_info); + file_info->parent_id = parent_id; + file_info->name = file_name; +} + +// Costs computed as per crbug.com/86114, based on the LevelDB implementation of +// path storage under Linux. It's not clear if that will differ on Windows, on +// which base::FilePath uses wide chars [since they're converted to UTF-8 for storage +// anyway], but as long as the cost is high enough that one can't cheat on quota +// by storing data in paths, it doesn't need to be all that accurate. +const int64 kPathCreationQuotaCost = 146; // Bytes per inode, basically. +const int64 kPathByteQuotaCost = 2; // Bytes per byte of path length in UTF-8. + +int64 UsageForPath(size_t length) { + return kPathCreationQuotaCost + + static_cast<int64>(length) * kPathByteQuotaCost; +} + +bool AllocateQuota(FileSystemOperationContext* context, int64 growth) { + if (context->allowed_bytes_growth() == quota::QuotaManager::kNoLimit) + return true; + + int64 new_quota = context->allowed_bytes_growth() - growth; + if (growth > 0 && new_quota < 0) + return false; + context->set_allowed_bytes_growth(new_quota); + return true; +} + +void UpdateUsage( + FileSystemOperationContext* context, + const FileSystemURL& url, + int64 growth) { + context->update_observers()->Notify( + &FileUpdateObserver::OnUpdate, MakeTuple(url, growth)); +} + +void TouchDirectory(SandboxDirectoryDatabase* db, FileId dir_id) { + DCHECK(db); + if (!db->UpdateModificationTime(dir_id, base::Time::Now())) + NOTREACHED(); +} + +const base::FilePath::CharType kTemporaryDirectoryName[] = FILE_PATH_LITERAL("t"); +const base::FilePath::CharType kPersistentDirectoryName[] = FILE_PATH_LITERAL("p"); +const base::FilePath::CharType kSyncableDirectoryName[] = FILE_PATH_LITERAL("s"); + +} // namespace + +using base::PlatformFile; +using base::PlatformFileError; + +class ObfuscatedFileEnumerator + : public FileSystemFileUtil::AbstractFileEnumerator { + public: + ObfuscatedFileEnumerator( + SandboxDirectoryDatabase* db, + FileSystemOperationContext* context, + ObfuscatedFileUtil* obfuscated_file_util, + const FileSystemURL& root_url, + bool recursive) + : db_(db), + context_(context), + obfuscated_file_util_(obfuscated_file_util), + origin_(root_url.origin()), + type_(root_url.type()), + recursive_(recursive), + current_file_id_(0) { + base::FilePath root_virtual_path = root_url.path(); + FileId file_id; + + if (!db_->GetFileWithPath(root_virtual_path, &file_id)) + return; + + FileRecord record = { file_id, root_virtual_path }; + recurse_queue_.push(record); + } + + virtual ~ObfuscatedFileEnumerator() {} + + virtual base::FilePath Next() OVERRIDE { + ProcessRecurseQueue(); + if (display_stack_.empty()) + return base::FilePath(); + + current_file_id_ = display_stack_.back(); + display_stack_.pop_back(); + + FileInfo file_info; + base::FilePath platform_file_path; + base::PlatformFileError error = + obfuscated_file_util_->GetFileInfoInternal( + db_, context_, origin_, type_, current_file_id_, + &file_info, ¤t_platform_file_info_, &platform_file_path); + if (error != base::PLATFORM_FILE_OK) + return Next(); + + base::FilePath virtual_path = + current_parent_virtual_path_.Append(file_info.name); + if (recursive_ && file_info.is_directory()) { + FileRecord record = { current_file_id_, virtual_path }; + recurse_queue_.push(record); + } + return virtual_path; + } + + virtual int64 Size() OVERRIDE { + return current_platform_file_info_.size; + } + + virtual base::Time LastModifiedTime() OVERRIDE { + return current_platform_file_info_.last_modified; + } + + virtual bool IsDirectory() OVERRIDE { + return current_platform_file_info_.is_directory; + } + + private: + typedef SandboxDirectoryDatabase::FileId FileId; + typedef SandboxDirectoryDatabase::FileInfo FileInfo; + + struct FileRecord { + FileId file_id; + base::FilePath virtual_path; + }; + + void ProcessRecurseQueue() { + while (display_stack_.empty() && !recurse_queue_.empty()) { + FileRecord entry = recurse_queue_.front(); + recurse_queue_.pop(); + if (!db_->ListChildren(entry.file_id, &display_stack_)) { + display_stack_.clear(); + return; + } + current_parent_virtual_path_ = entry.virtual_path; + } + } + + SandboxDirectoryDatabase* db_; + FileSystemOperationContext* context_; + ObfuscatedFileUtil* obfuscated_file_util_; + GURL origin_; + FileSystemType type_; + bool recursive_; + + std::queue<FileRecord> recurse_queue_; + std::vector<FileId> display_stack_; + base::FilePath current_parent_virtual_path_; + + FileId current_file_id_; + base::PlatformFileInfo current_platform_file_info_; +}; + +class ObfuscatedOriginEnumerator + : public ObfuscatedFileUtil::AbstractOriginEnumerator { + public: + typedef SandboxOriginDatabase::OriginRecord OriginRecord; + ObfuscatedOriginEnumerator( + SandboxOriginDatabaseInterface* origin_database, + const base::FilePath& base_file_path) + : base_file_path_(base_file_path) { + if (origin_database) + origin_database->ListAllOrigins(&origins_); + } + + virtual ~ObfuscatedOriginEnumerator() {} + + // Returns the next origin. Returns empty if there are no more origins. + virtual GURL Next() OVERRIDE { + OriginRecord record; + if (!origins_.empty()) { + record = origins_.back(); + origins_.pop_back(); + } + current_ = record; + return webkit_database::GetOriginFromIdentifier(record.origin); + } + + // Returns the current origin's information. + virtual bool HasFileSystemType(FileSystemType type) const OVERRIDE { + if (current_.path.empty()) + return false; + base::FilePath::StringType type_string = + ObfuscatedFileUtil::GetDirectoryNameForType(type); + if (type_string.empty()) { + NOTREACHED(); + return false; + } + base::FilePath path = + base_file_path_.Append(current_.path).Append(type_string); + return base::DirectoryExists(path); + } + + private: + std::vector<OriginRecord> origins_; + OriginRecord current_; + base::FilePath base_file_path_; +}; + +ObfuscatedFileUtil::ObfuscatedFileUtil( + quota::SpecialStoragePolicy* special_storage_policy, + const base::FilePath& file_system_directory, + base::SequencedTaskRunner* file_task_runner) + : special_storage_policy_(special_storage_policy), + file_system_directory_(file_system_directory), + db_flush_delay_seconds_(10 * 60), // 10 mins. + file_task_runner_(file_task_runner) { +} + +ObfuscatedFileUtil::~ObfuscatedFileUtil() { + DropDatabases(); +} + +PlatformFileError ObfuscatedFileUtil::CreateOrOpen( + FileSystemOperationContext* context, + const FileSystemURL& url, int file_flags, + PlatformFile* file_handle, bool* created) { + PlatformFileError error = CreateOrOpenInternal(context, url, file_flags, + file_handle, created); + if (*file_handle != base::kInvalidPlatformFileValue && + file_flags & base::PLATFORM_FILE_WRITE && + context->quota_limit_type() == quota::kQuotaLimitTypeUnlimited) { + DCHECK_EQ(base::PLATFORM_FILE_OK, error); + context->file_system_context()->sandbox_delegate()-> + StickyInvalidateUsageCache(url.origin(), url.type()); + } + return error; +} + +PlatformFileError ObfuscatedFileUtil::Close( + FileSystemOperationContext* context, + base::PlatformFile file) { + return NativeFileUtil::Close(file); +} + +PlatformFileError ObfuscatedFileUtil::EnsureFileExists( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool* created) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase( + url.origin(), url.type(), true); + if (!db) + return base::PLATFORM_FILE_ERROR_FAILED; + + FileId file_id; + if (db->GetFileWithPath(url.path(), &file_id)) { + FileInfo file_info; + if (!db->GetFileInfo(file_id, &file_info)) { + NOTREACHED(); + return base::PLATFORM_FILE_ERROR_FAILED; + } + if (file_info.is_directory()) + return base::PLATFORM_FILE_ERROR_NOT_A_FILE; + if (created) + *created = false; + return base::PLATFORM_FILE_OK; + } + FileId parent_id; + if (!db->GetFileWithPath(VirtualPath::DirName(url.path()), &parent_id)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + + FileInfo file_info; + InitFileInfo(&file_info, parent_id, + VirtualPath::BaseName(url.path()).value()); + + int64 growth = UsageForPath(file_info.name.size()); + if (!AllocateQuota(context, growth)) + return base::PLATFORM_FILE_ERROR_NO_SPACE; + PlatformFileError error = CreateFile( + context, base::FilePath(), url.origin(), url.type(), &file_info, 0, NULL); + if (created && base::PLATFORM_FILE_OK == error) { + *created = true; + UpdateUsage(context, url, growth); + context->change_observers()->Notify( + &FileChangeObserver::OnCreateFile, MakeTuple(url)); + } + return error; +} + +PlatformFileError ObfuscatedFileUtil::CreateDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool exclusive, + bool recursive) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase( + url.origin(), url.type(), true); + if (!db) + return base::PLATFORM_FILE_ERROR_FAILED; + + FileId file_id; + if (db->GetFileWithPath(url.path(), &file_id)) { + FileInfo file_info; + if (exclusive) + return base::PLATFORM_FILE_ERROR_EXISTS; + if (!db->GetFileInfo(file_id, &file_info)) { + NOTREACHED(); + return base::PLATFORM_FILE_ERROR_FAILED; + } + if (!file_info.is_directory()) + return base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY; + return base::PLATFORM_FILE_OK; + } + + std::vector<base::FilePath::StringType> components; + VirtualPath::GetComponents(url.path(), &components); + FileId parent_id = 0; + size_t index; + for (index = 0; index < components.size(); ++index) { + base::FilePath::StringType name = components[index]; + if (name == FILE_PATH_LITERAL("/")) + continue; + if (!db->GetChildWithName(parent_id, name, &parent_id)) + break; + } + if (!recursive && components.size() - index > 1) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + bool first = true; + for (; index < components.size(); ++index) { + FileInfo file_info; + file_info.name = components[index]; + if (file_info.name == FILE_PATH_LITERAL("/")) + continue; + file_info.modification_time = base::Time::Now(); + file_info.parent_id = parent_id; + int64 growth = UsageForPath(file_info.name.size()); + if (!AllocateQuota(context, growth)) + return base::PLATFORM_FILE_ERROR_NO_SPACE; + if (!db->AddFileInfo(file_info, &parent_id)) { + NOTREACHED(); + return base::PLATFORM_FILE_ERROR_FAILED; + } + UpdateUsage(context, url, growth); + context->change_observers()->Notify( + &FileChangeObserver::OnCreateDirectory, MakeTuple(url)); + if (first) { + first = false; + TouchDirectory(db, file_info.parent_id); + } + } + return base::PLATFORM_FILE_OK; +} + +PlatformFileError ObfuscatedFileUtil::GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::PlatformFileInfo* file_info, + base::FilePath* platform_file_path) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase( + url.origin(), url.type(), false); + if (!db) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + FileId file_id; + if (!db->GetFileWithPath(url.path(), &file_id)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + FileInfo local_info; + return GetFileInfoInternal(db, context, + url.origin(), url.type(), + file_id, &local_info, + file_info, platform_file_path); +} + +scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> + ObfuscatedFileUtil::CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url) { + return CreateFileEnumerator(context, root_url, false /* recursive */); +} + +PlatformFileError ObfuscatedFileUtil::GetLocalFilePath( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::FilePath* local_path) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase( + url.origin(), url.type(), false); + if (!db) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + FileId file_id; + if (!db->GetFileWithPath(url.path(), &file_id)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + FileInfo file_info; + if (!db->GetFileInfo(file_id, &file_info) || file_info.is_directory()) { + NOTREACHED(); + // Directories have no local file path. + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + } + *local_path = DataPathToLocalPath( + url.origin(), url.type(), file_info.data_path); + + if (local_path->empty()) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + return base::PLATFORM_FILE_OK; +} + +PlatformFileError ObfuscatedFileUtil::Touch( + FileSystemOperationContext* context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase( + url.origin(), url.type(), false); + if (!db) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + FileId file_id; + if (!db->GetFileWithPath(url.path(), &file_id)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + + FileInfo file_info; + if (!db->GetFileInfo(file_id, &file_info)) { + NOTREACHED(); + return base::PLATFORM_FILE_ERROR_FAILED; + } + if (file_info.is_directory()) { + if (!db->UpdateModificationTime(file_id, last_modified_time)) + return base::PLATFORM_FILE_ERROR_FAILED; + return base::PLATFORM_FILE_OK; + } + base::FilePath local_path = DataPathToLocalPath( + url.origin(), url.type(), file_info.data_path); + return NativeFileUtil::Touch( + local_path, last_access_time, last_modified_time); +} + +PlatformFileError ObfuscatedFileUtil::Truncate( + FileSystemOperationContext* context, + const FileSystemURL& url, + int64 length) { + base::PlatformFileInfo file_info; + base::FilePath local_path; + base::PlatformFileError error = + GetFileInfo(context, url, &file_info, &local_path); + if (error != base::PLATFORM_FILE_OK) + return error; + + int64 growth = length - file_info.size; + if (!AllocateQuota(context, growth)) + return base::PLATFORM_FILE_ERROR_NO_SPACE; + error = NativeFileUtil::Truncate(local_path, length); + if (error == base::PLATFORM_FILE_OK) { + UpdateUsage(context, url, growth); + context->change_observers()->Notify( + &FileChangeObserver::OnModifyFile, MakeTuple(url)); + } + return error; +} + +PlatformFileError ObfuscatedFileUtil::CopyOrMoveFile( + FileSystemOperationContext* context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + bool copy) { + // Cross-filesystem copies and moves should be handled via CopyInForeignFile. + DCHECK(src_url.origin() == dest_url.origin()); + DCHECK(src_url.type() == dest_url.type()); + + SandboxDirectoryDatabase* db = GetDirectoryDatabase( + src_url.origin(), src_url.type(), true); + if (!db) + return base::PLATFORM_FILE_ERROR_FAILED; + + FileId src_file_id; + if (!db->GetFileWithPath(src_url.path(), &src_file_id)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + + FileId dest_file_id; + bool overwrite = db->GetFileWithPath(dest_url.path(), + &dest_file_id); + + FileInfo src_file_info; + base::PlatformFileInfo src_platform_file_info; + base::FilePath src_local_path; + base::PlatformFileError error = GetFileInfoInternal( + db, context, src_url.origin(), src_url.type(), src_file_id, + &src_file_info, &src_platform_file_info, &src_local_path); + if (error != base::PLATFORM_FILE_OK) + return error; + if (src_file_info.is_directory()) + return base::PLATFORM_FILE_ERROR_NOT_A_FILE; + + FileInfo dest_file_info; + base::PlatformFileInfo dest_platform_file_info; // overwrite case only + base::FilePath dest_local_path; // overwrite case only + if (overwrite) { + base::PlatformFileError error = GetFileInfoInternal( + db, context, dest_url.origin(), dest_url.type(), dest_file_id, + &dest_file_info, &dest_platform_file_info, &dest_local_path); + if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) + overwrite = false; // fallback to non-overwrite case + else if (error != base::PLATFORM_FILE_OK) + return error; + else if (dest_file_info.is_directory()) + return base::PLATFORM_FILE_ERROR_INVALID_OPERATION; + } + if (!overwrite) { + FileId dest_parent_id; + if (!db->GetFileWithPath(VirtualPath::DirName(dest_url.path()), + &dest_parent_id)) { + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + } + + dest_file_info = src_file_info; + dest_file_info.parent_id = dest_parent_id; + dest_file_info.name = + VirtualPath::BaseName(dest_url.path()).value(); + } + + int64 growth = 0; + if (copy) + growth += src_platform_file_info.size; + else + growth -= UsageForPath(src_file_info.name.size()); + if (overwrite) + growth -= dest_platform_file_info.size; + else + growth += UsageForPath(dest_file_info.name.size()); + if (!AllocateQuota(context, growth)) + return base::PLATFORM_FILE_ERROR_NO_SPACE; + + /* + * Copy-with-overwrite + * Just overwrite data file + * Copy-without-overwrite + * Copy backing file + * Create new metadata pointing to new backing file. + * Move-with-overwrite + * transaction: + * Remove source entry. + * Point target entry to source entry's backing file. + * Delete target entry's old backing file + * Move-without-overwrite + * Just update metadata + */ + error = base::PLATFORM_FILE_ERROR_FAILED; + if (copy) { + if (overwrite) { + error = NativeFileUtil::CopyOrMoveFile( + src_local_path, + dest_local_path, + true /* copy */); + } else { // non-overwrite + error = CreateFile(context, src_local_path, + dest_url.origin(), dest_url.type(), + &dest_file_info, 0, NULL); + } + } else { + if (overwrite) { + if (db->OverwritingMoveFile(src_file_id, dest_file_id)) { + if (base::PLATFORM_FILE_OK != + NativeFileUtil::DeleteFile(dest_local_path)) + LOG(WARNING) << "Leaked a backing file."; + error = base::PLATFORM_FILE_OK; + } else { + error = base::PLATFORM_FILE_ERROR_FAILED; + } + } else { // non-overwrite + if (db->UpdateFileInfo(src_file_id, dest_file_info)) + error = base::PLATFORM_FILE_OK; + else + error = base::PLATFORM_FILE_ERROR_FAILED; + } + } + + if (error != base::PLATFORM_FILE_OK) + return error; + + if (overwrite) { + context->change_observers()->Notify( + &FileChangeObserver::OnModifyFile, + MakeTuple(dest_url)); + } else { + context->change_observers()->Notify( + &FileChangeObserver::OnCreateFileFrom, + MakeTuple(dest_url, src_url)); + } + + if (!copy) { + context->change_observers()->Notify( + &FileChangeObserver::OnRemoveFile, MakeTuple(src_url)); + TouchDirectory(db, src_file_info.parent_id); + } + + TouchDirectory(db, dest_file_info.parent_id); + + UpdateUsage(context, dest_url, growth); + return error; +} + +PlatformFileError ObfuscatedFileUtil::CopyInForeignFile( + FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase( + dest_url.origin(), dest_url.type(), true); + if (!db) + return base::PLATFORM_FILE_ERROR_FAILED; + + base::PlatformFileInfo src_platform_file_info; + if (!file_util::GetFileInfo(src_file_path, &src_platform_file_info)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + + FileId dest_file_id; + bool overwrite = db->GetFileWithPath(dest_url.path(), + &dest_file_id); + + FileInfo dest_file_info; + base::PlatformFileInfo dest_platform_file_info; // overwrite case only + if (overwrite) { + base::FilePath dest_local_path; + base::PlatformFileError error = GetFileInfoInternal( + db, context, dest_url.origin(), dest_url.type(), dest_file_id, + &dest_file_info, &dest_platform_file_info, &dest_local_path); + if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) + overwrite = false; // fallback to non-overwrite case + else if (error != base::PLATFORM_FILE_OK) + return error; + else if (dest_file_info.is_directory()) + return base::PLATFORM_FILE_ERROR_INVALID_OPERATION; + } + if (!overwrite) { + FileId dest_parent_id; + if (!db->GetFileWithPath(VirtualPath::DirName(dest_url.path()), + &dest_parent_id)) { + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + } + if (!dest_file_info.is_directory()) + return base::PLATFORM_FILE_ERROR_FAILED; + InitFileInfo(&dest_file_info, dest_parent_id, + VirtualPath::BaseName(dest_url.path()).value()); + } + + int64 growth = src_platform_file_info.size; + if (overwrite) + growth -= dest_platform_file_info.size; + else + growth += UsageForPath(dest_file_info.name.size()); + if (!AllocateQuota(context, growth)) + return base::PLATFORM_FILE_ERROR_NO_SPACE; + + base::PlatformFileError error; + if (overwrite) { + base::FilePath dest_local_path = DataPathToLocalPath( + dest_url.origin(), dest_url.type(), dest_file_info.data_path); + error = NativeFileUtil::CopyOrMoveFile( + src_file_path, dest_local_path, true); + } else { + error = CreateFile(context, src_file_path, + dest_url.origin(), dest_url.type(), + &dest_file_info, 0, NULL); + } + + if (error != base::PLATFORM_FILE_OK) + return error; + + if (overwrite) { + context->change_observers()->Notify( + &FileChangeObserver::OnModifyFile, MakeTuple(dest_url)); + } else { + context->change_observers()->Notify( + &FileChangeObserver::OnCreateFile, MakeTuple(dest_url)); + } + + UpdateUsage(context, dest_url, growth); + TouchDirectory(db, dest_file_info.parent_id); + return base::PLATFORM_FILE_OK; +} + +PlatformFileError ObfuscatedFileUtil::DeleteFile( + FileSystemOperationContext* context, + const FileSystemURL& url) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase( + url.origin(), url.type(), true); + if (!db) + return base::PLATFORM_FILE_ERROR_FAILED; + FileId file_id; + if (!db->GetFileWithPath(url.path(), &file_id)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + + FileInfo file_info; + base::PlatformFileInfo platform_file_info; + base::FilePath local_path; + base::PlatformFileError error = GetFileInfoInternal( + db, context, url.origin(), url.type(), file_id, + &file_info, &platform_file_info, &local_path); + if (error != base::PLATFORM_FILE_ERROR_NOT_FOUND && + error != base::PLATFORM_FILE_OK) + return error; + + if (file_info.is_directory()) + return base::PLATFORM_FILE_ERROR_NOT_A_FILE; + + int64 growth = -UsageForPath(file_info.name.size()) - platform_file_info.size; + AllocateQuota(context, growth); + if (!db->RemoveFileInfo(file_id)) { + NOTREACHED(); + return base::PLATFORM_FILE_ERROR_FAILED; + } + UpdateUsage(context, url, growth); + TouchDirectory(db, file_info.parent_id); + + context->change_observers()->Notify( + &FileChangeObserver::OnRemoveFile, MakeTuple(url)); + + if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) + return base::PLATFORM_FILE_OK; + + error = NativeFileUtil::DeleteFile(local_path); + if (base::PLATFORM_FILE_OK != error) + LOG(WARNING) << "Leaked a backing file."; + return base::PLATFORM_FILE_OK; +} + +PlatformFileError ObfuscatedFileUtil::DeleteDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase( + url.origin(), url.type(), true); + if (!db) + return base::PLATFORM_FILE_ERROR_FAILED; + + FileId file_id; + if (!db->GetFileWithPath(url.path(), &file_id)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + FileInfo file_info; + if (!db->GetFileInfo(file_id, &file_info)) { + NOTREACHED(); + return base::PLATFORM_FILE_ERROR_FAILED; + } + if (!file_info.is_directory()) + return base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY; + if (!db->RemoveFileInfo(file_id)) + return base::PLATFORM_FILE_ERROR_NOT_EMPTY; + int64 growth = -UsageForPath(file_info.name.size()); + AllocateQuota(context, growth); + UpdateUsage(context, url, growth); + TouchDirectory(db, file_info.parent_id); + context->change_observers()->Notify( + &FileChangeObserver::OnRemoveDirectory, MakeTuple(url)); + return base::PLATFORM_FILE_OK; +} + +webkit_blob::ScopedFile ObfuscatedFileUtil::CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::PlatformFileError* error, + base::PlatformFileInfo* file_info, + base::FilePath* platform_path) { + // We're just returning the local file information. + *error = GetFileInfo(context, url, file_info, platform_path); + if (*error == base::PLATFORM_FILE_OK && file_info->is_directory) { + *file_info = base::PlatformFileInfo(); + *error = base::PLATFORM_FILE_ERROR_NOT_A_FILE; + } + return webkit_blob::ScopedFile(); +} + +scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> + ObfuscatedFileUtil::CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url, + bool recursive) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase( + root_url.origin(), root_url.type(), false); + if (!db) { + return scoped_ptr<AbstractFileEnumerator>(new EmptyFileEnumerator()); + } + return scoped_ptr<AbstractFileEnumerator>( + new ObfuscatedFileEnumerator(db, context, this, root_url, recursive)); +} + +bool ObfuscatedFileUtil::IsDirectoryEmpty( + FileSystemOperationContext* context, + const FileSystemURL& url) { + SandboxDirectoryDatabase* db = GetDirectoryDatabase( + url.origin(), url.type(), false); + if (!db) + return true; // Not a great answer, but it's what others do. + FileId file_id; + if (!db->GetFileWithPath(url.path(), &file_id)) + return true; // Ditto. + FileInfo file_info; + if (!db->GetFileInfo(file_id, &file_info)) { + DCHECK(!file_id); + // It's the root directory and the database hasn't been initialized yet. + return true; + } + if (!file_info.is_directory()) + return true; + std::vector<FileId> children; + // TODO(ericu): This could easily be made faster with help from the database. + if (!db->ListChildren(file_id, &children)) + return true; + return children.empty(); +} + +base::FilePath ObfuscatedFileUtil::GetDirectoryForOriginAndType( + const GURL& origin, + FileSystemType type, + bool create, + base::PlatformFileError* error_code) { + base::FilePath origin_dir = GetDirectoryForOrigin(origin, create, error_code); + if (origin_dir.empty()) + return base::FilePath(); + base::FilePath::StringType type_string = GetDirectoryNameForType(type); + if (type_string.empty()) { + LOG(WARNING) << "Unknown filesystem type requested:" << type; + + if (error_code) + *error_code = base::PLATFORM_FILE_ERROR_INVALID_URL; + return base::FilePath(); + } + base::FilePath path = origin_dir.Append(type_string); + base::PlatformFileError error = base::PLATFORM_FILE_OK; + if (!base::DirectoryExists(path) && + (!create || !file_util::CreateDirectory(path))) { + error = create ? + base::PLATFORM_FILE_ERROR_FAILED : + base::PLATFORM_FILE_ERROR_NOT_FOUND; + } + + if (error_code) + *error_code = error; + return path; +} + +bool ObfuscatedFileUtil::DeleteDirectoryForOriginAndType( + const GURL& origin, FileSystemType type) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + base::FilePath origin_type_path = GetDirectoryForOriginAndType( + origin, type, false, &error); + if (origin_type_path.empty()) + return true; + + if (error != base::PLATFORM_FILE_ERROR_NOT_FOUND) { + // TODO(dmikurube): Consider the return value of DestroyDirectoryDatabase. + // We ignore its error now since 1) it doesn't matter the final result, and + // 2) it always returns false in Windows because of LevelDB's + // implementation. + // Information about failure would be useful for debugging. + DestroyDirectoryDatabase(origin, type); + if (!base::DeleteFile(origin_type_path, true /* recursive */)) + return false; + } + + base::FilePath origin_path = VirtualPath::DirName(origin_type_path); + DCHECK_EQ(origin_path.value(), + GetDirectoryForOrigin(origin, false, NULL).value()); + + // At this point we are sure we had successfully deleted the origin/type + // directory (i.e. we're ready to just return true). + // See if we have other directories in this origin directory. + std::vector<FileSystemType> other_types; + if (type != kFileSystemTypeTemporary) + other_types.push_back(kFileSystemTypeTemporary); + if (type != kFileSystemTypePersistent) + other_types.push_back(kFileSystemTypePersistent); + if (type != kFileSystemTypeSyncable) + other_types.push_back(kFileSystemTypeSyncable); + DCHECK(type != kFileSystemTypeSyncableForInternalSync); + + for (size_t i = 0; i < other_types.size(); ++i) { + if (base::DirectoryExists( + origin_path.Append(GetDirectoryNameForType(other_types[i])))) { + // Other type's directory exists; just return true here. + return true; + } + } + + // No other directories seem exist. Try deleting the entire origin directory. + InitOriginDatabase(false); + if (origin_database_) { + origin_database_->RemovePathForOrigin( + webkit_database::GetIdentifierFromOrigin(origin)); + } + if (!base::DeleteFile(origin_path, true /* recursive */)) + return false; + + return true; +} + +// static +base::FilePath::StringType ObfuscatedFileUtil::GetDirectoryNameForType( + FileSystemType type) { + switch (type) { + case kFileSystemTypeTemporary: + return kTemporaryDirectoryName; + case kFileSystemTypePersistent: + return kPersistentDirectoryName; + case kFileSystemTypeSyncable: + case kFileSystemTypeSyncableForInternalSync: + return kSyncableDirectoryName; + case kFileSystemTypeUnknown: + default: + return base::FilePath::StringType(); + } +} + +ObfuscatedFileUtil::AbstractOriginEnumerator* +ObfuscatedFileUtil::CreateOriginEnumerator() { + std::vector<SandboxOriginDatabase::OriginRecord> origins; + + InitOriginDatabase(false); + return new ObfuscatedOriginEnumerator( + origin_database_.get(), file_system_directory_); +} + +bool ObfuscatedFileUtil::DestroyDirectoryDatabase( + const GURL& origin, FileSystemType type) { + std::string key = GetDirectoryDatabaseKey(origin, type); + if (key.empty()) + return true; + DirectoryMap::iterator iter = directories_.find(key); + if (iter != directories_.end()) { + SandboxDirectoryDatabase* database = iter->second; + directories_.erase(iter); + delete database; + } + + PlatformFileError error = base::PLATFORM_FILE_OK; + base::FilePath path = GetDirectoryForOriginAndType( + origin, type, false, &error); + if (path.empty() || error == base::PLATFORM_FILE_ERROR_NOT_FOUND) + return true; + return SandboxDirectoryDatabase::DestroyDatabase(path); +} + +// static +int64 ObfuscatedFileUtil::ComputeFilePathCost(const base::FilePath& path) { + return UsageForPath(VirtualPath::BaseName(path).value().size()); +} + +void ObfuscatedFileUtil::MaybePrepopulateDatabase() { + // Always disable this for now. crbug.com/264429 + return; + + base::FilePath isolated_origin_dir = file_system_directory_.Append( + SandboxIsolatedOriginDatabase::kOriginDirectory); + if (!base::DirectoryExists(isolated_origin_dir)) + return; + + const FileSystemType kPrepopulateTypes[] = { + kFileSystemTypePersistent, kFileSystemTypeTemporary + }; + + // Prepulate the directory database(s) if and only if this instance is + // initialized for isolated storage dedicated for a single origin. + for (size_t i = 0; i < arraysize(kPrepopulateTypes); ++i) { + const FileSystemType type = kPrepopulateTypes[i]; + base::FilePath::StringType type_string = GetDirectoryNameForType(type); + DCHECK(!type_string.empty()); + base::FilePath path = isolated_origin_dir.Append(type_string); + if (!base::DirectoryExists(path)) + continue; + scoped_ptr<SandboxDirectoryDatabase> db(new SandboxDirectoryDatabase(path)); + if (db->Init(SandboxDirectoryDatabase::FAIL_ON_CORRUPTION)) { + directories_[GetFileSystemTypeString(type)] = db.release(); + MarkUsed(); + // Don't populate more than one database, as it may rather hurt + // performance. + break; + } + } +} + +PlatformFileError ObfuscatedFileUtil::GetFileInfoInternal( + SandboxDirectoryDatabase* db, + FileSystemOperationContext* context, + const GURL& origin, + FileSystemType type, + FileId file_id, + FileInfo* local_info, + base::PlatformFileInfo* file_info, + base::FilePath* platform_file_path) { + DCHECK(db); + DCHECK(context); + DCHECK(file_info); + DCHECK(platform_file_path); + + if (!db->GetFileInfo(file_id, local_info)) { + NOTREACHED(); + return base::PLATFORM_FILE_ERROR_FAILED; + } + + if (local_info->is_directory()) { + file_info->size = 0; + file_info->is_directory = true; + file_info->is_symbolic_link = false; + file_info->last_modified = local_info->modification_time; + *platform_file_path = base::FilePath(); + // We don't fill in ctime or atime. + return base::PLATFORM_FILE_OK; + } + if (local_info->data_path.empty()) + return base::PLATFORM_FILE_ERROR_INVALID_OPERATION; + base::FilePath local_path = DataPathToLocalPath( + origin, type, local_info->data_path); + base::PlatformFileError error = NativeFileUtil::GetFileInfo( + local_path, file_info); + // We should not follow symbolic links in sandboxed file system. + if (file_util::IsLink(local_path)) { + LOG(WARNING) << "Found a symbolic file."; + error = base::PLATFORM_FILE_ERROR_NOT_FOUND; + } + if (error == base::PLATFORM_FILE_OK) { + *platform_file_path = local_path; + } else if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) { + LOG(WARNING) << "Lost a backing file."; + InvalidateUsageCache(context, origin, type); + if (!db->RemoveFileInfo(file_id)) + return base::PLATFORM_FILE_ERROR_FAILED; + } + return error; +} + +PlatformFileError ObfuscatedFileUtil::CreateFile( + FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const GURL& dest_origin, + FileSystemType dest_type, + FileInfo* dest_file_info, int file_flags, PlatformFile* handle) { + if (handle) + *handle = base::kInvalidPlatformFileValue; + SandboxDirectoryDatabase* db = GetDirectoryDatabase( + dest_origin, dest_type, true); + + PlatformFileError error = base::PLATFORM_FILE_OK; + base::FilePath root = GetDirectoryForOriginAndType( + dest_origin, dest_type, false, &error); + if (error != base::PLATFORM_FILE_OK) + return error; + + base::FilePath dest_local_path; + error = GenerateNewLocalPath(db, context, dest_origin, dest_type, + &dest_local_path); + if (error != base::PLATFORM_FILE_OK) + return error; + + bool created = false; + if (!src_file_path.empty()) { + DCHECK(!file_flags); + DCHECK(!handle); + error = NativeFileUtil::CopyOrMoveFile( + src_file_path, dest_local_path, true /* copy */); + created = true; + } else { + if (base::PathExists(dest_local_path)) { + if (!base::DeleteFile(dest_local_path, true /* recursive */)) { + NOTREACHED(); + return base::PLATFORM_FILE_ERROR_FAILED; + } + LOG(WARNING) << "A stray file detected"; + InvalidateUsageCache(context, dest_origin, dest_type); + } + + if (handle) { + error = NativeFileUtil::CreateOrOpen( + dest_local_path, file_flags, handle, &created); + // If this succeeds, we must close handle on any subsequent error. + } else { + DCHECK(!file_flags); // file_flags is only used by CreateOrOpen. + error = NativeFileUtil::EnsureFileExists(dest_local_path, &created); + } + } + if (error != base::PLATFORM_FILE_OK) + return error; + + if (!created) { + NOTREACHED(); + if (handle) { + DCHECK_NE(base::kInvalidPlatformFileValue, *handle); + base::ClosePlatformFile(*handle); + base::DeleteFile(dest_local_path, false /* recursive */); + } + return base::PLATFORM_FILE_ERROR_FAILED; + } + + // This removes the root, including the trailing slash, leaving a relative + // path. + dest_file_info->data_path = base::FilePath( + dest_local_path.value().substr(root.value().length() + 1)); + + FileId file_id; + if (!db->AddFileInfo(*dest_file_info, &file_id)) { + if (handle) { + DCHECK_NE(base::kInvalidPlatformFileValue, *handle); + base::ClosePlatformFile(*handle); + } + base::DeleteFile(dest_local_path, false /* recursive */); + return base::PLATFORM_FILE_ERROR_FAILED; + } + TouchDirectory(db, dest_file_info->parent_id); + + return base::PLATFORM_FILE_OK; +} + +base::FilePath ObfuscatedFileUtil::DataPathToLocalPath( + const GURL& origin, FileSystemType type, const base::FilePath& data_path) { + PlatformFileError error = base::PLATFORM_FILE_OK; + base::FilePath root = GetDirectoryForOriginAndType( + origin, type, false, &error); + if (error != base::PLATFORM_FILE_OK) + return base::FilePath(); + return root.Append(data_path); +} + +std::string ObfuscatedFileUtil::GetDirectoryDatabaseKey( + const GURL& origin, FileSystemType type) { + std::string type_string = GetFileSystemTypeString(type); + if (type_string.empty()) { + LOG(WARNING) << "Unknown filesystem type requested:" << type; + return std::string(); + } + // For isolated origin we just use a type string as a key. + if (HasIsolatedStorage(origin)) { + CHECK_EQ(isolated_origin_.spec(), origin.spec()); + return type_string; + } + return webkit_database::GetIdentifierFromOrigin(origin) + + type_string; +} + +// TODO(ericu): How to do the whole validation-without-creation thing? +// We may not have quota even to create the database. +// Ah, in that case don't even get here? +// Still doesn't answer the quota issue, though. +SandboxDirectoryDatabase* ObfuscatedFileUtil::GetDirectoryDatabase( + const GURL& origin, FileSystemType type, bool create) { + std::string key = GetDirectoryDatabaseKey(origin, type); + if (key.empty()) + return NULL; + + DirectoryMap::iterator iter = directories_.find(key); + if (iter != directories_.end()) { + MarkUsed(); + return iter->second; + } + + PlatformFileError error = base::PLATFORM_FILE_OK; + base::FilePath path = GetDirectoryForOriginAndType( + origin, type, create, &error); + if (error != base::PLATFORM_FILE_OK) { + LOG(WARNING) << "Failed to get origin+type directory: " << path.value(); + return NULL; + } + MarkUsed(); + SandboxDirectoryDatabase* database = new SandboxDirectoryDatabase(path); + directories_[key] = database; + return database; +} + +base::FilePath ObfuscatedFileUtil::GetDirectoryForOrigin( + const GURL& origin, bool create, base::PlatformFileError* error_code) { + if (HasIsolatedStorage(origin)) { + CHECK_EQ(isolated_origin_.spec(), origin.spec()); + } + + if (!InitOriginDatabase(create)) { + if (error_code) { + *error_code = create ? + base::PLATFORM_FILE_ERROR_FAILED : + base::PLATFORM_FILE_ERROR_NOT_FOUND; + } + return base::FilePath(); + } + base::FilePath directory_name; + std::string id = webkit_database::GetIdentifierFromOrigin(origin); + + bool exists_in_db = origin_database_->HasOriginPath(id); + if (!exists_in_db && !create) { + if (error_code) + *error_code = base::PLATFORM_FILE_ERROR_NOT_FOUND; + return base::FilePath(); + } + if (!origin_database_->GetPathForOrigin(id, &directory_name)) { + if (error_code) + *error_code = base::PLATFORM_FILE_ERROR_FAILED; + return base::FilePath(); + } + + base::FilePath path = file_system_directory_.Append(directory_name); + bool exists_in_fs = base::DirectoryExists(path); + if (!exists_in_db && exists_in_fs) { + if (!base::DeleteFile(path, true)) { + if (error_code) + *error_code = base::PLATFORM_FILE_ERROR_FAILED; + return base::FilePath(); + } + exists_in_fs = false; + } + + if (!exists_in_fs) { + if (!create || !file_util::CreateDirectory(path)) { + if (error_code) + *error_code = create ? + base::PLATFORM_FILE_ERROR_FAILED : + base::PLATFORM_FILE_ERROR_NOT_FOUND; + return base::FilePath(); + } + } + + if (error_code) + *error_code = base::PLATFORM_FILE_OK; + + return path; +} + +void ObfuscatedFileUtil::InvalidateUsageCache( + FileSystemOperationContext* context, + const GURL& origin, + FileSystemType type) { + context->file_system_context()->sandbox_delegate()-> + InvalidateUsageCache(origin, type); +} + +void ObfuscatedFileUtil::MarkUsed() { + if (!timer_) + timer_.reset(new TimedTaskHelper(file_task_runner_.get())); + + if (timer_->IsRunning()) { + timer_->Reset(); + } else { + timer_->Start(FROM_HERE, + base::TimeDelta::FromSeconds(db_flush_delay_seconds_), + base::Bind(&ObfuscatedFileUtil::DropDatabases, + base::Unretained(this))); + } +} + +void ObfuscatedFileUtil::DropDatabases() { + origin_database_.reset(); + STLDeleteContainerPairSecondPointers( + directories_.begin(), directories_.end()); + directories_.clear(); + timer_.reset(); +} + +bool ObfuscatedFileUtil::InitOriginDatabase(bool create) { + if (origin_database_) + return true; + + if (!create && !base::DirectoryExists(file_system_directory_)) + return false; + if (!file_util::CreateDirectory(file_system_directory_)) { + LOG(WARNING) << "Failed to create FileSystem directory: " << + file_system_directory_.value(); + return false; + } + + origin_database_.reset( + new SandboxOriginDatabase(file_system_directory_)); + + base::FilePath isolated_origin_dir = file_system_directory_.Append( + SandboxIsolatedOriginDatabase::kOriginDirectory); + if (base::DirectoryExists(isolated_origin_dir) && + !isolated_origin_.is_empty()) { + SandboxIsolatedOriginDatabase::MigrateBackDatabase( + webkit_database::GetIdentifierFromOrigin(isolated_origin_), + file_system_directory_, + static_cast<SandboxOriginDatabase*>(origin_database_.get())); + } + + return true; +} + +PlatformFileError ObfuscatedFileUtil::GenerateNewLocalPath( + SandboxDirectoryDatabase* db, + FileSystemOperationContext* context, + const GURL& origin, + FileSystemType type, + base::FilePath* local_path) { + DCHECK(local_path); + int64 number; + if (!db || !db->GetNextInteger(&number)) + return base::PLATFORM_FILE_ERROR_FAILED; + + PlatformFileError error = base::PLATFORM_FILE_OK; + base::FilePath new_local_path = GetDirectoryForOriginAndType( + origin, type, false, &error); + if (error != base::PLATFORM_FILE_OK) + return base::PLATFORM_FILE_ERROR_FAILED; + + // We use the third- and fourth-to-last digits as the directory. + int64 directory_number = number % 10000 / 100; + new_local_path = new_local_path.AppendASCII( + base::StringPrintf("%02" PRId64, directory_number)); + + error = NativeFileUtil::CreateDirectory( + new_local_path, false /* exclusive */, false /* recursive */); + if (error != base::PLATFORM_FILE_OK) + return error; + + *local_path = + new_local_path.AppendASCII(base::StringPrintf("%08" PRId64, number)); + return base::PLATFORM_FILE_OK; +} + +PlatformFileError ObfuscatedFileUtil::CreateOrOpenInternal( + FileSystemOperationContext* context, + const FileSystemURL& url, int file_flags, + PlatformFile* file_handle, bool* created) { + DCHECK(!(file_flags & (base::PLATFORM_FILE_DELETE_ON_CLOSE | + base::PLATFORM_FILE_HIDDEN | base::PLATFORM_FILE_EXCLUSIVE_READ | + base::PLATFORM_FILE_EXCLUSIVE_WRITE))); + SandboxDirectoryDatabase* db = GetDirectoryDatabase( + url.origin(), url.type(), true); + if (!db) + return base::PLATFORM_FILE_ERROR_FAILED; + FileId file_id; + if (!db->GetFileWithPath(url.path(), &file_id)) { + // The file doesn't exist. + if (!(file_flags & (base::PLATFORM_FILE_CREATE | + base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_OPEN_ALWAYS))) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + FileId parent_id; + if (!db->GetFileWithPath(VirtualPath::DirName(url.path()), + &parent_id)) + return base::PLATFORM_FILE_ERROR_NOT_FOUND; + FileInfo file_info; + InitFileInfo(&file_info, parent_id, + VirtualPath::BaseName(url.path()).value()); + + int64 growth = UsageForPath(file_info.name.size()); + if (!AllocateQuota(context, growth)) + return base::PLATFORM_FILE_ERROR_NO_SPACE; + PlatformFileError error = CreateFile( + context, base::FilePath(), + url.origin(), url.type(), &file_info, + file_flags, file_handle); + if (created && base::PLATFORM_FILE_OK == error) { + *created = true; + UpdateUsage(context, url, growth); + context->change_observers()->Notify( + &FileChangeObserver::OnCreateFile, MakeTuple(url)); + } + return error; + } + + if (file_flags & base::PLATFORM_FILE_CREATE) + return base::PLATFORM_FILE_ERROR_EXISTS; + + base::PlatformFileInfo platform_file_info; + base::FilePath local_path; + FileInfo file_info; + base::PlatformFileError error = GetFileInfoInternal( + db, context, url.origin(), url.type(), file_id, + &file_info, &platform_file_info, &local_path); + if (error != base::PLATFORM_FILE_OK) + return error; + if (file_info.is_directory()) + return base::PLATFORM_FILE_ERROR_NOT_A_FILE; + + int64 delta = 0; + if (file_flags & (base::PLATFORM_FILE_CREATE_ALWAYS | + base::PLATFORM_FILE_OPEN_TRUNCATED)) { + // The file exists and we're truncating. + delta = -platform_file_info.size; + AllocateQuota(context, delta); + } + + error = NativeFileUtil::CreateOrOpen( + local_path, file_flags, file_handle, created); + if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) { + // TODO(tzik): Also invalidate on-memory usage cache in UsageTracker. + // TODO(tzik): Delete database entry after ensuring the file lost. + InvalidateUsageCache(context, url.origin(), url.type()); + LOG(WARNING) << "Lost a backing file."; + error = base::PLATFORM_FILE_ERROR_FAILED; + } + + // If truncating we need to update the usage. + if (error == base::PLATFORM_FILE_OK && delta) { + UpdateUsage(context, url, delta); + context->change_observers()->Notify( + &FileChangeObserver::OnModifyFile, MakeTuple(url)); + } + return error; +} + +bool ObfuscatedFileUtil::HasIsolatedStorage(const GURL& origin) { + if (special_storage_policy_.get() && + special_storage_policy_->HasIsolatedStorage(origin)) { + if (isolated_origin_.is_empty()) + isolated_origin_ = origin; + // Record isolated_origin_, but always disable for now. + // crbug.com/264429 + return false; + } + return false; +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/obfuscated_file_util.h b/chromium/webkit/browser/fileapi/obfuscated_file_util.h new file mode 100644 index 00000000000..0d9750d1fb5 --- /dev/null +++ b/chromium/webkit/browser/fileapi/obfuscated_file_util.h @@ -0,0 +1,296 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_H_ +#define WEBKIT_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_H_ + +#include <map> +#include <string> + +#include "base/files/file_path.h" +#include "base/files/file_util_proxy.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/platform_file.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/sandbox_directory_database.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/blob/shareable_file_reference.h" +#include "webkit/common/fileapi/file_system_types.h" + +namespace base { +class SequencedTaskRunner; +class TimeTicks; +} + +namespace quota { +class SpecialStoragePolicy; +} + +class GURL; + +namespace fileapi { + +class FileSystemOperationContext; +class SandboxOriginDatabaseInterface; +class TimedTaskHelper; + +// The overall implementation philosophy of this class is that partial failures +// should leave us with an intact database; we'd prefer to leak the occasional +// backing file than have a database entry whose backing file is missing. When +// doing FSCK operations, if you find a loose backing file with no reference, +// you may safely delete it. +// +// This class must be deleted on the FILE thread, because that's where +// DropDatabases needs to be called. +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE ObfuscatedFileUtil + : public FileSystemFileUtil { + public: + // Origin enumerator interface. + // An instance of this interface is assumed to be called on the file thread. + class AbstractOriginEnumerator { + public: + virtual ~AbstractOriginEnumerator() {} + + // Returns the next origin. Returns empty if there are no more origins. + virtual GURL Next() = 0; + + // Returns the current origin's information. + virtual bool HasFileSystemType(FileSystemType type) const = 0; + }; + + ObfuscatedFileUtil( + quota::SpecialStoragePolicy* special_storage_policy, + const base::FilePath& file_system_directory, + base::SequencedTaskRunner* file_task_runner); + virtual ~ObfuscatedFileUtil(); + + // FileSystemFileUtil overrides. + virtual base::PlatformFileError CreateOrOpen( + FileSystemOperationContext* context, + const FileSystemURL& url, + int file_flags, + base::PlatformFile* file_handle, + bool* created) OVERRIDE; + virtual base::PlatformFileError Close( + FileSystemOperationContext* context, + base::PlatformFile file) OVERRIDE; + virtual base::PlatformFileError EnsureFileExists( + FileSystemOperationContext* context, + const FileSystemURL& url, bool* created) OVERRIDE; + virtual base::PlatformFileError CreateDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url, + bool exclusive, + bool recursive) OVERRIDE; + virtual base::PlatformFileError GetFileInfo( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::PlatformFileInfo* file_info, + base::FilePath* platform_file) OVERRIDE; + virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url) OVERRIDE; + virtual base::PlatformFileError GetLocalFilePath( + FileSystemOperationContext* context, + const FileSystemURL& file_system_url, + base::FilePath* local_path) OVERRIDE; + virtual base::PlatformFileError Touch( + FileSystemOperationContext* context, + const FileSystemURL& url, + const base::Time& last_access_time, + const base::Time& last_modified_time) OVERRIDE; + virtual base::PlatformFileError Truncate( + FileSystemOperationContext* context, + const FileSystemURL& url, + int64 length) OVERRIDE; + virtual base::PlatformFileError CopyOrMoveFile( + FileSystemOperationContext* context, + const FileSystemURL& src_url, + const FileSystemURL& dest_url, + bool copy) OVERRIDE; + virtual base::PlatformFileError CopyInForeignFile( + FileSystemOperationContext* context, + const base::FilePath& src_file_path, + const FileSystemURL& dest_url) OVERRIDE; + virtual base::PlatformFileError DeleteFile( + FileSystemOperationContext* context, + const FileSystemURL& url) OVERRIDE; + virtual base::PlatformFileError DeleteDirectory( + FileSystemOperationContext* context, + const FileSystemURL& url) OVERRIDE; + virtual webkit_blob::ScopedFile CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::PlatformFileError* error, + base::PlatformFileInfo* file_info, + base::FilePath* platform_path) OVERRIDE; + + // Same as the other CreateFileEnumerator, but with recursive support. + scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator( + FileSystemOperationContext* context, + const FileSystemURL& root_url, + bool recursive); + + // Returns true if the directory |url| is empty. + bool IsDirectoryEmpty( + FileSystemOperationContext* context, + const FileSystemURL& url); + + // Gets the topmost directory specific to this origin and type. This will + // contain both the directory database's files and all the backing file + // subdirectories. + // Returns an empty path if the directory is undefined (e.g. because |type| + // is invalid). If the directory is defined, it will be returned, even if + // there is a file system error (e.g. the directory doesn't exist on disk and + // |create| is false). Callers should always check |error_code| to make sure + // the returned path is usable. + base::FilePath GetDirectoryForOriginAndType( + const GURL& origin, + FileSystemType type, + bool create, + base::PlatformFileError* error_code); + + // Deletes the topmost directory specific to this origin and type. This will + // delete its directory database. + bool DeleteDirectoryForOriginAndType(const GURL& origin, FileSystemType type); + + // TODO(ericu): This doesn't really feel like it belongs in this class. + // The previous version lives in FileSystemPathManager, but perhaps + // SandboxFileSystemBackend would be better? + static base::FilePath::StringType GetDirectoryNameForType( + FileSystemType type); + + // This method and all methods of its returned class must be called only on + // the FILE thread. The caller is responsible for deleting the returned + // object. + AbstractOriginEnumerator* CreateOriginEnumerator(); + + // Deletes a directory database from the database list in the ObfuscatedFSFU + // and destroys the database on the disk. + bool DestroyDirectoryDatabase(const GURL& origin, FileSystemType type); + + // Computes a cost for storing a given file in the obfuscated FSFU. + // As the cost of a file is independent of the cost of its parent directories, + // this ignores all but the BaseName of the supplied path. In order to + // compute the cost of adding a multi-segment directory recursively, call this + // on each path segment and add the results. + static int64 ComputeFilePathCost(const base::FilePath& path); + + void MaybePrepopulateDatabase(); + + private: + typedef SandboxDirectoryDatabase::FileId FileId; + typedef SandboxDirectoryDatabase::FileInfo FileInfo; + + friend class ObfuscatedFileEnumerator; + FRIEND_TEST_ALL_PREFIXES(ObfuscatedFileUtilTest, MaybeDropDatabasesAliveCase); + FRIEND_TEST_ALL_PREFIXES(ObfuscatedFileUtilTest, + MaybeDropDatabasesAlreadyDeletedCase); + FRIEND_TEST_ALL_PREFIXES(ObfuscatedFileUtilTest, + DestroyDirectoryDatabase_Isolated); + FRIEND_TEST_ALL_PREFIXES(ObfuscatedFileUtilTest, + GetDirectoryDatabase_Isolated); + FRIEND_TEST_ALL_PREFIXES(ObfuscatedFileUtilTest, + MigrationBackFromIsolated); + + base::PlatformFileError GetFileInfoInternal( + SandboxDirectoryDatabase* db, + FileSystemOperationContext* context, + const GURL& origin, + FileSystemType type, + FileId file_id, + FileInfo* local_info, + base::PlatformFileInfo* file_info, + base::FilePath* platform_file_path); + + // Creates a new file, both the underlying backing file and the entry in the + // database. |dest_file_info| is an in-out parameter. Supply the name and + // parent_id; data_path is ignored. On success, data_path will + // always be set to the relative path [from the root of the type-specific + // filesystem directory] of a NEW backing file, and handle, if supplied, will + // hold open PlatformFile for the backing file, which the caller is + // responsible for closing. If you supply a path in |source_path|, it will be + // used as a source from which to COPY data. + // Caveat: do not supply handle if you're also supplying a data path. It was + // easier not to support this, and no code has needed it so far, so it will + // DCHECK and handle will hold base::kInvalidPlatformFileValue. + base::PlatformFileError CreateFile( + FileSystemOperationContext* context, + const base::FilePath& source_file_path, + const GURL& dest_origin, + FileSystemType dest_type, + FileInfo* dest_file_info, + int file_flags, + base::PlatformFile* handle); + + // This converts from a relative path [as is stored in the FileInfo.data_path + // field] to an absolute platform path that can be given to the native + // filesystem. + base::FilePath DataPathToLocalPath( + const GURL& origin, + FileSystemType type, + const base::FilePath& data_file_path); + + std::string GetDirectoryDatabaseKey(const GURL& origin, FileSystemType type); + + // This returns NULL if |create| flag is false and a filesystem does not + // exist for the given |origin_url| and |type|. + // For read operations |create| should be false. + SandboxDirectoryDatabase* GetDirectoryDatabase( + const GURL& origin_url, FileSystemType type, bool create); + + // Gets the topmost directory specific to this origin. This will + // contain both the filesystem type subdirectories. + base::FilePath GetDirectoryForOrigin(const GURL& origin, + bool create, + base::PlatformFileError* error_code); + + void InvalidateUsageCache(FileSystemOperationContext* context, + const GURL& origin, + FileSystemType type); + + void MarkUsed(); + void DropDatabases(); + bool InitOriginDatabase(bool create); + + base::PlatformFileError GenerateNewLocalPath( + SandboxDirectoryDatabase* db, + FileSystemOperationContext* context, + const GURL& origin, + FileSystemType type, + base::FilePath* local_path); + + base::PlatformFileError CreateOrOpenInternal( + FileSystemOperationContext* context, + const FileSystemURL& url, + int file_flags, + base::PlatformFile* file_handle, + bool* created); + + bool HasIsolatedStorage(const GURL& origin); + + typedef std::map<std::string, SandboxDirectoryDatabase*> DirectoryMap; + DirectoryMap directories_; + scoped_ptr<SandboxOriginDatabaseInterface> origin_database_; + scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_; + base::FilePath file_system_directory_; + + // Used to delete database after a certain period of inactivity. + int64 db_flush_delay_seconds_; + + scoped_refptr<base::SequencedTaskRunner> file_task_runner_; + scoped_ptr<TimedTaskHelper> timer_; + + // If this instance is initialized for an isolated partition, this should + // only see a single origin. + GURL isolated_origin_; + + DISALLOW_COPY_AND_ASSIGN(ObfuscatedFileUtil); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_H_ diff --git a/chromium/webkit/browser/fileapi/obfuscated_file_util_unittest.cc b/chromium/webkit/browser/fileapi/obfuscated_file_util_unittest.cc new file mode 100644 index 00000000000..c4f409f76ed --- /dev/null +++ b/chromium/webkit/browser/fileapi/obfuscated_file_util_unittest.cc @@ -0,0 +1,2375 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <set> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/platform_file.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/async_file_test_helper.h" +#include "webkit/browser/fileapi/external_mount_points.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_usage_cache.h" +#include "webkit/browser/fileapi/mock_file_change_observer.h" +#include "webkit/browser/fileapi/mock_file_system_context.h" +#include "webkit/browser/fileapi/obfuscated_file_util.h" +#include "webkit/browser/fileapi/sandbox_directory_database.h" +#include "webkit/browser/fileapi/sandbox_file_system_test_helper.h" +#include "webkit/browser/fileapi/sandbox_isolated_origin_database.h" +#include "webkit/browser/fileapi/sandbox_origin_database.h" +#include "webkit/browser/fileapi/test_file_set.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/database/database_identifier.h" +#include "webkit/common/quota/quota_types.h" + +namespace fileapi { + +namespace { + +bool FileExists(const base::FilePath& path) { + return base::PathExists(path) && !base::DirectoryExists(path); +} + +int64 GetSize(const base::FilePath& path) { + int64 size; + EXPECT_TRUE(file_util::GetFileSize(path, &size)); + return size; +} + +// After a move, the dest exists and the source doesn't. +// After a copy, both source and dest exist. +struct CopyMoveTestCaseRecord { + bool is_copy_not_move; + const char source_path[64]; + const char dest_path[64]; + bool cause_overwrite; +}; + +const CopyMoveTestCaseRecord kCopyMoveTestCases[] = { + // This is the combinatoric set of: + // rename vs. same-name + // different directory vs. same directory + // overwrite vs. no-overwrite + // copy vs. move + // We can never be called with source and destination paths identical, so + // those cases are omitted. + {true, "dir0/file0", "dir0/file1", false}, + {false, "dir0/file0", "dir0/file1", false}, + {true, "dir0/file0", "dir0/file1", true}, + {false, "dir0/file0", "dir0/file1", true}, + + {true, "dir0/file0", "dir1/file0", false}, + {false, "dir0/file0", "dir1/file0", false}, + {true, "dir0/file0", "dir1/file0", true}, + {false, "dir0/file0", "dir1/file0", true}, + {true, "dir0/file0", "dir1/file1", false}, + {false, "dir0/file0", "dir1/file1", false}, + {true, "dir0/file0", "dir1/file1", true}, + {false, "dir0/file0", "dir1/file1", true}, +}; + +struct OriginEnumerationTestRecord { + std::string origin_url; + bool has_temporary; + bool has_persistent; +}; + +const OriginEnumerationTestRecord kOriginEnumerationTestRecords[] = { + {"http://example.com", false, true}, + {"http://example1.com", true, false}, + {"https://example1.com", true, true}, + {"file://", false, true}, + {"http://example.com:8000", false, true}, +}; + +FileSystemURL FileSystemURLAppend( + const FileSystemURL& url, const base::FilePath::StringType& child) { + return FileSystemURL::CreateForTest( + url.origin(), url.mount_type(), url.virtual_path().Append(child)); +} + +FileSystemURL FileSystemURLAppendUTF8( + const FileSystemURL& url, const std::string& child) { + return FileSystemURL::CreateForTest( + url.origin(), + url.mount_type(), + url.virtual_path().Append(base::FilePath::FromUTF8Unsafe(child))); +} + +FileSystemURL FileSystemURLDirName(const FileSystemURL& url) { + return FileSystemURL::CreateForTest( + url.origin(), url.mount_type(), VirtualPath::DirName(url.virtual_path())); +} + +} // namespace (anonymous) + +// TODO(ericu): The vast majority of this and the other FSFU subclass tests +// could theoretically be shared. It would basically be a FSFU interface +// compliance test, and only the subclass-specific bits that look into the +// implementation would need to be written per-subclass. +class ObfuscatedFileUtilTest : public testing::Test { + public: + ObfuscatedFileUtilTest() + : origin_(GURL("http://www.example.com")), + type_(kFileSystemTypeTemporary), + weak_factory_(this), + sandbox_file_system_(origin_, type_), + quota_status_(quota::kQuotaStatusUnknown), + usage_(-1) { + } + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + + storage_policy_ = new quota::MockSpecialStoragePolicy(); + + quota_manager_ = + new quota::QuotaManager(false /* is_incognito */, + data_dir_.path(), + base::MessageLoopProxy::current().get(), + base::MessageLoopProxy::current().get(), + storage_policy_.get()); + + // Every time we create a new sandbox_file_system helper, + // it creates another context, which creates another path manager, + // another sandbox_backend, and another OFU. + // We need to pass in the context to skip all that. + file_system_context_ = CreateFileSystemContextForTesting( + quota_manager_->proxy(), + data_dir_.path()); + + sandbox_file_system_.SetUp(file_system_context_.get()); + + change_observers_ = MockFileChangeObserver::CreateList(&change_observer_); + } + + virtual void TearDown() { + quota_manager_ = NULL; + sandbox_file_system_.TearDown(); + } + + scoped_ptr<FileSystemOperationContext> LimitedContext( + int64 allowed_bytes_growth) { + scoped_ptr<FileSystemOperationContext> context( + sandbox_file_system_.NewOperationContext()); + context->set_allowed_bytes_growth(allowed_bytes_growth); + return context.Pass(); + } + + scoped_ptr<FileSystemOperationContext> UnlimitedContext() { + return LimitedContext(kint64max); + } + + FileSystemOperationContext* NewContext( + SandboxFileSystemTestHelper* file_system) { + change_observer()->ResetCount(); + FileSystemOperationContext* context; + if (file_system) + context = file_system->NewOperationContext(); + else + context = sandbox_file_system_.NewOperationContext(); + // Setting allowed_bytes_growth big enough for all tests. + context->set_allowed_bytes_growth(1024 * 1024); + context->set_change_observers(change_observers()); + return context; + } + + const ChangeObserverList& change_observers() const { + return change_observers_; + } + + MockFileChangeObserver* change_observer() { + return &change_observer_; + } + + // This can only be used after SetUp has run and created file_system_context_ + // and obfuscated_file_util_. + // Use this for tests which need to run in multiple origins; we need a test + // helper per origin. + SandboxFileSystemTestHelper* NewFileSystem( + const GURL& origin, fileapi::FileSystemType type) { + SandboxFileSystemTestHelper* file_system = + new SandboxFileSystemTestHelper(origin, type); + + file_system->SetUp(file_system_context_.get()); + return file_system; + } + + ObfuscatedFileUtil* ofu() { + return static_cast<ObfuscatedFileUtil*>(sandbox_file_system_.file_util()); + } + + const base::FilePath& test_directory() const { + return data_dir_.path(); + } + + const GURL& origin() const { + return origin_; + } + + fileapi::FileSystemType type() const { + return type_; + } + + int64 ComputeTotalFileSize() { + return sandbox_file_system_.ComputeCurrentOriginUsage() - + sandbox_file_system_.ComputeCurrentDirectoryDatabaseUsage(); + } + + void GetUsageFromQuotaManager() { + int64 quota = -1; + quota_status_ = + AsyncFileTestHelper::GetUsageAndQuota(quota_manager_.get(), + origin(), + sandbox_file_system_.type(), + &usage_, + "a); + EXPECT_EQ(quota::kQuotaStatusOk, quota_status_); + } + + void RevokeUsageCache() { + quota_manager_->ResetUsageTracker(sandbox_file_system_.storage_type()); + usage_cache()->Delete(sandbox_file_system_.GetUsageCachePath()); + } + + int64 SizeByQuotaUtil() { + return sandbox_file_system_.GetCachedOriginUsage(); + } + + int64 SizeInUsageFile() { + base::MessageLoop::current()->RunUntilIdle(); + int64 usage = 0; + return usage_cache()->GetUsage( + sandbox_file_system_.GetUsageCachePath(), &usage) ? usage : -1; + } + + bool PathExists(const FileSystemURL& url) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + base::PlatformFileInfo file_info; + base::FilePath platform_path; + base::PlatformFileError error = ofu()->GetFileInfo( + context.get(), url, &file_info, &platform_path); + return error == base::PLATFORM_FILE_OK; + } + + bool DirectoryExists(const FileSystemURL& url) { + return AsyncFileTestHelper::DirectoryExists(file_system_context(), url); + } + + int64 usage() const { return usage_; } + FileSystemUsageCache* usage_cache() { + return sandbox_file_system_.usage_cache(); + } + + FileSystemURL CreateURLFromUTF8(const std::string& path) { + return sandbox_file_system_.CreateURLFromUTF8(path); + } + + int64 PathCost(const FileSystemURL& url) { + return ObfuscatedFileUtil::ComputeFilePathCost(url.path()); + } + + FileSystemURL CreateURL(const base::FilePath& path) { + return sandbox_file_system_.CreateURL(path); + } + + void CheckFileAndCloseHandle( + const FileSystemURL& url, base::PlatformFile file_handle) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + base::FilePath local_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetLocalFilePath( + context.get(), url, &local_path)); + + base::PlatformFileInfo file_info0; + base::FilePath data_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), url, &file_info0, &data_path)); + EXPECT_EQ(data_path, local_path); + EXPECT_TRUE(FileExists(data_path)); + EXPECT_EQ(0, GetSize(data_path)); + + const char data[] = "test data"; + const int length = arraysize(data) - 1; + + if (base::kInvalidPlatformFileValue == file_handle) { + bool created = true; + base::PlatformFileError error; + file_handle = base::CreatePlatformFile( + data_path, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE, + &created, + &error); + ASSERT_NE(base::kInvalidPlatformFileValue, file_handle); + ASSERT_EQ(base::PLATFORM_FILE_OK, error); + EXPECT_FALSE(created); + } + ASSERT_EQ(length, base::WritePlatformFile(file_handle, 0, data, length)); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); + + base::PlatformFileInfo file_info1; + EXPECT_EQ(length, GetSize(data_path)); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), url, &file_info1, &data_path)); + EXPECT_EQ(data_path, local_path); + + EXPECT_FALSE(file_info0.is_directory); + EXPECT_FALSE(file_info1.is_directory); + EXPECT_FALSE(file_info0.is_symbolic_link); + EXPECT_FALSE(file_info1.is_symbolic_link); + EXPECT_EQ(0, file_info0.size); + EXPECT_EQ(length, file_info1.size); + EXPECT_LE(file_info0.last_modified, file_info1.last_modified); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->Truncate( + context.get(), url, length * 2)); + EXPECT_EQ(length * 2, GetSize(data_path)); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->Truncate( + context.get(), url, 0)); + EXPECT_EQ(0, GetSize(data_path)); + } + + void ValidateTestDirectory( + const FileSystemURL& root_url, + const std::set<base::FilePath::StringType>& files, + const std::set<base::FilePath::StringType>& directories) { + scoped_ptr<FileSystemOperationContext> context; + std::set<base::FilePath::StringType>::const_iterator iter; + for (iter = files.begin(); iter != files.end(); ++iter) { + bool created = true; + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + context.get(), FileSystemURLAppend(root_url, *iter), + &created)); + ASSERT_FALSE(created); + } + for (iter = directories.begin(); iter != directories.end(); ++iter) { + context.reset(NewContext(NULL)); + EXPECT_TRUE(DirectoryExists( + FileSystemURLAppend(root_url, *iter))); + } + } + + class UsageVerifyHelper { + public: + UsageVerifyHelper(scoped_ptr<FileSystemOperationContext> context, + SandboxFileSystemTestHelper* file_system, + int64 expected_usage) + : context_(context.Pass()), + sandbox_file_system_(file_system), + expected_usage_(expected_usage) {} + + ~UsageVerifyHelper() { + base::MessageLoop::current()->RunUntilIdle(); + Check(); + } + + FileSystemOperationContext* context() { + return context_.get(); + } + + private: + void Check() { + ASSERT_EQ(expected_usage_, + sandbox_file_system_->GetCachedOriginUsage()); + } + + scoped_ptr<FileSystemOperationContext> context_; + SandboxFileSystemTestHelper* sandbox_file_system_; + int64 expected_usage_; + }; + + scoped_ptr<UsageVerifyHelper> AllowUsageIncrease(int64 requested_growth) { + int64 usage = sandbox_file_system_.GetCachedOriginUsage(); + return scoped_ptr<UsageVerifyHelper>(new UsageVerifyHelper( + LimitedContext(requested_growth), + &sandbox_file_system_, usage + requested_growth)); + } + + scoped_ptr<UsageVerifyHelper> DisallowUsageIncrease(int64 requested_growth) { + int64 usage = sandbox_file_system_.GetCachedOriginUsage(); + return scoped_ptr<UsageVerifyHelper>(new UsageVerifyHelper( + LimitedContext(requested_growth - 1), &sandbox_file_system_, usage)); + } + + void FillTestDirectory( + const FileSystemURL& root_url, + std::set<base::FilePath::StringType>* files, + std::set<base::FilePath::StringType>* directories) { + scoped_ptr<FileSystemOperationContext> context; + std::vector<DirectoryEntry> entries; + EXPECT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), root_url, &entries)); + EXPECT_EQ(0UL, entries.size()); + + files->clear(); + files->insert(FILE_PATH_LITERAL("first")); + files->insert(FILE_PATH_LITERAL("second")); + files->insert(FILE_PATH_LITERAL("third")); + directories->clear(); + directories->insert(FILE_PATH_LITERAL("fourth")); + directories->insert(FILE_PATH_LITERAL("fifth")); + directories->insert(FILE_PATH_LITERAL("sixth")); + std::set<base::FilePath::StringType>::iterator iter; + for (iter = files->begin(); iter != files->end(); ++iter) { + bool created = false; + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + context.get(), + FileSystemURLAppend(root_url, *iter), + &created)); + ASSERT_TRUE(created); + } + for (iter = directories->begin(); iter != directories->end(); ++iter) { + bool exclusive = true; + bool recursive = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory( + context.get(), + FileSystemURLAppend(root_url, *iter), + exclusive, recursive)); + } + ValidateTestDirectory(root_url, *files, *directories); + } + + void TestReadDirectoryHelper(const FileSystemURL& root_url) { + std::set<base::FilePath::StringType> files; + std::set<base::FilePath::StringType> directories; + FillTestDirectory(root_url, &files, &directories); + + scoped_ptr<FileSystemOperationContext> context; + std::vector<DirectoryEntry> entries; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), root_url, &entries)); + std::vector<DirectoryEntry>::iterator entry_iter; + EXPECT_EQ(files.size() + directories.size(), entries.size()); + EXPECT_TRUE(change_observer()->HasNoChange()); + for (entry_iter = entries.begin(); entry_iter != entries.end(); + ++entry_iter) { + const DirectoryEntry& entry = *entry_iter; + std::set<base::FilePath::StringType>::iterator iter = + files.find(entry.name); + if (iter != files.end()) { + EXPECT_FALSE(entry.is_directory); + files.erase(iter); + continue; + } + iter = directories.find(entry.name); + EXPECT_FALSE(directories.end() == iter); + EXPECT_TRUE(entry.is_directory); + directories.erase(iter); + } + } + + void TestTouchHelper(const FileSystemURL& url, bool is_file) { + base::Time last_access_time = base::Time::Now(); + base::Time last_modified_time = base::Time::Now(); + + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Touch( + context.get(), url, last_access_time, last_modified_time)); + // Currently we fire no change notifications for Touch. + EXPECT_TRUE(change_observer()->HasNoChange()); + base::FilePath local_path; + base::PlatformFileInfo file_info; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), url, &file_info, &local_path)); + // We compare as time_t here to lower our resolution, to avoid false + // negatives caused by conversion to the local filesystem's native + // representation and back. + EXPECT_EQ(file_info.last_modified.ToTimeT(), last_modified_time.ToTimeT()); + + context.reset(NewContext(NULL)); + last_modified_time += base::TimeDelta::FromHours(1); + last_access_time += base::TimeDelta::FromHours(14); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Touch( + context.get(), url, last_access_time, last_modified_time)); + EXPECT_TRUE(change_observer()->HasNoChange()); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), url, &file_info, &local_path)); + EXPECT_EQ(file_info.last_modified.ToTimeT(), last_modified_time.ToTimeT()); + if (is_file) // Directories in OFU don't support atime. + EXPECT_EQ(file_info.last_accessed.ToTimeT(), last_access_time.ToTimeT()); + } + + void TestCopyInForeignFileHelper(bool overwrite) { + base::ScopedTempDir source_dir; + ASSERT_TRUE(source_dir.CreateUniqueTempDir()); + base::FilePath root_file_path = source_dir.path(); + base::FilePath src_file_path = root_file_path.AppendASCII("file_name"); + FileSystemURL dest_url = CreateURLFromUTF8("new file"); + int64 src_file_length = 87; + + base::PlatformFileError error_code; + bool created = false; + int file_flags = base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE; + base::PlatformFile file_handle = + base::CreatePlatformFile( + src_file_path, file_flags, &created, &error_code); + EXPECT_TRUE(created); + ASSERT_EQ(base::PLATFORM_FILE_OK, error_code); + ASSERT_NE(base::kInvalidPlatformFileValue, file_handle); + ASSERT_TRUE(base::TruncatePlatformFile(file_handle, src_file_length)); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); + + scoped_ptr<FileSystemOperationContext> context; + + if (overwrite) { + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), dest_url, &created)); + EXPECT_TRUE(created); + + // We must have observed one (and only one) create_file_count. + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count()); + EXPECT_TRUE(change_observer()->HasNoChange()); + } + + const int64 path_cost = + ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()); + if (!overwrite) { + // Verify that file creation requires sufficient quota for the path. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(path_cost + src_file_length - 1); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->CopyInForeignFile(context.get(), + src_file_path, dest_url)); + } + + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(path_cost + src_file_length); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyInForeignFile(context.get(), + src_file_path, dest_url)); + + EXPECT_TRUE(PathExists(dest_url)); + EXPECT_FALSE(DirectoryExists(dest_url)); + + context.reset(NewContext(NULL)); + base::PlatformFileInfo file_info; + base::FilePath data_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), dest_url, &file_info, &data_path)); + EXPECT_NE(data_path, src_file_path); + EXPECT_TRUE(FileExists(data_path)); + EXPECT_EQ(src_file_length, GetSize(data_path)); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile(context.get(), dest_url)); + } + + void ClearTimestamp(const FileSystemURL& url) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Touch(context.get(), url, base::Time(), base::Time())); + EXPECT_EQ(base::Time(), GetModifiedTime(url)); + } + + base::Time GetModifiedTime(const FileSystemURL& url) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + base::FilePath data_path; + base::PlatformFileInfo file_info; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetFileInfo(context.get(), url, &file_info, &data_path)); + EXPECT_TRUE(change_observer()->HasNoChange()); + return file_info.last_modified; + } + + void TestDirectoryTimestampHelper(const FileSystemURL& base_dir, + bool copy, + bool overwrite) { + scoped_ptr<FileSystemOperationContext> context; + const FileSystemURL src_dir_url( + FileSystemURLAppendUTF8(base_dir, "foo_dir")); + const FileSystemURL dest_dir_url( + FileSystemURLAppendUTF8(base_dir, "bar_dir")); + + const FileSystemURL src_file_url( + FileSystemURLAppendUTF8(src_dir_url, "hoge")); + const FileSystemURL dest_file_url( + FileSystemURLAppendUTF8(dest_dir_url, "fuga")); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), src_dir_url, true, true)); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), dest_dir_url, true, true)); + + bool created = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), src_file_url, &created)); + if (overwrite) { + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), + dest_file_url, &created)); + } + + ClearTimestamp(src_dir_url); + ClearTimestamp(dest_dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile(context.get(), + src_file_url, dest_file_url, + copy)); + if (copy) + EXPECT_EQ(base::Time(), GetModifiedTime(src_dir_url)); + else + EXPECT_NE(base::Time(), GetModifiedTime(src_dir_url)); + EXPECT_NE(base::Time(), GetModifiedTime(dest_dir_url)); + } + + int64 ComputeCurrentUsage() { + return sandbox_file_system_.ComputeCurrentOriginUsage() - + sandbox_file_system_.ComputeCurrentDirectoryDatabaseUsage(); + } + + FileSystemContext* file_system_context() { + return sandbox_file_system_.file_system_context(); + } + + const base::FilePath& data_dir_path() const { + return data_dir_.path(); + } + + protected: + base::ScopedTempDir data_dir_; + base::MessageLoop message_loop_; + scoped_refptr<quota::MockSpecialStoragePolicy> storage_policy_; + scoped_refptr<quota::QuotaManager> quota_manager_; + scoped_refptr<FileSystemContext> file_system_context_; + GURL origin_; + fileapi::FileSystemType type_; + base::WeakPtrFactory<ObfuscatedFileUtilTest> weak_factory_; + SandboxFileSystemTestHelper sandbox_file_system_; + quota::QuotaStatusCode quota_status_; + int64 usage_; + MockFileChangeObserver change_observer_; + ChangeObserverList change_observers_; + + DISALLOW_COPY_AND_ASSIGN(ObfuscatedFileUtilTest); +}; + +TEST_F(ObfuscatedFileUtilTest, TestCreateAndDeleteFile) { + base::PlatformFile file_handle = base::kInvalidPlatformFileValue; + bool created; + FileSystemURL url = CreateURLFromUTF8("fake/file"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + int file_flags = base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE; + + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->CreateOrOpen( + context.get(), url, file_flags, &file_handle, + &created)); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->DeleteFile(context.get(), url)); + + url = CreateURLFromUTF8("test file"); + + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Verify that file creation requires sufficient quota for the path. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->CreateOrOpen( + context.get(), url, file_flags, &file_handle, &created)); + + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(url.path())); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + context.get(), url, file_flags, &file_handle, &created)); + ASSERT_TRUE(created); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count()); + EXPECT_NE(base::kInvalidPlatformFileValue, file_handle); + + CheckFileAndCloseHandle(url, file_handle); + + context.reset(NewContext(NULL)); + base::FilePath local_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetLocalFilePath( + context.get(), url, &local_path)); + EXPECT_TRUE(base::PathExists(local_path)); + + // Verify that deleting a file isn't stopped by zero quota, and that it frees + // up quote from its path. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(0); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile(context.get(), url)); + EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count()); + EXPECT_FALSE(base::PathExists(local_path)); + EXPECT_EQ(ObfuscatedFileUtil::ComputeFilePathCost(url.path()), + context->allowed_bytes_growth()); + + context.reset(NewContext(NULL)); + bool exclusive = true; + bool recursive = true; + FileSystemURL directory_url = CreateURLFromUTF8( + "series/of/directories"); + url = FileSystemURLAppendUTF8(directory_url, "file name"); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), directory_url, exclusive, recursive)); + // The oepration created 3 directories recursively. + EXPECT_EQ(3, change_observer()->get_and_reset_create_directory_count()); + + context.reset(NewContext(NULL)); + file_handle = base::kInvalidPlatformFileValue; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + context.get(), url, file_flags, &file_handle, &created)); + ASSERT_TRUE(created); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count()); + EXPECT_NE(base::kInvalidPlatformFileValue, file_handle); + + CheckFileAndCloseHandle(url, file_handle); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetLocalFilePath( + context.get(), url, &local_path)); + EXPECT_TRUE(base::PathExists(local_path)); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile(context.get(), url)); + EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count()); + EXPECT_FALSE(base::PathExists(local_path)); + + // Make sure we have no unexpected changes. + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(ObfuscatedFileUtilTest, TestTruncate) { + bool created = false; + FileSystemURL url = CreateURLFromUTF8("file"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->Truncate(context.get(), url, 4)); + + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + ASSERT_TRUE(created); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count()); + + context.reset(NewContext(NULL)); + base::FilePath local_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetLocalFilePath( + context.get(), url, &local_path)); + EXPECT_EQ(0, GetSize(local_path)); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->Truncate( + context.get(), url, 10)); + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); + EXPECT_EQ(10, GetSize(local_path)); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->Truncate( + context.get(), url, 1)); + EXPECT_EQ(1, GetSize(local_path)); + EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count()); + + EXPECT_FALSE(DirectoryExists(url)); + EXPECT_TRUE(PathExists(url)); + + // Make sure we have no unexpected changes. + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(ObfuscatedFileUtilTest, TestQuotaOnTruncation) { + bool created = false; + FileSystemURL url = CreateURLFromUTF8("file"); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(url))->context(), + url, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(0, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(1020)->context(), + url, 1020)); + ASSERT_EQ(1020, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(-1020)->context(), + url, 0)); + ASSERT_EQ(0, ComputeTotalFileSize()); + + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->Truncate( + DisallowUsageIncrease(1021)->context(), + url, 1021)); + ASSERT_EQ(0, ComputeTotalFileSize()); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(1020)->context(), + url, 1020)); + ASSERT_EQ(1020, ComputeTotalFileSize()); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(0)->context(), + url, 1020)); + ASSERT_EQ(1020, ComputeTotalFileSize()); + + // quota exceeded + { + scoped_ptr<UsageVerifyHelper> helper = AllowUsageIncrease(-1); + helper->context()->set_allowed_bytes_growth( + helper->context()->allowed_bytes_growth() - 1); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate(helper->context(), url, 1019)); + ASSERT_EQ(1019, ComputeTotalFileSize()); + } + + // Delete backing file to make following truncation fail. + base::FilePath local_path; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetLocalFilePath( + UnlimitedContext().get(), + url, &local_path)); + ASSERT_FALSE(local_path.empty()); + ASSERT_TRUE(base::DeleteFile(local_path, false)); + + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->Truncate( + LimitedContext(1234).get(), + url, 1234)); + ASSERT_EQ(0, ComputeTotalFileSize()); +} + +TEST_F(ObfuscatedFileUtilTest, TestEnsureFileExists) { + FileSystemURL url = CreateURLFromUTF8("fake/file"); + bool created = false; + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->EnsureFileExists( + context.get(), url, &created)); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Verify that file creation requires sufficient quota for the path. + context.reset(NewContext(NULL)); + url = CreateURLFromUTF8("test file"); + created = false; + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->EnsureFileExists(context.get(), url, &created)); + ASSERT_FALSE(created); + EXPECT_TRUE(change_observer()->HasNoChange()); + + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(url.path())); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + ASSERT_TRUE(created); + EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count()); + + CheckFileAndCloseHandle(url, base::kInvalidPlatformFileValue); + + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + ASSERT_FALSE(created); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Also test in a subdirectory. + url = CreateURLFromUTF8("path/to/file.txt"); + context.reset(NewContext(NULL)); + bool exclusive = true; + bool recursive = true; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), + FileSystemURLDirName(url), + exclusive, recursive)); + // 2 directories: path/ and path/to. + EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count()); + + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + ASSERT_TRUE(created); + EXPECT_FALSE(DirectoryExists(url)); + EXPECT_TRUE(PathExists(url)); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(ObfuscatedFileUtilTest, TestDirectoryOps) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + bool exclusive = false; + bool recursive = false; + FileSystemURL url = CreateURLFromUTF8("foo/bar"); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->DeleteDirectory(context.get(), url)); + + FileSystemURL root = CreateURLFromUTF8(std::string()); + EXPECT_FALSE(DirectoryExists(url)); + EXPECT_FALSE(PathExists(url)); + context.reset(NewContext(NULL)); + EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), root)); + + context.reset(NewContext(NULL)); + exclusive = false; + recursive = true; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count()); + + EXPECT_TRUE(DirectoryExists(url)); + EXPECT_TRUE(PathExists(url)); + + context.reset(NewContext(NULL)); + EXPECT_FALSE(ofu()->IsDirectoryEmpty(context.get(), root)); + EXPECT_TRUE(DirectoryExists(FileSystemURLDirName(url))); + + context.reset(NewContext(NULL)); + EXPECT_FALSE(ofu()->IsDirectoryEmpty(context.get(), + FileSystemURLDirName(url))); + + // Can't remove a non-empty directory. + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY, + ofu()->DeleteDirectory(context.get(), + FileSystemURLDirName(url))); + EXPECT_TRUE(change_observer()->HasNoChange()); + + base::PlatformFileInfo file_info; + base::FilePath local_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), url, &file_info, &local_path)); + EXPECT_TRUE(local_path.empty()); + EXPECT_TRUE(file_info.is_directory); + EXPECT_FALSE(file_info.is_symbolic_link); + + // Same create again should succeed, since exclusive is false. + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_TRUE(change_observer()->HasNoChange()); + + exclusive = true; + recursive = true; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_TRUE(change_observer()->HasNoChange()); + + // Verify that deleting a directory isn't stopped by zero quota, and that it + // frees up quota from its path. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(0); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->DeleteDirectory(context.get(), url)); + EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count()); + EXPECT_EQ(ObfuscatedFileUtil::ComputeFilePathCost(url.path()), + context->allowed_bytes_growth()); + + url = CreateURLFromUTF8("foo/bop"); + + EXPECT_FALSE(DirectoryExists(url)); + EXPECT_FALSE(PathExists(url)); + + context.reset(NewContext(NULL)); + EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), url)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, ofu()->GetFileInfo( + context.get(), url, &file_info, &local_path)); + + // Verify that file creation requires sufficient quota for the path. + exclusive = true; + recursive = false; + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_TRUE(change_observer()->HasNoChange()); + + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(url.path())); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); + + EXPECT_TRUE(DirectoryExists(url)); + EXPECT_TRUE(PathExists(url)); + + exclusive = true; + recursive = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_TRUE(change_observer()->HasNoChange()); + + exclusive = true; + recursive = false; + url = CreateURLFromUTF8("foo"); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_TRUE(change_observer()->HasNoChange()); + + url = CreateURLFromUTF8("blah"); + + EXPECT_FALSE(DirectoryExists(url)); + EXPECT_FALSE(PathExists(url)); + + exclusive = true; + recursive = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count()); + + EXPECT_TRUE(DirectoryExists(url)); + EXPECT_TRUE(PathExists(url)); + + exclusive = true; + recursive = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(ObfuscatedFileUtilTest, TestReadDirectory) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + bool exclusive = true; + bool recursive = true; + FileSystemURL url = CreateURLFromUTF8("directory/to/use"); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + TestReadDirectoryHelper(url); +} + +TEST_F(ObfuscatedFileUtilTest, TestReadRootWithSlash) { + TestReadDirectoryHelper(CreateURLFromUTF8(std::string())); +} + +TEST_F(ObfuscatedFileUtilTest, TestReadRootWithEmptyString) { + TestReadDirectoryHelper(CreateURLFromUTF8("/")); +} + +TEST_F(ObfuscatedFileUtilTest, TestReadDirectoryOnFile) { + FileSystemURL url = CreateURLFromUTF8("file"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + ASSERT_TRUE(created); + + std::vector<DirectoryEntry> entries; + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), url, &entries)); + + EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), url)); +} + +TEST_F(ObfuscatedFileUtilTest, TestTouch) { + FileSystemURL url = CreateURLFromUTF8("file"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + base::Time last_access_time = base::Time::Now(); + base::Time last_modified_time = base::Time::Now(); + + // It's not there yet. + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->Touch( + context.get(), url, last_access_time, last_modified_time)); + + // OK, now create it. + context.reset(NewContext(NULL)); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + ASSERT_TRUE(created); + TestTouchHelper(url, true); + + // Now test a directory: + context.reset(NewContext(NULL)); + bool exclusive = true; + bool recursive = false; + url = CreateURLFromUTF8("dir"); + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(context.get(), + url, exclusive, recursive)); + TestTouchHelper(url, false); +} + +TEST_F(ObfuscatedFileUtilTest, TestPathQuotas) { + FileSystemURL url = CreateURLFromUTF8("fake/file"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + url = CreateURLFromUTF8("file name"); + context->set_allowed_bytes_growth(5); + bool created = false; + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->EnsureFileExists(context.get(), url, &created)); + EXPECT_FALSE(created); + context->set_allowed_bytes_growth(1024); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + EXPECT_TRUE(created); + int64 path_cost = ObfuscatedFileUtil::ComputeFilePathCost(url.path()); + EXPECT_EQ(1024 - path_cost, context->allowed_bytes_growth()); + + context->set_allowed_bytes_growth(1024); + bool exclusive = true; + bool recursive = true; + url = CreateURLFromUTF8("directory/to/use"); + std::vector<base::FilePath::StringType> components; + url.path().GetComponents(&components); + path_cost = 0; + typedef std::vector<base::FilePath::StringType>::iterator iterator; + for (iterator iter = components.begin(); + iter != components.end(); ++iter) { + path_cost += ObfuscatedFileUtil::ComputeFilePathCost( + base::FilePath(*iter)); + } + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(1024); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), url, exclusive, recursive)); + EXPECT_EQ(1024 - path_cost, context->allowed_bytes_growth()); +} + +TEST_F(ObfuscatedFileUtilTest, TestCopyOrMoveFileNotFound) { + FileSystemURL source_url = CreateURLFromUTF8("path0.txt"); + FileSystemURL dest_url = CreateURLFromUTF8("path1.txt"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + bool is_copy_not_move = false; + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->CopyOrMoveFile(context.get(), source_url, dest_url, + is_copy_not_move)); + EXPECT_TRUE(change_observer()->HasNoChange()); + context.reset(NewContext(NULL)); + is_copy_not_move = true; + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->CopyOrMoveFile(context.get(), source_url, dest_url, + is_copy_not_move)); + EXPECT_TRUE(change_observer()->HasNoChange()); + source_url = CreateURLFromUTF8("dir/dir/file"); + bool exclusive = true; + bool recursive = true; + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), + FileSystemURLDirName(source_url), + exclusive, recursive)); + EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count()); + is_copy_not_move = false; + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->CopyOrMoveFile(context.get(), source_url, dest_url, + is_copy_not_move)); + EXPECT_TRUE(change_observer()->HasNoChange()); + context.reset(NewContext(NULL)); + is_copy_not_move = true; + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->CopyOrMoveFile(context.get(), source_url, dest_url, + is_copy_not_move)); + EXPECT_TRUE(change_observer()->HasNoChange()); +} + +TEST_F(ObfuscatedFileUtilTest, TestCopyOrMoveFileSuccess) { + const int64 kSourceLength = 5; + const int64 kDestLength = 50; + + for (size_t i = 0; i < arraysize(kCopyMoveTestCases); ++i) { + SCOPED_TRACE(testing::Message() << "kCopyMoveTestCase " << i); + const CopyMoveTestCaseRecord& test_case = kCopyMoveTestCases[i]; + SCOPED_TRACE(testing::Message() << "\t is_copy_not_move " << + test_case.is_copy_not_move); + SCOPED_TRACE(testing::Message() << "\t source_path " << + test_case.source_path); + SCOPED_TRACE(testing::Message() << "\t dest_path " << + test_case.dest_path); + SCOPED_TRACE(testing::Message() << "\t cause_overwrite " << + test_case.cause_overwrite); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + bool exclusive = false; + bool recursive = true; + FileSystemURL source_url = CreateURLFromUTF8(test_case.source_path); + FileSystemURL dest_url = CreateURLFromUTF8(test_case.dest_path); + + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), + FileSystemURLDirName(source_url), + exclusive, recursive)); + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), + FileSystemURLDirName(dest_url), + exclusive, recursive)); + + bool created = false; + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), source_url, &created)); + ASSERT_TRUE(created); + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate(context.get(), source_url, kSourceLength)); + + if (test_case.cause_overwrite) { + context.reset(NewContext(NULL)); + created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), dest_url, &created)); + ASSERT_TRUE(created); + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate(context.get(), dest_url, kDestLength)); + } + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CopyOrMoveFile(context.get(), + source_url, dest_url, test_case.is_copy_not_move)); + + if (test_case.is_copy_not_move) { + base::PlatformFileInfo file_info; + base::FilePath local_path; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), source_url, &file_info, &local_path)); + EXPECT_EQ(kSourceLength, file_info.size); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile(context.get(), source_url)); + } else { + base::PlatformFileInfo file_info; + base::FilePath local_path; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, ofu()->GetFileInfo( + context.get(), source_url, &file_info, &local_path)); + } + base::PlatformFileInfo file_info; + base::FilePath local_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo( + context.get(), dest_url, &file_info, &local_path)); + EXPECT_EQ(kSourceLength, file_info.size); + + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile(context.get(), dest_url)); + } +} + +TEST_F(ObfuscatedFileUtilTest, TestCopyPathQuotas) { + FileSystemURL src_url = CreateURLFromUTF8("src path"); + FileSystemURL dest_url = CreateURLFromUTF8("destination path"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists( + context.get(), src_url, &created)); + + bool is_copy = true; + // Copy, no overwrite. + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) - 1); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy)); + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path())); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy)); + + // Copy, with overwrite. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(0); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy)); +} + +TEST_F(ObfuscatedFileUtilTest, TestMovePathQuotasWithRename) { + FileSystemURL src_url = CreateURLFromUTF8("src path"); + FileSystemURL dest_url = CreateURLFromUTF8("destination path"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists( + context.get(), src_url, &created)); + + bool is_copy = false; + // Move, rename, no overwrite. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) - + ObfuscatedFileUtil::ComputeFilePathCost(src_url.path()) - 1); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy)); + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth( + ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) - + ObfuscatedFileUtil::ComputeFilePathCost(src_url.path())); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy)); + + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists( + context.get(), src_url, &created)); + + // Move, rename, with overwrite. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(0); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy)); +} + +TEST_F(ObfuscatedFileUtilTest, TestMovePathQuotasWithoutRename) { + FileSystemURL src_url = CreateURLFromUTF8("src path"); + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists( + context.get(), src_url, &created)); + + bool exclusive = true; + bool recursive = false; + FileSystemURL dir_url = CreateURLFromUTF8("directory path"); + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), dir_url, exclusive, recursive)); + + FileSystemURL dest_url = FileSystemURLAppend( + dir_url, src_url.path().value()); + + bool is_copy = false; + int64 allowed_bytes_growth = -1000; // Over quota, this should still work. + // Move, no rename, no overwrite. + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(allowed_bytes_growth); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy)); + EXPECT_EQ(allowed_bytes_growth, context->allowed_bytes_growth()); + + // Move, no rename, with overwrite. + context.reset(NewContext(NULL)); + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists( + context.get(), src_url, &created)); + context.reset(NewContext(NULL)); + context->set_allowed_bytes_growth(allowed_bytes_growth); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy)); + EXPECT_EQ( + allowed_bytes_growth + + ObfuscatedFileUtil::ComputeFilePathCost(src_url.path()), + context->allowed_bytes_growth()); +} + +TEST_F(ObfuscatedFileUtilTest, TestCopyInForeignFile) { + TestCopyInForeignFileHelper(false /* overwrite */); + TestCopyInForeignFileHelper(true /* overwrite */); +} + +TEST_F(ObfuscatedFileUtilTest, TestEnumerator) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + FileSystemURL src_url = CreateURLFromUTF8("source dir"); + bool exclusive = true; + bool recursive = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory( + context.get(), src_url, exclusive, recursive)); + + std::set<base::FilePath::StringType> files; + std::set<base::FilePath::StringType> directories; + FillTestDirectory(src_url, &files, &directories); + + FileSystemURL dest_url = CreateURLFromUTF8("destination dir"); + + EXPECT_FALSE(DirectoryExists(dest_url)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Copy( + file_system_context(), src_url, dest_url)); + + ValidateTestDirectory(dest_url, files, directories); + EXPECT_TRUE(DirectoryExists(src_url)); + EXPECT_TRUE(DirectoryExists(dest_url)); + recursive = true; + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Remove( + file_system_context(), dest_url, recursive)); + EXPECT_FALSE(DirectoryExists(dest_url)); +} + +TEST_F(ObfuscatedFileUtilTest, TestOriginEnumerator) { + scoped_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator> + enumerator(ofu()->CreateOriginEnumerator()); + // The test helper starts out with a single filesystem. + EXPECT_TRUE(enumerator.get()); + EXPECT_EQ(origin(), enumerator->Next()); + ASSERT_TRUE(type() == kFileSystemTypeTemporary); + EXPECT_TRUE(enumerator->HasFileSystemType(kFileSystemTypeTemporary)); + EXPECT_FALSE(enumerator->HasFileSystemType(kFileSystemTypePersistent)); + EXPECT_EQ(GURL(), enumerator->Next()); + EXPECT_FALSE(enumerator->HasFileSystemType(kFileSystemTypeTemporary)); + EXPECT_FALSE(enumerator->HasFileSystemType(kFileSystemTypePersistent)); + + std::set<GURL> origins_expected; + origins_expected.insert(origin()); + + for (size_t i = 0; i < arraysize(kOriginEnumerationTestRecords); ++i) { + SCOPED_TRACE(testing::Message() << + "Validating kOriginEnumerationTestRecords " << i); + const OriginEnumerationTestRecord& record = + kOriginEnumerationTestRecords[i]; + GURL origin_url(record.origin_url); + origins_expected.insert(origin_url); + if (record.has_temporary) { + scoped_ptr<SandboxFileSystemTestHelper> file_system( + NewFileSystem(origin_url, kFileSystemTypeTemporary)); + scoped_ptr<FileSystemOperationContext> context( + NewContext(file_system.get())); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + context.get(), + file_system->CreateURLFromUTF8("file"), + &created)); + EXPECT_TRUE(created); + } + if (record.has_persistent) { + scoped_ptr<SandboxFileSystemTestHelper> file_system( + NewFileSystem(origin_url, kFileSystemTypePersistent)); + scoped_ptr<FileSystemOperationContext> context( + NewContext(file_system.get())); + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + context.get(), + file_system->CreateURLFromUTF8("file"), + &created)); + EXPECT_TRUE(created); + } + } + enumerator.reset(ofu()->CreateOriginEnumerator()); + EXPECT_TRUE(enumerator.get()); + std::set<GURL> origins_found; + GURL origin_url; + while (!(origin_url = enumerator->Next()).is_empty()) { + origins_found.insert(origin_url); + SCOPED_TRACE(testing::Message() << "Handling " << origin_url.spec()); + bool found = false; + for (size_t i = 0; !found && i < arraysize(kOriginEnumerationTestRecords); + ++i) { + const OriginEnumerationTestRecord& record = + kOriginEnumerationTestRecords[i]; + if (GURL(record.origin_url) != origin_url) + continue; + found = true; + EXPECT_EQ(record.has_temporary, + enumerator->HasFileSystemType(kFileSystemTypeTemporary)); + EXPECT_EQ(record.has_persistent, + enumerator->HasFileSystemType(kFileSystemTypePersistent)); + } + // Deal with the default filesystem created by the test helper. + if (!found && origin_url == origin()) { + ASSERT_TRUE(type() == kFileSystemTypeTemporary); + EXPECT_EQ(true, + enumerator->HasFileSystemType(kFileSystemTypeTemporary)); + EXPECT_FALSE(enumerator->HasFileSystemType(kFileSystemTypePersistent)); + found = true; + } + EXPECT_TRUE(found); + } + + std::set<GURL> diff; + std::set_symmetric_difference(origins_expected.begin(), + origins_expected.end(), origins_found.begin(), origins_found.end(), + inserter(diff, diff.begin())); + EXPECT_TRUE(diff.empty()); +} + +TEST_F(ObfuscatedFileUtilTest, TestRevokeUsageCache) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + + int64 expected_quota = 0; + + for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) { + SCOPED_TRACE(testing::Message() << "Creating kRegularTestCase " << i); + const test::TestCaseRecord& test_case = test::kRegularTestCases[i]; + base::FilePath file_path(test_case.path); + expected_quota += ObfuscatedFileUtil::ComputeFilePathCost(file_path); + if (test_case.is_directory) { + bool exclusive = true; + bool recursive = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), CreateURL(file_path), + exclusive, recursive)); + } else { + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), CreateURL(file_path), + &created)); + ASSERT_TRUE(created); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate(context.get(), + CreateURL(file_path), + test_case.data_file_size)); + expected_quota += test_case.data_file_size; + } + } + + // Usually raw size in usage cache and the usage returned by QuotaUtil + // should be same. + EXPECT_EQ(expected_quota, SizeInUsageFile()); + EXPECT_EQ(expected_quota, SizeByQuotaUtil()); + + RevokeUsageCache(); + EXPECT_EQ(-1, SizeInUsageFile()); + EXPECT_EQ(expected_quota, SizeByQuotaUtil()); + + // This should reconstruct the cache. + GetUsageFromQuotaManager(); + EXPECT_EQ(expected_quota, SizeInUsageFile()); + EXPECT_EQ(expected_quota, SizeByQuotaUtil()); + EXPECT_EQ(expected_quota, usage()); +} + +TEST_F(ObfuscatedFileUtilTest, TestInconsistency) { + const FileSystemURL kPath1 = CreateURLFromUTF8("hoge"); + const FileSystemURL kPath2 = CreateURLFromUTF8("fuga"); + + scoped_ptr<FileSystemOperationContext> context; + base::PlatformFile file; + base::PlatformFileInfo file_info; + base::FilePath data_path; + bool created = false; + + // Create a non-empty file. + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), kPath1, &created)); + EXPECT_TRUE(created); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate(context.get(), kPath1, 10)); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetFileInfo( + context.get(), kPath1, &file_info, &data_path)); + EXPECT_EQ(10, file_info.size); + + // Destroy database to make inconsistency between database and filesystem. + ofu()->DestroyDirectoryDatabase(origin(), type()); + + // Try to get file info of broken file. + EXPECT_FALSE(PathExists(kPath1)); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), kPath1, &created)); + EXPECT_TRUE(created); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetFileInfo( + context.get(), kPath1, &file_info, &data_path)); + EXPECT_EQ(0, file_info.size); + + // Make another broken file to |kPath2|. + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), kPath2, &created)); + EXPECT_TRUE(created); + + // Destroy again. + ofu()->DestroyDirectoryDatabase(origin(), type()); + + // Repair broken |kPath1|. + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->Touch(context.get(), kPath1, base::Time::Now(), + base::Time::Now())); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), kPath1, &created)); + EXPECT_TRUE(created); + + // Copy from sound |kPath1| to broken |kPath2|. + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile(context.get(), kPath1, kPath2, + true /* copy */)); + + ofu()->DestroyDirectoryDatabase(origin(), type()); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + context.get(), kPath1, + base::PLATFORM_FILE_READ | base::PLATFORM_FILE_CREATE, + &file, &created)); + EXPECT_TRUE(created); + EXPECT_TRUE(base::GetPlatformFileInfo(file, &file_info)); + EXPECT_EQ(0, file_info.size); + EXPECT_TRUE(base::ClosePlatformFile(file)); +} + +TEST_F(ObfuscatedFileUtilTest, TestIncompleteDirectoryReading) { + const FileSystemURL kPath[] = { + CreateURLFromUTF8("foo"), + CreateURLFromUTF8("bar"), + CreateURLFromUTF8("baz") + }; + const FileSystemURL empty_path = CreateURL(base::FilePath()); + scoped_ptr<FileSystemOperationContext> context; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kPath); ++i) { + bool created = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), kPath[i], &created)); + EXPECT_TRUE(created); + } + + std::vector<DirectoryEntry> entries; + EXPECT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), empty_path, &entries)); + EXPECT_EQ(3u, entries.size()); + + base::FilePath local_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetLocalFilePath(context.get(), kPath[0], &local_path)); + EXPECT_TRUE(base::DeleteFile(local_path, false)); + + entries.clear(); + EXPECT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::ReadDirectory( + file_system_context(), empty_path, &entries)); + EXPECT_EQ(ARRAYSIZE_UNSAFE(kPath) - 1, entries.size()); +} + +TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForCreation) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + const FileSystemURL dir_url = CreateURLFromUTF8("foo_dir"); + + // Create working directory. + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), dir_url, false, false)); + + // EnsureFileExists, create case. + FileSystemURL url(FileSystemURLAppendUTF8( + dir_url, "EnsureFileExists_file")); + bool created = false; + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + EXPECT_TRUE(created); + EXPECT_NE(base::Time(), GetModifiedTime(dir_url)); + + // non create case. + created = true; + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + EXPECT_FALSE(created); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + + // fail case. + url = FileSystemURLAppendUTF8(dir_url, "EnsureFileExists_dir"); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), url, false, false)); + + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_FILE, + ofu()->EnsureFileExists(context.get(), url, &created)); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + + // CreateOrOpen, create case. + url = FileSystemURLAppendUTF8(dir_url, "CreateOrOpen_file"); + base::PlatformFile file_handle = base::kInvalidPlatformFileValue; + created = false; + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + context.get(), url, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, + &file_handle, &created)); + EXPECT_NE(base::kInvalidPlatformFileValue, file_handle); + EXPECT_TRUE(created); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); + EXPECT_NE(base::Time(), GetModifiedTime(dir_url)); + + // open case. + file_handle = base::kInvalidPlatformFileValue; + created = true; + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + context.get(), url, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE, + &file_handle, &created)); + EXPECT_NE(base::kInvalidPlatformFileValue, file_handle); + EXPECT_FALSE(created); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + + // fail case + file_handle = base::kInvalidPlatformFileValue; + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, + ofu()->CreateOrOpen( + context.get(), url, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, + &file_handle, &created)); + EXPECT_EQ(base::kInvalidPlatformFileValue, file_handle); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + + // CreateDirectory, create case. + // Creating CreateDirectory_dir and CreateDirectory_dir/subdir. + url = FileSystemURLAppendUTF8(dir_url, "CreateDirectory_dir"); + FileSystemURL subdir_url(FileSystemURLAppendUTF8(url, "subdir")); + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), subdir_url, + true /* exclusive */, true /* recursive */)); + EXPECT_NE(base::Time(), GetModifiedTime(dir_url)); + + // create subdir case. + // Creating CreateDirectory_dir/subdir2. + subdir_url = FileSystemURLAppendUTF8(url, "subdir2"); + ClearTimestamp(dir_url); + ClearTimestamp(url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), subdir_url, + true /* exclusive */, true /* recursive */)); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + EXPECT_NE(base::Time(), GetModifiedTime(url)); + + // fail case. + url = FileSystemURLAppendUTF8(dir_url, "CreateDirectory_dir"); + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, + ofu()->CreateDirectory(context.get(), url, + true /* exclusive */, true /* recursive */)); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + + // CopyInForeignFile, create case. + url = FileSystemURLAppendUTF8(dir_url, "CopyInForeignFile_file"); + FileSystemURL src_path = FileSystemURLAppendUTF8( + dir_url, "CopyInForeignFile_src_file"); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), src_path, &created)); + EXPECT_TRUE(created); + base::FilePath src_local_path; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetLocalFilePath(context.get(), src_path, &src_local_path)); + + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyInForeignFile(context.get(), + src_local_path, + url)); + EXPECT_NE(base::Time(), GetModifiedTime(dir_url)); +} + +TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForDeletion) { + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + const FileSystemURL dir_url = CreateURLFromUTF8("foo_dir"); + + // Create working directory. + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), dir_url, false, false)); + + // DeleteFile, delete case. + FileSystemURL url = FileSystemURLAppendUTF8( + dir_url, "DeleteFile_file"); + bool created = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url, &created)); + EXPECT_TRUE(created); + + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile(context.get(), url)); + EXPECT_NE(base::Time(), GetModifiedTime(dir_url)); + + // fail case. + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + ofu()->DeleteFile(context.get(), url)); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + + // DeleteDirectory, fail case. + url = FileSystemURLAppendUTF8(dir_url, "DeleteDirectory_dir"); + FileSystemURL file_path(FileSystemURLAppendUTF8(url, "pakeratta")); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), url, true, true)); + created = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), file_path, &created)); + EXPECT_TRUE(created); + + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY, + ofu()->DeleteDirectory(context.get(), url)); + EXPECT_EQ(base::Time(), GetModifiedTime(dir_url)); + + // delete case. + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile(context.get(), file_path)); + + ClearTimestamp(dir_url); + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->DeleteDirectory(context.get(), url)); + EXPECT_NE(base::Time(), GetModifiedTime(dir_url)); +} + +TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForCopyAndMove) { + TestDirectoryTimestampHelper( + CreateURLFromUTF8("copy overwrite"), true, true); + TestDirectoryTimestampHelper( + CreateURLFromUTF8("copy non-overwrite"), true, false); + TestDirectoryTimestampHelper( + CreateURLFromUTF8("move overwrite"), false, true); + TestDirectoryTimestampHelper( + CreateURLFromUTF8("move non-overwrite"), false, false); +} + +TEST_F(ObfuscatedFileUtilTest, TestFileEnumeratorTimestamp) { + FileSystemURL dir = CreateURLFromUTF8("foo"); + FileSystemURL url1 = FileSystemURLAppendUTF8(dir, "bar"); + FileSystemURL url2 = FileSystemURLAppendUTF8(dir, "baz"); + + scoped_ptr<FileSystemOperationContext> context(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), dir, false, false)); + + bool created = false; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists(context.get(), url1, &created)); + EXPECT_TRUE(created); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory(context.get(), url2, false, false)); + + base::FilePath file_path; + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetLocalFilePath(context.get(), url1, &file_path)); + EXPECT_FALSE(file_path.empty()); + + context.reset(NewContext(NULL)); + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->Touch(context.get(), url1, + base::Time::Now() + base::TimeDelta::FromHours(1), + base::Time())); + + context.reset(NewContext(NULL)); + scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> file_enum( + ofu()->CreateFileEnumerator(context.get(), dir, false)); + + int count = 0; + base::FilePath file_path_each; + while (!(file_path_each = file_enum->Next()).empty()) { + context.reset(NewContext(NULL)); + base::PlatformFileInfo file_info; + base::FilePath file_path; + EXPECT_EQ(base::PLATFORM_FILE_OK, + ofu()->GetFileInfo(context.get(), + FileSystemURL::CreateForTest( + dir.origin(), + dir.mount_type(), + file_path_each), + &file_info, &file_path)); + EXPECT_EQ(file_info.is_directory, file_enum->IsDirectory()); + EXPECT_EQ(file_info.last_modified, file_enum->LastModifiedTime()); + EXPECT_EQ(file_info.size, file_enum->Size()); + ++count; + } + EXPECT_EQ(2, count); +} + +// crbug.com/176470 +#if defined(OS_WIN) || defined(OS_ANDROID) +#define MAYBE_TestQuotaOnCopyFile DISABLED_TestQuotaOnCopyFile +#else +#define MAYBE_TestQuotaOnCopyFile TestQuotaOnCopyFile +#endif +TEST_F(ObfuscatedFileUtilTest, MAYBE_TestQuotaOnCopyFile) { + FileSystemURL from_file(CreateURLFromUTF8("fromfile")); + FileSystemURL obstacle_file(CreateURLFromUTF8("obstaclefile")); + FileSystemURL to_file1(CreateURLFromUTF8("tofile1")); + FileSystemURL to_file2(CreateURLFromUTF8("tofile2")); + bool created; + + int64 expected_total_file_size = 0; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(from_file))->context(), + from_file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(obstacle_file))->context(), + obstacle_file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 from_file_size = 1020; + expected_total_file_size += from_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(from_file_size)->context(), + from_file, from_file_size)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 obstacle_file_size = 1; + expected_total_file_size += obstacle_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(obstacle_file_size)->context(), + obstacle_file, obstacle_file_size)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 to_file1_size = from_file_size; + expected_total_file_size += to_file1_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + AllowUsageIncrease( + PathCost(to_file1) + to_file1_size)->context(), + from_file, to_file1, true /* copy */)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + ofu()->CopyOrMoveFile( + DisallowUsageIncrease( + PathCost(to_file2) + from_file_size)->context(), + from_file, to_file2, true /* copy */)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 old_obstacle_file_size = obstacle_file_size; + obstacle_file_size = from_file_size; + expected_total_file_size += obstacle_file_size - old_obstacle_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + AllowUsageIncrease( + obstacle_file_size - old_obstacle_file_size)->context(), + from_file, obstacle_file, true /* copy */)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 old_from_file_size = from_file_size; + from_file_size = old_from_file_size - 1; + expected_total_file_size += from_file_size - old_from_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease( + from_file_size - old_from_file_size)->context(), + from_file, from_file_size)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + // quota exceeded + { + old_obstacle_file_size = obstacle_file_size; + obstacle_file_size = from_file_size; + expected_total_file_size += obstacle_file_size - old_obstacle_file_size; + scoped_ptr<UsageVerifyHelper> helper = AllowUsageIncrease( + obstacle_file_size - old_obstacle_file_size); + helper->context()->set_allowed_bytes_growth( + helper->context()->allowed_bytes_growth() - 1); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + helper->context(), + from_file, obstacle_file, true /* copy */)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + } +} + +TEST_F(ObfuscatedFileUtilTest, TestQuotaOnMoveFile) { + FileSystemURL from_file(CreateURLFromUTF8("fromfile")); + FileSystemURL obstacle_file(CreateURLFromUTF8("obstaclefile")); + FileSystemURL to_file(CreateURLFromUTF8("tofile")); + bool created; + + int64 expected_total_file_size = 0; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(from_file))->context(), + from_file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 from_file_size = 1020; + expected_total_file_size += from_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(from_file_size)->context(), + from_file, from_file_size)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 to_file_size ALLOW_UNUSED = from_file_size; + from_file_size = 0; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + AllowUsageIncrease(-PathCost(from_file) + + PathCost(to_file))->context(), + from_file, to_file, false /* move */)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(from_file))->context(), + from_file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(obstacle_file))->context(), + obstacle_file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + from_file_size = 1020; + expected_total_file_size += from_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(from_file_size)->context(), + from_file, from_file_size)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 obstacle_file_size = 1; + expected_total_file_size += obstacle_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(1)->context(), + obstacle_file, obstacle_file_size)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + int64 old_obstacle_file_size = obstacle_file_size; + obstacle_file_size = from_file_size; + from_file_size = 0; + expected_total_file_size -= old_obstacle_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + AllowUsageIncrease( + -old_obstacle_file_size - PathCost(from_file))->context(), + from_file, obstacle_file, + false /* move */)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(from_file))->context(), + from_file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + from_file_size = 10; + expected_total_file_size += from_file_size; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(from_file_size)->context(), + from_file, from_file_size)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + + // quota exceeded even after operation + old_obstacle_file_size = obstacle_file_size; + obstacle_file_size = from_file_size; + from_file_size = 0; + expected_total_file_size -= old_obstacle_file_size; + scoped_ptr<FileSystemOperationContext> context = + LimitedContext(-old_obstacle_file_size - PathCost(from_file) - 1); + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CopyOrMoveFile( + context.get(), from_file, obstacle_file, false /* move */)); + ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize()); + context.reset(); +} + +TEST_F(ObfuscatedFileUtilTest, TestQuotaOnRemove) { + FileSystemURL dir(CreateURLFromUTF8("dir")); + FileSystemURL file(CreateURLFromUTF8("file")); + FileSystemURL dfile1(CreateURLFromUTF8("dir/dfile1")); + FileSystemURL dfile2(CreateURLFromUTF8("dir/dfile2")); + bool created; + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(file))->context(), + file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(0, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateDirectory( + AllowUsageIncrease(PathCost(dir))->context(), + dir, false, false)); + ASSERT_EQ(0, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(dfile1))->context(), + dfile1, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(0, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(dfile2))->context(), + dfile2, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(0, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(340)->context(), + file, 340)); + ASSERT_EQ(340, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(1020)->context(), + dfile1, 1020)); + ASSERT_EQ(1360, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(120)->context(), + dfile2, 120)); + ASSERT_EQ(1480, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->DeleteFile( + AllowUsageIncrease(-PathCost(file) - 340)->context(), + file)); + ASSERT_EQ(1140, ComputeTotalFileSize()); + + ASSERT_EQ(base::PLATFORM_FILE_OK, + AsyncFileTestHelper::Remove( + file_system_context(), dir, true /* recursive */)); + ASSERT_EQ(0, ComputeTotalFileSize()); +} + +TEST_F(ObfuscatedFileUtilTest, TestQuotaOnOpen) { + FileSystemURL file(CreateURLFromUTF8("file")); + base::PlatformFile file_handle; + bool created; + + // Creating a file. + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->EnsureFileExists( + AllowUsageIncrease(PathCost(file))->context(), + file, &created)); + ASSERT_TRUE(created); + ASSERT_EQ(0, ComputeTotalFileSize()); + + // Opening it, which shouldn't change the usage. + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + AllowUsageIncrease(0)->context(), file, + base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE, + &file_handle, &created)); + ASSERT_EQ(0, ComputeTotalFileSize()); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); + + const int length = 33; + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(length)->context(), file, length)); + ASSERT_EQ(length, ComputeTotalFileSize()); + + // Opening it with CREATE_ALWAYS flag, which should truncate the file size. + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + AllowUsageIncrease(-length)->context(), file, + base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE, + &file_handle, &created)); + ASSERT_EQ(0, ComputeTotalFileSize()); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); + + // Extending the file again. + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->Truncate( + AllowUsageIncrease(length)->context(), file, length)); + ASSERT_EQ(length, ComputeTotalFileSize()); + + // Opening it with TRUNCATED flag, which should truncate the file size. + ASSERT_EQ(base::PLATFORM_FILE_OK, + ofu()->CreateOrOpen( + AllowUsageIncrease(-length)->context(), file, + base::PLATFORM_FILE_OPEN_TRUNCATED | base::PLATFORM_FILE_WRITE, + &file_handle, &created)); + ASSERT_EQ(0, ComputeTotalFileSize()); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); +} + +TEST_F(ObfuscatedFileUtilTest, MaybeDropDatabasesAliveCase) { + ObfuscatedFileUtil file_util(NULL, + data_dir_path(), + base::MessageLoopProxy::current().get()); + file_util.InitOriginDatabase(true /*create*/); + ASSERT_TRUE(file_util.origin_database_ != NULL); + + // Callback to Drop DB is called while ObfuscatedFileUtilTest is still alive. + file_util.db_flush_delay_seconds_ = 0; + file_util.MarkUsed(); + base::MessageLoop::current()->RunUntilIdle(); + + ASSERT_TRUE(file_util.origin_database_ == NULL); +} + +TEST_F(ObfuscatedFileUtilTest, MaybeDropDatabasesAlreadyDeletedCase) { + // Run message loop after OFU is already deleted to make sure callback doesn't + // cause a crash for use after free. + { + ObfuscatedFileUtil file_util(NULL, + data_dir_path(), + base::MessageLoopProxy::current().get()); + file_util.InitOriginDatabase(true /*create*/); + file_util.db_flush_delay_seconds_ = 0; + file_util.MarkUsed(); + } + + // At this point the callback is still in the message queue but OFU is gone. + base::MessageLoop::current()->RunUntilIdle(); +} + +TEST_F(ObfuscatedFileUtilTest, DestroyDirectoryDatabase_Isolated) { + storage_policy_->AddIsolated(origin_); + ObfuscatedFileUtil file_util( + storage_policy_.get(), data_dir_path(), + base::MessageLoopProxy::current().get()); + + // Create DirectoryDatabase for isolated origin. + SandboxDirectoryDatabase* db = file_util.GetDirectoryDatabase( + origin_, kFileSystemTypePersistent, true /* create */); + ASSERT_TRUE(db != NULL); + + // Destory it. + ASSERT_TRUE( + file_util.DestroyDirectoryDatabase(origin_, kFileSystemTypePersistent)); + ASSERT_TRUE(file_util.directories_.empty()); +} + +TEST_F(ObfuscatedFileUtilTest, GetDirectoryDatabase_Isolated) { + storage_policy_->AddIsolated(origin_); + ObfuscatedFileUtil file_util( + storage_policy_.get(), data_dir_path(), + base::MessageLoopProxy::current().get()); + + // Create DirectoryDatabase for isolated origin. + SandboxDirectoryDatabase* db = file_util.GetDirectoryDatabase( + origin_, kFileSystemTypePersistent, true /* create */); + ASSERT_TRUE(db != NULL); + ASSERT_EQ(1U, file_util.directories_.size()); + + // Remove isolated. + storage_policy_->RemoveIsolated(origin_); + + // This should still get the same database. + SandboxDirectoryDatabase* db2 = file_util.GetDirectoryDatabase( + origin_, kFileSystemTypePersistent, false /* create */); + ASSERT_EQ(db, db2); +} + +TEST_F(ObfuscatedFileUtilTest, MigrationBackFromIsolated) { + std::string kFakeDirectoryData("0123456789"); + base::FilePath old_directory_db_path; + + // Initialize the directory with one origin using + // SandboxIsolatedOriginDatabase. + { + std::string origin_string = + webkit_database::GetIdentifierFromOrigin(origin_); + SandboxIsolatedOriginDatabase database_old(origin_string, data_dir_path()); + base::FilePath path; + EXPECT_TRUE(database_old.GetPathForOrigin(origin_string, &path)); + EXPECT_FALSE(path.empty()); + + // Populate the origin directory with some fake data. + old_directory_db_path = data_dir_path().Append(path); + ASSERT_TRUE(file_util::CreateDirectory(old_directory_db_path)); + EXPECT_EQ(static_cast<int>(kFakeDirectoryData.size()), + file_util::WriteFile(old_directory_db_path.AppendASCII("dummy"), + kFakeDirectoryData.data(), + kFakeDirectoryData.size())); + } + + storage_policy_->AddIsolated(origin_); + ObfuscatedFileUtil file_util( + storage_policy_.get(), data_dir_path(), + base::MessageLoopProxy::current().get()); + base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + base::FilePath origin_directory = file_util.GetDirectoryForOrigin( + origin_, true /* create */, &error); + EXPECT_EQ(base::PLATFORM_FILE_OK, error); + + // The database is migrated from the old one. + EXPECT_TRUE(base::DirectoryExists(origin_directory)); + EXPECT_FALSE(base::DirectoryExists(old_directory_db_path)); + + // Check we see the same contents in the new origin directory. + std::string origin_db_data; + EXPECT_TRUE(base::PathExists(origin_directory.AppendASCII("dummy"))); + EXPECT_TRUE(file_util::ReadFileToString( + origin_directory.AppendASCII("dummy"), &origin_db_data)); + EXPECT_EQ(kFakeDirectoryData, origin_db_data); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/open_file_system_mode.h b/chromium/webkit/browser/fileapi/open_file_system_mode.h new file mode 100644 index 00000000000..72a2bcb2f44 --- /dev/null +++ b/chromium/webkit/browser/fileapi/open_file_system_mode.h @@ -0,0 +1,22 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_OPEN_FILE_SYSTEM_MODE_H_ +#define WEBKIT_BROWSER_FILEAPI_OPEN_FILE_SYSTEM_MODE_H_ + +namespace fileapi { + +// Determines the behavior on OpenFileSystem when a specified +// FileSystem does not exist. +// Specifying CREATE_IF_NONEXISTENT may make actual modification on +// disk (e.g. creating a root directory, setting up a metadata database etc) +// if the filesystem hasn't been initialized. +enum OpenFileSystemMode { + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT, +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_OPEN_FILE_SYSTEM_MODE_H_ diff --git a/chromium/webkit/browser/fileapi/recursive_operation_delegate.cc b/chromium/webkit/browser/fileapi/recursive_operation_delegate.cc new file mode 100644 index 00000000000..c91c74cf148 --- /dev/null +++ b/chromium/webkit/browser/fileapi/recursive_operation_delegate.cc @@ -0,0 +1,141 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/recursive_operation_delegate.h" + +#include "base/bind.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" + +namespace fileapi { + +namespace { +// Don't start too many inflight operations. +const int kMaxInflightOperations = 5; +} + +RecursiveOperationDelegate::RecursiveOperationDelegate( + FileSystemContext* file_system_context) + : file_system_context_(file_system_context), + inflight_operations_(0) { +} + +RecursiveOperationDelegate::~RecursiveOperationDelegate() { +} + +void RecursiveOperationDelegate::StartRecursiveOperation( + const FileSystemURL& root, + const StatusCallback& callback) { + callback_ = callback; + pending_directories_.push(root); + ProcessNextDirectory(); +} + +FileSystemOperationRunner* RecursiveOperationDelegate::operation_runner() { + return file_system_context_->operation_runner(); +} + +void RecursiveOperationDelegate::ProcessNextDirectory() { + DCHECK(pending_files_.empty()); + if (inflight_operations_ > 0) + return; + if (pending_directories_.empty()) { + callback_.Run(base::PLATFORM_FILE_OK); + return; + } + FileSystemURL url = pending_directories_.front(); + pending_directories_.pop(); + inflight_operations_++; + ProcessDirectory( + url, base::Bind(&RecursiveOperationDelegate::DidProcessDirectory, + AsWeakPtr(), url)); +} + +void RecursiveOperationDelegate::ProcessPendingFiles() { + if (pending_files_.empty()) { + ProcessNextDirectory(); + return; + } + while (!pending_files_.empty() && + inflight_operations_ < kMaxInflightOperations) { + FileSystemURL url = pending_files_.front(); + pending_files_.pop(); + inflight_operations_++; + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&RecursiveOperationDelegate::ProcessFile, + AsWeakPtr(), url, + base::Bind(&RecursiveOperationDelegate::DidProcessFile, + AsWeakPtr()))); + } +} + +void RecursiveOperationDelegate::DidProcessFile(base::PlatformFileError error) { + inflight_operations_--; + DCHECK_GE(inflight_operations_, 0); + if (error != base::PLATFORM_FILE_OK) { + callback_.Run(error); + return; + } + ProcessPendingFiles(); +} + +void RecursiveOperationDelegate::DidProcessDirectory( + const FileSystemURL& url, + base::PlatformFileError error) { + if (error != base::PLATFORM_FILE_OK) { + callback_.Run(error); + return; + } + operation_runner()->ReadDirectory( + url, base::Bind(&RecursiveOperationDelegate::DidReadDirectory, + AsWeakPtr(), url)); +} + +void RecursiveOperationDelegate::DidReadDirectory( + const FileSystemURL& parent, + base::PlatformFileError error, + const FileEntryList& entries, + bool has_more) { + if (error != base::PLATFORM_FILE_OK) { + if (error == base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY) { + // The given path may have been a file, so try RemoveFile now. + ProcessFile(parent, + base::Bind(&RecursiveOperationDelegate::DidTryProcessFile, + AsWeakPtr(), error)); + return; + } + callback_.Run(error); + return; + } + for (size_t i = 0; i < entries.size(); i++) { + FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( + parent.origin(), + parent.mount_type(), + parent.virtual_path().Append(entries[i].name)); + if (entries[i].is_directory) + pending_directories_.push(url); + else + pending_files_.push(url); + } + if (has_more) + return; + + inflight_operations_--; + DCHECK_GE(inflight_operations_, 0); + ProcessPendingFiles(); +} + +void RecursiveOperationDelegate::DidTryProcessFile( + base::PlatformFileError previous_error, + base::PlatformFileError error) { + if (error == base::PLATFORM_FILE_ERROR_NOT_A_FILE) { + // It wasn't a file either; returns with the previous error. + callback_.Run(previous_error); + return; + } + DidProcessFile(error); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/recursive_operation_delegate.h b/chromium/webkit/browser/fileapi/recursive_operation_delegate.h new file mode 100644 index 00000000000..551d875d125 --- /dev/null +++ b/chromium/webkit/browser/fileapi/recursive_operation_delegate.h @@ -0,0 +1,101 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_RECURSIVE_OPERATION_DELEGATE_H_ +#define WEBKIT_BROWSER_FILEAPI_RECURSIVE_OPERATION_DELEGATE_H_ + +#include <queue> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "webkit/browser/fileapi/file_system_operation.h" +#include "webkit/browser/fileapi/file_system_url.h" + +namespace fileapi { + +class FileSystemContext; +class FileSystemOperationRunner; + +// A base class for recursive operation delegates. +// This also provides some convenient default implementations for subclasses +// like StartRecursiveOperation() and NewNestedOperation(). +// +// In short, each subclass should override ProcessFile and ProcessDirectory +// to process a directory or a file. To start the recursive operation it +// should also call StartRecursiveOperation. +// +// Each subclass can call NewNestedOperation to create a new file system +// operation to perform a sub-operations, e.g. can call RemoveFile for +// recursive Remove. +class RecursiveOperationDelegate + : public base::SupportsWeakPtr<RecursiveOperationDelegate> { + public: + typedef FileSystemOperation::StatusCallback StatusCallback; + typedef FileSystemOperation::FileEntryList FileEntryList; + + RecursiveOperationDelegate(FileSystemContext* file_system_context); + virtual ~RecursiveOperationDelegate(); + + // This is called when the consumer of this instance starts a non-recursive + // operation. + virtual void Run() = 0; + + // This is called when the consumer of this instance starts a recursive + // operation. + virtual void RunRecursively() = 0; + + // This is called each time a file is found while recursively + // performing an operation. + virtual void ProcessFile(const FileSystemURL& url, + const StatusCallback& callback) = 0; + + // This is called each time a directory is found while recursively + // performing an operation. + virtual void ProcessDirectory(const FileSystemURL& url, + const StatusCallback& callback) = 0; + + protected: + // Starts to process files/directories recursively from the given |root|. + // This will call ProcessFile and ProcessDirectory on each directory or file. + // If the given |root| is a file this simply calls ProcessFile and exits. + // + // |callback| is fired with base::PLATFORM_FILE_OK when every file/directory + // under |root| is processed, or fired earlier when any suboperation fails. + void StartRecursiveOperation(const FileSystemURL& root, + const StatusCallback& callback); + + FileSystemContext* file_system_context() { return file_system_context_; } + const FileSystemContext* file_system_context() const { + return file_system_context_; + } + + FileSystemOperationRunner* operation_runner(); + + private: + void ProcessNextDirectory(); + void ProcessPendingFiles(); + void DidProcessFile(base::PlatformFileError error); + void DidProcessDirectory(const FileSystemURL& url, + base::PlatformFileError error); + void DidReadDirectory( + const FileSystemURL& parent, + base::PlatformFileError error, + const FileEntryList& entries, + bool has_more); + void DidTryProcessFile(base::PlatformFileError previous_error, + base::PlatformFileError error); + + FileSystemContext* file_system_context_; + StatusCallback callback_; + std::queue<FileSystemURL> pending_directories_; + std::queue<FileSystemURL> pending_files_; + int inflight_operations_; + + DISALLOW_COPY_AND_ASSIGN(RecursiveOperationDelegate); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_RECURSIVE_OPERATION_DELEGATE_H_ diff --git a/chromium/webkit/browser/fileapi/remove_operation_delegate.cc b/chromium/webkit/browser/fileapi/remove_operation_delegate.cc new file mode 100644 index 00000000000..fcd9ba9cee2 --- /dev/null +++ b/chromium/webkit/browser/fileapi/remove_operation_delegate.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/remove_operation_delegate.h" + +#include "base/bind.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" + +namespace fileapi { + +RemoveOperationDelegate::RemoveOperationDelegate( + FileSystemContext* file_system_context, + const FileSystemURL& url, + const StatusCallback& callback) + : RecursiveOperationDelegate(file_system_context), + url_(url), + callback_(callback), + weak_factory_(this) { +} + +RemoveOperationDelegate::~RemoveOperationDelegate() {} + +void RemoveOperationDelegate::Run() { + operation_runner()->RemoveFile(url_, base::Bind( + &RemoveOperationDelegate::DidTryRemoveFile, weak_factory_.GetWeakPtr())); +} + +void RemoveOperationDelegate::RunRecursively() { + StartRecursiveOperation( + url_, + base::Bind(&RemoveOperationDelegate::RemoveNextDirectory, + weak_factory_.GetWeakPtr())); +} + +void RemoveOperationDelegate::ProcessFile(const FileSystemURL& url, + const StatusCallback& callback) { + if (to_remove_directories_.size() == 1u && + to_remove_directories_.top() == url) { + // We seem to have been re-directed from ProcessDirectory. + to_remove_directories_.pop(); + } + operation_runner()->RemoveFile(url, base::Bind( + &RemoveOperationDelegate::DidRemoveFile, + weak_factory_.GetWeakPtr(), callback)); +} + +void RemoveOperationDelegate::ProcessDirectory(const FileSystemURL& url, + const StatusCallback& callback) { + to_remove_directories_.push(url); + callback.Run(base::PLATFORM_FILE_OK); +} + +void RemoveOperationDelegate::DidTryRemoveFile( + base::PlatformFileError error) { + if (error == base::PLATFORM_FILE_OK || + error != base::PLATFORM_FILE_ERROR_NOT_A_FILE) { + callback_.Run(error); + return; + } + operation_runner()->RemoveDirectory(url_, callback_); +} + +void RemoveOperationDelegate::DidRemoveFile(const StatusCallback& callback, + base::PlatformFileError error) { + if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) { + callback.Run(base::PLATFORM_FILE_OK); + return; + } + callback.Run(error); +} + +void RemoveOperationDelegate::RemoveNextDirectory( + base::PlatformFileError error) { + if (error != base::PLATFORM_FILE_OK || + to_remove_directories_.empty()) { + callback_.Run(error); + return; + } + FileSystemURL url = to_remove_directories_.top(); + to_remove_directories_.pop(); + operation_runner()->RemoveDirectory(url, base::Bind( + &RemoveOperationDelegate::RemoveNextDirectory, + weak_factory_.GetWeakPtr())); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/remove_operation_delegate.h b/chromium/webkit/browser/fileapi/remove_operation_delegate.h new file mode 100644 index 00000000000..ef9e1a09409 --- /dev/null +++ b/chromium/webkit/browser/fileapi/remove_operation_delegate.h @@ -0,0 +1,48 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_REMOVE_OPERATION_DELEGATE_H_ +#define WEBKIT_BROWSER_FILEAPI_REMOVE_OPERATION_DELEGATE_H_ + +#include <stack> + +#include "webkit/browser/fileapi/recursive_operation_delegate.h" + +namespace fileapi { + +class RemoveOperationDelegate + : public RecursiveOperationDelegate { + public: + RemoveOperationDelegate(FileSystemContext* file_system_context, + const FileSystemURL& url, + const StatusCallback& callback); + virtual ~RemoveOperationDelegate(); + + // RecursiveOperationDelegate overrides: + virtual void Run() OVERRIDE; + virtual void RunRecursively() OVERRIDE; + virtual void ProcessFile(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + virtual void ProcessDirectory(const FileSystemURL& url, + const StatusCallback& callback) OVERRIDE; + + private: + void DidTryRemoveFile(base::PlatformFileError error); + void DidRemoveFile(const StatusCallback& callback, + base::PlatformFileError error); + void RemoveNextDirectory(base::PlatformFileError error); + + FileSystemURL url_; + StatusCallback callback_; + + std::stack<FileSystemURL> to_remove_directories_; + + base::WeakPtrFactory<RemoveOperationDelegate> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(RemoveOperationDelegate); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_REMOVE_OPERATION_DELEGATE_H_ diff --git a/chromium/webkit/browser/fileapi/sandbox_database_test_helper.cc b/chromium/webkit/browser/fileapi/sandbox_database_test_helper.cc new file mode 100644 index 00000000000..6364b2bd7f5 --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_database_test_helper.cc @@ -0,0 +1,100 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/sandbox_database_test_helper.h" + +#include <algorithm> +#include <functional> +#include <vector> + +#include "base/file_util.h" +#include "base/files/file_enumerator.h" +#include "base/stl_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +void CorruptDatabase(const base::FilePath& db_path, + leveldb::FileType type, + ptrdiff_t offset, + size_t size) { + base::FileEnumerator file_enum(db_path, false /* not recursive */, + base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES); + base::FilePath file_path; + base::FilePath picked_file_path; + uint64 picked_file_number = kuint64max; + + while (!(file_path = file_enum.Next()).empty()) { + uint64 number = kuint64max; + leveldb::FileType file_type; + EXPECT_TRUE(leveldb::ParseFileName(FilePathToString(file_path.BaseName()), + &number, &file_type)); + if (file_type == type && + (picked_file_number == kuint64max || picked_file_number < number)) { + picked_file_path = file_path; + picked_file_number = number; + } + } + + EXPECT_FALSE(picked_file_path.empty()); + EXPECT_NE(kuint64max, picked_file_number); + + bool created = true; + base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + base::PlatformFile file = + CreatePlatformFile(picked_file_path, + base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE, + &created, &error); + EXPECT_EQ(base::PLATFORM_FILE_OK, error); + EXPECT_FALSE(created); + + base::PlatformFileInfo file_info; + EXPECT_TRUE(base::GetPlatformFileInfo(file, &file_info)); + if (offset < 0) + offset += file_info.size; + EXPECT_GE(offset, 0); + EXPECT_LE(offset, file_info.size); + + size = std::min(size, static_cast<size_t>(file_info.size - offset)); + + std::vector<char> buf(size); + int read_size = base::ReadPlatformFile(file, offset, + vector_as_array(&buf), buf.size()); + EXPECT_LT(0, read_size); + EXPECT_GE(buf.size(), static_cast<size_t>(read_size)); + buf.resize(read_size); + + std::transform(buf.begin(), buf.end(), buf.begin(), + std::logical_not<char>()); + + int written_size = base::WritePlatformFile(file, offset, + vector_as_array(&buf), buf.size()); + EXPECT_GT(written_size, 0); + EXPECT_EQ(buf.size(), static_cast<size_t>(written_size)); + + base::ClosePlatformFile(file); +} + +void DeleteDatabaseFile(const base::FilePath& db_path, + leveldb::FileType type) { + base::FileEnumerator file_enum(db_path, false /* not recursive */, + base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES); + base::FilePath file_path; + while (!(file_path = file_enum.Next()).empty()) { + uint64 number = kuint64max; + leveldb::FileType file_type; + EXPECT_TRUE(leveldb::ParseFileName(FilePathToString(file_path.BaseName()), + &number, &file_type)); + if (file_type == type) { + base::DeleteFile(file_path, false); + // We may have multiple files for the same type, so don't break here. + } + } + +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_database_test_helper.h b/chromium/webkit/browser/fileapi/sandbox_database_test_helper.h new file mode 100644 index 00000000000..1635c2fd265 --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_database_test_helper.h @@ -0,0 +1,28 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_DATABASE_TEST_HELPER_H_ +#define WEBKIT_BROWSER_FILEAPI_SANDBOX_DATABASE_TEST_HELPER_H_ + +#include <cstddef> + +#include "third_party/leveldatabase/src/db/filename.h" + +namespace base { +class FilePath; +} + +namespace fileapi { + +void CorruptDatabase(const base::FilePath& db_path, + leveldb::FileType type, + ptrdiff_t offset, + size_t size); + +void DeleteDatabaseFile(const base::FilePath& db_path, + leveldb::FileType type); + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_DATABASE_TEST_HELPER_H_ diff --git a/chromium/webkit/browser/fileapi/sandbox_directory_database.cc b/chromium/webkit/browser/fileapi/sandbox_directory_database.cc new file mode 100644 index 00000000000..24790b15e31 --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_directory_database.cc @@ -0,0 +1,928 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/sandbox_directory_database.h" + +#include <math.h> +#include <algorithm> +#include <set> +#include <stack> + +#include "base/file_util.h" +#include "base/files/file_enumerator.h" +#include "base/location.h" +#include "base/metrics/histogram.h" +#include "base/pickle.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" +#include "third_party/leveldatabase/src/include/leveldb/write_batch.h" +#include "webkit/browser/fileapi/file_system_usage_cache.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace { + +bool PickleFromFileInfo( + const fileapi::SandboxDirectoryDatabase::FileInfo& info, + Pickle* pickle) { + DCHECK(pickle); + std::string data_path; + // Round off here to match the behavior of the filesystem on real files. + base::Time time = + base::Time::FromDoubleT(floor(info.modification_time.ToDoubleT())); + std::string name; + + data_path = fileapi::FilePathToString(info.data_path); + name = fileapi::FilePathToString(base::FilePath(info.name)); + + if (pickle->WriteInt64(info.parent_id) && + pickle->WriteString(data_path) && + pickle->WriteString(name) && + pickle->WriteInt64(time.ToInternalValue())) + return true; + + NOTREACHED(); + return false; +} + +bool FileInfoFromPickle( + const Pickle& pickle, + fileapi::SandboxDirectoryDatabase::FileInfo* info) { + PickleIterator iter(pickle); + std::string data_path; + std::string name; + int64 internal_time; + + if (pickle.ReadInt64(&iter, &info->parent_id) && + pickle.ReadString(&iter, &data_path) && + pickle.ReadString(&iter, &name) && + pickle.ReadInt64(&iter, &internal_time)) { + info->data_path = fileapi::StringToFilePath(data_path); + info->name = fileapi::StringToFilePath(name).value(); + info->modification_time = base::Time::FromInternalValue(internal_time); + return true; + } + LOG(ERROR) << "Pickle could not be digested!"; + return false; +} + +const base::FilePath::CharType kDirectoryDatabaseName[] = + FILE_PATH_LITERAL("Paths"); +const char kChildLookupPrefix[] = "CHILD_OF:"; +const char kChildLookupSeparator[] = ":"; +const char kLastFileIdKey[] = "LAST_FILE_ID"; +const char kLastIntegerKey[] = "LAST_INTEGER"; +const int64 kMinimumReportIntervalHours = 1; +const char kInitStatusHistogramLabel[] = "FileSystem.DirectoryDatabaseInit"; +const char kDatabaseRepairHistogramLabel[] = + "FileSystem.DirectoryDatabaseRepair"; + +enum InitStatus { + INIT_STATUS_OK = 0, + INIT_STATUS_CORRUPTION, + INIT_STATUS_IO_ERROR, + INIT_STATUS_UNKNOWN_ERROR, + INIT_STATUS_MAX +}; + +enum RepairResult { + DB_REPAIR_SUCCEEDED = 0, + DB_REPAIR_FAILED, + DB_REPAIR_MAX +}; + +std::string GetChildLookupKey( + fileapi::SandboxDirectoryDatabase::FileId parent_id, + const base::FilePath::StringType& child_name) { + std::string name; + name = fileapi::FilePathToString(base::FilePath(child_name)); + return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) + + std::string(kChildLookupSeparator) + name; +} + +std::string GetChildListingKeyPrefix( + fileapi::SandboxDirectoryDatabase::FileId parent_id) { + return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) + + std::string(kChildLookupSeparator); +} + +const char* LastFileIdKey() { + return kLastFileIdKey; +} + +const char* LastIntegerKey() { + return kLastIntegerKey; +} + +std::string GetFileLookupKey( + fileapi::SandboxDirectoryDatabase::FileId file_id) { + return base::Int64ToString(file_id); +} + +// Assumptions: +// - Any database entry is one of: +// - ("CHILD_OF:|parent_id|:<name>", "|file_id|"), +// - ("LAST_FILE_ID", "|last_file_id|"), +// - ("LAST_INTEGER", "|last_integer|"), +// - ("|file_id|", "pickled FileInfo") +// where FileInfo has |parent_id|, |data_path|, |name| and +// |modification_time|, +// Constraints: +// - Each file in the database has unique backing file. +// - Each file in |filesystem_data_directory_| has a database entry. +// - Directory structure is tree, i.e. connected and acyclic. +class DatabaseCheckHelper { + public: + typedef fileapi::SandboxDirectoryDatabase::FileId FileId; + typedef fileapi::SandboxDirectoryDatabase::FileInfo FileInfo; + + DatabaseCheckHelper(fileapi::SandboxDirectoryDatabase* dir_db, + leveldb::DB* db, + const base::FilePath& path); + + bool IsFileSystemConsistent() { + return IsDatabaseEmpty() || + (ScanDatabase() && ScanDirectory() && ScanHierarchy()); + } + + private: + bool IsDatabaseEmpty(); + // These 3 methods need to be called in the order. Each method requires its + // previous method finished successfully. They also require the database is + // not empty. + bool ScanDatabase(); + bool ScanDirectory(); + bool ScanHierarchy(); + + fileapi::SandboxDirectoryDatabase* dir_db_; + leveldb::DB* db_; + base::FilePath path_; + + std::set<base::FilePath> files_in_db_; + + size_t num_directories_in_db_; + size_t num_files_in_db_; + size_t num_hierarchy_links_in_db_; + + FileId last_file_id_; + FileId last_integer_; +}; + +DatabaseCheckHelper::DatabaseCheckHelper( + fileapi::SandboxDirectoryDatabase* dir_db, + leveldb::DB* db, + const base::FilePath& path) + : dir_db_(dir_db), db_(db), path_(path), + num_directories_in_db_(0), + num_files_in_db_(0), + num_hierarchy_links_in_db_(0), + last_file_id_(-1), last_integer_(-1) { + DCHECK(dir_db_); + DCHECK(db_); + DCHECK(!path_.empty() && base::DirectoryExists(path_)); +} + +bool DatabaseCheckHelper::IsDatabaseEmpty() { + scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); + itr->SeekToFirst(); + return !itr->Valid(); +} + +bool DatabaseCheckHelper::ScanDatabase() { + // Scans all database entries sequentially to verify each of them has unique + // backing file. + int64 max_file_id = -1; + std::set<FileId> file_ids; + + scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); + for (itr->SeekToFirst(); itr->Valid(); itr->Next()) { + std::string key = itr->key().ToString(); + if (StartsWithASCII(key, kChildLookupPrefix, true)) { + // key: "CHILD_OF:<parent_id>:<name>" + // value: "<child_id>" + ++num_hierarchy_links_in_db_; + } else if (key == kLastFileIdKey) { + // key: "LAST_FILE_ID" + // value: "<last_file_id>" + if (last_file_id_ >= 0 || + !base::StringToInt64(itr->value().ToString(), &last_file_id_)) + return false; + + if (last_file_id_ < 0) + return false; + } else if (key == kLastIntegerKey) { + // key: "LAST_INTEGER" + // value: "<last_integer>" + if (last_integer_ >= 0 || + !base::StringToInt64(itr->value().ToString(), &last_integer_)) + return false; + } else { + // key: "<entry_id>" + // value: "<pickled FileInfo>" + FileInfo file_info; + if (!FileInfoFromPickle( + Pickle(itr->value().data(), itr->value().size()), &file_info)) + return false; + + FileId file_id = -1; + if (!base::StringToInt64(key, &file_id) || file_id < 0) + return false; + + if (max_file_id < file_id) + max_file_id = file_id; + if (!file_ids.insert(file_id).second) + return false; + + if (file_info.is_directory()) { + ++num_directories_in_db_; + DCHECK(file_info.data_path.empty()); + } else { + // Ensure any pair of file entry don't share their data_path. + if (!files_in_db_.insert(file_info.data_path).second) + return false; + + // Ensure the backing file exists as a normal file. + base::PlatformFileInfo platform_file_info; + if (!file_util::GetFileInfo( + path_.Append(file_info.data_path), &platform_file_info) || + platform_file_info.is_directory || + platform_file_info.is_symbolic_link) { + // leveldb::Iterator iterates a snapshot of the database. + // So even after RemoveFileInfo() call, we'll visit hierarchy link + // from |parent_id| to |file_id|. + if (!dir_db_->RemoveFileInfo(file_id)) + return false; + --num_hierarchy_links_in_db_; + files_in_db_.erase(file_info.data_path); + } else { + ++num_files_in_db_; + } + } + } + } + + // TODO(tzik): Add constraint for |last_integer_| to avoid possible + // data path confliction on ObfuscatedFileUtil. + return max_file_id <= last_file_id_; +} + +bool DatabaseCheckHelper::ScanDirectory() { + // TODO(kinuko): Scans all local file system entries to verify each of them + // has a database entry. + const base::FilePath kExcludes[] = { + base::FilePath(kDirectoryDatabaseName), + base::FilePath(fileapi::FileSystemUsageCache::kUsageFileName), + }; + + // Any path in |pending_directories| is relative to |path_|. + std::stack<base::FilePath> pending_directories; + pending_directories.push(base::FilePath()); + + while (!pending_directories.empty()) { + base::FilePath dir_path = pending_directories.top(); + pending_directories.pop(); + + base::FileEnumerator file_enum( + dir_path.empty() ? path_ : path_.Append(dir_path), + false /* not recursive */, + base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES); + + base::FilePath absolute_file_path; + while (!(absolute_file_path = file_enum.Next()).empty()) { + base::FileEnumerator::FileInfo find_info = file_enum.GetInfo(); + + base::FilePath relative_file_path; + if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path)) + return false; + + if (std::find(kExcludes, kExcludes + arraysize(kExcludes), + relative_file_path) != kExcludes + arraysize(kExcludes)) + continue; + + if (find_info.IsDirectory()) { + pending_directories.push(relative_file_path); + continue; + } + + // Check if the file has a database entry. + std::set<base::FilePath>::iterator itr = + files_in_db_.find(relative_file_path); + if (itr == files_in_db_.end()) { + if (!base::DeleteFile(absolute_file_path, false)) + return false; + } else { + files_in_db_.erase(itr); + } + } + } + + return files_in_db_.empty(); +} + +bool DatabaseCheckHelper::ScanHierarchy() { + size_t visited_directories = 0; + size_t visited_files = 0; + size_t visited_links = 0; + + std::stack<FileId> directories; + directories.push(0); + + // Check if the root directory exists as a directory. + FileInfo file_info; + if (!dir_db_->GetFileInfo(0, &file_info)) + return false; + if (file_info.parent_id != 0 || + !file_info.is_directory()) + return false; + + while (!directories.empty()) { + ++visited_directories; + FileId dir_id = directories.top(); + directories.pop(); + + std::vector<FileId> children; + if (!dir_db_->ListChildren(dir_id, &children)) + return false; + for (std::vector<FileId>::iterator itr = children.begin(); + itr != children.end(); + ++itr) { + // Any directory must not have root directory as child. + if (!*itr) + return false; + + // Check if the child knows the parent as its parent. + FileInfo file_info; + if (!dir_db_->GetFileInfo(*itr, &file_info)) + return false; + if (file_info.parent_id != dir_id) + return false; + + // Check if the parent knows the name of its child correctly. + FileId file_id; + if (!dir_db_->GetChildWithName(dir_id, file_info.name, &file_id) || + file_id != *itr) + return false; + + if (file_info.is_directory()) + directories.push(*itr); + else + ++visited_files; + ++visited_links; + } + } + + // Check if we've visited all database entries. + return num_directories_in_db_ == visited_directories && + num_files_in_db_ == visited_files && + num_hierarchy_links_in_db_ == visited_links; +} + +// Returns true if the given |data_path| contains no parent references ("..") +// and does not refer to special system files. +// This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to +// ensure we're only dealing with valid data paths. +bool VerifyDataPath(const base::FilePath& data_path) { + // |data_path| should not contain any ".." and should be a relative path + // (to the filesystem_data_directory_). + if (data_path.ReferencesParent() || data_path.IsAbsolute()) + return false; + // See if it's not pointing to the special system paths. + const base::FilePath kExcludes[] = { + base::FilePath(kDirectoryDatabaseName), + base::FilePath(fileapi::FileSystemUsageCache::kUsageFileName), + }; + for (size_t i = 0; i < arraysize(kExcludes); ++i) { + if (data_path == kExcludes[i] || kExcludes[i].IsParent(data_path)) + return false; + } + return true; +} + +} // namespace + +namespace fileapi { + +SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) { +} + +SandboxDirectoryDatabase::FileInfo::~FileInfo() { +} + +SandboxDirectoryDatabase::SandboxDirectoryDatabase( + const base::FilePath& filesystem_data_directory) + : filesystem_data_directory_(filesystem_data_directory) { +} + +SandboxDirectoryDatabase::~SandboxDirectoryDatabase() { +} + +bool SandboxDirectoryDatabase::GetChildWithName( + FileId parent_id, + const base::FilePath::StringType& name, + FileId* child_id) { + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + DCHECK(child_id); + std::string child_key = GetChildLookupKey(parent_id, name); + std::string child_id_string; + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), child_key, &child_id_string); + if (status.IsNotFound()) + return false; + if (status.ok()) { + if (!base::StringToInt64(child_id_string, child_id)) { + LOG(ERROR) << "Hit database corruption!"; + return false; + } + return true; + } + HandleError(FROM_HERE, status); + return false; +} + +bool SandboxDirectoryDatabase::GetFileWithPath( + const base::FilePath& path, FileId* file_id) { + std::vector<base::FilePath::StringType> components; + VirtualPath::GetComponents(path, &components); + FileId local_id = 0; + std::vector<base::FilePath::StringType>::iterator iter; + for (iter = components.begin(); iter != components.end(); ++iter) { + base::FilePath::StringType name; + name = *iter; + if (name == FILE_PATH_LITERAL("/")) + continue; + if (!GetChildWithName(local_id, name, &local_id)) + return false; + } + *file_id = local_id; + return true; +} + +bool SandboxDirectoryDatabase::ListChildren( + FileId parent_id, std::vector<FileId>* children) { + // Check to add later: fail if parent is a file, at least in debug builds. + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + DCHECK(children); + std::string child_key_prefix = GetChildListingKeyPrefix(parent_id); + + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); + iter->Seek(child_key_prefix); + children->clear(); + while (iter->Valid() && + StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) { + std::string child_id_string = iter->value().ToString(); + FileId child_id; + if (!base::StringToInt64(child_id_string, &child_id)) { + LOG(ERROR) << "Hit database corruption!"; + return false; + } + children->push_back(child_id); + iter->Next(); + } + return true; +} + +bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) { + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + DCHECK(info); + std::string file_key = GetFileLookupKey(file_id); + std::string file_data_string; + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), file_key, &file_data_string); + if (status.ok()) { + bool success = FileInfoFromPickle( + Pickle(file_data_string.data(), file_data_string.length()), info); + if (!success) + return false; + if (!VerifyDataPath(info->data_path)) { + LOG(ERROR) << "Resolved data path is invalid: " + << info->data_path.value(); + return false; + } + return true; + } + // Special-case the root, for databases that haven't been initialized yet. + // Without this, a query for the root's file info, made before creating the + // first file in the database, will fail and confuse callers. + if (status.IsNotFound() && !file_id) { + info->name = base::FilePath::StringType(); + info->data_path = base::FilePath(); + info->modification_time = base::Time::Now(); + info->parent_id = 0; + return true; + } + HandleError(FROM_HERE, status); + return false; +} + +bool SandboxDirectoryDatabase::AddFileInfo( + const FileInfo& info, FileId* file_id) { + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + DCHECK(file_id); + std::string child_key = GetChildLookupKey(info.parent_id, info.name); + std::string child_id_string; + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), child_key, &child_id_string); + if (status.ok()) { + LOG(ERROR) << "File exists already!"; + return false; + } + if (!status.IsNotFound()) { + HandleError(FROM_HERE, status); + return false; + } + + if (!VerifyIsDirectory(info.parent_id)) + return false; + + // This would be a fine place to limit the number of files in a directory, if + // we decide to add that restriction. + + FileId temp_id; + if (!GetLastFileId(&temp_id)) + return false; + ++temp_id; + + leveldb::WriteBatch batch; + if (!AddFileInfoHelper(info, temp_id, &batch)) + return false; + + batch.Put(LastFileIdKey(), base::Int64ToString(temp_id)); + status = db_->Write(leveldb::WriteOptions(), &batch); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + *file_id = temp_id; + return true; +} + +bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) { + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + leveldb::WriteBatch batch; + if (!RemoveFileInfoHelper(file_id, &batch)) + return false; + leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + return true; +} + +bool SandboxDirectoryDatabase::UpdateFileInfo( + FileId file_id, const FileInfo& new_info) { + // TODO(ericu): We should also check to see that this doesn't create a loop, + // but perhaps only in a debug build. + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + DCHECK(file_id); // You can't remove the root, ever. Just delete the DB. + FileInfo old_info; + if (!GetFileInfo(file_id, &old_info)) + return false; + if (old_info.parent_id != new_info.parent_id && + !VerifyIsDirectory(new_info.parent_id)) + return false; + if (old_info.parent_id != new_info.parent_id || + old_info.name != new_info.name) { + // Check for name clashes. + FileId temp_id; + if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) { + LOG(ERROR) << "Name collision on move."; + return false; + } + } + leveldb::WriteBatch batch; + if (!RemoveFileInfoHelper(file_id, &batch) || + !AddFileInfoHelper(new_info, file_id, &batch)) + return false; + leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + return true; +} + +bool SandboxDirectoryDatabase::UpdateModificationTime( + FileId file_id, const base::Time& modification_time) { + FileInfo info; + if (!GetFileInfo(file_id, &info)) + return false; + info.modification_time = modification_time; + Pickle pickle; + if (!PickleFromFileInfo(info, &pickle)) + return false; + leveldb::Status status = db_->Put( + leveldb::WriteOptions(), + GetFileLookupKey(file_id), + leveldb::Slice(reinterpret_cast<const char *>(pickle.data()), + pickle.size())); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + return true; +} + +bool SandboxDirectoryDatabase::OverwritingMoveFile( + FileId src_file_id, FileId dest_file_id) { + FileInfo src_file_info; + FileInfo dest_file_info; + + if (!GetFileInfo(src_file_id, &src_file_info)) + return false; + if (!GetFileInfo(dest_file_id, &dest_file_info)) + return false; + if (src_file_info.is_directory() || dest_file_info.is_directory()) + return false; + leveldb::WriteBatch batch; + // This is the only field that really gets moved over; if you add fields to + // FileInfo, e.g. ctime, they might need to be copied here. + dest_file_info.data_path = src_file_info.data_path; + if (!RemoveFileInfoHelper(src_file_id, &batch)) + return false; + Pickle pickle; + if (!PickleFromFileInfo(dest_file_info, &pickle)) + return false; + batch.Put( + GetFileLookupKey(dest_file_id), + leveldb::Slice(reinterpret_cast<const char *>(pickle.data()), + pickle.size())); + leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + return true; +} + +bool SandboxDirectoryDatabase::GetNextInteger(int64* next) { + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + DCHECK(next); + std::string int_string; + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string); + if (status.ok()) { + int64 temp; + if (!base::StringToInt64(int_string, &temp)) { + LOG(ERROR) << "Hit database corruption!"; + return false; + } + ++temp; + status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(), + base::Int64ToString(temp)); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + *next = temp; + return true; + } + if (!status.IsNotFound()) { + HandleError(FROM_HERE, status); + return false; + } + // The database must not yet exist; initialize it. + if (!StoreDefaultValues()) + return false; + + return GetNextInteger(next); +} + +// static +bool SandboxDirectoryDatabase::DestroyDatabase(const base::FilePath& path) { + std::string name = FilePathToString(path.Append(kDirectoryDatabaseName)); + leveldb::Status status = leveldb::DestroyDB(name, leveldb::Options()); + if (status.ok()) + return true; + LOG(WARNING) << "Failed to destroy a database with status " << + status.ToString(); + return false; +} + +bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) { + if (db_) + return true; + + std::string path = + FilePathToString(filesystem_data_directory_.Append( + kDirectoryDatabaseName)); + leveldb::Options options; + options.max_open_files = 0; // Use minimum. + options.create_if_missing = true; + leveldb::DB* db; + leveldb::Status status = leveldb::DB::Open(options, path, &db); + ReportInitStatus(status); + if (status.ok()) { + db_.reset(db); + return true; + } + HandleError(FROM_HERE, status); + + // Corruption due to missing necessary MANIFEST-* file causes IOError instead + // of Corruption error. + // Try to repair database even when IOError case. + if (!status.IsCorruption() && !status.IsIOError()) + return false; + + switch (recovery_option) { + case FAIL_ON_CORRUPTION: + return false; + case REPAIR_ON_CORRUPTION: + LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected." + << " Attempting to repair."; + if (RepairDatabase(path)) { + UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel, + DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX); + return true; + } + UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel, + DB_REPAIR_FAILED, DB_REPAIR_MAX); + LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase."; + // fall through + case DELETE_ON_CORRUPTION: + LOG(WARNING) << "Clearing SandboxDirectoryDatabase."; + if (!base::DeleteFile(filesystem_data_directory_, true)) + return false; + if (!file_util::CreateDirectory(filesystem_data_directory_)) + return false; + return Init(FAIL_ON_CORRUPTION); + } + + NOTREACHED(); + return false; +} + +bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) { + DCHECK(!db_.get()); + leveldb::Options options; + options.max_open_files = 0; // Use minimum. + if (!leveldb::RepairDB(db_path, options).ok()) + return false; + if (!Init(FAIL_ON_CORRUPTION)) + return false; + if (IsFileSystemConsistent()) + return true; + db_.reset(); + return false; +} + +bool SandboxDirectoryDatabase::IsFileSystemConsistent() { + if (!Init(FAIL_ON_CORRUPTION)) + return false; + DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_); + return helper.IsFileSystemConsistent(); +} + +void SandboxDirectoryDatabase::ReportInitStatus( + const leveldb::Status& status) { + base::Time now = base::Time::Now(); + const base::TimeDelta minimum_interval = + base::TimeDelta::FromHours(kMinimumReportIntervalHours); + if (last_reported_time_ + minimum_interval >= now) + return; + last_reported_time_ = now; + + if (status.ok()) { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_OK, INIT_STATUS_MAX); + } else if (status.IsCorruption()) { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_CORRUPTION, INIT_STATUS_MAX); + } else if (status.IsIOError()) { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_IO_ERROR, INIT_STATUS_MAX); + } else { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX); + } +} + +bool SandboxDirectoryDatabase::StoreDefaultValues() { + // Verify that this is a totally new database, and initialize it. + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); + iter->SeekToFirst(); + if (iter->Valid()) { // DB was not empty--we shouldn't have been called. + LOG(ERROR) << "File system origin database is corrupt!"; + return false; + } + // This is always the first write into the database. If we ever add a + // version number, it should go in this transaction too. + FileInfo root; + root.parent_id = 0; + root.modification_time = base::Time::Now(); + leveldb::WriteBatch batch; + if (!AddFileInfoHelper(root, 0, &batch)) + return false; + batch.Put(LastFileIdKey(), base::Int64ToString(0)); + batch.Put(LastIntegerKey(), base::Int64ToString(-1)); + leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + return true; +} + +bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) { + if (!Init(REPAIR_ON_CORRUPTION)) + return false; + DCHECK(file_id); + std::string id_string; + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string); + if (status.ok()) { + if (!base::StringToInt64(id_string, file_id)) { + LOG(ERROR) << "Hit database corruption!"; + return false; + } + return true; + } + if (!status.IsNotFound()) { + HandleError(FROM_HERE, status); + return false; + } + // The database must not yet exist; initialize it. + if (!StoreDefaultValues()) + return false; + *file_id = 0; + return true; +} + +bool SandboxDirectoryDatabase::VerifyIsDirectory(FileId file_id) { + FileInfo info; + if (!file_id) + return true; // The root is a directory. + if (!GetFileInfo(file_id, &info)) + return false; + if (!info.is_directory()) { + LOG(ERROR) << "New parent directory is a file!"; + return false; + } + return true; +} + +// This does very few safety checks! +bool SandboxDirectoryDatabase::AddFileInfoHelper( + const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) { + if (!VerifyDataPath(info.data_path)) { + LOG(ERROR) << "Invalid data path is given: " << info.data_path.value(); + return false; + } + std::string id_string = GetFileLookupKey(file_id); + if (!file_id) { + // The root directory doesn't need to be looked up by path from its parent. + DCHECK(!info.parent_id); + DCHECK(info.data_path.empty()); + } else { + std::string child_key = GetChildLookupKey(info.parent_id, info.name); + batch->Put(child_key, id_string); + } + Pickle pickle; + if (!PickleFromFileInfo(info, &pickle)) + return false; + batch->Put( + id_string, + leveldb::Slice(reinterpret_cast<const char *>(pickle.data()), + pickle.size())); + return true; +} + +// This does very few safety checks! +bool SandboxDirectoryDatabase::RemoveFileInfoHelper( + FileId file_id, leveldb::WriteBatch* batch) { + DCHECK(file_id); // You can't remove the root, ever. Just delete the DB. + FileInfo info; + if (!GetFileInfo(file_id, &info)) + return false; + if (info.data_path.empty()) { // It's a directory + std::vector<FileId> children; + // TODO(ericu): Make a faster is-the-directory-empty check. + if (!ListChildren(file_id, &children)) + return false; + if (children.size()) { + LOG(ERROR) << "Can't remove a directory with children."; + return false; + } + } + batch->Delete(GetChildLookupKey(info.parent_id, info.name)); + batch->Delete(GetFileLookupKey(file_id)); + return true; +} + +void SandboxDirectoryDatabase::HandleError( + const tracked_objects::Location& from_here, + const leveldb::Status& status) { + LOG(ERROR) << "SandboxDirectoryDatabase failed at: " + << from_here.ToString() << " with error: " << status.ToString(); + db_.reset(); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_directory_database.h b/chromium/webkit/browser/fileapi/sandbox_directory_database.h new file mode 100644 index 00000000000..4cbd43b6cbd --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_directory_database.h @@ -0,0 +1,125 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_DIRECTORY_DATABASE_H_ +#define WEBKIT_BROWSER_FILEAPI_SANDBOX_DIRECTORY_DATABASE_H_ + +#include <string> +#include <vector> + +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace tracked_objects { +class Location; +} + +namespace leveldb { +class DB; +class Status; +class WriteBatch; +} + +namespace fileapi { + +// This class WILL NOT protect you against producing directory loops, giving an +// empty directory a backing data file, giving two files the same backing file, +// or pointing to a nonexistent backing file. It does no file IO other than +// that involved with talking to its underlying database. It does not create or +// in any way touch real files; it only creates path entries in its database. + +// TODO(ericu): Safe mode, which does more checks such as the above on debug +// builds. +// TODO(ericu): Add a method that will give a unique filename for a data file. +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE SandboxDirectoryDatabase { + public: + typedef int64 FileId; + + struct WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileInfo { + FileInfo(); + ~FileInfo(); + + bool is_directory() const { + return data_path.empty(); + } + + FileId parent_id; + base::FilePath data_path; + base::FilePath::StringType name; + // This modification time is valid only for directories, not files, as + // FileWriter will get the files out of sync. + // For files, look at the modification time of the underlying data_path. + base::Time modification_time; + }; + + explicit SandboxDirectoryDatabase( + const base::FilePath& filesystem_data_directory); + ~SandboxDirectoryDatabase(); + + bool GetChildWithName( + FileId parent_id, + const base::FilePath::StringType& name, + FileId* child_id); + bool GetFileWithPath(const base::FilePath& path, FileId* file_id); + // ListChildren will succeed, returning 0 children, if parent_id doesn't + // exist. + bool ListChildren(FileId parent_id, std::vector<FileId>* children); + bool GetFileInfo(FileId file_id, FileInfo* info); + bool AddFileInfo(const FileInfo& info, FileId* file_id); + bool RemoveFileInfo(FileId file_id); + // This does a full update of the FileInfo, and is what you'd use for moves + // and renames. If you just want to update the modification_time, use + // UpdateModificationTime. + bool UpdateFileInfo(FileId file_id, const FileInfo& info); + bool UpdateModificationTime( + FileId file_id, const base::Time& modification_time); + // This is used for an overwriting move of a file [not a directory] on top of + // another file [also not a directory]; we need to alter two files' info in a + // single transaction to avoid weird backing file references in the event of a + // partial failure. + bool OverwritingMoveFile(FileId src_file_id, FileId dest_file_id); + + // This produces the series 0, 1, 2..., starting at 0 when the underlying + // filesystem is first created, and maintaining state across + // creation/destruction of SandboxDirectoryDatabase objects. + bool GetNextInteger(int64* next); + + // Returns true if the database looks consistent with local filesystem. + bool IsFileSystemConsistent(); + + static bool DestroyDatabase(const base::FilePath& path); + + private: + enum RecoveryOption { + DELETE_ON_CORRUPTION, + REPAIR_ON_CORRUPTION, + FAIL_ON_CORRUPTION, + }; + + friend class ObfuscatedFileUtil; + friend class SandboxDirectoryDatabaseTest; + + bool Init(RecoveryOption recovery_option); + bool RepairDatabase(const std::string& db_path); + void ReportInitStatus(const leveldb::Status& status); + bool StoreDefaultValues(); + bool GetLastFileId(FileId* file_id); + bool VerifyIsDirectory(FileId file_id); + bool AddFileInfoHelper( + const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch); + bool RemoveFileInfoHelper(FileId file_id, leveldb::WriteBatch* batch); + void HandleError(const tracked_objects::Location& from_here, + const leveldb::Status& status); + + const base::FilePath filesystem_data_directory_; + scoped_ptr<leveldb::DB> db_; + base::Time last_reported_time_; + DISALLOW_COPY_AND_ASSIGN(SandboxDirectoryDatabase); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_DIRECTORY_DATABASE_H_ diff --git a/chromium/webkit/browser/fileapi/sandbox_directory_database_unittest.cc b/chromium/webkit/browser/fileapi/sandbox_directory_database_unittest.cc new file mode 100644 index 00000000000..593ead96efb --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_directory_database_unittest.cc @@ -0,0 +1,678 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/sandbox_directory_database.h" + +#include <math.h> +#include <limits> + +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/platform_file.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" +#include "webkit/browser/fileapi/sandbox_database_test_helper.h" +#include "webkit/common/fileapi/file_system_util.h" + +#define FPL(x) FILE_PATH_LITERAL(x) + +namespace fileapi { + +namespace { +const base::FilePath::CharType kDirectoryDatabaseName[] = FPL("Paths"); +} + +class SandboxDirectoryDatabaseTest : public testing::Test { + public: + typedef SandboxDirectoryDatabase::FileId FileId; + typedef SandboxDirectoryDatabase::FileInfo FileInfo; + + SandboxDirectoryDatabaseTest() { + EXPECT_TRUE(base_.CreateUniqueTempDir()); + InitDatabase(); + } + + SandboxDirectoryDatabase* db() { + return db_.get(); + } + + void InitDatabase() { + // Call CloseDatabase() to avoid having multiple database instances for + // single directory at once. + CloseDatabase(); + db_.reset(new SandboxDirectoryDatabase(path())); + } + + void CloseDatabase() { + db_.reset(); + } + + bool AddFileInfo(FileId parent_id, const base::FilePath::StringType& name) { + FileId file_id; + FileInfo info; + info.parent_id = parent_id; + info.name = name; + return db_->AddFileInfo(info, &file_id); + } + + void CreateDirectory(FileId parent_id, + const base::FilePath::StringType& name, + FileId* file_id_out) { + FileInfo info; + info.parent_id = parent_id; + info.name = name; + ASSERT_TRUE(db_->AddFileInfo(info, file_id_out)); + } + + void CreateFile(FileId parent_id, + const base::FilePath::StringType& name, + const base::FilePath::StringType& data_path, + FileId* file_id_out) { + FileId file_id; + + FileInfo info; + info.parent_id = parent_id; + info.name = name; + info.data_path = base::FilePath(data_path).NormalizePathSeparators(); + ASSERT_TRUE(db_->AddFileInfo(info, &file_id)); + + base::FilePath local_path = path().Append(data_path); + if (!base::DirectoryExists(local_path.DirName())) + ASSERT_TRUE(file_util::CreateDirectory(local_path.DirName())); + + bool created = false; + base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + base::PlatformFile file = base::CreatePlatformFile( + local_path, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, + &created, &error); + ASSERT_EQ(base::PLATFORM_FILE_OK, error); + ASSERT_TRUE(created); + ASSERT_TRUE(base::ClosePlatformFile(file)); + + if (file_id_out) + *file_id_out = file_id; + } + + void ClearDatabaseAndDirectory() { + db_.reset(); + ASSERT_TRUE(base::DeleteFile(path(), true /* recursive */)); + ASSERT_TRUE(file_util::CreateDirectory(path())); + db_.reset(new SandboxDirectoryDatabase(path())); + } + + bool RepairDatabase() { + return db()->RepairDatabase( + FilePathToString(path().Append(kDirectoryDatabaseName))); + } + + const base::FilePath& path() { + return base_.path(); + } + + // Makes link from |parent_id| to |child_id| with |name|. + void MakeHierarchyLink(FileId parent_id, + FileId child_id, + const base::FilePath::StringType& name) { + ASSERT_TRUE(db()->db_->Put( + leveldb::WriteOptions(), + "CHILD_OF:" + base::Int64ToString(parent_id) + ":" + + FilePathToString(base::FilePath(name)), + base::Int64ToString(child_id)).ok()); + } + + // Deletes link from parent of |file_id| to |file_id|. + void DeleteHierarchyLink(FileId file_id) { + FileInfo file_info; + ASSERT_TRUE(db()->GetFileInfo(file_id, &file_info)); + ASSERT_TRUE(db()->db_->Delete( + leveldb::WriteOptions(), + "CHILD_OF:" + base::Int64ToString(file_info.parent_id) + ":" + + FilePathToString(base::FilePath(file_info.name))).ok()); + } + + protected: + // Common temp base for nondestructive uses. + base::ScopedTempDir base_; + scoped_ptr<SandboxDirectoryDatabase> db_; + + DISALLOW_COPY_AND_ASSIGN(SandboxDirectoryDatabaseTest); +}; + +TEST_F(SandboxDirectoryDatabaseTest, TestMissingFileGetInfo) { + FileId file_id = 888; + FileInfo info; + EXPECT_FALSE(db()->GetFileInfo(file_id, &info)); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestGetRootFileInfoBeforeCreate) { + FileId file_id = 0; + FileInfo info; + EXPECT_TRUE(db()->GetFileInfo(file_id, &info)); + EXPECT_EQ(0, info.parent_id); + EXPECT_TRUE(info.name.empty()); + EXPECT_TRUE(info.data_path.empty()); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestMissingParentAddFileInfo) { + FileId parent_id = 7; + EXPECT_FALSE(AddFileInfo(parent_id, FILE_PATH_LITERAL("foo"))); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestAddNameClash) { + FileInfo info; + FileId file_id; + info.parent_id = 0; + info.name = FILE_PATH_LITERAL("dir 0"); + EXPECT_TRUE(db()->AddFileInfo(info, &file_id)); + + // Check for name clash in the root directory. + base::FilePath::StringType name = info.name; + EXPECT_FALSE(AddFileInfo(0, name)); + name = FILE_PATH_LITERAL("dir 1"); + EXPECT_TRUE(AddFileInfo(0, name)); + + name = FILE_PATH_LITERAL("subdir 0"); + EXPECT_TRUE(AddFileInfo(file_id, name)); + + // Check for name clash in a subdirectory. + EXPECT_FALSE(AddFileInfo(file_id, name)); + name = FILE_PATH_LITERAL("subdir 1"); + EXPECT_TRUE(AddFileInfo(file_id, name)); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestRenameNoMoveNameClash) { + FileInfo info; + FileId file_id0; + base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo"); + base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar"); + base::FilePath::StringType name2 = FILE_PATH_LITERAL("bas"); + info.parent_id = 0; + info.name = name0; + EXPECT_TRUE(db()->AddFileInfo(info, &file_id0)); + EXPECT_TRUE(AddFileInfo(0, name1)); + info.name = name1; + EXPECT_FALSE(db()->UpdateFileInfo(file_id0, info)); + info.name = name2; + EXPECT_TRUE(db()->UpdateFileInfo(file_id0, info)); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestMoveSameNameNameClash) { + FileInfo info; + FileId file_id0; + FileId file_id1; + base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo"); + base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar"); + info.parent_id = 0; + info.name = name0; + EXPECT_TRUE(db()->AddFileInfo(info, &file_id0)); + info.parent_id = file_id0; + EXPECT_TRUE(db()->AddFileInfo(info, &file_id1)); + info.parent_id = 0; + EXPECT_FALSE(db()->UpdateFileInfo(file_id1, info)); + info.name = name1; + EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info)); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestMoveRenameNameClash) { + FileInfo info; + FileId file_id0; + FileId file_id1; + base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo"); + base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar"); + base::FilePath::StringType name2 = FILE_PATH_LITERAL("bas"); + info.parent_id = 0; + info.name = name0; + EXPECT_TRUE(db()->AddFileInfo(info, &file_id0)); + info.parent_id = file_id0; + info.name = name1; + EXPECT_TRUE(db()->AddFileInfo(info, &file_id1)); + info.parent_id = 0; + info.name = name0; + EXPECT_FALSE(db()->UpdateFileInfo(file_id1, info)); + info.name = name1; + EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info)); + // Also test a successful move+rename. + info.parent_id = file_id0; + info.name = name2; + EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info)); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestRemoveWithChildren) { + FileInfo info; + FileId file_id0; + FileId file_id1; + info.parent_id = 0; + info.name = FILE_PATH_LITERAL("foo"); + EXPECT_TRUE(db()->AddFileInfo(info, &file_id0)); + info.parent_id = file_id0; + EXPECT_TRUE(db()->AddFileInfo(info, &file_id1)); + EXPECT_FALSE(db()->RemoveFileInfo(file_id0)); + EXPECT_TRUE(db()->RemoveFileInfo(file_id1)); + EXPECT_TRUE(db()->RemoveFileInfo(file_id0)); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestGetChildWithName) { + FileInfo info; + FileId file_id0; + FileId file_id1; + base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo"); + base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar"); + info.parent_id = 0; + info.name = name0; + EXPECT_TRUE(db()->AddFileInfo(info, &file_id0)); + info.parent_id = file_id0; + info.name = name1; + EXPECT_TRUE(db()->AddFileInfo(info, &file_id1)); + EXPECT_NE(file_id0, file_id1); + + FileId check_file_id; + EXPECT_FALSE(db()->GetChildWithName(0, name1, &check_file_id)); + EXPECT_TRUE(db()->GetChildWithName(0, name0, &check_file_id)); + EXPECT_EQ(file_id0, check_file_id); + EXPECT_FALSE(db()->GetChildWithName(file_id0, name0, &check_file_id)); + EXPECT_TRUE(db()->GetChildWithName(file_id0, name1, &check_file_id)); + EXPECT_EQ(file_id1, check_file_id); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestGetFileWithPath) { + FileInfo info; + FileId file_id0; + FileId file_id1; + FileId file_id2; + base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo"); + base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar"); + base::FilePath::StringType name2 = FILE_PATH_LITERAL("dog"); + + info.parent_id = 0; + info.name = name0; + EXPECT_TRUE(db()->AddFileInfo(info, &file_id0)); + info.parent_id = file_id0; + info.name = name1; + EXPECT_TRUE(db()->AddFileInfo(info, &file_id1)); + EXPECT_NE(file_id0, file_id1); + info.parent_id = file_id1; + info.name = name2; + EXPECT_TRUE(db()->AddFileInfo(info, &file_id2)); + EXPECT_NE(file_id0, file_id2); + EXPECT_NE(file_id1, file_id2); + + FileId check_file_id; + base::FilePath path = base::FilePath(name0); + EXPECT_TRUE(db()->GetFileWithPath(path, &check_file_id)); + EXPECT_EQ(file_id0, check_file_id); + + path = path.Append(name1); + EXPECT_TRUE(db()->GetFileWithPath(path, &check_file_id)); + EXPECT_EQ(file_id1, check_file_id); + + path = path.Append(name2); + EXPECT_TRUE(db()->GetFileWithPath(path, &check_file_id)); + EXPECT_EQ(file_id2, check_file_id); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestListChildren) { + // No children in the root. + std::vector<FileId> children; + EXPECT_TRUE(db()->ListChildren(0, &children)); + EXPECT_TRUE(children.empty()); + + // One child in the root. + FileId file_id0; + FileInfo info; + info.parent_id = 0; + info.name = FILE_PATH_LITERAL("foo"); + EXPECT_TRUE(db()->AddFileInfo(info, &file_id0)); + EXPECT_TRUE(db()->ListChildren(0, &children)); + EXPECT_EQ(children.size(), 1UL); + EXPECT_EQ(children[0], file_id0); + + // Two children in the root. + FileId file_id1; + info.name = FILE_PATH_LITERAL("bar"); + EXPECT_TRUE(db()->AddFileInfo(info, &file_id1)); + EXPECT_TRUE(db()->ListChildren(0, &children)); + EXPECT_EQ(2UL, children.size()); + if (children[0] == file_id0) { + EXPECT_EQ(children[1], file_id1); + } else { + EXPECT_EQ(children[1], file_id0); + EXPECT_EQ(children[0], file_id1); + } + + // No children in a subdirectory. + EXPECT_TRUE(db()->ListChildren(file_id0, &children)); + EXPECT_TRUE(children.empty()); + + // One child in a subdirectory. + info.parent_id = file_id0; + info.name = FILE_PATH_LITERAL("foo"); + FileId file_id2; + FileId file_id3; + EXPECT_TRUE(db()->AddFileInfo(info, &file_id2)); + EXPECT_TRUE(db()->ListChildren(file_id0, &children)); + EXPECT_EQ(1UL, children.size()); + EXPECT_EQ(children[0], file_id2); + + // Two children in a subdirectory. + info.name = FILE_PATH_LITERAL("bar"); + EXPECT_TRUE(db()->AddFileInfo(info, &file_id3)); + EXPECT_TRUE(db()->ListChildren(file_id0, &children)); + EXPECT_EQ(2UL, children.size()); + if (children[0] == file_id2) { + EXPECT_EQ(children[1], file_id3); + } else { + EXPECT_EQ(children[1], file_id2); + EXPECT_EQ(children[0], file_id3); + } +} + +TEST_F(SandboxDirectoryDatabaseTest, TestUpdateModificationTime) { + FileInfo info0; + FileId file_id; + info0.parent_id = 0; + info0.name = FILE_PATH_LITERAL("name"); + info0.data_path = base::FilePath(FILE_PATH_LITERAL("fake path")); + info0.modification_time = base::Time::Now(); + EXPECT_TRUE(db()->AddFileInfo(info0, &file_id)); + FileInfo info1; + EXPECT_TRUE(db()->GetFileInfo(file_id, &info1)); + EXPECT_EQ(info0.name, info1.name); + EXPECT_EQ(info0.parent_id, info1.parent_id); + EXPECT_EQ(info0.data_path, info1.data_path); + EXPECT_EQ( + floor(info0.modification_time.ToDoubleT()), + info1.modification_time.ToDoubleT()); + + EXPECT_TRUE(db()->UpdateModificationTime(file_id, base::Time::UnixEpoch())); + EXPECT_TRUE(db()->GetFileInfo(file_id, &info1)); + EXPECT_EQ(info0.name, info1.name); + EXPECT_EQ(info0.parent_id, info1.parent_id); + EXPECT_EQ(info0.data_path, info1.data_path); + EXPECT_NE(info0.modification_time, info1.modification_time); + EXPECT_EQ( + info1.modification_time.ToDoubleT(), + floor(base::Time::UnixEpoch().ToDoubleT())); + + EXPECT_FALSE(db()->UpdateModificationTime(999, base::Time::UnixEpoch())); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestSimpleFileOperations) { + FileId file_id = 888; + FileInfo info0; + EXPECT_FALSE(db()->GetFileInfo(file_id, &info0)); + info0.parent_id = 0; + info0.data_path = base::FilePath(FILE_PATH_LITERAL("foo")); + info0.name = FILE_PATH_LITERAL("file name"); + info0.modification_time = base::Time::Now(); + EXPECT_TRUE(db()->AddFileInfo(info0, &file_id)); + FileInfo info1; + EXPECT_TRUE(db()->GetFileInfo(file_id, &info1)); + EXPECT_EQ(info0.parent_id, info1.parent_id); + EXPECT_EQ(info0.data_path, info1.data_path); + EXPECT_EQ(info0.name, info1.name); + EXPECT_EQ( + floor(info0.modification_time.ToDoubleT()), + info1.modification_time.ToDoubleT()); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestOverwritingMoveFileSrcDirectory) { + FileId directory_id; + FileInfo info0; + info0.parent_id = 0; + info0.name = FILE_PATH_LITERAL("directory"); + info0.modification_time = base::Time::Now(); + EXPECT_TRUE(db()->AddFileInfo(info0, &directory_id)); + + FileId file_id; + FileInfo info1; + info1.parent_id = 0; + info1.data_path = base::FilePath(FILE_PATH_LITERAL("bar")); + info1.name = FILE_PATH_LITERAL("file"); + info1.modification_time = base::Time::UnixEpoch(); + EXPECT_TRUE(db()->AddFileInfo(info1, &file_id)); + + EXPECT_FALSE(db()->OverwritingMoveFile(directory_id, file_id)); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestOverwritingMoveFileDestDirectory) { + FileId file_id; + FileInfo info0; + info0.parent_id = 0; + info0.name = FILE_PATH_LITERAL("file"); + info0.data_path = base::FilePath(FILE_PATH_LITERAL("bar")); + info0.modification_time = base::Time::Now(); + EXPECT_TRUE(db()->AddFileInfo(info0, &file_id)); + + FileId directory_id; + FileInfo info1; + info1.parent_id = 0; + info1.name = FILE_PATH_LITERAL("directory"); + info1.modification_time = base::Time::UnixEpoch(); + EXPECT_TRUE(db()->AddFileInfo(info1, &directory_id)); + + EXPECT_FALSE(db()->OverwritingMoveFile(file_id, directory_id)); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestOverwritingMoveFileSuccess) { + FileId file_id0; + FileInfo info0; + info0.parent_id = 0; + info0.data_path = base::FilePath(FILE_PATH_LITERAL("foo")); + info0.name = FILE_PATH_LITERAL("file name 0"); + info0.modification_time = base::Time::Now(); + EXPECT_TRUE(db()->AddFileInfo(info0, &file_id0)); + + FileInfo dir_info; + FileId dir_id; + dir_info.parent_id = 0; + dir_info.name = FILE_PATH_LITERAL("directory name"); + EXPECT_TRUE(db()->AddFileInfo(dir_info, &dir_id)); + + FileId file_id1; + FileInfo info1; + info1.parent_id = dir_id; + info1.data_path = base::FilePath(FILE_PATH_LITERAL("bar")); + info1.name = FILE_PATH_LITERAL("file name 1"); + info1.modification_time = base::Time::UnixEpoch(); + EXPECT_TRUE(db()->AddFileInfo(info1, &file_id1)); + + EXPECT_TRUE(db()->OverwritingMoveFile(file_id0, file_id1)); + + FileInfo check_info; + FileId check_id; + + EXPECT_FALSE(db()->GetFileWithPath(base::FilePath(info0.name), &check_id)); + EXPECT_TRUE(db()->GetFileWithPath( + base::FilePath(dir_info.name).Append(info1.name), &check_id)); + EXPECT_TRUE(db()->GetFileInfo(check_id, &check_info)); + + EXPECT_EQ(info0.data_path, check_info.data_path); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestGetNextInteger) { + int64 next = -1; + EXPECT_TRUE(db()->GetNextInteger(&next)); + EXPECT_EQ(0, next); + EXPECT_TRUE(db()->GetNextInteger(&next)); + EXPECT_EQ(1, next); + InitDatabase(); + EXPECT_TRUE(db()->GetNextInteger(&next)); + EXPECT_EQ(2, next); + EXPECT_TRUE(db()->GetNextInteger(&next)); + EXPECT_EQ(3, next); + InitDatabase(); + EXPECT_TRUE(db()->GetNextInteger(&next)); + EXPECT_EQ(4, next); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_Empty) { + EXPECT_TRUE(db()->IsFileSystemConsistent()); + + int64 next = -1; + EXPECT_TRUE(db()->GetNextInteger(&next)); + EXPECT_EQ(0, next); + EXPECT_TRUE(db()->IsFileSystemConsistent()); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_Consistent) { + FileId dir_id; + CreateFile(0, FPL("foo"), FPL("hoge"), NULL); + CreateDirectory(0, FPL("bar"), &dir_id); + CreateFile(dir_id, FPL("baz"), FPL("fuga"), NULL); + CreateFile(dir_id, FPL("fizz"), FPL("buzz"), NULL); + + EXPECT_TRUE(db()->IsFileSystemConsistent()); +} + +TEST_F(SandboxDirectoryDatabaseTest, + TestConsistencyCheck_BackingMultiEntry) { + const base::FilePath::CharType kBackingFileName[] = FPL("the celeb"); + CreateFile(0, FPL("foo"), kBackingFileName, NULL); + + EXPECT_TRUE(db()->IsFileSystemConsistent()); + ASSERT_TRUE(base::DeleteFile(path().Append(kBackingFileName), false)); + CreateFile(0, FPL("bar"), kBackingFileName, NULL); + EXPECT_FALSE(db()->IsFileSystemConsistent()); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_FileLost) { + const base::FilePath::CharType kBackingFileName[] = FPL("hoge"); + CreateFile(0, FPL("foo"), kBackingFileName, NULL); + + EXPECT_TRUE(db()->IsFileSystemConsistent()); + ASSERT_TRUE(base::DeleteFile(path().Append(kBackingFileName), false)); + EXPECT_TRUE(db()->IsFileSystemConsistent()); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_OrphanFile) { + CreateFile(0, FPL("foo"), FPL("hoge"), NULL); + + EXPECT_TRUE(db()->IsFileSystemConsistent()); + + bool created = false; + base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED; + base::PlatformFile file = base::CreatePlatformFile( + path().Append(FPL("Orphan File")), + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, + &created, &error); + ASSERT_EQ(base::PLATFORM_FILE_OK, error); + ASSERT_TRUE(created); + ASSERT_TRUE(base::ClosePlatformFile(file)); + + EXPECT_TRUE(db()->IsFileSystemConsistent()); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_RootLoop) { + EXPECT_TRUE(db()->IsFileSystemConsistent()); + MakeHierarchyLink(0, 0, base::FilePath::StringType()); + EXPECT_FALSE(db()->IsFileSystemConsistent()); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_DirectoryLoop) { + FileId dir1_id; + FileId dir2_id; + base::FilePath::StringType dir1_name = FPL("foo"); + CreateDirectory(0, dir1_name, &dir1_id); + CreateDirectory(dir1_id, FPL("bar"), &dir2_id); + + EXPECT_TRUE(db()->IsFileSystemConsistent()); + MakeHierarchyLink(dir2_id, dir1_id, dir1_name); + EXPECT_FALSE(db()->IsFileSystemConsistent()); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_NameMismatch) { + FileId dir_id; + FileId file_id; + CreateDirectory(0, FPL("foo"), &dir_id); + CreateFile(dir_id, FPL("bar"), FPL("hoge/fuga/piyo"), &file_id); + + EXPECT_TRUE(db()->IsFileSystemConsistent()); + DeleteHierarchyLink(file_id); + MakeHierarchyLink(dir_id, file_id, FPL("baz")); + EXPECT_FALSE(db()->IsFileSystemConsistent()); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_WreckedEntries) { + FileId dir1_id; + FileId dir2_id; + CreateDirectory(0, FPL("foo"), &dir1_id); + CreateDirectory(dir1_id, FPL("bar"), &dir2_id); + CreateFile(dir2_id, FPL("baz"), FPL("fizz/buzz"), NULL); + + EXPECT_TRUE(db()->IsFileSystemConsistent()); + DeleteHierarchyLink(dir2_id); // Delete link from |dir1_id| to |dir2_id|. + EXPECT_FALSE(db()->IsFileSystemConsistent()); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestRepairDatabase_Success) { + base::FilePath::StringType kFileName = FPL("bar"); + + FileId file_id_prev; + CreateFile(0, FPL("foo"), FPL("hoge"), NULL); + CreateFile(0, kFileName, FPL("fuga"), &file_id_prev); + + const base::FilePath kDatabaseDirectory = + path().Append(kDirectoryDatabaseName); + CloseDatabase(); + CorruptDatabase(kDatabaseDirectory, leveldb::kDescriptorFile, + 0, std::numeric_limits<size_t>::max()); + InitDatabase(); + EXPECT_FALSE(db()->IsFileSystemConsistent()); + + FileId file_id; + EXPECT_TRUE(db()->GetChildWithName(0, kFileName, &file_id)); + EXPECT_EQ(file_id_prev, file_id); + + EXPECT_TRUE(db()->IsFileSystemConsistent()); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestRepairDatabase_Failure) { + base::FilePath::StringType kFileName = FPL("bar"); + + CreateFile(0, FPL("foo"), FPL("hoge"), NULL); + CreateFile(0, kFileName, FPL("fuga"), NULL); + + const base::FilePath kDatabaseDirectory = + path().Append(kDirectoryDatabaseName); + CloseDatabase(); + CorruptDatabase(kDatabaseDirectory, leveldb::kDescriptorFile, + 0, std::numeric_limits<size_t>::max()); + CorruptDatabase(kDatabaseDirectory, leveldb::kLogFile, + -1, 1); + InitDatabase(); + EXPECT_FALSE(db()->IsFileSystemConsistent()); + + FileId file_id; + EXPECT_FALSE(db()->GetChildWithName(0, kFileName, &file_id)); + EXPECT_TRUE(db()->IsFileSystemConsistent()); +} + +TEST_F(SandboxDirectoryDatabaseTest, TestRepairDatabase_MissingManifest) { + base::FilePath::StringType kFileName = FPL("bar"); + + FileId file_id_prev; + CreateFile(0, FPL("foo"), FPL("hoge"), NULL); + CreateFile(0, kFileName, FPL("fuga"), &file_id_prev); + + const base::FilePath kDatabaseDirectory = + path().Append(kDirectoryDatabaseName); + CloseDatabase(); + + DeleteDatabaseFile(kDatabaseDirectory, leveldb::kDescriptorFile); + + InitDatabase(); + EXPECT_FALSE(db()->IsFileSystemConsistent()); + + FileId file_id; + EXPECT_TRUE(db()->GetChildWithName(0, kFileName, &file_id)); + EXPECT_EQ(file_id_prev, file_id); + + EXPECT_TRUE(db()->IsFileSystemConsistent()); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_file_stream_writer.cc b/chromium/webkit/browser/fileapi/sandbox_file_stream_writer.cc new file mode 100644 index 00000000000..2e075ce39fe --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_file_stream_writer.cc @@ -0,0 +1,245 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/sandbox_file_stream_writer.h" + +#include "base/files/file_util_proxy.h" +#include "base/platform_file.h" +#include "base/sequenced_task_runner.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "webkit/browser/blob/local_file_stream_reader.h" +#include "webkit/browser/fileapi/file_observers.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" +#include "webkit/browser/fileapi/local_file_stream_writer.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +namespace { + +// Adjust the |quota| value in overwriting case (i.e. |file_size| > 0 and +// |file_offset| < |file_size|) to make the remaining quota calculation easier. +// Specifically this widens the quota for overlapping range (so that we can +// simply compare written bytes against the adjusted quota). +int64 AdjustQuotaForOverlap(int64 quota, + int64 file_offset, + int64 file_size) { + DCHECK_LE(file_offset, file_size); + if (quota < 0) + quota = 0; + int64 overlap = file_size - file_offset; + if (kint64max - overlap > quota) + quota += overlap; + return quota; +} + +} // namespace + +SandboxFileStreamWriter::SandboxFileStreamWriter( + FileSystemContext* file_system_context, + const FileSystemURL& url, + int64 initial_offset, + const UpdateObserverList& observers) + : file_system_context_(file_system_context), + url_(url), + initial_offset_(initial_offset), + observers_(observers), + file_size_(0), + total_bytes_written_(0), + allowed_bytes_to_write_(0), + has_pending_operation_(false), + default_quota_(kint64max), + weak_factory_(this) { + DCHECK(url_.is_valid()); +} + +SandboxFileStreamWriter::~SandboxFileStreamWriter() {} + +int SandboxFileStreamWriter::Write( + net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + has_pending_operation_ = true; + if (local_file_writer_) + return WriteInternal(buf, buf_len, callback); + + net::CompletionCallback write_task = + base::Bind(&SandboxFileStreamWriter::DidInitializeForWrite, + weak_factory_.GetWeakPtr(), + make_scoped_refptr(buf), buf_len, callback); + file_system_context_->operation_runner()->CreateSnapshotFile( + url_, base::Bind(&SandboxFileStreamWriter::DidCreateSnapshotFile, + weak_factory_.GetWeakPtr(), write_task)); + return net::ERR_IO_PENDING; +} + +int SandboxFileStreamWriter::Cancel(const net::CompletionCallback& callback) { + if (!has_pending_operation_) + return net::ERR_UNEXPECTED; + + DCHECK(!callback.is_null()); + cancel_callback_ = callback; + return net::ERR_IO_PENDING; +} + +int SandboxFileStreamWriter::WriteInternal( + net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) { + // allowed_bytes_to_write could be negative if the file size is + // greater than the current (possibly new) quota. + DCHECK(total_bytes_written_ <= allowed_bytes_to_write_ || + allowed_bytes_to_write_ < 0); + if (total_bytes_written_ >= allowed_bytes_to_write_) { + has_pending_operation_ = false; + return net::ERR_FILE_NO_SPACE; + } + + if (buf_len > allowed_bytes_to_write_ - total_bytes_written_) + buf_len = allowed_bytes_to_write_ - total_bytes_written_; + + DCHECK(local_file_writer_.get()); + const int result = local_file_writer_->Write( + buf, buf_len, + base::Bind(&SandboxFileStreamWriter::DidWrite, weak_factory_.GetWeakPtr(), + callback)); + if (result != net::ERR_IO_PENDING) + has_pending_operation_ = false; + return result; +} + +void SandboxFileStreamWriter::DidCreateSnapshotFile( + const net::CompletionCallback& callback, + base::PlatformFileError file_error, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) { + DCHECK(!file_ref.get()); + + if (CancelIfRequested()) + return; + if (file_error != base::PLATFORM_FILE_OK) { + callback.Run(net::PlatformFileErrorToNetError(file_error)); + return; + } + if (file_info.is_directory) { + // We should not be writing to a directory. + callback.Run(net::ERR_ACCESS_DENIED); + return; + } + file_size_ = file_info.size; + if (initial_offset_ > file_size_) { + LOG(ERROR) << initial_offset_ << ", " << file_size_; + // This shouldn't happen as long as we check offset in the renderer. + NOTREACHED(); + initial_offset_ = file_size_; + } + DCHECK(!local_file_writer_.get()); + local_file_writer_.reset(new LocalFileStreamWriter( + file_system_context_->default_file_task_runner(), platform_path, + initial_offset_)); + + quota::QuotaManagerProxy* quota_manager_proxy = + file_system_context_->quota_manager_proxy(); + if (!quota_manager_proxy) { + // If we don't have the quota manager or the requested filesystem type + // does not support quota, we should be able to let it go. + allowed_bytes_to_write_ = default_quota_; + callback.Run(net::OK); + return; + } + + DCHECK(quota_manager_proxy->quota_manager()); + quota_manager_proxy->quota_manager()->GetUsageAndQuota( + url_.origin(), + FileSystemTypeToQuotaStorageType(url_.type()), + base::Bind(&SandboxFileStreamWriter::DidGetUsageAndQuota, + weak_factory_.GetWeakPtr(), callback)); +} + +void SandboxFileStreamWriter::DidGetUsageAndQuota( + const net::CompletionCallback& callback, + quota::QuotaStatusCode status, + int64 usage, int64 quota) { + if (CancelIfRequested()) + return; + if (status != quota::kQuotaStatusOk) { + LOG(WARNING) << "Got unexpected quota error : " << status; + callback.Run(net::ERR_FAILED); + return; + } + + allowed_bytes_to_write_ = quota - usage; + callback.Run(net::OK); +} + +void SandboxFileStreamWriter::DidInitializeForWrite( + net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback, + int init_status) { + if (CancelIfRequested()) + return; + if (init_status != net::OK) { + has_pending_operation_ = false; + callback.Run(init_status); + return; + } + allowed_bytes_to_write_ = AdjustQuotaForOverlap( + allowed_bytes_to_write_, initial_offset_, file_size_); + const int result = WriteInternal(buf, buf_len, callback); + if (result != net::ERR_IO_PENDING) + callback.Run(result); +} + +void SandboxFileStreamWriter::DidWrite( + const net::CompletionCallback& callback, + int write_response) { + DCHECK(has_pending_operation_); + has_pending_operation_ = false; + + if (write_response <= 0) { + if (CancelIfRequested()) + return; + callback.Run(write_response); + return; + } + + if (total_bytes_written_ + write_response + initial_offset_ > file_size_) { + int overlapped = file_size_ - total_bytes_written_ - initial_offset_; + if (overlapped < 0) + overlapped = 0; + observers_.Notify(&FileUpdateObserver::OnUpdate, + MakeTuple(url_, write_response - overlapped)); + } + total_bytes_written_ += write_response; + + if (CancelIfRequested()) + return; + callback.Run(write_response); +} + +bool SandboxFileStreamWriter::CancelIfRequested() { + if (cancel_callback_.is_null()) + return false; + + net::CompletionCallback pending_cancel = cancel_callback_; + has_pending_operation_ = false; + cancel_callback_.Reset(); + pending_cancel.Run(net::OK); + return true; +} + +int SandboxFileStreamWriter::Flush(const net::CompletionCallback& callback) { + DCHECK(!has_pending_operation_); + DCHECK(cancel_callback_.is_null()); + + // Write() is not called yet, so there's nothing to flush. + if (!local_file_writer_) + return net::OK; + + return local_file_writer_->Flush(callback); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_file_stream_writer.h b/chromium/webkit/browser/fileapi/sandbox_file_stream_writer.h new file mode 100644 index 00000000000..43fb0cd7743 --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_file_stream_writer.h @@ -0,0 +1,95 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_STREAM_WRITER_H_ +#define WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_STREAM_WRITER_H_ + +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/platform_file.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/file_stream_writer.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/task_runner_bound_observer_list.h" +#include "webkit/browser/webkit_storage_browser_export.h" +#include "webkit/common/blob/shareable_file_reference.h" +#include "webkit/common/fileapi/file_system_types.h" +#include "webkit/common/quota/quota_types.h" + +namespace fileapi { + +class FileSystemContext; +class FileSystemQuotaUtil; +class LocalFileStreamWriter; + +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE SandboxFileStreamWriter + : public FileStreamWriter { + public: + SandboxFileStreamWriter(FileSystemContext* file_system_context, + const FileSystemURL& url, + int64 initial_offset, + const UpdateObserverList& observers); + virtual ~SandboxFileStreamWriter(); + + // FileStreamWriter overrides. + virtual int Write(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback) OVERRIDE; + virtual int Cancel(const net::CompletionCallback& callback) OVERRIDE; + virtual int Flush(const net::CompletionCallback& callback) OVERRIDE; + + // Used only by tests. + void set_default_quota(int64 quota) { + default_quota_ = quota; + } + + private: + // Performs quota calculation and calls local_file_writer_->Write(). + int WriteInternal(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback); + + // Callbacks that are chained for the first write. This eventually calls + // WriteInternal. + void DidCreateSnapshotFile( + const net::CompletionCallback& callback, + base::PlatformFileError file_error, + const base::PlatformFileInfo& file_info, + const base::FilePath& platform_path, + const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref); + void DidGetUsageAndQuota(const net::CompletionCallback& callback, + quota::QuotaStatusCode status, + int64 usage, int64 quota); + void DidInitializeForWrite(net::IOBuffer* buf, int buf_len, + const net::CompletionCallback& callback, + int init_status); + + void DidWrite(const net::CompletionCallback& callback, int write_response); + + // Stops the in-flight operation, calls |cancel_callback_| and returns true + // if there's a pending cancel request. + bool CancelIfRequested(); + + scoped_refptr<FileSystemContext> file_system_context_; + FileSystemURL url_; + int64 initial_offset_; + scoped_ptr<LocalFileStreamWriter> local_file_writer_; + net::CompletionCallback cancel_callback_; + + UpdateObserverList observers_; + + base::FilePath file_path_; + int64 file_size_; + int64 total_bytes_written_; + int64 allowed_bytes_to_write_; + bool has_pending_operation_; + + int64 default_quota_; + + base::WeakPtrFactory<SandboxFileStreamWriter> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(SandboxFileStreamWriter); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_STREAM_WRITER_H_ diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_backend.cc b/chromium/webkit/browser/fileapi/sandbox_file_system_backend.cc new file mode 100644 index 00000000000..665c0f1f798 --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_file_system_backend.cc @@ -0,0 +1,258 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/sandbox_file_system_backend.h" + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/task_runner_util.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/async_file_util_adapter.h" +#include "webkit/browser/fileapi/copy_or_move_file_validator.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_stream_reader.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_operation_impl.h" +#include "webkit/browser/fileapi/file_system_options.h" +#include "webkit/browser/fileapi/file_system_usage_cache.h" +#include "webkit/browser/fileapi/obfuscated_file_util.h" +#include "webkit/browser/fileapi/sandbox_file_stream_writer.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h" +#include "webkit/browser/fileapi/sandbox_quota_observer.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/fileapi/file_system_types.h" +#include "webkit/common/fileapi/file_system_util.h" + +using quota::QuotaManagerProxy; +using quota::SpecialStoragePolicy; + +namespace fileapi { + +namespace { + +const char kTemporaryOriginsCountLabel[] = "FileSystem.TemporaryOriginsCount"; +const char kPersistentOriginsCountLabel[] = "FileSystem.PersistentOriginsCount"; + +} // anonymous namespace + +SandboxFileSystemBackend::SandboxFileSystemBackend( + SandboxFileSystemBackendDelegate* delegate) + : delegate_(delegate), + enable_temporary_file_system_in_incognito_(false) { +} + +SandboxFileSystemBackend::~SandboxFileSystemBackend() { +} + +bool SandboxFileSystemBackend::CanHandleType(FileSystemType type) const { + return type == kFileSystemTypeTemporary || + type == kFileSystemTypePersistent; +} + +void SandboxFileSystemBackend::Initialize(FileSystemContext* context) { + // Set quota observers. + update_observers_ = update_observers_.AddObserver( + delegate_->quota_observer(), + delegate_->file_task_runner()); + access_observers_ = access_observers_.AddObserver( + delegate_->quota_observer(), NULL); +} + +void SandboxFileSystemBackend::OpenFileSystem( + const GURL& origin_url, + fileapi::FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) { + DCHECK(CanHandleType(type)); + DCHECK(delegate_); + if (delegate_->file_system_options().is_incognito() && + !(type == kFileSystemTypeTemporary && + enable_temporary_file_system_in_incognito_)) { + // TODO(kinuko): return an isolated temporary directory. + callback.Run(GURL(), std::string(), base::PLATFORM_FILE_ERROR_SECURITY); + return; + } + + delegate_->OpenFileSystem( + origin_url, type, mode, callback, + GetFileSystemRootURI(origin_url, type)); +} + +FileSystemFileUtil* SandboxFileSystemBackend::GetFileUtil( + FileSystemType type) { + return delegate_->sync_file_util(); +} + +AsyncFileUtil* SandboxFileSystemBackend::GetAsyncFileUtil( + FileSystemType type) { + DCHECK(delegate_); + return delegate_->file_util(); +} + +CopyOrMoveFileValidatorFactory* +SandboxFileSystemBackend::GetCopyOrMoveFileValidatorFactory( + FileSystemType type, + base::PlatformFileError* error_code) { + DCHECK(error_code); + *error_code = base::PLATFORM_FILE_OK; + return NULL; +} + +FileSystemOperation* SandboxFileSystemBackend::CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::PlatformFileError* error_code) const { + DCHECK(CanHandleType(url.type())); + DCHECK(delegate_); + if (!delegate_->IsAccessValid(url)) { + *error_code = base::PLATFORM_FILE_ERROR_SECURITY; + return NULL; + } + + scoped_ptr<FileSystemOperationContext> operation_context( + new FileSystemOperationContext(context)); + operation_context->set_update_observers(update_observers_); + operation_context->set_change_observers(change_observers_); + + SpecialStoragePolicy* policy = delegate_->special_storage_policy(); + if (policy && policy->IsStorageUnlimited(url.origin())) + operation_context->set_quota_limit_type(quota::kQuotaLimitTypeUnlimited); + else + operation_context->set_quota_limit_type(quota::kQuotaLimitTypeLimited); + + return new FileSystemOperationImpl(url, context, operation_context.Pass()); +} + +scoped_ptr<webkit_blob::FileStreamReader> +SandboxFileSystemBackend::CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const { + DCHECK(CanHandleType(url.type())); + DCHECK(delegate_); + if (!delegate_->IsAccessValid(url)) + return scoped_ptr<webkit_blob::FileStreamReader>(); + return scoped_ptr<webkit_blob::FileStreamReader>( + new FileSystemFileStreamReader( + context, url, offset, expected_modification_time)); +} + +scoped_ptr<fileapi::FileStreamWriter> +SandboxFileSystemBackend::CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const { + DCHECK(CanHandleType(url.type())); + DCHECK(delegate_); + if (!delegate_->IsAccessValid(url)) + return scoped_ptr<fileapi::FileStreamWriter>(); + return scoped_ptr<fileapi::FileStreamWriter>( + new SandboxFileStreamWriter(context, url, offset, update_observers_)); +} + +FileSystemQuotaUtil* SandboxFileSystemBackend::GetQuotaUtil() { + return this; +} + +SandboxFileSystemBackendDelegate::OriginEnumerator* +SandboxFileSystemBackend::CreateOriginEnumerator() { + DCHECK(delegate_); + return delegate_->CreateOriginEnumerator(); +} + +base::PlatformFileError +SandboxFileSystemBackend::DeleteOriginDataOnFileThread( + FileSystemContext* file_system_context, + QuotaManagerProxy* proxy, + const GURL& origin_url, + fileapi::FileSystemType type) { + DCHECK(CanHandleType(type)); + DCHECK(delegate_); + return delegate_->DeleteOriginDataOnFileThread( + file_system_context, proxy, origin_url, type); +} + +void SandboxFileSystemBackend::GetOriginsForTypeOnFileThread( + fileapi::FileSystemType type, std::set<GURL>* origins) { + DCHECK(CanHandleType(type)); + DCHECK(delegate_); + delegate_->GetOriginsForTypeOnFileThread(type, origins); + switch (type) { + case kFileSystemTypeTemporary: + UMA_HISTOGRAM_COUNTS(kTemporaryOriginsCountLabel, origins->size()); + break; + case kFileSystemTypePersistent: + UMA_HISTOGRAM_COUNTS(kPersistentOriginsCountLabel, origins->size()); + break; + default: + break; + } +} + +void SandboxFileSystemBackend::GetOriginsForHostOnFileThread( + fileapi::FileSystemType type, const std::string& host, + std::set<GURL>* origins) { + DCHECK(CanHandleType(type)); + DCHECK(delegate_); + delegate_->GetOriginsForHostOnFileThread(type, host, origins); +} + +int64 SandboxFileSystemBackend::GetOriginUsageOnFileThread( + FileSystemContext* file_system_context, + const GURL& origin_url, + fileapi::FileSystemType type) { + DCHECK(CanHandleType(type)); + DCHECK(delegate_); + return delegate_->GetOriginUsageOnFileThread( + file_system_context, origin_url, type); +} + +void SandboxFileSystemBackend::AddFileUpdateObserver( + FileSystemType type, + FileUpdateObserver* observer, + base::SequencedTaskRunner* task_runner) { + DCHECK(CanHandleType(type)); + UpdateObserverList* list = &update_observers_; + *list = list->AddObserver(observer, task_runner); +} + +void SandboxFileSystemBackend::AddFileChangeObserver( + FileSystemType type, + FileChangeObserver* observer, + base::SequencedTaskRunner* task_runner) { + DCHECK(CanHandleType(type)); + ChangeObserverList* list = &change_observers_; + *list = list->AddObserver(observer, task_runner); +} + +void SandboxFileSystemBackend::AddFileAccessObserver( + FileSystemType type, + FileAccessObserver* observer, + base::SequencedTaskRunner* task_runner) { + DCHECK(CanHandleType(type)); + access_observers_ = access_observers_.AddObserver(observer, task_runner); +} + +const UpdateObserverList* SandboxFileSystemBackend::GetUpdateObservers( + FileSystemType type) const { + DCHECK(CanHandleType(type)); + return &update_observers_; +} + +const ChangeObserverList* SandboxFileSystemBackend::GetChangeObservers( + FileSystemType type) const { + DCHECK(CanHandleType(type)); + return &change_observers_; +} + +const AccessObserverList* SandboxFileSystemBackend::GetAccessObservers( + FileSystemType type) const { + DCHECK(CanHandleType(type)); + return &access_observers_; +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_backend.h b/chromium/webkit/browser/fileapi/sandbox_file_system_backend.h new file mode 100644 index 00000000000..a1a568637a0 --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_file_system_backend.h @@ -0,0 +1,123 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_H_ +#define WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_H_ + +#include <set> +#include <string> + +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_quota_util.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h" +#include "webkit/browser/fileapi/task_runner_bound_observer_list.h" +#include "webkit/browser/quota/special_storage_policy.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace fileapi { + +// An interface to construct or crack sandboxed filesystem paths for +// TEMPORARY or PERSISTENT filesystems, which are placed under the user's +// profile directory in a sandboxed way. +// This interface also lets one enumerate and remove storage for the origins +// that use the filesystem. +class WEBKIT_STORAGE_BROWSER_EXPORT SandboxFileSystemBackend + : public FileSystemBackend, + public FileSystemQuotaUtil { + public: + explicit SandboxFileSystemBackend(SandboxFileSystemBackendDelegate* delegate); + virtual ~SandboxFileSystemBackend(); + + // FileSystemBackend overrides. + virtual bool CanHandleType(FileSystemType type) const OVERRIDE; + virtual void Initialize(FileSystemContext* context) OVERRIDE; + virtual void OpenFileSystem( + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) OVERRIDE; + virtual FileSystemFileUtil* GetFileUtil(FileSystemType type) OVERRIDE; + virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) OVERRIDE; + virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory( + FileSystemType type, + base::PlatformFileError* error_code) OVERRIDE; + virtual FileSystemOperation* CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::PlatformFileError* error_code) const OVERRIDE; + virtual scoped_ptr<webkit_blob::FileStreamReader> CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const OVERRIDE; + virtual scoped_ptr<FileStreamWriter> CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const OVERRIDE; + virtual FileSystemQuotaUtil* GetQuotaUtil() OVERRIDE; + + // Returns an origin enumerator of this backend. + // This method can only be called on the file thread. + SandboxFileSystemBackendDelegate::OriginEnumerator* CreateOriginEnumerator(); + + // FileSystemQuotaUtil overrides. + virtual base::PlatformFileError DeleteOriginDataOnFileThread( + FileSystemContext* context, + quota::QuotaManagerProxy* proxy, + const GURL& origin_url, + FileSystemType type) OVERRIDE; + virtual void GetOriginsForTypeOnFileThread( + FileSystemType type, + std::set<GURL>* origins) OVERRIDE; + virtual void GetOriginsForHostOnFileThread( + FileSystemType type, + const std::string& host, + std::set<GURL>* origins) OVERRIDE; + virtual int64 GetOriginUsageOnFileThread( + FileSystemContext* context, + const GURL& origin_url, + FileSystemType type) OVERRIDE; + virtual void AddFileUpdateObserver( + FileSystemType type, + FileUpdateObserver* observer, + base::SequencedTaskRunner* task_runner) OVERRIDE; + virtual void AddFileChangeObserver( + FileSystemType type, + FileChangeObserver* observer, + base::SequencedTaskRunner* task_runner) OVERRIDE; + virtual void AddFileAccessObserver( + FileSystemType type, + FileAccessObserver* observer, + base::SequencedTaskRunner* task_runner) OVERRIDE; + virtual const UpdateObserverList* GetUpdateObservers( + FileSystemType type) const OVERRIDE; + virtual const ChangeObserverList* GetChangeObservers( + FileSystemType type) const OVERRIDE; + virtual const AccessObserverList* GetAccessObservers( + FileSystemType type) const OVERRIDE; + + void set_enable_temporary_file_system_in_incognito(bool enable) { + enable_temporary_file_system_in_incognito_ = enable; + } + + private: + SandboxFileSystemBackendDelegate* delegate_; // Not owned. + + bool enable_temporary_file_system_in_incognito_; + + // Observers. + UpdateObserverList update_observers_; + ChangeObserverList change_observers_; + AccessObserverList access_observers_; + + DISALLOW_COPY_AND_ASSIGN(SandboxFileSystemBackend); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_H_ diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate.cc b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate.cc new file mode 100644 index 00000000000..0a6034e7d9a --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate.cc @@ -0,0 +1,442 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h" + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/metrics/histogram.h" +#include "base/stl_util.h" +#include "base/task_runner_util.h" +#include "net/base/net_util.h" +#include "webkit/browser/fileapi/async_file_util_adapter.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/file_system_usage_cache.h" +#include "webkit/browser/fileapi/obfuscated_file_util.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend.h" +#include "webkit/browser/fileapi/sandbox_quota_observer.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +namespace { + +const char kOpenFileSystemLabel[] = "FileSystem.OpenFileSystem"; +const char kOpenFileSystemDetailLabel[] = "FileSystem.OpenFileSystemDetail"; +const char kOpenFileSystemDetailNonThrottledLabel[] = + "FileSystem.OpenFileSystemDetailNonthrottled"; +int64 kMinimumStatsCollectionIntervalHours = 1; + +enum FileSystemError { + kOK = 0, + kIncognito, + kInvalidSchemeError, + kCreateDirectoryError, + kNotFound, + kUnknownError, + kFileSystemErrorMax, +}; + +// Restricted names. +// http://dev.w3.org/2009/dap/file-system/file-dir-sys.html#naming-restrictions +const base::FilePath::CharType* const kRestrictedNames[] = { + FILE_PATH_LITERAL("."), FILE_PATH_LITERAL(".."), +}; + +// Restricted chars. +const base::FilePath::CharType kRestrictedChars[] = { + FILE_PATH_LITERAL('/'), FILE_PATH_LITERAL('\\'), +}; + +class ObfuscatedOriginEnumerator + : public SandboxFileSystemBackendDelegate::OriginEnumerator { + public: + explicit ObfuscatedOriginEnumerator(ObfuscatedFileUtil* file_util) { + enum_.reset(file_util->CreateOriginEnumerator()); + } + virtual ~ObfuscatedOriginEnumerator() {} + + virtual GURL Next() OVERRIDE { + return enum_->Next(); + } + + virtual bool HasFileSystemType(fileapi::FileSystemType type) const OVERRIDE { + return enum_->HasFileSystemType(type); + } + + private: + scoped_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator> enum_; +}; + +void OpenFileSystemOnFileThread( + ObfuscatedFileUtil* file_util, + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + base::PlatformFileError* error_ptr) { + DCHECK(error_ptr); + const bool create = (mode == OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT); + file_util->GetDirectoryForOriginAndType(origin_url, type, create, error_ptr); + if (*error_ptr != base::PLATFORM_FILE_OK) { + UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemLabel, + kCreateDirectoryError, + kFileSystemErrorMax); + } else { + UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemLabel, kOK, kFileSystemErrorMax); + } + // The reference of file_util will be derefed on the FILE thread + // when the storage of this callback gets deleted regardless of whether + // this method is called or not. +} + +void DidOpenFileSystem( + base::WeakPtr<SandboxFileSystemBackendDelegate> delegate, + const base::Callback<void(base::PlatformFileError error)>& callback, + base::PlatformFileError* error) { + if (delegate.get()) + delegate.get()->CollectOpenFileSystemMetrics(*error); + callback.Run(*error); +} + +} // namespace + +const base::FilePath::CharType +SandboxFileSystemBackendDelegate::kFileSystemDirectory[] = + FILE_PATH_LITERAL("File System"); + +SandboxFileSystemBackendDelegate::SandboxFileSystemBackendDelegate( + quota::QuotaManagerProxy* quota_manager_proxy, + base::SequencedTaskRunner* file_task_runner, + const base::FilePath& profile_path, + quota::SpecialStoragePolicy* special_storage_policy, + const FileSystemOptions& file_system_options) + : file_task_runner_(file_task_runner), + sandbox_file_util_(new AsyncFileUtilAdapter( + new ObfuscatedFileUtil( + special_storage_policy, + profile_path.Append(kFileSystemDirectory), + file_task_runner))), + file_system_usage_cache_(new FileSystemUsageCache(file_task_runner)), + quota_observer_(new SandboxQuotaObserver( + quota_manager_proxy, + file_task_runner, + obfuscated_file_util(), + usage_cache())), + special_storage_policy_(special_storage_policy), + file_system_options_(file_system_options), + weak_factory_(this) { +} + +SandboxFileSystemBackendDelegate::~SandboxFileSystemBackendDelegate() { + if (!file_task_runner_->RunsTasksOnCurrentThread()) { + AsyncFileUtil* sandbox_file_util = sandbox_file_util_.release(); + SandboxQuotaObserver* quota_observer = quota_observer_.release(); + FileSystemUsageCache* file_system_usage_cache = + file_system_usage_cache_.release(); + if (!file_task_runner_->DeleteSoon(FROM_HERE, sandbox_file_util)) + delete sandbox_file_util; + if (!file_task_runner_->DeleteSoon(FROM_HERE, quota_observer)) + delete quota_observer; + if (!file_task_runner_->DeleteSoon(FROM_HERE, file_system_usage_cache)) + delete file_system_usage_cache; + } +} + +bool SandboxFileSystemBackendDelegate::IsAccessValid( + const FileSystemURL& url) const { + if (!IsAllowedScheme(url.origin())) + return false; + + if (url.path().ReferencesParent()) + return false; + + // Return earlier if the path is '/', because VirtualPath::BaseName() + // returns '/' for '/' and we fail the "basename != '/'" check below. + // (We exclude '.' because it's disallowed by spec.) + if (VirtualPath::IsRootPath(url.path()) && + url.path() != base::FilePath(base::FilePath::kCurrentDirectory)) + return true; + + // Restricted names specified in + // http://dev.w3.org/2009/dap/file-system/file-dir-sys.html#naming-restrictions + base::FilePath filename = VirtualPath::BaseName(url.path()); + // See if the name is allowed to create. + for (size_t i = 0; i < arraysize(kRestrictedNames); ++i) { + if (filename.value() == kRestrictedNames[i]) + return false; + } + for (size_t i = 0; i < arraysize(kRestrictedChars); ++i) { + if (filename.value().find(kRestrictedChars[i]) != + base::FilePath::StringType::npos) + return false; + } + + return true; +} + +bool SandboxFileSystemBackendDelegate::IsAllowedScheme(const GURL& url) const { + // Basically we only accept http or https. We allow file:// URLs + // only if --allow-file-access-from-files flag is given. + if (url.SchemeIs("http") || url.SchemeIs("https")) + return true; + if (url.SchemeIsFileSystem()) + return url.inner_url() && IsAllowedScheme(*url.inner_url()); + + for (size_t i = 0; + i < file_system_options_.additional_allowed_schemes().size(); + ++i) { + if (url.SchemeIs( + file_system_options_.additional_allowed_schemes()[i].c_str())) + return true; + } + return false; +} + +SandboxFileSystemBackendDelegate::OriginEnumerator* +SandboxFileSystemBackendDelegate::CreateOriginEnumerator() { + return new ObfuscatedOriginEnumerator(obfuscated_file_util()); +} + +base::FilePath +SandboxFileSystemBackendDelegate::GetBaseDirectoryForOriginAndType( + const GURL& origin_url, + fileapi::FileSystemType type, + bool create) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + base::FilePath path = obfuscated_file_util()->GetDirectoryForOriginAndType( + origin_url, type, create, &error); + if (error != base::PLATFORM_FILE_OK) + return base::FilePath(); + return path; +} + +void SandboxFileSystemBackendDelegate::OpenFileSystem( + const GURL& origin_url, + fileapi::FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback, + const GURL& root_url) { + if (!IsAllowedScheme(origin_url)) { + callback.Run(GURL(), std::string(), base::PLATFORM_FILE_ERROR_SECURITY); + return; + } + + std::string name = GetFileSystemName(origin_url, type); + + base::PlatformFileError* error_ptr = new base::PlatformFileError; + file_task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind(&OpenFileSystemOnFileThread, + obfuscated_file_util(), origin_url, type, mode, + base::Unretained(error_ptr)), + base::Bind(&DidOpenFileSystem, + weak_factory_.GetWeakPtr(), + base::Bind(callback, root_url, name), + base::Owned(error_ptr))); +} + +base::PlatformFileError +SandboxFileSystemBackendDelegate::DeleteOriginDataOnFileThread( + FileSystemContext* file_system_context, + quota::QuotaManagerProxy* proxy, + const GURL& origin_url, + fileapi::FileSystemType type) { + int64 usage = GetOriginUsageOnFileThread( + file_system_context, origin_url, type); + usage_cache()->CloseCacheFiles(); + bool result = obfuscated_file_util()->DeleteDirectoryForOriginAndType( + origin_url, type); + if (result && proxy) { + proxy->NotifyStorageModified( + quota::QuotaClient::kFileSystem, + origin_url, + FileSystemTypeToQuotaStorageType(type), + -usage); + } + + if (result) + return base::PLATFORM_FILE_OK; + return base::PLATFORM_FILE_ERROR_FAILED; +} + +void SandboxFileSystemBackendDelegate::GetOriginsForTypeOnFileThread( + fileapi::FileSystemType type, std::set<GURL>* origins) { + DCHECK(origins); + scoped_ptr<OriginEnumerator> enumerator(CreateOriginEnumerator()); + GURL origin; + while (!(origin = enumerator->Next()).is_empty()) { + if (enumerator->HasFileSystemType(type)) + origins->insert(origin); + } +} + +void SandboxFileSystemBackendDelegate::GetOriginsForHostOnFileThread( + fileapi::FileSystemType type, const std::string& host, + std::set<GURL>* origins) { + DCHECK(origins); + scoped_ptr<OriginEnumerator> enumerator(CreateOriginEnumerator()); + GURL origin; + while (!(origin = enumerator->Next()).is_empty()) { + if (host == net::GetHostOrSpecFromURL(origin) && + enumerator->HasFileSystemType(type)) + origins->insert(origin); + } +} + +int64 SandboxFileSystemBackendDelegate::GetOriginUsageOnFileThread( + FileSystemContext* file_system_context, + const GURL& origin_url, + fileapi::FileSystemType type) { + // Don't use usage cache and return recalculated usage for sticky invalidated + // origins. + if (ContainsKey(sticky_dirty_origins_, std::make_pair(origin_url, type))) + return RecalculateUsage(file_system_context, origin_url, type); + + base::FilePath base_path = + GetBaseDirectoryForOriginAndType(origin_url, type, false); + if (base_path.empty() || !base::DirectoryExists(base_path)) + return 0; + base::FilePath usage_file_path = + base_path.Append(FileSystemUsageCache::kUsageFileName); + + bool is_valid = usage_cache()->IsValid(usage_file_path); + uint32 dirty_status = 0; + bool dirty_status_available = + usage_cache()->GetDirty(usage_file_path, &dirty_status); + bool visited = !visited_origins_.insert(origin_url).second; + if (is_valid && (dirty_status == 0 || (dirty_status_available && visited))) { + // The usage cache is clean (dirty == 0) or the origin is already + // initialized and running. Read the cache file to get the usage. + int64 usage = 0; + return usage_cache()->GetUsage(usage_file_path, &usage) ? usage : -1; + } + // The usage cache has not been initialized or the cache is dirty. + // Get the directory size now and update the cache. + usage_cache()->Delete(usage_file_path); + + int64 usage = RecalculateUsage(file_system_context, origin_url, type); + + // This clears the dirty flag too. + usage_cache()->UpdateUsage(usage_file_path, usage); + return usage; +} + +void SandboxFileSystemBackendDelegate::InvalidateUsageCache( + const GURL& origin, + fileapi::FileSystemType type) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + base::FilePath usage_file_path = GetUsageCachePathForOriginAndType( + obfuscated_file_util(), origin, type, &error); + if (error != base::PLATFORM_FILE_OK) + return; + usage_cache()->IncrementDirty(usage_file_path); +} + +void SandboxFileSystemBackendDelegate::StickyInvalidateUsageCache( + const GURL& origin, + fileapi::FileSystemType type) { + sticky_dirty_origins_.insert(std::make_pair(origin, type)); + quota_observer()->SetUsageCacheEnabled(origin, type, false); + InvalidateUsageCache(origin, type); +} + +FileSystemFileUtil* SandboxFileSystemBackendDelegate::sync_file_util() { + return static_cast<AsyncFileUtilAdapter*>(file_util())->sync_file_util(); +} + +base::FilePath +SandboxFileSystemBackendDelegate::GetUsageCachePathForOriginAndType( + const GURL& origin_url, + FileSystemType type) { + base::PlatformFileError error; + base::FilePath path = GetUsageCachePathForOriginAndType( + obfuscated_file_util(), origin_url, type, &error); + if (error != base::PLATFORM_FILE_OK) + return base::FilePath(); + return path; +} + +// static +base::FilePath +SandboxFileSystemBackendDelegate::GetUsageCachePathForOriginAndType( + ObfuscatedFileUtil* sandbox_file_util, + const GURL& origin_url, + fileapi::FileSystemType type, + base::PlatformFileError* error_out) { + DCHECK(error_out); + *error_out = base::PLATFORM_FILE_OK; + base::FilePath base_path = sandbox_file_util->GetDirectoryForOriginAndType( + origin_url, type, false /* create */, error_out); + if (*error_out != base::PLATFORM_FILE_OK) + return base::FilePath(); + return base_path.Append(FileSystemUsageCache::kUsageFileName); +} + +int64 SandboxFileSystemBackendDelegate::RecalculateUsage( + FileSystemContext* context, + const GURL& origin, + FileSystemType type) { + FileSystemOperationContext operation_context(context); + FileSystemURL url = context->CreateCrackedFileSystemURL( + origin, type, base::FilePath()); + scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator( + obfuscated_file_util()->CreateFileEnumerator( + &operation_context, url, true)); + + base::FilePath file_path_each; + int64 usage = 0; + + while (!(file_path_each = enumerator->Next()).empty()) { + usage += enumerator->Size(); + usage += ObfuscatedFileUtil::ComputeFilePathCost(file_path_each); + } + + return usage; +} + +void SandboxFileSystemBackendDelegate::CollectOpenFileSystemMetrics( + base::PlatformFileError error_code) { + base::Time now = base::Time::Now(); + bool throttled = now < next_release_time_for_open_filesystem_stat_; + if (!throttled) { + next_release_time_for_open_filesystem_stat_ = + now + base::TimeDelta::FromHours(kMinimumStatsCollectionIntervalHours); + } + +#define REPORT(report_value) \ + UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemDetailLabel, \ + (report_value), \ + kFileSystemErrorMax); \ + if (!throttled) { \ + UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemDetailNonThrottledLabel, \ + (report_value), \ + kFileSystemErrorMax); \ + } + + switch (error_code) { + case base::PLATFORM_FILE_OK: + REPORT(kOK); + break; + case base::PLATFORM_FILE_ERROR_INVALID_URL: + REPORT(kInvalidSchemeError); + break; + case base::PLATFORM_FILE_ERROR_NOT_FOUND: + REPORT(kNotFound); + break; + case base::PLATFORM_FILE_ERROR_FAILED: + default: + REPORT(kUnknownError); + break; + } +#undef REPORT +} + +ObfuscatedFileUtil* SandboxFileSystemBackendDelegate::obfuscated_file_util() { + return static_cast<ObfuscatedFileUtil*>(sync_file_util()); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate.h b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate.h new file mode 100644 index 00000000000..5fb83f082b9 --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate.h @@ -0,0 +1,194 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_DELEGATE_H_ +#define WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_DELEGATE_H_ + +#include <set> +#include <string> +#include <utility> + +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_options.h" +#include "webkit/browser/fileapi/file_system_quota_util.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace quota { +class QuotaManagerProxy; +class SpecialStoragePolicy; +} + +namespace fileapi { + +class AsyncFileUtil; +class FileSystemFileUtil; +class FileSystemURL; +class FileSystemUsageCache; +class ObfuscatedFileUtil; +class SandboxFileSystemBackend; +class SandboxFileSystemTestHelper; +class SandboxQuotaObserver; + +// Delegate implementation of the some methods in Sandbox/SyncFileSystemBackend. +// An instance of this class is created and owned by FileSystemContext. +class WEBKIT_STORAGE_BROWSER_EXPORT SandboxFileSystemBackendDelegate { + public: + typedef FileSystemBackend::OpenFileSystemCallback OpenFileSystemCallback; + + // The FileSystem directory name. + static const base::FilePath::CharType kFileSystemDirectory[]; + + // Origin enumerator interface. + // An instance of this interface is assumed to be called on the file thread. + class OriginEnumerator { + public: + virtual ~OriginEnumerator() {} + + // Returns the next origin. Returns empty if there are no more origins. + virtual GURL Next() = 0; + + // Returns the current origin's information. + virtual bool HasFileSystemType(FileSystemType type) const = 0; + }; + + SandboxFileSystemBackendDelegate( + quota::QuotaManagerProxy* quota_manager_proxy, + base::SequencedTaskRunner* file_task_runner, + const base::FilePath& profile_path, + quota::SpecialStoragePolicy* special_storage_policy, + const FileSystemOptions& file_system_options); + + ~SandboxFileSystemBackendDelegate(); + + // Performs API-specific validity checks on the given path |url|. + // Returns true if access to |url| is valid in this filesystem. + bool IsAccessValid(const FileSystemURL& url) const; + + // Returns true if the given |url|'s scheme is allowed to access + // filesystem. + bool IsAllowedScheme(const GURL& url) const; + + // Returns an origin enumerator of sandbox filesystem. + // This method can only be called on the file thread. + OriginEnumerator* CreateOriginEnumerator(); + + // Gets a base directory path of the sandboxed filesystem that is + // specified by |origin_url| and |type|. + // (The path is similar to the origin's root path but doesn't contain + // the 'unique' part.) + // Returns an empty path if the given type is invalid. + // This method can only be called on the file thread. + base::FilePath GetBaseDirectoryForOriginAndType( + const GURL& origin_url, + FileSystemType type, + bool create); + + // FileSystemBackend helpers. + void OpenFileSystem( + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback, + const GURL& root_url); + + // FileSystemQuotaUtil helpers. + base::PlatformFileError DeleteOriginDataOnFileThread( + FileSystemContext* context, + quota::QuotaManagerProxy* proxy, + const GURL& origin_url, + FileSystemType type); + void GetOriginsForTypeOnFileThread( + FileSystemType type, + std::set<GURL>* origins); + void GetOriginsForHostOnFileThread( + FileSystemType type, + const std::string& host, + std::set<GURL>* origins); + int64 GetOriginUsageOnFileThread( + FileSystemContext* context, + const GURL& origin_url, + FileSystemType type); + void InvalidateUsageCache( + const GURL& origin_url, + FileSystemType type); + void StickyInvalidateUsageCache( + const GURL& origin_url, + FileSystemType type); + + void CollectOpenFileSystemMetrics(base::PlatformFileError error_code); + + base::SequencedTaskRunner* file_task_runner() { + return file_task_runner_.get(); + } + + AsyncFileUtil* file_util() { return sandbox_file_util_.get(); } + FileSystemUsageCache* usage_cache() { return file_system_usage_cache_.get(); } + SandboxQuotaObserver* quota_observer() { return quota_observer_.get(); }; + + quota::SpecialStoragePolicy* special_storage_policy() { + return special_storage_policy_.get(); + } + + const FileSystemOptions& file_system_options() const { + return file_system_options_; + } + + FileSystemFileUtil* sync_file_util(); + + private: + friend class SandboxQuotaObserver; + friend class SandboxFileSystemTestHelper; + + // Returns a path to the usage cache file. + base::FilePath GetUsageCachePathForOriginAndType( + const GURL& origin_url, + FileSystemType type); + + // Returns a path to the usage cache file (static version). + static base::FilePath GetUsageCachePathForOriginAndType( + ObfuscatedFileUtil* sandbox_file_util, + const GURL& origin_url, + FileSystemType type, + base::PlatformFileError* error_out); + + int64 RecalculateUsage(FileSystemContext* context, + const GURL& origin, + FileSystemType type); + + ObfuscatedFileUtil* obfuscated_file_util(); + + scoped_refptr<base::SequencedTaskRunner> file_task_runner_; + + scoped_ptr<AsyncFileUtil> sandbox_file_util_; + scoped_ptr<FileSystemUsageCache> file_system_usage_cache_; + scoped_ptr<SandboxQuotaObserver> quota_observer_; + + scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_; + + FileSystemOptions file_system_options_; + + // Acccessed only on the file thread. + std::set<GURL> visited_origins_; + + std::set<std::pair<GURL, FileSystemType> > sticky_dirty_origins_; + + base::Time next_release_time_for_open_filesystem_stat_; + + base::WeakPtrFactory<SandboxFileSystemBackendDelegate> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(SandboxFileSystemBackendDelegate); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_DELEGATE_H_ diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate_unittest.cc b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate_unittest.cc new file mode 100644 index 00000000000..48c3b8c4e70 --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate_unittest.cc @@ -0,0 +1,82 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h" + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/mock_file_system_options.h" + +namespace fileapi { + +namespace { + +FileSystemURL CreateFileSystemURL(const char* path) { + const GURL kOrigin("http://foo/"); + return FileSystemURL::CreateForTest( + kOrigin, kFileSystemTypeTemporary, base::FilePath::FromUTF8Unsafe(path)); +} + +} // namespace + +class SandboxFileSystemBackendDelegateTest : public testing::Test { + protected: + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + delegate_.reset(new SandboxFileSystemBackendDelegate( + NULL /* quota_manager_proxy */, + base::MessageLoopProxy::current().get(), + data_dir_.path(), + NULL /* special_storage_policy */, + CreateAllowFileAccessOptions())); + } + + base::ScopedTempDir data_dir_; + base::MessageLoop message_loop_; + scoped_ptr<SandboxFileSystemBackendDelegate> delegate_; +}; + +TEST_F(SandboxFileSystemBackendDelegateTest, IsAccessValid) { + // Normal case. + EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL("a"))); + + // Access to a path with parent references ('..') should be disallowed. + EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL("a/../b"))); + + // Access from non-allowed scheme should be disallowed. + EXPECT_FALSE(delegate_->IsAccessValid( + FileSystemURL::CreateForTest( + GURL("unknown://bar"), kFileSystemTypeTemporary, + base::FilePath::FromUTF8Unsafe("foo")))); + + // Access with restricted name should be disallowed. + EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL("."))); + EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL(".."))); + + // This is also disallowed due to Windows XP parent path handling. + EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL("..."))); + + // These are identified as unsafe cases due to weird path handling + // on Windows. + EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL(" .."))); + EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL(".. "))); + + // Similar but safe cases. + EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL(" ."))); + EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL(". "))); + EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL("b."))); + EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL(".b"))); + + // A path that looks like a drive letter. + EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL("c:"))); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_backend_unittest.cc b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_unittest.cc new file mode 100644 index 00000000000..c7f03170e42 --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_unittest.cc @@ -0,0 +1,325 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/sandbox_file_system_backend.h" + +#include <set> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/mock_file_system_options.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h" +#include "webkit/common/fileapi/file_system_util.h" + +// PS stands for path separator. +#if defined(FILE_PATH_USES_WIN_SEPARATORS) +#define PS "\\" +#else +#define PS "/" +#endif + +namespace fileapi { + +namespace { + +const struct RootPathTest { + fileapi::FileSystemType type; + const char* origin_url; + const char* expected_path; +} kRootPathTestCases[] = { + { fileapi::kFileSystemTypeTemporary, "http://foo:1/", + "000" PS "t" }, + { fileapi::kFileSystemTypePersistent, "http://foo:1/", + "000" PS "p" }, + { fileapi::kFileSystemTypeTemporary, "http://bar.com/", + "001" PS "t" }, + { fileapi::kFileSystemTypePersistent, "http://bar.com/", + "001" PS "p" }, + { fileapi::kFileSystemTypeTemporary, "https://foo:2/", + "002" PS "t" }, + { fileapi::kFileSystemTypePersistent, "https://foo:2/", + "002" PS "p" }, + { fileapi::kFileSystemTypeTemporary, "https://bar.com/", + "003" PS "t" }, + { fileapi::kFileSystemTypePersistent, "https://bar.com/", + "003" PS "p" }, +}; + +const struct RootPathFileURITest { + fileapi::FileSystemType type; + const char* origin_url; + const char* expected_path; + const char* virtual_path; +} kRootPathFileURITestCases[] = { + { fileapi::kFileSystemTypeTemporary, "file:///", + "000" PS "t", NULL }, + { fileapi::kFileSystemTypePersistent, "file:///", + "000" PS "p", NULL }, +}; + +FileSystemURL CreateFileSystemURL(const char* path) { + const GURL kOrigin("http://foo/"); + return FileSystemURL::CreateForTest( + kOrigin, kFileSystemTypeTemporary, base::FilePath::FromUTF8Unsafe(path)); +} + +void DidOpenFileSystem(base::PlatformFileError* error_out, + const GURL& origin_url, + const std::string& name, + base::PlatformFileError error) { + *error_out = error; +} + +} // namespace + +class SandboxFileSystemBackendTest : public testing::Test { + protected: + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + SetUpNewDelegate(CreateAllowFileAccessOptions()); + } + + void SetUpNewDelegate(const FileSystemOptions& options) { + delegate_.reset(new SandboxFileSystemBackendDelegate( + NULL /* quota_manager_proxy */, + base::MessageLoopProxy::current().get(), + data_dir_.path(), + NULL /* special_storage_policy */, + options)); + } + + void SetUpNewBackend(const FileSystemOptions& options) { + SetUpNewDelegate(options); + backend_.reset(new SandboxFileSystemBackend(delegate_.get())); + } + + SandboxFileSystemBackendDelegate::OriginEnumerator* + CreateOriginEnumerator() const { + return backend_->CreateOriginEnumerator(); + } + + void CreateOriginTypeDirectory(const GURL& origin, + fileapi::FileSystemType type) { + base::FilePath target = delegate_-> + GetBaseDirectoryForOriginAndType(origin, type, true); + ASSERT_TRUE(!target.empty()); + ASSERT_TRUE(base::DirectoryExists(target)); + } + + bool GetRootPath(const GURL& origin_url, + fileapi::FileSystemType type, + OpenFileSystemMode mode, + base::FilePath* root_path) { + base::PlatformFileError error = base::PLATFORM_FILE_OK; + backend_->OpenFileSystem( + origin_url, type, mode, + base::Bind(&DidOpenFileSystem, &error)); + base::MessageLoop::current()->RunUntilIdle(); + if (error != base::PLATFORM_FILE_OK) + return false; + base::FilePath returned_root_path = + delegate_->GetBaseDirectoryForOriginAndType( + origin_url, type, false /* create */); + if (root_path) + *root_path = returned_root_path; + return !returned_root_path.empty(); + } + + base::FilePath file_system_path() const { + return data_dir_.path().Append( + SandboxFileSystemBackendDelegate::kFileSystemDirectory); + } + + base::ScopedTempDir data_dir_; + base::MessageLoop message_loop_; + scoped_ptr<SandboxFileSystemBackendDelegate> delegate_; + scoped_ptr<SandboxFileSystemBackend> backend_; +}; + +TEST_F(SandboxFileSystemBackendTest, Empty) { + SetUpNewBackend(CreateAllowFileAccessOptions()); + scoped_ptr<SandboxFileSystemBackendDelegate::OriginEnumerator> enumerator( + CreateOriginEnumerator()); + ASSERT_TRUE(enumerator->Next().is_empty()); +} + +TEST_F(SandboxFileSystemBackendTest, EnumerateOrigins) { + SetUpNewBackend(CreateAllowFileAccessOptions()); + const char* temporary_origins[] = { + "http://www.bar.com/", + "http://www.foo.com/", + "http://www.foo.com:1/", + "http://www.example.com:8080/", + "http://www.google.com:80/", + }; + const char* persistent_origins[] = { + "http://www.bar.com/", + "http://www.foo.com:8080/", + "http://www.foo.com:80/", + }; + size_t temporary_size = ARRAYSIZE_UNSAFE(temporary_origins); + size_t persistent_size = ARRAYSIZE_UNSAFE(persistent_origins); + std::set<GURL> temporary_set, persistent_set; + for (size_t i = 0; i < temporary_size; ++i) { + CreateOriginTypeDirectory(GURL(temporary_origins[i]), + fileapi::kFileSystemTypeTemporary); + temporary_set.insert(GURL(temporary_origins[i])); + } + for (size_t i = 0; i < persistent_size; ++i) { + CreateOriginTypeDirectory(GURL(persistent_origins[i]), + kFileSystemTypePersistent); + persistent_set.insert(GURL(persistent_origins[i])); + } + + scoped_ptr<SandboxFileSystemBackendDelegate::OriginEnumerator> enumerator( + CreateOriginEnumerator()); + size_t temporary_actual_size = 0; + size_t persistent_actual_size = 0; + GURL current; + while (!(current = enumerator->Next()).is_empty()) { + SCOPED_TRACE(testing::Message() << "EnumerateOrigin " << current.spec()); + if (enumerator->HasFileSystemType(kFileSystemTypeTemporary)) { + ASSERT_TRUE(temporary_set.find(current) != temporary_set.end()); + ++temporary_actual_size; + } + if (enumerator->HasFileSystemType(kFileSystemTypePersistent)) { + ASSERT_TRUE(persistent_set.find(current) != persistent_set.end()); + ++persistent_actual_size; + } + } + + EXPECT_EQ(temporary_size, temporary_actual_size); + EXPECT_EQ(persistent_size, persistent_actual_size); +} + +TEST_F(SandboxFileSystemBackendTest, GetRootPathCreateAndExamine) { + std::vector<base::FilePath> returned_root_path( + ARRAYSIZE_UNSAFE(kRootPathTestCases)); + SetUpNewBackend(CreateAllowFileAccessOptions()); + + // Create a new root directory. + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathTestCases); ++i) { + SCOPED_TRACE(testing::Message() << "RootPath (create) #" << i << " " + << kRootPathTestCases[i].expected_path); + + base::FilePath root_path; + EXPECT_TRUE(GetRootPath(GURL(kRootPathTestCases[i].origin_url), + kRootPathTestCases[i].type, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + &root_path)); + + base::FilePath expected = file_system_path().AppendASCII( + kRootPathTestCases[i].expected_path); + EXPECT_EQ(expected.value(), root_path.value()); + EXPECT_TRUE(base::DirectoryExists(root_path)); + ASSERT_TRUE(returned_root_path.size() > i); + returned_root_path[i] = root_path; + } + + // Get the root directory with create=false and see if we get the + // same directory. + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathTestCases); ++i) { + SCOPED_TRACE(testing::Message() << "RootPath (get) #" << i << " " + << kRootPathTestCases[i].expected_path); + + base::FilePath root_path; + EXPECT_TRUE(GetRootPath(GURL(kRootPathTestCases[i].origin_url), + kRootPathTestCases[i].type, + OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT, + &root_path)); + ASSERT_TRUE(returned_root_path.size() > i); + EXPECT_EQ(returned_root_path[i].value(), root_path.value()); + } +} + +TEST_F(SandboxFileSystemBackendTest, + GetRootPathCreateAndExamineWithNewBackend) { + std::vector<base::FilePath> returned_root_path( + ARRAYSIZE_UNSAFE(kRootPathTestCases)); + SetUpNewBackend(CreateAllowFileAccessOptions()); + + GURL origin_url("http://foo.com:1/"); + + base::FilePath root_path1; + EXPECT_TRUE(GetRootPath(origin_url, kFileSystemTypeTemporary, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + &root_path1)); + + SetUpNewBackend(CreateDisallowFileAccessOptions()); + base::FilePath root_path2; + EXPECT_TRUE(GetRootPath(origin_url, kFileSystemTypeTemporary, + OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT, + &root_path2)); + + EXPECT_EQ(root_path1.value(), root_path2.value()); +} + +TEST_F(SandboxFileSystemBackendTest, GetRootPathGetWithoutCreate) { + SetUpNewBackend(CreateDisallowFileAccessOptions()); + + // Try to get a root directory without creating. + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathTestCases); ++i) { + SCOPED_TRACE(testing::Message() << "RootPath (create=false) #" << i << " " + << kRootPathTestCases[i].expected_path); + EXPECT_FALSE(GetRootPath(GURL(kRootPathTestCases[i].origin_url), + kRootPathTestCases[i].type, + OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT, + NULL)); + } +} + +TEST_F(SandboxFileSystemBackendTest, GetRootPathInIncognito) { + SetUpNewBackend(CreateIncognitoFileSystemOptions()); + + // Try to get a root directory. + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathTestCases); ++i) { + SCOPED_TRACE(testing::Message() << "RootPath (incognito) #" << i << " " + << kRootPathTestCases[i].expected_path); + EXPECT_FALSE( + GetRootPath(GURL(kRootPathTestCases[i].origin_url), + kRootPathTestCases[i].type, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + NULL)); + } +} + +TEST_F(SandboxFileSystemBackendTest, GetRootPathFileURI) { + SetUpNewBackend(CreateDisallowFileAccessOptions()); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathFileURITestCases); ++i) { + SCOPED_TRACE(testing::Message() << "RootPathFileURI (disallow) #" + << i << " " << kRootPathFileURITestCases[i].expected_path); + EXPECT_FALSE( + GetRootPath(GURL(kRootPathFileURITestCases[i].origin_url), + kRootPathFileURITestCases[i].type, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + NULL)); + } +} + +TEST_F(SandboxFileSystemBackendTest, GetRootPathFileURIWithAllowFlag) { + SetUpNewBackend(CreateAllowFileAccessOptions()); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathFileURITestCases); ++i) { + SCOPED_TRACE(testing::Message() << "RootPathFileURI (allow) #" + << i << " " << kRootPathFileURITestCases[i].expected_path); + base::FilePath root_path; + EXPECT_TRUE(GetRootPath(GURL(kRootPathFileURITestCases[i].origin_url), + kRootPathFileURITestCases[i].type, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + &root_path)); + base::FilePath expected = file_system_path().AppendASCII( + kRootPathFileURITestCases[i].expected_path); + EXPECT_EQ(expected.value(), root_path.value()); + EXPECT_TRUE(base::DirectoryExists(root_path)); + } +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_test_helper.cc b/chromium/webkit/browser/fileapi/sandbox_file_system_test_helper.cc new file mode 100644 index 00000000000..97d7a5f2e4a --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_file_system_test_helper.cc @@ -0,0 +1,152 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/sandbox_file_system_test_helper.h" + +#include "base/file_util.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_operation_runner.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/file_system_usage_cache.h" +#include "webkit/browser/fileapi/mock_file_system_context.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +SandboxFileSystemTestHelper::SandboxFileSystemTestHelper( + const GURL& origin, FileSystemType type) + : origin_(origin), type_(type), file_util_(NULL) { +} + +SandboxFileSystemTestHelper::SandboxFileSystemTestHelper() + : origin_(GURL("http://foo.com")), + type_(kFileSystemTypeTemporary), + file_util_(NULL) { +} + +SandboxFileSystemTestHelper::~SandboxFileSystemTestHelper() { +} + +void SandboxFileSystemTestHelper::SetUp(const base::FilePath& base_dir) { + SetUp(base_dir, NULL); +} + +void SandboxFileSystemTestHelper::SetUp( + FileSystemContext* file_system_context) { + file_system_context_ = file_system_context; + + SetUpFileSystem(); +} + +void SandboxFileSystemTestHelper::SetUp( + const base::FilePath& base_dir, + quota::QuotaManagerProxy* quota_manager_proxy) { + file_system_context_ = CreateFileSystemContextForTesting( + quota_manager_proxy, base_dir); + + SetUpFileSystem(); +} + +void SandboxFileSystemTestHelper::TearDown() { + file_system_context_ = NULL; + base::MessageLoop::current()->RunUntilIdle(); +} + +base::FilePath SandboxFileSystemTestHelper::GetOriginRootPath() { + return file_system_context_->sandbox_delegate()-> + GetBaseDirectoryForOriginAndType(origin_, type_, false); +} + +base::FilePath SandboxFileSystemTestHelper::GetLocalPath( + const base::FilePath& path) { + DCHECK(file_util_); + base::FilePath local_path; + scoped_ptr<FileSystemOperationContext> context(NewOperationContext()); + file_util_->GetLocalFilePath(context.get(), CreateURL(path), &local_path); + return local_path; +} + +base::FilePath SandboxFileSystemTestHelper::GetLocalPathFromASCII( + const std::string& path) { + return GetLocalPath(base::FilePath().AppendASCII(path)); +} + +base::FilePath SandboxFileSystemTestHelper::GetUsageCachePath() const { + return file_system_context_->sandbox_delegate()-> + GetUsageCachePathForOriginAndType(origin_, type_); +} + +FileSystemURL SandboxFileSystemTestHelper::CreateURL( + const base::FilePath& path) const { + return file_system_context_->CreateCrackedFileSystemURL(origin_, type_, path); +} + +int64 SandboxFileSystemTestHelper::GetCachedOriginUsage() const { + return file_system_context_->GetQuotaUtil(type_) + ->GetOriginUsageOnFileThread(file_system_context_.get(), origin_, type_); +} + +int64 SandboxFileSystemTestHelper::ComputeCurrentOriginUsage() { + usage_cache()->CloseCacheFiles(); + int64 size = base::ComputeDirectorySize(GetOriginRootPath()); + if (base::PathExists(GetUsageCachePath())) + size -= FileSystemUsageCache::kUsageFileSize; + return size; +} + +int64 +SandboxFileSystemTestHelper::ComputeCurrentDirectoryDatabaseUsage() { + return base::ComputeDirectorySize( + GetOriginRootPath().AppendASCII("Paths")); +} + +FileSystemOperationRunner* SandboxFileSystemTestHelper::operation_runner() { + return file_system_context_->operation_runner(); +} + +FileSystemOperationContext* +SandboxFileSystemTestHelper::NewOperationContext() { + DCHECK(file_system_context_.get()); + FileSystemOperationContext* context = + new FileSystemOperationContext(file_system_context_.get()); + context->set_update_observers( + *file_system_context_->GetUpdateObservers(type_)); + return context; +} + +void SandboxFileSystemTestHelper::AddFileChangeObserver( + FileChangeObserver* observer) { + file_system_context_->sandbox_backend()-> + AddFileChangeObserver(type_, observer, NULL); +} + +FileSystemUsageCache* SandboxFileSystemTestHelper::usage_cache() { + return file_system_context()->sandbox_delegate()->usage_cache(); +} + +void SandboxFileSystemTestHelper::SetUpFileSystem() { + DCHECK(file_system_context_.get()); + DCHECK(file_system_context_->sandbox_backend()->CanHandleType(type_)); + + file_util_ = file_system_context_->GetFileUtil(type_); + DCHECK(file_util_); + + // Prepare the origin's root directory. + file_system_context_->sandbox_delegate()-> + GetBaseDirectoryForOriginAndType(origin_, type_, true /* create */); + + // Initialize the usage cache file. + base::FilePath usage_cache_path = GetUsageCachePath(); + if (!usage_cache_path.empty()) + usage_cache()->UpdateUsage(usage_cache_path, 0); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_test_helper.h b/chromium/webkit/browser/fileapi/sandbox_file_system_test_helper.h new file mode 100644 index 00000000000..5a48872326c --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_file_system_test_helper.h @@ -0,0 +1,103 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_TEST_HELPER_H_ +#define WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_TEST_HELPER_H_ + +#include <string> + +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "url/gurl.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/file_system_usage_cache.h" +#include "webkit/browser/fileapi/task_runner_bound_observer_list.h" +#include "webkit/common/fileapi/file_system_types.h" +#include "webkit/common/fileapi/file_system_util.h" +#include "webkit/common/quota/quota_types.h" + +namespace base { +class FilePath; +} + +namespace quota { +class QuotaManagerProxy; +} + +namespace fileapi { + +class FileSystemContext; +class FileSystemFileUtil; +class FileSystemOperationContext; +class FileSystemOperationRunner; + +// Filesystem test helper class that encapsulates test environment for +// a given {origin, type} pair. This helper only works for sandboxed +// file systems (Temporary or Persistent). +class SandboxFileSystemTestHelper { + public: + SandboxFileSystemTestHelper(const GURL& origin, FileSystemType type); + SandboxFileSystemTestHelper(); + ~SandboxFileSystemTestHelper(); + + void SetUp(const base::FilePath& base_dir); + // If you want to use more than one SandboxFileSystemTestHelper in + // a single base directory, they have to share a context, so that they don't + // have multiple databases fighting over the lock to the origin directory + // [deep down inside ObfuscatedFileUtil]. + void SetUp(FileSystemContext* file_system_context); + void SetUp(const base::FilePath& base_dir, + quota::QuotaManagerProxy* quota_manager_proxy); + void TearDown(); + + base::FilePath GetOriginRootPath(); + base::FilePath GetLocalPath(const base::FilePath& path); + base::FilePath GetLocalPathFromASCII(const std::string& path); + + // Returns empty path if filesystem type is neither temporary nor persistent. + base::FilePath GetUsageCachePath() const; + + FileSystemURL CreateURL(const base::FilePath& path) const; + FileSystemURL CreateURLFromUTF8(const std::string& utf8) const { + return CreateURL(base::FilePath::FromUTF8Unsafe(utf8)); + } + + // This returns cached usage size returned by QuotaUtil. + int64 GetCachedOriginUsage() const; + + // This doesn't work with OFSFU. + int64 ComputeCurrentOriginUsage(); + + int64 ComputeCurrentDirectoryDatabaseUsage(); + + FileSystemOperationRunner* operation_runner(); + FileSystemOperationContext* NewOperationContext(); + + void AddFileChangeObserver(FileChangeObserver* observer); + + FileSystemContext* file_system_context() const { + return file_system_context_.get(); + } + + const GURL& origin() const { return origin_; } + FileSystemType type() const { return type_; } + quota::StorageType storage_type() const { + return FileSystemTypeToQuotaStorageType(type_); + } + FileSystemFileUtil* file_util() const { return file_util_; } + FileSystemUsageCache* usage_cache(); + + private: + void SetUpFileSystem(); + + scoped_refptr<FileSystemContext> file_system_context_; + + const GURL origin_; + const FileSystemType type_; + FileSystemFileUtil* file_util_; +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_TEST_HELPER_H_ diff --git a/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database.cc b/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database.cc new file mode 100644 index 00000000000..7ed4e8ce4af --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database.cc @@ -0,0 +1,105 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/sandbox_isolated_origin_database.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "webkit/browser/fileapi/sandbox_origin_database.h" + +namespace fileapi { + +// Special directory name for isolated origin. +const base::FilePath::CharType +SandboxIsolatedOriginDatabase::kOriginDirectory[] = FILE_PATH_LITERAL("iso"); + +SandboxIsolatedOriginDatabase::SandboxIsolatedOriginDatabase( + const std::string& origin, + const base::FilePath& file_system_directory) + : migration_checked_(false), + origin_(origin), + file_system_directory_(file_system_directory) { +} + +SandboxIsolatedOriginDatabase::~SandboxIsolatedOriginDatabase() { +} + +bool SandboxIsolatedOriginDatabase::HasOriginPath( + const std::string& origin) { + MigrateDatabaseIfNeeded(); + return (origin_ == origin); +} + +bool SandboxIsolatedOriginDatabase::GetPathForOrigin( + const std::string& origin, base::FilePath* directory) { + MigrateDatabaseIfNeeded(); + if (origin != origin_) + return false; + *directory = base::FilePath(kOriginDirectory); + return true; +} + +bool SandboxIsolatedOriginDatabase::RemovePathForOrigin( + const std::string& origin) { + return true; +} + +bool SandboxIsolatedOriginDatabase::ListAllOrigins( + std::vector<OriginRecord>* origins) { + MigrateDatabaseIfNeeded(); + origins->push_back(OriginRecord(origin_, base::FilePath(kOriginDirectory))); + return true; +} + +void SandboxIsolatedOriginDatabase::DropDatabase() { +} + +void SandboxIsolatedOriginDatabase::MigrateBackDatabase( + const std::string& origin, + const base::FilePath& file_system_directory, + SandboxOriginDatabase* database) { + base::FilePath isolated_directory = + file_system_directory.Append(kOriginDirectory); + + if (database->HasOriginPath(origin)) { + // Don't bother. + base::DeleteFile(isolated_directory, true /* recursive */); + return; + } + + base::FilePath directory_name; + if (database->GetPathForOrigin(origin, &directory_name)) { + base::FilePath origin_directory = + file_system_directory.Append(directory_name); + base::DeleteFile(origin_directory, true /* recursive */); + base::Move(isolated_directory, origin_directory); + } +} + +void SandboxIsolatedOriginDatabase::MigrateDatabaseIfNeeded() { + if (migration_checked_) + return; + + migration_checked_ = true; + // See if we have non-isolated version of sandbox database. + scoped_ptr<SandboxOriginDatabase> database( + new SandboxOriginDatabase(file_system_directory_)); + if (!database->HasOriginPath(origin_)) + return; + + base::FilePath directory_name; + if (database->GetPathForOrigin(origin_, &directory_name) && + directory_name != base::FilePath(kOriginDirectory)) { + base::FilePath from_path = file_system_directory_.Append(directory_name); + base::FilePath to_path = file_system_directory_.Append(kOriginDirectory); + + if (base::PathExists(to_path)) + base::DeleteFile(to_path, true /* recursive */); + base::Move(from_path, to_path); + } + + database->RemoveDatabase(); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database.h b/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database.h new file mode 100644 index 00000000000..2505659b064 --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database.h @@ -0,0 +1,49 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_ +#define WEBKIT_BROWSER_FILEAPI_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_ + +#include "webkit/browser/fileapi/sandbox_origin_database_interface.h" + +namespace fileapi { + +class SandboxOriginDatabase; + +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE SandboxIsolatedOriginDatabase + : public SandboxOriginDatabaseInterface { + public: + static const base::FilePath::CharType kOriginDirectory[]; + + explicit SandboxIsolatedOriginDatabase( + const std::string& origin, + const base::FilePath& file_system_directory); + virtual ~SandboxIsolatedOriginDatabase(); + + // SandboxOriginDatabaseInterface overrides. + virtual bool HasOriginPath(const std::string& origin) OVERRIDE; + virtual bool GetPathForOrigin(const std::string& origin, + base::FilePath* directory) OVERRIDE; + virtual bool RemovePathForOrigin(const std::string& origin) OVERRIDE; + virtual bool ListAllOrigins(std::vector<OriginRecord>* origins) OVERRIDE; + virtual void DropDatabase() OVERRIDE; + + static void MigrateBackDatabase( + const std::string& origin, + const base::FilePath& file_system_directory, + SandboxOriginDatabase* origin_database); + + private: + void MigrateDatabaseIfNeeded(); + + bool migration_checked_; + const std::string origin_; + const base::FilePath file_system_directory_; + + DISALLOW_COPY_AND_ASSIGN(SandboxIsolatedOriginDatabase); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_ diff --git a/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database_unittest.cc b/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database_unittest.cc new file mode 100644 index 00000000000..7eeab2185b6 --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database_unittest.cc @@ -0,0 +1,87 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/sandbox_isolated_origin_database.h" +#include "webkit/browser/fileapi/sandbox_origin_database.h" + +namespace fileapi { + +TEST(SandboxIsolatedOriginDatabaseTest, BasicTest) { + base::ScopedTempDir dir; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + + std::string kOrigin("origin"); + SandboxIsolatedOriginDatabase database(kOrigin, dir.path()); + + EXPECT_TRUE(database.HasOriginPath(kOrigin)); + + base::FilePath path1, path2; + + EXPECT_FALSE(database.GetPathForOrigin(std::string(), &path1)); + EXPECT_FALSE(database.GetPathForOrigin("foo", &path1)); + + EXPECT_TRUE(database.HasOriginPath(kOrigin)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin, &path1)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin, &path2)); + EXPECT_FALSE(path1.empty()); + EXPECT_FALSE(path2.empty()); + EXPECT_EQ(path1, path2); +} + +TEST(SandboxIsolatedOriginDatabaseTest, MigrationTest) { + base::ScopedTempDir dir; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + + std::string kOrigin("origin"); + std::string kFakeDirectoryData("0123456789"); + base::FilePath path; + base::FilePath old_db_path; + + // Initialize the directory with one origin using the regular + // SandboxOriginDatabase. + { + SandboxOriginDatabase database_old(dir.path()); + old_db_path = database_old.GetDatabasePath(); + EXPECT_FALSE(base::PathExists(old_db_path)); + EXPECT_TRUE(database_old.GetPathForOrigin(kOrigin, &path)); + EXPECT_FALSE(path.empty()); + EXPECT_TRUE(base::DirectoryExists(old_db_path)); + + // Populate the origin directory with some fake data. + base::FilePath directory_db_path = dir.path().Append(path); + ASSERT_TRUE(file_util::CreateDirectory(directory_db_path)); + EXPECT_EQ(static_cast<int>(kFakeDirectoryData.size()), + file_util::WriteFile(directory_db_path.AppendASCII("dummy"), + kFakeDirectoryData.data(), + kFakeDirectoryData.size())); + } + + // Re-open the directory using sandboxIsolatedOriginDatabase. + SandboxIsolatedOriginDatabase database(kOrigin, dir.path()); + + // The database is migrated from the old one, so we should still + // see the same origin. + EXPECT_TRUE(database.HasOriginPath(kOrigin)); + EXPECT_TRUE(database.GetPathForOrigin(kOrigin, &path)); + EXPECT_FALSE(path.empty()); + + // The directory content must be kept (or migrated if necessary), + // so we should see the same fake data. + std::string origin_db_data; + base::FilePath directory_db_path = dir.path().Append(path); + EXPECT_TRUE(base::DirectoryExists(directory_db_path)); + EXPECT_TRUE(base::PathExists(directory_db_path.AppendASCII("dummy"))); + EXPECT_TRUE(file_util::ReadFileToString( + directory_db_path.AppendASCII("dummy"), &origin_db_data)); + EXPECT_EQ(kFakeDirectoryData, origin_db_data); + + // After the migration the database must be gone. + EXPECT_FALSE(base::PathExists(old_db_path)); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_origin_database.cc b/chromium/webkit/browser/fileapi/sandbox_origin_database.cc new file mode 100644 index 00000000000..4ce83017cc0 --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_origin_database.cc @@ -0,0 +1,341 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/sandbox_origin_database.h" + +#include <set> +#include <utility> + +#include "base/file_util.h" +#include "base/files/file_enumerator.h" +#include "base/format_macros.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" +#include "third_party/leveldatabase/src/include/leveldb/write_batch.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace { + +const base::FilePath::CharType kOriginDatabaseName[] = + FILE_PATH_LITERAL("Origins"); +const char kOriginKeyPrefix[] = "ORIGIN:"; +const char kLastPathKey[] = "LAST_PATH"; +const int64 kMinimumReportIntervalHours = 1; +const char kInitStatusHistogramLabel[] = "FileSystem.OriginDatabaseInit"; +const char kDatabaseRepairHistogramLabel[] = "FileSystem.OriginDatabaseRepair"; + +enum InitStatus { + INIT_STATUS_OK = 0, + INIT_STATUS_CORRUPTION, + INIT_STATUS_IO_ERROR, + INIT_STATUS_UNKNOWN_ERROR, + INIT_STATUS_MAX +}; + +enum RepairResult { + DB_REPAIR_SUCCEEDED = 0, + DB_REPAIR_FAILED, + DB_REPAIR_MAX +}; + +std::string OriginToOriginKey(const std::string& origin) { + std::string key(kOriginKeyPrefix); + return key + origin; +} + +const char* LastPathKey() { + return kLastPathKey; +} + +} // namespace + +namespace fileapi { + +SandboxOriginDatabase::SandboxOriginDatabase( + const base::FilePath& file_system_directory) + : file_system_directory_(file_system_directory) { +} + +SandboxOriginDatabase::~SandboxOriginDatabase() { +} + +bool SandboxOriginDatabase::Init(InitOption init_option, + RecoveryOption recovery_option) { + if (db_) + return true; + + base::FilePath db_path = GetDatabasePath(); + if (init_option == FAIL_IF_NONEXISTENT && !base::PathExists(db_path)) + return false; + + std::string path = FilePathToString(db_path); + leveldb::Options options; + options.max_open_files = 0; // Use minimum. + options.create_if_missing = true; + leveldb::DB* db; + leveldb::Status status = leveldb::DB::Open(options, path, &db); + ReportInitStatus(status); + if (status.ok()) { + db_.reset(db); + return true; + } + HandleError(FROM_HERE, status); + + // Corruption due to missing necessary MANIFEST-* file causes IOError instead + // of Corruption error. + // Try to repair database even when IOError case. + if (!status.IsCorruption() && !status.IsIOError()) + return false; + + switch (recovery_option) { + case FAIL_ON_CORRUPTION: + return false; + case REPAIR_ON_CORRUPTION: + LOG(WARNING) << "Attempting to repair SandboxOriginDatabase."; + + if (RepairDatabase(path)) { + UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel, + DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX); + LOG(WARNING) << "Repairing SandboxOriginDatabase completed."; + return true; + } + UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel, + DB_REPAIR_FAILED, DB_REPAIR_MAX); + // fall through + case DELETE_ON_CORRUPTION: + if (!base::DeleteFile(file_system_directory_, true)) + return false; + if (!file_util::CreateDirectory(file_system_directory_)) + return false; + return Init(init_option, FAIL_ON_CORRUPTION); + } + NOTREACHED(); + return false; +} + +bool SandboxOriginDatabase::RepairDatabase(const std::string& db_path) { + DCHECK(!db_.get()); + leveldb::Options options; + options.max_open_files = 0; // Use minimum. + if (!leveldb::RepairDB(db_path, options).ok() || + !Init(FAIL_IF_NONEXISTENT, FAIL_ON_CORRUPTION)) { + LOG(WARNING) << "Failed to repair SandboxOriginDatabase."; + return false; + } + + // See if the repaired entries match with what we have on disk. + std::set<base::FilePath> directories; + base::FileEnumerator file_enum(file_system_directory_, + false /* recursive */, + base::FileEnumerator::DIRECTORIES); + base::FilePath path_each; + while (!(path_each = file_enum.Next()).empty()) + directories.insert(path_each.BaseName()); + std::set<base::FilePath>::iterator db_dir_itr = + directories.find(base::FilePath(kOriginDatabaseName)); + // Make sure we have the database file in its directory and therefore we are + // working on the correct path. + DCHECK(db_dir_itr != directories.end()); + directories.erase(db_dir_itr); + + std::vector<OriginRecord> origins; + if (!ListAllOrigins(&origins)) { + DropDatabase(); + return false; + } + + // Delete any obsolete entries from the origins database. + for (std::vector<OriginRecord>::iterator db_origin_itr = origins.begin(); + db_origin_itr != origins.end(); + ++db_origin_itr) { + std::set<base::FilePath>::iterator dir_itr = + directories.find(db_origin_itr->path); + if (dir_itr == directories.end()) { + if (!RemovePathForOrigin(db_origin_itr->origin)) { + DropDatabase(); + return false; + } + } else { + directories.erase(dir_itr); + } + } + + // Delete any directories not listed in the origins database. + for (std::set<base::FilePath>::iterator dir_itr = directories.begin(); + dir_itr != directories.end(); + ++dir_itr) { + if (!base::DeleteFile(file_system_directory_.Append(*dir_itr), + true /* recursive */)) { + DropDatabase(); + return false; + } + } + + return true; +} + +void SandboxOriginDatabase::HandleError( + const tracked_objects::Location& from_here, + const leveldb::Status& status) { + db_.reset(); + LOG(ERROR) << "SandboxOriginDatabase failed at: " + << from_here.ToString() << " with error: " << status.ToString(); +} + +void SandboxOriginDatabase::ReportInitStatus(const leveldb::Status& status) { + base::Time now = base::Time::Now(); + base::TimeDelta minimum_interval = + base::TimeDelta::FromHours(kMinimumReportIntervalHours); + if (last_reported_time_ + minimum_interval >= now) + return; + last_reported_time_ = now; + + if (status.ok()) { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_OK, INIT_STATUS_MAX); + } else if (status.IsCorruption()) { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_CORRUPTION, INIT_STATUS_MAX); + } else if (status.IsIOError()) { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_IO_ERROR, INIT_STATUS_MAX); + } else { + UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel, + INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX); + } +} + +bool SandboxOriginDatabase::HasOriginPath(const std::string& origin) { + if (!Init(FAIL_IF_NONEXISTENT, REPAIR_ON_CORRUPTION)) + return false; + if (origin.empty()) + return false; + std::string path; + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), OriginToOriginKey(origin), &path); + if (status.ok()) + return true; + if (status.IsNotFound()) + return false; + HandleError(FROM_HERE, status); + return false; +} + +bool SandboxOriginDatabase::GetPathForOrigin( + const std::string& origin, base::FilePath* directory) { + if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION)) + return false; + DCHECK(directory); + if (origin.empty()) + return false; + std::string path_string; + std::string origin_key = OriginToOriginKey(origin); + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), origin_key, &path_string); + if (status.IsNotFound()) { + int last_path_number; + if (!GetLastPathNumber(&last_path_number)) + return false; + path_string = base::StringPrintf("%03u", last_path_number + 1); + // store both back as a single transaction + leveldb::WriteBatch batch; + batch.Put(LastPathKey(), path_string); + batch.Put(origin_key, path_string); + status = db_->Write(leveldb::WriteOptions(), &batch); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + } + if (status.ok()) { + *directory = StringToFilePath(path_string); + return true; + } + HandleError(FROM_HERE, status); + return false; +} + +bool SandboxOriginDatabase::RemovePathForOrigin(const std::string& origin) { + if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION)) + return false; + leveldb::Status status = + db_->Delete(leveldb::WriteOptions(), OriginToOriginKey(origin)); + if (status.ok() || status.IsNotFound()) + return true; + HandleError(FROM_HERE, status); + return false; +} + +bool SandboxOriginDatabase::ListAllOrigins( + std::vector<OriginRecord>* origins) { + DCHECK(origins); + if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION)) { + origins->clear(); + return false; + } + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); + std::string origin_key_prefix = OriginToOriginKey(std::string()); + iter->Seek(origin_key_prefix); + origins->clear(); + while (iter->Valid() && + StartsWithASCII(iter->key().ToString(), origin_key_prefix, true)) { + std::string origin = + iter->key().ToString().substr(origin_key_prefix.length()); + base::FilePath path = StringToFilePath(iter->value().ToString()); + origins->push_back(OriginRecord(origin, path)); + iter->Next(); + } + return true; +} + +void SandboxOriginDatabase::DropDatabase() { + db_.reset(); +} + +base::FilePath SandboxOriginDatabase::GetDatabasePath() const { + return file_system_directory_.Append(kOriginDatabaseName); +} + +void SandboxOriginDatabase::RemoveDatabase() { + DropDatabase(); + base::DeleteFile(GetDatabasePath(), true /* recursive */); +} + +bool SandboxOriginDatabase::GetLastPathNumber(int* number) { + DCHECK(db_); + DCHECK(number); + std::string number_string; + leveldb::Status status = + db_->Get(leveldb::ReadOptions(), LastPathKey(), &number_string); + if (status.ok()) + return base::StringToInt(number_string, number); + if (!status.IsNotFound()) { + HandleError(FROM_HERE, status); + return false; + } + // Verify that this is a totally new database, and initialize it. + scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions())); + iter->SeekToFirst(); + if (iter->Valid()) { // DB was not empty, but had no last path number! + LOG(ERROR) << "File system origin database is corrupt!"; + return false; + } + // This is always the first write into the database. If we ever add a + // version number, they should go in in a single transaction. + status = + db_->Put(leveldb::WriteOptions(), LastPathKey(), std::string("-1")); + if (!status.ok()) { + HandleError(FROM_HERE, status); + return false; + } + *number = -1; + return true; +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_origin_database.h b/chromium/webkit/browser/fileapi/sandbox_origin_database.h new file mode 100644 index 00000000000..94cde472ade --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_origin_database.h @@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_H_ +#define WEBKIT_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "webkit/browser/fileapi/sandbox_origin_database_interface.h" + +namespace leveldb { +class DB; +class Status; +} + +namespace tracked_objects { +class Location; +} + +namespace fileapi { + +// All methods of this class other than the constructor may be used only from +// the browser's FILE thread. The constructor may be used on any thread. +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE SandboxOriginDatabase + : public SandboxOriginDatabaseInterface { + public: + // Only one instance of SandboxOriginDatabase should exist for a given path + // at a given time. + explicit SandboxOriginDatabase(const base::FilePath& file_system_directory); + virtual ~SandboxOriginDatabase(); + + // SandboxOriginDatabaseInterface overrides. + virtual bool HasOriginPath(const std::string& origin) OVERRIDE; + virtual bool GetPathForOrigin(const std::string& origin, + base::FilePath* directory) OVERRIDE; + virtual bool RemovePathForOrigin(const std::string& origin) OVERRIDE; + virtual bool ListAllOrigins(std::vector<OriginRecord>* origins) OVERRIDE; + virtual void DropDatabase() OVERRIDE; + + base::FilePath GetDatabasePath() const; + void RemoveDatabase(); + + private: + enum RecoveryOption { + REPAIR_ON_CORRUPTION, + DELETE_ON_CORRUPTION, + FAIL_ON_CORRUPTION, + }; + + enum InitOption { + CREATE_IF_NONEXISTENT, + FAIL_IF_NONEXISTENT, + }; + + bool Init(InitOption init_option, RecoveryOption recovery_option); + bool RepairDatabase(const std::string& db_path); + void HandleError(const tracked_objects::Location& from_here, + const leveldb::Status& status); + void ReportInitStatus(const leveldb::Status& status); + bool GetLastPathNumber(int* number); + + base::FilePath file_system_directory_; + scoped_ptr<leveldb::DB> db_; + base::Time last_reported_time_; + DISALLOW_COPY_AND_ASSIGN(SandboxOriginDatabase); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_H_ diff --git a/chromium/webkit/browser/fileapi/sandbox_origin_database_interface.cc b/chromium/webkit/browser/fileapi/sandbox_origin_database_interface.cc new file mode 100644 index 00000000000..e2f80c58b32 --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_origin_database_interface.cc @@ -0,0 +1,20 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/sandbox_origin_database_interface.h" + +namespace fileapi { + +SandboxOriginDatabaseInterface::OriginRecord::OriginRecord() { +} + +SandboxOriginDatabaseInterface::OriginRecord::OriginRecord( + const std::string& origin_in, const base::FilePath& path_in) + : origin(origin_in), path(path_in) { +} + +SandboxOriginDatabaseInterface::OriginRecord::~OriginRecord() { +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_origin_database_interface.h b/chromium/webkit/browser/fileapi/sandbox_origin_database_interface.h new file mode 100644 index 00000000000..8b88f9349af --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_origin_database_interface.h @@ -0,0 +1,55 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_INTERFACE_H_ +#define WEBKIT_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_INTERFACE_H_ + +#include <string> +#include <vector> + +#include "base/files/file_path.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace fileapi { + +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE SandboxOriginDatabaseInterface { + public: + struct WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE OriginRecord { + std::string origin; + base::FilePath path; + + OriginRecord(); + OriginRecord(const std::string& origin, const base::FilePath& path); + ~OriginRecord(); + }; + + virtual ~SandboxOriginDatabaseInterface() {} + + // Returns true if the origin's path is included in this database. + virtual bool HasOriginPath(const std::string& origin) = 0; + + // This will produce a unique path and add it to its database, if it's not + // already present. + virtual bool GetPathForOrigin(const std::string& origin, + base::FilePath* directory) = 0; + + // Removes the origin's path from the database. + // Returns success if the origin has been successfully removed, or + // the origin is not found. + // (This doesn't remove the actual path). + virtual bool RemovePathForOrigin(const std::string& origin) = 0; + + // Lists all origins in this database. + virtual bool ListAllOrigins(std::vector<OriginRecord>* origins) = 0; + + // This will release all database resources in use; call it to save memory. + virtual void DropDatabase() = 0; + + protected: + SandboxOriginDatabaseInterface() {} +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_INTERFACE_H_ diff --git a/chromium/webkit/browser/fileapi/sandbox_origin_database_unittest.cc b/chromium/webkit/browser/fileapi/sandbox_origin_database_unittest.cc new file mode 100644 index 00000000000..867dc273aad --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_origin_database_unittest.cc @@ -0,0 +1,307 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> +#include <functional> +#include <limits> +#include <string> + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/platform_file.h" +#include "base/stl_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/leveldatabase/src/db/filename.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" +#include "webkit/browser/fileapi/sandbox_database_test_helper.h" +#include "webkit/browser/fileapi/sandbox_origin_database.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +namespace { +const base::FilePath::CharType kFileSystemDirName[] = + FILE_PATH_LITERAL("File System"); +const base::FilePath::CharType kOriginDatabaseName[] = + FILE_PATH_LITERAL("Origins"); +} // namespace + +TEST(SandboxOriginDatabaseTest, BasicTest) { + base::ScopedTempDir dir; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + const base::FilePath kFSDir = dir.path().Append(kFileSystemDirName); + EXPECT_FALSE(base::PathExists(kFSDir)); + EXPECT_TRUE(file_util::CreateDirectory(kFSDir)); + + SandboxOriginDatabase database(kFSDir); + std::string origin("origin"); + + EXPECT_FALSE(database.HasOriginPath(origin)); + // Double-check to make sure that had no side effects. + EXPECT_FALSE(database.HasOriginPath(origin)); + + base::FilePath path0; + base::FilePath path1; + + // Empty strings aren't valid origins. + EXPECT_FALSE(database.GetPathForOrigin(std::string(), &path0)); + + EXPECT_TRUE(database.GetPathForOrigin(origin, &path0)); + EXPECT_TRUE(database.HasOriginPath(origin)); + EXPECT_TRUE(database.GetPathForOrigin(origin, &path1)); + EXPECT_FALSE(path0.empty()); + EXPECT_FALSE(path1.empty()); + EXPECT_EQ(path0, path1); + + EXPECT_TRUE(base::PathExists(kFSDir.Append(kOriginDatabaseName))); +} + +TEST(SandboxOriginDatabaseTest, TwoPathTest) { + base::ScopedTempDir dir; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + const base::FilePath kFSDir = dir.path().Append(kFileSystemDirName); + EXPECT_FALSE(base::PathExists(kFSDir)); + EXPECT_TRUE(file_util::CreateDirectory(kFSDir)); + + SandboxOriginDatabase database(kFSDir); + std::string origin0("origin0"); + std::string origin1("origin1"); + + EXPECT_FALSE(database.HasOriginPath(origin0)); + EXPECT_FALSE(database.HasOriginPath(origin1)); + + base::FilePath path0; + base::FilePath path1; + EXPECT_TRUE(database.GetPathForOrigin(origin0, &path0)); + EXPECT_TRUE(database.HasOriginPath(origin0)); + EXPECT_FALSE(database.HasOriginPath(origin1)); + EXPECT_TRUE(database.GetPathForOrigin(origin1, &path1)); + EXPECT_TRUE(database.HasOriginPath(origin1)); + EXPECT_FALSE(path0.empty()); + EXPECT_FALSE(path1.empty()); + EXPECT_NE(path0, path1); + + EXPECT_TRUE(base::PathExists(kFSDir.Append(kOriginDatabaseName))); +} + +TEST(SandboxOriginDatabaseTest, DropDatabaseTest) { + base::ScopedTempDir dir; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + const base::FilePath kFSDir = dir.path().Append(kFileSystemDirName); + EXPECT_FALSE(base::PathExists(kFSDir)); + EXPECT_TRUE(file_util::CreateDirectory(kFSDir)); + + SandboxOriginDatabase database(kFSDir); + std::string origin("origin"); + + EXPECT_FALSE(database.HasOriginPath(origin)); + + base::FilePath path0; + EXPECT_TRUE(database.GetPathForOrigin(origin, &path0)); + EXPECT_TRUE(database.HasOriginPath(origin)); + EXPECT_FALSE(path0.empty()); + + EXPECT_TRUE(base::PathExists(kFSDir.Append(kOriginDatabaseName))); + + database.DropDatabase(); + + base::FilePath path1; + EXPECT_TRUE(database.HasOriginPath(origin)); + EXPECT_TRUE(database.GetPathForOrigin(origin, &path1)); + EXPECT_FALSE(path1.empty()); + EXPECT_EQ(path0, path1); +} + +TEST(SandboxOriginDatabaseTest, DeleteOriginTest) { + base::ScopedTempDir dir; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + const base::FilePath kFSDir = dir.path().Append(kFileSystemDirName); + EXPECT_FALSE(base::PathExists(kFSDir)); + EXPECT_TRUE(file_util::CreateDirectory(kFSDir)); + + SandboxOriginDatabase database(kFSDir); + std::string origin("origin"); + + EXPECT_FALSE(database.HasOriginPath(origin)); + EXPECT_TRUE(database.RemovePathForOrigin(origin)); + + base::FilePath path0; + EXPECT_TRUE(database.GetPathForOrigin(origin, &path0)); + EXPECT_TRUE(database.HasOriginPath(origin)); + EXPECT_FALSE(path0.empty()); + + EXPECT_TRUE(database.RemovePathForOrigin(origin)); + EXPECT_FALSE(database.HasOriginPath(origin)); + + base::FilePath path1; + EXPECT_TRUE(database.GetPathForOrigin(origin, &path1)); + EXPECT_FALSE(path1.empty()); + EXPECT_NE(path0, path1); +} + +TEST(SandboxOriginDatabaseTest, ListOriginsTest) { + base::ScopedTempDir dir; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + const base::FilePath kFSDir = dir.path().Append(kFileSystemDirName); + EXPECT_FALSE(base::PathExists(kFSDir)); + EXPECT_TRUE(file_util::CreateDirectory(kFSDir)); + + std::vector<SandboxOriginDatabase::OriginRecord> origins; + + SandboxOriginDatabase database(kFSDir); + EXPECT_TRUE(database.ListAllOrigins(&origins)); + EXPECT_TRUE(origins.empty()); + origins.clear(); + + std::string origin0("origin0"); + std::string origin1("origin1"); + + EXPECT_FALSE(database.HasOriginPath(origin0)); + EXPECT_FALSE(database.HasOriginPath(origin1)); + + base::FilePath path0; + base::FilePath path1; + EXPECT_TRUE(database.GetPathForOrigin(origin0, &path0)); + EXPECT_TRUE(database.ListAllOrigins(&origins)); + EXPECT_EQ(origins.size(), 1UL); + EXPECT_EQ(origins[0].origin, origin0); + EXPECT_EQ(origins[0].path, path0); + origins.clear(); + EXPECT_TRUE(database.GetPathForOrigin(origin1, &path1)); + EXPECT_TRUE(database.ListAllOrigins(&origins)); + EXPECT_EQ(origins.size(), 2UL); + if (origins[0].origin == origin0) { + EXPECT_EQ(origins[0].path, path0); + EXPECT_EQ(origins[1].origin, origin1); + EXPECT_EQ(origins[1].path, path1); + } else { + EXPECT_EQ(origins[0].origin, origin1); + EXPECT_EQ(origins[0].path, path1); + EXPECT_EQ(origins[1].origin, origin0); + EXPECT_EQ(origins[1].path, path0); + } +} + +TEST(SandboxOriginDatabaseTest, DatabaseRecoveryTest) { + // Checks if SandboxOriginDatabase properly handles database corruption. + // In this test, we'll register some origins to the origin database, then + // corrupt database and its log file. + // After repairing, the origin database should be consistent even when some + // entries lost. + + base::ScopedTempDir dir; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + const base::FilePath kFSDir = dir.path().Append(kFileSystemDirName); + const base::FilePath kDBDir = kFSDir.Append(kOriginDatabaseName); + EXPECT_FALSE(base::PathExists(kFSDir)); + EXPECT_TRUE(file_util::CreateDirectory(kFSDir)); + + const std::string kOrigins[] = { + "foo.example.com", + "bar.example.com", + "baz.example.com", + "hoge.example.com", + "fuga.example.com", + }; + + scoped_ptr<SandboxOriginDatabase> database( + new SandboxOriginDatabase(kFSDir)); + for (size_t i = 0; i < arraysize(kOrigins); ++i) { + base::FilePath path; + EXPECT_FALSE(database->HasOriginPath(kOrigins[i])); + EXPECT_TRUE(database->GetPathForOrigin(kOrigins[i], &path)); + EXPECT_FALSE(path.empty()); + EXPECT_TRUE(database->GetPathForOrigin(kOrigins[i], &path)); + + if (i != 1) + EXPECT_TRUE(file_util::CreateDirectory(kFSDir.Append(path))); + } + database.reset(); + + const base::FilePath kGarbageDir = kFSDir.AppendASCII("foo"); + const base::FilePath kGarbageFile = kGarbageDir.AppendASCII("bar"); + EXPECT_TRUE(file_util::CreateDirectory(kGarbageDir)); + bool created = false; + base::PlatformFileError error; + base::PlatformFile file = base::CreatePlatformFile( + kGarbageFile, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, + &created, &error); + EXPECT_EQ(base::PLATFORM_FILE_OK, error); + EXPECT_TRUE(created); + EXPECT_TRUE(base::ClosePlatformFile(file)); + + // Corrupt database itself and last log entry to drop last 1 database + // operation. The database should detect the corruption and should recover + // its consistency after recovery. + CorruptDatabase(kDBDir, leveldb::kDescriptorFile, + 0, std::numeric_limits<size_t>::max()); + CorruptDatabase(kDBDir, leveldb::kLogFile, -1, 1); + + base::FilePath path; + database.reset(new SandboxOriginDatabase(kFSDir)); + std::vector<SandboxOriginDatabase::OriginRecord> origins_in_db; + EXPECT_TRUE(database->ListAllOrigins(&origins_in_db)); + + // Expect all but last added origin will be repaired back, and kOrigins[1] + // should be dropped due to absence of backing directory. + EXPECT_EQ(arraysize(kOrigins) - 2, origins_in_db.size()); + + const std::string kOrigin("piyo.example.org"); + EXPECT_FALSE(database->HasOriginPath(kOrigin)); + EXPECT_TRUE(database->GetPathForOrigin(kOrigin, &path)); + EXPECT_FALSE(path.empty()); + EXPECT_TRUE(database->HasOriginPath(kOrigin)); + + EXPECT_FALSE(base::PathExists(kGarbageFile)); + EXPECT_FALSE(base::PathExists(kGarbageDir)); +} + +TEST(SandboxOriginDatabaseTest, DatabaseRecoveryForMissingDBFileTest) { + const leveldb::FileType kLevelDBFileTypes[] = { + leveldb::kLogFile, + leveldb::kDBLockFile, + leveldb::kTableFile, + leveldb::kDescriptorFile, + leveldb::kCurrentFile, + leveldb::kTempFile, + leveldb::kInfoLogFile, + }; + + for (size_t i = 0; i < arraysize(kLevelDBFileTypes); ++i) { + base::ScopedTempDir dir; + ASSERT_TRUE(dir.CreateUniqueTempDir()); + const base::FilePath kFSDir = dir.path().Append(kFileSystemDirName); + const base::FilePath kDBDir = kFSDir.Append(kOriginDatabaseName); + EXPECT_FALSE(base::PathExists(kFSDir)); + EXPECT_TRUE(file_util::CreateDirectory(kFSDir)); + + const std::string kOrigin = "foo.example.com"; + base::FilePath path; + + scoped_ptr<SandboxOriginDatabase> database( + new SandboxOriginDatabase(kFSDir)); + EXPECT_FALSE(database->HasOriginPath(kOrigin)); + EXPECT_TRUE(database->GetPathForOrigin(kOrigin, &path)); + EXPECT_FALSE(path.empty()); + EXPECT_TRUE(database->GetPathForOrigin(kOrigin, &path)); + EXPECT_TRUE(file_util::CreateDirectory(kFSDir.Append(path))); + database.reset(); + + DeleteDatabaseFile(kDBDir, kLevelDBFileTypes[i]); + + database.reset(new SandboxOriginDatabase(kFSDir)); + std::vector<SandboxOriginDatabase::OriginRecord> origins_in_db; + EXPECT_TRUE(database->ListAllOrigins(&origins_in_db)); + + const std::string kOrigin2("piyo.example.org"); + EXPECT_FALSE(database->HasOriginPath(kOrigin2)); + EXPECT_TRUE(database->GetPathForOrigin(kOrigin2, &path)); + EXPECT_FALSE(path.empty()); + EXPECT_TRUE(database->HasOriginPath(kOrigin2)); + } +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_quota_observer.cc b/chromium/webkit/browser/fileapi/sandbox_quota_observer.cc new file mode 100644 index 00000000000..fe5ee3796fb --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_quota_observer.cc @@ -0,0 +1,139 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/sandbox_quota_observer.h" + +#include "base/sequenced_task_runner.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/file_system_usage_cache.h" +#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h" +#include "webkit/browser/fileapi/timed_task_helper.h" +#include "webkit/browser/quota/quota_client.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +SandboxQuotaObserver::SandboxQuotaObserver( + quota::QuotaManagerProxy* quota_manager_proxy, + base::SequencedTaskRunner* update_notify_runner, + ObfuscatedFileUtil* sandbox_file_util, + FileSystemUsageCache* file_system_usage_cache) + : quota_manager_proxy_(quota_manager_proxy), + update_notify_runner_(update_notify_runner), + sandbox_file_util_(sandbox_file_util), + file_system_usage_cache_(file_system_usage_cache) {} + +SandboxQuotaObserver::~SandboxQuotaObserver() {} + +void SandboxQuotaObserver::OnStartUpdate(const FileSystemURL& url) { + DCHECK(update_notify_runner_->RunsTasksOnCurrentThread()); + base::FilePath usage_file_path = GetUsageCachePath(url); + if (usage_file_path.empty()) + return; + file_system_usage_cache_->IncrementDirty(usage_file_path); +} + +void SandboxQuotaObserver::OnUpdate(const FileSystemURL& url, + int64 delta) { + DCHECK(update_notify_runner_->RunsTasksOnCurrentThread()); + + if (quota_manager_proxy_.get()) { + quota_manager_proxy_->NotifyStorageModified( + quota::QuotaClient::kFileSystem, + url.origin(), + FileSystemTypeToQuotaStorageType(url.type()), + delta); + } + + base::FilePath usage_file_path = GetUsageCachePath(url); + if (usage_file_path.empty()) + return; + + pending_update_notification_[usage_file_path] += delta; + if (!delayed_cache_update_helper_) { + delayed_cache_update_helper_.reset( + new TimedTaskHelper(update_notify_runner_.get())); + delayed_cache_update_helper_->Start( + FROM_HERE, + base::TimeDelta(), // No delay. + base::Bind(&SandboxQuotaObserver::ApplyPendingUsageUpdate, + base::Unretained(this))); + } +} + +void SandboxQuotaObserver::OnEndUpdate(const FileSystemURL& url) { + DCHECK(update_notify_runner_->RunsTasksOnCurrentThread()); + + base::FilePath usage_file_path = GetUsageCachePath(url); + if (usage_file_path.empty()) + return; + + PendingUpdateNotificationMap::iterator found = + pending_update_notification_.find(usage_file_path); + if (found != pending_update_notification_.end()) { + UpdateUsageCacheFile(found->first, found->second); + pending_update_notification_.erase(found); + } + + file_system_usage_cache_->DecrementDirty(usage_file_path); +} + +void SandboxQuotaObserver::OnAccess(const FileSystemURL& url) { + if (quota_manager_proxy_.get()) { + quota_manager_proxy_->NotifyStorageAccessed( + quota::QuotaClient::kFileSystem, + url.origin(), + FileSystemTypeToQuotaStorageType(url.type())); + } +} + +void SandboxQuotaObserver::SetUsageCacheEnabled( + const GURL& origin, + FileSystemType type, + bool enabled) { + if (quota_manager_proxy_.get()) { + quota_manager_proxy_->SetUsageCacheEnabled( + quota::QuotaClient::kFileSystem, + origin, + FileSystemTypeToQuotaStorageType(type), + enabled); + } +} + +base::FilePath SandboxQuotaObserver::GetUsageCachePath( + const FileSystemURL& url) { + DCHECK(sandbox_file_util_); + base::PlatformFileError error = base::PLATFORM_FILE_OK; + base::FilePath path = + SandboxFileSystemBackendDelegate::GetUsageCachePathForOriginAndType( + sandbox_file_util_, url.origin(), url.type(), &error); + if (error != base::PLATFORM_FILE_OK) { + LOG(WARNING) << "Could not get usage cache path for: " + << url.DebugString(); + return base::FilePath(); + } + return path; +} + +void SandboxQuotaObserver::ApplyPendingUsageUpdate() { + delayed_cache_update_helper_.reset(); + for (PendingUpdateNotificationMap::iterator itr = + pending_update_notification_.begin(); + itr != pending_update_notification_.end(); + ++itr) { + UpdateUsageCacheFile(itr->first, itr->second); + } + pending_update_notification_.clear(); +} + +void SandboxQuotaObserver::UpdateUsageCacheFile( + const base::FilePath& usage_file_path, + int64 delta) { + DCHECK(!usage_file_path.empty()); + if (!usage_file_path.empty() && delta != 0) + file_system_usage_cache_->AtomicUpdateUsageByDelta(usage_file_path, delta); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/sandbox_quota_observer.h b/chromium/webkit/browser/fileapi/sandbox_quota_observer.h new file mode 100644 index 00000000000..2c16860149c --- /dev/null +++ b/chromium/webkit/browser/fileapi/sandbox_quota_observer.h @@ -0,0 +1,82 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_QUOTA_OBSERVER_H_ +#define WEBKIT_BROWSER_FILEAPI_SANDBOX_QUOTA_OBSERVER_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "webkit/browser/fileapi/file_observers.h" +#include "webkit/browser/fileapi/file_system_url.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace quota { +class QuotaManagerProxy; +} + +namespace fileapi { + +class FileSystemUsageCache; +class FileSystemURL; +class TimedTaskHelper; +class ObfuscatedFileUtil; + +class SandboxQuotaObserver + : public FileUpdateObserver, + public FileAccessObserver { + public: + typedef std::map<base::FilePath, int64> PendingUpdateNotificationMap; + + SandboxQuotaObserver( + quota::QuotaManagerProxy* quota_manager_proxy, + base::SequencedTaskRunner* update_notify_runner, + ObfuscatedFileUtil* sandbox_file_util, + FileSystemUsageCache* file_system_usage_cache_); + virtual ~SandboxQuotaObserver(); + + // FileUpdateObserver overrides. + virtual void OnStartUpdate(const FileSystemURL& url) OVERRIDE; + virtual void OnUpdate(const FileSystemURL& url, int64 delta) OVERRIDE; + virtual void OnEndUpdate(const FileSystemURL& url) OVERRIDE; + + // FileAccessObserver overrides. + virtual void OnAccess(const FileSystemURL& url) OVERRIDE; + + void SetUsageCacheEnabled(const GURL& origin, + FileSystemType type, + bool enabled); + + private: + void ApplyPendingUsageUpdate(); + void UpdateUsageCacheFile(const base::FilePath& usage_file_path, int64 delta); + + base::FilePath GetUsageCachePath(const FileSystemURL& url); + + scoped_refptr<quota::QuotaManagerProxy> quota_manager_proxy_; + scoped_refptr<base::SequencedTaskRunner> update_notify_runner_; + + // Not owned; sandbox_file_util_ should have identical lifetime with this. + ObfuscatedFileUtil* sandbox_file_util_; + + // Not owned; file_system_usage_cache_ should have longer lifetime than this. + FileSystemUsageCache* file_system_usage_cache_; + + PendingUpdateNotificationMap pending_update_notification_; + scoped_ptr<TimedTaskHelper> delayed_cache_update_helper_; + + DISALLOW_COPY_AND_ASSIGN(SandboxQuotaObserver); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_QUOTA_OBSERVER_H_ diff --git a/chromium/webkit/browser/fileapi/task_runner_bound_observer_list.h b/chromium/webkit/browser/fileapi/task_runner_bound_observer_list.h new file mode 100644 index 00000000000..36a3393fe1d --- /dev/null +++ b/chromium/webkit/browser/fileapi/task_runner_bound_observer_list.h @@ -0,0 +1,99 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_TASK_RUNNER_BOUND_OBSERVER_LIST_H_ +#define WEBKIT_BROWSER_FILEAPI_TASK_RUNNER_BOUND_OBSERVER_LIST_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/sequenced_task_runner.h" +#include "base/threading/thread.h" + +namespace fileapi { + +// A wrapper for dispatching method. +template <class T, class Method, class Params> +void NotifyWrapper(T obj, Method m, const Params& p) { + DispatchToMethod(base::internal::UnwrapTraits<T>::Unwrap(obj), m, p); +} + +// An observer list helper to notify on a given task runner. +// Observer pointers (stored as ObserverStoreType) must be kept alive +// until this list dispatches all the notifications. +// +// Unlike regular ObserverList or ObserverListThreadSafe internal observer +// list is immutable (though not declared const) and cannot be modified after +// constructed. +// +// It is ok to specify scoped_refptr<Observer> as ObserverStoreType to +// explicitly keep references if necessary. +template <class Observer, class ObserverStoreType = Observer*> +class TaskRunnerBoundObserverList { + public: + typedef scoped_refptr<base::SequencedTaskRunner> TaskRunnerPtr; + typedef std::map<ObserverStoreType, TaskRunnerPtr> ObserversListMap; + + // Creates an empty list. + TaskRunnerBoundObserverList<Observer, ObserverStoreType>() {} + + // Creates a new list with given |observers|. + explicit TaskRunnerBoundObserverList<Observer, ObserverStoreType>( + const ObserversListMap& observers) + : observers_(observers) {} + + virtual ~TaskRunnerBoundObserverList<Observer, ObserverStoreType>() {} + + // Returns a new observer list with given observer. + // It is valid to give NULL as |runner_to_notify|, and in that case + // notifications are dispatched on the current runner. + // Note that this is a const method and does NOT change 'this' observer + // list but returns a new list. + TaskRunnerBoundObserverList<Observer, ObserverStoreType> AddObserver( + Observer* observer, + base::SequencedTaskRunner* runner_to_notify) const { + ObserversListMap observers = observers_; + observers.insert(std::make_pair(observer, runner_to_notify)); + return TaskRunnerBoundObserverList<Observer, ObserverStoreType>(observers); + } + + // Notify on the task runner that is given to AddObserver. + // If we're already on the runner this just dispatches the method. + template <class Method, class Params> + void Notify(Method method, const Params& params) const { + COMPILE_ASSERT( + (base::internal::ParamsUseScopedRefptrCorrectly<Params>::value), + badunboundmethodparams); + for (typename ObserversListMap::const_iterator it = observers_.begin(); + it != observers_.end(); ++it) { + if (!it->second.get() || it->second->RunsTasksOnCurrentThread()) { + DispatchToMethod(UnwrapTraits::Unwrap(it->first), method, params); + continue; + } + it->second->PostTask( + FROM_HERE, + base::Bind(&NotifyWrapper<ObserverStoreType, Method, Params>, + it->first, method, params)); + } + } + + private: + typedef base::internal::UnwrapTraits<ObserverStoreType> UnwrapTraits; + + ObserversListMap observers_; +}; + +class FileAccessObserver; +class FileChangeObserver; +class FileUpdateObserver; + +typedef TaskRunnerBoundObserverList<FileAccessObserver> AccessObserverList; +typedef TaskRunnerBoundObserverList<FileChangeObserver> ChangeObserverList; +typedef TaskRunnerBoundObserverList<FileUpdateObserver> UpdateObserverList; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_TASK_RUNNER_BOUND_OBSERVER_LIST_H_ diff --git a/chromium/webkit/browser/fileapi/test_file_set.cc b/chromium/webkit/browser/fileapi/test_file_set.cc new file mode 100644 index 00000000000..7b0b0f0a7b7 --- /dev/null +++ b/chromium/webkit/browser/fileapi/test_file_set.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/test_file_set.h" + +#include <string> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/platform_file.h" +#include "base/rand_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace fileapi { + +namespace test { + +const TestCaseRecord kRegularTestCases[] = { + {true, FILE_PATH_LITERAL("dir a"), 0}, + {true, FILE_PATH_LITERAL("dir a/dir A"), 0}, + {true, FILE_PATH_LITERAL("dir a/dir d"), 0}, + {true, FILE_PATH_LITERAL("dir a/dir d/dir e"), 0}, + {true, FILE_PATH_LITERAL("dir a/dir d/dir e/dir f"), 0}, + {true, FILE_PATH_LITERAL("dir a/dir d/dir e/dir g"), 0}, + {true, FILE_PATH_LITERAL("dir a/dir d/dir e/dir h"), 0}, + {true, FILE_PATH_LITERAL("dir b"), 0}, + {true, FILE_PATH_LITERAL("dir b/dir a"), 0}, + {true, FILE_PATH_LITERAL("dir c"), 0}, + {false, FILE_PATH_LITERAL("file 0"), 38}, + {false, FILE_PATH_LITERAL("file 2"), 60}, + {false, FILE_PATH_LITERAL("file 3"), 0}, + {false, FILE_PATH_LITERAL("dir a/file 0"), 39}, + {false, FILE_PATH_LITERAL("dir a/dir d/dir e/dir g/file 0"), 40}, + {false, FILE_PATH_LITERAL("dir a/dir d/dir e/dir g/file 1"), 41}, + {false, FILE_PATH_LITERAL("dir a/dir d/dir e/dir g/file 2"), 42}, + {false, FILE_PATH_LITERAL("dir a/dir d/dir e/dir g/file 3"), 50}, +}; + +const size_t kRegularTestCaseSize = arraysize(kRegularTestCases); + +void SetUpOneTestCase(const base::FilePath& root_path, + const TestCaseRecord& test_case) { + base::FilePath path = root_path.Append(test_case.path); + if (test_case.is_directory) { + ASSERT_TRUE(file_util::CreateDirectory(path)); + return; + } + base::PlatformFileError error_code; + bool created = false; + int file_flags = base::PLATFORM_FILE_CREATE_ALWAYS | + base::PLATFORM_FILE_WRITE; + base::PlatformFile file_handle = + base::CreatePlatformFile(path, file_flags, &created, &error_code); + EXPECT_TRUE(created); + ASSERT_EQ(base::PLATFORM_FILE_OK, error_code); + ASSERT_NE(base::kInvalidPlatformFileValue, file_handle); + EXPECT_TRUE(base::ClosePlatformFile(file_handle)); + if (test_case.data_file_size > 0U) { + std::string content = base::RandBytesAsString(test_case.data_file_size); + ASSERT_EQ(static_cast<int>(content.size()), + file_util::WriteFile(path, content.data(), content.size())); + } +} + + +void SetUpRegularTestCases(const base::FilePath& root_path) { + for (size_t i = 0; i < arraysize(kRegularTestCases); ++i) { + SCOPED_TRACE(testing::Message() << "Creating kRegularTestCases " << i); + SetUpOneTestCase(root_path, kRegularTestCases[i]); + } +} + +} // namespace test + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/test_file_set.h b/chromium/webkit/browser/fileapi/test_file_set.h new file mode 100644 index 00000000000..59e90ab6e0a --- /dev/null +++ b/chromium/webkit/browser/fileapi/test_file_set.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_TEST_FILE_SET_H_ +#define WEBKIT_BROWSER_FILEAPI_TEST_FILE_SET_H_ + +#include <set> + +#include "base/files/file_path.h" + +// Common test data structures and test cases. + +namespace fileapi { + +class FileSystemFileUtil; + +namespace test { + +struct TestCaseRecord { + bool is_directory; + const base::FilePath::CharType path[64]; + int64 data_file_size; +}; + +extern const TestCaseRecord kRegularTestCases[]; +extern const size_t kRegularTestCaseSize; + +size_t GetRegularTestCaseSize(); + +// Creates one file or directory specified by |record|. +void SetUpOneTestCase(const base::FilePath& root_path, const TestCaseRecord& record); + +// Creates the files and directories specified in kRegularTestCases. +void SetUpRegularTestCases(const base::FilePath& root_path); + +} // namespace test + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_TEST_FILE_SET_H_ diff --git a/chromium/webkit/browser/fileapi/test_file_system_backend.cc b/chromium/webkit/browser/fileapi/test_file_system_backend.cc new file mode 100644 index 00000000000..1cff5c2fb14 --- /dev/null +++ b/chromium/webkit/browser/fileapi/test_file_system_backend.cc @@ -0,0 +1,230 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/test_file_system_backend.h" + +#include <set> +#include <string> +#include <vector> + +#include "base/file_util.h" +#include "base/sequenced_task_runner.h" +#include "webkit/browser/fileapi/copy_or_move_file_validator.h" +#include "webkit/browser/fileapi/file_observers.h" +#include "webkit/browser/fileapi/file_system_file_stream_reader.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_operation_impl.h" +#include "webkit/browser/fileapi/file_system_quota_util.h" +#include "webkit/browser/fileapi/local_file_util.h" +#include "webkit/browser/fileapi/native_file_util.h" +#include "webkit/browser/fileapi/sandbox_file_stream_writer.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/fileapi/file_system_util.h" + +namespace fileapi { + +// This only supports single origin. +class TestFileSystemBackend::QuotaUtil + : public FileSystemQuotaUtil, + public FileUpdateObserver { + public: + QuotaUtil(base::SequencedTaskRunner* task_runner) + : usage_(0), + task_runner_(task_runner) { + update_observers_ = update_observers_.AddObserver(this, task_runner_.get()); + } + virtual ~QuotaUtil() {} + + // FileSystemQuotaUtil overrides. + virtual base::PlatformFileError DeleteOriginDataOnFileThread( + FileSystemContext* context, + quota::QuotaManagerProxy* proxy, + const GURL& origin_url, + FileSystemType type) OVERRIDE { + NOTREACHED(); + return base::PLATFORM_FILE_OK; + } + + virtual void GetOriginsForTypeOnFileThread( + FileSystemType type, + std::set<GURL>* origins) OVERRIDE { + NOTREACHED(); + } + + virtual void GetOriginsForHostOnFileThread( + FileSystemType type, + const std::string& host, + std::set<GURL>* origins) OVERRIDE { + NOTREACHED(); + } + + virtual int64 GetOriginUsageOnFileThread( + FileSystemContext* context, + const GURL& origin_url, + FileSystemType type) OVERRIDE { + return usage_; + } + + virtual void AddFileUpdateObserver( + FileSystemType type, + FileUpdateObserver* observer, + base::SequencedTaskRunner* task_runner) OVERRIDE { + NOTIMPLEMENTED(); + } + + virtual void AddFileChangeObserver( + FileSystemType type, + FileChangeObserver* observer, + base::SequencedTaskRunner* task_runner) OVERRIDE { + change_observers_ = change_observers_.AddObserver(observer, task_runner); + } + + virtual void AddFileAccessObserver( + FileSystemType type, + FileAccessObserver* observer, + base::SequencedTaskRunner* task_runner) OVERRIDE { + NOTIMPLEMENTED(); + } + + virtual const UpdateObserverList* GetUpdateObservers( + FileSystemType type) const OVERRIDE { + return &update_observers_; + } + + virtual const ChangeObserverList* GetChangeObservers( + FileSystemType type) const OVERRIDE { + return &change_observers_; + } + + virtual const AccessObserverList* GetAccessObservers( + FileSystemType type) const OVERRIDE { + NOTIMPLEMENTED(); + return NULL; + } + + // FileUpdateObserver overrides. + virtual void OnStartUpdate(const FileSystemURL& url) OVERRIDE {} + virtual void OnUpdate(const FileSystemURL& url, int64 delta) OVERRIDE { + usage_ += delta; + } + virtual void OnEndUpdate(const FileSystemURL& url) OVERRIDE {} + + base::SequencedTaskRunner* task_runner() { return task_runner_.get(); } + + private: + int64 usage_; + + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + UpdateObserverList update_observers_; + ChangeObserverList change_observers_; +}; + +TestFileSystemBackend::TestFileSystemBackend( + base::SequencedTaskRunner* task_runner, + const base::FilePath& base_path) + : base_path_(base_path), + local_file_util_(new AsyncFileUtilAdapter(new LocalFileUtil())), + quota_util_(new QuotaUtil(task_runner)), + require_copy_or_move_validator_(false) { +} + +TestFileSystemBackend::~TestFileSystemBackend() { +} + +bool TestFileSystemBackend::CanHandleType(FileSystemType type) const { + return (type == kFileSystemTypeTest); +} + +void TestFileSystemBackend::Initialize(FileSystemContext* context) { +} + +void TestFileSystemBackend::OpenFileSystem( + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) { + callback.Run(GetFileSystemRootURI(origin_url, type), + GetFileSystemName(origin_url, type), + base::PLATFORM_FILE_OK); +} + +FileSystemFileUtil* TestFileSystemBackend::GetFileUtil(FileSystemType type) { + DCHECK(local_file_util_.get()); + return local_file_util_->sync_file_util(); +} + +AsyncFileUtil* TestFileSystemBackend::GetAsyncFileUtil(FileSystemType type) { + return local_file_util_.get(); +} + +CopyOrMoveFileValidatorFactory* +TestFileSystemBackend::GetCopyOrMoveFileValidatorFactory( + FileSystemType type, base::PlatformFileError* error_code) { + DCHECK(error_code); + *error_code = base::PLATFORM_FILE_OK; + if (require_copy_or_move_validator_) { + if (!copy_or_move_file_validator_factory_) + *error_code = base::PLATFORM_FILE_ERROR_SECURITY; + return copy_or_move_file_validator_factory_.get(); + } + return NULL; +} + +void TestFileSystemBackend::InitializeCopyOrMoveFileValidatorFactory( + scoped_ptr<CopyOrMoveFileValidatorFactory> factory) { + if (!copy_or_move_file_validator_factory_) + copy_or_move_file_validator_factory_ = factory.Pass(); +} + +FileSystemOperation* TestFileSystemBackend::CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::PlatformFileError* error_code) const { + scoped_ptr<FileSystemOperationContext> operation_context( + new FileSystemOperationContext(context)); + operation_context->set_update_observers(*GetUpdateObservers(url.type())); + operation_context->set_change_observers( + *quota_util_->GetChangeObservers(url.type())); + operation_context->set_root_path(base_path_); + return new FileSystemOperationImpl(url, context, operation_context.Pass()); +} + +scoped_ptr<webkit_blob::FileStreamReader> +TestFileSystemBackend::CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const { + return scoped_ptr<webkit_blob::FileStreamReader>( + new FileSystemFileStreamReader( + context, url, offset, expected_modification_time)); +} + +scoped_ptr<fileapi::FileStreamWriter> +TestFileSystemBackend::CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const { + return scoped_ptr<fileapi::FileStreamWriter>( + new SandboxFileStreamWriter(context, url, offset, + *GetUpdateObservers(url.type()))); +} + +FileSystemQuotaUtil* TestFileSystemBackend::GetQuotaUtil() { + return quota_util_.get(); +} + +const UpdateObserverList* TestFileSystemBackend::GetUpdateObservers( + FileSystemType type) const { + return quota_util_->GetUpdateObservers(type); +} + +void TestFileSystemBackend::AddFileChangeObserver( + FileChangeObserver* observer) { + quota_util_->AddFileChangeObserver( + kFileSystemTypeTest, observer, quota_util_->task_runner()); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/test_file_system_backend.h b/chromium/webkit/browser/fileapi/test_file_system_backend.h new file mode 100644 index 00000000000..ef252c13118 --- /dev/null +++ b/chromium/webkit/browser/fileapi/test_file_system_backend.h @@ -0,0 +1,96 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_TEST_FILE_SYSTEM_BACKEND_H_ +#define WEBKIT_BROWSER_FILEAPI_TEST_FILE_SYSTEM_BACKEND_H_ + +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "webkit/browser/fileapi/async_file_util_adapter.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/task_runner_bound_observer_list.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace fileapi { + +class AsyncFileUtilAdapter; +class FileSystemQuotaUtil; + +// This should be only used for testing. +// This file system backend uses LocalFileUtil and stores data file +// under the given directory. +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE TestFileSystemBackend + : public FileSystemBackend { + public: + TestFileSystemBackend( + base::SequencedTaskRunner* task_runner, + const base::FilePath& base_path); + virtual ~TestFileSystemBackend(); + + // FileSystemBackend implementation. + virtual bool CanHandleType(FileSystemType type) const OVERRIDE; + virtual void Initialize(FileSystemContext* context) OVERRIDE; + virtual void OpenFileSystem( + const GURL& origin_url, + FileSystemType type, + OpenFileSystemMode mode, + const OpenFileSystemCallback& callback) OVERRIDE; + virtual FileSystemFileUtil* GetFileUtil(FileSystemType type) OVERRIDE; + virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) OVERRIDE; + virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory( + FileSystemType type, + base::PlatformFileError* error_code) OVERRIDE; + virtual FileSystemOperation* CreateFileSystemOperation( + const FileSystemURL& url, + FileSystemContext* context, + base::PlatformFileError* error_code) const OVERRIDE; + virtual scoped_ptr<webkit_blob::FileStreamReader> CreateFileStreamReader( + const FileSystemURL& url, + int64 offset, + const base::Time& expected_modification_time, + FileSystemContext* context) const OVERRIDE; + virtual scoped_ptr<FileStreamWriter> CreateFileStreamWriter( + const FileSystemURL& url, + int64 offset, + FileSystemContext* context) const OVERRIDE; + virtual FileSystemQuotaUtil* GetQuotaUtil() OVERRIDE; + + // Initialize the CopyOrMoveFileValidatorFactory. Invalid to call more than + // once. + void InitializeCopyOrMoveFileValidatorFactory( + scoped_ptr<CopyOrMoveFileValidatorFactory> factory); + + const UpdateObserverList* GetUpdateObservers(FileSystemType type) const; + void AddFileChangeObserver(FileChangeObserver* observer); + + // For CopyOrMoveFileValidatorFactory testing. Once it's set to true + // GetCopyOrMoveFileValidatorFactory will start returning security + // error if validator is not initialized. + void set_require_copy_or_move_validator(bool flag) { + require_copy_or_move_validator_ = flag; + } + + private: + class QuotaUtil; + + base::FilePath base_path_; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + scoped_ptr<AsyncFileUtilAdapter> local_file_util_; + scoped_ptr<QuotaUtil> quota_util_; + + bool require_copy_or_move_validator_; + scoped_ptr<CopyOrMoveFileValidatorFactory> + copy_or_move_file_validator_factory_; + + DISALLOW_COPY_AND_ASSIGN(TestFileSystemBackend); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_TEST_FILE_SYSTEM_BACKEND_H_ diff --git a/chromium/webkit/browser/fileapi/timed_task_helper.cc b/chromium/webkit/browser/fileapi/timed_task_helper.cc new file mode 100644 index 00000000000..50d1dd00d9b --- /dev/null +++ b/chromium/webkit/browser/fileapi/timed_task_helper.cc @@ -0,0 +1,92 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/timed_task_helper.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/sequenced_task_runner.h" + +namespace fileapi { + +struct TimedTaskHelper::Tracker { + explicit Tracker(TimedTaskHelper* timer) : timer(timer) {} + + ~Tracker() { + if (timer) + timer->tracker_ = NULL; + } + + TimedTaskHelper* timer; +}; + +TimedTaskHelper::TimedTaskHelper(base::SequencedTaskRunner* task_runner) + : task_runner_(task_runner), + tracker_(NULL) { +} + +TimedTaskHelper::~TimedTaskHelper() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + if (tracker_) + tracker_->timer = NULL; +} + +bool TimedTaskHelper::IsRunning() const { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + return tracker_ != NULL; +} + +void TimedTaskHelper::Start( + const tracked_objects::Location& posted_from, + base::TimeDelta delay, + const base::Closure& user_task) { + posted_from_ = posted_from; + delay_ = delay; + user_task_ = user_task; + Reset(); +} + +void TimedTaskHelper::Reset() { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + DCHECK(!user_task_.is_null()); + desired_run_time_ = base::TimeTicks::Now() + delay_; + + if (tracker_) + return; + + // Initialize the tracker for the first time. + tracker_ = new Tracker(this); + PostDelayedTask(make_scoped_ptr(tracker_), delay_); +} + +// static +void TimedTaskHelper::Fired(scoped_ptr<Tracker> tracker) { + if (!tracker->timer) + return; + TimedTaskHelper* timer = tracker->timer; + timer->OnFired(tracker.Pass()); +} + +void TimedTaskHelper::OnFired(scoped_ptr<Tracker> tracker) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + base::TimeTicks now = base::TimeTicks::Now(); + if (desired_run_time_ > now) { + PostDelayedTask(tracker.Pass(), desired_run_time_ - now); + return; + } + tracker.reset(); + base::Closure task = user_task_; + user_task_.Reset(); + task.Run(); +} + +void TimedTaskHelper::PostDelayedTask(scoped_ptr<Tracker> tracker, + base::TimeDelta delay) { + task_runner_->PostDelayedTask( + posted_from_, + base::Bind(&TimedTaskHelper::Fired, base::Passed(&tracker)), + delay); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/timed_task_helper.h b/chromium/webkit/browser/fileapi/timed_task_helper.h new file mode 100644 index 00000000000..fcfb80ab78b --- /dev/null +++ b/chromium/webkit/browser/fileapi/timed_task_helper.h @@ -0,0 +1,59 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_TIMED_TASK_HELPER_H_ +#define WEBKIT_BROWSER_FILEAPI_TIMED_TASK_HELPER_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/location.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace fileapi { + +// Works similarly as base::Timer, but takes SequencedTaskRunner and +// runs tasks on it (instead of implicitly bound to a thread). +// TODO(kinuko): This has nothing to do with fileapi. Move somewhere +// more common place. +class WEBKIT_STORAGE_BROWSER_EXPORT TimedTaskHelper { + public: + explicit TimedTaskHelper(base::SequencedTaskRunner* task_runner); + ~TimedTaskHelper(); + + bool IsRunning() const; + void Start(const tracked_objects::Location& posted_from, + base::TimeDelta delay, + const base::Closure& user_task); + void Reset(); + + private: + struct Tracker; + static void Fired(scoped_ptr<Tracker> tracker); + + void OnFired(scoped_ptr<Tracker> tracker); + void PostDelayedTask(scoped_ptr<Tracker> tracker, base::TimeDelta delay); + + scoped_refptr<base::SequencedTaskRunner> task_runner_; + tracked_objects::Location posted_from_; + base::TimeDelta delay_; + base::Closure user_task_; + + base::TimeTicks desired_run_time_; + + // This is set to non-null and owned by a timer task while timer is running. + Tracker* tracker_; + + DISALLOW_COPY_AND_ASSIGN(TimedTaskHelper); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_TIMED_TASK_HELPER_H_ diff --git a/chromium/webkit/browser/fileapi/timed_task_helper_unittest.cc b/chromium/webkit/browser/fileapi/timed_task_helper_unittest.cc new file mode 100644 index 00000000000..6966c614c3d --- /dev/null +++ b/chromium/webkit/browser/fileapi/timed_task_helper_unittest.cc @@ -0,0 +1,83 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/location.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/timed_task_helper.h" + +namespace fileapi { + +namespace { + +class Embedder { + public: + Embedder() + : timer_(base::MessageLoopProxy::current().get()), timer_fired_(false) {} + + void OnTimerFired() { + timer_fired_ = true; + } + + TimedTaskHelper* timer() { return &timer_; } + bool timer_fired() const { return timer_fired_; } + + private: + TimedTaskHelper timer_; + bool timer_fired_; +}; + +} // namespace + +TEST(TimedTaskHelper, FireTimerWhenAlive) { + base::MessageLoop message_loop; + Embedder embedder; + + ASSERT_FALSE(embedder.timer_fired()); + ASSERT_FALSE(embedder.timer()->IsRunning()); + + embedder.timer()->Start( + FROM_HERE, + base::TimeDelta::FromSeconds(0), + base::Bind(&Embedder::OnTimerFired, base::Unretained(&embedder))); + + ASSERT_TRUE(embedder.timer()->IsRunning()); + embedder.timer()->Reset(); + ASSERT_TRUE(embedder.timer()->IsRunning()); + + base::MessageLoop::current()->RunUntilIdle(); + + ASSERT_TRUE(embedder.timer_fired()); +} + +TEST(TimedTaskHelper, FireTimerWhenAlreadyDeleted) { + base::MessageLoop message_loop; + + // Run message loop after embedder is already deleted to make sure callback + // doesn't cause a crash for use after free. + { + Embedder embedder; + + ASSERT_FALSE(embedder.timer_fired()); + ASSERT_FALSE(embedder.timer()->IsRunning()); + + embedder.timer()->Start( + FROM_HERE, + base::TimeDelta::FromSeconds(0), + base::Bind(&Embedder::OnTimerFired, base::Unretained(&embedder))); + + ASSERT_TRUE(embedder.timer()->IsRunning()); + } + + // At this point the callback is still in the message queue but + // embedder is gone. + base::MessageLoop::current()->RunUntilIdle(); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/transient_file_util.cc b/chromium/webkit/browser/fileapi/transient_file_util.cc new file mode 100644 index 00000000000..74c2dbea8c3 --- /dev/null +++ b/chromium/webkit/browser/fileapi/transient_file_util.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/transient_file_util.h" + +#include <string> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/isolated_context.h" + +using webkit_blob::ScopedFile; + +namespace fileapi { + +namespace { + +void RevokeFileSystem(const std::string& filesystem_id, + const base::FilePath& /*path*/) { + IsolatedContext::GetInstance()->RevokeFileSystem(filesystem_id); +} + +} // namespace + +ScopedFile TransientFileUtil::CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::PlatformFileError* error, + base::PlatformFileInfo* file_info, + base::FilePath* platform_path) { + DCHECK(file_info); + *error = GetFileInfo(context, url, file_info, platform_path); + if (*error == base::PLATFORM_FILE_OK && file_info->is_directory) + *error = base::PLATFORM_FILE_ERROR_NOT_A_FILE; + if (*error != base::PLATFORM_FILE_OK) + return ScopedFile(); + + // Sets-up a transient filesystem. + DCHECK(!platform_path->empty()); + DCHECK(!url.filesystem_id().empty()); + + ScopedFile scoped_file( + *platform_path, + ScopedFile::DELETE_ON_SCOPE_OUT, + context->task_runner()); + scoped_file.AddScopeOutCallback( + base::Bind(&RevokeFileSystem, url.filesystem_id()), NULL); + + return scoped_file.Pass(); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/transient_file_util.h b/chromium/webkit/browser/fileapi/transient_file_util.h new file mode 100644 index 00000000000..d3d56c1c173 --- /dev/null +++ b/chromium/webkit/browser/fileapi/transient_file_util.h @@ -0,0 +1,36 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_TRANSIENT_FILE_UTIL_H_ +#define WEBKIT_BROWSER_FILEAPI_TRANSIENT_FILE_UTIL_H_ + +#include "base/memory/scoped_ptr.h" +#include "webkit/browser/fileapi/isolated_file_util.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace fileapi { + +class FileSystemOperationContext; + +class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE TransientFileUtil + : public IsolatedFileUtil { + public: + TransientFileUtil() {} + virtual ~TransientFileUtil() {} + + // IsolatedFileUtil overrides. + virtual webkit_blob::ScopedFile CreateSnapshotFile( + FileSystemOperationContext* context, + const FileSystemURL& url, + base::PlatformFileError* error, + base::PlatformFileInfo* file_info, + base::FilePath* platform_path) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(TransientFileUtil); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_TRANSIENT_FILE_UTIL_H_ diff --git a/chromium/webkit/browser/fileapi/transient_file_util_unittest.cc b/chromium/webkit/browser/fileapi/transient_file_util_unittest.cc new file mode 100644 index 00000000000..2a809a30779 --- /dev/null +++ b/chromium/webkit/browser/fileapi/transient_file_util_unittest.cc @@ -0,0 +1,122 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/platform_file.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/isolated_context.h" +#include "webkit/browser/fileapi/mock_file_system_context.h" +#include "webkit/browser/fileapi/transient_file_util.h" +#include "webkit/common/blob/scoped_file.h" + +namespace fileapi { + +class TransientFileUtilTest : public testing::Test { + public: + TransientFileUtilTest() {} + virtual ~TransientFileUtilTest() {} + + virtual void SetUp() OVERRIDE { + file_system_context_ = CreateFileSystemContextForTesting( + NULL, base::FilePath(FILE_PATH_LITERAL("dummy"))); + transient_file_util_.reset(new TransientFileUtil); + + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + } + + virtual void TearDown() OVERRIDE { + file_system_context_ = NULL; + base::MessageLoop::current()->RunUntilIdle(); + } + + void CreateAndRegisterTemporaryFile( + FileSystemURL* file_url, + base::FilePath* file_path) { + EXPECT_TRUE( + file_util::CreateTemporaryFileInDir(data_dir_.path(), file_path)); + IsolatedContext* isolated_context = IsolatedContext::GetInstance(); + std::string name = "tmp"; + std::string fsid = isolated_context->RegisterFileSystemForPath( + kFileSystemTypeForTransientFile, + *file_path, + &name); + ASSERT_TRUE(!fsid.empty()); + base::FilePath virtual_path = isolated_context->CreateVirtualRootPath( + fsid).AppendASCII(name); + *file_url = file_system_context_->CreateCrackedFileSystemURL( + GURL("http://foo"), + kFileSystemTypeIsolated, + virtual_path); + } + + scoped_ptr<FileSystemOperationContext> NewOperationContext() { + return make_scoped_ptr( + new FileSystemOperationContext(file_system_context_.get())); + } + + FileSystemFileUtil* file_util() { return transient_file_util_.get(); } + + private: + base::MessageLoop message_loop_; + base::ScopedTempDir data_dir_; + scoped_refptr<FileSystemContext> file_system_context_; + scoped_ptr<TransientFileUtil> transient_file_util_; + + DISALLOW_COPY_AND_ASSIGN(TransientFileUtilTest); +}; + +TEST_F(TransientFileUtilTest, TransientFile) { + FileSystemURL temp_url; + base::FilePath temp_path; + + CreateAndRegisterTemporaryFile(&temp_url, &temp_path); + + base::PlatformFileError error; + base::PlatformFileInfo file_info; + base::FilePath path; + + // Make sure the file is there. + ASSERT_TRUE(temp_url.is_valid()); + ASSERT_TRUE(base::PathExists(temp_path)); + ASSERT_FALSE(base::DirectoryExists(temp_path)); + + // Create a snapshot file. + { + webkit_blob::ScopedFile scoped_file = + file_util()->CreateSnapshotFile(NewOperationContext().get(), + temp_url, + &error, + &file_info, + &path); + ASSERT_EQ(base::PLATFORM_FILE_OK, error); + ASSERT_EQ(temp_path, path); + ASSERT_FALSE(file_info.is_directory); + + // The file should be still there. + ASSERT_TRUE(base::PathExists(temp_path)); + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util()->GetFileInfo(NewOperationContext().get(), + temp_url, &file_info, &path)); + ASSERT_EQ(temp_path, path); + ASSERT_FALSE(file_info.is_directory); + } + + // The file's now scoped out. + base::MessageLoop::current()->RunUntilIdle(); + + // Now the temporary file and the transient filesystem must be gone too. + ASSERT_FALSE(base::PathExists(temp_path)); + ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, + file_util()->GetFileInfo(NewOperationContext().get(), + temp_url, &file_info, &path)); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader.cc b/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader.cc new file mode 100644 index 00000000000..3fb5ab4d31e --- /dev/null +++ b/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/upload_file_system_file_element_reader.h" + +#include "base/bind.h" +#include "net/base/net_errors.h" +#include "webkit/browser/blob/file_stream_reader.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_url.h" + +namespace fileapi { + +UploadFileSystemFileElementReader::UploadFileSystemFileElementReader( + FileSystemContext* file_system_context, + const GURL& url, + uint64 range_offset, + uint64 range_length, + const base::Time& expected_modification_time) + : file_system_context_(file_system_context), + url_(url), + range_offset_(range_offset), + range_length_(range_length), + expected_modification_time_(expected_modification_time), + stream_length_(0), + position_(0), + weak_ptr_factory_(this) { +} + +UploadFileSystemFileElementReader::~UploadFileSystemFileElementReader() { +} + +int UploadFileSystemFileElementReader::Init( + const net::CompletionCallback& callback) { + // Reset states. + weak_ptr_factory_.InvalidateWeakPtrs(); + stream_length_ = 0; + position_ = 0; + + // Initialize the stream reader and the length. + stream_reader_ = + file_system_context_->CreateFileStreamReader( + file_system_context_->CrackURL(url_), + range_offset_, + expected_modification_time_); + DCHECK(stream_reader_); + + const int64 result = stream_reader_->GetLength( + base::Bind(&UploadFileSystemFileElementReader::OnGetLength, + weak_ptr_factory_.GetWeakPtr(), + callback)); + if (result >= 0) { + stream_length_ = result; + return net::OK; + } + + // The error code can be casted to int. + return static_cast<int>(result); +} + +uint64 UploadFileSystemFileElementReader::GetContentLength() const { + return std::min(stream_length_, range_length_); +} + +uint64 UploadFileSystemFileElementReader::BytesRemaining() const { + return GetContentLength() - position_; +} + +int UploadFileSystemFileElementReader::Read( + net::IOBuffer* buf, + int buf_length, + const net::CompletionCallback& callback) { + DCHECK_LT(0, buf_length); + DCHECK(stream_reader_); + + const uint64 num_bytes_to_read = + std::min(BytesRemaining(), static_cast<uint64>(buf_length)); + + if (num_bytes_to_read == 0) + return 0; + + const int result = stream_reader_->Read( + buf, num_bytes_to_read, + base::Bind(&UploadFileSystemFileElementReader::OnRead, + weak_ptr_factory_.GetWeakPtr(), + callback)); + if (result >= 0) + OnRead(net::CompletionCallback(), result); + return result; +} + +void UploadFileSystemFileElementReader::OnGetLength( + const net::CompletionCallback& callback, + int64 result) { + if (result >= 0) { + stream_length_ = result; + callback.Run(net::OK); + return; + } + callback.Run(result); +} + +void UploadFileSystemFileElementReader::OnRead( + const net::CompletionCallback& callback, + int result) { + if (result > 0) { + position_ += result; + DCHECK_LE(position_, GetContentLength()); + } + if (!callback.is_null()) + callback.Run(result); +} + +} // namespace fileapi diff --git a/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader.h b/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader.h new file mode 100644 index 00000000000..dba30868b93 --- /dev/null +++ b/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader.h @@ -0,0 +1,64 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_BROWSER_FILEAPI_UPLOAD_FILE_SYSTEM_FILE_ELEMENT_READER_H_ +#define WEBKIT_BROWSER_FILEAPI_UPLOAD_FILE_SYSTEM_FILE_ELEMENT_READER_H_ + +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "net/base/upload_element_reader.h" +#include "url/gurl.h" +#include "webkit/browser/webkit_storage_browser_export.h" + +namespace webkit_blob { +class FileStreamReader; +} + +namespace fileapi { + +class FileSystemContext; + +// An UploadElementReader implementation for filesystem file. +class WEBKIT_STORAGE_BROWSER_EXPORT UploadFileSystemFileElementReader + : public net::UploadElementReader { + public: + UploadFileSystemFileElementReader( + FileSystemContext* file_system_context, + const GURL& url, + uint64 range_offset, + uint64 range_length, + const base::Time& expected_modification_time); + virtual ~UploadFileSystemFileElementReader(); + + // UploadElementReader overrides: + virtual int Init(const net::CompletionCallback& callback) OVERRIDE; + virtual uint64 GetContentLength() const OVERRIDE; + virtual uint64 BytesRemaining() const OVERRIDE; + virtual int Read(net::IOBuffer* buf, + int buf_length, + const net::CompletionCallback& callback) OVERRIDE; + + private: + void OnGetLength(const net::CompletionCallback& callback, int64 result); + void OnRead(const net::CompletionCallback& callback, int result); + + scoped_refptr<FileSystemContext> file_system_context_; + const GURL url_; + const uint64 range_offset_; + const uint64 range_length_; + const base::Time expected_modification_time_; + + scoped_ptr<webkit_blob::FileStreamReader> stream_reader_; + + uint64 stream_length_; + uint64 position_; + + base::WeakPtrFactory<UploadFileSystemFileElementReader> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(UploadFileSystemFileElementReader); +}; + +} // namespace fileapi + +#endif // WEBKIT_BROWSER_FILEAPI_UPLOAD_FILE_SYSTEM_FILE_ELEMENT_READER_H_ diff --git a/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader_unittest.cc b/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader_unittest.cc new file mode 100644 index 00000000000..e6e0d4cd2b4 --- /dev/null +++ b/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader_unittest.cc @@ -0,0 +1,295 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/browser/fileapi/upload_file_system_file_element_reader.h" + +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "net/base/io_buffer.h" +#include "net/base/test_completion_callback.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/fileapi/file_system_backend.h" +#include "webkit/browser/fileapi/file_system_context.h" +#include "webkit/browser/fileapi/file_system_file_util.h" +#include "webkit/browser/fileapi/file_system_operation_context.h" +#include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/fileapi/mock_file_system_context.h" + +namespace fileapi { + +namespace { + +const char kFileSystemURLOrigin[] = "http://remote"; +const fileapi::FileSystemType kFileSystemType = + fileapi::kFileSystemTypeTemporary; + +} // namespace + +class UploadFileSystemFileElementReaderTest : public testing::Test { + public: + UploadFileSystemFileElementReaderTest() + : message_loop_(base::MessageLoop::TYPE_IO) {} + + virtual void SetUp() OVERRIDE { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + file_system_context_ = fileapi::CreateFileSystemContextForTesting( + NULL, temp_dir_.path()); + + file_system_context_->OpenFileSystem( + GURL(kFileSystemURLOrigin), + kFileSystemType, + OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT, + base::Bind(&UploadFileSystemFileElementReaderTest::OnOpenFileSystem, + base::Unretained(this))); + base::MessageLoop::current()->RunUntilIdle(); + ASSERT_TRUE(file_system_root_url_.is_valid()); + + // Prepare a file on file system. + const char kTestData[] = "abcdefghijklmnop0123456789"; + file_data_.assign(kTestData, kTestData + arraysize(kTestData) - 1); + const char kFilename[] = "File.dat"; + file_url_ = GetFileSystemURL(kFilename); + WriteFileSystemFile(kFilename, &file_data_[0], file_data_.size(), + &file_modification_time_); + + // Create and initialize a reader. + reader_.reset( + new UploadFileSystemFileElementReader(file_system_context_.get(), + file_url_, + 0, + kuint64max, + file_modification_time_)); + net::TestCompletionCallback callback; + ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(callback.callback())); + EXPECT_EQ(net::OK, callback.WaitForResult()); + EXPECT_EQ(file_data_.size(), reader_->GetContentLength()); + EXPECT_EQ(file_data_.size(), reader_->BytesRemaining()); + EXPECT_FALSE(reader_->IsInMemory()); + } + + virtual void TearDown() OVERRIDE { + reader_.reset(); + base::MessageLoop::current()->RunUntilIdle(); + } + + protected: + GURL GetFileSystemURL(const std::string& filename) { + return GURL(file_system_root_url_.spec() + filename); + } + + void WriteFileSystemFile(const std::string& filename, + const char* buf, + int buf_size, + base::Time* modification_time) { + fileapi::FileSystemURL url = + file_system_context_->CreateCrackedFileSystemURL( + GURL(kFileSystemURLOrigin), + kFileSystemType, + base::FilePath().AppendASCII(filename)); + + fileapi::FileSystemFileUtil* file_util = + file_system_context_->GetFileUtil(kFileSystemType); + + fileapi::FileSystemOperationContext context(file_system_context_.get()); + context.set_allowed_bytes_growth(1024); + + base::PlatformFile handle = base::kInvalidPlatformFileValue; + bool created = false; + ASSERT_EQ(base::PLATFORM_FILE_OK, file_util->CreateOrOpen( + &context, + url, + base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, + &handle, + &created)); + EXPECT_TRUE(created); + ASSERT_NE(base::kInvalidPlatformFileValue, handle); + ASSERT_EQ(buf_size, + base::WritePlatformFile(handle, 0 /* offset */, buf, buf_size)); + base::ClosePlatformFile(handle); + + base::PlatformFileInfo file_info; + base::FilePath platform_path; + ASSERT_EQ(base::PLATFORM_FILE_OK, + file_util->GetFileInfo(&context, url, &file_info, + &platform_path)); + *modification_time = file_info.last_modified; + } + + void OnOpenFileSystem(base::PlatformFileError result, + const std::string& name, + const GURL& root) { + ASSERT_EQ(base::PLATFORM_FILE_OK, result); + ASSERT_TRUE(root.is_valid()); + file_system_root_url_ = root; + } + + base::MessageLoop message_loop_; + base::ScopedTempDir temp_dir_; + scoped_refptr<FileSystemContext> file_system_context_; + GURL file_system_root_url_; + std::vector<char> file_data_; + GURL file_url_; + base::Time file_modification_time_; + scoped_ptr<UploadFileSystemFileElementReader> reader_; +}; + +TEST_F(UploadFileSystemFileElementReaderTest, ReadAll) { + scoped_refptr<net::IOBufferWithSize> buf = + new net::IOBufferWithSize(file_data_.size()); + net::TestCompletionCallback read_callback; + ASSERT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback.callback())); + EXPECT_EQ(buf->size(), read_callback.WaitForResult()); + EXPECT_EQ(0U, reader_->BytesRemaining()); + EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.end(), buf->data())); + // Try to read again. + EXPECT_EQ(0, reader_->Read(buf.get(), buf->size(), read_callback.callback())); +} + +TEST_F(UploadFileSystemFileElementReaderTest, ReadPartially) { + const size_t kHalfSize = file_data_.size() / 2; + ASSERT_EQ(file_data_.size(), kHalfSize * 2); + + scoped_refptr<net::IOBufferWithSize> buf = + new net::IOBufferWithSize(kHalfSize); + + net::TestCompletionCallback read_callback1; + ASSERT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback1.callback())); + EXPECT_EQ(buf->size(), read_callback1.WaitForResult()); + EXPECT_EQ(file_data_.size() - buf->size(), reader_->BytesRemaining()); + EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.begin() + kHalfSize, + buf->data())); + + net::TestCompletionCallback read_callback2; + EXPECT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback2.callback())); + EXPECT_EQ(buf->size(), read_callback2.WaitForResult()); + EXPECT_EQ(0U, reader_->BytesRemaining()); + EXPECT_TRUE(std::equal(file_data_.begin() + kHalfSize, file_data_.end(), + buf->data())); +} + +TEST_F(UploadFileSystemFileElementReaderTest, ReadTooMuch) { + const size_t kTooLargeSize = file_data_.size() * 2; + scoped_refptr<net::IOBufferWithSize> buf = + new net::IOBufferWithSize(kTooLargeSize); + net::TestCompletionCallback read_callback; + ASSERT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback.callback())); + EXPECT_EQ(static_cast<int>(file_data_.size()), read_callback.WaitForResult()); + EXPECT_EQ(0U, reader_->BytesRemaining()); + EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.end(), buf->data())); +} + +TEST_F(UploadFileSystemFileElementReaderTest, MultipleInit) { + scoped_refptr<net::IOBufferWithSize> buf = + new net::IOBufferWithSize(file_data_.size()); + + // Read all. + net::TestCompletionCallback read_callback1; + ASSERT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback1.callback())); + EXPECT_EQ(buf->size(), read_callback1.WaitForResult()); + EXPECT_EQ(0U, reader_->BytesRemaining()); + EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.end(), buf->data())); + + // Call Init() again to reset the state. + net::TestCompletionCallback init_callback; + ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback.callback())); + EXPECT_EQ(net::OK, init_callback.WaitForResult()); + EXPECT_EQ(file_data_.size(), reader_->GetContentLength()); + EXPECT_EQ(file_data_.size(), reader_->BytesRemaining()); + + // Read again. + net::TestCompletionCallback read_callback2; + ASSERT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback2.callback())); + EXPECT_EQ(buf->size(), read_callback2.WaitForResult()); + EXPECT_EQ(0U, reader_->BytesRemaining()); + EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.end(), buf->data())); +} + +TEST_F(UploadFileSystemFileElementReaderTest, InitDuringAsyncOperation) { + scoped_refptr<net::IOBufferWithSize> buf = + new net::IOBufferWithSize(file_data_.size()); + + // Start reading all. + net::TestCompletionCallback read_callback1; + EXPECT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback1.callback())); + + // Call Init to cancel the previous read. + net::TestCompletionCallback init_callback1; + EXPECT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback1.callback())); + + // Call Init again to cancel the previous init. + net::TestCompletionCallback init_callback2; + EXPECT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback2.callback())); + EXPECT_EQ(net::OK, init_callback2.WaitForResult()); + EXPECT_EQ(file_data_.size(), reader_->GetContentLength()); + EXPECT_EQ(file_data_.size(), reader_->BytesRemaining()); + + // Read half. + scoped_refptr<net::IOBufferWithSize> buf2 = + new net::IOBufferWithSize(file_data_.size() / 2); + net::TestCompletionCallback read_callback2; + EXPECT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf2.get(), buf2->size(), read_callback2.callback())); + EXPECT_EQ(buf2->size(), read_callback2.WaitForResult()); + EXPECT_EQ(file_data_.size() - buf2->size(), reader_->BytesRemaining()); + EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.begin() + buf2->size(), + buf2->data())); + + // Make sure callbacks are not called for cancelled operations. + EXPECT_FALSE(read_callback1.have_result()); + EXPECT_FALSE(init_callback1.have_result()); +} + +TEST_F(UploadFileSystemFileElementReaderTest, Range) { + const int kOffset = 2; + const int kLength = file_data_.size() - kOffset * 3; + reader_.reset(new UploadFileSystemFileElementReader( + file_system_context_.get(), file_url_, kOffset, kLength, base::Time())); + net::TestCompletionCallback init_callback; + ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback.callback())); + EXPECT_EQ(net::OK, init_callback.WaitForResult()); + EXPECT_EQ(static_cast<uint64>(kLength), reader_->GetContentLength()); + EXPECT_EQ(static_cast<uint64>(kLength), reader_->BytesRemaining()); + scoped_refptr<net::IOBufferWithSize> buf = new net::IOBufferWithSize(kLength); + net::TestCompletionCallback read_callback; + ASSERT_EQ(net::ERR_IO_PENDING, + reader_->Read(buf.get(), buf->size(), read_callback.callback())); + EXPECT_EQ(kLength, read_callback.WaitForResult()); + EXPECT_TRUE(std::equal(file_data_.begin() + kOffset, + file_data_.begin() + kOffset + kLength, + buf->data())); +} + +TEST_F(UploadFileSystemFileElementReaderTest, FileChanged) { + // Expect one second before the actual modification time to simulate change. + const base::Time expected_modification_time = + file_modification_time_ - base::TimeDelta::FromSeconds(1); + reader_.reset( + new UploadFileSystemFileElementReader(file_system_context_.get(), + file_url_, + 0, + kuint64max, + expected_modification_time)); + net::TestCompletionCallback init_callback; + ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback.callback())); + EXPECT_EQ(net::ERR_UPLOAD_FILE_CHANGED, init_callback.WaitForResult()); +} + +TEST_F(UploadFileSystemFileElementReaderTest, WrongURL) { + const GURL wrong_url = GetFileSystemURL("wrong_file_name.dat"); + reader_.reset(new UploadFileSystemFileElementReader( + file_system_context_.get(), wrong_url, 0, kuint64max, base::Time())); + net::TestCompletionCallback init_callback; + ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback.callback())); + EXPECT_EQ(net::ERR_FILE_NOT_FOUND, init_callback.WaitForResult()); +} + +} // namespace fileapi |
