fix buggy impl of innerHTML and outerHTML

This commit is contained in:
velzie 2024-10-08 17:08:09 -04:00
parent 00e11793aa
commit 33bcfaedab
2 changed files with 90 additions and 78 deletions

View file

@ -48,7 +48,7 @@ export default function (client: ScramjetClient, self: typeof window) {
for (const element of attrObject[attr]) { for (const element of attrObject[attr]) {
const descriptor = nativeGetOwnPropertyDescriptor( const descriptor = nativeGetOwnPropertyDescriptor(
element.prototype, element.prototype,
attr attr,
); );
Object.defineProperty(element.prototype, attr, { Object.defineProperty(element.prototype, attr, {
get() { get() {
@ -75,7 +75,7 @@ export default function (client: ScramjetClient, self: typeof window) {
base: new URL(client.url.origin), base: new URL(client.url.origin),
origin: new URL(client.url.origin), origin: new URL(client.url.origin),
} as URLMeta, } as URLMeta,
true true,
); );
} else if (["srcset", "imagesrcset"].includes(attr)) { } else if (["srcset", "imagesrcset"].includes(attr)) {
value = rewriteSrcset(value, client.meta); value = rewriteSrcset(value, client.meta);
@ -174,10 +174,15 @@ export default function (client: ScramjetClient, self: typeof window) {
ctx.set(newval); ctx.set(newval);
}, },
get(ctx) { get(ctx) {
if ( if (ctx.this instanceof self.HTMLScriptElement) {
ctx.this instanceof self.HTMLScriptElement || return atob(
ctx.this instanceof self.HTMLStyleElement client.natives["Element.prototype.getAttribute"].call(
) { ctx.this,
"data-scramjet-script-source-src",
),
);
}
if (ctx.this instanceof self.HTMLStyleElement) {
return ctx.get(); return ctx.get();
} }
@ -264,7 +269,7 @@ export default function (client: ScramjetClient, self: typeof window) {
ctx.args[0], ctx.args[0],
client.cookieStore, client.cookieStore,
client.meta, client.meta,
false false,
); );
} }
}, },
@ -276,7 +281,7 @@ export default function (client: ScramjetClient, self: typeof window) {
ctx.args[0], ctx.args[0],
client.cookieStore, client.cookieStore,
client.meta, client.meta,
true true,
); );
}, },
}); });
@ -287,7 +292,7 @@ export default function (client: ScramjetClient, self: typeof window) {
ctx.args[0], ctx.args[0],
client.cookieStore, client.cookieStore,
client.meta, client.meta,
false false,
); );
}, },
}); });

View file

@ -10,7 +10,7 @@ export function rewriteHtml(
html: string, html: string,
cookieStore: CookieStore, cookieStore: CookieStore,
meta: URLMeta, meta: URLMeta,
fromTop: boolean = false fromTop: boolean = false,
) { ) {
const handler = new DomHandler((err, dom) => dom); const handler = new DomHandler((err, dom) => dom);
const parser = new Parser(handler); const parser = new Parser(handler);
@ -56,7 +56,7 @@ export function rewriteHtml(
script(self.$scramjet.config["codecs"]), script(self.$scramjet.config["codecs"]),
script("data:application/javascript;base64," + btoa(injected)), script("data:application/javascript;base64," + btoa(injected)),
script(self.$scramjet.config["shared"]), script(self.$scramjet.config["shared"]),
script(self.$scramjet.config["client"]) script(self.$scramjet.config["client"]),
); );
} }
@ -78,6 +78,12 @@ export function unrewriteHtml(html: string) {
function traverse(node: ChildNode) { function traverse(node: ChildNode) {
if ("attribs" in node) { if ("attribs" in node) {
for (const key in node.attribs) { for (const key in node.attribs) {
if (key == "data-scramjet-script-source-src") {
if (node.children[0] && "data" in node.children[0])
node.children[0].data = atob(node.attribs[key]);
continue;
}
if (key.startsWith("data-scramjet-")) { if (key.startsWith("data-scramjet-")) {
node.attribs[key.slice(13)] = node.attribs[key]; node.attribs[key.slice(13)] = node.attribs[key];
delete node.attribs[key]; delete node.attribs[key];
@ -101,80 +107,80 @@ export const htmlRules: {
[key: string]: "*" | string[] | ((...any: any[]) => string | null); [key: string]: "*" | string[] | ((...any: any[]) => string | null);
fn: (value: string, meta: URLMeta, cookieStore: CookieStore) => string | null; fn: (value: string, meta: URLMeta, cookieStore: CookieStore) => string | null;
}[] = [ }[] = [
{ {
fn: (value: string, meta: URLMeta) => { fn: (value: string, meta: URLMeta) => {
return encodeUrl(value, meta); return encodeUrl(value, meta);
},
// url rewrites
src: [
"embed",
"script",
"img",
"image",
"iframe",
"source",
"video",
"audio",
"input",
"track",
],
href: ["a", "link", "area"],
data: ["object"],
action: ["form"],
formaction: ["button", "input", "textarea", "submit"],
poster: ["video"],
"xlink:href": ["image"],
}, },
{
fn: () => null,
// url rewrites // csp stuff that must be deleted
src: [ nonce: "*",
"embed", integrity: ["script", "link"],
"script", csp: ["iframe"],
"img",
"image",
"iframe",
"source",
"video",
"audio",
"input",
"track",
],
href: ["a", "link", "area"],
data: ["object"],
action: ["form"],
formaction: ["button", "input", "textarea", "submit"],
poster: ["video"],
"xlink:href": ["image"],
},
{
fn: () => null,
// csp stuff that must be deleted
nonce: "*",
integrity: ["script", "link"],
csp: ["iframe"],
},
{
fn: (value: string, meta: URLMeta) => rewriteSrcset(value, meta),
// srcset
srcset: ["img", "source"],
imagesrcset: ["link"],
},
{
fn: (value: string, meta: URLMeta, cookieStore: CookieStore) =>
rewriteHtml(
value,
cookieStore,
{
// for srcdoc origin is the origin of the page that the iframe is on. base and path get dropped
origin: new URL(meta.origin.origin),
base: new URL(meta.origin.origin),
},
true
),
// srcdoc
srcdoc: ["iframe"],
},
{
fn: (value: string, meta: URLMeta) => rewriteCss(value, meta),
style: "*",
},
{
fn: (value: string) => {
if (["_parent", "_top", "_unfencedTop"].includes(value)) return "_self";
}, },
target: ["a", "base"], {
}, fn: (value: string, meta: URLMeta) => rewriteSrcset(value, meta),
];
// srcset
srcset: ["img", "source"],
imagesrcset: ["link"],
},
{
fn: (value: string, meta: URLMeta, cookieStore: CookieStore) =>
rewriteHtml(
value,
cookieStore,
{
// for srcdoc origin is the origin of the page that the iframe is on. base and path get dropped
origin: new URL(meta.origin.origin),
base: new URL(meta.origin.origin),
},
true,
),
// srcdoc
srcdoc: ["iframe"],
},
{
fn: (value: string, meta: URLMeta) => rewriteCss(value, meta),
style: "*",
},
{
fn: (value: string) => {
if (["_parent", "_top", "_unfencedTop"].includes(value)) return "_self";
},
target: ["a", "base"],
},
];
// i need to add the attributes in during rewriting // i need to add the attributes in during rewriting
function traverseParsedHtml( function traverseParsedHtml(
node: any, node: any,
cookieStore: CookieStore, cookieStore: CookieStore,
meta: URLMeta meta: URLMeta,
) { ) {
if (node.name === "base" && node.attribs.href !== undefined) { if (node.name === "base" && node.attribs.href !== undefined) {
meta.base = new URL(node.attribs.href, meta.origin); meta.base = new URL(node.attribs.href, meta.origin);
@ -207,11 +213,12 @@ function traverseParsedHtml(
if ( if (
node.name === "script" && node.name === "script" &&
/(application|text)\/javascript|module|importmap|undefined/.test( /(application|text)\/javascript|module|importmap|undefined/.test(
node.attribs.type node.attribs.type,
) && ) &&
node.children[0] !== undefined node.children[0] !== undefined
) { ) {
let js = node.children[0].data; let js = node.children[0].data;
node.attribs[`data-scramjet-script-source-src`] = btoa(js);
const htmlcomment = /<!--[\s\S]*?-->/g; const htmlcomment = /<!--[\s\S]*?-->/g;
js = js.replace(htmlcomment, ""); js = js.replace(htmlcomment, "");
node.children[0].data = rewriteJs(js, meta); node.children[0].data = rewriteJs(js, meta);
@ -238,7 +245,7 @@ function traverseParsedHtml(
node.childNodes[childNode] = traverseParsedHtml( node.childNodes[childNode] = traverseParsedHtml(
node.childNodes[childNode], node.childNodes[childNode],
cookieStore, cookieStore,
meta meta,
); );
} }
} }