/* -*- 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 "PostMessageEvent.h" #include "MessageEvent.h" #include "mozilla/dom/BlobBinding.h" #include "mozilla/dom/MessagePort.h" #include "mozilla/dom/MessagePortBinding.h" #include "mozilla/dom/PMessagePort.h" #include "mozilla/dom/StructuredCloneTags.h" #include "mozilla/EventDispatcher.h" #include "nsGlobalWindow.h" #include "nsIPresShell.h" #include "nsIPrincipal.h" #include "nsPresContext.h" namespace mozilla { namespace dom { namespace { struct StructuredCloneInfo { PostMessageEvent* event; bool subsumes; nsPIDOMWindow* window; // This hashtable contains the transferred ports - used to avoid duplicates. nsTArray> transferredPorts; // This array is populated when the ports are cloned. nsTArray> clonedPorts; }; } // namespace const JSStructuredCloneCallbacks PostMessageEvent::sPostMessageCallbacks = { PostMessageEvent::ReadStructuredClone, PostMessageEvent::WriteStructuredClone, nullptr, PostMessageEvent::ReadTransferStructuredClone, PostMessageEvent::TransferStructuredClone, PostMessageEvent::FreeTransferStructuredClone }; /* static */ JSObject* PostMessageEvent::ReadStructuredClone(JSContext* cx, JSStructuredCloneReader* reader, uint32_t tag, uint32_t data, void* closure) { StructuredCloneInfo* scInfo = static_cast(closure); NS_ASSERTION(scInfo, "Must have scInfo!"); if (tag == SCTAG_DOM_BLOB) { NS_ASSERTION(!data, "Data should be empty"); // What we get back from the reader is a BlobImpl. // From that we create a new File. BlobImpl* blobImpl; if (JS_ReadBytes(reader, &blobImpl, sizeof(blobImpl))) { MOZ_ASSERT(blobImpl); // nsRefPtr needs to go out of scope before toObjectOrNull() 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(cx); { nsRefPtr blob = Blob::Create(scInfo->window, blobImpl); if (!ToJSValue(cx, blob, &val)) { return nullptr; } } return &val.toObject(); } } if (tag == SCTAG_DOM_FILELIST) { NS_ASSERTION(!data, "Data should be empty"); nsISupports* supports; if (JS_ReadBytes(reader, &supports, sizeof(supports))) { JS::Rooted val(cx); if (NS_SUCCEEDED(nsContentUtils::WrapNative(cx, supports, &val))) { return val.toObjectOrNull(); } } } const JSStructuredCloneCallbacks* runtimeCallbacks = js::GetContextStructuredCloneCallbacks(cx); if (runtimeCallbacks) { return runtimeCallbacks->read(cx, reader, tag, data, nullptr); } return nullptr; } /* static */ bool PostMessageEvent::WriteStructuredClone(JSContext* cx, JSStructuredCloneWriter* writer, JS::Handle obj, void *closure) { StructuredCloneInfo* scInfo = static_cast(closure); NS_ASSERTION(scInfo, "Must have scInfo!"); // See if this is a File/Blob object. { Blob* blob = nullptr; if (scInfo->subsumes && NS_SUCCEEDED(UNWRAP_OBJECT(Blob, obj, blob))) { BlobImpl* blobImpl = blob->Impl(); if (JS_WriteUint32Pair(writer, SCTAG_DOM_BLOB, 0) && JS_WriteBytes(writer, &blobImpl, sizeof(blobImpl))) { scInfo->event->StoreISupports(blobImpl); return true; } } } nsCOMPtr wrappedNative; nsContentUtils::XPConnect()-> GetWrappedNativeOfJSObject(cx, obj, getter_AddRefs(wrappedNative)); if (wrappedNative) { uint32_t scTag = 0; nsISupports* supports = wrappedNative->Native(); nsCOMPtr list = do_QueryInterface(supports); if (list && scInfo->subsumes) scTag = SCTAG_DOM_FILELIST; if (scTag) return JS_WriteUint32Pair(writer, scTag, 0) && JS_WriteBytes(writer, &supports, sizeof(supports)) && scInfo->event->StoreISupports(supports); } const JSStructuredCloneCallbacks* runtimeCallbacks = js::GetContextStructuredCloneCallbacks(cx); if (runtimeCallbacks) { return runtimeCallbacks->write(cx, writer, obj, nullptr); } return false; } /* static */ bool PostMessageEvent::ReadTransferStructuredClone(JSContext* aCx, JSStructuredCloneReader* reader, uint32_t tag, void* aData, uint64_t aExtraData, void* aClosure, JS::MutableHandle returnObject) { StructuredCloneInfo* scInfo = static_cast(aClosure); NS_ASSERTION(scInfo, "Must have scInfo!"); if (tag == SCTAG_DOM_MAP_MESSAGEPORT) { MOZ_ASSERT(!aData); // aExtraData is the index of this port identifier. ErrorResult rv; nsRefPtr port = MessagePort::Create(scInfo->window, scInfo->event->GetPortIdentifier(aExtraData), rv); if (NS_WARN_IF(rv.Failed())) { return false; } scInfo->clonedPorts.AppendElement(port); JS::Rooted value(aCx); if (!GetOrCreateDOMReflector(aCx, port, &value)) { JS_ClearPendingException(aCx); return false; } returnObject.set(&value.toObject()); return true; } return false; } /* static */ bool PostMessageEvent::TransferStructuredClone(JSContext* aCx, JS::Handle aObj, void* aClosure, uint32_t* aTag, JS::TransferableOwnership* aOwnership, void** aContent, uint64_t* aExtraData) { StructuredCloneInfo* scInfo = static_cast(aClosure); NS_ASSERTION(scInfo, "Must have scInfo!"); MessagePortBase* port = nullptr; nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port); if (NS_SUCCEEDED(rv)) { if (scInfo->transferredPorts.Contains(port)) { // No duplicates. return false; } // We use aExtraData to store the index of this new port identifier. MessagePortIdentifier* identifier = scInfo->event->NewPortIdentifier(aExtraData); if (!port->CloneAndDisentangle(*identifier)) { return false; } scInfo->transferredPorts.AppendElement(port); *aTag = SCTAG_DOM_MAP_MESSAGEPORT; *aOwnership = JS::SCTAG_TMO_CUSTOM; *aContent = nullptr; return true; } return false; } /* static */ void PostMessageEvent::FreeTransferStructuredClone(uint32_t aTag, JS::TransferableOwnership aOwnership, void *aContent, uint64_t aExtraData, void* aClosure) { if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { MOZ_ASSERT(aClosure); MOZ_ASSERT(!aContent); StructuredCloneInfo* scInfo = static_cast(aClosure); MessagePort::ForceClose(scInfo->event->GetPortIdentifier(aExtraData)); } } PostMessageEvent::PostMessageEvent(nsGlobalWindow* aSource, const nsAString& aCallerOrigin, nsGlobalWindow* aTargetWindow, nsIPrincipal* aProvidedPrincipal, bool aTrustedCaller) : mSource(aSource), mCallerOrigin(aCallerOrigin), mTargetWindow(aTargetWindow), mProvidedPrincipal(aProvidedPrincipal), mTrustedCaller(aTrustedCaller) { MOZ_COUNT_CTOR(PostMessageEvent); } PostMessageEvent::~PostMessageEvent() { MOZ_COUNT_DTOR(PostMessageEvent); } const MessagePortIdentifier& PostMessageEvent::GetPortIdentifier(uint64_t aId) { MOZ_ASSERT(aId < mPortIdentifiers.Length()); return mPortIdentifiers[aId]; } MessagePortIdentifier* PostMessageEvent::NewPortIdentifier(uint64_t* aPosition) { *aPosition = mPortIdentifiers.Length(); return mPortIdentifiers.AppendElement(); } NS_IMETHODIMP PostMessageEvent::Run() { MOZ_ASSERT(mTargetWindow->IsOuterWindow(), "should have been passed an outer window!"); MOZ_ASSERT(!mSource || mSource->IsOuterWindow(), "should have been passed an outer window!"); AutoJSAPI jsapi; jsapi.Init(); JSContext* cx = jsapi.cx(); // If we bailed before this point we're going to leak mMessage, but // that's probably better than crashing. nsRefPtr targetWindow; if (mTargetWindow->IsClosedOrClosing() || !(targetWindow = mTargetWindow->GetCurrentInnerWindowInternal()) || targetWindow->IsClosedOrClosing()) return NS_OK; MOZ_ASSERT(targetWindow->IsInnerWindow(), "we ordered an inner window!"); JSAutoCompartment ac(cx, targetWindow->GetWrapperPreserveColor()); // Ensure that any origin which might have been provided is the origin of this // window's document. Note that we do this *now* instead of when postMessage // is called because the target window might have been navigated to a // different location between then and now. If this check happened when // postMessage was called, it would be fairly easy for a malicious webpage to // intercept messages intended for another site by carefully timing navigation // of the target window so it changed location after postMessage but before // now. if (mProvidedPrincipal) { // Get the target's origin either from its principal or, in the case the // principal doesn't carry a URI (e.g. the system principal), the target's // document. nsIPrincipal* targetPrin = targetWindow->GetPrincipal(); if (NS_WARN_IF(!targetPrin)) return NS_OK; // Note: This is contrary to the spec with respect to file: URLs, which // the spec groups into a single origin, but given we intentionally // don't do that in other places it seems better to hold the line for // now. Long-term, we want HTML5 to address this so that we can // be compliant while being safer. if (!targetPrin->Equals(mProvidedPrincipal)) { return NS_OK; } } // Deserialize the structured clone data JS::Rooted messageData(cx); StructuredCloneInfo scInfo; scInfo.event = this; scInfo.window = targetWindow; if (!mBuffer.read(cx, &messageData, &sPostMessageCallbacks, &scInfo)) { return NS_ERROR_DOM_DATA_CLONE_ERR; } // Create the event nsCOMPtr eventTarget = do_QueryInterface(static_cast(targetWindow.get())); nsRefPtr event = new MessageEvent(eventTarget, nullptr, nullptr); event->InitMessageEvent(NS_LITERAL_STRING("message"), false /*non-bubbling */, false /*cancelable */, messageData, mCallerOrigin, EmptyString(), mSource); event->SetPorts(new MessagePortList(static_cast(event.get()), scInfo.clonedPorts)); // We can't simply call dispatchEvent on the window because doing so ends // up flipping the trusted bit on the event, and we don't want that to // happen because then untrusted content can call postMessage on a chrome // window if it can get a reference to it. nsIPresShell *shell = targetWindow->GetExtantDoc()->GetShell(); nsRefPtr presContext; if (shell) presContext = shell->GetPresContext(); event->SetTrusted(mTrustedCaller); WidgetEvent* internalEvent = event->GetInternalNSEvent(); nsEventStatus status = nsEventStatus_eIgnore; EventDispatcher::Dispatch(static_cast(mTargetWindow), presContext, internalEvent, static_cast(event.get()), &status); return NS_OK; } bool PostMessageEvent::Write(JSContext* aCx, JS::Handle aMessage, JS::Handle aTransfer, bool aSubsumes, nsPIDOMWindow* aWindow) { // We *must* clone the data here, or the JS::Value could be modified // by script StructuredCloneInfo scInfo; scInfo.event = this; scInfo.window = aWindow; scInfo.subsumes = aSubsumes; return mBuffer.write(aCx, aMessage, aTransfer, &sPostMessageCallbacks, &scInfo); } } // namespace dom } // namespace mozilla