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
61using apps::SavedFileEntry;
62using apps::SavedFilesService;
63using storage::IsolatedContext;
64
65const char kInvalidCallingPage[] = "Invalid calling page. This function can't "
66    "be called from a background page.";
67const char kUserCancelled[] = "User cancelled";
68const char kWritableFileErrorFormat[] = "Error opening %s";
69const char kRequiresFileSystemWriteError[] =
70    "Operation requires fileSystem.write permission";
71const char kRequiresFileSystemDirectoryError[] =
72    "Operation requires fileSystem.directory permission";
73const char kMultipleUnsupportedError[] =
74    "acceptsMultiple: true is not supported for 'saveFile'";
75const char kUnknownIdError[] = "Unknown id";
76
77namespace file_system = extensions::api::file_system;
78namespace ChooseEntry = file_system::ChooseEntry;
79
80namespace {
81
82bool g_skip_picker_for_test = false;
83bool g_use_suggested_path_for_test = false;
84base::FilePath* g_path_to_be_picked_for_test;
85std::vector<base::FilePath>* g_paths_to_be_picked_for_test;
86bool g_skip_directory_confirmation_for_test = false;
87bool 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.
92bool 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.
154const char kLastChooseEntryDirectory[] = "last_choose_file_directory";
155
156const 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
167namespace extensions {
168
169namespace file_system_api {
170
171base::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
183void 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
191std::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
203bool 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
222FileSystemEntryFunction::FileSystemEntryFunction()
223    : multiple_(false),
224      is_directory_(false),
225      response_(NULL) {}
226
227void 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
240void 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
254void 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
263void 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
288void 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
296bool 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
326void 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
339void FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread() {
340  DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
341  if (base::DirectoryExists(path_)) {
342    is_directory_ = true;
343  }
344}
345
346bool 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.
370class 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
482void 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
514void 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
522void 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
530void 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
538void 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
546void FileSystemChooseEntryFunction::StopSkippingPickerForTest() {
547  g_skip_picker_for_test = false;
548}
549
550// static
551void FileSystemChooseEntryFunction::SkipDirectoryConfirmationForTest() {
552  g_skip_directory_confirmation_for_test = true;
553  g_allow_directory_access_for_test = true;
554}
555
556// static
557void FileSystemChooseEntryFunction::AutoCancelDirectoryConfirmationForTest() {
558  g_skip_directory_confirmation_for_test = true;
559  g_allow_directory_access_for_test = false;
560}
561
562// static
563void FileSystemChooseEntryFunction::StopSkippingDirectoryConfirmationForTest() {
564  g_skip_directory_confirmation_for_test = false;
565}
566
567// static
568void 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
580void 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
596void 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
648void FileSystemChooseEntryFunction::FileSelectionCanceled() {
649  error_ = kUserCancelled;
650  SendResponse(false);
651}
652
653void 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
710void 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
721void 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
761void 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
781bool 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
853bool 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
887void 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
896void FileSystemRetainEntryFunction::SetIsDirectoryOnFileThread() {
897  is_directory_ = base::DirectoryExists(path_);
898}
899
900bool 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
908bool 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
935bool FileSystemObserveDirectoryFunction::RunSync() {
936  NOTIMPLEMENTED();
937  error_ = kUnknownIdError;
938  return false;
939}
940
941bool FileSystemUnobserveEntryFunction::RunSync() {
942  NOTIMPLEMENTED();
943  error_ = kUnknownIdError;
944  return false;
945}
946
947bool FileSystemGetObservedEntriesFunction::RunSync() {
948  NOTIMPLEMENTED();
949  error_ = kUnknownIdError;
950  return false;
951}
952
953}  // namespace extensions
954