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