file_browser_handlers.cc revision f2477e01787aa58f445919b809d89e252beef54f
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 37using content::BrowserThread; 38using content::ChildProcessSecurityPolicy; 39using content::SiteInstance; 40using content::WebContents; 41using extensions::Extension; 42using fileapi::FileSystemURL; 43 44namespace file_manager { 45namespace file_browser_handlers { 46namespace { 47 48// Returns process id of the process the extension is running in. 49int 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. 66const 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 81std::string EscapedUtf8ToLower(const std::string& str) { 82 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|. 90FileBrowserHandlerList 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. 133FileBrowserHandlerList::iterator 134FindFileBrowserHandlerForExtensionIdAndActionId( 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. 161class FileBrowserHandlerExecutor { 162 public: 163 FileBrowserHandlerExecutor(Profile* profile, 164 const Extension* extension, 165 int32 tab_id, 166 const std::string& action_id); 167 168 // Executes the task for each file. |done| will be run with the result. 169 void Execute(const std::vector<FileSystemURL>& file_urls, 170 const file_tasks::FileTaskFinishedCallback& done); 171 172 private: 173 // This object is responsible to delete itself. 174 virtual ~FileBrowserHandlerExecutor(); 175 176 struct FileDefinition { 177 FileDefinition(); 178 ~FileDefinition(); 179 180 base::FilePath virtual_path; 181 base::FilePath absolute_path; 182 bool is_directory; 183 }; 184 185 typedef std::vector<FileDefinition> FileDefinitionList; 186 187 // Checks legitimacy of file url and grants file RO access permissions from 188 // handler (target) extension and its renderer process. 189 static FileDefinitionList SetupFileAccessPermissions( 190 scoped_refptr<fileapi::FileSystemContext> file_system_context_handler, 191 const scoped_refptr<const Extension>& handler_extension, 192 const std::vector<FileSystemURL>& file_urls); 193 194 void ExecuteDoneOnUIThread(bool success); 195 void ExecuteFileActionsOnUIThread(const FileDefinitionList& file_list); 196 void SetupPermissionsAndDispatchEvent(const std::string& file_system_name, 197 const GURL& file_system_root, 198 const FileDefinitionList& file_list, 199 int handler_pid_in, 200 extensions::ExtensionHost* host); 201 202 // Registers file permissions from |handler_host_permissions_| with 203 // ChildProcessSecurityPolicy for process with id |handler_pid|. 204 void SetupHandlerHostFileAccessPermissions( 205 const FileDefinitionList& file_list, 206 const Extension* extension, 207 int handler_pid); 208 209 Profile* profile_; 210 scoped_refptr<const Extension> extension_; 211 int32 tab_id_; 212 const std::string action_id_; 213 file_tasks::FileTaskFinishedCallback done_; 214 base::WeakPtrFactory<FileBrowserHandlerExecutor> weak_ptr_factory_; 215 216 DISALLOW_COPY_AND_ASSIGN(FileBrowserHandlerExecutor); 217}; 218 219FileBrowserHandlerExecutor::FileDefinition::FileDefinition() 220 : is_directory(false) { 221} 222 223FileBrowserHandlerExecutor::FileDefinition::~FileDefinition() { 224} 225 226// static 227FileBrowserHandlerExecutor::FileDefinitionList 228FileBrowserHandlerExecutor::SetupFileAccessPermissions( 229 scoped_refptr<fileapi::FileSystemContext> file_system_context_handler, 230 const scoped_refptr<const Extension>& handler_extension, 231 const std::vector<FileSystemURL>& file_urls) { 232 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 233 DCHECK(handler_extension.get()); 234 235 fileapi::ExternalFileSystemBackend* backend = 236 file_system_context_handler->external_backend(); 237 238 FileDefinitionList file_list; 239 for (size_t i = 0; i < file_urls.size(); ++i) { 240 const FileSystemURL& url = file_urls[i]; 241 242 // Check if this file system entry exists first. 243 base::PlatformFileInfo file_info; 244 245 base::FilePath local_path = url.path(); 246 base::FilePath virtual_path = url.virtual_path(); 247 248 bool is_drive_file = url.type() == fileapi::kFileSystemTypeDrive; 249 DCHECK(!is_drive_file || drive::util::IsUnderDriveMountPoint(local_path)); 250 251 // If the file is under drive mount point, there is no actual file to be 252 // found on the url.path(). 253 if (!is_drive_file) { 254 if (!base::PathExists(local_path) || 255 file_util::IsLink(local_path) || 256 !file_util::GetFileInfo(local_path, &file_info)) { 257 continue; 258 } 259 } 260 261 // Grant access to this particular file to target extension. This will 262 // ensure that the target extension can access only this FS entry and 263 // prevent from traversing FS hierarchy upward. 264 backend->GrantFileAccessToExtension( 265 handler_extension->id(), virtual_path); 266 267 // Output values. 268 FileDefinition file; 269 file.virtual_path = virtual_path; 270 file.is_directory = file_info.is_directory; 271 file.absolute_path = local_path; 272 file_list.push_back(file); 273 } 274 return file_list; 275} 276 277FileBrowserHandlerExecutor::FileBrowserHandlerExecutor( 278 Profile* profile, 279 const Extension* extension, 280 int32 tab_id, 281 const std::string& action_id) 282 : profile_(profile), 283 extension_(extension), 284 tab_id_(tab_id), 285 action_id_(action_id), 286 weak_ptr_factory_(this) { 287} 288 289FileBrowserHandlerExecutor::~FileBrowserHandlerExecutor() {} 290 291void FileBrowserHandlerExecutor::Execute( 292 const std::vector<FileSystemURL>& file_urls, 293 const file_tasks::FileTaskFinishedCallback& done) { 294 done_ = done; 295 296 // Get file system context for the extension to which onExecute event will be 297 // sent. The file access permissions will be granted to the extension in the 298 // file system context for the files in |file_urls|. 299 scoped_refptr<fileapi::FileSystemContext> file_system_context( 300 util::GetFileSystemContextForExtensionId( 301 profile_, extension_->id())); 302 303 BrowserThread::PostTaskAndReplyWithResult( 304 BrowserThread::FILE, 305 FROM_HERE, 306 base::Bind(&SetupFileAccessPermissions, 307 file_system_context, 308 extension_, 309 file_urls), 310 base::Bind(&FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread, 311 weak_ptr_factory_.GetWeakPtr())); 312} 313 314void FileBrowserHandlerExecutor::ExecuteDoneOnUIThread(bool success) { 315 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 316 if (!done_.is_null()) 317 done_.Run(success); 318 delete this; 319} 320 321void FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread( 322 const FileDefinitionList& file_list) { 323 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 324 325 if (file_list.empty()) { 326 ExecuteDoneOnUIThread(false); 327 return; 328 } 329 330 int handler_pid = ExtractProcessFromExtensionId(profile_, extension_->id()); 331 if (handler_pid <= 0 && 332 !extensions::BackgroundInfo::HasLazyBackgroundPage(extension_.get())) { 333 ExecuteDoneOnUIThread(false); 334 return; 335 } 336 337 fileapi::FileSystemInfo info = 338 fileapi::GetFileSystemInfoForChromeOS( 339 Extension::GetBaseURLFromExtensionId(extension_->id()).GetOrigin()); 340 341 if (handler_pid > 0) { 342 SetupPermissionsAndDispatchEvent(info.name, info.root_url, 343 file_list, handler_pid, NULL); 344 } else { 345 // We have to wake the handler background page before we proceed. 346 extensions::LazyBackgroundTaskQueue* queue = 347 extensions::ExtensionSystem::Get(profile_)-> 348 lazy_background_task_queue(); 349 if (!queue->ShouldEnqueueTask(profile_, extension_.get())) { 350 ExecuteDoneOnUIThread(false); 351 return; 352 } 353 queue->AddPendingTask( 354 profile_, extension_->id(), 355 base::Bind( 356 &FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent, 357 weak_ptr_factory_.GetWeakPtr(), 358 info.name, info.root_url, file_list, handler_pid)); 359 } 360} 361 362void FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent( 363 const std::string& file_system_name, 364 const GURL& file_system_root, 365 const FileDefinitionList& file_list, 366 int handler_pid_in, 367 extensions::ExtensionHost* host) { 368 int handler_pid = host ? host->render_process_host()->GetID() : 369 handler_pid_in; 370 371 if (handler_pid <= 0) { 372 ExecuteDoneOnUIThread(false); 373 return; 374 } 375 376 extensions::EventRouter* event_router = 377 extensions::ExtensionSystem::Get(profile_)->event_router(); 378 if (!event_router) { 379 ExecuteDoneOnUIThread(false); 380 return; 381 } 382 383 SetupHandlerHostFileAccessPermissions( 384 file_list, extension_.get(), handler_pid); 385 386 scoped_ptr<ListValue> event_args(new ListValue()); 387 event_args->Append(new base::StringValue(action_id_)); 388 DictionaryValue* details = new DictionaryValue(); 389 event_args->Append(details); 390 // Get file definitions. These will be replaced with Entry instances by 391 // dispatchEvent() method from event_binding.js. 392 ListValue* file_entries = new ListValue(); 393 details->Set("entries", file_entries); 394 for (FileDefinitionList::const_iterator iter = file_list.begin(); 395 iter != file_list.end(); 396 ++iter) { 397 DictionaryValue* file_def = new DictionaryValue(); 398 file_entries->Append(file_def); 399 file_def->SetString("fileSystemName", file_system_name); 400 file_def->SetString("fileSystemRoot", file_system_root.spec()); 401 base::FilePath root(FILE_PATH_LITERAL("/")); 402 base::FilePath full_path = root.Append(iter->virtual_path); 403 file_def->SetString("fileFullPath", full_path.value()); 404 file_def->SetBoolean("fileIsDirectory", iter->is_directory); 405 } 406 407 details->SetInteger("tab_id", tab_id_); 408 409 scoped_ptr<extensions::Event> event(new extensions::Event( 410 "fileBrowserHandler.onExecute", event_args.Pass())); 411 event->restrict_to_browser_context = profile_; 412 event_router->DispatchEventToExtension(extension_->id(), event.Pass()); 413 414 ExecuteDoneOnUIThread(true); 415} 416 417void FileBrowserHandlerExecutor::SetupHandlerHostFileAccessPermissions( 418 const FileDefinitionList& file_list, 419 const Extension* extension, 420 int handler_pid) { 421 const FileBrowserHandler* action = FindFileBrowserHandlerForActionId( 422 extension_, action_id_); 423 for (FileDefinitionList::const_iterator iter = file_list.begin(); 424 iter != file_list.end(); 425 ++iter) { 426 if (!action) 427 continue; 428 if (action->CanRead()) { 429 content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile( 430 handler_pid, iter->absolute_path); 431 } 432 if (action->CanWrite()) { 433 content::ChildProcessSecurityPolicy::GetInstance()-> 434 GrantCreateReadWriteFile(handler_pid, iter->absolute_path); 435 } 436 } 437} 438 439// Returns true if |extension_id| and |action_id| indicate that the file 440// currently being handled should be opened with the browser. This function 441// is used to handle certain action IDs of the file manager. 442bool ShouldBeOpenedWithBrowser(const std::string& extension_id, 443 const std::string& action_id) { 444 445 return (extension_id == kFileManagerAppId && 446 (action_id == "view-pdf" || 447 action_id == "view-swf" || 448 action_id == "view-in-browser" || 449 action_id == "open-hosted-generic" || 450 action_id == "open-hosted-gdoc" || 451 action_id == "open-hosted-gsheet" || 452 action_id == "open-hosted-gslides")); 453} 454 455// Opens the files specified by |file_urls| with the browser for |profile|. 456// Returns true on success. It's a failure if no files are opened. 457bool OpenFilesWithBrowser(Profile* profile, 458 const std::vector<FileSystemURL>& file_urls) { 459 int num_opened = 0; 460 for (size_t i = 0; i < file_urls.size(); ++i) { 461 const FileSystemURL& file_url = file_urls[i]; 462 if (chromeos::FileSystemBackend::CanHandleURL(file_url)) { 463 const base::FilePath& file_path = file_url.path(); 464 num_opened += util::OpenFileWithBrowser(profile, file_path); 465 } 466 } 467 return num_opened > 0; 468} 469 470} // namespace 471 472bool ExecuteFileBrowserHandler( 473 Profile* profile, 474 const Extension* extension, 475 int32 tab_id, 476 const std::string& action_id, 477 const std::vector<FileSystemURL>& file_urls, 478 const file_tasks::FileTaskFinishedCallback& done) { 479 // Forbid calling undeclared handlers. 480 if (!FindFileBrowserHandlerForActionId(extension, action_id)) 481 return false; 482 483 // Some action IDs of the file manager's file browser handlers require the 484 // files to be directly opened with the browser. 485 if (ShouldBeOpenedWithBrowser(extension->id(), action_id)) { 486 return OpenFilesWithBrowser(profile, file_urls); 487 } 488 489 // The executor object will be self deleted on completion. 490 (new FileBrowserHandlerExecutor( 491 profile, extension, tab_id, action_id))->Execute(file_urls, done); 492 return true; 493} 494 495bool IsFallbackFileBrowserHandler(const file_tasks::TaskDescriptor& task) { 496 return (task.task_type == file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER && 497 (task.app_id == kFileManagerAppId || 498 task.app_id == extension_misc::kQuickOfficeComponentExtensionId || 499 task.app_id == extension_misc::kQuickOfficeDevExtensionId || 500 task.app_id == extension_misc::kQuickOfficeExtensionId)); 501} 502 503FileBrowserHandlerList FindFileBrowserHandlers( 504 Profile* profile, 505 const std::vector<GURL>& file_list) { 506 FileBrowserHandlerList common_handlers; 507 for (std::vector<GURL>::const_iterator it = file_list.begin(); 508 it != file_list.end(); ++it) { 509 FileBrowserHandlerList handlers = 510 FindFileBrowserHandlersForURL(profile, *it); 511 // If there is nothing to do for one file, the intersection of handlers 512 // for all files will be empty at the end, so no need to check further. 513 if (handlers.empty()) 514 return FileBrowserHandlerList(); 515 516 // For the very first file, just copy all the elements. 517 if (it == file_list.begin()) { 518 common_handlers = handlers; 519 } else { 520 // For all additional files, find intersection between the accumulated and 521 // file specific set. 522 FileBrowserHandlerList intersection; 523 std::set_intersection(common_handlers.begin(), common_handlers.end(), 524 handlers.begin(), handlers.end(), 525 std::back_inserter(intersection)); 526 common_handlers = intersection; 527 if (common_handlers.empty()) 528 return FileBrowserHandlerList(); 529 } 530 } 531 532 // "watch" and "gallery" are defined in the file manager's manifest.json. 533 FileBrowserHandlerList::iterator watch_iter = 534 FindFileBrowserHandlerForExtensionIdAndActionId( 535 &common_handlers, kFileManagerAppId, "watch"); 536 FileBrowserHandlerList::iterator gallery_iter = 537 FindFileBrowserHandlerForExtensionIdAndActionId( 538 &common_handlers, kFileManagerAppId, "gallery"); 539 if (watch_iter != common_handlers.end() && 540 gallery_iter != common_handlers.end()) { 541 // Both "watch" and "gallery" actions are applicable which means that the 542 // selection is all videos. Showing them both is confusing, so we only keep 543 // the one that makes more sense ("watch" for single selection, "gallery" 544 // for multiple selection). 545 if (file_list.size() == 1) 546 common_handlers.erase(gallery_iter); 547 else 548 common_handlers.erase(watch_iter); 549 } 550 551 return common_handlers; 552} 553 554} // namespace file_browser_handlers 555} // namespace file_manager 556