chromium/chrome/browser/ash/extensions/file_manager/private_api_mount.cc

// 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/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(const 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::FileTasksNotifier::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_IN_MIGRATION();
      [[fallthrough]];

    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