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 |