/* -*- 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 "mozilla/dom/StructuredCloneHolder.h" #include #include "ErrorList.h" #include "MainThreadUtils.h" #include "js/CallArgs.h" #include "js/Value.h" #include "js/WasmModule.h" #include "js/Wrapper.h" #include "jsapi.h" #include "mozilla/AlreadyAddRefed.h" #include "mozilla/AutoRestore.h" #include "mozilla/ErrorResult.h" #include "mozilla/OwningNonNull.h" #include "mozilla/RefPtr.h" #include "mozilla/ScopeExit.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/Blob.h" #include "mozilla/dom/BlobBinding.h" #include "mozilla/dom/BlobImpl.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/ClonedErrorHolder.h" #include "mozilla/dom/ClonedErrorHolderBinding.h" #include "mozilla/dom/DirectoryBinding.h" #include "mozilla/dom/DOMJSClass.h" #include "mozilla/dom/DOMTypes.h" #include "mozilla/dom/Directory.h" #include "mozilla/dom/DocGroup.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FileList.h" #include "mozilla/dom/FileListBinding.h" #include "mozilla/dom/FormData.h" #include "mozilla/dom/FormDataBinding.h" #include "mozilla/dom/ImageBitmap.h" #include "mozilla/dom/ImageBitmapBinding.h" #include "mozilla/dom/JSExecutionManager.h" #include "mozilla/dom/MessagePort.h" #include "mozilla/dom/MessagePortBinding.h" #include "mozilla/dom/OffscreenCanvas.h" #include "mozilla/dom/OffscreenCanvasBinding.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/StructuredCloneBlob.h" #include "mozilla/dom/StructuredCloneHolderBinding.h" #include "mozilla/dom/StructuredCloneTags.h" #include "mozilla/dom/ToJSValue.h" #include "mozilla/dom/WebIDLSerializable.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/fallible.h" #include "mozilla/gfx/2D.h" #include "nsContentUtils.h" #include "nsDebug.h" #include "nsError.h" #include "nsID.h" #include "nsIEventTarget.h" #include "nsIFile.h" #include "nsIGlobalObject.h" #include "nsIInputStream.h" #include "nsIPrincipal.h" #include "nsISupports.h" #include "nsJSPrincipals.h" #include "nsPIDOMWindow.h" #include "nsString.h" #include "nsThreadUtils.h" #include "nsXPCOM.h" #include "xpcpublic.h" using namespace mozilla::ipc; namespace mozilla::dom { namespace { JSObject* StructuredCloneCallbacksRead( JSContext* aCx, JSStructuredCloneReader* aReader, const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aIndex, void* aClosure) { StructuredCloneHolderBase* holder = static_cast(aClosure); MOZ_ASSERT(holder); return holder->CustomReadHandler(aCx, aReader, aCloneDataPolicy, aTag, aIndex); } bool StructuredCloneCallbacksWrite(JSContext* aCx, JSStructuredCloneWriter* aWriter, JS::Handle aObj, bool* aSameProcessScopeRequired, void* aClosure) { StructuredCloneHolderBase* holder = static_cast(aClosure); MOZ_ASSERT(holder); return holder->CustomWriteHandler(aCx, aWriter, aObj, aSameProcessScopeRequired); } bool StructuredCloneCallbacksReadTransfer( JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, void* aContent, uint64_t aExtraData, void* aClosure, JS::MutableHandleObject aReturnObject) { StructuredCloneHolderBase* holder = static_cast(aClosure); MOZ_ASSERT(holder); return holder->CustomReadTransferHandler(aCx, aReader, aTag, aContent, aExtraData, aReturnObject); } bool StructuredCloneCallbacksWriteTransfer( JSContext* aCx, JS::Handle aObj, void* aClosure, // Output: uint32_t* aTag, JS::TransferableOwnership* aOwnership, void** aContent, uint64_t* aExtraData) { StructuredCloneHolderBase* holder = static_cast(aClosure); MOZ_ASSERT(holder); return holder->CustomWriteTransferHandler(aCx, aObj, aTag, aOwnership, aContent, aExtraData); } void StructuredCloneCallbacksFreeTransfer(uint32_t aTag, JS::TransferableOwnership aOwnership, void* aContent, uint64_t aExtraData, void* aClosure) { StructuredCloneHolderBase* holder = static_cast(aClosure); MOZ_ASSERT(holder); return holder->CustomFreeTransferHandler(aTag, aOwnership, aContent, aExtraData); } bool StructuredCloneCallbacksCanTransfer(JSContext* aCx, JS::Handle aObject, bool* aSameProcessScopeRequired, void* aClosure) { StructuredCloneHolderBase* holder = static_cast(aClosure); MOZ_ASSERT(holder); return holder->CustomCanTransferHandler(aCx, aObject, aSameProcessScopeRequired); } bool StructuredCloneCallbacksSharedArrayBuffer(JSContext* cx, bool aReceiving, void* aClosure) { if (!StaticPrefs::dom_workers_serialized_sab_access()) { return true; } WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); if (workerPrivate) { workerPrivate->SetExecutionManager( JSExecutionManager::GetSABSerializationManager()); } else if (NS_IsMainThread()) { nsIGlobalObject* global = GetCurrentGlobal(); nsPIDOMWindowInner* innerWindow = nullptr; if (global) { innerWindow = global->AsInnerWindow(); } DocGroup* docGroup = nullptr; if (innerWindow) { docGroup = innerWindow->GetDocGroup(); } if (docGroup) { docGroup->SetExecutionManager( JSExecutionManager::GetSABSerializationManager()); } } return true; } void StructuredCloneCallbacksError(JSContext* aCx, uint32_t aErrorId, void* aClosure, const char* aErrorMessage) { NS_WARNING("Failed to clone data."); StructuredCloneHolderBase* holder = static_cast(aClosure); MOZ_ASSERT(holder); return holder->SetErrorMessage(aErrorMessage); } void AssertTagValues() { static_assert(SCTAG_DOM_IMAGEDATA == 0xffff8007 && SCTAG_DOM_DOMPOINT == 0xffff8008 && SCTAG_DOM_DOMPOINTREADONLY == 0xffff8009 && SCTAG_DOM_CRYPTOKEY == 0xffff800a && SCTAG_DOM_NULL_PRINCIPAL == 0xffff800b && SCTAG_DOM_SYSTEM_PRINCIPAL == 0xffff800c && SCTAG_DOM_CONTENT_PRINCIPAL == 0xffff800d && SCTAG_DOM_DOMQUAD == 0xffff800e && SCTAG_DOM_RTCCERTIFICATE == 0xffff800f && SCTAG_DOM_DOMRECT == 0xffff8010 && SCTAG_DOM_DOMRECTREADONLY == 0xffff8011 && SCTAG_DOM_EXPANDED_PRINCIPAL == 0xffff8012 && SCTAG_DOM_DOMMATRIX == 0xffff8013 && SCTAG_DOM_URLSEARCHPARAMS == 0xffff8014 && SCTAG_DOM_DOMMATRIXREADONLY == 0xffff8015 && SCTAG_DOM_STRUCTUREDCLONETESTER == 0xffff8018, "Something has changed the sctag values. This is wrong!"); } } // anonymous namespace const JSStructuredCloneCallbacks StructuredCloneHolder::sCallbacks = { StructuredCloneCallbacksRead, StructuredCloneCallbacksWrite, StructuredCloneCallbacksError, StructuredCloneCallbacksReadTransfer, StructuredCloneCallbacksWriteTransfer, StructuredCloneCallbacksFreeTransfer, StructuredCloneCallbacksCanTransfer, StructuredCloneCallbacksSharedArrayBuffer, }; // StructuredCloneHolderBase class StructuredCloneHolderBase::StructuredCloneHolderBase( StructuredCloneScope aScope) : mStructuredCloneScope(aScope) #ifdef DEBUG , mClearCalled(false) #endif { } StructuredCloneHolderBase::~StructuredCloneHolderBase() { #ifdef DEBUG MOZ_ASSERT(mClearCalled); #endif } void StructuredCloneHolderBase::Clear() { #ifdef DEBUG mClearCalled = true; #endif mBuffer = nullptr; } bool StructuredCloneHolderBase::Write(JSContext* aCx, JS::Handle aValue) { return Write(aCx, aValue, JS::UndefinedHandleValue, JS::CloneDataPolicy()); } bool StructuredCloneHolderBase::Write( JSContext* aCx, JS::Handle aValue, JS::Handle aTransfer, const JS::CloneDataPolicy& aCloneDataPolicy) { MOZ_ASSERT(!mBuffer, "Double Write is not allowed"); MOZ_ASSERT(!mClearCalled, "This method cannot be called after Clear."); mBuffer = MakeUnique( mStructuredCloneScope, &StructuredCloneHolder::sCallbacks, this); if (!mBuffer->write(aCx, aValue, aTransfer, aCloneDataPolicy, &StructuredCloneHolder::sCallbacks, this)) { mBuffer = nullptr; return false; } // Let's update our scope to the final one. The new one could be more // restrictive of the current one. MOZ_ASSERT(mStructuredCloneScope >= mBuffer->scope()); mStructuredCloneScope = mBuffer->scope(); return true; } bool StructuredCloneHolderBase::Read(JSContext* aCx, JS::MutableHandle aValue) { return Read(aCx, aValue, JS::CloneDataPolicy()); } bool StructuredCloneHolderBase::Read( JSContext* aCx, JS::MutableHandle aValue, const JS::CloneDataPolicy& aCloneDataPolicy) { MOZ_ASSERT(mBuffer, "Read() without Write() is not allowed."); MOZ_ASSERT(!mClearCalled, "This method cannot be called after Clear."); bool ok = mBuffer->read(aCx, aValue, aCloneDataPolicy, &StructuredCloneHolder::sCallbacks, this); return ok; } bool StructuredCloneHolderBase::CustomReadTransferHandler( JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, void* aContent, uint64_t aExtraData, JS::MutableHandleObject aReturnObject) { MOZ_CRASH("Nothing to read."); return false; } bool StructuredCloneHolderBase::CustomWriteTransferHandler( JSContext* aCx, JS::Handle aObj, uint32_t* aTag, JS::TransferableOwnership* aOwnership, void** aContent, uint64_t* aExtraData) { // No transfers are supported by default. return false; } void StructuredCloneHolderBase::CustomFreeTransferHandler( uint32_t aTag, JS::TransferableOwnership aOwnership, void* aContent, uint64_t aExtraData) { MOZ_CRASH("Nothing to free."); } bool StructuredCloneHolderBase::CustomCanTransferHandler( JSContext* aCx, JS::Handle aObj, bool* aSameProcessScopeRequired) { return false; } // StructuredCloneHolder class StructuredCloneHolder::StructuredCloneHolder( CloningSupport aSupportsCloning, TransferringSupport aSupportsTransferring, StructuredCloneScope aScope) : StructuredCloneHolderBase(aScope), mSupportsCloning(aSupportsCloning == CloningSupported), mSupportsTransferring(aSupportsTransferring == TransferringSupported), mGlobal(nullptr) #ifdef DEBUG , mCreationEventTarget(GetCurrentEventTarget()) #endif { } StructuredCloneHolder::~StructuredCloneHolder() { Clear(); MOZ_ASSERT(mTransferredPorts.IsEmpty()); } void StructuredCloneHolder::Write(JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) { Write(aCx, aValue, JS::UndefinedHandleValue, JS::CloneDataPolicy(), aRv); } void StructuredCloneHolder::Write(JSContext* aCx, JS::Handle aValue, JS::Handle aTransfer, const JS::CloneDataPolicy& aCloneDataPolicy, ErrorResult& aRv) { if (!StructuredCloneHolderBase::Write(aCx, aValue, aTransfer, aCloneDataPolicy)) { aRv.ThrowDataCloneError(mErrorMessage); return; } } void StructuredCloneHolder::Read(nsIGlobalObject* aGlobal, JSContext* aCx, JS::MutableHandle aValue, ErrorResult& aRv) { return Read(aGlobal, aCx, aValue, JS::CloneDataPolicy(), aRv); } void StructuredCloneHolder::Read(nsIGlobalObject* aGlobal, JSContext* aCx, JS::MutableHandle aValue, const JS::CloneDataPolicy& aCloneDataPolicy, ErrorResult& aRv) { MOZ_ASSERT(aGlobal); mozilla::AutoRestore guard(mGlobal); auto errorMessageGuard = MakeScopeExit([&] { mErrorMessage.Truncate(); }); mGlobal = aGlobal; if (!StructuredCloneHolderBase::Read(aCx, aValue, aCloneDataPolicy)) { JS_ClearPendingException(aCx); aRv.ThrowDataCloneError(mErrorMessage); return; } // If we are tranferring something, we cannot call 'Read()' more than once. if (mSupportsTransferring) { mBlobImplArray.Clear(); mWasmModuleArray.Clear(); mClonedSurfaces.Clear(); mInputStreamArray.Clear(); Clear(); } } void StructuredCloneHolder::ReadFromBuffer( nsIGlobalObject* aGlobal, JSContext* aCx, JSStructuredCloneData& aBuffer, JS::MutableHandle aValue, const JS::CloneDataPolicy& aCloneDataPolicy, ErrorResult& aRv) { ReadFromBuffer(aGlobal, aCx, aBuffer, JS_STRUCTURED_CLONE_VERSION, aValue, aCloneDataPolicy, aRv); } void StructuredCloneHolder::ReadFromBuffer( nsIGlobalObject* aGlobal, JSContext* aCx, JSStructuredCloneData& aBuffer, uint32_t aAlgorithmVersion, JS::MutableHandle aValue, const JS::CloneDataPolicy& aCloneDataPolicy, ErrorResult& aRv) { MOZ_ASSERT(!mBuffer, "ReadFromBuffer() must be called without a Write()."); mozilla::AutoRestore guard(mGlobal); auto errorMessageGuard = MakeScopeExit([&] { mErrorMessage.Truncate(); }); mGlobal = aGlobal; if (!JS_ReadStructuredClone(aCx, aBuffer, aAlgorithmVersion, CloneScope(), aValue, aCloneDataPolicy, &sCallbacks, this)) { JS_ClearPendingException(aCx); aRv.ThrowDataCloneError(mErrorMessage); return; } } /* static */ JSObject* StructuredCloneHolder::ReadFullySerializableObjects( JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag) { AssertTagValues(); nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); if (!global) { return nullptr; } WebIDLDeserializer deserializer = LookupDeserializer(StructuredCloneTags(aTag)); if (deserializer) { return deserializer(aCx, global, aReader); } if (aTag == SCTAG_DOM_NULL_PRINCIPAL || aTag == SCTAG_DOM_SYSTEM_PRINCIPAL || aTag == SCTAG_DOM_CONTENT_PRINCIPAL || aTag == SCTAG_DOM_EXPANDED_PRINCIPAL) { JSPrincipals* prin; if (!nsJSPrincipals::ReadKnownPrincipalType(aCx, aReader, aTag, &prin)) { return nullptr; } JS::RootedValue result(aCx); { // nsJSPrincipals::ReadKnownPrincipalType addrefs for us, but because of // the casting between JSPrincipals* and nsIPrincipal* we can't use // getter_AddRefs above and have to already_AddRefed here. nsCOMPtr principal = already_AddRefed(nsJSPrincipals::get(prin)); nsresult rv = nsContentUtils::WrapNative( aCx, principal, &NS_GET_IID(nsIPrincipal), &result); if (NS_FAILED(rv)) { xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); return nullptr; } } return result.toObjectOrNull(); } // Don't know what this is. Bail. xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); return nullptr; } /* static */ bool StructuredCloneHolder::WriteFullySerializableObjects( JSContext* aCx, JSStructuredCloneWriter* aWriter, JS::Handle aObj) { AssertTagValues(); // Window and Location are not serializable, so it's OK to just do a static // unwrap here. JS::Rooted obj(aCx, js::CheckedUnwrapStatic(aObj)); if (!obj) { return xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); } const DOMJSClass* domClass = GetDOMClass(obj); if (domClass && domClass->mSerializer) { return domClass->mSerializer(aCx, aWriter, obj); } if (NS_IsMainThread() && xpc::IsReflector(obj, aCx)) { // We only care about principals, so ReflectorToISupportsStatic is fine. nsCOMPtr base = xpc::ReflectorToISupportsStatic(obj); nsCOMPtr principal = do_QueryInterface(base); if (principal) { auto nsjsprincipals = nsJSPrincipals::get(principal); return nsjsprincipals->write(aCx, aWriter); } } // Don't know what this is xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); return false; } /* static */ bool StructuredCloneHolder::ReadString(JSStructuredCloneReader* aReader, nsString& aString) { uint32_t length, zero; if (!JS_ReadUint32Pair(aReader, &length, &zero)) { return false; } if (NS_WARN_IF(!aString.SetLength(length, fallible))) { return false; } size_t charSize = sizeof(nsString::char_type); return JS_ReadBytes(aReader, (void*)aString.BeginWriting(), length * charSize); } /* static */ bool StructuredCloneHolder::WriteString(JSStructuredCloneWriter* aWriter, const nsAString& aString) { size_t charSize = sizeof(nsString::char_type); return JS_WriteUint32Pair(aWriter, aString.Length(), 0) && JS_WriteBytes(aWriter, aString.BeginReading(), aString.Length() * charSize); } namespace { JSObject* ReadBlob(JSContext* aCx, uint32_t aIndex, StructuredCloneHolder* aHolder) { MOZ_ASSERT(aHolder); #ifdef FUZZING if (aIndex >= aHolder->BlobImpls().Length()) { return nullptr; } #endif MOZ_ASSERT(aIndex < aHolder->BlobImpls().Length()); JS::Rooted val(aCx); { // RefPtr and RefPtr need to go out of scope before // toObject() is called because the static analysis thinks releasing XPCOM // objects can GC (because in some cases it can!), and a return statement // with a JSObject* type means that JSObject* is on the stack as a raw // pointer while destructors are running. RefPtr blobImpl = aHolder->BlobImpls()[aIndex]; RefPtr blob = Blob::Create(aHolder->GlobalDuringRead(), blobImpl); if (NS_WARN_IF(!blob)) { return nullptr; } if (!ToJSValue(aCx, blob, &val)) { return nullptr; } } return &val.toObject(); } bool WriteBlob(JSStructuredCloneWriter* aWriter, Blob* aBlob, StructuredCloneHolder* aHolder) { MOZ_ASSERT(aWriter); MOZ_ASSERT(aBlob); MOZ_ASSERT(aHolder); RefPtr blobImpl = aBlob->Impl(); // We store the position of the blobImpl in the array as index. if (JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB, aHolder->BlobImpls().Length())) { aHolder->BlobImpls().AppendElement(blobImpl); return true; } return false; } // A directory is serialized as: // - pair of ints: SCTAG_DOM_DIRECTORY, path length // - path as string bool WriteDirectory(JSStructuredCloneWriter* aWriter, Directory* aDirectory) { MOZ_ASSERT(aWriter); MOZ_ASSERT(aDirectory); nsAutoString path; aDirectory->GetFullRealPath(path); size_t charSize = sizeof(nsString::char_type); return JS_WriteUint32Pair(aWriter, SCTAG_DOM_DIRECTORY, path.Length()) && JS_WriteBytes(aWriter, path.get(), path.Length() * charSize); } already_AddRefed ReadDirectoryInternal( JSStructuredCloneReader* aReader, uint32_t aPathLength, StructuredCloneHolder* aHolder) { MOZ_ASSERT(aReader); MOZ_ASSERT(aHolder); nsAutoString path; if (NS_WARN_IF(!path.SetLength(aPathLength, fallible))) { return nullptr; } size_t charSize = sizeof(nsString::char_type); if (!JS_ReadBytes(aReader, (void*)path.BeginWriting(), aPathLength * charSize)) { return nullptr; } nsCOMPtr file; nsresult rv = NS_NewLocalFile(path, true, getter_AddRefs(file)); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } RefPtr directory = Directory::Create(aHolder->GlobalDuringRead(), file); return directory.forget(); } JSObject* ReadDirectory(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aPathLength, StructuredCloneHolder* aHolder) { MOZ_ASSERT(aCx); MOZ_ASSERT(aReader); MOZ_ASSERT(aHolder); // RefPtr needs to go out of scope before toObject() is // called because the static analysis thinks dereferencing XPCOM objects // can GC (because in some cases it can!), and a return statement with a // JSObject* type means that JSObject* is on the stack as a raw pointer // while destructors are running. JS::Rooted val(aCx); { RefPtr directory = ReadDirectoryInternal(aReader, aPathLength, aHolder); if (!directory) { return nullptr; } if (!ToJSValue(aCx, directory, &val)) { return nullptr; } } return &val.toObject(); } // Read the WriteFileList for the format. JSObject* ReadFileList(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aCount, StructuredCloneHolder* aHolder) { MOZ_ASSERT(aCx); MOZ_ASSERT(aReader); JS::Rooted val(aCx); { RefPtr fileList = new FileList(aHolder->GlobalDuringRead()); uint32_t zero, index; // |index| is the index of the first blobImpl. if (!JS_ReadUint32Pair(aReader, &zero, &index)) { return nullptr; } MOZ_ASSERT(zero == 0); // |aCount| is the number of BlobImpls to use from the |index|. for (uint32_t i = 0; i < aCount; ++i) { uint32_t pos = index + i; #ifdef FUZZING if (pos >= aHolder->BlobImpls().Length()) { return nullptr; } #endif MOZ_ASSERT(pos < aHolder->BlobImpls().Length()); RefPtr blobImpl = aHolder->BlobImpls()[pos]; MOZ_ASSERT(blobImpl->IsFile()); RefPtr file = File::Create(aHolder->GlobalDuringRead(), blobImpl); if (NS_WARN_IF(!file)) { return nullptr; } if (!fileList->Append(file)) { return nullptr; } } if (!ToJSValue(aCx, fileList, &val)) { return nullptr; } } return &val.toObject(); } // The format of the FileList serialization is: // - pair of ints: SCTAG_DOM_FILELIST, Length of the FileList // - pair of ints: 0, The offset of the BlobImpl array bool WriteFileList(JSStructuredCloneWriter* aWriter, FileList* aFileList, StructuredCloneHolder* aHolder) { MOZ_ASSERT(aWriter); MOZ_ASSERT(aFileList); MOZ_ASSERT(aHolder); // A FileList is serialized writing the X number of elements and the offset // from mBlobImplArray. The Read will take X elements from mBlobImplArray // starting from the offset. if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_FILELIST, aFileList->Length()) || !JS_WriteUint32Pair(aWriter, 0, aHolder->BlobImpls().Length())) { return false; } nsTArray> blobImpls; for (uint32_t i = 0; i < aFileList->Length(); ++i) { RefPtr blobImpl = aFileList->Item(i)->Impl(); blobImpls.AppendElement(blobImpl); } aHolder->BlobImpls().AppendElements(blobImpls); return true; } // Read the WriteFormData for the format. JSObject* ReadFormData(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aCount, StructuredCloneHolder* aHolder) { MOZ_ASSERT(aCx); MOZ_ASSERT(aReader); MOZ_ASSERT(aHolder); // See the serialization of the FormData for the format. JS::Rooted val(aCx); { RefPtr formData = new FormData(aHolder->GlobalDuringRead()); Optional thirdArg; for (uint32_t i = 0; i < aCount; ++i) { nsAutoString name; if (!StructuredCloneHolder::ReadString(aReader, name)) { return nullptr; } uint32_t tag, indexOrLengthOfString; if (!JS_ReadUint32Pair(aReader, &tag, &indexOrLengthOfString)) { return nullptr; } if (tag == SCTAG_DOM_BLOB) { #ifdef FUZZING if (indexOrLengthOfString >= aHolder->BlobImpls().Length()) { return nullptr; } #endif MOZ_ASSERT(indexOrLengthOfString < aHolder->BlobImpls().Length()); RefPtr blobImpl = aHolder->BlobImpls()[indexOrLengthOfString]; RefPtr blob = Blob::Create(aHolder->GlobalDuringRead(), blobImpl); if (NS_WARN_IF(!blob)) { return nullptr; } ErrorResult rv; formData->Append(name, *blob, thirdArg, rv); if (NS_WARN_IF(rv.Failed())) { rv.SuppressException(); return nullptr; } } else if (tag == SCTAG_DOM_DIRECTORY) { RefPtr directory = ReadDirectoryInternal(aReader, indexOrLengthOfString, aHolder); if (!directory) { return nullptr; } formData->Append(name, directory); } else { MOZ_ASSERT(tag == 0); nsAutoString value; if (NS_WARN_IF(!value.SetLength(indexOrLengthOfString, fallible))) { return nullptr; } size_t charSize = sizeof(nsString::char_type); if (!JS_ReadBytes(aReader, (void*)value.BeginWriting(), indexOrLengthOfString * charSize)) { return nullptr; } ErrorResult rv; formData->Append(name, value, rv); if (NS_WARN_IF(rv.Failed())) { rv.SuppressException(); return nullptr; } } } if (!ToJSValue(aCx, formData, &val)) { return nullptr; } } return &val.toObject(); } // The format of the FormData serialization is: // - pair of ints: SCTAG_DOM_FORMDATA, Length of the FormData elements // - for each Element element: // - name string // - if it's a blob: // - pair of ints: SCTAG_DOM_BLOB, index of the BlobImpl in the array // mBlobImplArray. // - if it's a directory (See WriteDirectory): // - pair of ints: SCTAG_DOM_DIRECTORY, path length // - path as string // - else: // - pair of ints: 0, string length // - value string bool WriteFormData(JSStructuredCloneWriter* aWriter, FormData* aFormData, StructuredCloneHolder* aHolder) { MOZ_ASSERT(aWriter); MOZ_ASSERT(aFormData); MOZ_ASSERT(aHolder); if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_FORMDATA, aFormData->Length())) { return false; } class MOZ_STACK_CLASS Closure final { JSStructuredCloneWriter* mWriter; StructuredCloneHolder* mHolder; public: Closure(JSStructuredCloneWriter* aWriter, StructuredCloneHolder* aHolder) : mWriter(aWriter), mHolder(aHolder) {} static bool Write(const nsString& aName, const OwningBlobOrDirectoryOrUSVString& aValue, void* aClosure) { Closure* closure = static_cast(aClosure); if (!StructuredCloneHolder::WriteString(closure->mWriter, aName)) { return false; } if (aValue.IsBlob()) { if (!JS_WriteUint32Pair(closure->mWriter, SCTAG_DOM_BLOB, closure->mHolder->BlobImpls().Length())) { return false; } RefPtr blobImpl = aValue.GetAsBlob()->Impl(); closure->mHolder->BlobImpls().AppendElement(blobImpl); return true; } if (aValue.IsDirectory()) { Directory* directory = aValue.GetAsDirectory(); return WriteDirectory(closure->mWriter, directory); } size_t charSize = sizeof(nsString::char_type); if (!JS_WriteUint32Pair(closure->mWriter, 0, aValue.GetAsUSVString().Length()) || !JS_WriteBytes(closure->mWriter, aValue.GetAsUSVString().get(), aValue.GetAsUSVString().Length() * charSize)) { return false; } return true; } }; Closure closure(aWriter, aHolder); return aFormData->ForEach(Closure::Write, &closure); } JSObject* ReadWasmModule(JSContext* aCx, uint32_t aIndex, StructuredCloneHolder* aHolder) { MOZ_ASSERT(aHolder); MOZ_ASSERT(aHolder->CloneScope() == StructuredCloneHolder::StructuredCloneScope::SameProcess); #ifdef FUZZING if (aIndex >= aHolder->WasmModules().Length()) { return nullptr; } #endif MOZ_ASSERT(aIndex < aHolder->WasmModules().Length()); return aHolder->WasmModules()[aIndex]->createObject(aCx); } bool WriteWasmModule(JSStructuredCloneWriter* aWriter, JS::WasmModule* aWasmModule, StructuredCloneHolder* aHolder) { MOZ_ASSERT(aWriter); MOZ_ASSERT(aWasmModule); MOZ_ASSERT(aHolder); MOZ_ASSERT(aHolder->CloneScope() == StructuredCloneHolder::StructuredCloneScope::SameProcess); // We store the position of the wasmModule in the array as index. if (JS_WriteUint32Pair(aWriter, SCTAG_DOM_WASM_MODULE, aHolder->WasmModules().Length())) { aHolder->WasmModules().AppendElement(aWasmModule); return true; } return false; } JSObject* ReadInputStream(JSContext* aCx, uint32_t aIndex, StructuredCloneHolder* aHolder) { MOZ_ASSERT(aHolder); #ifdef FUZZING if (aIndex >= aHolder->InputStreams().Length()) { return nullptr; } #endif MOZ_ASSERT(aIndex < aHolder->InputStreams().Length()); JS::RootedValue result(aCx); { nsCOMPtr inputStream = aHolder->InputStreams()[aIndex]; nsresult rv = nsContentUtils::WrapNative( aCx, inputStream, &NS_GET_IID(nsIInputStream), &result); if (NS_FAILED(rv)) { return nullptr; } } return &result.toObject(); } bool WriteInputStream(JSStructuredCloneWriter* aWriter, nsIInputStream* aInputStream, StructuredCloneHolder* aHolder) { MOZ_ASSERT(aWriter); MOZ_ASSERT(aInputStream); MOZ_ASSERT(aHolder); // We store the position of the inputStream in the array as index. if (JS_WriteUint32Pair(aWriter, SCTAG_DOM_INPUTSTREAM, aHolder->InputStreams().Length())) { aHolder->InputStreams().AppendElement(aInputStream); return true; } return false; } } // anonymous namespace JSObject* StructuredCloneHolder::CustomReadHandler( JSContext* aCx, JSStructuredCloneReader* aReader, const JS::CloneDataPolicy& aCloneDataPolicy, uint32_t aTag, uint32_t aIndex) { MOZ_ASSERT(mSupportsCloning); if (aTag == SCTAG_DOM_BLOB) { return ReadBlob(aCx, aIndex, this); } if (aTag == SCTAG_DOM_DIRECTORY) { return ReadDirectory(aCx, aReader, aIndex, this); } if (aTag == SCTAG_DOM_FILELIST) { return ReadFileList(aCx, aReader, aIndex, this); } if (aTag == SCTAG_DOM_FORMDATA) { return ReadFormData(aCx, aReader, aIndex, this); } if (aTag == SCTAG_DOM_IMAGEBITMAP && CloneScope() == StructuredCloneScope::SameProcess) { // Get the current global object. // This can be null. JS::RootedObject result(aCx); { // aIndex is the index of the cloned image. result = ImageBitmap::ReadStructuredClone(aCx, aReader, mGlobal, GetSurfaces(), aIndex); } return result; } if (aTag == SCTAG_DOM_STRUCTURED_CLONE_HOLDER) { return StructuredCloneBlob::ReadStructuredClone(aCx, aReader, this); } if (aTag == SCTAG_DOM_WASM_MODULE && CloneScope() == StructuredCloneScope::SameProcess && aCloneDataPolicy.areIntraClusterClonableSharedObjectsAllowed()) { return ReadWasmModule(aCx, aIndex, this); } if (aTag == SCTAG_DOM_INPUTSTREAM) { return ReadInputStream(aCx, aIndex, this); } if (aTag == SCTAG_DOM_BROWSING_CONTEXT) { return BrowsingContext::ReadStructuredClone(aCx, aReader, this); } if (aTag == SCTAG_DOM_CLONED_ERROR_OBJECT) { return ClonedErrorHolder::ReadStructuredClone(aCx, aReader, this); } return ReadFullySerializableObjects(aCx, aReader, aTag); } bool StructuredCloneHolder::CustomWriteHandler( JSContext* aCx, JSStructuredCloneWriter* aWriter, JS::Handle aObj, bool* aSameProcessScopeRequired) { if (!mSupportsCloning) { return false; } JS::Rooted obj(aCx, aObj); // See if this is a File/Blob object. { Blob* blob = nullptr; if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) { return WriteBlob(aWriter, blob, this); } } // See if this is a Directory object. { Directory* directory = nullptr; if (NS_SUCCEEDED(UNWRAP_OBJECT(Directory, &obj, directory))) { return WriteDirectory(aWriter, directory); } } // See if this is a FileList object. { FileList* fileList = nullptr; if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, &obj, fileList))) { return WriteFileList(aWriter, fileList, this); } } // See if this is a FormData object. { FormData* formData = nullptr; if (NS_SUCCEEDED(UNWRAP_OBJECT(FormData, &obj, formData))) { return WriteFormData(aWriter, formData, this); } } // See if this is an ImageBitmap object. { ImageBitmap* imageBitmap = nullptr; if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, &obj, imageBitmap))) { if (imageBitmap->IsWriteOnly()) { return false; } SameProcessScopeRequired(aSameProcessScopeRequired); if (CloneScope() == StructuredCloneScope::SameProcess) { return ImageBitmap::WriteStructuredClone(aWriter, GetSurfaces(), imageBitmap); } return false; } } // See if this is a StructuredCloneBlob object. { StructuredCloneBlob* holder = nullptr; if (NS_SUCCEEDED(UNWRAP_OBJECT(StructuredCloneHolder, &obj, holder))) { return holder->WriteStructuredClone(aCx, aWriter, this); } } // See if this is a BrowsingContext object. { BrowsingContext* holder = nullptr; if (NS_SUCCEEDED(UNWRAP_OBJECT(BrowsingContext, &obj, holder))) { return holder->WriteStructuredClone(aCx, aWriter, this); } } // See if this is a ClonedErrorHolder object. { ClonedErrorHolder* holder = nullptr; if (NS_SUCCEEDED(UNWRAP_OBJECT(ClonedErrorHolder, &obj, holder))) { return holder->WriteStructuredClone(aCx, aWriter, this); } } // See if this is a WasmModule. if (JS::IsWasmModuleObject(obj)) { SameProcessScopeRequired(aSameProcessScopeRequired); if (CloneScope() == StructuredCloneScope::SameProcess) { RefPtr module = JS::GetWasmModule(obj); MOZ_ASSERT(module); return WriteWasmModule(aWriter, module, this); } return false; } { // We only care about streams, so ReflectorToISupportsStatic is fine. nsCOMPtr base = xpc::ReflectorToISupportsStatic(aObj); nsCOMPtr inputStream = do_QueryInterface(base); if (inputStream) { return WriteInputStream(aWriter, inputStream, this); } } return WriteFullySerializableObjects(aCx, aWriter, aObj); } bool StructuredCloneHolder::CustomReadTransferHandler( JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, void* aContent, uint64_t aExtraData, JS::MutableHandleObject aReturnObject) { MOZ_ASSERT(mSupportsTransferring); if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { #ifdef FUZZING if (aExtraData >= mPortIdentifiers.Length()) { return false; } #endif MOZ_ASSERT(aExtraData < mPortIdentifiers.Length()); UniqueMessagePortId portIdentifier(mPortIdentifiers[aExtraData]); ErrorResult rv; RefPtr port = MessagePort::Create(mGlobal, portIdentifier, rv); if (NS_WARN_IF(rv.Failed())) { rv.SuppressException(); return false; } mTransferredPorts.AppendElement(port); JS::Rooted value(aCx); if (!GetOrCreateDOMReflector(aCx, port, &value)) { JS_ClearPendingException(aCx); return false; } aReturnObject.set(&value.toObject()); return true; } if (aTag == SCTAG_DOM_CANVAS && CloneScope() == StructuredCloneScope::SameProcess) { MOZ_ASSERT(aContent); OffscreenCanvasCloneData* data = static_cast(aContent); RefPtr canvas = OffscreenCanvas::CreateFromCloneData(mGlobal, data); delete data; JS::Rooted value(aCx); if (!GetOrCreateDOMReflector(aCx, canvas, &value)) { JS_ClearPendingException(aCx); return false; } aReturnObject.set(&value.toObject()); return true; } if (aTag == SCTAG_DOM_IMAGEBITMAP && CloneScope() == StructuredCloneScope::SameProcess) { MOZ_ASSERT(aContent); ImageBitmapCloneData* data = static_cast(aContent); RefPtr bitmap = ImageBitmap::CreateFromCloneData(mGlobal, data); delete data; JS::Rooted value(aCx); if (!GetOrCreateDOMReflector(aCx, bitmap, &value)) { JS_ClearPendingException(aCx); return false; } aReturnObject.set(&value.toObject()); return true; } return false; } bool StructuredCloneHolder::CustomWriteTransferHandler( JSContext* aCx, JS::Handle aObj, uint32_t* aTag, JS::TransferableOwnership* aOwnership, void** aContent, uint64_t* aExtraData) { if (!mSupportsTransferring) { return false; } JS::Rooted obj(aCx, aObj); { MessagePort* port = nullptr; nsresult rv = UNWRAP_OBJECT(MessagePort, &obj, port); if (NS_SUCCEEDED(rv)) { if (!port->CanBeCloned()) { return false; } UniqueMessagePortId identifier; port->CloneAndDisentangle(identifier); // We use aExtraData to store the index of this new port identifier. *aExtraData = mPortIdentifiers.Length(); mPortIdentifiers.AppendElement(identifier.release()); *aTag = SCTAG_DOM_MAP_MESSAGEPORT; *aOwnership = JS::SCTAG_TMO_CUSTOM; *aContent = nullptr; return true; } if (CloneScope() == StructuredCloneScope::SameProcess) { OffscreenCanvas* canvas = nullptr; rv = UNWRAP_OBJECT(OffscreenCanvas, &obj, canvas); if (NS_SUCCEEDED(rv)) { MOZ_ASSERT(canvas); if (canvas->IsNeutered()) { return false; } *aExtraData = 0; *aTag = SCTAG_DOM_CANVAS; *aOwnership = JS::SCTAG_TMO_CUSTOM; *aContent = canvas->ToCloneData(); MOZ_ASSERT(*aContent); canvas->SetNeutered(); return true; } ImageBitmap* bitmap = nullptr; rv = UNWRAP_OBJECT(ImageBitmap, &obj, bitmap); if (NS_SUCCEEDED(rv)) { MOZ_ASSERT(bitmap); MOZ_ASSERT(!bitmap->IsWriteOnly()); *aExtraData = 0; *aTag = SCTAG_DOM_IMAGEBITMAP; *aOwnership = JS::SCTAG_TMO_CUSTOM; UniquePtr clonedBitmap = bitmap->ToCloneData(); if (!clonedBitmap) { return false; } *aContent = clonedBitmap.release(); MOZ_ASSERT(*aContent); bitmap->Close(); return true; } } } return false; } void StructuredCloneHolder::CustomFreeTransferHandler( uint32_t aTag, JS::TransferableOwnership aOwnership, void* aContent, uint64_t aExtraData) { MOZ_ASSERT(mSupportsTransferring); if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { MOZ_ASSERT(!aContent); #ifdef FUZZING if (aExtraData >= mPortIdentifiers.Length()) { return; } #endif MOZ_ASSERT(aExtraData < mPortIdentifiers.Length()); MessagePort::ForceClose(mPortIdentifiers[aExtraData]); return; } if (aTag == SCTAG_DOM_CANVAS && CloneScope() == StructuredCloneScope::SameProcess) { MOZ_ASSERT(aContent); OffscreenCanvasCloneData* data = static_cast(aContent); delete data; return; } if (aTag == SCTAG_DOM_IMAGEBITMAP && CloneScope() == StructuredCloneScope::SameProcess) { MOZ_ASSERT(aContent); ImageBitmapCloneData* data = static_cast(aContent); delete data; return; } } bool StructuredCloneHolder::CustomCanTransferHandler( JSContext* aCx, JS::Handle aObj, bool* aSameProcessScopeRequired) { if (!mSupportsTransferring) { return false; } JS::Rooted obj(aCx, aObj); { MessagePort* port = nullptr; nsresult rv = UNWRAP_OBJECT(MessagePort, &obj, port); if (NS_SUCCEEDED(rv)) { return true; } } { OffscreenCanvas* canvas = nullptr; nsresult rv = UNWRAP_OBJECT(OffscreenCanvas, &obj, canvas); if (NS_SUCCEEDED(rv)) { SameProcessScopeRequired(aSameProcessScopeRequired); return CloneScope() == StructuredCloneScope::SameProcess; } } { ImageBitmap* bitmap = nullptr; nsresult rv = UNWRAP_OBJECT(ImageBitmap, &obj, bitmap); if (NS_SUCCEEDED(rv)) { if (bitmap->IsWriteOnly()) { return false; } SameProcessScopeRequired(aSameProcessScopeRequired); return CloneScope() == StructuredCloneScope::SameProcess; } } return false; } bool StructuredCloneHolder::TakeTransferredPortsAsSequence( Sequence>& aPorts) { nsTArray> ports = TakeTransferredPorts(); aPorts.Clear(); for (uint32_t i = 0, len = ports.Length(); i < len; ++i) { if (!aPorts.AppendElement(ports[i].forget(), fallible)) { return false; } } return true; } void StructuredCloneHolder::SameProcessScopeRequired( bool* aSameProcessScopeRequired) { MOZ_ASSERT(aSameProcessScopeRequired); if (mStructuredCloneScope == StructuredCloneScope::UnknownDestination) { mStructuredCloneScope = StructuredCloneScope::SameProcess; *aSameProcessScopeRequired = true; } } } // namespace mozilla::dom