chromium/chrome/browser/ash/crosapi/file_system_provider_service_ash.cc

// Copyright 2022 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/crosapi/file_system_provider_service_ash.h"

#include "base/base64.h"
#include "base/numerics/safe_conversions.h"
#include "base/trace_event/trace_event.h"
#include "base/types/expected.h"
#include "chrome/browser/ash/file_system_provider/cloud_file_info.h"
#include "chrome/browser/ash/file_system_provider/icon_set.h"
#include "chrome/browser/ash/file_system_provider/operation_request_manager.h"
#include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
#include "chrome/browser/ash/file_system_provider/provider_interface.h"
#include "chrome/browser/ash/file_system_provider/request_value.h"
#include "chrome/browser/ash/file_system_provider/service.h"
#include "chrome/browser/chromeos/extensions/file_system_provider/provider_function.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "extensions/common/extension_id.h"
#include "ui/gfx/codec/png_codec.h"
#include "url/url_constants.h"

using ash::file_system_provider::IconSet;
using ash::file_system_provider::MountOptions;
using ash::file_system_provider::OpenedFiles;
using ash::file_system_provider::ProvidedFileSystemInfo;
using ash::file_system_provider::ProvidedFileSystemInterface;
using ash::file_system_provider::ProvidedFileSystemObserver;
using ash::file_system_provider::ProviderId;
using ash::file_system_provider::RequestValue;
using ash::file_system_provider::Service;
using ash::file_system_provider::Watchers;

namespace crosapi {
namespace {

constexpr char kDeserializationError[] = "deserialization error";

// Either returns a valid request manager for provider-level requests, or else
// an error string.
base::expected<ash::file_system_provider::RequestManager*, std::string>
GetProviderRequestManager(Profile* profile,
                          extensions::ExtensionId extension_id) {
  Service* service = Service::Get(profile);
  if (!service) {
    return base::unexpected("File system provider service not found.");
  }

  ash::file_system_provider::ProviderInterface* provider =
      service->GetProvider(ProviderId::CreateFromExtensionId(extension_id));
  if (!provider) {
    return base::unexpected(
        extensions::FileErrorToString(base::File::FILE_ERROR_NOT_FOUND));
  }

  return provider->GetRequestManager();
}

// Either returns a valid request manager for file system level requests, or
// else an error string.
base::expected<ash::file_system_provider::OperationRequestManager*, std::string>
GetProvidedFileSystemRequestManager(
    Profile* profile,
    const mojom::FileSystemIdPtr& file_system_id) {
  Service* service = Service::Get(profile);
  if (!service) {
    return base::unexpected("File system provider service not found.");
  }

  ProvidedFileSystemInterface* file_system = service->GetProvidedFileSystem(
      ProviderId::CreateFromExtensionId(file_system_id->provider),
      file_system_id->id);
  if (!file_system) {
    return base::unexpected(
        extensions::FileErrorToString(base::File::FILE_ERROR_NOT_FOUND));
  }

  return file_system->GetRequestManager();
}

// Forwards an operation response from an extension to the request manager and
// then returns the error message. Empty string means success.
std::string ForwardOperationResponse(mojom::FileSystemIdPtr file_system_id,
                                     int64_t request_id,
                                     const RequestValue& value,
                                     bool has_more,
                                     Profile* profile) {
  auto manager = GetProvidedFileSystemRequestManager(profile, file_system_id);
  if (!manager.has_value())
    return manager.error();

  const base::File::Error result =
      manager.value()->FulfillRequest(request_id, value, has_more);
  if (result != base::File::FILE_OK) {
    return extensions::FileErrorToString(result);
  }
  return "";
}

// Forwards an operation failure from an extension to the request manager and
// then returns the error message. Empty string means success.
std::string ForwardOperationFailure(mojom::FileSystemIdPtr file_system_id,
                                    int64_t request_id,
                                    const RequestValue& value,
                                    base::File::Error error,
                                    Profile* profile) {
  auto manager = GetProvidedFileSystemRequestManager(profile, file_system_id);
  if (!manager.has_value())
    return manager.error();

  const base::File::Error result =
      manager.value()->RejectRequest(request_id, value, error);
  if (result != base::File::FILE_OK) {
    return extensions::FileErrorToString(result);
  }
  return "";
}

// Convert |result| to a string, empty string for success and invokes
// |callback|.
void RunErrorCallback(base::OnceCallback<void(const std::string&)> callback,
                      const base::File::Error result) {
  std::string error;
  if (result != base::File::FILE_OK) {
    error = extensions::FileErrorToString(result);
  }
  std::move(callback).Run(std::move(error));
}

// Converts from native filesystem watchers to their mojom counterparts
std::vector<crosapi::mojom::FSPWatcherPtr> ConvertWatchersToMojom(
    const Watchers& watchers) {
  std::vector<crosapi::mojom::FSPWatcherPtr> mojom_watchers;
  for (const auto& watcher : watchers) {
    crosapi::mojom::FSPWatcherPtr watcher_item =
        crosapi::mojom::FSPWatcher::New();
    watcher_item->entry_path = watcher.second.entry_path;
    watcher_item->recursive = watcher.second.recursive;
    watcher_item->last_tag = watcher.second.last_tag;
    mojom_watchers.push_back(std::move(watcher_item));
  }
  return mojom_watchers;
}

// Converts from native filesystem opened files to their mojom counterparts
std::vector<crosapi::mojom::OpenedFilePtr> ConvertOpenedFilesToMojom(
    const OpenedFiles& opened_files) {
  std::vector<crosapi::mojom::OpenedFilePtr> mojom_opened_files;
  for (const auto& opened_file : opened_files) {
    crosapi::mojom::OpenedFilePtr opened_file_item =
        crosapi::mojom::OpenedFile::New();
    opened_file_item->open_request_id = opened_file.first;
    opened_file_item->file_path = opened_file.second.file_path.value();
    switch (opened_file.second.mode) {
      case ash::file_system_provider::OPEN_FILE_MODE_READ:
        opened_file_item->mode = crosapi::mojom::OpenFileMode::kRead;
        break;
      case ash::file_system_provider::OPEN_FILE_MODE_WRITE:
        opened_file_item->mode = crosapi::mojom::OpenFileMode::kWrite;
        break;
    }
    mojom_opened_files.push_back(std::move(opened_file_item));
  }
  return mojom_opened_files;
}

// Converts native file system to mojom.
crosapi::mojom::FileSystemInfoPtr ConvertFileSystemToMojom(
    Profile* profile,
    const ProvidedFileSystemInfo& file_system_info,
    const std::string& provider) {
  Service* const service = Service::Get(profile);
  crosapi::mojom::FileSystemInfoPtr item =
      crosapi::mojom::FileSystemInfo::New();
  item->metadata = crosapi::mojom::FileSystemMetadata::New();
  item->metadata->file_system_id = crosapi::mojom::FileSystemId::New();
  item->metadata->file_system_id->provider = provider;

  ProvidedFileSystemInterface* const file_system =
      service->GetProvidedFileSystem(file_system_info.provider_id(),
                                     file_system_info.file_system_id());

  DCHECK(file_system);

  item->watchers = ConvertWatchersToMojom(
      file_system_info.watchable() ? *file_system->GetWatchers() : Watchers());
  item->opened_files = ConvertOpenedFilesToMojom(file_system->GetOpenedFiles());
  item->metadata->file_system_id->id = file_system_info.file_system_id();
  item->metadata->display_name = file_system_info.display_name();
  item->metadata->writable = file_system_info.writable();
  item->metadata->opened_files_limit = file_system_info.opened_files_limit();
  item->metadata->supports_notify = file_system_info.supports_notify_tag();

  return item;
}

storage::WatcherManager::ChangeType ParseChangeType(mojom::FSPChangeType type) {
  switch (type) {
    case mojom::FSPChangeType::kChanged:
      return storage::WatcherManager::CHANGED;
    case mojom::FSPChangeType::kDeleted:
      return storage::WatcherManager::DELETED;
  }
}

std::unique_ptr<ash::file_system_provider::CloudFileInfo> ParseCloudFileInfo(
    mojom::CloudFileInfoPtr cloud_file_info) {
  if (cloud_file_info.is_null()) {
    return nullptr;
  }
  if (!cloud_file_info->version_tag.has_value()) {
    return nullptr;
  }
  return std::make_unique<ash::file_system_provider::CloudFileInfo>(
      cloud_file_info->version_tag.value());
}

// Convert the change from the mojom type to a native type.
ProvidedFileSystemObserver::Change ParseChange(mojom::FSPChangePtr change) {
  return ProvidedFileSystemObserver::Change(
      change->path, ParseChangeType(change->type),
      ParseCloudFileInfo(std::move(change->cloud_file_info)));
}

// Converts a list of child changes from the mojom type to a native type.
std::unique_ptr<ProvidedFileSystemObserver::Changes> ParseChanges(
    std::vector<mojom::FSPChangePtr> changes) {
  auto results = std::make_unique<ProvidedFileSystemObserver::Changes>();
  for (auto& change : changes) {
    results->push_back(ParseChange(std::move(change)));
  }
  return results;
}

std::optional<GURL> ToPNGDataURL(const gfx::ImageSkia& image) {
  if (image.isNull()) {
    return std::nullopt;
  }
  std::vector<unsigned char> output;
  gfx::PNGCodec::EncodeBGRASkBitmap(*image.bitmap(), false, &output);
  GURL url("data:image/png;base64," + base::Base64Encode(output));
  if (url.spec().size() > url::kMaxURLChars) {
    return std::nullopt;
  }
  return url;
}

}  // namespace

FileSystemProviderServiceAsh::FileSystemProviderServiceAsh() = default;
FileSystemProviderServiceAsh::~FileSystemProviderServiceAsh() = default;

void FileSystemProviderServiceAsh::BindReceiver(
    mojo::PendingReceiver<mojom::FileSystemProviderService> receiver) {
  receivers_.Add(this, std::move(receiver));
}

void FileSystemProviderServiceAsh::RegisterFileSystemProvider(
    mojo::PendingRemote<mojom::FileSystemProvider> provider) {
  remotes_.Add(mojo::Remote<mojom::FileSystemProvider>(std::move(provider)));
}

void FileSystemProviderServiceAsh::Mount(mojom::FileSystemMetadataPtr metadata,
                                         bool persistent,
                                         MountCallback callback) {
  MountWithProfile(std::move(metadata), persistent, std::move(callback),
                   ProfileManager::GetPrimaryUserProfile());
}

void FileSystemProviderServiceAsh::Unmount(
    mojom::FileSystemIdPtr file_system_id,
    UnmountCallback callback) {
  UnmountWithProfile(std::move(file_system_id), std::move(callback),
                     ProfileManager::GetPrimaryUserProfile());
}

void FileSystemProviderServiceAsh::GetAll(const std::string& provider,
                                          GetAllCallback callback) {
  GetAllWithProfile(provider, std::move(callback),
                    ProfileManager::GetPrimaryUserProfile());
}
void FileSystemProviderServiceAsh::Get(mojom::FileSystemIdPtr file_system_id,
                                       GetCallback callback) {
  GetWithProfile(std::move(file_system_id), std::move(callback),
                 ProfileManager::GetPrimaryUserProfile());
}
void FileSystemProviderServiceAsh::Notify(
    mojom::FileSystemIdPtr file_system_id,
    mojom::FSPWatcherPtr watcher,
    mojom::FSPChangeType type,
    std::vector<mojom::FSPChangePtr> changes,
    NotifyCallback callback) {
  NotifyWithProfile(std::move(file_system_id), std::move(watcher), type,
                    std::move(changes), std::move(callback),
                    ProfileManager::GetPrimaryUserProfile());
}

void FileSystemProviderServiceAsh::DeprecatedOperationFinished(
    mojom::FSPOperationResponse response,
    mojom::FileSystemIdPtr file_system_id,
    int64_t request_id,
    std::vector<base::Value> args,
    OperationFinishedCallback callback) {
  base::Value::List list;
  for (auto& value : args) {
    list.Append(std::move(value));
  }
  OperationFinished(response, std::move(file_system_id), request_id,
                    std::move(list), std::move(callback));
}

void FileSystemProviderServiceAsh::OperationFinished(
    mojom::FSPOperationResponse response,
    mojom::FileSystemIdPtr file_system_id,
    int64_t request_id,
    base::Value::List args,
    OperationFinishedCallback callback) {
  OperationFinishedWithProfile(response, std::move(file_system_id), request_id,
                               std::move(args), std::move(callback),
                               ProfileManager::GetPrimaryUserProfile());
}

void FileSystemProviderServiceAsh::OpenFileFinishedSuccessfully(
    mojom::FileSystemIdPtr file_system_id,
    int64_t request_id,
    base::Value::List args,
    OperationFinishedCallback callback) {
  OpenFileFinishedSuccessfullyWithProfile(
      std::move(file_system_id), request_id, std::move(args),
      std::move(callback), ProfileManager::GetPrimaryUserProfile());
}

void FileSystemProviderServiceAsh::MountFinished(
    const std::string& extension_id,
    int64_t request_id,
    base::Value::List args,
    MountFinishedCallback callback) {
  MountFinishedWithProfile(extension_id, request_id, std::move(args),
                           std::move(callback),
                           ProfileManager::GetPrimaryUserProfile());
}

void FileSystemProviderServiceAsh::ExtensionLoadedDeprecated(
    bool configurable,
    bool watchable,
    bool multiple_mounts,
    mojom::FileSystemSource source,
    const std::string& name,
    const std::string& id) {
  ExtensionLoaded(configurable, watchable, multiple_mounts, source, name, id,
                  /*icon16x16=*/gfx::ImageSkia(),
                  /*icon32x32=*/gfx::ImageSkia());
}

void FileSystemProviderServiceAsh::ExtensionLoaded(
    bool configurable,
    bool watchable,
    bool multiple_mounts,
    mojom::FileSystemSource source,
    const std::string& name,
    const std::string& id,
    const gfx::ImageSkia& icon16x16,
    const gfx::ImageSkia& icon32x32) {
  Service* const service =
      Service::Get(ProfileManager::GetPrimaryUserProfile());
  DCHECK(service);

  ProviderId provider_id = ProviderId::CreateFromExtensionId(id);
  extensions::FileSystemProviderSource extension_source;
  switch (source) {
    case crosapi::mojom::FileSystemSource::kFile:
      extension_source = extensions::FileSystemProviderSource::SOURCE_FILE;
      break;
    case crosapi::mojom::FileSystemSource::kNetwork:
      extension_source = extensions::FileSystemProviderSource::SOURCE_NETWORK;
      break;
    case crosapi::mojom::FileSystemSource::kDevice:
      extension_source = extensions::FileSystemProviderSource::SOURCE_DEVICE;
      break;
  }

  std::optional<IconSet> icon_set;
  std::optional<GURL> url_icon16x16 = ToPNGDataURL(icon16x16);
  std::optional<GURL> url_icon32x32 = ToPNGDataURL(icon32x32);
  if (url_icon16x16 && url_icon32x32) {
    icon_set = IconSet();
    icon_set->SetIcon(IconSet::IconSize::SIZE_16x16, *url_icon16x16);
    icon_set->SetIcon(IconSet::IconSize::SIZE_32x32, *url_icon32x32);
  }

  auto provider =
      std::make_unique<ash::file_system_provider::ExtensionProvider>(
          ProfileManager::GetPrimaryUserProfile(), std::move(provider_id),
          ash::file_system_provider::Capabilities{
              .configurable = configurable,
              .watchable = watchable,
              .multiple_mounts = multiple_mounts,
              .source = extension_source},
          name, icon_set);
  service->RegisterProvider(std::move(provider));
}

void FileSystemProviderServiceAsh::ExtensionUnloaded(const std::string& id,
                                                     bool due_to_shutdown) {
  Service* const service =
      Service::Get(ProfileManager::GetPrimaryUserProfile());
  DCHECK(service);
  ProviderId provider_id = ProviderId::CreateFromExtensionId(id);
  service->UnregisterProvider(
      provider_id,
      due_to_shutdown
          ? ash::file_system_provider::Service::UNMOUNT_REASON_SHUTDOWN
          : ash::file_system_provider::Service::UNMOUNT_REASON_USER);
}

void FileSystemProviderServiceAsh::MountWithProfile(
    mojom::FileSystemMetadataPtr metadata,
    bool persistent,
    MountCallback callback,
    Profile* profile) {
  Service* const service = Service::Get(profile);
  DCHECK(service);

  MountOptions options;
  options.file_system_id = metadata->file_system_id->id;
  options.display_name = metadata->display_name;
  options.writable = metadata->writable;
  options.opened_files_limit =
      base::saturated_cast<int>(metadata->opened_files_limit);
  options.supports_notify_tag = metadata->supports_notify;
  options.persistent = persistent;

  const base::File::Error result = service->MountFileSystem(
      ProviderId::CreateFromExtensionId(metadata->file_system_id->provider),
      options);
  RunErrorCallback(std::move(callback), result);
}

void FileSystemProviderServiceAsh::UnmountWithProfile(
    mojom::FileSystemIdPtr file_system_id,
    UnmountCallback callback,
    Profile* profile) {
  Service* const service = Service::Get(profile);
  const base::File::Error result = service->UnmountFileSystem(
      ProviderId::CreateFromExtensionId(file_system_id->provider),
      file_system_id->id, Service::UNMOUNT_REASON_USER);
  RunErrorCallback(std::move(callback), result);
}

void FileSystemProviderServiceAsh::GetAllWithProfile(
    const std::string& provider,
    GetAllCallback callback,
    Profile* profile) {
  Service* const service = Service::Get(profile);
  ProviderId provider_id = ProviderId::CreateFromExtensionId(provider);
  const std::vector<ProvidedFileSystemInfo> file_systems =
      service->GetProvidedFileSystemInfoList(provider_id);

  std::vector<crosapi::mojom::FileSystemInfoPtr> items;

  for (const auto& file_system_info : file_systems) {
    items.push_back(
        ConvertFileSystemToMojom(profile, file_system_info, provider));
  }

  std::move(callback).Run(std::move(items));
}

void FileSystemProviderServiceAsh::FileSystemProviderServiceAsh::GetWithProfile(
    mojom::FileSystemIdPtr file_system_id,
    GetCallback callback,
    Profile* profile) {
  Service* const service = Service::Get(profile);
  DCHECK(service);

  ProvidedFileSystemInterface* file_system = service->GetProvidedFileSystem(
      ProviderId::CreateFromExtensionId(file_system_id->provider),
      file_system_id->id);
  if (!file_system) {
    std::move(callback).Run(nullptr);
    return;
  }

  std::move(callback).Run(ConvertFileSystemToMojom(
      profile, file_system->GetFileSystemInfo(), file_system_id->provider));
}

void FileSystemProviderServiceAsh::NotifyWithProfile(
    mojom::FileSystemIdPtr file_system_id,
    mojom::FSPWatcherPtr watcher,
    mojom::FSPChangeType type,
    std::vector<mojom::FSPChangePtr> changes,
    NotifyCallback callback,
    Profile* profile) {
  Service* const service = Service::Get(profile);
  DCHECK(service);

  ProvidedFileSystemInterface* const file_system =
      service->GetProvidedFileSystem(
          ProviderId::CreateFromExtensionId(file_system_id->provider),
          file_system_id->id);
  if (!file_system) {
    std::move(callback).Run(
        extensions::FileErrorToString(base::File::FILE_ERROR_NOT_FOUND));
    return;
  }

  file_system->Notify(watcher->entry_path, watcher->recursive,
                      ParseChangeType(type), ParseChanges(std::move(changes)),
                      watcher->last_tag,
                      base::BindOnce(&RunErrorCallback, std::move(callback)));
}

void FileSystemProviderServiceAsh::OperationFinishedWithProfile(
    mojom::FSPOperationResponse response,
    mojom::FileSystemIdPtr file_system_id,
    int64_t request_id,
    base::Value::List args,
    OperationFinishedCallback callback,
    Profile* profile) {
  std::string error;
  switch (response) {
    case mojom::FSPOperationResponse::kUnknown:
      error = "unknown operation response";
      break;
    case mojom::FSPOperationResponse::kUnmountSuccess: {
      using extensions::api::file_system_provider_internal::
          UnmountRequestedSuccess::Params;
      std::optional<Params> params = Params::Create(std::move(args));
      if (!params) {
        error = kDeserializationError;
        break;
      }
      auto value = RequestValue::CreateForUnmountSuccess(std::move(*params));
      error = ForwardOperationResponse(std::move(file_system_id), request_id,
                                       value, /*has_more=*/false, profile);
      break;
    }
    case mojom::FSPOperationResponse::kGetEntryMetadataSuccess: {
      using extensions::api::file_system_provider_internal::
          GetMetadataRequestedSuccess::Params;
      std::optional<Params> params = Params::Create(std::move(args));
      if (!params) {
        error = kDeserializationError;
        break;
      }
      auto value =
          RequestValue::CreateForGetMetadataSuccess(std::move(*params));
      error = ForwardOperationResponse(std::move(file_system_id), request_id,
                                       value, /*has_more=*/false, profile);
      break;
    }
    case mojom::FSPOperationResponse::kGetActionsSuccess: {
      using extensions::api::file_system_provider_internal::
          GetActionsRequestedSuccess::Params;
      std::optional<Params> params = Params::Create(std::move(args));
      if (!params) {
        error = kDeserializationError;
        break;
      }
      auto value = RequestValue::CreateForGetActionsSuccess(std::move(*params));
      error = ForwardOperationResponse(std::move(file_system_id), request_id,
                                       value, /*has_more=*/false, profile);
      break;
    }
    case mojom::FSPOperationResponse::kReadDirectorySuccess: {
      using extensions::api::file_system_provider_internal::
          ReadDirectoryRequestedSuccess::Params;
      std::optional<Params> params = Params::Create(std::move(args));
      if (!params) {
        error = kDeserializationError;
        break;
      }
      bool has_more = params->has_more;
      auto value =
          RequestValue::CreateForReadDirectorySuccess(std::move(*params));
      error = ForwardOperationResponse(std::move(file_system_id), request_id,
                                       value, has_more, profile);
      break;
    }
    case mojom::FSPOperationResponse::kReadFileSuccess: {
      TRACE_EVENT0("file_system_provider", "ReadFileSuccessWithProfile");
      using extensions::api::file_system_provider_internal::
          ReadFileRequestedSuccess::Params;
      std::optional<Params> params = Params::Create(std::move(args));
      if (!params) {
        error = kDeserializationError;
        break;
      }
      bool has_more = params->has_more;
      auto value = RequestValue::CreateForReadFileSuccess(std::move(*params));
      error = ForwardOperationResponse(std::move(file_system_id), request_id,
                                       value, has_more, profile);
      break;
    }
    case mojom::FSPOperationResponse::kOpenFileSuccess: {
      TRACE_EVENT0("file_system_provider", "OpenFileSuccessWithProfile");
      using extensions::api::file_system_provider_internal::
          OpenFileRequestedSuccess::Params;
      std::optional<Params> params = Params::Create(std::move(args));
      if (!params) {
        error = kDeserializationError;
        break;
      }
      auto value = RequestValue::CreateForOpenFileSuccess(std::move(*params));
      error = ForwardOperationResponse(std::move(file_system_id), request_id,
                                       value, /*has_more=*/false, profile);
      break;
    }
    case mojom::FSPOperationResponse::kGenericSuccess: {
      using extensions::api::file_system_provider_internal::
          OperationRequestedSuccess::Params;
      std::optional<Params> params = Params::Create(std::move(args));
      if (!params) {
        error = kDeserializationError;
        break;
      }
      auto value = RequestValue::CreateForOperationSuccess(std::move(*params));
      error = ForwardOperationResponse(std::move(file_system_id), request_id,
                                       value, /*has_more=*/false, profile);
      break;
    }
    case mojom::FSPOperationResponse::kGenericFailure: {
      using extensions::api::file_system_provider_internal::
          OperationRequestedError::Params;
      std::optional<Params> params = Params::Create(std::move(args));
      if (!params) {
        error = kDeserializationError;
        break;
      }
      base::File::Error operation_error =
          extensions::ProviderErrorToFileError(params->error);
      auto value = RequestValue::CreateForOperationError(std::move(*params));
      error = ForwardOperationFailure(std::move(file_system_id), request_id,
                                      value, operation_error, profile);
      break;
    }
  }
  std::move(callback).Run(std::move(error));
}

void FileSystemProviderServiceAsh::OpenFileFinishedSuccessfullyWithProfile(
    mojom::FileSystemIdPtr file_system_id,
    int64_t request_id,
    base::Value::List args,
    OperationFinishedCallback callback,
    Profile* profile) {
  using extensions::api::file_system_provider_internal::
      OpenFileRequestedSuccess::Params;
  std::optional<Params> params = Params::Create(std::move(args));
  if (!params) {
    std::move(callback).Run(kDeserializationError);
  }
  auto value = RequestValue::CreateForOpenFileSuccess(std::move(*params));
  std::string error =
      ForwardOperationResponse(std::move(file_system_id), request_id, value,
                               /*has_more=*/false, profile);
  std::move(callback).Run(std::move(error));
}

void FileSystemProviderServiceAsh::MountFinishedWithProfile(
    const std::string& extension_id,
    int64_t request_id,
    base::Value::List args,
    MountFinishedCallback callback,
    Profile* profile) {
  auto manager = GetProviderRequestManager(profile, extension_id);
  if (!manager.has_value()) {
    std::move(callback).Run(manager.error());
    return;
  }

  using extensions::api::file_system_provider_internal::RespondToMountRequest::
      Params;
  std::optional<Params> params = Params::Create(std::move(args));
  if (!params) {
    std::move(callback).Run(kDeserializationError);
    return;
  }
  base::File::Error mount_error =
      extensions::ProviderErrorToFileError(params->error);
  base::File::Error result =
      mount_error == base::File::FILE_OK
          ? manager.value()->FulfillRequest(request_id,
                                            /*response=*/RequestValue(),
                                            /*has_more=*/false)
          : manager.value()->RejectRequest(
                request_id, /*response=*/RequestValue(), mount_error);

  std::string error_str;
  if (result != base::File::FILE_OK)
    error_str = extensions::FileErrorToString(result);
  std::move(callback).Run(error_str);
}

}  // namespace crosapi