// 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 "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h" #include "base/files/file.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "build/build_config.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_security_policy.h" #include "extensions/browser/entry_info.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/granted_file_entry.h" #include "extensions/common/permissions/permissions_data.h" #include "net/base/mime_util.h" #include "storage/browser/fileapi/isolated_context.h" #include "storage/common/fileapi/file_system_mount_option.h" #include "storage/common/fileapi/file_system_types.h" #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/file_manager/filesystem_api_util.h" #endif namespace extensions { namespace app_file_handler_util { const char kInvalidParameters[] = "Invalid parameters"; const char kSecurityError[] = "Security error"; namespace { bool FileHandlerCanHandleFileWithExtension( const FileHandlerInfo& handler, const base::FilePath& path) { for (std::set<std::string>::const_iterator extension = handler.extensions.begin(); extension != handler.extensions.end(); ++extension) { if (*extension == "*") return true; // Accept files whose extension or combined extension (e.g. ".tar.gz") // match the supported extensions of file handler. base::FilePath::StringType handler_extention( base::FilePath::kExtensionSeparator + base::FilePath::FromUTF8Unsafe(*extension).value()); if (base::FilePath::CompareEqualIgnoreCase( handler_extention, path.Extension()) || base::FilePath::CompareEqualIgnoreCase( handler_extention, path.FinalExtension())) { return true; } // Also accept files with no extension for handlers that support an // empty extension, i.e. both "foo" and "foo." match. if (extension->empty() && path.MatchesExtension(base::FilePath::StringType())) { return true; } } return false; } bool FileHandlerCanHandleFileWithMimeType( const FileHandlerInfo& handler, const std::string& mime_type) { for (std::set<std::string>::const_iterator type = handler.types.begin(); type != handler.types.end(); ++type) { if (net::MatchesMimeType(*type, mime_type)) return true; } return false; } bool PrepareNativeLocalFileForWritableApp(const base::FilePath& path, bool is_directory) { DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); // Don't allow links. if (base::PathExists(path) && base::IsLink(path)) return false; if (is_directory) return base::DirectoryExists(path); // Create the file if it doesn't already exist. int creation_flags = base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ; base::File file(path, creation_flags); return file.IsValid(); } // Checks whether a list of paths are all OK for writing and calls a provided // on_success or on_failure callback when done. A file is OK for writing if it // is not a symlink, is not in a blacklisted path and can be opened for writing; // files are created if they do not exist. class WritableFileChecker : public base::RefCountedThreadSafe<WritableFileChecker> { public: WritableFileChecker( const std::vector<base::FilePath>& paths, Profile* profile, const std::set<base::FilePath>& directory_paths, const base::Closure& on_success, const base::Callback<void(const base::FilePath&)>& on_failure); void Check(); private: friend class base::RefCountedThreadSafe<WritableFileChecker>; virtual ~WritableFileChecker(); // Called when a work item is completed. If all work items are done, this // calls the success or failure callback. void TaskDone(); // Reports an error in completing a work item. This may be called more than // once, but only the last message will be retained. void Error(const base::FilePath& error_path); void CheckLocalWritableFiles(); // Called when processing a file is completed with either a success or an // error. void OnPrepareFileDone(const base::FilePath& path, bool success); const std::vector<base::FilePath> paths_; Profile* profile_; const std::set<base::FilePath> directory_paths_; int outstanding_tasks_; base::FilePath error_path_; base::Closure on_success_; base::Callback<void(const base::FilePath&)> on_failure_; }; WritableFileChecker::WritableFileChecker( const std::vector<base::FilePath>& paths, Profile* profile, const std::set<base::FilePath>& directory_paths, const base::Closure& on_success, const base::Callback<void(const base::FilePath&)>& on_failure) : paths_(paths), profile_(profile), directory_paths_(directory_paths), outstanding_tasks_(1), on_success_(on_success), on_failure_(on_failure) {} void WritableFileChecker::Check() { outstanding_tasks_ = paths_.size(); for (const auto& path : paths_) { bool is_directory = directory_paths_.find(path) != directory_paths_.end(); #if defined(OS_CHROMEOS) if (file_manager::util::IsUnderNonNativeLocalPath(profile_, path)) { if (is_directory) { file_manager::util::IsNonNativeLocalPathDirectory( profile_, path, base::Bind(&WritableFileChecker::OnPrepareFileDone, this, path)); } else { file_manager::util::PrepareNonNativeLocalFileForWritableApp( profile_, path, base::Bind(&WritableFileChecker::OnPrepareFileDone, this, path)); } continue; } #endif content::BrowserThread::PostTaskAndReplyWithResult( content::BrowserThread::FILE, FROM_HERE, base::Bind(&PrepareNativeLocalFileForWritableApp, path, is_directory), base::Bind(&WritableFileChecker::OnPrepareFileDone, this, path)); } } WritableFileChecker::~WritableFileChecker() {} void WritableFileChecker::TaskDone() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (--outstanding_tasks_ == 0) { if (error_path_.empty()) on_success_.Run(); else on_failure_.Run(error_path_); } } // Reports an error in completing a work item. This may be called more than // once, but only the last message will be retained. void WritableFileChecker::Error(const base::FilePath& error_path) { DCHECK(!error_path.empty()); error_path_ = error_path; TaskDone(); } void WritableFileChecker::OnPrepareFileDone(const base::FilePath& path, bool success) { if (success) TaskDone(); else Error(path); } } // namespace const FileHandlerInfo* FileHandlerForId(const Extension& app, const std::string& handler_id) { const FileHandlersInfo* file_handlers = FileHandlers::GetFileHandlers(&app); if (!file_handlers) return NULL; for (FileHandlersInfo::const_iterator i = file_handlers->begin(); i != file_handlers->end(); i++) { if (i->id == handler_id) return &*i; } return NULL; } const FileHandlerInfo* FirstFileHandlerForEntry(const Extension& app, const EntryInfo& entry) { const FileHandlersInfo* file_handlers = FileHandlers::GetFileHandlers(&app); if (!file_handlers) return NULL; for (FileHandlersInfo::const_iterator i = file_handlers->begin(); i != file_handlers->end(); i++) { if (FileHandlerCanHandleEntry(*i, entry)) return &*i; } return NULL; } std::vector<const FileHandlerInfo*> FindFileHandlersForEntries( const Extension& app, const std::vector<EntryInfo> entries) { std::vector<const FileHandlerInfo*> handlers; if (entries.empty()) return handlers; // Look for file handlers which can handle all the MIME types specified. const FileHandlersInfo* file_handlers = FileHandlers::GetFileHandlers(&app); if (!file_handlers) return handlers; for (FileHandlersInfo::const_iterator data = file_handlers->begin(); data != file_handlers->end(); ++data) { bool handles_all_types = true; for (std::vector<EntryInfo>::const_iterator it = entries.begin(); it != entries.end(); ++it) { if (!FileHandlerCanHandleEntry(*data, *it)) { handles_all_types = false; break; } } if (handles_all_types) handlers.push_back(&*data); } return handlers; } bool FileHandlerCanHandleEntry(const FileHandlerInfo& handler, const EntryInfo& entry) { if (entry.is_directory) return handler.include_directories; return FileHandlerCanHandleFileWithMimeType(handler, entry.mime_type) || FileHandlerCanHandleFileWithExtension(handler, entry.path); } GrantedFileEntry CreateFileEntry( Profile* profile, const Extension* extension, int renderer_id, const base::FilePath& path, bool is_directory) { GrantedFileEntry result; storage::IsolatedContext* isolated_context = storage::IsolatedContext::GetInstance(); DCHECK(isolated_context); result.filesystem_id = isolated_context->RegisterFileSystemForPath( storage::kFileSystemTypeNativeForPlatformApp, std::string(), path, &result.registered_name); content::ChildProcessSecurityPolicy* policy = content::ChildProcessSecurityPolicy::GetInstance(); policy->GrantReadFileSystem(renderer_id, result.filesystem_id); if (HasFileSystemWritePermission(extension)) { if (is_directory) { policy->GrantCreateReadWriteFileSystem(renderer_id, result.filesystem_id); } else { policy->GrantWriteFileSystem(renderer_id, result.filesystem_id); policy->GrantDeleteFromFileSystem(renderer_id, result.filesystem_id); } } result.id = result.filesystem_id + ":" + result.registered_name; return result; } void PrepareFilesForWritableApp( const std::vector<base::FilePath>& paths, Profile* profile, const std::set<base::FilePath>& directory_paths, const base::Closure& on_success, const base::Callback<void(const base::FilePath&)>& on_failure) { scoped_refptr<WritableFileChecker> checker(new WritableFileChecker( paths, profile, directory_paths, on_success, on_failure)); checker->Check(); } bool HasFileSystemWritePermission(const Extension* extension) { return extension->permissions_data()->HasAPIPermission( APIPermission::kFileSystemWrite); } bool ValidateFileEntryAndGetPath(const std::string& filesystem_name, const std::string& filesystem_path, int render_process_id, base::FilePath* file_path, std::string* error) { if (filesystem_path.empty()) { *error = kInvalidParameters; return false; } std::string filesystem_id; if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id)) { *error = kInvalidParameters; return false; } // Only return the display path if the process has read access to the // filesystem. content::ChildProcessSecurityPolicy* policy = content::ChildProcessSecurityPolicy::GetInstance(); if (!policy->CanReadFileSystem(render_process_id, filesystem_id)) { *error = kSecurityError; return false; } storage::IsolatedContext* context = storage::IsolatedContext::GetInstance(); base::FilePath relative_path = base::FilePath::FromUTF8Unsafe(filesystem_path); base::FilePath virtual_path = context->CreateVirtualRootPath(filesystem_id) .Append(relative_path); storage::FileSystemType type; storage::FileSystemMountOption mount_option; std::string cracked_id; if (!context->CrackVirtualPath( virtual_path, &filesystem_id, &type, &cracked_id, file_path, &mount_option)) { *error = kInvalidParameters; return false; } // The file system API is only intended to operate on file entries that // correspond to a native file, selected by the user so only allow file // systems returned by the file system API or from a drag and drop operation. if (type != storage::kFileSystemTypeNativeForPlatformApp && type != storage::kFileSystemTypeDragged) { *error = kInvalidParameters; return false; } return true; } } // namespace app_file_handler_util } // namespace extensions