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