// 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/chromeos/file_manager/file_browser_handlers.h" #include "base/bind.h" #include "base/file_util.h" #include "base/i18n/case_conversion.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/chromeos/drive/file_system_util.h" #include "chrome/browser/chromeos/file_manager/app_id.h" #include "chrome/browser/chromeos/file_manager/fileapi_util.h" #include "chrome/browser/chromeos/file_manager/open_with_browser.h" #include "chrome/browser/chromeos/fileapi/file_system_backend.h" #include "chrome/browser/extensions/extension_host.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/child_process_security_policy.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/site_instance.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/lazy_background_task_queue.h" #include "extensions/common/extension_set.h" #include "extensions/common/manifest_handlers/background_info.h" #include "net/base/escape.h" #include "webkit/browser/fileapi/file_system_context.h" #include "webkit/browser/fileapi/file_system_url.h" #include "webkit/common/fileapi/file_system_info.h" #include "webkit/common/fileapi/file_system_util.h" using content::BrowserThread; using content::ChildProcessSecurityPolicy; using content::SiteInstance; using content::WebContents; using extensions::Extension; using fileapi::FileSystemURL; namespace file_manager { namespace file_browser_handlers { namespace { // Returns process id of the process the extension is running in. int ExtractProcessFromExtensionId(Profile* profile, const std::string& extension_id) { GURL extension_url = Extension::GetBaseURLFromExtensionId(extension_id); extensions::ProcessManager* manager = extensions::ExtensionSystem::Get(profile)->process_manager(); SiteInstance* site_instance = manager->GetSiteInstanceForURL(extension_url); if (!site_instance || !site_instance->HasProcess()) return -1; content::RenderProcessHost* process = site_instance->GetProcess(); return process->GetID(); } // Finds a file browser handler that matches |action_id|. Returns NULL if not // found. const FileBrowserHandler* FindFileBrowserHandlerForActionId( const Extension* extension, const std::string& action_id) { FileBrowserHandler::List* handler_list = FileBrowserHandler::GetHandlers(extension); for (FileBrowserHandler::List::const_iterator handler_iter = handler_list->begin(); handler_iter != handler_list->end(); ++handler_iter) { if (handler_iter->get()->id() == action_id) return handler_iter->get(); } return NULL; } std::string EscapedUtf8ToLower(const std::string& str) { base::string16 utf16 = base::UTF8ToUTF16( net::UnescapeURLComponent(str, net::UnescapeRule::NORMAL)); return net::EscapeUrlEncodedData( base::UTF16ToUTF8(base::i18n::ToLower(utf16)), false /* do not replace space with plus */); } // Finds file browser handlers that can handle the |selected_file_url|. FileBrowserHandlerList FindFileBrowserHandlersForURL( Profile* profile, const GURL& selected_file_url) { ExtensionService* service = extensions::ExtensionSystem::Get(profile)->extension_service(); // In unit-tests, we may not have an ExtensionService. if (!service) return FileBrowserHandlerList(); // We need case-insensitive matching, and pattern in the handler is already // in lower case. const GURL lowercase_url(EscapedUtf8ToLower(selected_file_url.spec())); FileBrowserHandlerList results; for (extensions::ExtensionSet::const_iterator iter = service->extensions()->begin(); iter != service->extensions()->end(); ++iter) { const Extension* extension = iter->get(); if (profile->IsOffTheRecord() && !extensions::util::IsIncognitoEnabled(extension->id(), profile)) continue; FileBrowserHandler::List* handler_list = FileBrowserHandler::GetHandlers(extension); if (!handler_list) continue; for (FileBrowserHandler::List::const_iterator handler_iter = handler_list->begin(); handler_iter != handler_list->end(); ++handler_iter) { const FileBrowserHandler* handler = handler_iter->get(); if (!handler->MatchesURL(lowercase_url)) continue; results.push_back(handler_iter->get()); } } return results; } // Finds a file browser handler that matches |extension_id| and |action_id| // from |handler_list|. Returns a mutable iterator to the handler if // found. Returns handler_list->end() if not found. FileBrowserHandlerList::iterator FindFileBrowserHandlerForExtensionIdAndActionId( FileBrowserHandlerList* handler_list, const std::string& extension_id, const std::string& action_id) { DCHECK(handler_list); FileBrowserHandlerList::iterator iter = handler_list->begin(); while (iter != handler_list->end() && !((*iter)->extension_id() == extension_id && (*iter)->id() == action_id)) { ++iter; } return iter; } // This class is used to execute a file browser handler task. Here's how this // works: // // 1) Open the "external" file system // 2) Set up permissions for the target files on the external file system. // 3) Raise onExecute event with the action ID and entries of the target // files. The event will launch the file browser handler if not active. // 4) In the file browser handler, onExecute event is handled and executes the // task in JavaScript. // // That said, the class itself does not execute a task. The task will be // executed in JavaScript. class FileBrowserHandlerExecutor { public: FileBrowserHandlerExecutor(Profile* profile, const Extension* extension, const std::string& action_id); // Executes the task for each file. |done| will be run with the result. void Execute(const std::vector& file_urls, const file_tasks::FileTaskFinishedCallback& done); private: // This object is responsible to delete itself. virtual ~FileBrowserHandlerExecutor(); struct FileDefinition { FileDefinition(); ~FileDefinition(); base::FilePath virtual_path; base::FilePath absolute_path; bool is_directory; }; typedef std::vector FileDefinitionList; // Checks legitimacy of file url and grants file RO access permissions from // handler (target) extension and its renderer process. static FileDefinitionList SetupFileAccessPermissions( scoped_refptr file_system_context_handler, const scoped_refptr& handler_extension, const std::vector& file_urls); void ExecuteDoneOnUIThread(bool success); void ExecuteFileActionsOnUIThread(const FileDefinitionList& file_list); void SetupPermissionsAndDispatchEvent(const std::string& file_system_name, const GURL& file_system_root, const FileDefinitionList& file_list, int handler_pid_in, extensions::ExtensionHost* host); // Registers file permissions from |handler_host_permissions_| with // ChildProcessSecurityPolicy for process with id |handler_pid|. void SetupHandlerHostFileAccessPermissions( const FileDefinitionList& file_list, const Extension* extension, int handler_pid); Profile* profile_; scoped_refptr extension_; const std::string action_id_; file_tasks::FileTaskFinishedCallback done_; base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(FileBrowserHandlerExecutor); }; FileBrowserHandlerExecutor::FileDefinition::FileDefinition() : is_directory(false) { } FileBrowserHandlerExecutor::FileDefinition::~FileDefinition() { } // static FileBrowserHandlerExecutor::FileDefinitionList FileBrowserHandlerExecutor::SetupFileAccessPermissions( scoped_refptr file_system_context_handler, const scoped_refptr& handler_extension, const std::vector& file_urls) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); DCHECK(handler_extension.get()); fileapi::ExternalFileSystemBackend* backend = file_system_context_handler->external_backend(); FileDefinitionList file_list; for (size_t i = 0; i < file_urls.size(); ++i) { const FileSystemURL& url = file_urls[i]; // Check if this file system entry exists first. base::File::Info file_info; base::FilePath local_path = url.path(); base::FilePath virtual_path = url.virtual_path(); bool is_drive_file = url.type() == fileapi::kFileSystemTypeDrive; DCHECK(!is_drive_file || drive::util::IsUnderDriveMountPoint(local_path)); // If the file is under drive mount point, there is no actual file to be // found on the url.path(). if (!is_drive_file) { if (!base::PathExists(local_path) || base::IsLink(local_path) || !base::GetFileInfo(local_path, &file_info)) { continue; } } // Grant access to this particular file to target extension. This will // ensure that the target extension can access only this FS entry and // prevent from traversing FS hierarchy upward. backend->GrantFileAccessToExtension(handler_extension->id(), virtual_path); // Output values. FileDefinition file; file.virtual_path = virtual_path; file.is_directory = file_info.is_directory; file.absolute_path = local_path; file_list.push_back(file); } return file_list; } FileBrowserHandlerExecutor::FileBrowserHandlerExecutor( Profile* profile, const Extension* extension, const std::string& action_id) : profile_(profile), extension_(extension), action_id_(action_id), weak_ptr_factory_(this) { } FileBrowserHandlerExecutor::~FileBrowserHandlerExecutor() {} void FileBrowserHandlerExecutor::Execute( const std::vector& file_urls, const file_tasks::FileTaskFinishedCallback& done) { done_ = done; // Get file system context for the extension to which onExecute event will be // sent. The file access permissions will be granted to the extension in the // file system context for the files in |file_urls|. scoped_refptr file_system_context( util::GetFileSystemContextForExtensionId( profile_, extension_->id())); BrowserThread::PostTaskAndReplyWithResult( BrowserThread::FILE, FROM_HERE, base::Bind(&SetupFileAccessPermissions, file_system_context, extension_, file_urls), base::Bind(&FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread, weak_ptr_factory_.GetWeakPtr())); } void FileBrowserHandlerExecutor::ExecuteDoneOnUIThread(bool success) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (!done_.is_null()) done_.Run(success); delete this; } void FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread( const FileDefinitionList& file_list) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (file_list.empty()) { ExecuteDoneOnUIThread(false); return; } int handler_pid = ExtractProcessFromExtensionId(profile_, extension_->id()); if (handler_pid <= 0 && !extensions::BackgroundInfo::HasLazyBackgroundPage(extension_.get())) { ExecuteDoneOnUIThread(false); return; } fileapi::FileSystemInfo info = fileapi::GetFileSystemInfoForChromeOS( Extension::GetBaseURLFromExtensionId(extension_->id()).GetOrigin()); if (handler_pid > 0) { SetupPermissionsAndDispatchEvent(info.name, info.root_url, file_list, handler_pid, NULL); } else { // We have to wake the handler background page before we proceed. extensions::LazyBackgroundTaskQueue* queue = extensions::ExtensionSystem::Get(profile_)-> lazy_background_task_queue(); if (!queue->ShouldEnqueueTask(profile_, extension_.get())) { ExecuteDoneOnUIThread(false); return; } queue->AddPendingTask( profile_, extension_->id(), base::Bind( &FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent, weak_ptr_factory_.GetWeakPtr(), info.name, info.root_url, file_list, handler_pid)); } } void FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent( const std::string& file_system_name, const GURL& file_system_root, const FileDefinitionList& file_list, int handler_pid_in, extensions::ExtensionHost* host) { int handler_pid = host ? host->render_process_host()->GetID() : handler_pid_in; if (handler_pid <= 0) { ExecuteDoneOnUIThread(false); return; } extensions::EventRouter* event_router = extensions::ExtensionSystem::Get(profile_)->event_router(); if (!event_router) { ExecuteDoneOnUIThread(false); return; } SetupHandlerHostFileAccessPermissions( file_list, extension_.get(), handler_pid); scoped_ptr event_args(new base::ListValue()); event_args->Append(new base::StringValue(action_id_)); base::DictionaryValue* details = new base::DictionaryValue(); event_args->Append(details); // Get file definitions. These will be replaced with Entry instances by // dispatchEvent() method from event_binding.js. base::ListValue* file_entries = new base::ListValue(); details->Set("entries", file_entries); for (FileDefinitionList::const_iterator iter = file_list.begin(); iter != file_list.end(); ++iter) { base::DictionaryValue* file_def = new base::DictionaryValue(); file_entries->Append(file_def); file_def->SetString("fileSystemName", file_system_name); file_def->SetString("fileSystemRoot", file_system_root.spec()); file_def->SetString("fileFullPath", "/" + iter->virtual_path.AsUTF8Unsafe()); file_def->SetBoolean("fileIsDirectory", iter->is_directory); } scoped_ptr event(new extensions::Event( "fileBrowserHandler.onExecute", event_args.Pass())); event->restrict_to_browser_context = profile_; event_router->DispatchEventToExtension(extension_->id(), event.Pass()); ExecuteDoneOnUIThread(true); } void FileBrowserHandlerExecutor::SetupHandlerHostFileAccessPermissions( const FileDefinitionList& file_list, const Extension* extension, int handler_pid) { const FileBrowserHandler* action = FindFileBrowserHandlerForActionId( extension_, action_id_); for (FileDefinitionList::const_iterator iter = file_list.begin(); iter != file_list.end(); ++iter) { if (!action) continue; if (action->CanRead()) { content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile( handler_pid, iter->absolute_path); } if (action->CanWrite()) { content::ChildProcessSecurityPolicy::GetInstance()-> GrantCreateReadWriteFile(handler_pid, iter->absolute_path); } } } // Returns true if |extension_id| and |action_id| indicate that the file // currently being handled should be opened with the browser. This function // is used to handle certain action IDs of the file manager. bool ShouldBeOpenedWithBrowser(const std::string& extension_id, const std::string& action_id) { return (extension_id == kFileManagerAppId && (action_id == "view-pdf" || action_id == "view-swf" || action_id == "view-in-browser" || action_id == "open-hosted-generic" || action_id == "open-hosted-gdoc" || action_id == "open-hosted-gsheet" || action_id == "open-hosted-gslides")); } // Opens the files specified by |file_urls| with the browser for |profile|. // Returns true on success. It's a failure if no files are opened. bool OpenFilesWithBrowser(Profile* profile, const std::vector& file_urls) { int num_opened = 0; for (size_t i = 0; i < file_urls.size(); ++i) { const FileSystemURL& file_url = file_urls[i]; if (chromeos::FileSystemBackend::CanHandleURL(file_url)) { const base::FilePath& file_path = file_url.path(); num_opened += util::OpenFileWithBrowser(profile, file_path); } } return num_opened > 0; } } // namespace bool ExecuteFileBrowserHandler( Profile* profile, const Extension* extension, const std::string& action_id, const std::vector& file_urls, const file_tasks::FileTaskFinishedCallback& done) { // Forbid calling undeclared handlers. if (!FindFileBrowserHandlerForActionId(extension, action_id)) return false; // Some action IDs of the file manager's file browser handlers require the // files to be directly opened with the browser. if (ShouldBeOpenedWithBrowser(extension->id(), action_id)) { return OpenFilesWithBrowser(profile, file_urls); } // The executor object will be self deleted on completion. (new FileBrowserHandlerExecutor( profile, extension, action_id))->Execute(file_urls, done); return true; } bool IsFallbackFileBrowserHandler(const file_tasks::TaskDescriptor& task) { return (task.task_type == file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER && (task.app_id == kFileManagerAppId || task.app_id == extension_misc::kQuickOfficeComponentExtensionId || task.app_id == extension_misc::kQuickOfficeDevExtensionId || task.app_id == extension_misc::kQuickOfficeExtensionId)); } FileBrowserHandlerList FindFileBrowserHandlers( Profile* profile, const std::vector& file_list) { FileBrowserHandlerList common_handlers; for (std::vector::const_iterator it = file_list.begin(); it != file_list.end(); ++it) { FileBrowserHandlerList handlers = FindFileBrowserHandlersForURL(profile, *it); // If there is nothing to do for one file, the intersection of handlers // for all files will be empty at the end, so no need to check further. if (handlers.empty()) return FileBrowserHandlerList(); // For the very first file, just copy all the elements. if (it == file_list.begin()) { common_handlers = handlers; } else { // For all additional files, find intersection between the accumulated and // file specific set. FileBrowserHandlerList intersection; std::set common_handler_set( common_handlers.begin(), common_handlers.end()); for (FileBrowserHandlerList::const_iterator itr = handlers.begin(); itr != handlers.end(); ++itr) { if (ContainsKey(common_handler_set, *itr)) intersection.push_back(*itr); } std::swap(common_handlers, intersection); if (common_handlers.empty()) return FileBrowserHandlerList(); } } // "watch" and "gallery" are defined in the file manager's manifest.json. FileBrowserHandlerList::iterator watch_iter = FindFileBrowserHandlerForExtensionIdAndActionId( &common_handlers, kFileManagerAppId, "watch"); FileBrowserHandlerList::iterator gallery_iter = FindFileBrowserHandlerForExtensionIdAndActionId( &common_handlers, kFileManagerAppId, "gallery"); if (watch_iter != common_handlers.end() && gallery_iter != common_handlers.end()) { // Both "watch" and "gallery" actions are applicable which means that the // selection is all videos. Showing them both is confusing, so we only keep // the one that makes more sense ("watch" for single selection, "gallery" // for multiple selection). if (file_list.size() == 1) common_handlers.erase(gallery_iter); else common_handlers.erase(watch_iter); } return common_handlers; } } // namespace file_browser_handlers } // namespace file_manager