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