| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "webkit/fileapi/file_system_path_manager.h" |
| |
| #include "base/file_util.h" |
| #include "base/rand_util.h" |
| #include "base/logging.h" |
| #include "base/message_loop.h" |
| #include "base/message_loop_proxy.h" |
| #include "base/scoped_callback_factory.h" |
| #include "base/stringprintf.h" |
| #include "base/string_util.h" |
| #include "base/utf_string_conversions.h" |
| #include "googleurl/src/gurl.h" |
| #include "third_party/WebKit/WebKit/chromium/public/WebCString.h" |
| #include "third_party/WebKit/WebKit/chromium/public/WebFileSystem.h" |
| #include "third_party/WebKit/WebKit/chromium/public/WebSecurityOrigin.h" |
| #include "webkit/glue/webkit_glue.h" |
| |
| // We use some of WebKit types for conversions between storage identifiers |
| // and origin URLs. |
| using WebKit::WebFileSystem; |
| using WebKit::WebSecurityOrigin; |
| using WebKit::WebString; |
| |
| using base::PlatformFileError; |
| |
| namespace fileapi { |
| |
| const FilePath::CharType FileSystemPathManager::kFileSystemDirectory[] = |
| FILE_PATH_LITERAL("FileSystem"); |
| |
| 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. |
| // http://dev.w3.org/2009/dap/file-system/file-dir-sys.html#naming-restrictions |
| static const char* const kRestrictedNames[] = { |
| "con", "prn", "aux", "nul", |
| "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9", |
| "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", |
| }; |
| |
| // Restricted chars. |
| static const FilePath::CharType kRestrictedChars[] = { |
| '/', '\\', '<', '>', ':', '?', '*', '"', '|', |
| }; |
| |
| inline std::string FilePathStringToASCII( |
| const FilePath::StringType& path_string) { |
| #if defined(OS_WIN) |
| return WideToASCII(path_string); |
| #elif defined(OS_POSIX) |
| return path_string; |
| #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( |
| scoped_refptr<base::MessageLoopProxy> file_message_loop, |
| const FilePath& profile_path, |
| bool is_incognito, |
| bool allow_file_access_from_files) |
| : 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) { |
| } |
| |
| FileSystemPathManager::~FileSystemPathManager() {} |
| |
| void FileSystemPathManager::GetFileSystemRootPath( |
| const GURL& origin_url, fileapi::FileSystemType type, |
| 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)) { |
| callback->Run(false, FilePath(), std::string()); |
| return; |
| } |
| |
| if (type != fileapi::kFileSystemTypeTemporary && |
| type != fileapi::kFileSystemTypePersistent) { |
| LOG(WARNING) << "Unknown filesystem type is requested:" << type; |
| callback->Run(false, FilePath(), std::string()); |
| return; |
| } |
| |
| 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::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; |
| |
| // The path should be a child of the profile FileSystem path. |
| FilePath relative; |
| if (!base_path_.AppendRelativePath(path, &relative)) |
| return false; |
| |
| // 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() < 3) |
| return false; |
| |
| // The second component of the relative path to the root directory |
| // must be kPersistent or kTemporary. |
| if (!IsStringASCII(components[1])) |
| return false; |
| |
| std::string ascii_type_component = FilePathStringToASCII(components[1]); |
| FileSystemType cracked_type = kFileSystemTypeUnknown; |
| if (ascii_type_component == kPersistentName) |
| cracked_type = kFileSystemTypePersistent; |
| else if (ascii_type_component == kTemporaryName) |
| cracked_type = kFileSystemTypeTemporary; |
| else |
| return false; |
| |
| DCHECK(cracked_type != kFileSystemTypeUnknown); |
| |
| // 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:///"); |
| } |
| |
| if (type) |
| *type = cracked_type; |
| |
| 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( |
| const FilePath& filename) const { |
| if (filename.value().size() == 0) |
| return false; |
| |
| if (IsWhitespace(filename.value()[filename.value().size() - 1]) || |
| filename.value()[filename.value().size() - 1] == '.') |
| return true; |
| |
| std::string filename_lower = StringToLowerASCII( |
| FilePathStringToASCII(filename.value())); |
| |
| for (size_t i = 0; i < arraysize(kRestrictedNames); ++i) { |
| // Exact match. |
| if (filename_lower == kRestrictedNames[i]) |
| return true; |
| // Starts with "RESTRICTED_NAME.". |
| if (filename_lower.find(std::string(kRestrictedNames[i]) + ".") == 0) |
| return true; |
| } |
| |
| for (size_t i = 0; i < arraysize(kRestrictedChars); ++i) { |
| if (filename.value().find(kRestrictedChars[i]) != |
| FilePath::StringType::npos) |
| return true; |
| } |
| |
| 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 = |
| WebKit::WebSecurityOrigin::createFromString(UTF8ToUTF16(url.spec())); |
| return web_security_origin.databaseIdentifier().utf8(); |
| } |
| |
| } // namespace fileapi |
| |
| COMPILE_ASSERT(int(WebFileSystem::TypeTemporary) == \ |
| int(fileapi::kFileSystemTypeTemporary), mismatching_enums); |
| COMPILE_ASSERT(int(WebFileSystem::TypePersistent) == \ |
| int(fileapi::kFileSystemTypePersistent), mismatching_enums); |