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

Unified Diff: chrome/browser/extensions/api/file_system/file_system_api.cc

Issue 2934143002: Move chrome.fileSystem implementation to //extensions (Closed)
Patch Set: devlin Created 3 years, 6 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/extensions/api/file_system/file_system_api.cc
diff --git a/chrome/browser/extensions/api/file_system/file_system_api.cc b/chrome/browser/extensions/api/file_system/file_system_api.cc
deleted file mode 100644
index 8d5c628b3b36e0d2ba4dbdfb7cea602191b59580..0000000000000000000000000000000000000000
--- a/chrome/browser/extensions/api/file_system/file_system_api.cc
+++ /dev/null
@@ -1,1244 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/extensions/api/file_system/file_system_api.h"
-
-#include <stddef.h>
-
-#include <memory>
-#include <set>
-#include <utility>
-#include <vector>
-
-#include "apps/saved_files_service.h"
-#include "base/bind.h"
-#include "base/files/file_path.h"
-#include "base/files/file_util.h"
-#include "base/macros.h"
-#include "base/memory/ptr_util.h"
-#include "base/path_service.h"
-#include "base/strings/string_util.h"
-#include "base/strings/stringprintf.h"
-#include "base/strings/sys_string_conversions.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/task_scheduler/post_task.h"
-#include "base/value_conversions.h"
-#include "base/values.h"
-#include "build/build_config.h"
-#include "chrome/browser/platform_util.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/apps/directory_access_confirmation_dialog.h"
-#include "chrome/browser/ui/chrome_select_file_policy.h"
-#include "chrome/common/chrome_paths.h"
-#include "chrome/common/extensions/api/file_system.h"
-#include "chrome/grit/generated_resources.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/child_process_security_policy.h"
-#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/render_process_host.h"
-#include "content/public/browser/storage_partition.h"
-#include "content/public/browser/web_contents.h"
-#include "extensions/browser/api/file_handlers/app_file_handler_util.h"
-#include "extensions/browser/app_window/app_window.h"
-#include "extensions/browser/app_window/app_window_registry.h"
-#include "extensions/browser/extension_prefs.h"
-#include "extensions/browser/extension_system.h"
-#include "extensions/browser/extension_util.h"
-#include "extensions/browser/granted_file_entry.h"
-#include "extensions/browser/path_util.h"
-#include "extensions/common/permissions/api_permission.h"
-#include "extensions/common/permissions/permissions_data.h"
-#include "net/base/mime_util.h"
-#include "storage/browser/fileapi/external_mount_points.h"
-#include "storage/browser/fileapi/file_system_operation_runner.h"
-#include "storage/browser/fileapi/isolated_context.h"
-#include "storage/common/fileapi/file_system_types.h"
-#include "storage/common/fileapi/file_system_util.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/ui_base_types.h"
-#include "ui/shell_dialogs/select_file_dialog.h"
-#include "ui/shell_dialogs/selected_file_info.h"
-
-#if defined(OS_MACOSX)
-#include <CoreFoundation/CoreFoundation.h>
-#include "base/mac/foundation_util.h"
-#endif
-
-#if defined(OS_CHROMEOS)
-#include "base/strings/string16.h"
-#include "chrome/browser/chromeos/file_manager/filesystem_api_util.h"
-#include "chrome/browser/chromeos/file_manager/volume_manager.h"
-#include "extensions/browser/event_router.h"
-#include "extensions/browser/extension_registry.h"
-#include "extensions/common/constants.h"
-#include "url/url_constants.h"
-#endif
-
-using apps::SavedFileEntry;
-using apps::SavedFilesService;
-using storage::IsolatedContext;
-
-const char kInvalidCallingPage[] =
- "Invalid calling page. "
- "This function can't be called from a background page.";
-const char kUserCancelled[] = "User cancelled";
-const char kWritableFileErrorFormat[] = "Error opening %s";
-const char kRequiresFileSystemWriteError[] =
- "Operation requires fileSystem.write permission";
-const char kRequiresFileSystemDirectoryError[] =
- "Operation requires fileSystem.directory permission";
-const char kMultipleUnsupportedError[] =
- "acceptsMultiple: true is only supported for 'openFile'";
-const char kUnknownIdError[] = "Unknown id";
-
-#if !defined(OS_CHROMEOS)
-const char kNotSupportedOnCurrentPlatformError[] =
- "Operation not supported on the current platform.";
-#else
-const char kNotSupportedOnNonKioskSessionError[] =
- "Operation only supported for kiosk apps running in a kiosk session.";
-const char kVolumeNotFoundError[] = "Volume not found.";
-const char kSecurityError[] = "Security error.";
-const char kConsentImpossible[] =
- "Impossible to ask for user consent as there is no app window visible.";
-#endif
-
-namespace extensions {
-
-namespace file_system = api::file_system;
-namespace ChooseEntry = file_system::ChooseEntry;
-
-namespace {
-
-bool g_skip_picker_for_test = false;
-bool g_use_suggested_path_for_test = false;
-base::FilePath* g_path_to_be_picked_for_test;
-std::vector<base::FilePath>* g_paths_to_be_picked_for_test;
-bool g_skip_directory_confirmation_for_test = false;
-bool g_allow_directory_access_for_test = false;
-
-// Expand the mime-types and extensions provided in an AcceptOption, returning
-// them within the passed extension vector. Returns false if no valid types
-// were found.
-bool GetFileTypesFromAcceptOption(
- const file_system::AcceptOption& accept_option,
- std::vector<base::FilePath::StringType>* extensions,
- base::string16* description) {
- std::set<base::FilePath::StringType> extension_set;
- int description_id = 0;
-
- if (accept_option.mime_types.get()) {
- std::vector<std::string>* list = accept_option.mime_types.get();
- bool valid_type = false;
- for (std::vector<std::string>::const_iterator iter = list->begin();
- iter != list->end(); ++iter) {
- std::vector<base::FilePath::StringType> inner;
- std::string accept_type = base::ToLowerASCII(*iter);
- net::GetExtensionsForMimeType(accept_type, &inner);
- if (inner.empty())
- continue;
-
- if (valid_type)
- description_id = 0; // We already have an accept type with label; if
- // we find another, give up and use the default.
- else if (accept_type == "image/*")
- description_id = IDS_IMAGE_FILES;
- else if (accept_type == "audio/*")
- description_id = IDS_AUDIO_FILES;
- else if (accept_type == "video/*")
- description_id = IDS_VIDEO_FILES;
-
- extension_set.insert(inner.begin(), inner.end());
- valid_type = true;
- }
- }
-
- if (accept_option.extensions.get()) {
- std::vector<std::string>* list = accept_option.extensions.get();
- for (std::vector<std::string>::const_iterator iter = list->begin();
- iter != list->end(); ++iter) {
- std::string extension = base::ToLowerASCII(*iter);
-#if defined(OS_WIN)
- extension_set.insert(base::UTF8ToWide(*iter));
-#else
- extension_set.insert(*iter);
-#endif
- }
- }
-
- extensions->assign(extension_set.begin(), extension_set.end());
- if (extensions->empty())
- return false;
-
- if (accept_option.description.get())
- *description = base::UTF8ToUTF16(*accept_option.description);
- else if (description_id)
- *description = l10n_util::GetStringUTF16(description_id);
-
- return true;
-}
-
-// Key for the path of the directory of the file last chosen by the user in
-// response to a chrome.fileSystem.chooseEntry() call.
-const char kLastChooseEntryDirectory[] = "last_choose_file_directory";
-
-const int kGraylistedPaths[] = {
- base::DIR_HOME,
-#if defined(OS_WIN)
- base::DIR_PROGRAM_FILES, base::DIR_PROGRAM_FILESX86, base::DIR_WINDOWS,
-#endif
-};
-
-typedef base::Callback<void(std::unique_ptr<base::File::Info>)>
- FileInfoOptCallback;
-
-// Passes optional file info to the UI thread depending on |result| and |info|.
-void PassFileInfoToUIThread(const FileInfoOptCallback& callback,
- base::File::Error result,
- const base::File::Info& info) {
- DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
- std::unique_ptr<base::File::Info> file_info(
- result == base::File::FILE_OK ? new base::File::Info(info) : NULL);
- content::BrowserThread::PostTask(
- content::BrowserThread::UI, FROM_HERE,
- base::BindOnce(callback, base::Passed(&file_info)));
-}
-
-// Gets a WebContents instance handle for a platform app hosted in
-// |render_frame_host|. If not found, then returns NULL.
-content::WebContents* GetWebContentsForRenderFrameHost(
- Profile* profile,
- content::RenderFrameHost* render_frame_host) {
- content::WebContents* web_contents =
- content::WebContents::FromRenderFrameHost(render_frame_host);
- // Check if there is an app window associated with the web contents; if not,
- // return null.
- return AppWindowRegistry::Get(profile)->GetAppWindowForWebContents(
- web_contents)
- ? web_contents
- : nullptr;
-}
-
-#if defined(OS_CHROMEOS)
-// Fills a list of volumes mounted in the system.
-void FillVolumeList(Profile* profile,
- std::vector<api::file_system::Volume>* result) {
- file_manager::VolumeManager* const volume_manager =
- file_manager::VolumeManager::Get(profile);
- DCHECK(volume_manager);
-
- const auto& volume_list = volume_manager->GetVolumeList();
- // Convert volume_list to result_volume_list.
- for (const auto& volume : volume_list) {
- api::file_system::Volume result_volume;
- result_volume.volume_id = volume->volume_id();
- result_volume.writable = !volume->is_read_only();
- result->push_back(std::move(result_volume));
- }
-}
-#endif
-
-} // namespace
-
-namespace file_system_api {
-
-base::FilePath GetLastChooseEntryDirectory(const ExtensionPrefs* prefs,
- const std::string& extension_id) {
- base::FilePath path;
- std::string string_path;
- if (prefs->ReadPrefAsString(extension_id, kLastChooseEntryDirectory,
- &string_path)) {
- path = base::FilePath::FromUTF8Unsafe(string_path);
- }
- return path;
-}
-
-void SetLastChooseEntryDirectory(ExtensionPrefs* prefs,
- const std::string& extension_id,
- const base::FilePath& path) {
- prefs->UpdateExtensionPref(extension_id, kLastChooseEntryDirectory,
- base::CreateFilePathValue(path));
-}
-
-#if defined(OS_CHROMEOS)
-void DispatchVolumeListChangeEvent(Profile* profile) {
- DCHECK(profile);
- EventRouter* const event_router = EventRouter::Get(profile);
- if (!event_router) // Possible on shutdown.
- return;
-
- ExtensionRegistry* const registry = ExtensionRegistry::Get(profile);
- if (!registry) // Possible on shutdown.
- return;
-
- ConsentProviderDelegate consent_provider_delegate(profile, nullptr);
- ConsentProvider consent_provider(&consent_provider_delegate);
- api::file_system::VolumeListChangedEvent event_args;
- FillVolumeList(profile, &event_args.volumes);
- for (const auto& extension : registry->enabled_extensions()) {
- if (!consent_provider.IsGrantable(*extension.get()))
- continue;
- event_router->DispatchEventToExtension(
- extension->id(),
- base::MakeUnique<Event>(
- events::FILE_SYSTEM_ON_VOLUME_LIST_CHANGED,
- api::file_system::OnVolumeListChanged::kEventName,
- api::file_system::OnVolumeListChanged::Create(event_args)));
- }
-}
-#endif
-
-} // namespace file_system_api
-
-#if defined(OS_CHROMEOS)
-using file_system_api::ConsentProvider;
-#endif
-
-ExtensionFunction::ResponseAction FileSystemGetDisplayPathFunction::Run() {
- std::string filesystem_name;
- std::string filesystem_path;
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
-
- base::FilePath file_path;
- std::string error;
- if (!app_file_handler_util::ValidateFileEntryAndGetPath(
- filesystem_name, filesystem_path,
- render_frame_host()->GetProcess()->GetID(), &file_path, &error)) {
- return RespondNow(Error(error));
- }
-
- file_path = path_util::PrettifyPath(file_path);
- return RespondNow(
- OneArgument(base::MakeUnique<base::Value>(file_path.value())));
-}
-
-FileSystemEntryFunction::FileSystemEntryFunction()
- : multiple_(false), is_directory_(false) {}
-
-void FileSystemEntryFunction::PrepareFilesForWritableApp(
- const std::vector<base::FilePath>& paths) {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- // TODO(cmihail): Path directory set should be initialized only with the
- // paths that are actually directories, but for now we will consider
- // all paths directories in case is_directory_ is true, otherwise
- // all paths files, as this was the previous logic.
- std::set<base::FilePath> path_directory_set_ =
- is_directory_ ? std::set<base::FilePath>(paths.begin(), paths.end())
- : std::set<base::FilePath>{};
- app_file_handler_util::PrepareFilesForWritableApp(
- paths, GetProfile(), path_directory_set_,
- base::Bind(&FileSystemEntryFunction::RegisterFileSystemsAndSendResponse,
- this, paths),
- base::Bind(&FileSystemEntryFunction::HandleWritableFileError, this));
-}
-
-void FileSystemEntryFunction::RegisterFileSystemsAndSendResponse(
- const std::vector<base::FilePath>& paths) {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- if (!render_frame_host())
- return;
-
- std::unique_ptr<base::DictionaryValue> result = CreateResult();
- for (const auto& path : paths)
- AddEntryToResult(path, std::string(), result.get());
- SetResult(std::move(result));
- SendResponse(true);
-}
-
-std::unique_ptr<base::DictionaryValue> FileSystemEntryFunction::CreateResult() {
- std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
- result->Set("entries", base::MakeUnique<base::ListValue>());
- result->SetBoolean("multiple", multiple_);
- return result;
-}
-
-void FileSystemEntryFunction::AddEntryToResult(const base::FilePath& path,
- const std::string& id_override,
- base::DictionaryValue* result) {
- GrantedFileEntry file_entry = app_file_handler_util::CreateFileEntry(
- GetProfile(), extension(), render_frame_host()->GetProcess()->GetID(),
- path, is_directory_);
- base::ListValue* entries;
- bool success = result->GetList("entries", &entries);
- DCHECK(success);
-
- std::unique_ptr<base::DictionaryValue> entry(new base::DictionaryValue());
- entry->SetString("fileSystemId", file_entry.filesystem_id);
- entry->SetString("baseName", file_entry.registered_name);
- if (id_override.empty())
- entry->SetString("id", file_entry.id);
- else
- entry->SetString("id", id_override);
- entry->SetBoolean("isDirectory", is_directory_);
- entries->Append(std::move(entry));
-}
-
-void FileSystemEntryFunction::HandleWritableFileError(
- const base::FilePath& error_path) {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- error_ = base::StringPrintf(kWritableFileErrorFormat,
- error_path.BaseName().AsUTF8Unsafe().c_str());
- SendResponse(false);
-}
-
-bool FileSystemGetWritableEntryFunction::RunAsync() {
- std::string filesystem_name;
- std::string filesystem_path;
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
-
- if (!app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
- error_ = kRequiresFileSystemWriteError;
- return false;
- }
-
- if (!app_file_handler_util::ValidateFileEntryAndGetPath(
- filesystem_name, filesystem_path,
- render_frame_host()->GetProcess()->GetID(), &path_, &error_))
- return false;
-
- base::PostTaskWithTraitsAndReply(
- FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
- base::BindOnce(&FileSystemGetWritableEntryFunction::SetIsDirectoryAsync,
- this),
- base::BindOnce(
- &FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse,
- this));
- return true;
-}
-
-void FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse() {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- if (is_directory_ && !extension_->permissions_data()->HasAPIPermission(
- APIPermission::kFileSystemDirectory)) {
- error_ = kRequiresFileSystemDirectoryError;
- SendResponse(false);
- }
- std::vector<base::FilePath> paths;
- paths.push_back(path_);
- PrepareFilesForWritableApp(paths);
-}
-
-void FileSystemGetWritableEntryFunction::SetIsDirectoryAsync() {
- if (base::DirectoryExists(path_)) {
- is_directory_ = true;
- }
-}
-
-ExtensionFunction::ResponseAction FileSystemIsWritableEntryFunction::Run() {
- std::string filesystem_name;
- std::string filesystem_path;
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
-
- std::string filesystem_id;
- if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id))
- return RespondNow(Error(app_file_handler_util::kInvalidParameters));
-
- content::ChildProcessSecurityPolicy* policy =
- content::ChildProcessSecurityPolicy::GetInstance();
- int renderer_id = render_frame_host()->GetProcess()->GetID();
- bool is_writable = policy->CanReadWriteFileSystem(renderer_id, filesystem_id);
-
- return RespondNow(OneArgument(base::MakeUnique<base::Value>(is_writable)));
-}
-
-// Handles showing a dialog to the user to ask for the filename for a file to
-// save or open.
-class FileSystemChooseEntryFunction::FilePicker
- : public ui::SelectFileDialog::Listener {
- 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)
- : function_(function) {
- select_file_dialog_ = ui::SelectFileDialog::Create(
- this, new ChromeSelectFilePolicy(web_contents));
- gfx::NativeWindow owning_window =
- web_contents ? platform_util::GetTopLevel(web_contents->GetNativeView())
- : NULL;
-
- if (g_skip_picker_for_test) {
- if (g_use_suggested_path_for_test) {
- content::BrowserThread::PostTask(
- content::BrowserThread::UI, FROM_HERE,
- base::BindOnce(
- &FileSystemChooseEntryFunction::FilePicker::FileSelected,
- base::Unretained(this), suggested_name, 1,
- static_cast<void*>(NULL)));
- } else if (g_path_to_be_picked_for_test) {
- content::BrowserThread::PostTask(
- content::BrowserThread::UI, FROM_HERE,
- base::BindOnce(
- &FileSystemChooseEntryFunction::FilePicker::FileSelected,
- base::Unretained(this), *g_path_to_be_picked_for_test, 1,
- static_cast<void*>(NULL)));
- } else if (g_paths_to_be_picked_for_test) {
- content::BrowserThread::PostTask(
- content::BrowserThread::UI, FROM_HERE,
- base::BindOnce(
- &FileSystemChooseEntryFunction::FilePicker::MultiFilesSelected,
- base::Unretained(this), *g_paths_to_be_picked_for_test,
- static_cast<void*>(NULL)));
- } else {
- content::BrowserThread::PostTask(
- content::BrowserThread::UI, FROM_HERE,
- base::BindOnce(&FileSystemChooseEntryFunction::FilePicker::
- FileSelectionCanceled,
- base::Unretained(this), static_cast<void*>(NULL)));
- }
- return;
- }
-
- select_file_dialog_->SelectFile(
- picker_type, base::string16(), suggested_name, &file_type_info, 0,
- base::FilePath::StringType(), owning_window, NULL);
- }
-
- ~FilePicker() override {}
-
- private:
- // ui::SelectFileDialog::Listener implementation.
- void FileSelected(const base::FilePath& path,
- int index,
- void* params) override {
- std::vector<base::FilePath> paths;
- paths.push_back(path);
- MultiFilesSelected(paths, params);
- }
-
- void FileSelectedWithExtraInfo(const ui::SelectedFileInfo& file,
- int index,
- void* params) override {
- // Normally, file.local_path is used because it is a native path to the
- // local read-only cached file in the case of remote file system like
- // Chrome OS's Google Drive integration. Here, however, |file.file_path| is
- // necessary because we need to create a FileEntry denoting the remote file,
- // not its cache. On other platforms than Chrome OS, they are the same.
- //
- // TODO(kinaba): remove this, once after the file picker implements proper
- // switch of the path treatment depending on the |allowed_paths|.
- FileSelected(file.file_path, index, params);
- }
-
- void MultiFilesSelected(const std::vector<base::FilePath>& files,
- void* params) override {
- function_->FilesSelected(files);
- delete this;
- }
-
- void MultiFilesSelectedWithExtraInfo(
- const std::vector<ui::SelectedFileInfo>& files,
- void* params) override {
- std::vector<base::FilePath> paths;
- for (std::vector<ui::SelectedFileInfo>::const_iterator it = files.begin();
- it != files.end(); ++it) {
- paths.push_back(it->file_path);
- }
- MultiFilesSelected(paths, params);
- }
-
- void FileSelectionCanceled(void* params) override {
- function_->FileSelectionCanceled();
- delete this;
- }
-
- scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
- scoped_refptr<FileSystemChooseEntryFunction> function_;
-
- DISALLOW_COPY_AND_ASSIGN(FilePicker);
-};
-
-void FileSystemChooseEntryFunction::ShowPicker(
- const ui::SelectFileDialog::FileTypeInfo& file_type_info,
- ui::SelectFileDialog::Type picker_type) {
- // TODO(asargent/benwells) - As a short term remediation for crbug.com/179010
- // we're adding the ability for a whitelisted extension to use this API since
- // chrome.fileBrowserHandler.selectFile is ChromeOS-only. Eventually we'd
- // like a better solution and likely this code will go back to being
- // platform-app only.
- content::WebContents* const web_contents =
- extension_->is_platform_app()
- ? GetWebContentsForRenderFrameHost(GetProfile(), render_frame_host())
- : GetAssociatedWebContents();
- if (!web_contents) {
- error_ = kInvalidCallingPage;
- SendResponse(false);
- return;
- }
-
- // The file picker will hold a reference to this function instance, preventing
- // its destruction (and subsequent sending of the function response) until the
- // user has selected a file or cancelled the picker. At that point, the picker
- // will delete itself, which will also free the function instance.
- new FilePicker(this, web_contents, initial_path_, file_type_info,
- picker_type);
-}
-
-// static
-void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
- base::FilePath* path) {
- g_skip_picker_for_test = true;
- g_use_suggested_path_for_test = false;
- g_path_to_be_picked_for_test = path;
- g_paths_to_be_picked_for_test = NULL;
-}
-
-void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathsForTest(
- std::vector<base::FilePath>* paths) {
- g_skip_picker_for_test = true;
- g_use_suggested_path_for_test = false;
- g_paths_to_be_picked_for_test = paths;
-}
-
-// static
-void FileSystemChooseEntryFunction::SkipPickerAndSelectSuggestedPathForTest() {
- g_skip_picker_for_test = true;
- g_use_suggested_path_for_test = true;
- g_path_to_be_picked_for_test = NULL;
- g_paths_to_be_picked_for_test = NULL;
-}
-
-// static
-void FileSystemChooseEntryFunction::SkipPickerAndAlwaysCancelForTest() {
- g_skip_picker_for_test = true;
- g_use_suggested_path_for_test = false;
- g_path_to_be_picked_for_test = NULL;
- g_paths_to_be_picked_for_test = NULL;
-}
-
-// static
-void FileSystemChooseEntryFunction::StopSkippingPickerForTest() {
- g_skip_picker_for_test = false;
-}
-
-// static
-void FileSystemChooseEntryFunction::SkipDirectoryConfirmationForTest() {
- g_skip_directory_confirmation_for_test = true;
- g_allow_directory_access_for_test = true;
-}
-
-// static
-void FileSystemChooseEntryFunction::AutoCancelDirectoryConfirmationForTest() {
- g_skip_directory_confirmation_for_test = true;
- g_allow_directory_access_for_test = false;
-}
-
-// static
-void FileSystemChooseEntryFunction::StopSkippingDirectoryConfirmationForTest() {
- g_skip_directory_confirmation_for_test = false;
-}
-
-// static
-void FileSystemChooseEntryFunction::RegisterTempExternalFileSystemForTest(
- const std::string& name,
- const base::FilePath& path) {
- // For testing on Chrome OS, where to deal with remote and local paths
- // smoothly, all accessed paths need to be registered in the list of
- // external mount points.
- storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
- name, storage::kFileSystemTypeNativeLocal,
- storage::FileSystemMountOption(), path);
-}
-
-void FileSystemChooseEntryFunction::FilesSelected(
- const std::vector<base::FilePath>& paths) {
- DCHECK(!paths.empty());
- base::FilePath last_choose_directory;
- if (is_directory_) {
- last_choose_directory = paths[0];
- } else {
- last_choose_directory = paths[0].DirName();
- }
- file_system_api::SetLastChooseEntryDirectory(
- ExtensionPrefs::Get(GetProfile()), extension()->id(),
- last_choose_directory);
- if (is_directory_) {
- // Get the WebContents for the app window to be the parent window of the
- // confirmation dialog if necessary.
- content::WebContents* const web_contents =
- GetWebContentsForRenderFrameHost(GetProfile(), render_frame_host());
- if (!web_contents) {
- error_ = kInvalidCallingPage;
- SendResponse(false);
- return;
- }
-
- DCHECK_EQ(paths.size(), 1u);
- bool non_native_path = false;
-#if defined(OS_CHROMEOS)
- non_native_path =
- file_manager::util::IsUnderNonNativeLocalPath(GetProfile(), paths[0]);
-#endif
-
- base::PostTaskWithTraits(
- FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
- base::BindOnce(
- &FileSystemChooseEntryFunction::ConfirmDirectoryAccessAsync, this,
- non_native_path, paths, web_contents));
- return;
- }
-
- OnDirectoryAccessConfirmed(paths);
-}
-
-void FileSystemChooseEntryFunction::FileSelectionCanceled() {
- error_ = kUserCancelled;
- SendResponse(false);
-}
-
-void FileSystemChooseEntryFunction::ConfirmDirectoryAccessAsync(
- bool non_native_path,
- const std::vector<base::FilePath>& paths,
- content::WebContents* web_contents) {
- const base::FilePath check_path =
- non_native_path ? paths[0] : base::MakeAbsoluteFilePath(paths[0]);
- if (check_path.empty()) {
- content::BrowserThread::PostTask(
- content::BrowserThread::UI, FROM_HERE,
- base::BindOnce(&FileSystemChooseEntryFunction::FileSelectionCanceled,
- this));
- return;
- }
-
- for (size_t i = 0; i < arraysize(kGraylistedPaths); i++) {
- base::FilePath graylisted_path;
- if (PathService::Get(kGraylistedPaths[i], &graylisted_path) &&
- (check_path == graylisted_path ||
- check_path.IsParent(graylisted_path))) {
- if (g_skip_directory_confirmation_for_test) {
- if (g_allow_directory_access_for_test) {
- break;
- } else {
- content::BrowserThread::PostTask(
- content::BrowserThread::UI, FROM_HERE,
- base::BindOnce(
- &FileSystemChooseEntryFunction::FileSelectionCanceled, this));
- }
- return;
- }
-
- content::BrowserThread::PostTask(
- content::BrowserThread::UI, FROM_HERE,
- base::BindOnce(
- CreateDirectoryAccessConfirmationDialog,
- app_file_handler_util::HasFileSystemWritePermission(
- extension_.get()),
- base::UTF8ToUTF16(extension_->name()), web_contents,
- base::Bind(
- &FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed,
- this, paths),
- base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
- this)));
- return;
- }
- }
-
- content::BrowserThread::PostTask(
- content::BrowserThread::UI, FROM_HERE,
- base::BindOnce(&FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed,
- this, paths));
-}
-
-void FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed(
- const std::vector<base::FilePath>& paths) {
- if (app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
- PrepareFilesForWritableApp(paths);
- return;
- }
-
- // Don't need to check the file, it's for reading.
- RegisterFileSystemsAndSendResponse(paths);
-}
-
-void FileSystemChooseEntryFunction::BuildFileTypeInfo(
- ui::SelectFileDialog::FileTypeInfo* file_type_info,
- const base::FilePath::StringType& suggested_extension,
- const AcceptOptions* accepts,
- const bool* acceptsAllTypes) {
- file_type_info->include_all_files = true;
- if (acceptsAllTypes)
- file_type_info->include_all_files = *acceptsAllTypes;
-
- bool need_suggestion =
- !file_type_info->include_all_files && !suggested_extension.empty();
-
- if (accepts) {
- for (const file_system::AcceptOption& option : *accepts) {
- base::string16 description;
- std::vector<base::FilePath::StringType> extensions;
-
- if (!GetFileTypesFromAcceptOption(option, &extensions, &description))
- continue; // No extensions were found.
-
- file_type_info->extensions.push_back(extensions);
- file_type_info->extension_description_overrides.push_back(description);
-
- // If we still need to find suggested_extension, hunt for it inside the
- // extensions returned from GetFileTypesFromAcceptOption.
- if (need_suggestion &&
- std::find(extensions.begin(), extensions.end(),
- suggested_extension) != extensions.end()) {
- need_suggestion = false;
- }
- }
- }
-
- // If there's nothing in our accepted extension list or we couldn't find the
- // suggested extension required, then default to accepting all types.
- if (file_type_info->extensions.empty() || need_suggestion)
- file_type_info->include_all_files = true;
-}
-
-void FileSystemChooseEntryFunction::BuildSuggestion(
- const std::string* opt_name,
- base::FilePath* suggested_name,
- base::FilePath::StringType* suggested_extension) {
- if (opt_name) {
- *suggested_name = base::FilePath::FromUTF8Unsafe(*opt_name);
-
- // Don't allow any path components; shorten to the base name. This should
- // result in a relative path, but in some cases may not. Clear the
- // suggestion for safety if this is the case.
- *suggested_name = suggested_name->BaseName();
- if (suggested_name->IsAbsolute())
- *suggested_name = base::FilePath();
-
- *suggested_extension = suggested_name->Extension();
- if (!suggested_extension->empty())
- suggested_extension->erase(suggested_extension->begin()); // drop the .
- }
-}
-
-void FileSystemChooseEntryFunction::SetInitialPathAndShowPicker(
- const base::FilePath& previous_path,
- const base::FilePath& suggested_name,
- const ui::SelectFileDialog::FileTypeInfo& file_type_info,
- ui::SelectFileDialog::Type picker_type,
- bool is_previous_path_directory) {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- if (is_previous_path_directory) {
- initial_path_ = previous_path.Append(suggested_name);
- } else {
- base::FilePath documents_dir;
- if (PathService::Get(chrome::DIR_USER_DOCUMENTS, &documents_dir)) {
- initial_path_ = documents_dir.Append(suggested_name);
- } else {
- initial_path_ = suggested_name;
- }
- }
- ShowPicker(file_type_info, picker_type);
-}
-
-bool FileSystemChooseEntryFunction::RunAsync() {
- std::unique_ptr<ChooseEntry::Params> params(
- ChooseEntry::Params::Create(*args_));
- EXTENSION_FUNCTION_VALIDATE(params.get());
-
- base::FilePath suggested_name;
- ui::SelectFileDialog::FileTypeInfo file_type_info;
- ui::SelectFileDialog::Type picker_type =
- ui::SelectFileDialog::SELECT_OPEN_FILE;
-
- file_system::ChooseEntryOptions* options = params->options.get();
- if (options) {
- multiple_ = options->accepts_multiple && *options->accepts_multiple;
- if (multiple_)
- picker_type = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
-
- if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENWRITABLEFILE &&
- !app_file_handler_util::HasFileSystemWritePermission(
- extension_.get())) {
- error_ = kRequiresFileSystemWriteError;
- return false;
- } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_SAVEFILE) {
- if (!app_file_handler_util::HasFileSystemWritePermission(
- extension_.get())) {
- error_ = kRequiresFileSystemWriteError;
- return false;
- }
- if (multiple_) {
- error_ = kMultipleUnsupportedError;
- return false;
- }
- picker_type = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
- } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENDIRECTORY) {
- is_directory_ = true;
- if (!extension_->permissions_data()->HasAPIPermission(
- APIPermission::kFileSystemDirectory)) {
- error_ = kRequiresFileSystemDirectoryError;
- return false;
- }
- if (multiple_) {
- error_ = kMultipleUnsupportedError;
- return false;
- }
- picker_type = ui::SelectFileDialog::SELECT_FOLDER;
- }
-
- base::FilePath::StringType suggested_extension;
- BuildSuggestion(options->suggested_name.get(), &suggested_name,
- &suggested_extension);
-
- BuildFileTypeInfo(&file_type_info, suggested_extension,
- options->accepts.get(), options->accepts_all_types.get());
- }
-
- file_type_info.allowed_paths = ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
-
- base::FilePath previous_path = file_system_api::GetLastChooseEntryDirectory(
- ExtensionPrefs::Get(GetProfile()), extension()->id());
-
- if (previous_path.empty()) {
- SetInitialPathAndShowPicker(previous_path, suggested_name, file_type_info,
- picker_type, false);
- return true;
- }
-
- base::Callback<void(bool)> set_initial_path_callback = base::Bind(
- &FileSystemChooseEntryFunction::SetInitialPathAndShowPicker, this,
- previous_path, suggested_name, file_type_info, picker_type);
-
-// Check whether the |previous_path| is a non-native directory.
-#if defined(OS_CHROMEOS)
- if (file_manager::util::IsUnderNonNativeLocalPath(GetProfile(),
- previous_path)) {
- file_manager::util::IsNonNativeLocalPathDirectory(
- GetProfile(), previous_path, set_initial_path_callback);
- return true;
- }
-#endif
- base::PostTaskWithTraitsAndReplyWithResult(
- FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
- base::Bind(&base::DirectoryExists, previous_path),
- set_initial_path_callback);
-
- return true;
-}
-
-bool FileSystemRetainEntryFunction::RunAsync() {
- std::string entry_id;
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
- SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile());
- // Add the file to the retain list if it is not already on there.
- if (!saved_files_service->IsRegistered(extension_->id(), entry_id)) {
- std::string filesystem_name;
- std::string filesystem_path;
- base::FilePath path;
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_name));
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &filesystem_path));
- if (!app_file_handler_util::ValidateFileEntryAndGetPath(
- filesystem_name, filesystem_path,
- render_frame_host()->GetProcess()->GetID(), &path, &error_)) {
- return false;
- }
-
- std::string filesystem_id;
- if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id))
- return false;
-
- const GURL site = util::GetSiteForExtensionId(extension_id(), GetProfile());
- storage::FileSystemContext* const context =
- content::BrowserContext::GetStoragePartitionForSite(GetProfile(), site)
- ->GetFileSystemContext();
- const storage::FileSystemURL url = context->CreateCrackedFileSystemURL(
- site, storage::kFileSystemTypeIsolated,
- IsolatedContext::GetInstance()
- ->CreateVirtualRootPath(filesystem_id)
- .Append(base::FilePath::FromUTF8Unsafe(filesystem_path)));
-
- content::BrowserThread::PostTask(
- content::BrowserThread::IO, FROM_HERE,
- base::BindOnce(
- base::IgnoreResult(
- &storage::FileSystemOperationRunner::GetMetadata),
- context->operation_runner()->AsWeakPtr(), url,
- storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY,
- base::Bind(
- &PassFileInfoToUIThread,
- base::Bind(&FileSystemRetainEntryFunction::RetainFileEntry,
- this, entry_id, path))));
- return true;
- }
-
- saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
- SendResponse(true);
- return true;
-}
-
-void FileSystemRetainEntryFunction::RetainFileEntry(
- const std::string& entry_id,
- const base::FilePath& path,
- std::unique_ptr<base::File::Info> file_info) {
- if (!file_info) {
- SendResponse(false);
- return;
- }
-
- SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile());
- saved_files_service->RegisterFileEntry(extension_->id(), entry_id, path,
- file_info->is_directory);
- saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
- SendResponse(true);
-}
-
-ExtensionFunction::ResponseAction FileSystemIsRestorableFunction::Run() {
- std::string entry_id;
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
- return RespondNow(OneArgument(base::MakeUnique<base::Value>(
- SavedFilesService::Get(Profile::FromBrowserContext(browser_context()))
- ->IsRegistered(extension_->id(), entry_id))));
-}
-
-bool FileSystemRestoreEntryFunction::RunAsync() {
- std::string entry_id;
- bool needs_new_entry;
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
- EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &needs_new_entry));
- const SavedFileEntry* file_entry =
- SavedFilesService::Get(GetProfile())
- ->GetFileEntry(extension_->id(), entry_id);
- if (!file_entry) {
- error_ = kUnknownIdError;
- return false;
- }
-
- SavedFilesService::Get(GetProfile())
- ->EnqueueFileEntry(extension_->id(), entry_id);
-
- // Only create a new file entry if the renderer requests one.
- // |needs_new_entry| will be false if the renderer already has an Entry for
- // |entry_id|.
- if (needs_new_entry) {
- is_directory_ = file_entry->is_directory;
- std::unique_ptr<base::DictionaryValue> result = CreateResult();
- AddEntryToResult(file_entry->path, file_entry->id, result.get());
- SetResult(std::move(result));
- }
- SendResponse(true);
- return true;
-}
-
-ExtensionFunction::ResponseAction FileSystemObserveDirectoryFunction::Run() {
- NOTIMPLEMENTED();
- return RespondNow(Error(kUnknownIdError));
-}
-
-ExtensionFunction::ResponseAction FileSystemUnobserveEntryFunction::Run() {
- NOTIMPLEMENTED();
- return RespondNow(Error(kUnknownIdError));
-}
-
-ExtensionFunction::ResponseAction FileSystemGetObservedEntriesFunction::Run() {
- NOTIMPLEMENTED();
- return RespondNow(Error(kUnknownIdError));
-}
-
-#if !defined(OS_CHROMEOS)
-ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() {
- using api::file_system::RequestFileSystem::Params;
- const std::unique_ptr<Params> params(Params::Create(*args_));
- EXTENSION_FUNCTION_VALIDATE(params);
-
- NOTIMPLEMENTED();
- return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
-}
-
-ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
- NOTIMPLEMENTED();
- return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
-}
-#else
-
-FileSystemRequestFileSystemFunction::FileSystemRequestFileSystemFunction()
- : chrome_details_(this) {}
-
-FileSystemRequestFileSystemFunction::~FileSystemRequestFileSystemFunction() {}
-
-ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() {
- using api::file_system::RequestFileSystem::Params;
- const std::unique_ptr<Params> params(Params::Create(*args_));
- EXTENSION_FUNCTION_VALIDATE(params);
-
- // Only kiosk apps in kiosk sessions can use this API.
- // Additionally it is enabled for whitelisted component extensions and apps.
- file_system_api::ConsentProviderDelegate consent_provider_delegate(
- chrome_details_.GetProfile(), render_frame_host());
- file_system_api::ConsentProvider consent_provider(&consent_provider_delegate);
-
- if (!consent_provider.IsGrantable(*extension()))
- return RespondNow(Error(kNotSupportedOnNonKioskSessionError));
-
- using file_manager::VolumeManager;
- using file_manager::Volume;
- VolumeManager* const volume_manager =
- VolumeManager::Get(chrome_details_.GetProfile());
- DCHECK(volume_manager);
-
- const bool writable =
- params->options.writable.get() && *params->options.writable.get();
- if (writable &&
- !app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
- return RespondNow(Error(kRequiresFileSystemWriteError));
- }
-
- base::WeakPtr<file_manager::Volume> volume =
- volume_manager->FindVolumeById(params->options.volume_id);
- if (!volume.get())
- return RespondNow(Error(kVolumeNotFoundError));
-
- const GURL site =
- util::GetSiteForExtensionId(extension_id(), chrome_details_.GetProfile());
- scoped_refptr<storage::FileSystemContext> file_system_context =
- content::BrowserContext::GetStoragePartitionForSite(
- chrome_details_.GetProfile(), site)
- ->GetFileSystemContext();
- storage::ExternalFileSystemBackend* const backend =
- file_system_context->external_backend();
- DCHECK(backend);
-
- base::FilePath virtual_path;
- if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path))
- return RespondNow(Error(kSecurityError));
-
- if (writable && (volume->is_read_only()))
- return RespondNow(Error(kSecurityError));
-
- consent_provider.RequestConsent(
- *extension(), volume, writable,
- base::Bind(&FileSystemRequestFileSystemFunction::OnConsentReceived, this,
- volume, writable));
- return RespondLater();
-}
-
-void FileSystemRequestFileSystemFunction::OnConsentReceived(
- const base::WeakPtr<file_manager::Volume>& volume,
- bool writable,
- ConsentProvider::Consent result) {
- using file_manager::VolumeManager;
- using file_manager::Volume;
-
- // Render frame host can be gone before this callback method is executed.
- if (!render_frame_host()) {
- Respond(Error(""));
- return;
- }
-
- switch (result) {
- case ConsentProvider::CONSENT_REJECTED:
- Respond(Error(kSecurityError));
- return;
-
- case ConsentProvider::CONSENT_IMPOSSIBLE:
- Respond(Error(kConsentImpossible));
- return;
-
- case ConsentProvider::CONSENT_GRANTED:
- break;
- }
-
- if (!volume.get()) {
- Respond(Error(kVolumeNotFoundError));
- return;
- }
-
- const GURL site =
- util::GetSiteForExtensionId(extension_id(), chrome_details_.GetProfile());
- scoped_refptr<storage::FileSystemContext> file_system_context =
- content::BrowserContext::GetStoragePartitionForSite(
- chrome_details_.GetProfile(), site)
- ->GetFileSystemContext();
- storage::ExternalFileSystemBackend* const backend =
- file_system_context->external_backend();
- DCHECK(backend);
-
- base::FilePath virtual_path;
- if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) {
- Respond(Error(kSecurityError));
- return;
- }
-
- storage::IsolatedContext* const isolated_context =
- storage::IsolatedContext::GetInstance();
- DCHECK(isolated_context);
-
- const storage::FileSystemURL original_url =
- file_system_context->CreateCrackedFileSystemURL(
- GURL(std::string(kExtensionScheme) + url::kStandardSchemeSeparator +
- extension_id()),
- storage::kFileSystemTypeExternal, virtual_path);
-
- // Set a fixed register name, as the automatic one would leak the mount point
- // directory.
- std::string register_name = "fs";
- const std::string file_system_id =
- isolated_context->RegisterFileSystemForPath(
- storage::kFileSystemTypeNativeForPlatformApp,
- std::string() /* file_system_id */, original_url.path(),
- &register_name);
- if (file_system_id.empty()) {
- Respond(Error(kSecurityError));
- return;
- }
-
- backend->GrantFileAccessToExtension(extension_->id(), virtual_path);
-
- // Grant file permissions to the renderer hosting component.
- content::ChildProcessSecurityPolicy* policy =
- content::ChildProcessSecurityPolicy::GetInstance();
- DCHECK(policy);
-
- // Read-only permisisons.
- policy->GrantReadFile(render_frame_host()->GetProcess()->GetID(),
- volume->mount_path());
- policy->GrantReadFileSystem(render_frame_host()->GetProcess()->GetID(),
- file_system_id);
-
- // Additional write permissions.
- if (writable) {
- policy->GrantCreateReadWriteFile(render_frame_host()->GetProcess()->GetID(),
- volume->mount_path());
- policy->GrantCopyInto(render_frame_host()->GetProcess()->GetID(),
- volume->mount_path());
- policy->GrantWriteFileSystem(render_frame_host()->GetProcess()->GetID(),
- file_system_id);
- policy->GrantDeleteFromFileSystem(
- render_frame_host()->GetProcess()->GetID(), file_system_id);
- policy->GrantCreateFileForFileSystem(
- render_frame_host()->GetProcess()->GetID(), file_system_id);
- }
-
- std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
- dict->SetString("file_system_id", file_system_id);
- dict->SetString("file_system_path", register_name);
-
- Respond(OneArgument(std::move(dict)));
-}
-
-FileSystemGetVolumeListFunction::FileSystemGetVolumeListFunction()
- : chrome_details_(this) {}
-
-FileSystemGetVolumeListFunction::~FileSystemGetVolumeListFunction() {}
-
-ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
- // Only kiosk apps in kiosk sessions can use this API.
- // Additionally it is enabled for whitelisted component extensions and apps.
- file_system_api::ConsentProviderDelegate consent_provider_delegate(
- chrome_details_.GetProfile(), render_frame_host());
- file_system_api::ConsentProvider consent_provider(&consent_provider_delegate);
-
- if (!consent_provider.IsGrantable(*extension()))
- return RespondNow(Error(kNotSupportedOnNonKioskSessionError));
- std::vector<api::file_system::Volume> result_volume_list;
- FillVolumeList(chrome_details_.GetProfile(), &result_volume_list);
-
- return RespondNow(ArgumentList(
- api::file_system::GetVolumeList::Results::Create(result_volume_list)));
-}
-#endif
-
-} // namespace extensions

Powered by Google App Engine
This is Rietveld 408576698