1 // Copyright (c) 2011 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/extensions/extension_file_browser_private_api.h" 6 7 #include "base/base64.h" 8 #include "base/command_line.h" 9 #include "base/json/json_writer.h" 10 #include "base/logging.h" 11 #include "base/memory/singleton.h" 12 #include "base/stringprintf.h" 13 #include "base/string_util.h" 14 #include "base/task.h" 15 #include "base/time.h" 16 #include "base/values.h" 17 #include "chrome/browser/profiles/profile.h" 18 #include "chrome/browser/extensions/extension_event_router.h" 19 #include "chrome/browser/extensions/extension_function_dispatcher.h" 20 #include "chrome/browser/extensions/extension_process_manager.h" 21 #include "chrome/browser/extensions/extension_service.h" 22 #include "chrome/browser/extensions/extension_tabs_module.h" 23 #include "chrome/browser/extensions/file_manager_util.h" 24 #include "chrome/browser/prefs/pref_service.h" 25 #include "chrome/browser/prefs/scoped_user_pref_update.h" 26 #include "chrome/browser/ui/browser.h" 27 #include "chrome/browser/ui/views/html_dialog_view.h" 28 #include "chrome/browser/ui/webui/extension_icon_source.h" 29 #include "chrome/common/chrome_switches.h" 30 #include "chrome/common/extensions/extension.h" 31 #include "chrome/common/extensions/file_browser_handler.h" 32 #include "chrome/common/pref_names.h" 33 #include "content/browser/browser_thread.h" 34 #include "content/browser/child_process_security_policy.h" 35 #include "content/browser/renderer_host/render_process_host.h" 36 #include "content/browser/renderer_host/render_view_host.h" 37 #include "content/browser/tab_contents/tab_contents.h" 38 #include "googleurl/src/gurl.h" 39 #include "grit/generated_resources.h" 40 #include "webkit/fileapi/file_system_context.h" 41 #include "webkit/fileapi/file_system_mount_point_provider.h" 42 #include "webkit/fileapi/file_system_operation.h" 43 #include "webkit/fileapi/file_system_operation_context.h" 44 #include "webkit/fileapi/file_system_path_manager.h" 45 #include "webkit/fileapi/file_system_types.h" 46 #include "webkit/fileapi/file_system_util.h" 47 #include "webkit/fileapi/file_system_file_util.h" 48 #include "webkit/fileapi/local_file_system_file_util.h" 49 #include "ui/base/l10n/l10n_util.h" 50 51 // Error messages. 52 const char kFileError[] = "File error %d"; 53 const char kInvalidFileUrl[] = "Invalid file URL"; 54 55 // Internal task ids. 56 const char kEnqueueTaskId[] = "enqueue"; 57 58 const int kReadOnlyFilePermissions = base::PLATFORM_FILE_OPEN | 59 base::PLATFORM_FILE_READ | 60 base::PLATFORM_FILE_EXCLUSIVE_READ | 61 base::PLATFORM_FILE_ASYNC; 62 63 const int kReadWriteFilePermissions = base::PLATFORM_FILE_OPEN | 64 base::PLATFORM_FILE_CREATE | 65 base::PLATFORM_FILE_OPEN_ALWAYS | 66 base::PLATFORM_FILE_CREATE_ALWAYS | 67 base::PLATFORM_FILE_READ | 68 base::PLATFORM_FILE_WRITE | 69 base::PLATFORM_FILE_EXCLUSIVE_READ | 70 base::PLATFORM_FILE_EXCLUSIVE_WRITE | 71 base::PLATFORM_FILE_ASYNC | 72 base::PLATFORM_FILE_TRUNCATE | 73 base::PLATFORM_FILE_WRITE_ATTRIBUTES; 74 75 typedef std::pair<int, const FileBrowserHandler* > LastUsedHandler; 76 typedef std::vector<LastUsedHandler> LastUsedHandlerList; 77 78 typedef std::vector<const FileBrowserHandler*> ActionList; 79 80 81 // Breaks down task_id that is used between getFileTasks() and executeTask() on 82 // its building blocks. task_id field the following structure: 83 // <extension-id>|<task-action-id> 84 // Currently, the only supported task-type is of 'context'. 85 bool CrackTaskIdentifier(const std::string& task_id, 86 std::string* target_extension_id, 87 std::string* action_id) { 88 std::vector<std::string> result; 89 int count = Tokenize(task_id, std::string("|"), &result); 90 if (count != 2) 91 return false; 92 *target_extension_id = result[0]; 93 *action_id = result[1]; 94 return true; 95 } 96 97 std::string MakeTaskID(const char* extension_id, 98 const char* action_id) { 99 return base::StringPrintf("%s|%s", extension_id, action_id); 100 } 101 102 bool GetFileBrowserHandlers(Profile* profile, 103 const GURL& selected_file_url, 104 ActionList* results) { 105 ExtensionService* service = profile->GetExtensionService(); 106 if (!service) 107 return false; // In unit-tests, we may not have an ExtensionService. 108 109 for (ExtensionList::const_iterator iter = service->extensions()->begin(); 110 iter != service->extensions()->end(); 111 ++iter) { 112 const Extension* extension = iter->get(); 113 if (!extension->file_browser_handlers()) 114 continue; 115 116 for (Extension::FileBrowserHandlerList::const_iterator action_iter = 117 extension->file_browser_handlers()->begin(); 118 action_iter != extension->file_browser_handlers()->end(); 119 ++action_iter) { 120 const FileBrowserHandler* action = action_iter->get(); 121 if (!action->MatchesURL(selected_file_url)) 122 continue; 123 124 results->push_back(action_iter->get()); 125 } 126 } 127 return true; 128 } 129 130 bool SortByLastUsedTimestampDesc(const LastUsedHandler& a, 131 const LastUsedHandler& b) { 132 return a.first > b.first; 133 } 134 135 // TODO(zelidrag): Wire this with ICU to make this sort I18N happy. 136 bool SortByTaskName(const LastUsedHandler& a, const LastUsedHandler& b) { 137 return base::strcasecmp(a.second->title().data(), 138 b.second->title().data()) > 0; 139 } 140 141 // Given the list of selected files, returns array of context menu tasks 142 // that are shared 143 bool FindCommonTasks(Profile* profile, 144 ListValue* files_list, 145 LastUsedHandlerList* named_action_list) { 146 named_action_list->clear(); 147 ActionList common_tasks; 148 for (size_t i = 0; i < files_list->GetSize(); ++i) { 149 std::string file_url; 150 if (!files_list->GetString(i, &file_url)) 151 return false; 152 153 ActionList file_actions; 154 if (!GetFileBrowserHandlers(profile, GURL(file_url), &file_actions)) 155 return false; 156 // If there is nothing to do for one file, the intersection of tasks for all 157 // files will be empty at the end. 158 if (!file_actions.size()) { 159 common_tasks.clear(); 160 return true; 161 } 162 // For the very first file, just copy elements. 163 if (i == 0) { 164 common_tasks.insert(common_tasks.begin(), 165 file_actions.begin(), 166 file_actions.end()); 167 std::sort(common_tasks.begin(), common_tasks.end()); 168 } else if (common_tasks.size()) { 169 // For all additional files, find intersection between the accumulated 170 // and file specific set. 171 std::sort(file_actions.begin(), file_actions.end()); 172 ActionList intersection(common_tasks.size()); 173 ActionList::iterator intersection_end = 174 std::set_intersection(common_tasks.begin(), 175 common_tasks.end(), 176 file_actions.begin(), 177 file_actions.end(), 178 intersection.begin()); 179 common_tasks.clear(); 180 common_tasks.insert(common_tasks.begin(), 181 intersection.begin(), 182 intersection_end); 183 std::sort(common_tasks.begin(), common_tasks.end()); 184 } 185 } 186 187 const DictionaryValue* prefs_tasks = 188 profile->GetPrefs()->GetDictionary(prefs::kLastUsedFileBrowserHandlers); 189 for (ActionList::const_iterator iter = common_tasks.begin(); 190 iter != common_tasks.end(); ++iter) { 191 // Get timestamp of when this task was used last time. 192 int last_used_timestamp = 0; 193 prefs_tasks->GetInteger(MakeTaskID((*iter)->extension_id().data(), 194 (*iter)->id().data()), 195 &last_used_timestamp); 196 named_action_list->push_back(LastUsedHandler(last_used_timestamp, *iter)); 197 } 198 // Sort by the last used descending. 199 std::sort(named_action_list->begin(), named_action_list->end(), 200 SortByLastUsedTimestampDesc); 201 if (named_action_list->size() > 1) { 202 // Sort the rest by name. 203 std::sort(named_action_list->begin() + 1, named_action_list->end(), 204 SortByTaskName); 205 } 206 return true; 207 } 208 209 // Update file handler usage stats. 210 void UpdateFileHandlerUsageStats(Profile* profile, const std::string& task_id) { 211 if (!profile || !profile->GetPrefs()) 212 return; 213 DictionaryPrefUpdate prefs_usage_update(profile->GetPrefs(), 214 prefs::kLastUsedFileBrowserHandlers); 215 prefs_usage_update->SetWithoutPathExpansion(task_id, 216 new FundamentalValue( 217 static_cast<int>(base::Time::Now().ToInternalValue()/ 218 base::Time::kMicrosecondsPerSecond))); 219 } 220 221 222 class LocalFileSystemCallbackDispatcher 223 : public fileapi::FileSystemCallbackDispatcher { 224 public: 225 explicit LocalFileSystemCallbackDispatcher( 226 RequestLocalFileSystemFunction* function, 227 Profile* profile, 228 int child_id, 229 scoped_refptr<const Extension> extension) 230 : function_(function), 231 profile_(profile), 232 child_id_(child_id), 233 extension_(extension) { 234 DCHECK(function_); 235 } 236 237 // fileapi::FileSystemCallbackDispatcher overrides. 238 virtual void DidSucceed() OVERRIDE { 239 NOTREACHED(); 240 } 241 242 virtual void DidGetLocalPath(const FilePath& local_path) { 243 NOTREACHED(); 244 } 245 246 virtual void DidReadMetadata(const base::PlatformFileInfo& info, 247 const FilePath& unused) OVERRIDE { 248 NOTREACHED(); 249 } 250 251 virtual void DidReadDirectory( 252 const std::vector<base::FileUtilProxy::Entry>& entries, 253 bool has_more) OVERRIDE { 254 NOTREACHED(); 255 } 256 257 virtual void DidWrite(int64 bytes, bool complete) OVERRIDE { 258 NOTREACHED(); 259 } 260 261 virtual void DidOpenFileSystem(const std::string& name, 262 const GURL& root_path) OVERRIDE { 263 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 264 // Set up file permission access. 265 if (!SetupFileSystemAccessPermissions()) { 266 DidFail(base::PLATFORM_FILE_ERROR_SECURITY); 267 return; 268 } 269 270 BrowserThread::PostTask( 271 BrowserThread::UI, FROM_HERE, 272 NewRunnableMethod(function_, 273 &RequestLocalFileSystemFunction::RespondSuccessOnUIThread, 274 name, 275 root_path)); 276 } 277 278 virtual void DidFail(base::PlatformFileError error_code) OVERRIDE { 279 BrowserThread::PostTask( 280 BrowserThread::UI, FROM_HERE, 281 NewRunnableMethod(function_, 282 &RequestLocalFileSystemFunction::RespondFailedOnUIThread, 283 error_code)); 284 } 285 286 private: 287 288 // Grants file system access permissions to file browser component. 289 bool SetupFileSystemAccessPermissions() { 290 if (!extension_.get()) 291 return false; 292 293 // Make sure that only component extension can access the entire 294 // local file system. 295 if (extension_->location() != Extension::COMPONENT 296 #ifndef NDEBUG 297 && !CommandLine::ForCurrentProcess()->HasSwitch( 298 switches::kExposePrivateExtensionApi) 299 #endif 300 ) { 301 NOTREACHED() << "Private method access by non-component extension " 302 << extension_->id(); 303 return false; 304 } 305 306 fileapi::FileSystemPathManager* path_manager = 307 profile_->GetFileSystemContext()->path_manager(); 308 fileapi::ExternalFileSystemMountPointProvider* provider = 309 path_manager->external_provider(); 310 if (!provider) 311 return false; 312 313 // Grant full access to File API from this component extension. 314 provider->GrantFullAccessToExtension(extension_->id()); 315 316 // Grant R/W file permissions to the renderer hosting component 317 // extension for all paths exposed by our local file system provider. 318 std::vector<FilePath> root_dirs = provider->GetRootDirectories(); 319 for (std::vector<FilePath>::iterator iter = root_dirs.begin(); 320 iter != root_dirs.end(); 321 ++iter) { 322 ChildProcessSecurityPolicy::GetInstance()->GrantPermissionsForFile( 323 child_id_, *iter, kReadWriteFilePermissions); 324 } 325 return true; 326 } 327 328 RequestLocalFileSystemFunction* function_; 329 Profile* profile_; 330 // Renderer process id. 331 int child_id_; 332 // Extension source URL. 333 scoped_refptr<const Extension> extension_; 334 DISALLOW_COPY_AND_ASSIGN(LocalFileSystemCallbackDispatcher); 335 }; 336 337 void RequestLocalFileSystemFunction::RequestOnFileThread( 338 const GURL& source_url, int child_id) { 339 fileapi::FileSystemOperation* operation = 340 new fileapi::FileSystemOperation( 341 new LocalFileSystemCallbackDispatcher( 342 this, 343 profile(), 344 child_id, 345 GetExtension()), 346 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE), 347 profile()->GetFileSystemContext(), 348 NULL); 349 GURL origin_url = source_url.GetOrigin(); 350 operation->OpenFileSystem(origin_url, fileapi::kFileSystemTypeExternal, 351 false); // create 352 } 353 354 bool RequestLocalFileSystemFunction::RunImpl() { 355 if (!dispatcher() || !dispatcher()->render_view_host() || 356 !dispatcher()->render_view_host()->process()) 357 return false; 358 359 BrowserThread::PostTask( 360 BrowserThread::FILE, FROM_HERE, 361 NewRunnableMethod(this, 362 &RequestLocalFileSystemFunction::RequestOnFileThread, 363 source_url_, 364 dispatcher()->render_view_host()->process()->id())); 365 // Will finish asynchronously. 366 return true; 367 } 368 369 void RequestLocalFileSystemFunction::RespondSuccessOnUIThread( 370 const std::string& name, const GURL& root_path) { 371 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 372 result_.reset(new DictionaryValue()); 373 DictionaryValue* dict = reinterpret_cast<DictionaryValue*>(result_.get()); 374 dict->SetString("name", name); 375 dict->SetString("path", root_path.spec()); 376 dict->SetInteger("error", base::PLATFORM_FILE_OK); 377 SendResponse(true); 378 } 379 380 void RequestLocalFileSystemFunction::RespondFailedOnUIThread( 381 base::PlatformFileError error_code) { 382 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 383 error_ = base::StringPrintf(kFileError, static_cast<int>(error_code)); 384 SendResponse(false); 385 } 386 387 bool GetFileTasksFileBrowserFunction::RunImpl() { 388 ListValue* files_list = NULL; 389 if (!args_->GetList(0, &files_list)) 390 return false; 391 392 ListValue* result_list = new ListValue(); 393 result_.reset(result_list); 394 395 LastUsedHandlerList common_tasks; 396 if (!FindCommonTasks(profile_, files_list, &common_tasks)) 397 return false; 398 399 ExtensionService* service = profile_->GetExtensionService(); 400 for (LastUsedHandlerList::const_iterator iter = common_tasks.begin(); 401 iter != common_tasks.end(); 402 ++iter) { 403 const std::string extension_id = iter->second->extension_id(); 404 const Extension* extension = service->GetExtensionById(extension_id, false); 405 CHECK(extension); 406 DictionaryValue* task = new DictionaryValue(); 407 task->SetString("taskId", MakeTaskID(extension_id.data(), 408 iter->second->id().data())); 409 task->SetString("title", iter->second->title()); 410 // TODO(zelidrag): Figure out how to expose icon URL that task defined in 411 // manifest instead of the default extension icon. 412 GURL icon = 413 ExtensionIconSource::GetIconURL(extension, 414 Extension::EXTENSION_ICON_BITTY, 415 ExtensionIconSet::MATCH_BIGGER, 416 false); // grayscale 417 task->SetString("iconUrl", icon.spec()); 418 result_list->Append(task); 419 } 420 421 // TODO(zelidrag, serya): Add intent content tasks to result_list once we 422 // implement that API. 423 SendResponse(true); 424 return true; 425 } 426 427 class ExecuteTasksFileSystemCallbackDispatcher 428 : public fileapi::FileSystemCallbackDispatcher { 429 public: 430 explicit ExecuteTasksFileSystemCallbackDispatcher( 431 ExecuteTasksFileBrowserFunction* function, 432 Profile* profile, 433 int child_id, 434 const GURL& source_url, 435 scoped_refptr<const Extension> extension, 436 const std::string task_id, 437 const std::vector<GURL>& file_urls) 438 : function_(function), 439 profile_(profile), 440 source_url_(source_url), 441 extension_(extension), 442 task_id_(task_id), 443 origin_file_urls_(file_urls) { 444 DCHECK(function_); 445 } 446 447 // fileapi::FileSystemCallbackDispatcher overrides. 448 virtual void DidSucceed() OVERRIDE { 449 NOTREACHED(); 450 } 451 452 virtual void DidGetLocalPath(const FilePath& local_path) { 453 NOTREACHED(); 454 } 455 456 virtual void DidReadMetadata(const base::PlatformFileInfo& info, 457 const FilePath& unused) OVERRIDE { 458 NOTREACHED(); 459 } 460 461 virtual void DidReadDirectory( 462 const std::vector<base::FileUtilProxy::Entry>& entries, 463 bool has_more) OVERRIDE { 464 NOTREACHED(); 465 } 466 467 virtual void DidWrite(int64 bytes, bool complete) OVERRIDE { 468 NOTREACHED(); 469 } 470 471 virtual void DidOpenFileSystem(const std::string& file_system_name, 472 const GURL& file_system_root) OVERRIDE { 473 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 474 ExecuteTasksFileBrowserFunction::FileDefinitionList file_list; 475 for (std::vector<GURL>::iterator iter = origin_file_urls_.begin(); 476 iter != origin_file_urls_.end(); 477 ++iter) { 478 // Set up file permission access. 479 ExecuteTasksFileBrowserFunction::FileDefinition file; 480 if (!SetupFileAccessPermissions(*iter, &file.target_file_url, 481 &file.virtual_path, &file.is_directory)) { 482 continue; 483 } 484 file_list.push_back(file); 485 } 486 if (file_list.empty()) { 487 BrowserThread::PostTask( 488 BrowserThread::UI, FROM_HERE, 489 NewRunnableMethod(function_, 490 &ExecuteTasksFileBrowserFunction::ExecuteFailedOnUIThread)); 491 return; 492 } 493 494 BrowserThread::PostTask( 495 BrowserThread::UI, FROM_HERE, 496 NewRunnableMethod(function_, 497 &ExecuteTasksFileBrowserFunction::ExecuteFileActionsOnUIThread, 498 task_id_, 499 file_system_name, 500 file_system_root, 501 file_list)); 502 } 503 504 virtual void DidFail(base::PlatformFileError error_code) OVERRIDE { 505 LOG(WARNING) << "Local file system cant be resolved"; 506 BrowserThread::PostTask( 507 BrowserThread::UI, FROM_HERE, 508 NewRunnableMethod(function_, 509 &ExecuteTasksFileBrowserFunction::ExecuteFailedOnUIThread)); 510 } 511 512 private: 513 // Checks legitimacy of file url and grants file RO access permissions from 514 // handler (target) extension and its renderer process. 515 bool SetupFileAccessPermissions(const GURL& origin_file_url, 516 GURL* target_file_url, FilePath* file_path, bool* is_directory) { 517 518 if (!extension_.get()) 519 return false; 520 521 GURL file_origin_url; 522 FilePath virtual_path; 523 fileapi::FileSystemType type; 524 if (!CrackFileSystemURL(origin_file_url, &file_origin_url, &type, 525 &virtual_path)) { 526 return false; 527 } 528 529 if (type != fileapi::kFileSystemTypeExternal) 530 return false; 531 532 fileapi::FileSystemPathManager* path_manager = 533 profile_->GetFileSystemContext()->path_manager(); 534 if (!path_manager->IsAccessAllowed(file_origin_url, 535 type, 536 virtual_path)) { 537 return false; 538 } 539 540 // Make sure this url really being used by the right caller extension. 541 if (source_url_.GetOrigin() != file_origin_url) { 542 DidFail(base::PLATFORM_FILE_ERROR_SECURITY); 543 return false; 544 } 545 546 FilePath root_path = 547 path_manager->ValidateFileSystemRootAndGetPathOnFileThread( 548 file_origin_url, 549 fileapi::kFileSystemTypeExternal, 550 virtual_path, 551 false); // create 552 FilePath final_file_path = root_path.Append(virtual_path); 553 554 // Check if this file system entry exists first. 555 base::PlatformFileInfo file_info; 556 FilePath platform_path; 557 fileapi::FileSystemOperationContext file_system_operation_context( 558 profile_->GetFileSystemContext(), 559 fileapi::LocalFileSystemFileUtil::GetInstance()); 560 if (base::PLATFORM_FILE_OK != 561 fileapi::FileSystemFileUtil::GetInstance()->GetFileInfo( 562 &file_system_operation_context, final_file_path, &file_info, 563 &platform_path)) { 564 return false; 565 } 566 567 fileapi::ExternalFileSystemMountPointProvider* external_provider = 568 path_manager->external_provider(); 569 if (!external_provider) 570 return false; 571 572 // TODO(zelidrag): Let's just prevent all symlinks for now. We don't want a 573 // USB drive content to point to something in the rest of the file system. 574 // Ideally, we should permit symlinks within the boundary of the same 575 // virtual mount point. 576 if (file_info.is_symbolic_link) 577 return false; 578 579 // Get task details. 580 std::string target_extension_id; 581 std::string action_id; 582 if (!CrackTaskIdentifier(task_id_, &target_extension_id, 583 &action_id)) { 584 return false; 585 } 586 587 // TODO(zelidrag): Add explicit R/W + R/O permissions for non-component 588 // extensions. 589 590 // Get target extension's process. 591 RenderProcessHost* target_host = 592 profile_->GetExtensionProcessManager()->GetExtensionProcess( 593 target_extension_id); 594 if (!target_host) 595 return false; 596 597 // Grant R/O access permission to non-component extension and R/W to 598 // component extensions. 599 ChildProcessSecurityPolicy::GetInstance()->GrantPermissionsForFile( 600 target_host->id(), final_file_path, 601 extension_->location() != Extension::COMPONENT ? 602 kReadOnlyFilePermissions : kReadWriteFilePermissions); 603 604 // Grant access to this particular file to target extension. This will 605 // ensure that the target extension can access only this FS entry and 606 // prevent from traversing FS hierarchy upward. 607 external_provider->GrantFileAccessToExtension(target_extension_id, 608 virtual_path); 609 610 // Output values. 611 GURL target_origin_url(Extension::GetBaseURLFromExtensionId( 612 target_extension_id)); 613 GURL base_url = fileapi::GetFileSystemRootURI(target_origin_url, 614 fileapi::kFileSystemTypeExternal); 615 *target_file_url = GURL(base_url.spec() + virtual_path.value()); 616 FilePath root(FILE_PATH_LITERAL("/")); 617 *file_path = root.Append(virtual_path); 618 *is_directory = file_info.is_directory; 619 return true; 620 } 621 622 ExecuteTasksFileBrowserFunction* function_; 623 Profile* profile_; 624 // Extension source URL. 625 GURL source_url_; 626 scoped_refptr<const Extension> extension_; 627 std::string task_id_; 628 std::vector<GURL> origin_file_urls_; 629 DISALLOW_COPY_AND_ASSIGN(ExecuteTasksFileSystemCallbackDispatcher); 630 }; 631 632 bool ExecuteTasksFileBrowserFunction::RunImpl() { 633 // First param is task id that was to the extension with getFileTasks call. 634 std::string task_id; 635 if (!args_->GetString(0, &task_id) || !task_id.size()) 636 return false; 637 638 // The second param is the list of files that need to be executed with this 639 // task. 640 ListValue* files_list = NULL; 641 if (!args_->GetList(1, &files_list)) 642 return false; 643 644 if (!files_list->GetSize()) 645 return true; 646 647 return InitiateFileTaskExecution(task_id, files_list); 648 } 649 650 bool ExecuteTasksFileBrowserFunction::InitiateFileTaskExecution( 651 const std::string& task_id, ListValue* files_list) { 652 std::vector<GURL> file_urls; 653 for (size_t i = 0; i < files_list->GetSize(); i++) { 654 std::string origin_file_url; 655 if (!files_list->GetString(i, &origin_file_url)) { 656 error_ = kInvalidFileUrl; 657 return false; 658 } 659 file_urls.push_back(GURL(origin_file_url)); 660 } 661 // Get local file system instance on file thread. 662 BrowserThread::PostTask( 663 BrowserThread::FILE, FROM_HERE, 664 NewRunnableMethod(this, 665 &ExecuteTasksFileBrowserFunction::RequestFileEntryOnFileThread, 666 source_url_, 667 task_id, 668 file_urls)); 669 result_.reset(new FundamentalValue(true)); 670 return true; 671 } 672 673 void ExecuteTasksFileBrowserFunction::RequestFileEntryOnFileThread( 674 const GURL& source_url, const std::string& task_id, 675 const std::vector<GURL>& file_urls) { 676 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 677 fileapi::FileSystemOperation* operation = 678 new fileapi::FileSystemOperation( 679 new ExecuteTasksFileSystemCallbackDispatcher( 680 this, 681 profile(), 682 dispatcher()->render_view_host()->process()->id(), 683 source_url, 684 GetExtension(), 685 task_id, 686 file_urls), 687 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE), 688 profile()->GetFileSystemContext(), 689 NULL); 690 GURL origin_url = source_url.GetOrigin(); 691 operation->OpenFileSystem(origin_url, fileapi::kFileSystemTypeExternal, 692 false); // create 693 } 694 695 void ExecuteTasksFileBrowserFunction::ExecuteFailedOnUIThread() { 696 SendResponse(false); 697 } 698 699 700 void ExecuteTasksFileBrowserFunction::ExecuteFileActionsOnUIThread( 701 const std::string& task_id, 702 const std::string& file_system_name, 703 const GURL& file_system_root, 704 const FileDefinitionList& file_list) { 705 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 706 ExtensionService* service = profile_->GetExtensionService(); 707 if (!service) 708 return; 709 // Get task details. 710 std::string handler_extension_id; 711 std::string action_id; 712 if (!CrackTaskIdentifier(task_id, &handler_extension_id, 713 &action_id)) { 714 LOG(WARNING) << "Invalid task " << task_id; 715 SendResponse(false); 716 return; 717 } 718 719 const Extension* extension = service->GetExtensionById(handler_extension_id, 720 false); 721 if (!extension) { 722 SendResponse(false); 723 return; 724 } 725 726 ExtensionEventRouter* event_router = profile_->GetExtensionEventRouter(); 727 if (!event_router) { 728 SendResponse(false); 729 return; 730 } 731 732 scoped_ptr<ListValue> event_args(new ListValue()); 733 event_args->Append(Value::CreateStringValue(action_id)); 734 DictionaryValue* details = new DictionaryValue(); 735 event_args->Append(details); 736 // Get file definitions. These will be replaced with Entry instances by 737 // chromeHidden.Event.dispatchJSON() method from even_binding.js. 738 ListValue* files_urls = new ListValue(); 739 details->Set("entries", files_urls); 740 for (FileDefinitionList::const_iterator iter = file_list.begin(); 741 iter != file_list.end(); 742 ++iter) { 743 DictionaryValue* file_def = new DictionaryValue(); 744 files_urls->Append(file_def); 745 file_def->SetString("fileSystemName", file_system_name); 746 file_def->SetString("fileSystemRoot", file_system_root.spec()); 747 file_def->SetString("fileFullPath", iter->virtual_path.value()); 748 file_def->SetBoolean("fileIsDirectory", iter->is_directory); 749 } 750 // Get tab id. 751 Browser* browser = GetCurrentBrowser(); 752 if (browser) { 753 TabContents* contents = browser->GetSelectedTabContents(); 754 if (contents) 755 details->SetInteger("tab_id", ExtensionTabUtil::GetTabId(contents)); 756 } 757 758 UpdateFileHandlerUsageStats(profile_, task_id); 759 760 std::string json_args; 761 base::JSONWriter::Write(event_args.get(), false, &json_args); 762 event_router->DispatchEventToExtension( 763 handler_extension_id, std::string("fileBrowserHandler.onExecute"), 764 json_args, profile_, 765 GURL()); 766 SendResponse(true); 767 } 768 769 FileDialogFunction::FileDialogFunction() { 770 } 771 772 FileDialogFunction::~FileDialogFunction() { 773 } 774 775 // static 776 FileDialogFunction::Callback 777 FileDialogFunction::Callback::null_(NULL, NULL, NULL); 778 779 // static 780 FileDialogFunction::Callback::Map FileDialogFunction::Callback::map_; 781 782 // static 783 void FileDialogFunction::Callback::Add(int32 tab_id, 784 SelectFileDialog::Listener* listener, 785 HtmlDialogView* dialog, 786 void* params) { 787 if (map_.find(tab_id) == map_.end()) { 788 map_.insert(std::make_pair(tab_id, Callback(listener, dialog, params))); 789 } else { 790 DLOG_ASSERT("FileDialogFunction::AddCallback tab_id already present"); 791 } 792 } 793 794 // static 795 void FileDialogFunction::Callback::Remove(int32 tab_id) { 796 map_.erase(tab_id); 797 } 798 799 // static 800 const FileDialogFunction::Callback& 801 FileDialogFunction::Callback::Find(int32 tab_id) { 802 Callback::Map::const_iterator it = map_.find(tab_id); 803 return (it == map_.end()) ? null_ : it->second; 804 } 805 806 807 int32 FileDialogFunction::GetTabId() const { 808 return dispatcher()->delegate()->associated_tab_contents()-> 809 controller().session_id().id(); 810 } 811 812 const FileDialogFunction::Callback& FileDialogFunction::GetCallback() const { 813 if (!dispatcher() || !dispatcher()->delegate() || 814 !dispatcher()->delegate()->associated_tab_contents()) { 815 return Callback::null(); 816 } 817 return Callback::Find(GetTabId()); 818 } 819 820 void FileDialogFunction::CloseDialog(HtmlDialogView* dialog) { 821 DCHECK(dialog); 822 TabContents* contents = dispatcher()->delegate()->associated_tab_contents(); 823 if (contents) 824 dialog->CloseContents(contents); 825 } 826 827 // GetFileSystemRootPathOnFileThread can only be called from the file thread, 828 // so here we are. This function takes a vector of virtual paths, converts 829 // them to local paths and calls GetLocalPathsResponseOnUIThread with the 830 // result vector, on the UI thread. 831 void FileDialogFunction::GetLocalPathsOnFileThread(const UrlList& file_urls, 832 const std::string& task_id) { 833 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 834 FilePathList selected_files; 835 836 // FilePath(virtual_path) doesn't work on win, so limit this to ChromeOS. 837 #if defined(OS_CHROMEOS) 838 GURL origin_url = source_url().GetOrigin(); 839 fileapi::FileSystemPathManager* path_manager = 840 profile()->GetFileSystemContext()->path_manager(); 841 842 size_t len = file_urls.size(); 843 selected_files.reserve(len); 844 for (size_t i = 0; i < len; ++i) { 845 const GURL& file_url = file_urls[i]; 846 GURL file_origin_url; 847 FilePath virtual_path; 848 fileapi::FileSystemType type; 849 if (!CrackFileSystemURL(file_url, &file_origin_url, &type, 850 &virtual_path)) { 851 continue; 852 } 853 if (type != fileapi::kFileSystemTypeExternal) { 854 NOTREACHED(); 855 continue; 856 } 857 FilePath root = path_manager->ValidateFileSystemRootAndGetPathOnFileThread( 858 origin_url, 859 fileapi::kFileSystemTypeExternal, 860 FilePath(virtual_path), 861 false); 862 if (!root.empty()) { 863 selected_files.push_back(root.Append(virtual_path)); 864 } else { 865 LOG(WARNING) << "GetLocalPathsOnFileThread failed " 866 << file_url.spec(); 867 } 868 } 869 #endif 870 871 if (!selected_files.empty()) { 872 BrowserThread::PostTask( 873 BrowserThread::UI, FROM_HERE, 874 NewRunnableMethod(this, 875 &FileDialogFunction::GetLocalPathsResponseOnUIThread, 876 selected_files, task_id)); 877 } 878 } 879 880 bool SelectFileFunction::RunImpl() { 881 if (args_->GetSize() != 2) { 882 return false; 883 } 884 std::string file_url; 885 args_->GetString(0, &file_url); 886 UrlList file_paths; 887 file_paths.push_back(GURL(file_url)); 888 889 BrowserThread::PostTask( 890 BrowserThread::FILE, FROM_HERE, 891 NewRunnableMethod(this, 892 &SelectFileFunction::GetLocalPathsOnFileThread, 893 file_paths, std::string())); 894 895 return true; 896 } 897 898 void SelectFileFunction::GetLocalPathsResponseOnUIThread( 899 const FilePathList& files, const std::string& task_id) { 900 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 901 if (files.size() != 1) { 902 SendResponse(false); 903 return; 904 } 905 int index; 906 args_->GetInteger(1, &index); 907 const Callback& callback = GetCallback(); 908 DCHECK(!callback.IsNull()); 909 if (!callback.IsNull()) { 910 // Must do this before callback, as the callback may delete listeners 911 // waiting for window close. 912 CloseDialog(callback.dialog()); 913 callback.listener()->FileSelected(files[0], 914 index, 915 callback.params()); 916 } 917 SendResponse(true); 918 } 919 920 921 ViewFilesFunction::ViewFilesFunction() { 922 } 923 924 ViewFilesFunction::~ViewFilesFunction() { 925 } 926 927 bool ViewFilesFunction::RunImpl() { 928 if (args_->GetSize() < 1) { 929 return false; 930 } 931 932 ListValue* path_list = NULL; 933 args_->GetList(0, &path_list); 934 DCHECK(path_list); 935 936 std::string internal_task_id; 937 args_->GetString(1, &internal_task_id); 938 939 std::string virtual_path; 940 size_t len = path_list->GetSize(); 941 UrlList file_urls; 942 file_urls.reserve(len); 943 for (size_t i = 0; i < len; ++i) { 944 path_list->GetString(i, &virtual_path); 945 file_urls.push_back(GURL(virtual_path)); 946 } 947 948 BrowserThread::PostTask( 949 BrowserThread::FILE, FROM_HERE, 950 NewRunnableMethod(this, 951 &ViewFilesFunction::GetLocalPathsOnFileThread, 952 file_urls, internal_task_id)); 953 954 return true; 955 } 956 957 void ViewFilesFunction::GetLocalPathsResponseOnUIThread( 958 const FilePathList& files, const std::string& internal_task_id) { 959 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 960 for (FilePathList::const_iterator iter = files.begin(); 961 iter != files.end(); 962 ++iter) { 963 FileManagerUtil::ViewItem(*iter, internal_task_id == kEnqueueTaskId); 964 } 965 UpdateFileHandlerUsageStats(profile_, internal_task_id); 966 SendResponse(true); 967 } 968 969 SelectFilesFunction::SelectFilesFunction() { 970 } 971 972 SelectFilesFunction::~SelectFilesFunction() { 973 } 974 975 bool SelectFilesFunction::RunImpl() { 976 if (args_->GetSize() != 1) { 977 return false; 978 } 979 980 ListValue* path_list = NULL; 981 args_->GetList(0, &path_list); 982 DCHECK(path_list); 983 984 std::string virtual_path; 985 size_t len = path_list->GetSize(); 986 UrlList file_urls; 987 file_urls.reserve(len); 988 for (size_t i = 0; i < len; ++i) { 989 path_list->GetString(i, &virtual_path); 990 file_urls.push_back(GURL(virtual_path)); 991 } 992 993 BrowserThread::PostTask( 994 BrowserThread::FILE, FROM_HERE, 995 NewRunnableMethod(this, 996 &SelectFilesFunction::GetLocalPathsOnFileThread, 997 file_urls, std::string())); 998 999 return true; 1000 } 1001 1002 void SelectFilesFunction::GetLocalPathsResponseOnUIThread( 1003 const FilePathList& files, const std::string& internal_task_id) { 1004 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 1005 1006 const Callback& callback = GetCallback(); 1007 DCHECK(!callback.IsNull()); 1008 if (!callback.IsNull()) { 1009 // Must do this before callback, as the callback may delete listeners 1010 // waiting for window close. 1011 CloseDialog(callback.dialog()); 1012 callback.listener()->MultiFilesSelected(files, callback.params()); 1013 } 1014 SendResponse(true); 1015 } 1016 1017 bool CancelFileDialogFunction::RunImpl() { 1018 const Callback& callback = GetCallback(); 1019 DCHECK(!callback.IsNull()); 1020 if (!callback.IsNull()) { 1021 // Must do this before callback, as the callback may delete listeners 1022 // waiting for window close. 1023 CloseDialog(callback.dialog()); 1024 callback.listener()->FileSelectionCanceled(callback.params()); 1025 } 1026 SendResponse(true); 1027 return true; 1028 } 1029 1030 bool FileDialogStringsFunction::RunImpl() { 1031 result_.reset(new DictionaryValue()); 1032 DictionaryValue* dict = reinterpret_cast<DictionaryValue*>(result_.get()); 1033 1034 #define SET_STRING(ns, id) \ 1035 dict->SetString(#id, l10n_util::GetStringUTF16(ns##_##id)) 1036 1037 SET_STRING(IDS, LOCALE_FMT_DATE_SHORT); 1038 SET_STRING(IDS, LOCALE_MONTHS_SHORT); 1039 SET_STRING(IDS, LOCALE_DAYS_SHORT); 1040 1041 SET_STRING(IDS_FILE_BROWSER, BODY_FONT_FAMILY); 1042 SET_STRING(IDS_FILE_BROWSER, BODY_FONT_SIZE); 1043 1044 SET_STRING(IDS_FILE_BROWSER, ROOT_DIRECTORY_LABEL); 1045 SET_STRING(IDS_FILE_BROWSER, DOWNLOADS_DIRECTORY_LABEL); 1046 SET_STRING(IDS_FILE_BROWSER, MEDIA_DIRECTORY_LABEL); 1047 SET_STRING(IDS_FILE_BROWSER, NAME_COLUMN_LABEL); 1048 SET_STRING(IDS_FILE_BROWSER, SIZE_COLUMN_LABEL); 1049 SET_STRING(IDS_FILE_BROWSER, DATE_COLUMN_LABEL); 1050 SET_STRING(IDS_FILE_BROWSER, PREVIEW_COLUMN_LABEL); 1051 1052 SET_STRING(IDS_FILE_BROWSER, ERROR_CREATING_FOLDER); 1053 SET_STRING(IDS_FILE_BROWSER, ERROR_INVALID_FOLDER_CHARACTER); 1054 SET_STRING(IDS_FILE_BROWSER, ERROR_INVALID_FILE_CHARACTER); 1055 SET_STRING(IDS_FILE_BROWSER, NEW_FOLDER_PROMPT); 1056 SET_STRING(IDS_FILE_BROWSER, NEW_FOLDER_BUTTON_LABEL); 1057 SET_STRING(IDS_FILE_BROWSER, FILENAME_LABEL); 1058 1059 SET_STRING(IDS_FILE_BROWSER, EJECT_BUTTON); 1060 SET_STRING(IDS_FILE_BROWSER, IMAGE_DIMENSIONS); 1061 SET_STRING(IDS_FILE_BROWSER, VOLUME_LABEL); 1062 SET_STRING(IDS_FILE_BROWSER, READ_ONLY); 1063 1064 SET_STRING(IDS_FILE_BROWSER, ERROR_RENAMING); 1065 SET_STRING(IDS_FILE_BROWSER, RENAME_PROMPT); 1066 SET_STRING(IDS_FILE_BROWSER, RENAME_BUTTON_LABEL); 1067 1068 SET_STRING(IDS_FILE_BROWSER, ERROR_DELETING); 1069 SET_STRING(IDS_FILE_BROWSER, DELETE_BUTTON_LABEL); 1070 1071 SET_STRING(IDS_FILE_BROWSER, ERROR_MOVING); 1072 SET_STRING(IDS_FILE_BROWSER, MOVE_BUTTON_LABEL); 1073 1074 SET_STRING(IDS_FILE_BROWSER, ERROR_PASTING); 1075 SET_STRING(IDS_FILE_BROWSER, PASTE_BUTTON_LABEL); 1076 1077 SET_STRING(IDS_FILE_BROWSER, COPY_BUTTON_LABEL); 1078 SET_STRING(IDS_FILE_BROWSER, CUT_BUTTON_LABEL); 1079 1080 SET_STRING(IDS_FILE_BROWSER, DEVICE_TYPE_FLASH); 1081 SET_STRING(IDS_FILE_BROWSER, DEVICE_TYPE_HDD); 1082 SET_STRING(IDS_FILE_BROWSER, DEVICE_TYPE_OPTICAL); 1083 SET_STRING(IDS_FILE_BROWSER, DEVICE_TYPE_UNDEFINED); 1084 1085 SET_STRING(IDS_FILE_BROWSER, CANCEL_LABEL); 1086 SET_STRING(IDS_FILE_BROWSER, OPEN_LABEL); 1087 SET_STRING(IDS_FILE_BROWSER, SAVE_LABEL); 1088 1089 SET_STRING(IDS_FILE_BROWSER, SELECT_FOLDER_TITLE); 1090 SET_STRING(IDS_FILE_BROWSER, SELECT_OPEN_FILE_TITLE); 1091 SET_STRING(IDS_FILE_BROWSER, SELECT_OPEN_MULTI_FILE_TITLE); 1092 SET_STRING(IDS_FILE_BROWSER, SELECT_SAVEAS_FILE_TITLE); 1093 1094 SET_STRING(IDS_FILE_BROWSER, COMPUTING_SELECTION); 1095 SET_STRING(IDS_FILE_BROWSER, NOTHING_SELECTED); 1096 SET_STRING(IDS_FILE_BROWSER, ONE_FILE_SELECTED); 1097 SET_STRING(IDS_FILE_BROWSER, MANY_FILES_SELECTED); 1098 1099 // FILEBROWSER, without the underscore, is from the old school codebase. 1100 // TODO(rginda): Move these into IDS_FILE_BROWSER post M12. 1101 SET_STRING(IDS_FILEBROWSER, CONFIRM_DELETE); 1102 1103 SET_STRING(IDS_FILEBROWSER, ENQUEUE); 1104 #undef SET_STRING 1105 1106 // TODO(serya): Create a new string in .grd file for this one in M13. 1107 dict->SetString("PREVIEW_IMAGE", 1108 l10n_util::GetStringUTF16(IDS_CERT_MANAGER_VIEW_CERT_BUTTON)); 1109 dict->SetString("PLAY_MEDIA", 1110 l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_PLAY)); 1111 1112 return true; 1113 } 1114