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