| // 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/numerics/safe_conversions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/types/expected.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" |
| |
| 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, |
| std::unique_ptr<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, std::move(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, |
| std::unique_ptr<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, std::move(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; |
| } |
| } |
| |
| // Convert the change from the mojom type to a native type. |
| ProvidedFileSystemObserver::Change ParseChange(mojom::FSPChangePtr change) { |
| ProvidedFileSystemObserver::Change result; |
| result.entry_path = change->path; |
| result.change_type = ParseChangeType(change->type); |
| return result; |
| } |
| |
| // 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; |
| } |
| |
| } // 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::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::ExtensionLoaded( |
| bool configurable, |
| bool watchable, |
| bool multiple_mounts, |
| mojom::FileSystemSource source, |
| const std::string& name, |
| const std::string& id) { |
| 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; |
| } |
| ash::file_system_provider::Capabilities capabilities{ |
| configurable, watchable, multiple_mounts, extension_source}; |
| auto provider = |
| std::make_unique<ash::file_system_provider::ExtensionProvider>( |
| ProfileManager::GetPrimaryUserProfile(), std::move(provider_id), |
| std::move(capabilities), name); |
| 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::unique_ptr<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, |
| std::move(value), /*has_more=*/false, |
| profile); |
| break; |
| } |
| case mojom::FSPOperationResponse::kGetEntryMetadataSuccess: { |
| using extensions::api::file_system_provider_internal:: |
| GetMetadataRequestedSuccess::Params; |
| std::unique_ptr<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, |
| std::move(value), /*has_more=*/false, |
| profile); |
| break; |
| } |
| case mojom::FSPOperationResponse::kGetActionsSuccess: { |
| using extensions::api::file_system_provider_internal:: |
| GetActionsRequestedSuccess::Params; |
| std::unique_ptr<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, |
| std::move(value), /*has_more=*/false, |
| profile); |
| break; |
| } |
| case mojom::FSPOperationResponse::kReadDirectorySuccess: { |
| using extensions::api::file_system_provider_internal:: |
| ReadDirectoryRequestedSuccess::Params; |
| std::unique_ptr<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, |
| std::move(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::unique_ptr<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, |
| std::move(value), has_more, profile); |
| break; |
| } |
| case mojom::FSPOperationResponse::kGenericSuccess: { |
| using extensions::api::file_system_provider_internal:: |
| OperationRequestedSuccess::Params; |
| std::unique_ptr<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, |
| std::move(value), /*has_more=*/false, |
| profile); |
| break; |
| } |
| case mojom::FSPOperationResponse::kGenericFailure: { |
| using extensions::api::file_system_provider_internal:: |
| OperationRequestedError::Params; |
| std::unique_ptr<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, |
| std::move(value), operation_error, profile); |
| break; |
| } |
| } |
| 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::unique_ptr<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=*/std::make_unique<RequestValue>(), |
| /*has_more=*/false) |
| : manager.value()->RejectRequest( |
| request_id, /*response=*/std::make_unique<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 |