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