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