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