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