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