From f48ecdb2b92584be40c2f94e57a1b5f8654332d5 Mon Sep 17 00:00:00 2001 From: velzie Date: Thu, 19 Sep 2024 20:04:32 -0400 Subject: [PATCH] fix postmessage stuff a little more --- src/client/dom/postmessage.ts | 81 ------------------------------- src/client/shared/postmessage.ts | 82 ++++++++++++++++++++++++++++++++ src/client/shared/worker.ts | 5 +- 3 files changed, 86 insertions(+), 82 deletions(-) delete mode 100644 src/client/dom/postmessage.ts create mode 100644 src/client/shared/postmessage.ts diff --git a/src/client/dom/postmessage.ts b/src/client/dom/postmessage.ts deleted file mode 100644 index d313edb..0000000 --- a/src/client/dom/postmessage.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { iswindow } from ".."; -import { SCRAMJETCLIENT } from "../../symbols"; -import { ScramjetClient } from "../client"; -import { POLLUTANT } from "../shared/realm"; - -export default function (client: ScramjetClient) { - client.Proxy("window.postMessage", { - apply(ctx) { - // so we need to send the real origin here, since the recieving window can't possibly know. - // except, remember that this code is being ran in a different realm than the invoker, so if we ask our `client` it may give us the wrong origin - // if we were given any object that came from the real realm we can use that to get the real origin - // and this works in every case EXCEPT for the fact that all three arguments can be strings which are copied instead of cloned - // so we have to use `$setrealm` which will pollute this with an object from the real realm - - let pollutant; - - if (typeof ctx.args[0] === "object" && ctx.args[0] !== null) { - pollutant = ctx.args[0]; // try to use the first object we can find because it's more reliable - } else if (typeof ctx.args[2] === "object" && ctx.args[2] !== null) { - pollutant = ctx.args[2]; // next try to use transfer - } else if ( - POLLUTANT in ctx.this && - typeof ctx.this[POLLUTANT] === "object" && - ctx.this[POLLUTANT] !== null - ) { - pollutant = ctx.this[POLLUTANT]; // lastly try to use the object from $setrealm - } else { - pollutant = {}; // give up - } - - // and now we can steal Function from the caller's realm - const { - constructor: { constructor: Function }, - } = pollutant; - - // invoking stolen function will give us the caller's globalThis, remember scramjet has already proxied it!!! - const callerGlobalThisProxied: Self = Function("return globalThis")(); - const callerClient = callerGlobalThisProxied[SCRAMJETCLIENT]; - - // this WOULD be enough but the source argument of MessageEvent has to return the caller's window - // and if we just call it normally it would be coming from here, which WILL NOT BE THE CALLER'S because the accessor is from the parent - // so with the stolen function we wrap postmessage so the source will truly be the caller's window (remember that function is scramjet's!!!) - const wrappedPostMessage = Function( - "data", - "origin", - "transfer", - "this(data, origin, transfer)" - ); - - ctx.args[0] = { - $scramjet$messagetype: "window", - $scramjet$origin: callerClient.url.origin, - $scramjet$data: ctx.args[0], - }; - - // * origin because obviously - if (typeof ctx.args[1] === "string") ctx.args[1] = "*"; - - ctx.return( - wrappedPostMessage.call(ctx.fn, ctx.args[0], ctx.args[1], ctx.args[2]) - ); - }, - }); - - const toproxy = [ - "Worker.prototype.postMessage", - "MessagePort.prototype.postMessage", - ]; - if (!iswindow) toproxy.push("self.postMessage"); // only do the generic version if we're in a worker - - client.Proxy(toproxy, { - apply(ctx) { - // origin/source doesn't need to be preserved - it's null in the message event - - ctx.args[0] = { - $scramjet$messagetype: "worker", - $scramjet$data: ctx.args[0], - }; - }, - }); -} diff --git a/src/client/shared/postmessage.ts b/src/client/shared/postmessage.ts new file mode 100644 index 0000000..f0fb4df --- /dev/null +++ b/src/client/shared/postmessage.ts @@ -0,0 +1,82 @@ +import { iswindow } from ".."; +import { SCRAMJETCLIENT } from "../../symbols"; +import { ScramjetClient } from "../client"; +import { POLLUTANT } from "../shared/realm"; + +export default function (client: ScramjetClient) { + if (iswindow) + client.Proxy("window.postMessage", { + apply(ctx) { + // so we need to send the real origin here, since the recieving window can't possibly know. + // except, remember that this code is being ran in a different realm than the invoker, so if we ask our `client` it may give us the wrong origin + // if we were given any object that came from the real realm we can use that to get the real origin + // and this works in every case EXCEPT for the fact that all three arguments can be strings which are copied instead of cloned + // so we have to use `$setrealm` which will pollute this with an object from the real realm + + let pollutant; + + if (typeof ctx.args[0] === "object" && ctx.args[0] !== null) { + pollutant = ctx.args[0]; // try to use the first object we can find because it's more reliable + } else if (typeof ctx.args[2] === "object" && ctx.args[2] !== null) { + pollutant = ctx.args[2]; // next try to use transfer + } else if ( + POLLUTANT in ctx.this && + typeof ctx.this[POLLUTANT] === "object" && + ctx.this[POLLUTANT] !== null + ) { + pollutant = ctx.this[POLLUTANT]; // lastly try to use the object from $setrealm + } else { + pollutant = {}; // give up + } + + // and now we can steal Function from the caller's realm + const { + constructor: { constructor: Function }, + } = pollutant; + + // invoking stolen function will give us the caller's globalThis, remember scramjet has already proxied it!!! + const callerGlobalThisProxied: Self = Function("return globalThis")(); + const callerClient = callerGlobalThisProxied[SCRAMJETCLIENT]; + + // this WOULD be enough but the source argument of MessageEvent has to return the caller's window + // and if we just call it normally it would be coming from here, which WILL NOT BE THE CALLER'S because the accessor is from the parent + // so with the stolen function we wrap postmessage so the source will truly be the caller's window (remember that function is scramjet's!!!) + const wrappedPostMessage = Function( + "data", + "origin", + "transfer", + "this(data, origin, transfer)" + ); + + ctx.args[0] = { + $scramjet$messagetype: "window", + $scramjet$origin: callerClient.url.origin, + $scramjet$data: ctx.args[0], + }; + + // * origin because obviously + if (typeof ctx.args[1] === "string") ctx.args[1] = "*"; + + ctx.return( + wrappedPostMessage.call(ctx.fn, ctx.args[0], ctx.args[1], ctx.args[2]) + ); + }, + }); + + const toproxy = [ + "Worker.prototype.postMessage", + "MessagePort.prototype.postMessage", + ]; + if (!iswindow) toproxy.push("self.postMessage"); // only do the generic version if we're in a worker + + client.Proxy(toproxy, { + apply(ctx) { + // origin/source doesn't need to be preserved - it's null in the message event + + ctx.args[0] = { + $scramjet$messagetype: "worker", + $scramjet$data: ctx.args[0], + }; + }, + }); +} diff --git a/src/client/shared/worker.ts b/src/client/shared/worker.ts index e64bd83..c3a2f57 100644 --- a/src/client/shared/worker.ts +++ b/src/client/shared/worker.ts @@ -3,6 +3,8 @@ import { encodeUrl } from "../../shared/rewriters/url"; import type { MessageC2W } from "../../worker"; import { ScramjetClient } from "../client"; +const workerpostmessage = Worker.prototype.postMessage; + export default function (client: ScramjetClient, self: typeof globalThis) { client.Proxy("Worker", { construct({ args, call }) { @@ -36,7 +38,8 @@ export default function (client: ScramjetClient, self: typeof globalThis) { (async () => { const port = await conn.getInnerPort(); - worker.postMessage( + workerpostmessage.call( + worker, { $scramjet$type: "baremuxinit", port,