| Index: chrome/browser/extensions/api/file_handlers/app_file_handler_util.cc
|
| diff --git a/chrome/browser/extensions/api/file_handlers/app_file_handler_util.cc b/chrome/browser/extensions/api/file_handlers/app_file_handler_util.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..20dbf130a0ba724121cb8f54253ef9bd429e4fda
|
| --- /dev/null
|
| +++ b/chrome/browser/extensions/api/file_handlers/app_file_handler_util.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 "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 "chrome/browser/profiles/profile.h"
|
| +#include "content/public/browser/browser_thread.h"
|
| +#include "content/public/browser/child_process_security_policy.h"
|
| +#include "extensions/browser/api/extensions_api_client.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 "extensions/browser/api/file_handlers/non_native_file_system_delegate.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 path is OK for writing if it
|
| +// is not a symlink, is not in a blacklisted path and can be opened for writing.
|
| +// Creates files if they do not exist, but fails for non-existent directory
|
| +// paths. On Chrome OS, also fails for non-local files that don't already 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)
|
| + NonNativeFileSystemDelegate* delegate =
|
| + ExtensionsAPIClient::Get()->GetNonNativeFileSystemDelegate();
|
| + if (delegate && delegate->IsUnderNonNativeLocalPath(profile_, path)) {
|
| + if (is_directory) {
|
| + delegate->IsNonNativeLocalPathDirectory(
|
| + profile_,
|
| + path,
|
| + base::Bind(&WritableFileChecker::OnPrepareFileDone, this, path));
|
| + } else {
|
| + delegate->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;
|
| +}
|
| +
|
| +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
|
|
|