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/extensions/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/extensions/file_manager/file_manager_util.h" 13 #include "chrome/browser/chromeos/extensions/file_manager/fileapi_util.h" 14 #include "chrome/browser/chromeos/fileapi/file_system_backend.h" 15 #include "chrome/browser/extensions/event_router.h" 16 #include "chrome/browser/extensions/extension_host.h" 17 #include "chrome/browser/extensions/extension_service.h" 18 #include "chrome/browser/extensions/extension_system.h" 19 #include "chrome/browser/extensions/lazy_background_task_queue.h" 20 #include "chrome/browser/profiles/profile.h" 21 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h" 22 #include "chrome/common/extensions/background_info.h" 23 #include "content/public/browser/browser_thread.h" 24 #include "content/public/browser/child_process_security_policy.h" 25 #include "content/public/browser/render_process_host.h" 26 #include "content/public/browser/site_instance.h" 27 #include "content/public/browser/web_contents.h" 28 #include "net/base/escape.h" 29 #include "webkit/browser/fileapi/file_system_context.h" 30 #include "webkit/browser/fileapi/file_system_url.h" 31 32 using content::BrowserThread; 33 using content::ChildProcessSecurityPolicy; 34 using content::SiteInstance; 35 using content::WebContents; 36 using extensions::Extension; 37 using fileapi::FileSystemURL; 38 39 namespace file_manager { 40 namespace file_browser_handlers { 41 namespace { 42 43 // Returns process id of the process the extension is running in. 44 int ExtractProcessFromExtensionId(Profile* profile, 45 const std::string& extension_id) { 46 GURL extension_url = 47 Extension::GetBaseURLFromExtensionId(extension_id); 48 ExtensionProcessManager* manager = 49 extensions::ExtensionSystem::Get(profile)->process_manager(); 50 51 SiteInstance* site_instance = manager->GetSiteInstanceForURL(extension_url); 52 if (!site_instance || !site_instance->HasProcess()) 53 return -1; 54 content::RenderProcessHost* process = site_instance->GetProcess(); 55 56 return process->GetID(); 57 } 58 59 // Finds a file browser handler that matches |action_id|. Returns NULL if not 60 // found. 61 const FileBrowserHandler* FindFileBrowserHandlerForActionId( 62 const Extension* extension, 63 const std::string& action_id) { 64 FileBrowserHandler::List* handler_list = 65 FileBrowserHandler::GetHandlers(extension); 66 for (FileBrowserHandler::List::const_iterator handler_iter = 67 handler_list->begin(); 68 handler_iter != handler_list->end(); 69 ++handler_iter) { 70 if (handler_iter->get()->id() == action_id) 71 return handler_iter->get(); 72 } 73 return NULL; 74 } 75 76 std::string EscapedUtf8ToLower(const std::string& str) { 77 string16 utf16 = UTF8ToUTF16( 78 net::UnescapeURLComponent(str, net::UnescapeRule::NORMAL)); 79 return net::EscapeUrlEncodedData( 80 UTF16ToUTF8(base::i18n::ToLower(utf16)), 81 false /* do not replace space with plus */); 82 } 83 84 // Finds file browser handlers that can handle the |selected_file_url|. 85 FileBrowserHandlerList FindFileBrowserHandlersForURL( 86 Profile* profile, 87 const GURL& selected_file_url) { 88 ExtensionService* service = 89 extensions::ExtensionSystem::Get(profile)->extension_service(); 90 // In unit-tests, we may not have an ExtensionService. 91 if (!service) 92 return FileBrowserHandlerList(); 93 94 // We need case-insensitive matching, and pattern in the handler is already 95 // in lower case. 96 const GURL lowercase_url(EscapedUtf8ToLower(selected_file_url.spec())); 97 98 FileBrowserHandlerList results; 99 for (ExtensionSet::const_iterator iter = service->extensions()->begin(); 100 iter != service->extensions()->end(); 101 ++iter) { 102 const Extension* extension = iter->get(); 103 if (profile->IsOffTheRecord() && 104 !service->IsIncognitoEnabled(extension->id())) 105 continue; 106 107 FileBrowserHandler::List* handler_list = 108 FileBrowserHandler::GetHandlers(extension); 109 if (!handler_list) 110 continue; 111 for (FileBrowserHandler::List::const_iterator handler_iter = 112 handler_list->begin(); 113 handler_iter != handler_list->end(); 114 ++handler_iter) { 115 const FileBrowserHandler* handler = handler_iter->get(); 116 if (!handler->MatchesURL(lowercase_url)) 117 continue; 118 119 results.push_back(handler_iter->get()); 120 } 121 } 122 return results; 123 } 124 125 // Finds a file browser handler that matches |extension_id| and |action_id| 126 // from |handler_list|. Returns a mutable iterator to the handler if 127 // found. Returns handler_list->end() if not found. 128 FileBrowserHandlerList::iterator 129 FindFileBrowserHandlerForExtensionIdAndActionId( 130 FileBrowserHandlerList* handler_list, 131 const std::string& extension_id, 132 const std::string& action_id) { 133 DCHECK(handler_list); 134 135 FileBrowserHandlerList::iterator iter = handler_list->begin(); 136 while (iter != handler_list->end() && 137 !((*iter)->extension_id() == extension_id && 138 (*iter)->id() == action_id)) { 139 ++iter; 140 } 141 return iter; 142 } 143 144 // This class is used to execute a file browser handler task. Here's how this 145 // works: 146 // 147 // 1) Open the "external" file system 148 // 2) Set up permissions for the target files on the external file system. 149 // 3) Raise onExecute event with the action ID and entries of the target 150 // files. The event will launch the file browser handler if not active. 151 // 4) In the file browser handler, onExecute event is handled and executes the 152 // task in JavaScript. 153 // 154 // That said, the class itself does not execute a task. The task will be 155 // executed in JavaScript. 156 class FileBrowserHandlerExecutor { 157 public: 158 FileBrowserHandlerExecutor(Profile* profile, 159 const Extension* extension, 160 int32 tab_id, 161 const std::string& action_id); 162 163 // Executes the task for each file. |done| will be run with the result. 164 void Execute(const std::vector<FileSystemURL>& file_urls, 165 const file_tasks::FileTaskFinishedCallback& done); 166 167 private: 168 // This object is responsible to delete itself. 169 virtual ~FileBrowserHandlerExecutor(); 170 171 struct FileDefinition { 172 FileDefinition(); 173 ~FileDefinition(); 174 175 base::FilePath virtual_path; 176 base::FilePath absolute_path; 177 bool is_directory; 178 }; 179 180 typedef std::vector<FileDefinition> FileDefinitionList; 181 182 // Checks legitimacy of file url and grants file RO access permissions from 183 // handler (target) extension and its renderer process. 184 static FileDefinitionList SetupFileAccessPermissions( 185 scoped_refptr<fileapi::FileSystemContext> file_system_context_handler, 186 const scoped_refptr<const Extension>& handler_extension, 187 const std::vector<FileSystemURL>& file_urls); 188 189 void DidOpenFileSystem(const std::vector<FileSystemURL>& file_urls, 190 base::PlatformFileError result, 191 const std::string& file_system_name, 192 const GURL& file_system_root); 193 194 void ExecuteDoneOnUIThread(bool success); 195 void ExecuteFileActionsOnUIThread(const std::string& file_system_name, 196 const GURL& file_system_root, 197 const FileDefinitionList& file_list); 198 void SetupPermissionsAndDispatchEvent(const std::string& file_system_name, 199 const GURL& file_system_root, 200 const FileDefinitionList& file_list, 201 int handler_pid_in, 202 extensions::ExtensionHost* host); 203 204 // Registers file permissions from |handler_host_permissions_| with 205 // ChildProcessSecurityPolicy for process with id |handler_pid|. 206 void SetupHandlerHostFileAccessPermissions( 207 const FileDefinitionList& file_list, 208 const Extension* extension, 209 int handler_pid); 210 211 Profile* profile_; 212 scoped_refptr<const Extension> extension_; 213 int32 tab_id_; 214 const std::string action_id_; 215 file_tasks::FileTaskFinishedCallback done_; 216 base::WeakPtrFactory<FileBrowserHandlerExecutor> weak_ptr_factory_; 217 218 DISALLOW_COPY_AND_ASSIGN(FileBrowserHandlerExecutor); 219 }; 220 221 FileBrowserHandlerExecutor::FileDefinition::FileDefinition() 222 : is_directory(false) { 223 } 224 225 FileBrowserHandlerExecutor::FileDefinition::~FileDefinition() { 226 } 227 228 // static 229 FileBrowserHandlerExecutor::FileDefinitionList 230 FileBrowserHandlerExecutor::SetupFileAccessPermissions( 231 scoped_refptr<fileapi::FileSystemContext> file_system_context_handler, 232 const scoped_refptr<const Extension>& handler_extension, 233 const std::vector<FileSystemURL>& file_urls) { 234 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 235 DCHECK(handler_extension.get()); 236 237 fileapi::ExternalFileSystemBackend* backend = 238 file_system_context_handler->external_backend(); 239 240 FileDefinitionList file_list; 241 for (size_t i = 0; i < file_urls.size(); ++i) { 242 const FileSystemURL& url = file_urls[i]; 243 244 // Check if this file system entry exists first. 245 base::PlatformFileInfo file_info; 246 247 base::FilePath local_path = url.path(); 248 base::FilePath virtual_path = url.virtual_path(); 249 250 bool is_drive_file = url.type() == fileapi::kFileSystemTypeDrive; 251 DCHECK(!is_drive_file || drive::util::IsUnderDriveMountPoint(local_path)); 252 253 // If the file is under drive mount point, there is no actual file to be 254 // found on the url.path(). 255 if (!is_drive_file) { 256 if (!base::PathExists(local_path) || 257 file_util::IsLink(local_path) || 258 !file_util::GetFileInfo(local_path, &file_info)) { 259 continue; 260 } 261 } 262 263 // Grant access to this particular file to target extension. This will 264 // ensure that the target extension can access only this FS entry and 265 // prevent from traversing FS hierarchy upward. 266 backend->GrantFileAccessToExtension( 267 handler_extension->id(), virtual_path); 268 269 // Output values. 270 FileDefinition file; 271 file.virtual_path = virtual_path; 272 file.is_directory = file_info.is_directory; 273 file.absolute_path = local_path; 274 file_list.push_back(file); 275 } 276 return file_list; 277 } 278 279 FileBrowserHandlerExecutor::FileBrowserHandlerExecutor( 280 Profile* profile, 281 const Extension* extension, 282 int32 tab_id, 283 const std::string& action_id) 284 : profile_(profile), 285 extension_(extension), 286 tab_id_(tab_id), 287 action_id_(action_id), 288 weak_ptr_factory_(this) { 289 } 290 291 FileBrowserHandlerExecutor::~FileBrowserHandlerExecutor() {} 292 293 void FileBrowserHandlerExecutor::Execute( 294 const std::vector<FileSystemURL>& file_urls, 295 const file_tasks::FileTaskFinishedCallback& done) { 296 done_ = done; 297 298 // Get file system context for the extension to which onExecute event will be 299 // sent. The file access permissions will be granted to the extension in the 300 // file system context for the files in |file_urls|. 301 fileapi_util::GetFileSystemContextForExtensionId( 302 profile_, extension_->id())->OpenFileSystem( 303 Extension::GetBaseURLFromExtensionId(extension_->id()).GetOrigin(), 304 fileapi::kFileSystemTypeExternal, 305 fileapi::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT, 306 base::Bind(&FileBrowserHandlerExecutor::DidOpenFileSystem, 307 weak_ptr_factory_.GetWeakPtr(), 308 file_urls)); 309 } 310 311 void FileBrowserHandlerExecutor::DidOpenFileSystem( 312 const std::vector<FileSystemURL>& file_urls, 313 base::PlatformFileError result, 314 const std::string& file_system_name, 315 const GURL& file_system_root) { 316 if (result != base::PLATFORM_FILE_OK) { 317 ExecuteDoneOnUIThread(false); 318 return; 319 } 320 321 scoped_refptr<fileapi::FileSystemContext> file_system_context( 322 fileapi_util::GetFileSystemContextForExtensionId( 323 profile_, extension_->id())); 324 BrowserThread::PostTaskAndReplyWithResult( 325 BrowserThread::FILE, 326 FROM_HERE, 327 base::Bind(&SetupFileAccessPermissions, 328 file_system_context, 329 extension_, 330 file_urls), 331 base::Bind(&FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread, 332 weak_ptr_factory_.GetWeakPtr(), 333 file_system_name, 334 file_system_root)); 335 } 336 337 void FileBrowserHandlerExecutor::ExecuteDoneOnUIThread(bool success) { 338 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 339 if (!done_.is_null()) 340 done_.Run(success); 341 delete this; 342 } 343 344 void FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread( 345 const std::string& file_system_name, 346 const GURL& file_system_root, 347 const FileDefinitionList& file_list) { 348 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 349 350 if (file_list.empty()) { 351 ExecuteDoneOnUIThread(false); 352 return; 353 } 354 355 int handler_pid = ExtractProcessFromExtensionId(profile_, extension_->id()); 356 if (handler_pid <= 0 && 357 !extensions::BackgroundInfo::HasLazyBackgroundPage(extension_.get())) { 358 ExecuteDoneOnUIThread(false); 359 return; 360 } 361 362 if (handler_pid > 0) { 363 SetupPermissionsAndDispatchEvent(file_system_name, file_system_root, 364 file_list, handler_pid, NULL); 365 } else { 366 // We have to wake the handler background page before we proceed. 367 extensions::LazyBackgroundTaskQueue* queue = 368 extensions::ExtensionSystem::Get(profile_)-> 369 lazy_background_task_queue(); 370 if (!queue->ShouldEnqueueTask(profile_, extension_.get())) { 371 ExecuteDoneOnUIThread(false); 372 return; 373 } 374 queue->AddPendingTask( 375 profile_, extension_->id(), 376 base::Bind( 377 &FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent, 378 weak_ptr_factory_.GetWeakPtr(), 379 file_system_name, 380 file_system_root, 381 file_list, 382 handler_pid)); 383 } 384 } 385 386 void FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent( 387 const std::string& file_system_name, 388 const GURL& file_system_root, 389 const FileDefinitionList& file_list, 390 int handler_pid_in, 391 extensions::ExtensionHost* host) { 392 int handler_pid = host ? host->render_process_host()->GetID() : 393 handler_pid_in; 394 395 if (handler_pid <= 0) { 396 ExecuteDoneOnUIThread(false); 397 return; 398 } 399 400 extensions::EventRouter* event_router = 401 extensions::ExtensionSystem::Get(profile_)->event_router(); 402 if (!event_router) { 403 ExecuteDoneOnUIThread(false); 404 return; 405 } 406 407 SetupHandlerHostFileAccessPermissions( 408 file_list, extension_.get(), handler_pid); 409 410 scoped_ptr<ListValue> event_args(new ListValue()); 411 event_args->Append(new base::StringValue(action_id_)); 412 DictionaryValue* details = new DictionaryValue(); 413 event_args->Append(details); 414 // Get file definitions. These will be replaced with Entry instances by 415 // dispatchEvent() method from event_binding.js. 416 ListValue* file_entries = new ListValue(); 417 details->Set("entries", file_entries); 418 for (FileDefinitionList::const_iterator iter = file_list.begin(); 419 iter != file_list.end(); 420 ++iter) { 421 DictionaryValue* file_def = new DictionaryValue(); 422 file_entries->Append(file_def); 423 file_def->SetString("fileSystemName", file_system_name); 424 file_def->SetString("fileSystemRoot", file_system_root.spec()); 425 base::FilePath root(FILE_PATH_LITERAL("/")); 426 base::FilePath full_path = root.Append(iter->virtual_path); 427 file_def->SetString("fileFullPath", full_path.value()); 428 file_def->SetBoolean("fileIsDirectory", iter->is_directory); 429 } 430 431 details->SetInteger("tab_id", tab_id_); 432 433 scoped_ptr<extensions::Event> event(new extensions::Event( 434 "fileBrowserHandler.onExecute", event_args.Pass())); 435 event->restrict_to_profile = profile_; 436 event_router->DispatchEventToExtension(extension_->id(), event.Pass()); 437 438 ExecuteDoneOnUIThread(true); 439 } 440 441 void FileBrowserHandlerExecutor::SetupHandlerHostFileAccessPermissions( 442 const FileDefinitionList& file_list, 443 const Extension* extension, 444 int handler_pid) { 445 const FileBrowserHandler* action = FindFileBrowserHandlerForActionId( 446 extension_, action_id_); 447 for (FileDefinitionList::const_iterator iter = file_list.begin(); 448 iter != file_list.end(); 449 ++iter) { 450 if (!action) 451 continue; 452 if (action->CanRead()) { 453 content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile( 454 handler_pid, iter->absolute_path); 455 } 456 if (action->CanWrite()) { 457 content::ChildProcessSecurityPolicy::GetInstance()-> 458 GrantCreateReadWriteFile(handler_pid, iter->absolute_path); 459 } 460 } 461 } 462 463 } // namespace 464 465 bool ExecuteFileBrowserHandler( 466 Profile* profile, 467 const Extension* extension, 468 int32 tab_id, 469 const std::string& action_id, 470 const std::vector<FileSystemURL>& file_urls, 471 const file_tasks::FileTaskFinishedCallback& done) { 472 // Forbid calling undeclared handlers. 473 if (!FindFileBrowserHandlerForActionId(extension, action_id)) 474 return false; 475 476 // The executor object will be self deleted on completion. 477 (new FileBrowserHandlerExecutor( 478 profile, extension, tab_id, action_id))->Execute(file_urls, done); 479 return true; 480 } 481 482 bool IsFallbackFileBrowserHandler(const FileBrowserHandler* handler) { 483 const std::string& extension_id = handler->extension_id(); 484 return (extension_id == kFileBrowserDomain || 485 extension_id == extension_misc::kQuickOfficeComponentExtensionId || 486 extension_id == extension_misc::kQuickOfficeDevExtensionId || 487 extension_id == extension_misc::kQuickOfficeExtensionId); 488 } 489 490 FileBrowserHandlerList FindDefaultFileBrowserHandlers( 491 Profile* profile, 492 const std::vector<base::FilePath>& file_list, 493 const FileBrowserHandlerList& common_handlers) { 494 FileBrowserHandlerList default_handlers; 495 496 std::set<std::string> default_ids; 497 for (std::vector<base::FilePath>::const_iterator it = file_list.begin(); 498 it != file_list.end(); ++it) { 499 std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs( 500 profile, "", it->Extension()); 501 if (!task_id.empty()) 502 default_ids.insert(task_id); 503 } 504 505 const FileBrowserHandler* fallback_handler = NULL; 506 // Convert the default task IDs collected above to one of the handler pointers 507 // from common_handlers. 508 for (size_t i = 0; i < common_handlers.size(); ++i) { 509 const FileBrowserHandler* handler = common_handlers[i]; 510 std::string task_id = file_tasks::MakeTaskID( 511 handler->extension_id(), 512 file_tasks::kFileBrowserHandlerTaskType, 513 handler->id()); 514 std::set<std::string>::iterator default_iter = default_ids.find(task_id); 515 if (default_iter != default_ids.end()) { 516 default_handlers.push_back(handler); 517 continue; 518 } 519 520 // Remember the first fallback handler. 521 if (!fallback_handler && IsFallbackFileBrowserHandler(handler)) 522 fallback_handler = handler; 523 } 524 525 // If there are no default handlers found, use fallback as default. 526 if (fallback_handler && default_handlers.empty()) 527 default_handlers.push_back(fallback_handler); 528 529 return default_handlers; 530 } 531 532 FileBrowserHandlerList FindCommonFileBrowserHandlers( 533 Profile* profile, 534 const std::vector<GURL>& file_list) { 535 FileBrowserHandlerList common_handlers; 536 for (std::vector<GURL>::const_iterator it = file_list.begin(); 537 it != file_list.end(); ++it) { 538 FileBrowserHandlerList handlers = 539 FindFileBrowserHandlersForURL(profile, *it); 540 // If there is nothing to do for one file, the intersection of handlers 541 // for all files will be empty at the end, so no need to check further. 542 if (handlers.empty()) 543 return FileBrowserHandlerList(); 544 545 // For the very first file, just copy all the elements. 546 if (it == file_list.begin()) { 547 common_handlers = handlers; 548 } else { 549 // For all additional files, find intersection between the accumulated and 550 // file specific set. 551 FileBrowserHandlerList intersection; 552 std::set_intersection(common_handlers.begin(), common_handlers.end(), 553 handlers.begin(), handlers.end(), 554 std::back_inserter(intersection)); 555 common_handlers = intersection; 556 if (common_handlers.empty()) 557 return FileBrowserHandlerList(); 558 } 559 } 560 561 FileBrowserHandlerList::iterator watch_iter = 562 FindFileBrowserHandlerForExtensionIdAndActionId( 563 &common_handlers, kFileBrowserDomain, kFileBrowserWatchTaskId); 564 FileBrowserHandlerList::iterator gallery_iter = 565 FindFileBrowserHandlerForExtensionIdAndActionId( 566 &common_handlers, kFileBrowserDomain, kFileBrowserGalleryTaskId); 567 if (watch_iter != common_handlers.end() && 568 gallery_iter != common_handlers.end()) { 569 // Both "watch" and "gallery" actions are applicable which means that the 570 // selection is all videos. Showing them both is confusing, so we only keep 571 // the one that makes more sense ("watch" for single selection, "gallery" 572 // for multiple selection). 573 if (file_list.size() == 1) 574 common_handlers.erase(gallery_iter); 575 else 576 common_handlers.erase(watch_iter); 577 } 578 579 return common_handlers; 580 } 581 582 const FileBrowserHandler* FindFileBrowserHandlerForURLAndPath( 583 Profile* profile, 584 const GURL& url, 585 const base::FilePath& file_path) { 586 std::vector<GURL> file_urls; 587 file_urls.push_back(url); 588 589 FileBrowserHandlerList common_handlers = 590 FindCommonFileBrowserHandlers(profile, file_urls); 591 if (common_handlers.empty()) 592 return NULL; 593 594 std::vector<base::FilePath> file_paths; 595 file_paths.push_back(file_path); 596 597 FileBrowserHandlerList default_handlers = 598 FindDefaultFileBrowserHandlers(profile, file_paths, common_handlers); 599 600 // If there's none, or more than one, then we don't have a canonical default. 601 if (!default_handlers.empty()) { 602 // There should not be multiple default handlers for a single URL. 603 DCHECK_EQ(1u, default_handlers.size()); 604 605 return *default_handlers.begin(); 606 } 607 608 // If there are no default handlers, use first handler in the list (file 609 // manager does the same in this situation). TODO(tbarzic): This is not so 610 // optimal behaviour. 611 return *common_handlers.begin(); 612 } 613 614 } // namespace file_browser_handlers 615 } // namespace file_manager 616