| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/extensions/file_manager/private_api_mount.h" |
| |
| #include <memory> |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/feature_list.h" |
| #include "base/files/file_util.h" |
| #include "base/format_macros.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "chrome/browser/ash/drive/file_system_util.h" |
| #include "chrome/browser/ash/extensions/file_manager/event_router.h" |
| #include "chrome/browser/ash/extensions/file_manager/private_api_util.h" |
| #include "chrome/browser/ash/file_manager/file_tasks_notifier.h" |
| #include "chrome/browser/ash/file_manager/file_tasks_notifier_factory.h" |
| #include "chrome/browser/ash/file_manager/fileapi_util.h" |
| #include "chrome/browser/ash/file_manager/volume_manager.h" |
| #include "chrome/browser/ash/fileapi/file_system_backend.h" |
| #include "chrome/browser/ash/fusebox/fusebox_server.h" |
| #include "chrome/browser/ash/smb_client/smb_service.h" |
| #include "chrome/browser/ash/smb_client/smb_service_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/extensions/api/file_manager_private.h" |
| #include "chromeos/ash/components/dbus/cros_disks/cros_disks_client.h" |
| #include "chromeos/ash/components/disks/disk_mount_manager.h" |
| #include "components/drive/event_logger.h" |
| #include "components/services/unzip/content/unzip_service.h" |
| #include "components/services/unzip/public/cpp/unzip.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "google_apis/common/task_util.h" |
| #include "storage/browser/file_system/file_system_url.h" |
| #include "ui/shell_dialogs/selected_file_info.h" |
| |
| namespace extensions { |
| namespace { |
| |
| std::string Redact(std::string_view path) { |
| return LOG_IS_ON(INFO) ? base::StrCat({"'", path, "'"}) : "(redacted)"; |
| } |
| |
| } // namespace |
| |
| using ::ash::disks::DiskMountManager; |
| using content::BrowserThread; |
| namespace file_manager_private = extensions::api::file_manager_private; |
| |
| FileManagerPrivateAddMountFunction::FileManagerPrivateAddMountFunction() = |
| default; |
| |
| FileManagerPrivateAddMountFunction::~FileManagerPrivateAddMountFunction() = |
| default; |
| |
| ExtensionFunction::ResponseAction FileManagerPrivateAddMountFunction::Run() { |
| using file_manager_private::AddMount::Params; |
| const std::optional<Params> params = Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| Profile* const profile = Profile::FromBrowserContext(browser_context()); |
| if (drive::EventLogger* logger = file_manager::util::GetLogger(profile)) { |
| logger->Log(logging::LOGGING_INFO, "%s[%s] called. (source: '%s')", name(), |
| request_uuid().AsLowercaseString().c_str(), |
| params->file_url.empty() ? "(none)" : params->file_url.c_str()); |
| } |
| set_log_on_completion(true); |
| |
| const scoped_refptr<storage::FileSystemContext> file_system_context = |
| file_manager::util::GetFileSystemContextForRenderFrameHost( |
| profile, render_frame_host()); |
| const storage::FileSystemURL fs_url( |
| file_system_context->CrackURLInFirstPartyContext(GURL(params->file_url))); |
| path_ = ash::FileSystemBackend::CanHandleURL(fs_url) |
| ? (fs_url.TypeImpliesPathIsReal() |
| ? fs_url.path() |
| : fusebox::Server::SubstituteFuseboxFilePath(fs_url)) |
| : base::FilePath(); |
| |
| if (auto* notifier = |
| file_manager::file_tasks::FileTasksNotifierFactory::GetForProfile( |
| profile)) { |
| std::vector<storage::FileSystemURL> urls; |
| urls.push_back(std::move(fs_url)); |
| notifier->NotifyFileTasks(urls); |
| } |
| |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (params->password) { |
| options_.push_back("password=" + *params->password); |
| } |
| |
| extension_ = base::ToLowerASCII(path_.Extension()); |
| |
| // Detect the file path encoding of ZIP archives. |
| if (extension_ == ".zip") { |
| unzip::DetectEncoding( |
| unzip::LaunchUnzipper(), path_, |
| base::BindOnce(&FileManagerPrivateAddMountFunction::OnEncodingDetected, |
| this)); |
| } else { |
| FinishMounting(); |
| } |
| |
| // Pass back the actual source path of the mount point. |
| return RespondNow(WithArguments(path_.AsUTF8Unsafe())); |
| } |
| |
| void FileManagerPrivateAddMountFunction::OnEncodingDetected( |
| const Encoding encoding) { |
| // Pass the detected ZIP encoding as a mount option. |
| std::string& option = options_.emplace_back("encoding="); |
| |
| if (IsShiftJisOrVariant(encoding) || encoding == RUSSIAN_CP866) { |
| option += MimeEncodingName(encoding); |
| } else { |
| option += "libzip"; |
| } |
| |
| FinishMounting(); |
| } |
| |
| void FileManagerPrivateAddMountFunction::FinishMounting() { |
| DiskMountManager* const disk_mount_manager = DiskMountManager::GetInstance(); |
| DCHECK(disk_mount_manager); |
| disk_mount_manager->MountPath(path_.AsUTF8Unsafe(), std::move(extension_), |
| path_.BaseName().AsUTF8Unsafe(), |
| std::move(options_), ash::MountType::kArchive, |
| ash::MountAccessMode::kReadWrite, |
| base::DoNothing()); |
| } |
| |
| FileManagerPrivateCancelMountingFunction:: |
| FileManagerPrivateCancelMountingFunction() = default; |
| |
| FileManagerPrivateCancelMountingFunction:: |
| ~FileManagerPrivateCancelMountingFunction() = default; |
| |
| ExtensionFunction::ResponseAction |
| FileManagerPrivateCancelMountingFunction::Run() { |
| using file_manager_private::CancelMounting::Params; |
| const std::optional<Params> params = Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| Profile* const profile = Profile::FromBrowserContext(browser_context()); |
| |
| if (drive::EventLogger* logger = file_manager::util::GetLogger(profile)) { |
| logger->Log(logging::LOGGING_INFO, "%s[%s] called. (source: '%s')", name(), |
| request_uuid().AsLowercaseString().c_str(), |
| params->file_url.empty() ? "(none)" : params->file_url.c_str()); |
| } |
| set_log_on_completion(true); |
| |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| const scoped_refptr<storage::FileSystemContext> file_system_context = |
| file_manager::util::GetFileSystemContextForRenderFrameHost( |
| profile, render_frame_host()); |
| const storage::FileSystemURL fs_url( |
| file_system_context->CrackURLInFirstPartyContext(GURL(params->file_url))); |
| base::FilePath path = |
| ash::FileSystemBackend::CanHandleURL(fs_url) |
| ? (fs_url.TypeImpliesPathIsReal() |
| ? fs_url.path() |
| : fusebox::Server::SubstituteFuseboxFilePath(fs_url)) |
| : base::FilePath(); |
| |
| DiskMountManager* const disk_mount_manager = DiskMountManager::GetInstance(); |
| DCHECK(disk_mount_manager); |
| disk_mount_manager->UnmountPath( |
| path.AsUTF8Unsafe(), |
| base::BindOnce(&FileManagerPrivateCancelMountingFunction::OnCancelled, |
| this)); |
| |
| return RespondLater(); |
| } |
| |
| void FileManagerPrivateCancelMountingFunction::OnCancelled( |
| ash::MountError error) { |
| if (error == ash::MountError::kSuccess) { |
| Respond(NoArguments()); |
| } else { |
| Respond(Error(file_manager_private::ToString( |
| file_manager::MountErrorToMountCompletedStatus(error)))); |
| } |
| } |
| |
| ExtensionFunction::ResponseAction FileManagerPrivateRemoveMountFunction::Run() { |
| using file_manager_private::RemoveMount::Params; |
| const std::optional<Params> params = Params::Create(args()); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| Profile* const profile = Profile::FromBrowserContext(browser_context()); |
| if (drive::EventLogger* logger = file_manager::util::GetLogger(profile)) { |
| logger->Log(logging::LOGGING_INFO, "%s[%s] called. (volume_id: '%s')", |
| name(), request_uuid().AsLowercaseString().c_str(), |
| params->volume_id.c_str()); |
| } |
| set_log_on_completion(true); |
| |
| using file_manager::Volume; |
| using file_manager::VolumeManager; |
| VolumeManager* const volume_manager = VolumeManager::Get(profile); |
| DCHECK(volume_manager); |
| |
| std::string volume_id = params->volume_id; |
| volume_manager->ConvertFuseBoxFSPVolumeIdToFSPIfNeeded(&volume_id); |
| |
| const base::WeakPtr<Volume> volume = |
| volume_manager->FindVolumeById(volume_id); |
| if (!volume) { |
| LOG(ERROR) << "Cannot find volume " << Redact(volume_id); |
| return RespondNow(Error(file_manager_private::ToString( |
| api::file_manager_private::MountError::kPathNotMounted))); |
| } |
| |
| switch (volume->type()) { |
| case file_manager::VOLUME_TYPE_REMOVABLE_DISK_PARTITION: |
| case file_manager::VOLUME_TYPE_MOUNTED_ARCHIVE_FILE: |
| DiskMountManager::GetInstance()->UnmountPath( |
| volume->mount_path().value(), |
| base::BindOnce( |
| &FileManagerPrivateRemoveMountFunction::OnDiskUnmounted, this)); |
| return RespondLater(); |
| |
| case file_manager::VOLUME_TYPE_PROVIDED: { |
| auto* service = |
| ash::file_system_provider::Service::Get(browser_context()); |
| DCHECK(service); |
| if (!service->RequestUnmount(volume->provider_id(), |
| volume->file_system_id())) { |
| return RespondNow(Error("Unmount failed")); |
| } |
| return RespondNow(NoArguments()); |
| } |
| |
| case file_manager::VOLUME_TYPE_CROSTINI: |
| file_manager::VolumeManager::Get(profile)->RemoveSshfsCrostiniVolume( |
| volume->mount_path(), |
| base::BindOnce( |
| &FileManagerPrivateRemoveMountFunction::OnSshFsUnmounted, this)); |
| return RespondLater(); |
| |
| case file_manager::VOLUME_TYPE_SMB: |
| ash::smb_client::SmbServiceFactory::Get(profile)->UnmountSmbFs( |
| volume->mount_path()); |
| return RespondNow(NoArguments()); |
| |
| case file_manager::VOLUME_TYPE_TESTING: |
| file_manager::VolumeManager::Get(profile) |
| ->RemoveVolumeForTesting( // IN-TEST |
| volume->mount_path(), volume->type(), volume->device_type(), |
| volume->is_read_only(), volume->storage_device_path(), |
| volume->drive_label(), volume->file_system_type()); |
| |
| return RespondNow(NoArguments()); |
| |
| case file_manager::VOLUME_TYPE_GUEST_OS: |
| // TODO(crbug/1293229): Figure out if we need to support unmounting. I'm |
| // not actually sure if it's possible to reach here. |
| NOTREACHED(); |
| |
| default: |
| // Requested unmounting a device which is not unmountable. |
| return RespondNow(Error("Invalid volume type")); |
| } |
| } |
| |
| void FileManagerPrivateRemoveMountFunction::OnSshFsUnmounted(bool ok) { |
| if (ok) { |
| Respond(NoArguments()); |
| } else { |
| Respond(Error(file_manager_private::ToString( |
| api::file_manager_private::MountError::kUnknownError))); |
| } |
| } |
| |
| void FileManagerPrivateRemoveMountFunction::OnDiskUnmounted( |
| ash::MountError error) { |
| if (error == ash::MountError::kSuccess) { |
| Respond(NoArguments()); |
| } else { |
| Respond(Error(file_manager_private::ToString( |
| file_manager::MountErrorToMountCompletedStatus(error)))); |
| } |
| } |
| |
| ExtensionFunction::ResponseAction |
| FileManagerPrivateGetVolumeMetadataListFunction::Run() { |
| if (!args().empty()) { |
| return RespondNow(Error("Invalid arguments")); |
| } |
| |
| Profile* const profile = Profile::FromBrowserContext(browser_context()); |
| const std::vector<base::WeakPtr<file_manager::Volume>>& volume_list = |
| file_manager::VolumeManager::Get(profile)->GetVolumeList(); |
| |
| std::string log_string; |
| std::vector<file_manager_private::VolumeMetadata> result; |
| for (const auto& volume : volume_list) { |
| file_manager_private::VolumeMetadata volume_metadata; |
| file_manager::util::VolumeToVolumeMetadata(profile, *volume, |
| &volume_metadata); |
| result.push_back(std::move(volume_metadata)); |
| if (!log_string.empty()) { |
| log_string += ", "; |
| } |
| log_string += volume->mount_path().AsUTF8Unsafe(); |
| } |
| |
| if (drive::EventLogger* logger = file_manager::util::GetLogger(profile)) { |
| logger->Log(logging::LOGGING_INFO, |
| "%s[%s] succeeded. (results: '[%s]', %" PRIuS " mount points)", |
| name(), request_uuid().AsLowercaseString().c_str(), |
| log_string.c_str(), result.size()); |
| } |
| |
| return RespondNow(ArgumentList( |
| file_manager_private::GetVolumeMetadataList::Results::Create(result))); |
| } |
| |
| } // namespace extensions |