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