/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "fs/FileSystemRequestHandler.h" #include "FileSystemEntryMetadataArray.h" #include "fs/FileSystemConstants.h" #include "mozilla/ResultVariant.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/dom/BlobImpl.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FileSystemAccessHandleChild.h" #include "mozilla/dom/FileSystemDirectoryHandle.h" #include "mozilla/dom/FileSystemFileHandle.h" #include "mozilla/dom/FileSystemHandle.h" #include "mozilla/dom/FileSystemHelpers.h" #include "mozilla/dom/FileSystemLog.h" #include "mozilla/dom/FileSystemManager.h" #include "mozilla/dom/FileSystemManagerChild.h" #include "mozilla/dom/FileSystemSyncAccessHandle.h" #include "mozilla/dom/FileSystemWritableFileStream.h" #include "mozilla/dom/FileSystemWritableFileStreamChild.h" #include "mozilla/dom/IPCBlobUtils.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerRef.h" #include "mozilla/dom/fs/IPCRejectReporter.h" #include "mozilla/dom/quota/QuotaCommon.h" #include "mozilla/dom/quota/ResultExtensions.h" #include "mozilla/ipc/RandomAccessStreamUtils.h" namespace mozilla::dom::fs { using mozilla::ipc::RejectCallback; namespace { void HandleFailedStatus(nsresult aError, const RefPtr& aPromise) { switch (aError) { case NS_ERROR_FILE_ACCESS_DENIED: aPromise->MaybeRejectWithNotAllowedError("Permission denied"); break; case NS_ERROR_FILE_NOT_FOUND: [[fallthrough]]; case NS_ERROR_DOM_NOT_FOUND_ERR: aPromise->MaybeRejectWithNotFoundError("Entry not found"); break; case NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR: aPromise->MaybeRejectWithInvalidModificationError("Disallowed by system"); break; case NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR: aPromise->MaybeRejectWithNoModificationAllowedError( "No modification allowed"); break; case NS_ERROR_DOM_TYPE_MISMATCH_ERR: aPromise->MaybeRejectWithTypeMismatchError("Wrong type"); break; case NS_ERROR_DOM_INVALID_MODIFICATION_ERR: aPromise->MaybeRejectWithInvalidModificationError("Invalid modification"); break; default: if (NS_FAILED(aError)) { aPromise->MaybeRejectWithUnknownError("Unknown failure"); } else { aPromise->MaybeResolveWithUndefined(); } break; } } bool MakeResolution(nsIGlobalObject* aGlobal, FileSystemGetEntriesResponse&& aResponse, const bool& /* aResult */, RefPtr& aSink) { // TODO: Add page size to FileSystemConstants, preallocate and handle overflow const auto& listing = aResponse.get_FileSystemDirectoryListing(); for (const auto& it : listing.files()) { aSink->AppendElement(it); } for (const auto& it : listing.directories()) { aSink->AppendElement(it); } return true; } RefPtr MakeResolution( nsIGlobalObject* aGlobal, FileSystemGetHandleResponse&& aResponse, const RefPtr& /* aResult */, RefPtr& aManager) { RefPtr result = new FileSystemDirectoryHandle( aGlobal, aManager, FileSystemEntryMetadata(aResponse.get_EntryId(), kRootName, /* directory */ true)); return result; } RefPtr MakeResolution( nsIGlobalObject* aGlobal, FileSystemGetHandleResponse&& aResponse, const RefPtr& /* aResult */, const Name& aName, RefPtr& aManager) { RefPtr result = new FileSystemDirectoryHandle( aGlobal, aManager, FileSystemEntryMetadata(aResponse.get_EntryId(), aName, /* directory */ true)); return result; } RefPtr MakeResolution( nsIGlobalObject* aGlobal, FileSystemGetHandleResponse&& aResponse, const RefPtr& /* aResult */, const Name& aName, RefPtr& aManager) { RefPtr result = new FileSystemFileHandle( aGlobal, aManager, FileSystemEntryMetadata(aResponse.get_EntryId(), aName, /* directory */ false)); return result; } RefPtr MakeResolution( nsIGlobalObject* aGlobal, FileSystemGetAccessHandleResponse&& aResponse, const RefPtr& /* aReturns */, const FileSystemEntryMetadata& aMetadata, RefPtr& aManager) { auto& properties = aResponse.get_FileSystemAccessHandleProperties(); QM_TRY_UNWRAP( RefPtr result, FileSystemSyncAccessHandle::Create( aGlobal, aManager, std::move(properties.streamParams()), std::move(properties.accessHandleChildEndpoint()), std::move(properties.accessHandleControlChildEndpoint()), aMetadata), nullptr); return result; } RefPtr MakeResolution( nsIGlobalObject* aGlobal, FileSystemGetWritableFileStreamResponse&& aResponse, const RefPtr& /* aReturns */, const FileSystemEntryMetadata& aMetadata, RefPtr& aManager) { auto& properties = aResponse.get_FileSystemWritableFileStreamProperties(); auto* const actor = static_cast( properties.writableFileStream().AsChild().get()); QM_TRY_UNWRAP(RefPtr result, FileSystemWritableFileStream::Create( aGlobal, aManager, std::move(properties.streamParams()), actor, aMetadata), nullptr); return result; } RefPtr MakeResolution(nsIGlobalObject* aGlobal, FileSystemGetFileResponse&& aResponse, const RefPtr& /* aResult */, const Name& aName, RefPtr& aManager) { auto& fileProperties = aResponse.get_FileSystemFileProperties(); RefPtr blobImpl = IPCBlobUtils::Deserialize(fileProperties.file()); MOZ_ASSERT(blobImpl); RefPtr result = File::Create(aGlobal, blobImpl); return result; } template void ResolveCallback( TResponse&& aResponse, RefPtr aPromise, // NOLINT(performance-unnecessary-value-param) Args&&... args) { MOZ_ASSERT(aPromise); QM_TRY(OkIf(Promise::PromiseState::Pending == aPromise->State()), QM_VOID); if (TResponse::Tnsresult == aResponse.type()) { HandleFailedStatus(aResponse.get_nsresult(), aPromise); return; } auto resolution = MakeResolution(aPromise->GetParentObject(), std::forward(aResponse), std::forward(args)...); if (!resolution) { aPromise->MaybeRejectWithUnknownError("Could not complete request"); return; } aPromise->MaybeResolve(resolution); } template <> void ResolveCallback( FileSystemRemoveEntryResponse&& aResponse, RefPtr aPromise) { // NOLINT(performance-unnecessary-value-param) MOZ_ASSERT(aPromise); QM_TRY(OkIf(Promise::PromiseState::Pending == aPromise->State()), QM_VOID); if (FileSystemRemoveEntryResponse::Tvoid_t == aResponse.type()) { aPromise->MaybeResolveWithUndefined(); return; } MOZ_ASSERT(FileSystemRemoveEntryResponse::Tnsresult == aResponse.type()); HandleFailedStatus(aResponse.get_nsresult(), aPromise); } template <> void ResolveCallback( FileSystemMoveEntryResponse&& aResponse, RefPtr aPromise) { // NOLINT(performance-unnecessary-value-param) MOZ_ASSERT(aPromise); QM_TRY(OkIf(Promise::PromiseState::Pending == aPromise->State()), QM_VOID); MOZ_ASSERT(FileSystemMoveEntryResponse::Tnsresult == aResponse.type()); const auto& status = aResponse.get_nsresult(); if (NS_OK == status) { aPromise->MaybeResolveWithUndefined(); return; } HandleFailedStatus(status, aPromise); } template <> void ResolveCallback(FileSystemResolveResponse&& aResponse, // NOLINTNEXTLINE(performance-unnecessary-value-param) RefPtr aPromise) { MOZ_ASSERT(aPromise); QM_TRY(OkIf(Promise::PromiseState::Pending == aPromise->State()), QM_VOID); if (FileSystemResolveResponse::Tnsresult == aResponse.type()) { HandleFailedStatus(aResponse.get_nsresult(), aPromise); return; } auto& maybePath = aResponse.get_MaybeFileSystemPath(); if (maybePath.isSome()) { aPromise->MaybeResolve(maybePath.value().path()); return; } // Spec says if there is no parent/child relationship, return null aPromise->MaybeResolve(JS::NullHandleValue); } template ::value, bool> = true> mozilla::ipc::ResolveCallback SelectResolveCallback( RefPtr aPromise, // NOLINT(performance-unnecessary-value-param) Args&&... args) { using TOverload = void (*)(TResponse&&, RefPtr, Args...); return static_cast>( // NOLINTNEXTLINE(modernize-avoid-bind) std::bind(static_cast(ResolveCallback), std::placeholders::_1, aPromise, std::forward(args)...)); } template ::value, bool> = true> mozilla::ipc::ResolveCallback SelectResolveCallback( RefPtr aPromise, // NOLINT(performance-unnecessary-value-param) Args&&... args) { using TOverload = void (*)(TResponse&&, RefPtr, const TReturns&, Args...); return static_cast>( // NOLINTNEXTLINE(modernize-avoid-bind) std::bind(static_cast(ResolveCallback), std::placeholders::_1, aPromise, TReturns(), std::forward(args)...)); } void RejectCallback( RefPtr aPromise, // NOLINT(performance-unnecessary-value-param) mozilla::ipc::ResponseRejectReason aReason) { IPCRejectReporter(aReason); QM_TRY(OkIf(Promise::PromiseState::Pending == aPromise->State()), QM_VOID); aPromise->MaybeRejectWithUndefined(); } mozilla::ipc::RejectCallback GetRejectCallback( RefPtr aPromise) { // NOLINT(performance-unnecessary-value-param) return static_cast( // NOLINTNEXTLINE(modernize-avoid-bind) std::bind(RejectCallback, aPromise, std::placeholders::_1)); } struct BeginRequestFailureCallback { explicit BeginRequestFailureCallback(RefPtr aPromise) : mPromise(std::move(aPromise)) {} void operator()(nsresult aRv) const { if (aRv == NS_ERROR_DOM_SECURITY_ERR) { mPromise->MaybeRejectWithSecurityError( "Security error when calling GetDirectory"); return; } mPromise->MaybeRejectWithUnknownError("Could not create actor"); } RefPtr mPromise; }; } // namespace void FileSystemRequestHandler::GetRootHandle( RefPtr aManager, // NOLINT(performance-unnecessary-value-param) RefPtr aPromise, // NOLINT(performance-unnecessary-value-param) ErrorResult& aError) { MOZ_ASSERT(aManager); MOZ_ASSERT(aPromise); LOG(("GetRootHandle")); if (aManager->IsShutdown()) { aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN); return; } aManager->BeginRequest( [onResolve = SelectResolveCallback>( aPromise, aManager), onReject = GetRejectCallback(aPromise)](const auto& actor) mutable { actor->SendGetRootHandle(std::move(onResolve), std::move(onReject)); }, BeginRequestFailureCallback(aPromise)); } void FileSystemRequestHandler::GetDirectoryHandle( RefPtr& aManager, const FileSystemChildMetadata& aDirectory, bool aCreate, RefPtr aPromise, // NOLINT(performance-unnecessary-value-param) ErrorResult& aError) { MOZ_ASSERT(aManager); MOZ_ASSERT(!aDirectory.parentId().IsEmpty()); MOZ_ASSERT(aPromise); LOG(("GetDirectoryHandle")); if (aManager->IsShutdown()) { aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN); return; } if (!IsValidName(aDirectory.childName())) { aPromise->MaybeRejectWithTypeError("Invalid directory name"); return; } aManager->BeginRequest( [request = FileSystemGetHandleRequest(aDirectory, aCreate), onResolve = SelectResolveCallback>( aPromise, aDirectory.childName(), aManager), onReject = GetRejectCallback(aPromise)](const auto& actor) mutable { actor->SendGetDirectoryHandle(request, std::move(onResolve), std::move(onReject)); }, BeginRequestFailureCallback(aPromise)); } void FileSystemRequestHandler::GetFileHandle( RefPtr& aManager, const FileSystemChildMetadata& aFile, bool aCreate, RefPtr aPromise, // NOLINT(performance-unnecessary-value-param) ErrorResult& aError) { MOZ_ASSERT(aManager); MOZ_ASSERT(!aFile.parentId().IsEmpty()); MOZ_ASSERT(aPromise); LOG(("GetFileHandle")); if (aManager->IsShutdown()) { aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN); return; } if (!IsValidName(aFile.childName())) { aPromise->MaybeRejectWithTypeError("Invalid filename"); return; } aManager->BeginRequest( [request = FileSystemGetHandleRequest(aFile, aCreate), onResolve = SelectResolveCallback>( aPromise, aFile.childName(), aManager), onReject = GetRejectCallback(aPromise)](const auto& actor) mutable { actor->SendGetFileHandle(request, std::move(onResolve), std::move(onReject)); }, BeginRequestFailureCallback(aPromise)); } void FileSystemRequestHandler::GetAccessHandle( RefPtr& aManager, const FileSystemEntryMetadata& aFile, const RefPtr& aPromise, ErrorResult& aError) { MOZ_ASSERT(aManager); MOZ_ASSERT(aPromise); LOG(("GetAccessHandle %s", NS_ConvertUTF16toUTF8(aFile.entryName()).get())); if (aManager->IsShutdown()) { aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN); return; } aManager->BeginRequest( [request = FileSystemGetAccessHandleRequest(aFile.entryId()), onResolve = SelectResolveCallback>( aPromise, aFile, aManager), onReject = GetRejectCallback(aPromise)](const auto& actor) mutable { actor->SendGetAccessHandle(request, std::move(onResolve), std::move(onReject)); }, BeginRequestFailureCallback(aPromise)); } void FileSystemRequestHandler::GetWritable(RefPtr& aManager, const FileSystemEntryMetadata& aFile, bool aKeepData, const RefPtr& aPromise, ErrorResult& aError) { MOZ_ASSERT(aManager); MOZ_ASSERT(aPromise); LOG(("GetWritable %s keep %d", NS_ConvertUTF16toUTF8(aFile.entryName()).get(), aKeepData)); // XXX This should be removed once bug 1798513 is fixed. if (!StaticPrefs::dom_fs_writable_file_stream_enabled()) { aError.Throw(NS_ERROR_NOT_IMPLEMENTED); return; } if (aManager->IsShutdown()) { aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN); return; } aManager->BeginRequest( [request = FileSystemGetWritableRequest(aFile.entryId(), aKeepData), onResolve = SelectResolveCallback>( aPromise, aFile, aManager), onReject = GetRejectCallback(aPromise)](const auto& actor) mutable { actor->SendGetWritable(request, std::move(onResolve), std::move(onReject)); }, [promise = aPromise](const auto&) { promise->MaybeRejectWithUnknownError("Could not create actor"); }); } void FileSystemRequestHandler::GetFile( RefPtr& aManager, const FileSystemEntryMetadata& aFile, RefPtr aPromise, // NOLINT(performance-unnecessary-value-param) ErrorResult& aError) { MOZ_ASSERT(aManager); MOZ_ASSERT(!aFile.entryId().IsEmpty()); MOZ_ASSERT(aPromise); LOG(("GetFile %s", NS_ConvertUTF16toUTF8(aFile.entryName()).get())); if (aManager->IsShutdown()) { aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN); return; } aManager->BeginRequest( [request = FileSystemGetFileRequest(aFile.entryId()), onResolve = SelectResolveCallback>( aPromise, aFile.entryName(), aManager), onReject = GetRejectCallback(aPromise)](const auto& actor) mutable { actor->SendGetFile(request, std::move(onResolve), std::move(onReject)); }, BeginRequestFailureCallback(aPromise)); } void FileSystemRequestHandler::GetEntries( RefPtr& aManager, const EntryId& aDirectory, PageNumber aPage, RefPtr aPromise, // NOLINT(performance-unnecessary-value-param) RefPtr& aSink, ErrorResult& aError) { MOZ_ASSERT(aManager); MOZ_ASSERT(!aDirectory.IsEmpty()); MOZ_ASSERT(aPromise); LOG(("GetEntries, page %u", aPage)); if (aManager->IsShutdown()) { aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN); return; } aManager->BeginRequest( [request = FileSystemGetEntriesRequest(aDirectory, aPage), onResolve = SelectResolveCallback( aPromise, aSink), onReject = GetRejectCallback(aPromise)](const auto& actor) mutable { actor->SendGetEntries(request, std::move(onResolve), std::move(onReject)); }, BeginRequestFailureCallback(aPromise)); } void FileSystemRequestHandler::RemoveEntry( RefPtr& aManager, const FileSystemChildMetadata& aEntry, bool aRecursive, RefPtr aPromise, // NOLINT(performance-unnecessary-value-param) ErrorResult& aError) { MOZ_ASSERT(aManager); MOZ_ASSERT(!aEntry.parentId().IsEmpty()); MOZ_ASSERT(aPromise); LOG(("RemoveEntry")); if (aManager->IsShutdown()) { aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN); return; } if (!IsValidName(aEntry.childName())) { aPromise->MaybeRejectWithTypeError("Invalid name"); return; } aManager->BeginRequest( [request = FileSystemRemoveEntryRequest(aEntry, aRecursive), onResolve = SelectResolveCallback(aPromise), onReject = GetRejectCallback(aPromise)](const auto& actor) mutable { actor->SendRemoveEntry(request, std::move(onResolve), std::move(onReject)); }, BeginRequestFailureCallback(aPromise)); } void FileSystemRequestHandler::MoveEntry( RefPtr& aManager, FileSystemHandle* aHandle, const FileSystemEntryMetadata& aEntry, const FileSystemChildMetadata& aNewEntry, RefPtr aPromise, // NOLINT(performance-unnecessary-value-param) ErrorResult& aError) { MOZ_ASSERT(aPromise); LOG(("MoveEntry")); if (aManager->IsShutdown()) { aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN); return; } // reject invalid names: empty, path separators, current & parent directories if (!IsValidName(aNewEntry.childName())) { aPromise->MaybeRejectWithTypeError("Invalid name"); return; } aManager->BeginRequest( [request = FileSystemMoveEntryRequest(aEntry, aNewEntry), onResolve = SelectResolveCallback(aPromise), onReject = GetRejectCallback(aPromise)](const auto& actor) mutable { actor->SendMoveEntry(request, std::move(onResolve), std::move(onReject)); }, BeginRequestFailureCallback(aPromise)); } void FileSystemRequestHandler::RenameEntry( RefPtr& aManager, FileSystemHandle* aHandle, const FileSystemEntryMetadata& aEntry, const Name& aName, RefPtr aPromise, // NOLINT(performance-unnecessary-value-param) ErrorResult& aError) { MOZ_ASSERT(!aEntry.entryId().IsEmpty()); MOZ_ASSERT(aPromise); LOG(("RenameEntry")); if (aManager->IsShutdown()) { aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN); return; } // reject invalid names: empty, path separators, current & parent directories if (!IsValidName(aName)) { aPromise->MaybeRejectWithTypeError("Invalid name"); return; } aManager->BeginRequest( [request = FileSystemRenameEntryRequest(aEntry, aName), onResolve = SelectResolveCallback(aPromise), onReject = GetRejectCallback(aPromise)](const auto& actor) mutable { actor->SendRenameEntry(request, std::move(onResolve), std::move(onReject)); }, BeginRequestFailureCallback(aPromise)); } void FileSystemRequestHandler::Resolve( RefPtr& aManager, // NOLINTNEXTLINE(performance-unnecessary-value-param) const FileSystemEntryPair& aEndpoints, RefPtr aPromise, ErrorResult& aError) { MOZ_ASSERT(aManager); MOZ_ASSERT(!aEndpoints.parentId().IsEmpty()); MOZ_ASSERT(!aEndpoints.childId().IsEmpty()); MOZ_ASSERT(aPromise); LOG(("Resolve")); if (aManager->IsShutdown()) { aError.Throw(NS_ERROR_ILLEGAL_DURING_SHUTDOWN); return; } aManager->BeginRequest( [request = FileSystemResolveRequest(aEndpoints), onResolve = SelectResolveCallback(aPromise), onReject = GetRejectCallback(aPromise)](const auto& actor) mutable { actor->SendResolve(request, std::move(onResolve), std::move(onReject)); }, BeginRequestFailureCallback(aPromise)); } } // namespace mozilla::dom::fs