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