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