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/extensions/api/file_system/file_system_api.h" 6 7 #include <set> 8 9 #include "apps/saved_files_service.h" 10 #include "base/bind.h" 11 #include "base/files/file_path.h" 12 #include "base/files/file_util.h" 13 #include "base/logging.h" 14 #include "base/path_service.h" 15 #include "base/strings/string_util.h" 16 #include "base/strings/stringprintf.h" 17 #include "base/strings/sys_string_conversions.h" 18 #include "base/strings/utf_string_conversions.h" 19 #include "base/value_conversions.h" 20 #include "base/values.h" 21 #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h" 22 #include "chrome/browser/extensions/extension_service.h" 23 #include "chrome/browser/extensions/path_util.h" 24 #include "chrome/browser/platform_util.h" 25 #include "chrome/browser/profiles/profile.h" 26 #include "chrome/browser/ui/apps/directory_access_confirmation_dialog.h" 27 #include "chrome/browser/ui/chrome_select_file_policy.h" 28 #include "chrome/common/chrome_paths.h" 29 #include "chrome/common/extensions/api/file_system.h" 30 #include "chrome/grit/generated_resources.h" 31 #include "content/public/browser/browser_thread.h" 32 #include "content/public/browser/child_process_security_policy.h" 33 #include "content/public/browser/render_process_host.h" 34 #include "content/public/browser/render_view_host.h" 35 #include "content/public/browser/web_contents.h" 36 #include "extensions/browser/app_window/app_window.h" 37 #include "extensions/browser/app_window/app_window_registry.h" 38 #include "extensions/browser/extension_prefs.h" 39 #include "extensions/browser/extension_system.h" 40 #include "extensions/browser/granted_file_entry.h" 41 #include "extensions/common/permissions/api_permission.h" 42 #include "extensions/common/permissions/permissions_data.h" 43 #include "net/base/mime_util.h" 44 #include "storage/browser/fileapi/external_mount_points.h" 45 #include "storage/browser/fileapi/isolated_context.h" 46 #include "storage/common/fileapi/file_system_types.h" 47 #include "storage/common/fileapi/file_system_util.h" 48 #include "ui/base/l10n/l10n_util.h" 49 #include "ui/shell_dialogs/select_file_dialog.h" 50 #include "ui/shell_dialogs/selected_file_info.h" 51 52 #if defined(OS_MACOSX) 53 #include <CoreFoundation/CoreFoundation.h> 54 #include "base/mac/foundation_util.h" 55 #endif 56 57 #if defined(OS_CHROMEOS) 58 #include "chrome/browser/chromeos/file_manager/filesystem_api_util.h" 59 #endif 60 61 using apps::SavedFileEntry; 62 using apps::SavedFilesService; 63 using storage::IsolatedContext; 64 65 const char kInvalidCallingPage[] = "Invalid calling page. This function can't " 66 "be called from a background page."; 67 const char kUserCancelled[] = "User cancelled"; 68 const char kWritableFileErrorFormat[] = "Error opening %s"; 69 const char kRequiresFileSystemWriteError[] = 70 "Operation requires fileSystem.write permission"; 71 const char kRequiresFileSystemDirectoryError[] = 72 "Operation requires fileSystem.directory permission"; 73 const char kMultipleUnsupportedError[] = 74 "acceptsMultiple: true is not supported for 'saveFile'"; 75 const char kUnknownIdError[] = "Unknown id"; 76 77 namespace file_system = extensions::api::file_system; 78 namespace ChooseEntry = file_system::ChooseEntry; 79 80 namespace { 81 82 bool g_skip_picker_for_test = false; 83 bool g_use_suggested_path_for_test = false; 84 base::FilePath* g_path_to_be_picked_for_test; 85 std::vector<base::FilePath>* g_paths_to_be_picked_for_test; 86 bool g_skip_directory_confirmation_for_test = false; 87 bool g_allow_directory_access_for_test = false; 88 89 // Expand the mime-types and extensions provided in an AcceptOption, returning 90 // them within the passed extension vector. Returns false if no valid types 91 // were found. 92 bool GetFileTypesFromAcceptOption( 93 const file_system::AcceptOption& accept_option, 94 std::vector<base::FilePath::StringType>* extensions, 95 base::string16* description) { 96 std::set<base::FilePath::StringType> extension_set; 97 int description_id = 0; 98 99 if (accept_option.mime_types.get()) { 100 std::vector<std::string>* list = accept_option.mime_types.get(); 101 bool valid_type = false; 102 for (std::vector<std::string>::const_iterator iter = list->begin(); 103 iter != list->end(); ++iter) { 104 std::vector<base::FilePath::StringType> inner; 105 std::string accept_type = *iter; 106 base::StringToLowerASCII(&accept_type); 107 net::GetExtensionsForMimeType(accept_type, &inner); 108 if (inner.empty()) 109 continue; 110 111 if (valid_type) 112 description_id = 0; // We already have an accept type with label; if 113 // we find another, give up and use the default. 114 else if (accept_type == "image/*") 115 description_id = IDS_IMAGE_FILES; 116 else if (accept_type == "audio/*") 117 description_id = IDS_AUDIO_FILES; 118 else if (accept_type == "video/*") 119 description_id = IDS_VIDEO_FILES; 120 121 extension_set.insert(inner.begin(), inner.end()); 122 valid_type = true; 123 } 124 } 125 126 if (accept_option.extensions.get()) { 127 std::vector<std::string>* list = accept_option.extensions.get(); 128 for (std::vector<std::string>::const_iterator iter = list->begin(); 129 iter != list->end(); ++iter) { 130 std::string extension = *iter; 131 base::StringToLowerASCII(&extension); 132 #if defined(OS_WIN) 133 extension_set.insert(base::UTF8ToWide(*iter)); 134 #else 135 extension_set.insert(*iter); 136 #endif 137 } 138 } 139 140 extensions->assign(extension_set.begin(), extension_set.end()); 141 if (extensions->empty()) 142 return false; 143 144 if (accept_option.description.get()) 145 *description = base::UTF8ToUTF16(*accept_option.description.get()); 146 else if (description_id) 147 *description = l10n_util::GetStringUTF16(description_id); 148 149 return true; 150 } 151 152 // Key for the path of the directory of the file last chosen by the user in 153 // response to a chrome.fileSystem.chooseEntry() call. 154 const char kLastChooseEntryDirectory[] = "last_choose_file_directory"; 155 156 const int kGraylistedPaths[] = { 157 base::DIR_HOME, 158 #if defined(OS_WIN) 159 base::DIR_PROGRAM_FILES, 160 base::DIR_PROGRAM_FILESX86, 161 base::DIR_WINDOWS, 162 #endif 163 }; 164 165 } // namespace 166 167 namespace extensions { 168 169 namespace file_system_api { 170 171 base::FilePath GetLastChooseEntryDirectory(const ExtensionPrefs* prefs, 172 const std::string& extension_id) { 173 base::FilePath path; 174 std::string string_path; 175 if (prefs->ReadPrefAsString(extension_id, 176 kLastChooseEntryDirectory, 177 &string_path)) { 178 path = base::FilePath::FromUTF8Unsafe(string_path); 179 } 180 return path; 181 } 182 183 void SetLastChooseEntryDirectory(ExtensionPrefs* prefs, 184 const std::string& extension_id, 185 const base::FilePath& path) { 186 prefs->UpdateExtensionPref(extension_id, 187 kLastChooseEntryDirectory, 188 base::CreateFilePathValue(path)); 189 } 190 191 std::vector<base::FilePath> GetGrayListedDirectories() { 192 std::vector<base::FilePath> graylisted_directories; 193 for (size_t i = 0; i < arraysize(kGraylistedPaths); ++i) { 194 base::FilePath graylisted_path; 195 if (PathService::Get(kGraylistedPaths[i], &graylisted_path)) 196 graylisted_directories.push_back(graylisted_path); 197 } 198 return graylisted_directories; 199 } 200 201 } // namespace file_system_api 202 203 bool FileSystemGetDisplayPathFunction::RunSync() { 204 std::string filesystem_name; 205 std::string filesystem_path; 206 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name)); 207 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path)); 208 209 base::FilePath file_path; 210 if (!app_file_handler_util::ValidateFileEntryAndGetPath(filesystem_name, 211 filesystem_path, 212 render_view_host_, 213 &file_path, 214 &error_)) 215 return false; 216 217 file_path = path_util::PrettifyPath(file_path); 218 SetResult(new base::StringValue(file_path.value())); 219 return true; 220 } 221 222 FileSystemEntryFunction::FileSystemEntryFunction() 223 : multiple_(false), 224 is_directory_(false), 225 response_(NULL) {} 226 227 void FileSystemEntryFunction::PrepareFilesForWritableApp( 228 const std::vector<base::FilePath>& paths) { 229 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 230 app_file_handler_util::PrepareFilesForWritableApp( 231 paths, 232 GetProfile(), 233 is_directory_, 234 base::Bind(&FileSystemEntryFunction::RegisterFileSystemsAndSendResponse, 235 this, 236 paths), 237 base::Bind(&FileSystemEntryFunction::HandleWritableFileError, this)); 238 } 239 240 void FileSystemEntryFunction::RegisterFileSystemsAndSendResponse( 241 const std::vector<base::FilePath>& paths) { 242 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 243 if (!render_view_host_) 244 return; 245 246 CreateResponse(); 247 for (std::vector<base::FilePath>::const_iterator it = paths.begin(); 248 it != paths.end(); ++it) { 249 AddEntryToResponse(*it, ""); 250 } 251 SendResponse(true); 252 } 253 254 void FileSystemEntryFunction::CreateResponse() { 255 DCHECK(!response_); 256 response_ = new base::DictionaryValue(); 257 base::ListValue* list = new base::ListValue(); 258 response_->Set("entries", list); 259 response_->SetBoolean("multiple", multiple_); 260 SetResult(response_); 261 } 262 263 void FileSystemEntryFunction::AddEntryToResponse( 264 const base::FilePath& path, 265 const std::string& id_override) { 266 DCHECK(response_); 267 GrantedFileEntry file_entry = app_file_handler_util::CreateFileEntry( 268 GetProfile(), 269 extension(), 270 render_view_host_->GetProcess()->GetID(), 271 path, 272 is_directory_); 273 base::ListValue* entries; 274 bool success = response_->GetList("entries", &entries); 275 DCHECK(success); 276 277 base::DictionaryValue* entry = new base::DictionaryValue(); 278 entry->SetString("fileSystemId", file_entry.filesystem_id); 279 entry->SetString("baseName", file_entry.registered_name); 280 if (id_override.empty()) 281 entry->SetString("id", file_entry.id); 282 else 283 entry->SetString("id", id_override); 284 entry->SetBoolean("isDirectory", is_directory_); 285 entries->Append(entry); 286 } 287 288 void FileSystemEntryFunction::HandleWritableFileError( 289 const base::FilePath& error_path) { 290 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 291 error_ = base::StringPrintf(kWritableFileErrorFormat, 292 error_path.BaseName().AsUTF8Unsafe().c_str()); 293 SendResponse(false); 294 } 295 296 bool FileSystemGetWritableEntryFunction::RunAsync() { 297 std::string filesystem_name; 298 std::string filesystem_path; 299 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name)); 300 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path)); 301 302 if (!app_file_handler_util::HasFileSystemWritePermission(extension_.get())) { 303 error_ = kRequiresFileSystemWriteError; 304 return false; 305 } 306 307 if (!app_file_handler_util::ValidateFileEntryAndGetPath(filesystem_name, 308 filesystem_path, 309 render_view_host_, 310 &path_, 311 &error_)) 312 return false; 313 314 content::BrowserThread::PostTaskAndReply( 315 content::BrowserThread::FILE, 316 FROM_HERE, 317 base::Bind( 318 &FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread, 319 this), 320 base::Bind( 321 &FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse, 322 this)); 323 return true; 324 } 325 326 void FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse() { 327 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 328 if (is_directory_ && 329 !extension_->permissions_data()->HasAPIPermission( 330 APIPermission::kFileSystemDirectory)) { 331 error_ = kRequiresFileSystemDirectoryError; 332 SendResponse(false); 333 } 334 std::vector<base::FilePath> paths; 335 paths.push_back(path_); 336 PrepareFilesForWritableApp(paths); 337 } 338 339 void FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread() { 340 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); 341 if (base::DirectoryExists(path_)) { 342 is_directory_ = true; 343 } 344 } 345 346 bool FileSystemIsWritableEntryFunction::RunSync() { 347 std::string filesystem_name; 348 std::string filesystem_path; 349 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name)); 350 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path)); 351 352 std::string filesystem_id; 353 if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id)) { 354 error_ = app_file_handler_util::kInvalidParameters; 355 return false; 356 } 357 358 content::ChildProcessSecurityPolicy* policy = 359 content::ChildProcessSecurityPolicy::GetInstance(); 360 int renderer_id = render_view_host_->GetProcess()->GetID(); 361 bool is_writable = policy->CanReadWriteFileSystem(renderer_id, 362 filesystem_id); 363 364 SetResult(new base::FundamentalValue(is_writable)); 365 return true; 366 } 367 368 // Handles showing a dialog to the user to ask for the filename for a file to 369 // save or open. 370 class FileSystemChooseEntryFunction::FilePicker 371 : public ui::SelectFileDialog::Listener { 372 public: 373 FilePicker(FileSystemChooseEntryFunction* function, 374 content::WebContents* web_contents, 375 const base::FilePath& suggested_name, 376 const ui::SelectFileDialog::FileTypeInfo& file_type_info, 377 ui::SelectFileDialog::Type picker_type) 378 : function_(function) { 379 select_file_dialog_ = ui::SelectFileDialog::Create( 380 this, new ChromeSelectFilePolicy(web_contents)); 381 gfx::NativeWindow owning_window = web_contents ? 382 platform_util::GetTopLevel(web_contents->GetNativeView()) : 383 NULL; 384 385 if (g_skip_picker_for_test) { 386 if (g_use_suggested_path_for_test) { 387 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 388 base::Bind( 389 &FileSystemChooseEntryFunction::FilePicker::FileSelected, 390 base::Unretained(this), suggested_name, 1, 391 static_cast<void*>(NULL))); 392 } else if (g_path_to_be_picked_for_test) { 393 content::BrowserThread::PostTask( 394 content::BrowserThread::UI, FROM_HERE, 395 base::Bind( 396 &FileSystemChooseEntryFunction::FilePicker::FileSelected, 397 base::Unretained(this), *g_path_to_be_picked_for_test, 1, 398 static_cast<void*>(NULL))); 399 } else if (g_paths_to_be_picked_for_test) { 400 content::BrowserThread::PostTask( 401 content::BrowserThread::UI, 402 FROM_HERE, 403 base::Bind( 404 &FileSystemChooseEntryFunction::FilePicker::MultiFilesSelected, 405 base::Unretained(this), 406 *g_paths_to_be_picked_for_test, 407 static_cast<void*>(NULL))); 408 } else { 409 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, 410 base::Bind( 411 &FileSystemChooseEntryFunction::FilePicker:: 412 FileSelectionCanceled, 413 base::Unretained(this), static_cast<void*>(NULL))); 414 } 415 return; 416 } 417 418 select_file_dialog_->SelectFile(picker_type, 419 base::string16(), 420 suggested_name, 421 &file_type_info, 422 0, 423 base::FilePath::StringType(), 424 owning_window, 425 NULL); 426 } 427 428 virtual ~FilePicker() {} 429 430 private: 431 // ui::SelectFileDialog::Listener implementation. 432 virtual void FileSelected(const base::FilePath& path, 433 int index, 434 void* params) OVERRIDE { 435 std::vector<base::FilePath> paths; 436 paths.push_back(path); 437 MultiFilesSelected(paths, params); 438 } 439 440 virtual void FileSelectedWithExtraInfo(const ui::SelectedFileInfo& file, 441 int index, 442 void* params) OVERRIDE { 443 // Normally, file.local_path is used because it is a native path to the 444 // local read-only cached file in the case of remote file system like 445 // Chrome OS's Google Drive integration. Here, however, |file.file_path| is 446 // necessary because we need to create a FileEntry denoting the remote file, 447 // not its cache. On other platforms than Chrome OS, they are the same. 448 // 449 // TODO(kinaba): remove this, once after the file picker implements proper 450 // switch of the path treatment depending on the |support_drive| flag. 451 FileSelected(file.file_path, index, params); 452 } 453 454 virtual void MultiFilesSelected(const std::vector<base::FilePath>& files, 455 void* params) OVERRIDE { 456 function_->FilesSelected(files); 457 delete this; 458 } 459 460 virtual void MultiFilesSelectedWithExtraInfo( 461 const std::vector<ui::SelectedFileInfo>& files, 462 void* params) OVERRIDE { 463 std::vector<base::FilePath> paths; 464 for (std::vector<ui::SelectedFileInfo>::const_iterator it = files.begin(); 465 it != files.end(); ++it) { 466 paths.push_back(it->file_path); 467 } 468 MultiFilesSelected(paths, params); 469 } 470 471 virtual void FileSelectionCanceled(void* params) OVERRIDE { 472 function_->FileSelectionCanceled(); 473 delete this; 474 } 475 476 scoped_refptr<ui::SelectFileDialog> select_file_dialog_; 477 scoped_refptr<FileSystemChooseEntryFunction> function_; 478 479 DISALLOW_COPY_AND_ASSIGN(FilePicker); 480 }; 481 482 void FileSystemChooseEntryFunction::ShowPicker( 483 const ui::SelectFileDialog::FileTypeInfo& file_type_info, 484 ui::SelectFileDialog::Type picker_type) { 485 // TODO(asargent/benwells) - As a short term remediation for crbug.com/179010 486 // we're adding the ability for a whitelisted extension to use this API since 487 // chrome.fileBrowserHandler.selectFile is ChromeOS-only. Eventually we'd 488 // like a better solution and likely this code will go back to being 489 // platform-app only. 490 content::WebContents* web_contents = NULL; 491 if (extension_->is_platform_app()) { 492 AppWindowRegistry* registry = AppWindowRegistry::Get(GetProfile()); 493 DCHECK(registry); 494 AppWindow* app_window = 495 registry->GetAppWindowForRenderViewHost(render_view_host()); 496 if (!app_window) { 497 error_ = kInvalidCallingPage; 498 SendResponse(false); 499 return; 500 } 501 web_contents = app_window->web_contents(); 502 } else { 503 web_contents = GetAssociatedWebContents(); 504 } 505 // The file picker will hold a reference to this function instance, preventing 506 // its destruction (and subsequent sending of the function response) until the 507 // user has selected a file or cancelled the picker. At that point, the picker 508 // will delete itself, which will also free the function instance. 509 new FilePicker( 510 this, web_contents, initial_path_, file_type_info, picker_type); 511 } 512 513 // static 514 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest( 515 base::FilePath* path) { 516 g_skip_picker_for_test = true; 517 g_use_suggested_path_for_test = false; 518 g_path_to_be_picked_for_test = path; 519 g_paths_to_be_picked_for_test = NULL; 520 } 521 522 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathsForTest( 523 std::vector<base::FilePath>* paths) { 524 g_skip_picker_for_test = true; 525 g_use_suggested_path_for_test = false; 526 g_paths_to_be_picked_for_test = paths; 527 } 528 529 // static 530 void FileSystemChooseEntryFunction::SkipPickerAndSelectSuggestedPathForTest() { 531 g_skip_picker_for_test = true; 532 g_use_suggested_path_for_test = true; 533 g_path_to_be_picked_for_test = NULL; 534 g_paths_to_be_picked_for_test = NULL; 535 } 536 537 // static 538 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysCancelForTest() { 539 g_skip_picker_for_test = true; 540 g_use_suggested_path_for_test = false; 541 g_path_to_be_picked_for_test = NULL; 542 g_paths_to_be_picked_for_test = NULL; 543 } 544 545 // static 546 void FileSystemChooseEntryFunction::StopSkippingPickerForTest() { 547 g_skip_picker_for_test = false; 548 } 549 550 // static 551 void FileSystemChooseEntryFunction::SkipDirectoryConfirmationForTest() { 552 g_skip_directory_confirmation_for_test = true; 553 g_allow_directory_access_for_test = true; 554 } 555 556 // static 557 void FileSystemChooseEntryFunction::AutoCancelDirectoryConfirmationForTest() { 558 g_skip_directory_confirmation_for_test = true; 559 g_allow_directory_access_for_test = false; 560 } 561 562 // static 563 void FileSystemChooseEntryFunction::StopSkippingDirectoryConfirmationForTest() { 564 g_skip_directory_confirmation_for_test = false; 565 } 566 567 // static 568 void FileSystemChooseEntryFunction::RegisterTempExternalFileSystemForTest( 569 const std::string& name, const base::FilePath& path) { 570 // For testing on Chrome OS, where to deal with remote and local paths 571 // smoothly, all accessed paths need to be registered in the list of 572 // external mount points. 573 storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( 574 name, 575 storage::kFileSystemTypeNativeLocal, 576 storage::FileSystemMountOption(), 577 path); 578 } 579 580 void FileSystemChooseEntryFunction::SetInitialPathOnFileThread( 581 const base::FilePath& suggested_name, 582 const base::FilePath& previous_path) { 583 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); 584 if (!previous_path.empty() && base::DirectoryExists(previous_path)) { 585 initial_path_ = previous_path.Append(suggested_name); 586 } else { 587 base::FilePath documents_dir; 588 if (PathService::Get(chrome::DIR_USER_DOCUMENTS, &documents_dir)) { 589 initial_path_ = documents_dir.Append(suggested_name); 590 } else { 591 initial_path_ = suggested_name; 592 } 593 } 594 } 595 596 void FileSystemChooseEntryFunction::FilesSelected( 597 const std::vector<base::FilePath>& paths) { 598 DCHECK(!paths.empty()); 599 base::FilePath last_choose_directory; 600 if (is_directory_) { 601 last_choose_directory = paths[0]; 602 } else { 603 last_choose_directory = paths[0].DirName(); 604 } 605 file_system_api::SetLastChooseEntryDirectory( 606 ExtensionPrefs::Get(GetProfile()), 607 extension()->id(), 608 last_choose_directory); 609 if (is_directory_) { 610 // Get the WebContents for the app window to be the parent window of the 611 // confirmation dialog if necessary. 612 AppWindowRegistry* registry = AppWindowRegistry::Get(GetProfile()); 613 DCHECK(registry); 614 AppWindow* app_window = 615 registry->GetAppWindowForRenderViewHost(render_view_host()); 616 if (!app_window) { 617 error_ = kInvalidCallingPage; 618 SendResponse(false); 619 return; 620 } 621 content::WebContents* web_contents = app_window->web_contents(); 622 623 DCHECK_EQ(paths.size(), 1u); 624 #if defined(OS_CHROMEOS) 625 base::FilePath check_path = 626 file_manager::util::IsUnderNonNativeLocalPath(GetProfile(), paths[0]) 627 ? paths[0] 628 : base::MakeAbsoluteFilePath(paths[0]); 629 #else 630 base::FilePath check_path = base::MakeAbsoluteFilePath(paths[0]); 631 #endif 632 633 content::BrowserThread::PostTask( 634 content::BrowserThread::FILE, 635 FROM_HERE, 636 base::Bind( 637 &FileSystemChooseEntryFunction::ConfirmDirectoryAccessOnFileThread, 638 this, 639 check_path, 640 paths, 641 web_contents)); 642 return; 643 } 644 645 OnDirectoryAccessConfirmed(paths); 646 } 647 648 void FileSystemChooseEntryFunction::FileSelectionCanceled() { 649 error_ = kUserCancelled; 650 SendResponse(false); 651 } 652 653 void FileSystemChooseEntryFunction::ConfirmDirectoryAccessOnFileThread( 654 const base::FilePath& check_path, 655 const std::vector<base::FilePath>& paths, 656 content::WebContents* web_contents) { 657 if (check_path.empty()) { 658 content::BrowserThread::PostTask( 659 content::BrowserThread::UI, 660 FROM_HERE, 661 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled, 662 this)); 663 return; 664 } 665 666 for (size_t i = 0; i < arraysize(kGraylistedPaths); i++) { 667 base::FilePath graylisted_path; 668 if (PathService::Get(kGraylistedPaths[i], &graylisted_path) && 669 (check_path == graylisted_path || 670 check_path.IsParent(graylisted_path))) { 671 if (g_skip_directory_confirmation_for_test) { 672 if (g_allow_directory_access_for_test) { 673 break; 674 } else { 675 content::BrowserThread::PostTask( 676 content::BrowserThread::UI, 677 FROM_HERE, 678 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled, 679 this)); 680 } 681 return; 682 } 683 684 content::BrowserThread::PostTask( 685 content::BrowserThread::UI, 686 FROM_HERE, 687 base::Bind( 688 CreateDirectoryAccessConfirmationDialog, 689 app_file_handler_util::HasFileSystemWritePermission( 690 extension_.get()), 691 base::UTF8ToUTF16(extension_->name()), 692 web_contents, 693 base::Bind( 694 &FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed, 695 this, 696 paths), 697 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled, 698 this))); 699 return; 700 } 701 } 702 703 content::BrowserThread::PostTask( 704 content::BrowserThread::UI, 705 FROM_HERE, 706 base::Bind(&FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed, 707 this, paths)); 708 } 709 710 void FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed( 711 const std::vector<base::FilePath>& paths) { 712 if (app_file_handler_util::HasFileSystemWritePermission(extension_.get())) { 713 PrepareFilesForWritableApp(paths); 714 return; 715 } 716 717 // Don't need to check the file, it's for reading. 718 RegisterFileSystemsAndSendResponse(paths); 719 } 720 721 void FileSystemChooseEntryFunction::BuildFileTypeInfo( 722 ui::SelectFileDialog::FileTypeInfo* file_type_info, 723 const base::FilePath::StringType& suggested_extension, 724 const AcceptOptions* accepts, 725 const bool* acceptsAllTypes) { 726 file_type_info->include_all_files = true; 727 if (acceptsAllTypes) 728 file_type_info->include_all_files = *acceptsAllTypes; 729 730 bool need_suggestion = !file_type_info->include_all_files && 731 !suggested_extension.empty(); 732 733 if (accepts) { 734 typedef file_system::AcceptOption AcceptOption; 735 for (std::vector<linked_ptr<AcceptOption> >::const_iterator iter = 736 accepts->begin(); iter != accepts->end(); ++iter) { 737 base::string16 description; 738 std::vector<base::FilePath::StringType> extensions; 739 740 if (!GetFileTypesFromAcceptOption(**iter, &extensions, &description)) 741 continue; // No extensions were found. 742 743 file_type_info->extensions.push_back(extensions); 744 file_type_info->extension_description_overrides.push_back(description); 745 746 // If we still need to find suggested_extension, hunt for it inside the 747 // extensions returned from GetFileTypesFromAcceptOption. 748 if (need_suggestion && std::find(extensions.begin(), 749 extensions.end(), suggested_extension) != extensions.end()) { 750 need_suggestion = false; 751 } 752 } 753 } 754 755 // If there's nothing in our accepted extension list or we couldn't find the 756 // suggested extension required, then default to accepting all types. 757 if (file_type_info->extensions.empty() || need_suggestion) 758 file_type_info->include_all_files = true; 759 } 760 761 void FileSystemChooseEntryFunction::BuildSuggestion( 762 const std::string *opt_name, 763 base::FilePath* suggested_name, 764 base::FilePath::StringType* suggested_extension) { 765 if (opt_name) { 766 *suggested_name = base::FilePath::FromUTF8Unsafe(*opt_name); 767 768 // Don't allow any path components; shorten to the base name. This should 769 // result in a relative path, but in some cases may not. Clear the 770 // suggestion for safety if this is the case. 771 *suggested_name = suggested_name->BaseName(); 772 if (suggested_name->IsAbsolute()) 773 *suggested_name = base::FilePath(); 774 775 *suggested_extension = suggested_name->Extension(); 776 if (!suggested_extension->empty()) 777 suggested_extension->erase(suggested_extension->begin()); // drop the . 778 } 779 } 780 781 bool FileSystemChooseEntryFunction::RunAsync() { 782 scoped_ptr<ChooseEntry::Params> params(ChooseEntry::Params::Create(*args_)); 783 EXTENSION_FUNCTION_VALIDATE(params.get()); 784 785 base::FilePath suggested_name; 786 ui::SelectFileDialog::FileTypeInfo file_type_info; 787 ui::SelectFileDialog::Type picker_type = 788 ui::SelectFileDialog::SELECT_OPEN_FILE; 789 790 file_system::ChooseEntryOptions* options = params->options.get(); 791 if (options) { 792 multiple_ = options->accepts_multiple; 793 if (multiple_) 794 picker_type = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE; 795 796 if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENWRITABLEFILE && 797 !app_file_handler_util::HasFileSystemWritePermission( 798 extension_.get())) { 799 error_ = kRequiresFileSystemWriteError; 800 return false; 801 } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_SAVEFILE) { 802 if (!app_file_handler_util::HasFileSystemWritePermission( 803 extension_.get())) { 804 error_ = kRequiresFileSystemWriteError; 805 return false; 806 } 807 if (multiple_) { 808 error_ = kMultipleUnsupportedError; 809 return false; 810 } 811 picker_type = ui::SelectFileDialog::SELECT_SAVEAS_FILE; 812 } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENDIRECTORY) { 813 is_directory_ = true; 814 if (!extension_->permissions_data()->HasAPIPermission( 815 APIPermission::kFileSystemDirectory)) { 816 error_ = kRequiresFileSystemDirectoryError; 817 return false; 818 } 819 if (multiple_) { 820 error_ = kMultipleUnsupportedError; 821 return false; 822 } 823 picker_type = ui::SelectFileDialog::SELECT_FOLDER; 824 } 825 826 base::FilePath::StringType suggested_extension; 827 BuildSuggestion(options->suggested_name.get(), &suggested_name, 828 &suggested_extension); 829 830 BuildFileTypeInfo(&file_type_info, suggested_extension, 831 options->accepts.get(), options->accepts_all_types.get()); 832 } 833 834 file_type_info.support_drive = true; 835 836 base::FilePath previous_path = file_system_api::GetLastChooseEntryDirectory( 837 ExtensionPrefs::Get(GetProfile()), extension()->id()); 838 839 content::BrowserThread::PostTaskAndReply( 840 content::BrowserThread::FILE, 841 FROM_HERE, 842 base::Bind(&FileSystemChooseEntryFunction::SetInitialPathOnFileThread, 843 this, 844 suggested_name, 845 previous_path), 846 base::Bind(&FileSystemChooseEntryFunction::ShowPicker, 847 this, 848 file_type_info, 849 picker_type)); 850 return true; 851 } 852 853 bool FileSystemRetainEntryFunction::RunAsync() { 854 std::string entry_id; 855 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id)); 856 SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile()); 857 // Add the file to the retain list if it is not already on there. 858 if (!saved_files_service->IsRegistered(extension_->id(), entry_id)) { 859 std::string filesystem_name; 860 std::string filesystem_path; 861 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_name)); 862 EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &filesystem_path)); 863 if (!app_file_handler_util::ValidateFileEntryAndGetPath(filesystem_name, 864 filesystem_path, 865 render_view_host_, 866 &path_, 867 &error_)) { 868 return false; 869 } 870 871 content::BrowserThread::PostTaskAndReply( 872 content::BrowserThread::FILE, 873 FROM_HERE, 874 base::Bind(&FileSystemRetainEntryFunction::SetIsDirectoryOnFileThread, 875 this), 876 base::Bind(&FileSystemRetainEntryFunction::RetainFileEntry, 877 this, 878 entry_id)); 879 return true; 880 } 881 882 saved_files_service->EnqueueFileEntry(extension_->id(), entry_id); 883 SendResponse(true); 884 return true; 885 } 886 887 void FileSystemRetainEntryFunction::RetainFileEntry( 888 const std::string& entry_id) { 889 SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile()); 890 saved_files_service->RegisterFileEntry( 891 extension_->id(), entry_id, path_, is_directory_); 892 saved_files_service->EnqueueFileEntry(extension_->id(), entry_id); 893 SendResponse(true); 894 } 895 896 void FileSystemRetainEntryFunction::SetIsDirectoryOnFileThread() { 897 is_directory_ = base::DirectoryExists(path_); 898 } 899 900 bool FileSystemIsRestorableFunction::RunSync() { 901 std::string entry_id; 902 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id)); 903 SetResult(new base::FundamentalValue(SavedFilesService::Get( 904 GetProfile())->IsRegistered(extension_->id(), entry_id))); 905 return true; 906 } 907 908 bool FileSystemRestoreEntryFunction::RunAsync() { 909 std::string entry_id; 910 bool needs_new_entry; 911 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id)); 912 EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &needs_new_entry)); 913 const SavedFileEntry* file_entry = SavedFilesService::Get( 914 GetProfile())->GetFileEntry(extension_->id(), entry_id); 915 if (!file_entry) { 916 error_ = kUnknownIdError; 917 return false; 918 } 919 920 SavedFilesService::Get(GetProfile()) 921 ->EnqueueFileEntry(extension_->id(), entry_id); 922 923 // Only create a new file entry if the renderer requests one. 924 // |needs_new_entry| will be false if the renderer already has an Entry for 925 // |entry_id|. 926 if (needs_new_entry) { 927 is_directory_ = file_entry->is_directory; 928 CreateResponse(); 929 AddEntryToResponse(file_entry->path, file_entry->id); 930 } 931 SendResponse(true); 932 return true; 933 } 934 935 bool FileSystemObserveDirectoryFunction::RunSync() { 936 NOTIMPLEMENTED(); 937 error_ = kUnknownIdError; 938 return false; 939 } 940 941 bool FileSystemUnobserveEntryFunction::RunSync() { 942 NOTIMPLEMENTED(); 943 error_ = kUnknownIdError; 944 return false; 945 } 946 947 bool FileSystemGetObservedEntriesFunction::RunSync() { 948 NOTIMPLEMENTED(); 949 error_ = kUnknownIdError; 950 return false; 951 } 952 953 } // namespace extensions 954