/* -*- 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 "FileSystemHandle.h" #include "FileSystemDirectoryHandle.h" #include "FileSystemFileHandle.h" #include "fs/FileSystemRequestHandler.h" #include "js/StructuredClone.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/FileSystemHandleBinding.h" #include "mozilla/dom/FileSystemLog.h" #include "mozilla/dom/FileSystemManager.h" #include "mozilla/dom/Promise-inl.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/StorageManager.h" #include "mozilla/dom/StructuredCloneHolder.h" #include "mozilla/dom/quota/QuotaCommon.h" #include "mozilla/ipc/PBackgroundSharedTypes.h" #include "nsJSPrincipals.h" #include "nsString.h" #include "prio.h" #include "private/pprio.h" #include "xpcpublic.h" namespace mozilla::dom { namespace { bool ConstructHandleMetadata(JSContext* aCx, nsIGlobalObject* aGlobal, JSStructuredCloneReader* aReader, const bool aDirectory, fs::FileSystemEntryMetadata& aMetadata) { using namespace mozilla::dom::fs; EntryId entryId; if (!entryId.SetLength(32u, fallible)) { return false; } if (!JS_ReadBytes(aReader, static_cast(entryId.BeginWriting()), 32u)) { return false; } Name name; if (!StructuredCloneHolder::ReadString(aReader, name)) { return false; } mozilla::ipc::PrincipalInfo storageKey; if (!nsJSPrincipals::ReadPrincipalInfo(aReader, storageKey)) { return false; } QM_TRY_UNWRAP(auto hasEqualStorageKey, aGlobal->HasEqualStorageKey(storageKey), false); if (!hasEqualStorageKey) { LOG(("Blocking deserialization of %s due to cross-origin", NS_ConvertUTF16toUTF8(name).get())); return false; } LOG_VERBOSE(("Deserializing %s", NS_ConvertUTF16toUTF8(name).get())); aMetadata = fs::FileSystemEntryMetadata(entryId, name, aDirectory); return true; } } // namespace NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileSystemHandle) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(FileSystemHandle) NS_IMPL_CYCLE_COLLECTING_RELEASE(FileSystemHandle) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FileSystemHandle) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FileSystemHandle) NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal) // Don't unlink mManager! NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FileSystemHandle) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END FileSystemHandle::FileSystemHandle( nsIGlobalObject* aGlobal, RefPtr& aManager, const fs::FileSystemEntryMetadata& aMetadata, fs::FileSystemRequestHandler* aRequestHandler) : mGlobal(aGlobal), mManager(aManager), mMetadata(aMetadata), mRequestHandler(aRequestHandler) { MOZ_ASSERT(!mMetadata.entryId().IsEmpty()); } // WebIDL Boilerplate nsIGlobalObject* FileSystemHandle::GetParentObject() const { return mGlobal; } JSObject* FileSystemHandle::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return FileSystemHandle_Binding::Wrap(aCx, this, aGivenProto); } // WebIDL Interface void FileSystemHandle::GetName(nsAString& aResult) { aResult = mMetadata.entryName(); } already_AddRefed FileSystemHandle::IsSameEntry( FileSystemHandle& aOther, ErrorResult& aError) const { RefPtr promise = Promise::Create(GetParentObject(), aError); if (aError.Failed()) { return nullptr; } // Handles the case of "dir = createdir foo; removeEntry(foo); file = // createfile foo; issameentry(dir, file)" const bool result = mMetadata.entryId().Equals(aOther.mMetadata.entryId()) && Kind() == aOther.Kind(); promise->MaybeResolve(result); return promise.forget(); } already_AddRefed FileSystemHandle::Move(const nsAString& aName, ErrorResult& aError) { LOG(("Move %s to %s", NS_ConvertUTF16toUTF8(mMetadata.entryName()).get(), NS_ConvertUTF16toUTF8(aName).get())); fs::EntryId parent; // empty means same directory return Move(parent, aName, aError); } already_AddRefed FileSystemHandle::Move( FileSystemDirectoryHandle& aParent, ErrorResult& aError) { LOG(("Move %s to %s/%s", NS_ConvertUTF16toUTF8(mMetadata.entryName()).get(), NS_ConvertUTF16toUTF8(aParent.mMetadata.entryName()).get(), NS_ConvertUTF16toUTF8(mMetadata.entryName()).get())); return Move(aParent, mMetadata.entryName(), aError); } already_AddRefed FileSystemHandle::Move( FileSystemDirectoryHandle& aParent, const nsAString& aName, ErrorResult& aError) { LOG(("Move %s to %s/%s", NS_ConvertUTF16toUTF8(mMetadata.entryName()).get(), NS_ConvertUTF16toUTF8(aParent.mMetadata.entryName()).get(), NS_ConvertUTF16toUTF8(aName).get())); return Move(aParent.mMetadata.entryId(), aName, aError); } already_AddRefed FileSystemHandle::Move(const fs::EntryId& aParentId, const nsAString& aName, ErrorResult& aError) { RefPtr promise = Promise::Create(GetParentObject(), aError); if (aError.Failed()) { return nullptr; } fs::Name name(aName); if (!aParentId.IsEmpty()) { fs::FileSystemChildMetadata newMetadata; newMetadata.parentId() = aParentId; newMetadata.childName() = aName; mRequestHandler->MoveEntry(mManager, this, mMetadata, newMetadata, promise, aError); } else { mRequestHandler->RenameEntry(mManager, this, mMetadata, name, promise, aError); } if (aError.Failed()) { return nullptr; } // Other handles to this will be broken, and the spec is ok with this, but we // need to update our EntryId and name promise->AddCallbacksWithCycleCollectedArgs( [name](JSContext* aCx, JS::Handle aValue, ErrorResult& aRv, FileSystemHandle* aHandle) { // XXX Fix entryId! LOG(("Changing FileSystemHandle name from %s to %s", NS_ConvertUTF16toUTF8(aHandle->mMetadata.entryName()).get(), NS_ConvertUTF16toUTF8(name).get())); aHandle->mMetadata.entryName() = name; }, [](JSContext* aCx, JS::Handle aValue, ErrorResult& aRv, FileSystemHandle* aHandle) { LOG(("reject of move for %s", NS_ConvertUTF16toUTF8(aHandle->mMetadata.entryName()).get())); }, RefPtr(this)); return promise.forget(); } // [Serializable] implementation // static already_AddRefed FileSystemHandle::ReadStructuredClone( JSContext* aCx, nsIGlobalObject* aGlobal, JSStructuredCloneReader* aReader) { LOG_VERBOSE(("Reading File/DirectoryHandle")); uint32_t kind = static_cast(FileSystemHandleKind::EndGuard_); if (!JS_ReadBytes(aReader, reinterpret_cast(&kind), sizeof(uint32_t))) { return nullptr; } if (kind == static_cast(FileSystemHandleKind::Directory)) { RefPtr result = FileSystemHandle::ConstructDirectoryHandle(aCx, aGlobal, aReader); return result.forget(); } if (kind == static_cast(FileSystemHandleKind::File)) { RefPtr result = FileSystemHandle::ConstructFileHandle(aCx, aGlobal, aReader); return result.forget(); } return nullptr; } bool FileSystemHandle::WriteStructuredClone( JSContext* aCx, JSStructuredCloneWriter* aWriter) const { LOG_VERBOSE(("Writing File/DirectoryHandle")); MOZ_ASSERT(mMetadata.entryId().Length() == 32); auto kind = static_cast(Kind()); if (NS_WARN_IF(!JS_WriteBytes(aWriter, static_cast(&kind), sizeof(uint32_t)))) { return false; } if (NS_WARN_IF(!JS_WriteBytes( aWriter, static_cast(mMetadata.entryId().get()), mMetadata.entryId().Length()))) { return false; } if (!StructuredCloneHolder::WriteString(aWriter, mMetadata.entryName())) { return false; } // Needed to make sure the destination nsIGlobalObject is from the same // origin/principal QM_TRY_INSPECT(const auto& storageKey, mGlobal->GetStorageKey(), false); return nsJSPrincipals::WritePrincipalInfo(aWriter, storageKey); } // static already_AddRefed FileSystemHandle::ConstructFileHandle( JSContext* aCx, nsIGlobalObject* aGlobal, JSStructuredCloneReader* aReader) { LOG(("Reading FileHandle")); fs::FileSystemEntryMetadata metadata; if (!ConstructHandleMetadata(aCx, aGlobal, aReader, /* aDirectory */ false, metadata)) { return nullptr; } RefPtr storageManager = aGlobal->GetStorageManager(); if (!storageManager) { return nullptr; } // Note that the actor may not exist or may not be connected yet. RefPtr fileSystemManager = storageManager->GetFileSystemManager(); RefPtr fsHandle = new FileSystemFileHandle(aGlobal, fileSystemManager, metadata); return fsHandle.forget(); } // static already_AddRefed FileSystemHandle::ConstructDirectoryHandle(JSContext* aCx, nsIGlobalObject* aGlobal, JSStructuredCloneReader* aReader) { LOG(("Reading DirectoryHandle")); fs::FileSystemEntryMetadata metadata; if (!ConstructHandleMetadata(aCx, aGlobal, aReader, /* aDirectory */ true, metadata)) { return nullptr; } RefPtr storageManager = aGlobal->GetStorageManager(); if (!storageManager) { return nullptr; } // Note that the actor may not exist or may not be connected yet. RefPtr fileSystemManager = storageManager->GetFileSystemManager(); RefPtr fsHandle = new FileSystemDirectoryHandle(aGlobal, fileSystemManager, metadata); return fsHandle.forget(); } } // namespace mozilla::dom