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