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