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