file_browser_handlers.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/chromeos/file_manager/file_browser_handlers.h" 6 7#include "base/bind.h" 8#include "base/file_util.h" 9#include "base/i18n/case_conversion.h" 10#include "base/strings/utf_string_conversions.h" 11#include "chrome/browser/chromeos/drive/file_system_util.h" 12#include "chrome/browser/chromeos/file_manager/app_id.h" 13#include "chrome/browser/chromeos/file_manager/fileapi_util.h" 14#include "chrome/browser/chromeos/file_manager/open_with_browser.h" 15#include "chrome/browser/chromeos/fileapi/file_system_backend.h" 16#include "chrome/browser/extensions/extension_service.h" 17#include "chrome/browser/extensions/extension_util.h" 18#include "chrome/browser/profiles/profile.h" 19#include "chrome/browser/ui/browser_finder.h" 20#include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h" 21#include "chrome/common/extensions/api/file_browser_private.h" 22#include "content/public/browser/browser_thread.h" 23#include "content/public/browser/child_process_security_policy.h" 24#include "content/public/browser/render_process_host.h" 25#include "content/public/browser/site_instance.h" 26#include "content/public/browser/web_contents.h" 27#include "extensions/browser/event_router.h" 28#include "extensions/browser/extension_host.h" 29#include "extensions/browser/extension_system.h" 30#include "extensions/browser/lazy_background_task_queue.h" 31#include "extensions/common/extension_set.h" 32#include "extensions/common/manifest_handlers/background_info.h" 33#include "net/base/escape.h" 34#include "webkit/browser/fileapi/file_system_context.h" 35#include "webkit/browser/fileapi/file_system_url.h" 36#include "webkit/common/fileapi/file_system_info.h" 37#include "webkit/common/fileapi/file_system_util.h" 38 39using content::BrowserThread; 40using content::ChildProcessSecurityPolicy; 41using content::SiteInstance; 42using content::WebContents; 43using extensions::Extension; 44using fileapi::FileSystemURL; 45using file_manager::util::EntryDefinition; 46using file_manager::util::EntryDefinitionList; 47using file_manager::util::FileDefinition; 48using file_manager::util::FileDefinitionList; 49 50namespace file_manager { 51namespace file_browser_handlers { 52namespace { 53 54// Returns process id of the process the extension is running in. 55int ExtractProcessFromExtensionId(Profile* profile, 56 const std::string& extension_id) { 57 GURL extension_url = 58 Extension::GetBaseURLFromExtensionId(extension_id); 59 extensions::ProcessManager* manager = 60 extensions::ExtensionSystem::Get(profile)->process_manager(); 61 62 SiteInstance* site_instance = manager->GetSiteInstanceForURL(extension_url); 63 if (!site_instance || !site_instance->HasProcess()) 64 return -1; 65 content::RenderProcessHost* process = site_instance->GetProcess(); 66 67 return process->GetID(); 68} 69 70// Finds a file browser handler that matches |action_id|. Returns NULL if not 71// found. 72const FileBrowserHandler* FindFileBrowserHandlerForActionId( 73 const Extension* extension, 74 const std::string& action_id) { 75 FileBrowserHandler::List* handler_list = 76 FileBrowserHandler::GetHandlers(extension); 77 for (FileBrowserHandler::List::const_iterator handler_iter = 78 handler_list->begin(); 79 handler_iter != handler_list->end(); 80 ++handler_iter) { 81 if (handler_iter->get()->id() == action_id) 82 return handler_iter->get(); 83 } 84 return NULL; 85} 86 87std::string EscapedUtf8ToLower(const std::string& str) { 88 base::string16 utf16 = base::UTF8ToUTF16( 89 net::UnescapeURLComponent(str, net::UnescapeRule::NORMAL)); 90 return net::EscapeUrlEncodedData( 91 base::UTF16ToUTF8(base::i18n::ToLower(utf16)), 92 false /* do not replace space with plus */); 93} 94 95// Finds file browser handlers that can handle the |selected_file_url|. 96FileBrowserHandlerList FindFileBrowserHandlersForURL( 97 Profile* profile, 98 const GURL& selected_file_url) { 99 ExtensionService* service = 100 extensions::ExtensionSystem::Get(profile)->extension_service(); 101 // In unit-tests, we may not have an ExtensionService. 102 if (!service) 103 return FileBrowserHandlerList(); 104 105 // We need case-insensitive matching, and pattern in the handler is already 106 // in lower case. 107 const GURL lowercase_url(EscapedUtf8ToLower(selected_file_url.spec())); 108 109 FileBrowserHandlerList results; 110 for (extensions::ExtensionSet::const_iterator iter = 111 service->extensions()->begin(); 112 iter != service->extensions()->end(); ++iter) { 113 const Extension* extension = iter->get(); 114 if (profile->IsOffTheRecord() && 115 !extensions::util::IsIncognitoEnabled(extension->id(), profile)) 116 continue; 117 118 FileBrowserHandler::List* handler_list = 119 FileBrowserHandler::GetHandlers(extension); 120 if (!handler_list) 121 continue; 122 for (FileBrowserHandler::List::const_iterator handler_iter = 123 handler_list->begin(); 124 handler_iter != handler_list->end(); 125 ++handler_iter) { 126 const FileBrowserHandler* handler = handler_iter->get(); 127 if (!handler->MatchesURL(lowercase_url)) 128 continue; 129 130 results.push_back(handler_iter->get()); 131 } 132 } 133 return results; 134} 135 136// This class is used to execute a file browser handler task. Here's how this 137// works: 138// 139// 1) Open the "external" file system 140// 2) Set up permissions for the target files on the external file system. 141// 3) Raise onExecute event with the action ID and entries of the target 142// files. The event will launch the file browser handler if not active. 143// 4) In the file browser handler, onExecute event is handled and executes the 144// task in JavaScript. 145// 146// That said, the class itself does not execute a task. The task will be 147// executed in JavaScript. 148class FileBrowserHandlerExecutor { 149 public: 150 FileBrowserHandlerExecutor(Profile* profile, 151 const Extension* extension, 152 const std::string& action_id); 153 154 // Executes the task for each file. |done| will be run with the result. 155 void Execute(const std::vector<FileSystemURL>& file_urls, 156 const file_tasks::FileTaskFinishedCallback& done); 157 158 private: 159 // This object is responsible to delete itself. 160 virtual ~FileBrowserHandlerExecutor(); 161 162 // Checks legitimacy of file url and grants file RO access permissions from 163 // handler (target) extension and its renderer process. 164 static scoped_ptr<FileDefinitionList> SetupFileAccessPermissions( 165 scoped_refptr<fileapi::FileSystemContext> file_system_context_handler, 166 const scoped_refptr<const Extension>& handler_extension, 167 const std::vector<FileSystemURL>& file_urls); 168 169 void ExecuteDoneOnUIThread(bool success); 170 void ExecuteAfterSetupFileAccess(scoped_ptr<FileDefinitionList> file_list); 171 void ExecuteFileActionsOnUIThread( 172 scoped_ptr<FileDefinitionList> file_definition_list, 173 scoped_ptr<EntryDefinitionList> entry_definition_list); 174 void SetupPermissionsAndDispatchEvent( 175 scoped_ptr<FileDefinitionList> file_definition_list, 176 scoped_ptr<EntryDefinitionList> entry_definition_list, 177 int handler_pid_in, 178 extensions::ExtensionHost* host); 179 180 // Registers file permissions from |handler_host_permissions_| with 181 // ChildProcessSecurityPolicy for process with id |handler_pid|. 182 void SetupHandlerHostFileAccessPermissions( 183 FileDefinitionList* file_definition_list, 184 const Extension* extension, 185 int handler_pid); 186 187 Profile* profile_; 188 scoped_refptr<const Extension> extension_; 189 const std::string action_id_; 190 file_tasks::FileTaskFinishedCallback done_; 191 base::WeakPtrFactory<FileBrowserHandlerExecutor> weak_ptr_factory_; 192 193 DISALLOW_COPY_AND_ASSIGN(FileBrowserHandlerExecutor); 194}; 195 196// static 197scoped_ptr<FileDefinitionList> 198FileBrowserHandlerExecutor::SetupFileAccessPermissions( 199 scoped_refptr<fileapi::FileSystemContext> file_system_context_handler, 200 const scoped_refptr<const Extension>& handler_extension, 201 const std::vector<FileSystemURL>& file_urls) { 202 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 203 DCHECK(handler_extension.get()); 204 205 fileapi::ExternalFileSystemBackend* backend = 206 file_system_context_handler->external_backend(); 207 208 scoped_ptr<FileDefinitionList> file_definition_list(new FileDefinitionList); 209 for (size_t i = 0; i < file_urls.size(); ++i) { 210 const FileSystemURL& url = file_urls[i]; 211 212 // Check if this file system entry exists first. 213 base::File::Info file_info; 214 215 base::FilePath local_path = url.path(); 216 base::FilePath virtual_path = url.virtual_path(); 217 218 bool is_drive_file = url.type() == fileapi::kFileSystemTypeDrive; 219 DCHECK(!is_drive_file || drive::util::IsUnderDriveMountPoint(local_path)); 220 221 // If the file is under drive mount point, there is no actual file to be 222 // found on the url.path(). 223 if (!is_drive_file) { 224 if (!base::PathExists(local_path) || 225 base::IsLink(local_path) || 226 !base::GetFileInfo(local_path, &file_info)) { 227 continue; 228 } 229 } 230 231 // Grant access to this particular file to target extension. This will 232 // ensure that the target extension can access only this FS entry and 233 // prevent from traversing FS hierarchy upward. 234 backend->GrantFileAccessToExtension(handler_extension->id(), virtual_path); 235 236 // Output values. 237 FileDefinition file_definition; 238 file_definition.virtual_path = virtual_path; 239 file_definition.is_directory = file_info.is_directory; 240 file_definition.absolute_path = local_path; 241 file_definition_list->push_back(file_definition); 242 } 243 244 return file_definition_list.Pass(); 245} 246 247FileBrowserHandlerExecutor::FileBrowserHandlerExecutor( 248 Profile* profile, 249 const Extension* extension, 250 const std::string& action_id) 251 : profile_(profile), 252 extension_(extension), 253 action_id_(action_id), 254 weak_ptr_factory_(this) { 255} 256 257FileBrowserHandlerExecutor::~FileBrowserHandlerExecutor() {} 258 259void FileBrowserHandlerExecutor::Execute( 260 const std::vector<FileSystemURL>& file_urls, 261 const file_tasks::FileTaskFinishedCallback& done) { 262 done_ = done; 263 264 // Get file system context for the extension to which onExecute event will be 265 // sent. The file access permissions will be granted to the extension in the 266 // file system context for the files in |file_urls|. 267 scoped_refptr<fileapi::FileSystemContext> file_system_context( 268 util::GetFileSystemContextForExtensionId( 269 profile_, extension_->id())); 270 271 BrowserThread::PostTaskAndReplyWithResult( 272 BrowserThread::FILE, 273 FROM_HERE, 274 base::Bind(&SetupFileAccessPermissions, 275 file_system_context, 276 extension_, 277 file_urls), 278 base::Bind(&FileBrowserHandlerExecutor::ExecuteAfterSetupFileAccess, 279 weak_ptr_factory_.GetWeakPtr())); 280} 281 282void FileBrowserHandlerExecutor::ExecuteAfterSetupFileAccess( 283 scoped_ptr<FileDefinitionList> file_definition_list) { 284 // Outlives the conversion process, since bound to the callback. 285 const FileDefinitionList& file_definition_list_ref = 286 *file_definition_list.get(); 287 file_manager::util::ConvertFileDefinitionListToEntryDefinitionList( 288 profile_, 289 extension_->id(), 290 file_definition_list_ref, 291 base::Bind(&FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread, 292 weak_ptr_factory_.GetWeakPtr(), 293 base::Passed(&file_definition_list))); 294} 295 296void FileBrowserHandlerExecutor::ExecuteDoneOnUIThread(bool success) { 297 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 298 if (!done_.is_null()) 299 done_.Run( 300 success 301 ? extensions::api::file_browser_private::TASK_RESULT_MESSAGE_SENT 302 : extensions::api::file_browser_private::TASK_RESULT_FAILED); 303 delete this; 304} 305 306void FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread( 307 scoped_ptr<FileDefinitionList> file_definition_list, 308 scoped_ptr<EntryDefinitionList> entry_definition_list) { 309 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 310 311 if (file_definition_list->empty() || entry_definition_list->empty()) { 312 ExecuteDoneOnUIThread(false); 313 return; 314 } 315 316 int handler_pid = ExtractProcessFromExtensionId(profile_, extension_->id()); 317 if (handler_pid <= 0 && 318 !extensions::BackgroundInfo::HasLazyBackgroundPage(extension_.get())) { 319 ExecuteDoneOnUIThread(false); 320 return; 321 } 322 323 if (handler_pid > 0) { 324 SetupPermissionsAndDispatchEvent(file_definition_list.Pass(), 325 entry_definition_list.Pass(), 326 handler_pid, 327 NULL); 328 } else { 329 // We have to wake the handler background page before we proceed. 330 extensions::LazyBackgroundTaskQueue* queue = 331 extensions::ExtensionSystem::Get(profile_)-> 332 lazy_background_task_queue(); 333 if (!queue->ShouldEnqueueTask(profile_, extension_.get())) { 334 ExecuteDoneOnUIThread(false); 335 return; 336 } 337 queue->AddPendingTask( 338 profile_, 339 extension_->id(), 340 base::Bind( 341 &FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent, 342 weak_ptr_factory_.GetWeakPtr(), 343 base::Passed(file_definition_list.Pass()), 344 base::Passed(entry_definition_list.Pass()), 345 handler_pid)); 346 } 347} 348 349void FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent( 350 scoped_ptr<FileDefinitionList> file_definition_list, 351 scoped_ptr<EntryDefinitionList> entry_definition_list, 352 int handler_pid_in, 353 extensions::ExtensionHost* host) { 354 int handler_pid = host ? host->render_process_host()->GetID() : 355 handler_pid_in; 356 357 if (handler_pid <= 0) { 358 ExecuteDoneOnUIThread(false); 359 return; 360 } 361 362 extensions::EventRouter* event_router = 363 extensions::ExtensionSystem::Get(profile_)->event_router(); 364 if (!event_router) { 365 ExecuteDoneOnUIThread(false); 366 return; 367 } 368 369 SetupHandlerHostFileAccessPermissions( 370 file_definition_list.get(), extension_.get(), handler_pid); 371 372 scoped_ptr<base::ListValue> event_args(new base::ListValue()); 373 event_args->Append(new base::StringValue(action_id_)); 374 base::DictionaryValue* details = new base::DictionaryValue(); 375 event_args->Append(details); 376 // Get file definitions. These will be replaced with Entry instances by 377 // dispatchEvent() method from event_binding.js. 378 base::ListValue* file_entries = new base::ListValue(); 379 details->Set("entries", file_entries); 380 381 for (EntryDefinitionList::const_iterator iter = 382 entry_definition_list->begin(); 383 iter != entry_definition_list->end(); 384 ++iter) { 385 base::DictionaryValue* file_def = new base::DictionaryValue(); 386 file_entries->Append(file_def); 387 file_def->SetString("fileSystemName", iter->file_system_name); 388 file_def->SetString("fileSystemRoot", iter->file_system_root_url); 389 file_def->SetString("fileFullPath", 390 "/" + iter->full_path.AsUTF8Unsafe()); 391 file_def->SetBoolean("fileIsDirectory", iter->is_directory); 392 } 393 394 scoped_ptr<extensions::Event> event(new extensions::Event( 395 "fileBrowserHandler.onExecute", event_args.Pass())); 396 event->restrict_to_browser_context = profile_; 397 event_router->DispatchEventToExtension(extension_->id(), event.Pass()); 398 399 ExecuteDoneOnUIThread(true); 400} 401 402void FileBrowserHandlerExecutor::SetupHandlerHostFileAccessPermissions( 403 FileDefinitionList* file_definition_list, 404 const Extension* extension, 405 int handler_pid) { 406 const FileBrowserHandler* action = FindFileBrowserHandlerForActionId( 407 extension_, action_id_); 408 for (FileDefinitionList::const_iterator iter = file_definition_list->begin(); 409 iter != file_definition_list->end(); 410 ++iter) { 411 if (!action) 412 continue; 413 if (action->CanRead()) { 414 content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile( 415 handler_pid, iter->absolute_path); 416 } 417 if (action->CanWrite()) { 418 content::ChildProcessSecurityPolicy::GetInstance()-> 419 GrantCreateReadWriteFile(handler_pid, iter->absolute_path); 420 } 421 } 422} 423 424// Returns true if |extension_id| and |action_id| indicate that the file 425// currently being handled should be opened with the browser. This function 426// is used to handle certain action IDs of the file manager. 427bool ShouldBeOpenedWithBrowser(const std::string& extension_id, 428 const std::string& action_id) { 429 430 return (extension_id == kFileManagerAppId && 431 (action_id == "view-pdf" || 432 action_id == "view-swf" || 433 action_id == "view-in-browser" || 434 action_id == "open-hosted-generic" || 435 action_id == "open-hosted-gdoc" || 436 action_id == "open-hosted-gsheet" || 437 action_id == "open-hosted-gslides")); 438} 439 440// Opens the files specified by |file_urls| with the browser for |profile|. 441// Returns true on success. It's a failure if no files are opened. 442bool OpenFilesWithBrowser(Profile* profile, 443 const std::vector<FileSystemURL>& file_urls) { 444 int num_opened = 0; 445 for (size_t i = 0; i < file_urls.size(); ++i) { 446 const FileSystemURL& file_url = file_urls[i]; 447 if (chromeos::FileSystemBackend::CanHandleURL(file_url)) { 448 const base::FilePath& file_path = file_url.path(); 449 num_opened += util::OpenFileWithBrowser(profile, file_path); 450 } 451 } 452 return num_opened > 0; 453} 454 455} // namespace 456 457bool ExecuteFileBrowserHandler( 458 Profile* profile, 459 const Extension* extension, 460 const std::string& action_id, 461 const std::vector<FileSystemURL>& file_urls, 462 const file_tasks::FileTaskFinishedCallback& done) { 463 // Forbid calling undeclared handlers. 464 if (!FindFileBrowserHandlerForActionId(extension, action_id)) 465 return false; 466 467 // Some action IDs of the file manager's file browser handlers require the 468 // files to be directly opened with the browser. 469 if (ShouldBeOpenedWithBrowser(extension->id(), action_id)) { 470 const bool result = OpenFilesWithBrowser(profile, file_urls); 471 if (!done.is_null()) { 472 done.Run(result 473 ? extensions::api::file_browser_private::TASK_RESULT_OPENED 474 : extensions::api::file_browser_private::TASK_RESULT_FAILED); 475 } 476 return result; 477 } 478 479 // The executor object will be self deleted on completion. 480 (new FileBrowserHandlerExecutor( 481 profile, extension, action_id))->Execute(file_urls, done); 482 return true; 483} 484 485bool IsFallbackFileBrowserHandler(const file_tasks::TaskDescriptor& task) { 486 return (task.task_type == file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER && 487 (task.app_id == kFileManagerAppId || 488 task.app_id == extension_misc::kQuickOfficeComponentExtensionId || 489 task.app_id == extension_misc::kQuickOfficeInternalExtensionId || 490 task.app_id == extension_misc::kQuickOfficeExtensionId)); 491} 492 493FileBrowserHandlerList FindFileBrowserHandlers( 494 Profile* profile, 495 const std::vector<GURL>& file_list) { 496 FileBrowserHandlerList common_handlers; 497 for (std::vector<GURL>::const_iterator it = file_list.begin(); 498 it != file_list.end(); ++it) { 499 FileBrowserHandlerList handlers = 500 FindFileBrowserHandlersForURL(profile, *it); 501 // If there is nothing to do for one file, the intersection of handlers 502 // for all files will be empty at the end, so no need to check further. 503 if (handlers.empty()) 504 return FileBrowserHandlerList(); 505 506 // For the very first file, just copy all the elements. 507 if (it == file_list.begin()) { 508 common_handlers = handlers; 509 } else { 510 // For all additional files, find intersection between the accumulated and 511 // file specific set. 512 FileBrowserHandlerList intersection; 513 std::set<const FileBrowserHandler*> common_handler_set( 514 common_handlers.begin(), common_handlers.end()); 515 516 for (FileBrowserHandlerList::const_iterator itr = handlers.begin(); 517 itr != handlers.end(); ++itr) { 518 if (ContainsKey(common_handler_set, *itr)) 519 intersection.push_back(*itr); 520 } 521 522 std::swap(common_handlers, intersection); 523 if (common_handlers.empty()) 524 return FileBrowserHandlerList(); 525 } 526 } 527 528 return common_handlers; 529} 530 531} // namespace file_browser_handlers 532} // namespace file_manager 533