Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(660)

Side by Side Diff: chrome/browser/extensions/api/file_system/file_system_api.cc

Issue 2934143002: Move chrome.fileSystem implementation to //extensions (Closed)
Patch Set: rebase Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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 <stddef.h>
8
9 #include <memory>
10 #include <set>
11 #include <utility>
12 #include <vector>
13
14 #include "apps/saved_files_service.h"
15 #include "base/bind.h"
16 #include "base/callback.h"
17 #include "base/files/file_path.h"
18 #include "base/files/file_util.h"
19 #include "base/macros.h"
20 #include "base/memory/ptr_util.h"
21 #include "base/path_service.h"
22 #include "base/stl_util.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/stringprintf.h"
25 #include "base/strings/sys_string_conversions.h"
26 #include "base/strings/utf_string_conversions.h"
27 #include "base/task_scheduler/post_task.h"
28 #include "base/value_conversions.h"
29 #include "base/values.h"
30 #include "build/build_config.h"
31 #include "chrome/browser/extensions/api/file_system/file_entry_picker.h"
32 #include "chrome/browser/platform_util.h"
33 #include "chrome/browser/profiles/profile.h"
34 #include "chrome/browser/ui/apps/directory_access_confirmation_dialog.h"
35 #include "chrome/browser/ui/chrome_select_file_policy.h"
36 #include "chrome/common/chrome_paths.h"
37 #include "chrome/common/extensions/api/file_system.h"
38 #include "chrome/grit/generated_resources.h"
39 #include "content/public/browser/browser_thread.h"
40 #include "content/public/browser/child_process_security_policy.h"
41 #include "content/public/browser/render_frame_host.h"
42 #include "content/public/browser/render_process_host.h"
43 #include "content/public/browser/storage_partition.h"
44 #include "content/public/browser/web_contents.h"
45 #include "extensions/browser/api/file_handlers/app_file_handler_util.h"
46 #include "extensions/browser/api/file_system/saved_file_entry.h"
47 #include "extensions/browser/app_window/app_window.h"
48 #include "extensions/browser/app_window/app_window_registry.h"
49 #include "extensions/browser/extension_prefs.h"
50 #include "extensions/browser/extension_system.h"
51 #include "extensions/browser/extension_util.h"
52 #include "extensions/browser/granted_file_entry.h"
53 #include "extensions/browser/path_util.h"
54 #include "extensions/common/permissions/api_permission.h"
55 #include "extensions/common/permissions/permissions_data.h"
56 #include "net/base/mime_util.h"
57 #include "storage/browser/fileapi/external_mount_points.h"
58 #include "storage/browser/fileapi/file_system_operation_runner.h"
59 #include "storage/browser/fileapi/isolated_context.h"
60 #include "storage/common/fileapi/file_system_types.h"
61 #include "storage/common/fileapi/file_system_util.h"
62 #include "ui/base/l10n/l10n_util.h"
63 #include "ui/base/ui_base_types.h"
64 #include "ui/shell_dialogs/select_file_dialog.h"
65 #include "ui/shell_dialogs/select_file_policy.h"
66
67 #if defined(OS_MACOSX)
68 #include <CoreFoundation/CoreFoundation.h>
69 #include "base/mac/foundation_util.h"
70 #endif
71
72 #if defined(OS_CHROMEOS)
73 #include "base/strings/string16.h"
74 #include "chrome/browser/chromeos/file_manager/filesystem_api_util.h"
75 #include "chrome/browser/chromeos/file_manager/volume_manager.h"
76 #include "extensions/browser/event_router.h"
77 #include "extensions/browser/extension_registry.h"
78 #include "extensions/common/constants.h"
79 #include "url/url_constants.h"
80 #endif
81
82 using apps::SavedFilesService;
83 using storage::IsolatedContext;
84
85 const char kInvalidCallingPage[] =
86 "Invalid calling page. "
87 "This function can't be called from a background page.";
88 const char kUserCancelled[] = "User cancelled";
89 const char kWritableFileErrorFormat[] = "Error opening %s";
90 const char kRequiresFileSystemWriteError[] =
91 "Operation requires fileSystem.write permission";
92 const char kRequiresFileSystemDirectoryError[] =
93 "Operation requires fileSystem.directory permission";
94 const char kMultipleUnsupportedError[] =
95 "acceptsMultiple: true is only supported for 'openFile'";
96 const char kUnknownIdError[] = "Unknown id";
97
98 #if !defined(OS_CHROMEOS)
99 const char kNotSupportedOnCurrentPlatformError[] =
100 "Operation not supported on the current platform.";
101 #else
102 const char kNotSupportedOnNonKioskSessionError[] =
103 "Operation only supported for kiosk apps running in a kiosk session.";
104 const char kVolumeNotFoundError[] = "Volume not found.";
105 const char kSecurityError[] = "Security error.";
106 const char kConsentImpossible[] =
107 "Impossible to ask for user consent as there is no app window visible.";
108 #endif
109
110 namespace extensions {
111
112 namespace file_system = api::file_system;
113 namespace ChooseEntry = file_system::ChooseEntry;
114
115 namespace {
116
117 bool g_skip_picker_for_test = false;
118 bool g_use_suggested_path_for_test = false;
119 base::FilePath* g_path_to_be_picked_for_test;
120 std::vector<base::FilePath>* g_paths_to_be_picked_for_test;
121 bool g_skip_directory_confirmation_for_test = false;
122 bool g_allow_directory_access_for_test = false;
123
124 // Expand the mime-types and extensions provided in an AcceptOption, returning
125 // them within the passed extension vector. Returns false if no valid types
126 // were found.
127 bool GetFileTypesFromAcceptOption(
128 const file_system::AcceptOption& accept_option,
129 std::vector<base::FilePath::StringType>* extensions,
130 base::string16* description) {
131 std::set<base::FilePath::StringType> extension_set;
132 int description_id = 0;
133
134 if (accept_option.mime_types.get()) {
135 std::vector<std::string>* list = accept_option.mime_types.get();
136 bool valid_type = false;
137 for (std::vector<std::string>::const_iterator iter = list->begin();
138 iter != list->end(); ++iter) {
139 std::vector<base::FilePath::StringType> inner;
140 std::string accept_type = base::ToLowerASCII(*iter);
141 net::GetExtensionsForMimeType(accept_type, &inner);
142 if (inner.empty())
143 continue;
144
145 if (valid_type)
146 description_id = 0; // We already have an accept type with label; if
147 // we find another, give up and use the default.
148 else if (accept_type == "image/*")
149 description_id = IDS_IMAGE_FILES;
150 else if (accept_type == "audio/*")
151 description_id = IDS_AUDIO_FILES;
152 else if (accept_type == "video/*")
153 description_id = IDS_VIDEO_FILES;
154
155 extension_set.insert(inner.begin(), inner.end());
156 valid_type = true;
157 }
158 }
159
160 if (accept_option.extensions.get()) {
161 std::vector<std::string>* list = accept_option.extensions.get();
162 for (std::vector<std::string>::const_iterator iter = list->begin();
163 iter != list->end(); ++iter) {
164 std::string extension = base::ToLowerASCII(*iter);
165 #if defined(OS_WIN)
166 extension_set.insert(base::UTF8ToWide(*iter));
167 #else
168 extension_set.insert(*iter);
169 #endif
170 }
171 }
172
173 extensions->assign(extension_set.begin(), extension_set.end());
174 if (extensions->empty())
175 return false;
176
177 if (accept_option.description.get())
178 *description = base::UTF8ToUTF16(*accept_option.description);
179 else if (description_id)
180 *description = l10n_util::GetStringUTF16(description_id);
181
182 return true;
183 }
184
185 // Key for the path of the directory of the file last chosen by the user in
186 // response to a chrome.fileSystem.chooseEntry() call.
187 const char kLastChooseEntryDirectory[] = "last_choose_file_directory";
188
189 const int kGraylistedPaths[] = {
190 base::DIR_HOME,
191 #if defined(OS_WIN)
192 base::DIR_PROGRAM_FILES, base::DIR_PROGRAM_FILESX86, base::DIR_WINDOWS,
193 #endif
194 };
195
196 typedef base::Callback<void(std::unique_ptr<base::File::Info>)>
197 FileInfoOptCallback;
198
199 // Passes optional file info to the UI thread depending on |result| and |info|.
200 void PassFileInfoToUIThread(const FileInfoOptCallback& callback,
201 base::File::Error result,
202 const base::File::Info& info) {
203 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
204 std::unique_ptr<base::File::Info> file_info(
205 result == base::File::FILE_OK ? new base::File::Info(info) : NULL);
206 content::BrowserThread::PostTask(
207 content::BrowserThread::UI, FROM_HERE,
208 base::BindOnce(callback, base::Passed(&file_info)));
209 }
210
211 // Gets a WebContents instance handle for a platform app hosted in
212 // |render_frame_host|. If not found, then returns NULL.
213 content::WebContents* GetWebContentsForRenderFrameHost(
214 content::BrowserContext* browser_context,
215 content::RenderFrameHost* render_frame_host) {
216 content::WebContents* web_contents =
217 content::WebContents::FromRenderFrameHost(render_frame_host);
218 // Check if there is an app window associated with the web contents; if not,
219 // return null.
220 return AppWindowRegistry::Get(browser_context)
221 ->GetAppWindowForWebContents(web_contents)
222 ? web_contents
223 : nullptr;
224 }
225
226 #if defined(OS_CHROMEOS)
227 // Fills a list of volumes mounted in the system.
228 void FillVolumeList(Profile* profile,
229 std::vector<api::file_system::Volume>* result) {
230 file_manager::VolumeManager* const volume_manager =
231 file_manager::VolumeManager::Get(profile);
232 DCHECK(volume_manager);
233
234 const auto& volume_list = volume_manager->GetVolumeList();
235 // Convert volume_list to result_volume_list.
236 for (const auto& volume : volume_list) {
237 api::file_system::Volume result_volume;
238 result_volume.volume_id = volume->volume_id();
239 result_volume.writable = !volume->is_read_only();
240 result->push_back(std::move(result_volume));
241 }
242 }
243 #endif
244
245 // Creates and shows a SelectFileDialog, or returns false if the dialog could
246 // not be created.
247 bool ShowSelectFileDialog(
248 scoped_refptr<UIThreadExtensionFunction> extension_function,
249 ui::SelectFileDialog::Type type,
250 const base::FilePath& default_path,
251 const ui::SelectFileDialog::FileTypeInfo* file_types,
252 FileEntryPicker::FilesSelectedCallback files_selected_callback,
253 base::OnceClosure file_selection_canceled_callback) {
254 // TODO(asargent/benwells) - As a short term remediation for
255 // crbug.com/179010 we're adding the ability for a whitelisted extension to
256 // use this API since chrome.fileBrowserHandler.selectFile is ChromeOS-only.
257 // Eventually we'd like a better solution and likely this code will go back
258 // to being platform-app only.
259 content::WebContents* const web_contents =
260 extension_function->extension()->is_platform_app()
261 ? GetWebContentsForRenderFrameHost(
262 extension_function->browser_context(),
263 extension_function->render_frame_host())
264 : extension_function->GetAssociatedWebContents();
265 if (!web_contents)
266 return false;
267
268 // The file picker will hold a reference to the UIThreadExtensionFunction
269 // instance, preventing its destruction (and subsequent sending of the
270 // function response) until the user has selected a file or cancelled the
271 // picker. At that point, the picker will delete itself, which will also free
272 // the function instance.
273 new FileEntryPicker(web_contents, default_path, *file_types, type,
274 std::move(files_selected_callback),
275 std::move(file_selection_canceled_callback));
276 return true;
277 }
278
279 } // namespace
280
281 namespace file_system_api {
282
283 base::FilePath GetLastChooseEntryDirectory(const ExtensionPrefs* prefs,
284 const std::string& extension_id) {
285 base::FilePath path;
286 std::string string_path;
287 if (prefs->ReadPrefAsString(extension_id, kLastChooseEntryDirectory,
288 &string_path)) {
289 path = base::FilePath::FromUTF8Unsafe(string_path);
290 }
291 return path;
292 }
293
294 void SetLastChooseEntryDirectory(ExtensionPrefs* prefs,
295 const std::string& extension_id,
296 const base::FilePath& path) {
297 prefs->UpdateExtensionPref(extension_id, kLastChooseEntryDirectory,
298 base::CreateFilePathValue(path));
299 }
300
301 #if defined(OS_CHROMEOS)
302 void DispatchVolumeListChangeEvent(Profile* profile) {
303 DCHECK(profile);
304 EventRouter* const event_router = EventRouter::Get(profile);
305 if (!event_router) // Possible on shutdown.
306 return;
307
308 ExtensionRegistry* const registry = ExtensionRegistry::Get(profile);
309 if (!registry) // Possible on shutdown.
310 return;
311
312 ConsentProviderDelegate consent_provider_delegate(profile, nullptr);
313 ConsentProvider consent_provider(&consent_provider_delegate);
314 api::file_system::VolumeListChangedEvent event_args;
315 FillVolumeList(profile, &event_args.volumes);
316 for (const auto& extension : registry->enabled_extensions()) {
317 if (!consent_provider.IsGrantable(*extension.get()))
318 continue;
319 event_router->DispatchEventToExtension(
320 extension->id(),
321 base::MakeUnique<Event>(
322 events::FILE_SYSTEM_ON_VOLUME_LIST_CHANGED,
323 api::file_system::OnVolumeListChanged::kEventName,
324 api::file_system::OnVolumeListChanged::Create(event_args)));
325 }
326 }
327 #endif
328
329 } // namespace file_system_api
330
331 #if defined(OS_CHROMEOS)
332 using file_system_api::ConsentProvider;
333 #endif
334
335 ExtensionFunction::ResponseAction FileSystemGetDisplayPathFunction::Run() {
336 std::string filesystem_name;
337 std::string filesystem_path;
338 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
339 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
340
341 base::FilePath file_path;
342 std::string error;
343 if (!app_file_handler_util::ValidateFileEntryAndGetPath(
344 filesystem_name, filesystem_path,
345 render_frame_host()->GetProcess()->GetID(), &file_path, &error)) {
346 return RespondNow(Error(error));
347 }
348
349 file_path = path_util::PrettifyPath(file_path);
350 return RespondNow(
351 OneArgument(base::MakeUnique<base::Value>(file_path.value())));
352 }
353
354 FileSystemEntryFunction::FileSystemEntryFunction()
355 : multiple_(false), is_directory_(false) {}
356
357 void FileSystemEntryFunction::PrepareFilesForWritableApp(
358 const std::vector<base::FilePath>& paths) {
359 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
360 // TODO(cmihail): Path directory set should be initialized only with the
361 // paths that are actually directories, but for now we will consider
362 // all paths directories in case is_directory_ is true, otherwise
363 // all paths files, as this was the previous logic.
364 std::set<base::FilePath> path_directory_set_ =
365 is_directory_ ? std::set<base::FilePath>(paths.begin(), paths.end())
366 : std::set<base::FilePath>{};
367 app_file_handler_util::PrepareFilesForWritableApp(
368 paths, GetProfile(), path_directory_set_,
369 base::Bind(&FileSystemEntryFunction::RegisterFileSystemsAndSendResponse,
370 this, paths),
371 base::Bind(&FileSystemEntryFunction::HandleWritableFileError, this));
372 }
373
374 void FileSystemEntryFunction::RegisterFileSystemsAndSendResponse(
375 const std::vector<base::FilePath>& paths) {
376 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
377 if (!render_frame_host())
378 return;
379
380 std::unique_ptr<base::DictionaryValue> result = CreateResult();
381 for (const auto& path : paths)
382 AddEntryToResult(path, std::string(), result.get());
383 SetResult(std::move(result));
384 SendResponse(true);
385 }
386
387 std::unique_ptr<base::DictionaryValue> FileSystemEntryFunction::CreateResult() {
388 std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
389 result->Set("entries", base::MakeUnique<base::ListValue>());
390 result->SetBoolean("multiple", multiple_);
391 return result;
392 }
393
394 void FileSystemEntryFunction::AddEntryToResult(const base::FilePath& path,
395 const std::string& id_override,
396 base::DictionaryValue* result) {
397 GrantedFileEntry file_entry = app_file_handler_util::CreateFileEntry(
398 GetProfile(), extension(), render_frame_host()->GetProcess()->GetID(),
399 path, is_directory_);
400 base::ListValue* entries;
401 bool success = result->GetList("entries", &entries);
402 DCHECK(success);
403
404 std::unique_ptr<base::DictionaryValue> entry(new base::DictionaryValue());
405 entry->SetString("fileSystemId", file_entry.filesystem_id);
406 entry->SetString("baseName", file_entry.registered_name);
407 if (id_override.empty())
408 entry->SetString("id", file_entry.id);
409 else
410 entry->SetString("id", id_override);
411 entry->SetBoolean("isDirectory", is_directory_);
412 entries->Append(std::move(entry));
413 }
414
415 void FileSystemEntryFunction::HandleWritableFileError(
416 const base::FilePath& error_path) {
417 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
418 error_ = base::StringPrintf(kWritableFileErrorFormat,
419 error_path.BaseName().AsUTF8Unsafe().c_str());
420 SendResponse(false);
421 }
422
423 bool FileSystemGetWritableEntryFunction::RunAsync() {
424 std::string filesystem_name;
425 std::string filesystem_path;
426 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
427 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
428
429 if (!app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
430 error_ = kRequiresFileSystemWriteError;
431 return false;
432 }
433
434 if (!app_file_handler_util::ValidateFileEntryAndGetPath(
435 filesystem_name, filesystem_path,
436 render_frame_host()->GetProcess()->GetID(), &path_, &error_))
437 return false;
438
439 base::PostTaskWithTraitsAndReply(
440 FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
441 base::BindOnce(&FileSystemGetWritableEntryFunction::SetIsDirectoryAsync,
442 this),
443 base::BindOnce(
444 &FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse,
445 this));
446 return true;
447 }
448
449 void FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse() {
450 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
451 if (is_directory_ && !extension_->permissions_data()->HasAPIPermission(
452 APIPermission::kFileSystemDirectory)) {
453 error_ = kRequiresFileSystemDirectoryError;
454 SendResponse(false);
455 }
456 std::vector<base::FilePath> paths;
457 paths.push_back(path_);
458 PrepareFilesForWritableApp(paths);
459 }
460
461 void FileSystemGetWritableEntryFunction::SetIsDirectoryAsync() {
462 if (base::DirectoryExists(path_)) {
463 is_directory_ = true;
464 }
465 }
466
467 ExtensionFunction::ResponseAction FileSystemIsWritableEntryFunction::Run() {
468 std::string filesystem_name;
469 std::string filesystem_path;
470 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
471 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
472
473 std::string filesystem_id;
474 if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id))
475 return RespondNow(Error(app_file_handler_util::kInvalidParameters));
476
477 content::ChildProcessSecurityPolicy* policy =
478 content::ChildProcessSecurityPolicy::GetInstance();
479 int renderer_id = render_frame_host()->GetProcess()->GetID();
480 bool is_writable = policy->CanReadWriteFileSystem(renderer_id, filesystem_id);
481
482 return RespondNow(OneArgument(base::MakeUnique<base::Value>(is_writable)));
483 }
484
485 void FileSystemChooseEntryFunction::ShowPicker(
486 const ui::SelectFileDialog::FileTypeInfo& file_type_info,
487 ui::SelectFileDialog::Type picker_type) {
488 if (g_skip_picker_for_test) {
489 std::vector<base::FilePath> test_paths;
490 if (g_use_suggested_path_for_test)
491 test_paths.push_back(initial_path_);
492 else if (g_path_to_be_picked_for_test)
493 test_paths.push_back(*g_path_to_be_picked_for_test);
494 else if (g_paths_to_be_picked_for_test)
495 test_paths = *g_paths_to_be_picked_for_test;
496
497 content::BrowserThread::PostTask(
498 content::BrowserThread::UI, FROM_HERE,
499 test_paths.size() > 0
500 ? base::BindOnce(&FileSystemChooseEntryFunction::FilesSelected,
501 this, test_paths)
502 : base::BindOnce(
503 &FileSystemChooseEntryFunction::FileSelectionCanceled, this));
504 return;
505 }
506
507 // The callbacks passed to the dialog will retain references to this
508 // UIThreadExtenisonFunction, preventing its destruction (and subsequent
509 // sending of the function response) until the user has selected a file or
510 // cancelled the picker.
511 if (!ShowSelectFileDialog(
512 this, picker_type, initial_path_, &file_type_info,
513 base::BindOnce(&FileSystemChooseEntryFunction::FilesSelected, this),
514 base::BindOnce(&FileSystemChooseEntryFunction::FileSelectionCanceled,
515 this))) {
516 error_ = kInvalidCallingPage;
517 SendResponse(false);
518 }
519 }
520
521 // static
522 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
523 base::FilePath* path) {
524 g_skip_picker_for_test = true;
525 g_use_suggested_path_for_test = false;
526 g_path_to_be_picked_for_test = path;
527 g_paths_to_be_picked_for_test = NULL;
528 }
529
530 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathsForTest(
531 std::vector<base::FilePath>* paths) {
532 g_skip_picker_for_test = true;
533 g_use_suggested_path_for_test = false;
534 g_paths_to_be_picked_for_test = paths;
535 }
536
537 // static
538 void FileSystemChooseEntryFunction::SkipPickerAndSelectSuggestedPathForTest() {
539 g_skip_picker_for_test = true;
540 g_use_suggested_path_for_test = true;
541 g_path_to_be_picked_for_test = NULL;
542 g_paths_to_be_picked_for_test = NULL;
543 }
544
545 // static
546 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysCancelForTest() {
547 g_skip_picker_for_test = true;
548 g_use_suggested_path_for_test = false;
549 g_path_to_be_picked_for_test = NULL;
550 g_paths_to_be_picked_for_test = NULL;
551 }
552
553 // static
554 void FileSystemChooseEntryFunction::StopSkippingPickerForTest() {
555 g_skip_picker_for_test = false;
556 }
557
558 // static
559 void FileSystemChooseEntryFunction::SkipDirectoryConfirmationForTest() {
560 g_skip_directory_confirmation_for_test = true;
561 g_allow_directory_access_for_test = true;
562 }
563
564 // static
565 void FileSystemChooseEntryFunction::AutoCancelDirectoryConfirmationForTest() {
566 g_skip_directory_confirmation_for_test = true;
567 g_allow_directory_access_for_test = false;
568 }
569
570 // static
571 void FileSystemChooseEntryFunction::StopSkippingDirectoryConfirmationForTest() {
572 g_skip_directory_confirmation_for_test = false;
573 }
574
575 // static
576 void FileSystemChooseEntryFunction::RegisterTempExternalFileSystemForTest(
577 const std::string& name,
578 const base::FilePath& path) {
579 // For testing on Chrome OS, where to deal with remote and local paths
580 // smoothly, all accessed paths need to be registered in the list of
581 // external mount points.
582 storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
583 name, storage::kFileSystemTypeNativeLocal,
584 storage::FileSystemMountOption(), path);
585 }
586
587 void FileSystemChooseEntryFunction::FilesSelected(
588 const std::vector<base::FilePath>& paths) {
589 DCHECK(!paths.empty());
590 base::FilePath last_choose_directory;
591 if (is_directory_) {
592 last_choose_directory = paths[0];
593 } else {
594 last_choose_directory = paths[0].DirName();
595 }
596 file_system_api::SetLastChooseEntryDirectory(
597 ExtensionPrefs::Get(GetProfile()), extension()->id(),
598 last_choose_directory);
599 if (is_directory_) {
600 // Get the WebContents for the app window to be the parent window of the
601 // confirmation dialog if necessary.
602 content::WebContents* const web_contents =
603 GetWebContentsForRenderFrameHost(GetProfile(), render_frame_host());
604 if (!web_contents) {
605 error_ = kInvalidCallingPage;
606 SendResponse(false);
607 return;
608 }
609
610 DCHECK_EQ(paths.size(), 1u);
611 bool non_native_path = false;
612 #if defined(OS_CHROMEOS)
613 non_native_path =
614 file_manager::util::IsUnderNonNativeLocalPath(GetProfile(), paths[0]);
615 #endif
616
617 base::PostTaskWithTraits(
618 FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
619 base::BindOnce(
620 &FileSystemChooseEntryFunction::ConfirmDirectoryAccessAsync, this,
621 non_native_path, paths, web_contents));
622 return;
623 }
624
625 OnDirectoryAccessConfirmed(paths);
626 }
627
628 void FileSystemChooseEntryFunction::FileSelectionCanceled() {
629 error_ = kUserCancelled;
630 SendResponse(false);
631 }
632
633 void FileSystemChooseEntryFunction::ConfirmDirectoryAccessAsync(
634 bool non_native_path,
635 const std::vector<base::FilePath>& paths,
636 content::WebContents* web_contents) {
637 const base::FilePath check_path =
638 non_native_path ? paths[0] : base::MakeAbsoluteFilePath(paths[0]);
639 if (check_path.empty()) {
640 content::BrowserThread::PostTask(
641 content::BrowserThread::UI, FROM_HERE,
642 base::BindOnce(&FileSystemChooseEntryFunction::FileSelectionCanceled,
643 this));
644 return;
645 }
646
647 for (size_t i = 0; i < arraysize(kGraylistedPaths); i++) {
648 base::FilePath graylisted_path;
649 if (PathService::Get(kGraylistedPaths[i], &graylisted_path) &&
650 (check_path == graylisted_path ||
651 check_path.IsParent(graylisted_path))) {
652 if (g_skip_directory_confirmation_for_test) {
653 if (g_allow_directory_access_for_test) {
654 break;
655 } else {
656 content::BrowserThread::PostTask(
657 content::BrowserThread::UI, FROM_HERE,
658 base::BindOnce(
659 &FileSystemChooseEntryFunction::FileSelectionCanceled, this));
660 }
661 return;
662 }
663
664 content::BrowserThread::PostTask(
665 content::BrowserThread::UI, FROM_HERE,
666 base::BindOnce(
667 CreateDirectoryAccessConfirmationDialog,
668 app_file_handler_util::HasFileSystemWritePermission(
669 extension_.get()),
670 base::UTF8ToUTF16(extension_->name()), web_contents,
671 base::Bind(
672 &FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed,
673 this, paths),
674 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
675 this)));
676 return;
677 }
678 }
679
680 content::BrowserThread::PostTask(
681 content::BrowserThread::UI, FROM_HERE,
682 base::BindOnce(&FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed,
683 this, paths));
684 }
685
686 void FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed(
687 const std::vector<base::FilePath>& paths) {
688 if (app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
689 PrepareFilesForWritableApp(paths);
690 return;
691 }
692
693 // Don't need to check the file, it's for reading.
694 RegisterFileSystemsAndSendResponse(paths);
695 }
696
697 void FileSystemChooseEntryFunction::BuildFileTypeInfo(
698 ui::SelectFileDialog::FileTypeInfo* file_type_info,
699 const base::FilePath::StringType& suggested_extension,
700 const AcceptOptions* accepts,
701 const bool* acceptsAllTypes) {
702 file_type_info->include_all_files = true;
703 if (acceptsAllTypes)
704 file_type_info->include_all_files = *acceptsAllTypes;
705
706 bool need_suggestion =
707 !file_type_info->include_all_files && !suggested_extension.empty();
708
709 if (accepts) {
710 for (const file_system::AcceptOption& option : *accepts) {
711 base::string16 description;
712 std::vector<base::FilePath::StringType> extensions;
713
714 if (!GetFileTypesFromAcceptOption(option, &extensions, &description))
715 continue; // No extensions were found.
716
717 file_type_info->extensions.push_back(extensions);
718 file_type_info->extension_description_overrides.push_back(description);
719
720 // If we still need to find suggested_extension, hunt for it inside the
721 // extensions returned from GetFileTypesFromAcceptOption.
722 if (need_suggestion &&
723 base::ContainsValue(extensions, suggested_extension)) {
724 need_suggestion = false;
725 }
726 }
727 }
728
729 // If there's nothing in our accepted extension list or we couldn't find the
730 // suggested extension required, then default to accepting all types.
731 if (file_type_info->extensions.empty() || need_suggestion)
732 file_type_info->include_all_files = true;
733 }
734
735 void FileSystemChooseEntryFunction::BuildSuggestion(
736 const std::string* opt_name,
737 base::FilePath* suggested_name,
738 base::FilePath::StringType* suggested_extension) {
739 if (opt_name) {
740 *suggested_name = base::FilePath::FromUTF8Unsafe(*opt_name);
741
742 // Don't allow any path components; shorten to the base name. This should
743 // result in a relative path, but in some cases may not. Clear the
744 // suggestion for safety if this is the case.
745 *suggested_name = suggested_name->BaseName();
746 if (suggested_name->IsAbsolute())
747 *suggested_name = base::FilePath();
748
749 *suggested_extension = suggested_name->Extension();
750 if (!suggested_extension->empty())
751 suggested_extension->erase(suggested_extension->begin()); // drop the .
752 }
753 }
754
755 void FileSystemChooseEntryFunction::SetInitialPathAndShowPicker(
756 const base::FilePath& previous_path,
757 const base::FilePath& suggested_name,
758 const ui::SelectFileDialog::FileTypeInfo& file_type_info,
759 ui::SelectFileDialog::Type picker_type,
760 bool is_previous_path_directory) {
761 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
762 if (is_previous_path_directory) {
763 initial_path_ = previous_path.Append(suggested_name);
764 } else {
765 base::FilePath documents_dir;
766 if (PathService::Get(chrome::DIR_USER_DOCUMENTS, &documents_dir)) {
767 initial_path_ = documents_dir.Append(suggested_name);
768 } else {
769 initial_path_ = suggested_name;
770 }
771 }
772 ShowPicker(file_type_info, picker_type);
773 }
774
775 bool FileSystemChooseEntryFunction::RunAsync() {
776 std::unique_ptr<ChooseEntry::Params> params(
777 ChooseEntry::Params::Create(*args_));
778 EXTENSION_FUNCTION_VALIDATE(params.get());
779
780 base::FilePath suggested_name;
781 ui::SelectFileDialog::FileTypeInfo file_type_info;
782 ui::SelectFileDialog::Type picker_type =
783 ui::SelectFileDialog::SELECT_OPEN_FILE;
784
785 file_system::ChooseEntryOptions* options = params->options.get();
786 if (options) {
787 multiple_ = options->accepts_multiple && *options->accepts_multiple;
788 if (multiple_)
789 picker_type = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
790
791 if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENWRITABLEFILE &&
792 !app_file_handler_util::HasFileSystemWritePermission(
793 extension_.get())) {
794 error_ = kRequiresFileSystemWriteError;
795 return false;
796 } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_SAVEFILE) {
797 if (!app_file_handler_util::HasFileSystemWritePermission(
798 extension_.get())) {
799 error_ = kRequiresFileSystemWriteError;
800 return false;
801 }
802 if (multiple_) {
803 error_ = kMultipleUnsupportedError;
804 return false;
805 }
806 picker_type = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
807 } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENDIRECTORY) {
808 is_directory_ = true;
809 if (!extension_->permissions_data()->HasAPIPermission(
810 APIPermission::kFileSystemDirectory)) {
811 error_ = kRequiresFileSystemDirectoryError;
812 return false;
813 }
814 if (multiple_) {
815 error_ = kMultipleUnsupportedError;
816 return false;
817 }
818 picker_type = ui::SelectFileDialog::SELECT_FOLDER;
819 }
820
821 base::FilePath::StringType suggested_extension;
822 BuildSuggestion(options->suggested_name.get(), &suggested_name,
823 &suggested_extension);
824
825 BuildFileTypeInfo(&file_type_info, suggested_extension,
826 options->accepts.get(), options->accepts_all_types.get());
827 }
828
829 file_type_info.allowed_paths = ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
830
831 base::FilePath previous_path = file_system_api::GetLastChooseEntryDirectory(
832 ExtensionPrefs::Get(GetProfile()), extension()->id());
833
834 if (previous_path.empty()) {
835 SetInitialPathAndShowPicker(previous_path, suggested_name, file_type_info,
836 picker_type, false);
837 return true;
838 }
839
840 base::Callback<void(bool)> set_initial_path_callback = base::Bind(
841 &FileSystemChooseEntryFunction::SetInitialPathAndShowPicker, this,
842 previous_path, suggested_name, file_type_info, picker_type);
843
844 // Check whether the |previous_path| is a non-native directory.
845 #if defined(OS_CHROMEOS)
846 if (file_manager::util::IsUnderNonNativeLocalPath(GetProfile(),
847 previous_path)) {
848 file_manager::util::IsNonNativeLocalPathDirectory(
849 GetProfile(), previous_path, set_initial_path_callback);
850 return true;
851 }
852 #endif
853 base::PostTaskWithTraitsAndReplyWithResult(
854 FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
855 base::Bind(&base::DirectoryExists, previous_path),
856 set_initial_path_callback);
857
858 return true;
859 }
860
861 bool FileSystemRetainEntryFunction::RunAsync() {
862 std::string entry_id;
863 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
864 SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile());
865 // Add the file to the retain list if it is not already on there.
866 if (!saved_files_service->IsRegistered(extension_->id(), entry_id)) {
867 std::string filesystem_name;
868 std::string filesystem_path;
869 base::FilePath path;
870 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_name));
871 EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &filesystem_path));
872 if (!app_file_handler_util::ValidateFileEntryAndGetPath(
873 filesystem_name, filesystem_path,
874 render_frame_host()->GetProcess()->GetID(), &path, &error_)) {
875 return false;
876 }
877
878 std::string filesystem_id;
879 if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id))
880 return false;
881
882 const GURL site = util::GetSiteForExtensionId(extension_id(), GetProfile());
883 storage::FileSystemContext* const context =
884 content::BrowserContext::GetStoragePartitionForSite(GetProfile(), site)
885 ->GetFileSystemContext();
886 const storage::FileSystemURL url = context->CreateCrackedFileSystemURL(
887 site, storage::kFileSystemTypeIsolated,
888 IsolatedContext::GetInstance()
889 ->CreateVirtualRootPath(filesystem_id)
890 .Append(base::FilePath::FromUTF8Unsafe(filesystem_path)));
891
892 content::BrowserThread::PostTask(
893 content::BrowserThread::IO, FROM_HERE,
894 base::BindOnce(
895 base::IgnoreResult(
896 &storage::FileSystemOperationRunner::GetMetadata),
897 context->operation_runner()->AsWeakPtr(), url,
898 storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY,
899 base::Bind(
900 &PassFileInfoToUIThread,
901 base::Bind(&FileSystemRetainEntryFunction::RetainFileEntry,
902 this, entry_id, path))));
903 return true;
904 }
905
906 saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
907 SendResponse(true);
908 return true;
909 }
910
911 void FileSystemRetainEntryFunction::RetainFileEntry(
912 const std::string& entry_id,
913 const base::FilePath& path,
914 std::unique_ptr<base::File::Info> file_info) {
915 if (!file_info) {
916 SendResponse(false);
917 return;
918 }
919
920 SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile());
921 saved_files_service->RegisterFileEntry(extension_->id(), entry_id, path,
922 file_info->is_directory);
923 saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
924 SendResponse(true);
925 }
926
927 ExtensionFunction::ResponseAction FileSystemIsRestorableFunction::Run() {
928 std::string entry_id;
929 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
930 return RespondNow(OneArgument(base::MakeUnique<base::Value>(
931 SavedFilesService::Get(Profile::FromBrowserContext(browser_context()))
932 ->IsRegistered(extension_->id(), entry_id))));
933 }
934
935 bool FileSystemRestoreEntryFunction::RunAsync() {
936 std::string entry_id;
937 bool needs_new_entry;
938 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
939 EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &needs_new_entry));
940 const SavedFileEntry* file_entry =
941 SavedFilesService::Get(GetProfile())
942 ->GetFileEntry(extension_->id(), entry_id);
943 if (!file_entry) {
944 error_ = kUnknownIdError;
945 return false;
946 }
947
948 SavedFilesService::Get(GetProfile())
949 ->EnqueueFileEntry(extension_->id(), entry_id);
950
951 // Only create a new file entry if the renderer requests one.
952 // |needs_new_entry| will be false if the renderer already has an Entry for
953 // |entry_id|.
954 if (needs_new_entry) {
955 is_directory_ = file_entry->is_directory;
956 std::unique_ptr<base::DictionaryValue> result = CreateResult();
957 AddEntryToResult(file_entry->path, file_entry->id, result.get());
958 SetResult(std::move(result));
959 }
960 SendResponse(true);
961 return true;
962 }
963
964 ExtensionFunction::ResponseAction FileSystemObserveDirectoryFunction::Run() {
965 NOTIMPLEMENTED();
966 return RespondNow(Error(kUnknownIdError));
967 }
968
969 ExtensionFunction::ResponseAction FileSystemUnobserveEntryFunction::Run() {
970 NOTIMPLEMENTED();
971 return RespondNow(Error(kUnknownIdError));
972 }
973
974 ExtensionFunction::ResponseAction FileSystemGetObservedEntriesFunction::Run() {
975 NOTIMPLEMENTED();
976 return RespondNow(Error(kUnknownIdError));
977 }
978
979 #if !defined(OS_CHROMEOS)
980 ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() {
981 using api::file_system::RequestFileSystem::Params;
982 const std::unique_ptr<Params> params(Params::Create(*args_));
983 EXTENSION_FUNCTION_VALIDATE(params);
984
985 NOTIMPLEMENTED();
986 return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
987 }
988
989 ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
990 NOTIMPLEMENTED();
991 return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
992 }
993 #else
994
995 FileSystemRequestFileSystemFunction::FileSystemRequestFileSystemFunction()
996 : chrome_details_(this) {}
997
998 FileSystemRequestFileSystemFunction::~FileSystemRequestFileSystemFunction() {}
999
1000 ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() {
1001 using api::file_system::RequestFileSystem::Params;
1002 const std::unique_ptr<Params> params(Params::Create(*args_));
1003 EXTENSION_FUNCTION_VALIDATE(params);
1004
1005 // Only kiosk apps in kiosk sessions can use this API.
1006 // Additionally it is enabled for whitelisted component extensions and apps.
1007 file_system_api::ConsentProviderDelegate consent_provider_delegate(
1008 chrome_details_.GetProfile(), render_frame_host());
1009 file_system_api::ConsentProvider consent_provider(&consent_provider_delegate);
1010
1011 if (!consent_provider.IsGrantable(*extension()))
1012 return RespondNow(Error(kNotSupportedOnNonKioskSessionError));
1013
1014 using file_manager::VolumeManager;
1015 using file_manager::Volume;
1016 VolumeManager* const volume_manager =
1017 VolumeManager::Get(chrome_details_.GetProfile());
1018 DCHECK(volume_manager);
1019
1020 const bool writable =
1021 params->options.writable.get() && *params->options.writable.get();
1022 if (writable &&
1023 !app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
1024 return RespondNow(Error(kRequiresFileSystemWriteError));
1025 }
1026
1027 base::WeakPtr<file_manager::Volume> volume =
1028 volume_manager->FindVolumeById(params->options.volume_id);
1029 if (!volume.get())
1030 return RespondNow(Error(kVolumeNotFoundError));
1031
1032 const GURL site =
1033 util::GetSiteForExtensionId(extension_id(), chrome_details_.GetProfile());
1034 scoped_refptr<storage::FileSystemContext> file_system_context =
1035 content::BrowserContext::GetStoragePartitionForSite(
1036 chrome_details_.GetProfile(), site)
1037 ->GetFileSystemContext();
1038 storage::ExternalFileSystemBackend* const backend =
1039 file_system_context->external_backend();
1040 DCHECK(backend);
1041
1042 base::FilePath virtual_path;
1043 if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path))
1044 return RespondNow(Error(kSecurityError));
1045
1046 if (writable && (volume->is_read_only()))
1047 return RespondNow(Error(kSecurityError));
1048
1049 consent_provider.RequestConsent(
1050 *extension(), volume, writable,
1051 base::Bind(&FileSystemRequestFileSystemFunction::OnConsentReceived, this,
1052 volume, writable));
1053 return RespondLater();
1054 }
1055
1056 void FileSystemRequestFileSystemFunction::OnConsentReceived(
1057 const base::WeakPtr<file_manager::Volume>& volume,
1058 bool writable,
1059 ConsentProvider::Consent result) {
1060 using file_manager::VolumeManager;
1061 using file_manager::Volume;
1062
1063 // Render frame host can be gone before this callback method is executed.
1064 if (!render_frame_host()) {
1065 Respond(Error(""));
1066 return;
1067 }
1068
1069 switch (result) {
1070 case ConsentProvider::CONSENT_REJECTED:
1071 Respond(Error(kSecurityError));
1072 return;
1073
1074 case ConsentProvider::CONSENT_IMPOSSIBLE:
1075 Respond(Error(kConsentImpossible));
1076 return;
1077
1078 case ConsentProvider::CONSENT_GRANTED:
1079 break;
1080 }
1081
1082 if (!volume.get()) {
1083 Respond(Error(kVolumeNotFoundError));
1084 return;
1085 }
1086
1087 const GURL site =
1088 util::GetSiteForExtensionId(extension_id(), chrome_details_.GetProfile());
1089 scoped_refptr<storage::FileSystemContext> file_system_context =
1090 content::BrowserContext::GetStoragePartitionForSite(
1091 chrome_details_.GetProfile(), site)
1092 ->GetFileSystemContext();
1093 storage::ExternalFileSystemBackend* const backend =
1094 file_system_context->external_backend();
1095 DCHECK(backend);
1096
1097 base::FilePath virtual_path;
1098 if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) {
1099 Respond(Error(kSecurityError));
1100 return;
1101 }
1102
1103 storage::IsolatedContext* const isolated_context =
1104 storage::IsolatedContext::GetInstance();
1105 DCHECK(isolated_context);
1106
1107 const storage::FileSystemURL original_url =
1108 file_system_context->CreateCrackedFileSystemURL(
1109 GURL(std::string(kExtensionScheme) + url::kStandardSchemeSeparator +
1110 extension_id()),
1111 storage::kFileSystemTypeExternal, virtual_path);
1112
1113 // Set a fixed register name, as the automatic one would leak the mount point
1114 // directory.
1115 std::string register_name = "fs";
1116 const std::string file_system_id =
1117 isolated_context->RegisterFileSystemForPath(
1118 storage::kFileSystemTypeNativeForPlatformApp,
1119 std::string() /* file_system_id */, original_url.path(),
1120 &register_name);
1121 if (file_system_id.empty()) {
1122 Respond(Error(kSecurityError));
1123 return;
1124 }
1125
1126 backend->GrantFileAccessToExtension(extension_->id(), virtual_path);
1127
1128 // Grant file permissions to the renderer hosting component.
1129 content::ChildProcessSecurityPolicy* policy =
1130 content::ChildProcessSecurityPolicy::GetInstance();
1131 DCHECK(policy);
1132
1133 // Read-only permisisons.
1134 policy->GrantReadFile(render_frame_host()->GetProcess()->GetID(),
1135 volume->mount_path());
1136 policy->GrantReadFileSystem(render_frame_host()->GetProcess()->GetID(),
1137 file_system_id);
1138
1139 // Additional write permissions.
1140 if (writable) {
1141 policy->GrantCreateReadWriteFile(render_frame_host()->GetProcess()->GetID(),
1142 volume->mount_path());
1143 policy->GrantCopyInto(render_frame_host()->GetProcess()->GetID(),
1144 volume->mount_path());
1145 policy->GrantWriteFileSystem(render_frame_host()->GetProcess()->GetID(),
1146 file_system_id);
1147 policy->GrantDeleteFromFileSystem(
1148 render_frame_host()->GetProcess()->GetID(), file_system_id);
1149 policy->GrantCreateFileForFileSystem(
1150 render_frame_host()->GetProcess()->GetID(), file_system_id);
1151 }
1152
1153 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
1154 dict->SetString("file_system_id", file_system_id);
1155 dict->SetString("file_system_path", register_name);
1156
1157 Respond(OneArgument(std::move(dict)));
1158 }
1159
1160 FileSystemGetVolumeListFunction::FileSystemGetVolumeListFunction()
1161 : chrome_details_(this) {}
1162
1163 FileSystemGetVolumeListFunction::~FileSystemGetVolumeListFunction() {}
1164
1165 ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
1166 // Only kiosk apps in kiosk sessions can use this API.
1167 // Additionally it is enabled for whitelisted component extensions and apps.
1168 file_system_api::ConsentProviderDelegate consent_provider_delegate(
1169 chrome_details_.GetProfile(), render_frame_host());
1170 file_system_api::ConsentProvider consent_provider(&consent_provider_delegate);
1171
1172 if (!consent_provider.IsGrantable(*extension()))
1173 return RespondNow(Error(kNotSupportedOnNonKioskSessionError));
1174 std::vector<api::file_system::Volume> result_volume_list;
1175 FillVolumeList(chrome_details_.GetProfile(), &result_volume_list);
1176
1177 return RespondNow(ArgumentList(
1178 api::file_system::GetVolumeList::Results::Create(result_volume_list)));
1179 }
1180 #endif
1181
1182 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698