diff options
-rw-r--r-- | chrome/browser/file_system/file_system_dispatcher_host.cc | 114 | ||||
-rw-r--r-- | chrome/browser/file_system/file_system_dispatcher_host.h | 33 | ||||
-rw-r--r-- | chrome/browser/file_system/file_system_host_context.cc | 1 | ||||
-rw-r--r-- | webkit/fileapi/file_system_path_manager.cc | 261 | ||||
-rw-r--r-- | webkit/fileapi/file_system_path_manager.h | 56 | ||||
-rw-r--r-- | webkit/fileapi/file_system_path_manager_unittest.cc | 318 | ||||
-rw-r--r-- | webkit/fileapi/file_system_types.h | 1 |
7 files changed, 572 insertions, 212 deletions
diff --git a/chrome/browser/file_system/file_system_dispatcher_host.cc b/chrome/browser/file_system/file_system_dispatcher_host.cc index adf5a8f..f1ef3ca 100644 --- a/chrome/browser/file_system/file_system_dispatcher_host.cc +++ b/chrome/browser/file_system/file_system_dispatcher_host.cc @@ -25,24 +25,24 @@ using fileapi::FileSystemQuota; -// A class to hold an ongoing openFileSystem completion task. -struct OpenFileSystemCompletionTask { +class FileSystemDispatcherHost::OpenFileSystemTask { public: - static void Run( + static void Start( int request_id, - const std::string& name, - const FilePath& root_path, + const GURL& origin_url, + fileapi::FileSystemType type, FileSystemDispatcherHost* dispatcher_host) { // The task is self-destructed. - new OpenFileSystemCompletionTask(request_id, name, root_path, - dispatcher_host); + new OpenFileSystemTask(request_id, origin_url, type, dispatcher_host); } - void DidFinish(base::PlatformFileError error) { - if (error == base::PLATFORM_FILE_OK) + private: + void DidGetRootPath(bool success, const FilePath& root_path, + const std::string& name) { + if (success) dispatcher_host_->Send( new ViewMsg_OpenFileSystemRequest_Complete( - request_id_, true, name_, root_path_)); + request_id_, true, name, root_path)); else dispatcher_host_->Send( new ViewMsg_OpenFileSystemRequest_Complete( @@ -50,28 +50,24 @@ struct OpenFileSystemCompletionTask { delete this; } - private: - OpenFileSystemCompletionTask( + OpenFileSystemTask( int request_id, - const std::string& name, - const FilePath& root_path, + const GURL& origin_url, + fileapi::FileSystemType type, FileSystemDispatcherHost* dispatcher_host) : request_id_(request_id), - name_(name), - root_path_(root_path), dispatcher_host_(dispatcher_host), callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { - base::FileUtilProxy::CreateDirectory( - BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE), - root_path_, false, true, callback_factory_.NewCallback( - &OpenFileSystemCompletionTask::DidFinish)); + dispatcher_host->context_->path_manager()->GetFileSystemRootPath( + origin_url, type, true /* create */, + callback_factory_.NewCallback(&OpenFileSystemTask::DidGetRootPath)); } int request_id_; std::string name_; FilePath root_path_; scoped_refptr<FileSystemDispatcherHost> dispatcher_host_; - base::ScopedCallbackFactory<OpenFileSystemCompletionTask> callback_factory_; + base::ScopedCallbackFactory<OpenFileSystemTask> callback_factory_; }; FileSystemDispatcherHost::FileSystemDispatcherHost( @@ -157,26 +153,14 @@ void FileSystemDispatcherHost::OnOpenFileSystem( return; } - FilePath root_path; - std::string name; - if (!context_->path_manager()->GetFileSystemRootPath( - origin_url, type, &root_path, &name)) { - Send(new ViewMsg_OpenFileSystemRequest_Complete( - request_id, false, std::string(), FilePath())); - return; - } - - // Run the completion task that creates the root directory and sends - // back the status code to the dispatcher. - OpenFileSystemCompletionTask::Run(request_id, name, root_path, this); + OpenFileSystemTask::Start(request_id, origin_url, type, this); } void FileSystemDispatcherHost::OnMove( int request_id, const FilePath& src_path, const FilePath& dest_path) { - if (!CheckValidFileSystemPath(src_path, request_id) || - !CheckValidFileSystemPath(dest_path, request_id) || - !CheckIfFilePathIsSafe(dest_path, request_id) || - !CheckQuotaForPath(dest_path, FileSystemQuota::kUnknownSize, request_id)) + if (!VerifyFileSystemPathForRead(src_path, request_id) || + !VerifyFileSystemPathForWrite(dest_path, request_id, true /* create */, + FileSystemQuota::kUnknownSize)) return; GetNewOperation(request_id)->Move(src_path, dest_path); @@ -184,10 +168,9 @@ void FileSystemDispatcherHost::OnMove( void FileSystemDispatcherHost::OnCopy( int request_id, const FilePath& src_path, const FilePath& dest_path) { - if (!CheckValidFileSystemPath(src_path, request_id) || - !CheckValidFileSystemPath(dest_path, request_id) || - !CheckIfFilePathIsSafe(dest_path, request_id) || - !CheckQuotaForPath(dest_path, FileSystemQuota::kUnknownSize, request_id)) + if (!VerifyFileSystemPathForRead(src_path, request_id) || + !VerifyFileSystemPathForWrite(dest_path, request_id, true /* create */, + FileSystemQuota::kUnknownSize)) return; GetNewOperation(request_id)->Copy(src_path, dest_path); @@ -195,14 +178,14 @@ void FileSystemDispatcherHost::OnCopy( void FileSystemDispatcherHost::OnRemove( int request_id, const FilePath& path, bool recursive) { - if (!CheckValidFileSystemPath(path, request_id)) + if (!VerifyFileSystemPathForWrite(path, request_id, false /* create */, 0)) return; GetNewOperation(request_id)->Remove(path, recursive); } void FileSystemDispatcherHost::OnReadMetadata( int request_id, const FilePath& path) { - if (!CheckValidFileSystemPath(path, request_id)) + if (!VerifyFileSystemPathForRead(path, request_id)) return; GetNewOperation(request_id)->GetMetadata(path); } @@ -210,9 +193,7 @@ void FileSystemDispatcherHost::OnReadMetadata( void FileSystemDispatcherHost::OnCreate( int request_id, const FilePath& path, bool exclusive, bool is_directory, bool recursive) { - if (!CheckValidFileSystemPath(path, request_id) || - !CheckIfFilePathIsSafe(path, request_id) || - !CheckQuotaForPath(path, 0L, request_id)) + if (!VerifyFileSystemPathForWrite(path, request_id, true /* create */, 0)) return; if (is_directory) GetNewOperation(request_id)->CreateDirectory(path, exclusive, recursive); @@ -222,7 +203,7 @@ void FileSystemDispatcherHost::OnCreate( void FileSystemDispatcherHost::OnExists( int request_id, const FilePath& path, bool is_directory) { - if (!CheckValidFileSystemPath(path, request_id)) + if (!VerifyFileSystemPathForRead(path, request_id)) return; if (is_directory) GetNewOperation(request_id)->DirectoryExists(path); @@ -232,7 +213,7 @@ void FileSystemDispatcherHost::OnExists( void FileSystemDispatcherHost::OnReadDirectory( int request_id, const FilePath& path) { - if (!CheckValidFileSystemPath(path, request_id)) + if (!VerifyFileSystemPathForRead(path, request_id)) return; GetNewOperation(request_id)->ReadDirectory(path); } @@ -242,9 +223,8 @@ void FileSystemDispatcherHost::OnWrite( const FilePath& path, const GURL& blob_url, int64 offset) { - if (!CheckValidFileSystemPath(path, request_id) || - !CheckIfFilePathIsSafe(path, request_id) || - !CheckQuotaForPath(path, FileSystemQuota::kUnknownSize, request_id)) + if (!VerifyFileSystemPathForWrite(path, request_id, true /* create */, + FileSystemQuota::kUnknownSize)) return; GetNewOperation(request_id)->Write( request_context_, path, blob_url, offset); @@ -254,7 +234,7 @@ void FileSystemDispatcherHost::OnTruncate( int request_id, const FilePath& path, int64 length) { - if (!CheckValidFileSystemPath(path, request_id)) + if (!VerifyFileSystemPathForWrite(path, request_id, false /* create */, 0)) return; GetNewOperation(request_id)->Truncate(path, length); } @@ -264,8 +244,7 @@ void FileSystemDispatcherHost::OnTouchFile( const FilePath& path, const base::Time& last_access_time, const base::Time& last_modified_time) { - if (!CheckValidFileSystemPath(path, request_id) || - !CheckIfFilePathIsSafe(path, request_id)) + if (!VerifyFileSystemPathForWrite(path, request_id, true /* create */, 0)) return; GetNewOperation(request_id)->TouchFile( path, last_access_time, last_modified_time); @@ -295,11 +274,12 @@ void FileSystemDispatcherHost::Send(IPC::Message* message) { delete message; } -bool FileSystemDispatcherHost::CheckValidFileSystemPath( +bool FileSystemDispatcherHost::VerifyFileSystemPathForRead( const FilePath& path, int request_id) { // We may want do more checks, but for now it just checks if the given // |path| is under the valid FileSystem root path for this host context. - if (!context_->path_manager()->CheckValidFileSystemPath(path)) { + if (!context_->path_manager()->CrackFileSystemPath( + path, NULL, NULL, NULL)) { Send(new ViewMsg_FileSystem_DidFail( request_id, base::PLATFORM_FILE_ERROR_SECURITY)); return false; @@ -307,11 +287,25 @@ bool FileSystemDispatcherHost::CheckValidFileSystemPath( return true; } -bool FileSystemDispatcherHost::CheckQuotaForPath( - const FilePath& path, int64 growth, int request_id) { +bool FileSystemDispatcherHost::VerifyFileSystemPathForWrite( + const FilePath& path, int request_id, bool create, int64 growth) { GURL origin_url; - if (!context_->path_manager()->GetOriginFromPath(path, &origin_url)) { - // Appears to be an ill-formed path or for an unallowed scheme. + FilePath virtual_path; + if (!context_->path_manager()->CrackFileSystemPath( + path, &origin_url, NULL, &virtual_path)) { + Send(new ViewMsg_FileSystem_DidFail( + request_id, base::PLATFORM_FILE_ERROR_SECURITY)); + return false; + } + // Any write access is disallowed on the root path. + if (virtual_path.value().length() == 0 || + virtual_path.DirName().value() == virtual_path.value()) { + Send(new ViewMsg_FileSystem_DidFail( + request_id, base::PLATFORM_FILE_ERROR_SECURITY)); + return false; + } + if (create && context_->path_manager()->IsRestrictedFileName( + path.BaseName())) { Send(new ViewMsg_FileSystem_DidFail( request_id, base::PLATFORM_FILE_ERROR_SECURITY)); return false; diff --git a/chrome/browser/file_system/file_system_dispatcher_host.h b/chrome/browser/file_system/file_system_dispatcher_host.h index cbe86aa..94d0cf4 100644 --- a/chrome/browser/file_system/file_system_dispatcher_host.h +++ b/chrome/browser/file_system/file_system_dispatcher_host.h @@ -81,17 +81,28 @@ class FileSystemDispatcherHost // Creates a new FileSystemOperation. fileapi::FileSystemOperation* GetNewOperation(int request_id); - // Checks the validity of a given |path|. Returns true if the given |path| - // is valid as a path for FileSystem API. Otherwise it sends back - // PLATFORM_FILE_ERROR_SECURITY to the dispatcher and returns false. - bool CheckValidFileSystemPath(const FilePath& path, int request_id); - - // Checks the quota for the given |path|. This method only performs a - // in-memory quick check and returns immediately. - // Returns true if the given |path| will be able to grow by |growth|. - // Otherwise it sends back PLATFORM_FILE_ERROR_NO_SPACE to the dispatcher - // and returns false. - bool CheckQuotaForPath(const FilePath& path, int64 growth, int request_id); + // Checks the validity of a given |path| for reading. + // Returns true if the given |path| is a valid FileSystem path. + // Otherwise it sends back PLATFORM_FILE_ERROR_SECURITY to the + // dispatcher and returns false. + bool VerifyFileSystemPathForRead(const FilePath& path, int request_id); + + // Checks the validity of a given |path| for writing. + // Returns true if the given |path| is a valid FileSystem path, and + // its origin embedded in the path has the right to write as much as + // the given |growth|. + // Otherwise it sends back PLATFORM_FILE_ERROR_SECURITY if the path + // is not valid for writing, or sends back PLATFORM_FILE_ERROR_NO_SPACE + // if the origin is not allowed to increase the usage by |growth|. + // If |create| flag is true this also checks if the |path| contains + // any restricted names and chars. If it does, the call sends back + // PLATFORM_FILE_ERROR_SECURITY to the dispatcher. + bool VerifyFileSystemPathForWrite(const FilePath& path, + int request_id, + bool create, + int64 growth); + + class OpenFileSystemTask; // Checks if a given |path| does not contain any restricted names/chars // for new files. Returns true if the given |path| is safe. diff --git a/chrome/browser/file_system/file_system_host_context.cc b/chrome/browser/file_system/file_system_host_context.cc index f529864..3f26048 100644 --- a/chrome/browser/file_system/file_system_host_context.cc +++ b/chrome/browser/file_system/file_system_host_context.cc @@ -19,6 +19,7 @@ FileSystemHostContext::FileSystemHostContext( switches::kAllowFileAccessFromFiles)), quota_manager_(new fileapi::FileSystemQuota()), path_manager_(new fileapi::FileSystemPathManager( + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE), data_path, is_incognito, allow_file_access_from_files_)) { } diff --git a/webkit/fileapi/file_system_path_manager.cc b/webkit/fileapi/file_system_path_manager.cc index 12c273e..05adafc 100644 --- a/webkit/fileapi/file_system_path_manager.cc +++ b/webkit/fileapi/file_system_path_manager.cc @@ -5,9 +5,9 @@ #include "webkit/fileapi/file_system_path_manager.h" #include "base/file_util.h" -#include "base/file_util_proxy.h" #include "base/rand_util.h" #include "base/logging.h" +#include "base/message_loop.h" #include "base/scoped_callback_factory.h" #include "base/stringprintf.h" #include "base/string_util.h" @@ -24,7 +24,6 @@ using WebKit::WebFileSystem; using WebKit::WebSecurityOrigin; using WebKit::WebString; -using base::FileUtilProxy; using base::PlatformFileError; namespace fileapi { @@ -35,6 +34,12 @@ const FilePath::CharType FileSystemPathManager::kFileSystemDirectory[] = const char FileSystemPathManager::kPersistentName[] = "Persistent"; const char FileSystemPathManager::kTemporaryName[] = "Temporary"; +static const FilePath::CharType kFileSystemUniqueNamePrefix[] = + FILE_PATH_LITERAL("chrome-"); +static const int kFileSystemUniqueLength = 16; +static const unsigned kFileSystemUniqueDirectoryNameLength = + kFileSystemUniqueLength + arraysize(kFileSystemUniqueNamePrefix) - 1; + namespace { // Restricted names. @@ -59,50 +64,163 @@ inline std::string FilePathStringToASCII( #endif } +FilePath::StringType CreateUniqueDirectoryName(const GURL& origin_url) { + // This can be anything but need to be unpredictable. + static const FilePath::CharType letters[] = FILE_PATH_LITERAL( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); + FilePath::StringType unique(kFileSystemUniqueNamePrefix); + for (int i = 0; i < kFileSystemUniqueLength; ++i) + unique += letters[base::RandInt(0, arraysize(letters) - 2)]; + return unique; +} + } // anonymous namespace +class FileSystemPathManager::GetFileSystemRootPathTask + : public base::RefCountedThreadSafe< + FileSystemPathManager::GetFileSystemRootPathTask> { + public: + GetFileSystemRootPathTask( + scoped_refptr<base::MessageLoopProxy> file_message_loop, + const std::string& name, + FileSystemPathManager::GetRootPathCallback* callback) + : file_message_loop_(file_message_loop), + origin_message_loop_proxy_( + base::MessageLoopProxy::CreateForCurrentThread()), + name_(name), + callback_(callback) { + } + + void Start(const GURL& origin_url, + const FilePath& origin_base_path, + bool create) { + file_message_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &GetFileSystemRootPathTask::GetFileSystemRootPathOnFileThread, + origin_url, origin_base_path, create)); + } + + private: + void GetFileSystemRootPathOnFileThread( + const GURL& origin_url, + const FilePath& base_path, + bool create) { + FilePath root; + if (ReadOriginDirectory(base_path, origin_url, &root)) { + DispatchCallbackOnCallerThread(root); + return; + } + + if (!create) { + DispatchCallbackOnCallerThread(FilePath()); + return; + } + + // Creates the root directory. + root = base_path.Append(CreateUniqueDirectoryName(origin_url)); + if (!file_util::CreateDirectory(root)) { + DispatchCallbackOnCallerThread(FilePath()); + return; + } + DispatchCallbackOnCallerThread(root); + } + + bool ReadOriginDirectory(const FilePath& base_path, + const GURL& origin_url, + FilePath* unique) { + file_util::FileEnumerator file_enum( + base_path, false /* recursive */, + file_util::FileEnumerator::DIRECTORIES, + FilePath::StringType(kFileSystemUniqueNamePrefix) + + FILE_PATH_LITERAL("*")); + FilePath current; + bool found = false; + while (!(current = file_enum.Next()).empty()) { + if (current.BaseName().value().length() != + kFileSystemUniqueDirectoryNameLength) + continue; + if (found) { + // TODO(kinuko): Should notify the user to ask for some action. + LOG(WARNING) << "Unexpectedly found more than one FileSystem " + << "directories for " << origin_url; + return false; + } + found = true; + *unique = current; + } + return !unique->empty(); + } + + void DispatchCallbackOnCallerThread(const FilePath& root_path) { + origin_message_loop_proxy_->PostTask(FROM_HERE, + NewRunnableMethod(this, &GetFileSystemRootPathTask::DispatchCallback, + root_path)); + } + + void DispatchCallback(const FilePath& root_path) { + callback_->Run(!root_path.empty(), root_path, name_); + } + + scoped_refptr<base::MessageLoopProxy> file_message_loop_; + scoped_refptr<base::MessageLoopProxy> origin_message_loop_proxy_; + std::string name_; + scoped_ptr<FileSystemPathManager::GetRootPathCallback> callback_; +}; + FileSystemPathManager::FileSystemPathManager( - const FilePath& data_path, + scoped_refptr<base::MessageLoopProxy> file_message_loop, + const FilePath& profile_path, bool is_incognito, bool allow_file_access_from_files) - : base_path_(data_path.Append(kFileSystemDirectory)), + : file_message_loop_(file_message_loop), + base_path_(profile_path.Append(kFileSystemDirectory)), is_incognito_(is_incognito), allow_file_access_from_files_(allow_file_access_from_files) { } -bool FileSystemPathManager::GetFileSystemRootPath( +void FileSystemPathManager::GetFileSystemRootPath( const GURL& origin_url, fileapi::FileSystemType type, - FilePath* root_path, std::string* name) const { - // TODO(kinuko): should return an isolated temporary file system space. - if (is_incognito_) - return false; + bool create, GetRootPathCallback* callback_ptr) { + scoped_ptr<GetRootPathCallback> callback(callback_ptr); + if (is_incognito_) { + // TODO(kinuko): return an isolated temporary directory. + callback->Run(false, FilePath(), std::string()); + return; + } - if (!IsAllowedScheme(origin_url)) - return false; + if (!IsAllowedScheme(origin_url)) { + callback->Run(false, FilePath(), std::string()); + return; + } - std::string storage_identifier = GetStorageIdentifierFromURL(origin_url); - switch (type) { - case fileapi::kFileSystemTypeTemporary: - if (root_path) - *root_path = base_path_.AppendASCII(storage_identifier) - .AppendASCII(kTemporaryName); - if (name) - *name = storage_identifier + ":" + kTemporaryName; - return true; - case fileapi::kFileSystemTypePersistent: - if (root_path) - *root_path = base_path_.AppendASCII(storage_identifier) - .AppendASCII(kPersistentName); - if (name) - *name = storage_identifier + ":" + kPersistentName; - return true; + if (type != fileapi::kFileSystemTypeTemporary && + type != fileapi::kFileSystemTypePersistent) { + LOG(WARNING) << "Unknown filesystem type is requested:" << type; + callback->Run(false, FilePath(), std::string()); + return; } - LOG(WARNING) << "Unknown filesystem type is requested:" << type; - return false; + + std::string storage_identifier = GetStorageIdentifierFromURL(origin_url); + + std::string type_string; + if (type == fileapi::kFileSystemTypeTemporary) + type_string = kTemporaryName; + else if (type == fileapi::kFileSystemTypePersistent) + type_string = kPersistentName; + DCHECK(!type_string.empty()); + + FilePath origin_base_path = base_path_.AppendASCII(storage_identifier) + .AppendASCII(type_string); + std::string name = storage_identifier + ":" + type_string; + + scoped_refptr<GetFileSystemRootPathTask> task = + new GetFileSystemRootPathTask(file_message_loop_, + name, callback.release()); + task->Start(origin_url, origin_base_path, create); } -bool FileSystemPathManager::CheckValidFileSystemPath( - const FilePath& path) const { +bool FileSystemPathManager::CrackFileSystemPath( + const FilePath& path, GURL* origin_url, FileSystemType* type, + FilePath* virtual_path) const { // Any paths that includes parent references are considered invalid. if (path.ReferencesParent()) return false; @@ -112,12 +230,12 @@ bool FileSystemPathManager::CheckValidFileSystemPath( if (!base_path_.AppendRelativePath(path, &relative)) return false; - // The relative path from the profile FileSystem path should at least - // contains two components, one for storage identifier and the other for type - + // The relative path from the profile FileSystem path should contain + // at least three components, one for storage identifier, one for type + // and one for the 'unique' part. std::vector<FilePath::StringType> components; relative.GetComponents(&components); - if (components.size() < 2) + if (components.size() < 3) return false; // The second component of the relative path to the root directory @@ -126,48 +244,42 @@ bool FileSystemPathManager::CheckValidFileSystemPath( return false; std::string ascii_type_component = FilePathStringToASCII(components[1]); - if (ascii_type_component != kPersistentName && - ascii_type_component != kTemporaryName) + FileSystemType cracked_type = kFileSystemTypeUnknown; + if (ascii_type_component == kPersistentName) + cracked_type = kFileSystemTypePersistent; + else if (ascii_type_component == kTemporaryName) + cracked_type = kFileSystemTypeTemporary; + else return false; - return true; -} + DCHECK(cracked_type != kFileSystemTypeUnknown); -bool FileSystemPathManager::GetOriginFromPath( - const FilePath& path, GURL* origin_url) { - DCHECK(origin_url); - FilePath relative; - if (!base_path_.AppendRelativePath(path, &relative)) { - // The path should be a child of the profile's FileSystem path. - return false; - } - std::vector<FilePath::StringType> components; - relative.GetComponents(&components); - if (components.size() < 2) { - // The relative path should at least contain storage identifier and type. - return false; - } - WebSecurityOrigin web_security_origin = - WebSecurityOrigin::createFromDatabaseIdentifier( - webkit_glue::FilePathStringToWebString(components[0])); - *origin_url = GURL(web_security_origin.toString()); - - // We need this work-around for file:/// URIs as - // createFromDatabaseIdentifier returns empty origin_url for them. - if (allow_file_access_from_files_ && origin_url->spec().empty() && - components[0].find(FILE_PATH_LITERAL("file")) == 0) { - *origin_url = GURL("file:///"); - return true; + // The given |path| seems valid. Populates the |origin_url|, |type| + // and |virtual_path| if they are given. + + if (origin_url) { + WebSecurityOrigin web_security_origin = + WebSecurityOrigin::createFromDatabaseIdentifier( + webkit_glue::FilePathStringToWebString(components[0])); + *origin_url = GURL(web_security_origin.toString()); + + // We need this work-around for file:/// URIs as + // createFromDatabaseIdentifier returns empty origin_url for them. + if (allow_file_access_from_files_ && origin_url->spec().empty() && + components[0].find(FILE_PATH_LITERAL("file")) == 0) + *origin_url = GURL("file:///"); } - return IsAllowedScheme(*origin_url); -} + if (type) + *type = cracked_type; -bool FileSystemPathManager::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. - return url.SchemeIs("http") || url.SchemeIs("https") || - (url.SchemeIsFile() && allow_file_access_from_files_); + if (virtual_path) { + virtual_path->clear(); + for (size_t i = 3; i < components.size(); ++i) + *virtual_path = virtual_path->Append(components[i]); + } + + return true; } bool FileSystemPathManager::IsRestrictedFileName( @@ -200,6 +312,13 @@ bool FileSystemPathManager::IsRestrictedFileName( return false; } +bool FileSystemPathManager::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. + return url.SchemeIs("http") || url.SchemeIs("https") || + (url.SchemeIsFile() && allow_file_access_from_files_); +} + std::string FileSystemPathManager::GetStorageIdentifierFromURL( const GURL& url) { WebKit::WebSecurityOrigin web_security_origin = diff --git a/webkit/fileapi/file_system_path_manager.h b/webkit/fileapi/file_system_path_manager.h index 505a3c1..f66c02a 100644 --- a/webkit/fileapi/file_system_path_manager.h +++ b/webkit/fileapi/file_system_path_manager.h @@ -9,6 +9,7 @@ #include "base/callback.h" #include "base/file_path.h" +#include "base/message_loop_proxy.h" #include "base/scoped_ptr.h" #include "googleurl/src/gurl.h" #include "webkit/fileapi/file_system_types.h" @@ -17,33 +18,47 @@ namespace fileapi { class FileSystemPathManager { public: - FileSystemPathManager(const FilePath& data_path, + FileSystemPathManager(scoped_refptr<base::MessageLoopProxy> file_message_loop, + const FilePath& profile_path, bool is_incognito, bool allow_file_access_from_files); - // Returns the root path and name for the file system specified by given - // |origin_url| and |type|. Returns true if the file system is available - // for the profile and |root_path| and |name| are filled successfully. - bool GetFileSystemRootPath(const GURL& origin_url, + // Callback for GetFileSystemRootPath. + // If the request is accepted and the root filesystem for the origin exists + // the callback is called with success=true and valid root_path and name. + // If the request is accepted, |create| is specified for + // GetFileSystemRootPath, and the root directory does not exist, it creates + // a new one and calls back with success=true if the creation has succeeded. + typedef Callback3<bool /* success */, + const FilePath& /* root_path */, + const std::string& /* name */>::Type GetRootPathCallback; + + // Retrieves the root path for the given |origin_url| and |type|, and + // calls the given |callback| with the root path and name. + // If |create| is true this also creates the directory if it doesn't exist. + void GetFileSystemRootPath(const GURL& origin_url, fileapi::FileSystemType type, - FilePath* root_path, - std::string* name) const; - - // Checks if a given |path| is in the FileSystem base directory. - bool CheckValidFileSystemPath(const FilePath& path) const; - - // Retrieves the origin URL for the given |path| and populates - // |origin_url|. It returns false when the given |path| is not a - // valid filesystem path. - bool GetOriginFromPath(const FilePath& path, GURL* origin_url); + bool create, + GetRootPathCallback* callback); + + // Cracks the given |path|, retrieves the information embedded in the path + // and populates |origin_url| and |type|. Also it populates |virtual_path| + // that is a sandboxed path in the file system, i.e. the relative path to + // the file system's root path for the given origin and type. + // It returns false if the path does not conform to the expected + // filesystem path format. + bool CrackFileSystemPath(const FilePath& path, + GURL* origin_url, + FileSystemType* type, + FilePath* virtual_path) const; + + // Checks if a given |name| contains any restricted names/chars in it. + bool IsRestrictedFileName(const FilePath& filename) const; // Returns true if the given |url|'s scheme is allowed to access // filesystem. bool IsAllowedScheme(const GURL& url) const; - // Checks if a given |filename| contains any restricted names/chars in it. - bool IsRestrictedFileName(const FilePath& filename) const; - // The FileSystem directory name. static const FilePath::CharType kFileSystemDirectory[]; @@ -52,14 +67,15 @@ class FileSystemPathManager { private: class GetFileSystemRootPathTask; - friend class GetFileSystemRootPathTask; // Returns the storage identifier string for the given |url|. static std::string GetStorageIdentifierFromURL(const GURL& url); + scoped_refptr<base::MessageLoopProxy> file_message_loop_; + const FilePath base_path_; const bool is_incognito_; - bool allow_file_access_from_files_; + const bool allow_file_access_from_files_; DISALLOW_COPY_AND_ASSIGN(FileSystemPathManager); }; diff --git a/webkit/fileapi/file_system_path_manager_unittest.cc b/webkit/fileapi/file_system_path_manager_unittest.cc index 1e67c3a..1bb39f3 100644 --- a/webkit/fileapi/file_system_path_manager_unittest.cc +++ b/webkit/fileapi/file_system_path_manager_unittest.cc @@ -5,13 +5,16 @@ #include "webkit/fileapi/file_system_path_manager.h" #include "base/basictypes.h" -#include "base/file_path.h" +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/scoped_callback_factory.h" #include "base/scoped_ptr.h" #include "base/scoped_temp_dir.h" #include "googleurl/src/gurl.h" #include "testing/gtest/include/gtest/gtest.h" -using fileapi::FileSystemPathManager; +using namespace fileapi; namespace { @@ -22,27 +25,44 @@ namespace { #define PS "/" #endif -const FilePath::CharType kTestDataPath[] = FILE_PATH_LITERAL( - "//tmp/TestingProfilePath"); +struct RootPathTestCase { + fileapi::FileSystemType type; + const char* origin_url; + const char* expected_path; +}; const struct RootPathTest { fileapi::FileSystemType type; - bool off_the_record; const char* origin_url; - bool expect_root_path; const char* expected_path; } kRootPathTestCases[] = { - { fileapi::kFileSystemTypeTemporary, false, "http://host:1/", - true, "FileSystem" PS "http_host_1" PS "Temporary" }, - { fileapi::kFileSystemTypePersistent, false, "http://host:2/", - true, "FileSystem" PS "http_host_2" PS "Persistent" }, - { fileapi::kFileSystemTypeTemporary, true, "http://host:3/", - false, "" }, - { fileapi::kFileSystemTypePersistent, true, "http://host:4/", - false, "" }, - // We disallow file:// URIs to access filesystem. - { fileapi::kFileSystemTypeTemporary, false, "file:///some/path", false, "" }, - { fileapi::kFileSystemTypePersistent, false, "file:///some/path", false, "" }, + { fileapi::kFileSystemTypeTemporary, "http://foo:1/", + "http_foo_1" PS "Temporary" }, + { fileapi::kFileSystemTypePersistent, "http://foo:1/", + "http_foo_1" PS "Persistent" }, + { fileapi::kFileSystemTypeTemporary, "http://bar.com/", + "http_bar.com_0" PS "Temporary" }, + { fileapi::kFileSystemTypePersistent, "http://bar.com/", + "http_bar.com_0" PS "Persistent" }, + { fileapi::kFileSystemTypeTemporary, "https://foo:2/", + "https_foo_2" PS "Temporary" }, + { fileapi::kFileSystemTypePersistent, "https://foo:2/", + "https_foo_2" PS "Persistent" }, + { fileapi::kFileSystemTypeTemporary, "https://bar.com/", + "https_bar.com_0" PS "Temporary" }, + { fileapi::kFileSystemTypePersistent, "https://bar.com/", + "https_bar.com_0" PS "Persistent" }, +}; + +const struct RootPathFileURITest { + fileapi::FileSystemType type; + const char* origin_url; + const char* expected_path; +} kRootPathFileURITestCases[] = { + { fileapi::kFileSystemTypeTemporary, "file:///", + "file__0" PS "Temporary" }, + { fileapi::kFileSystemTypePersistent, "file:///", + "file__0" PS "Persistent" }, }; const struct CheckValidPathTest { @@ -59,6 +79,13 @@ const struct CheckValidPathTest { { FILE_PATH_LITERAL("a/b/../c/.."), false, }, }; +const char* const kPathToVirtualPathTestCases[] = { + "", + "a", + "a" PS "b", + "a" PS "b" PS "c", +}; + const struct IsRestrictedNameTest { FilePath::StringType name; bool expected_dangerous; @@ -132,58 +159,249 @@ const struct IsRestrictedNameTest { class FileSystemPathManagerTest : public testing::Test { public: FileSystemPathManagerTest() - : data_path_(kTestDataPath) { + : callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { + } + + void SetUp() { + data_dir_.reset(new ScopedTempDir); + data_dir_->CreateUniqueTempDir(); + ASSERT_TRUE(data_dir_->IsValid()); + root_path_callback_status_ = false; + root_path_.clear(); + file_system_name_.clear(); + } + + protected: + FileSystemPathManager* NewPathManager( + bool incognito, + bool allow_file_access) { + return new FileSystemPathManager( + base::MessageLoopProxy::CreateForCurrentThread(), + data_dir_->path(), incognito, allow_file_access); + } + + void OnGetRootPath(bool success, + const FilePath& root_path, + const std::string& name) { + root_path_callback_status_ = success; + root_path_ = root_path; + file_system_name_ = name; } - FileSystemPathManager* GetNewPathManager(bool incognito) { - path_manager_.reset(new FileSystemPathManager( - data_path_, incognito, false /* allow_file_access */)); - return path_manager_.get(); + bool GetRootPath(FileSystemPathManager* manager, + const GURL& origin_url, + fileapi::FileSystemType type, + bool create, + FilePath* root_path) { + manager->GetFileSystemRootPath(origin_url, type, create, + callback_factory_.NewCallback( + &FileSystemPathManagerTest::OnGetRootPath)); + MessageLoop::current()->RunAllPending(); + if (root_path) + *root_path = root_path_; + return root_path_callback_status_; + } + + bool CheckValidFileSystemPath(FileSystemPathManager* manager, + const FilePath& path) { + return manager->CrackFileSystemPath(path, NULL, NULL, NULL); + } + + FilePath data_path() { return data_dir_->path(); } + FilePath file_system_path() { + return data_dir_->path().Append( + FileSystemPathManager::kFileSystemDirectory); } private: - FilePath data_path_; - scoped_ptr<FileSystemPathManager> path_manager_; + scoped_ptr<ScopedTempDir> data_dir_; + base::ScopedCallbackFactory<FileSystemPathManagerTest> callback_factory_; + + bool root_path_callback_status_; + FilePath root_path_; + std::string file_system_name_; + + DISALLOW_COPY_AND_ASSIGN(FileSystemPathManagerTest); }; -TEST_F(FileSystemPathManagerTest, GetRootPath) { +TEST_F(FileSystemPathManagerTest, GetRootPathCreateAndExamine) { + std::vector<FilePath> returned_root_path( + ARRAYSIZE_UNSAFE(kRootPathTestCases)); + scoped_ptr<FileSystemPathManager> manager(NewPathManager(false, false)); + + // Create a new root directory. for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathTestCases); ++i) { - SCOPED_TRACE(testing::Message() << "RootPath #" << i << " " + SCOPED_TRACE(testing::Message() << "RootPath (create) #" << i << " " << kRootPathTestCases[i].expected_path); - FileSystemPathManager* manager = GetNewPathManager( - kRootPathTestCases[i].off_the_record); + FilePath root_path; + EXPECT_TRUE(GetRootPath(manager.get(), + GURL(kRootPathTestCases[i].origin_url), + kRootPathTestCases[i].type, + true /* create */, &root_path)); + + FilePath expected = file_system_path().AppendASCII( + kRootPathTestCases[i].expected_path); + EXPECT_EQ(expected.value(), root_path.DirName().value()); + EXPECT_TRUE(file_util::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); FilePath root_path; - bool result = manager->GetFileSystemRootPath( - GURL(kRootPathTestCases[i].origin_url), - kRootPathTestCases[i].type, - &root_path, NULL); - EXPECT_EQ(kRootPathTestCases[i].expect_root_path, result); - if (result) { - FilePath expected = FilePath(kTestDataPath).AppendASCII( - kRootPathTestCases[i].expected_path); - EXPECT_EQ(expected.value(), root_path.value()); - } + EXPECT_TRUE(GetRootPath(manager.get(), + GURL(kRootPathTestCases[i].origin_url), + kRootPathTestCases[i].type, + false /* create */, &root_path)); + ASSERT_TRUE(returned_root_path.size() > i); + EXPECT_EQ(returned_root_path[i].value(), root_path.value()); } } +TEST_F(FileSystemPathManagerTest, GetRootPathCreateAndExamineWithNewManager) { + std::vector<FilePath> returned_root_path( + ARRAYSIZE_UNSAFE(kRootPathTestCases)); + scoped_ptr<FileSystemPathManager> manager1(NewPathManager(false, false)); + scoped_ptr<FileSystemPathManager> manager2(NewPathManager(false, false)); + + GURL origin_url("http://foo.com:1/"); + + FilePath root_path1; + EXPECT_TRUE(GetRootPath(manager1.get(), origin_url, + kFileSystemTypeTemporary, true, &root_path1)); + FilePath root_path2; + EXPECT_TRUE(GetRootPath(manager2.get(), origin_url, + kFileSystemTypeTemporary, false, &root_path2)); + + EXPECT_EQ(root_path1.value(), root_path2.value()); +} + +TEST_F(FileSystemPathManagerTest, GetRootPathGetWithoutCreate) { + scoped_ptr<FileSystemPathManager> manager(NewPathManager(false, false)); + + // 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(manager.get(), + GURL(kRootPathTestCases[i].origin_url), + kRootPathTestCases[i].type, + false /* create */, NULL)); + } +} + +TEST_F(FileSystemPathManagerTest, GetRootPathInIncognito) { + scoped_ptr<FileSystemPathManager> manager(NewPathManager( + true /* incognito */, false)); + + // 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(manager.get(), + GURL(kRootPathTestCases[i].origin_url), + kRootPathTestCases[i].type, + true /* create */, NULL)); + } +} + +TEST_F(FileSystemPathManagerTest, GetRootPathFileURI) { + scoped_ptr<FileSystemPathManager> manager(NewPathManager(false, false)); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathFileURITestCases); ++i) { + SCOPED_TRACE(testing::Message() << "RootPathFileURI (disallow) #" + << i << " " << kRootPathFileURITestCases[i].expected_path); + EXPECT_FALSE(GetRootPath(manager.get(), + GURL(kRootPathFileURITestCases[i].origin_url), + kRootPathFileURITestCases[i].type, + true /* create */, NULL)); + } +} + +TEST_F(FileSystemPathManagerTest, GetRootPathFileURIWithAllowFlag) { + scoped_ptr<FileSystemPathManager> manager(NewPathManager( + false, true /* allow_file_access_from_files */)); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathFileURITestCases); ++i) { + SCOPED_TRACE(testing::Message() << "RootPathFileURI (allow) #" + << i << " " << kRootPathFileURITestCases[i].expected_path); + FilePath root_path; + EXPECT_TRUE(GetRootPath(manager.get(), + GURL(kRootPathFileURITestCases[i].origin_url), + kRootPathFileURITestCases[i].type, + true /* create */, &root_path)); + FilePath expected = file_system_path().AppendASCII( + kRootPathFileURITestCases[i].expected_path); + EXPECT_EQ(expected.value(), root_path.DirName().value()); + EXPECT_TRUE(file_util::DirectoryExists(root_path)); + } +} + +TEST_F(FileSystemPathManagerTest, VirtualPathFromFileSystemPathTest) { + scoped_ptr<FileSystemPathManager> manager(NewPathManager(false, false)); + FilePath root_path; + EXPECT_TRUE(GetRootPath(manager.get(), GURL("http://foo.com/"), + fileapi::kFileSystemTypeTemporary, + true /* create */, &root_path)); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kPathToVirtualPathTestCases); ++i) { + SCOPED_TRACE(testing::Message() << "PathToVirtualPath #" + << i << " " << kPathToVirtualPathTestCases[i]); + FilePath absolute_path = root_path.AppendASCII( + kPathToVirtualPathTestCases[i]); + FilePath virtual_path; + EXPECT_TRUE(manager->CrackFileSystemPath(absolute_path, NULL, NULL, + &virtual_path)); + + FilePath test_case_path; + test_case_path = test_case_path.AppendASCII( + kPathToVirtualPathTestCases[i]); + EXPECT_EQ(test_case_path.value(), virtual_path.value()); + } +} + +TEST_F(FileSystemPathManagerTest, TypeFromFileSystemPathTest) { + scoped_ptr<FileSystemPathManager> manager(NewPathManager(false, false)); + + FilePath root_path; + fileapi::FileSystemType type; + + EXPECT_TRUE(GetRootPath(manager.get(), GURL("http://foo.com/"), + fileapi::kFileSystemTypeTemporary, + true /* create */, &root_path)); + FilePath path = root_path.AppendASCII("test"); + EXPECT_TRUE(manager->CrackFileSystemPath(path, NULL, &type, NULL)); + EXPECT_EQ(fileapi::kFileSystemTypeTemporary, type); + + EXPECT_TRUE(GetRootPath(manager.get(), GURL("http://foo.com/"), + fileapi::kFileSystemTypePersistent, + true /* create */, &root_path)); + path = root_path.AppendASCII("test"); + EXPECT_TRUE(manager->CrackFileSystemPath(path, NULL, &type, NULL)); + EXPECT_EQ(fileapi::kFileSystemTypePersistent, type); +} + TEST_F(FileSystemPathManagerTest, CheckValidPath) { - FileSystemPathManager* manager = GetNewPathManager(false); + scoped_ptr<FileSystemPathManager> manager(NewPathManager(false, false)); FilePath root_path; - EXPECT_TRUE(manager->GetFileSystemRootPath( - GURL("http://foo.com/"), fileapi::kFileSystemTypePersistent, - &root_path, NULL)); + EXPECT_TRUE(GetRootPath(manager.get(), GURL("http://foo.com/"), + kFileSystemTypePersistent, true, &root_path)); // The root path must be valid, but upper directories or directories // that are not in our temporary or persistent directory must be // evaluated invalid. - EXPECT_TRUE(manager->CheckValidFileSystemPath(root_path)); - EXPECT_FALSE(manager->CheckValidFileSystemPath(root_path.DirName())); - EXPECT_FALSE(manager->CheckValidFileSystemPath( - root_path.DirName().DirName())); - EXPECT_FALSE(manager->CheckValidFileSystemPath( - root_path.DirName().AppendASCII("ArbitraryName"))); + EXPECT_TRUE(CheckValidFileSystemPath(manager.get(), root_path)); + EXPECT_FALSE(CheckValidFileSystemPath(manager.get(), root_path.DirName())); + EXPECT_FALSE(CheckValidFileSystemPath(manager.get(), + root_path.DirName().DirName())); + EXPECT_FALSE(CheckValidFileSystemPath(manager.get(), + root_path.DirName().DirName() + .AppendASCII("ArbitraryName") + .AppendASCII("chrome-dummy"))); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kCheckValidPathTestCases); ++i) { SCOPED_TRACE(testing::Message() << "CheckValidPath #" << i << " " @@ -192,12 +410,12 @@ TEST_F(FileSystemPathManagerTest, CheckValidPath) { if (!path.IsAbsolute()) path = root_path.Append(path); EXPECT_EQ(kCheckValidPathTestCases[i].expected_valid, - manager->CheckValidFileSystemPath(path)); + CheckValidFileSystemPath(manager.get(), path)); } } TEST_F(FileSystemPathManagerTest, IsRestrictedName) { - FileSystemPathManager* manager = GetNewPathManager(false); + scoped_ptr<FileSystemPathManager> manager(NewPathManager(false, false)); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kIsRestrictedNameTestCases); ++i) { SCOPED_TRACE(testing::Message() << "IsRestrictedName #" << i << " " << kIsRestrictedNameTestCases[i].name); diff --git a/webkit/fileapi/file_system_types.h b/webkit/fileapi/file_system_types.h index 5229f52..043e3a2 100644 --- a/webkit/fileapi/file_system_types.h +++ b/webkit/fileapi/file_system_types.h @@ -10,6 +10,7 @@ namespace fileapi { enum FileSystemType { kFileSystemTypeTemporary, kFileSystemTypePersistent, + kFileSystemTypeUnknown, }; } |