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