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