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