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