diff --git a/src/client/client.ts b/src/client/client.ts index c2cd8fb..77f52a6 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -15,6 +15,7 @@ import { } from "../shared"; import { createWrapFn } from "./shared/wrap"; import { NavigateEvent } from "./events"; +import type { URLMeta } from "../shared/rewriters/url"; declare global { interface Window { @@ -78,6 +79,8 @@ export class ScramjetClient { ] > = new Map(); + meta: URLMeta; + constructor(public global: typeof globalThis) { this.serviceWorker = this.global.navigator.serviceWorker; @@ -103,6 +106,27 @@ export class ScramjetClient { }) ); } + + let baseurl: URL; + if (iswindow) { + // setup base url + // base url can only be updated at document load time and it will affect all urls resolved by encodeurl/rewriteurl + const base = this.global.document.querySelector("base"); + if (base) { + baseurl = new URL(decodeUrl(base.href)); + } + } + + const client = this; + this.meta = { + get origin() { + return client.url; + }, + get base() { + return baseurl || client.url; + }, + }; + global[SCRAMJETCLIENT] = this; } @@ -169,7 +193,7 @@ export class ScramjetClient { } if (ev.defaultPrevented) return; - self.location.href = encodeUrl(ev.url); + self.location.href = encodeUrl(ev.url, this.meta); } // below are the utilities for proxying and trapping dom APIs diff --git a/src/client/document.ts b/src/client/document.ts index b7ffd58..9adcf62 100644 --- a/src/client/document.ts +++ b/src/client/document.ts @@ -21,7 +21,7 @@ export function createDocumentProxy( }, set(target, prop, newValue) { if (prop === "location") { - location.href = encodeUrl(newValue); + location.href = encodeUrl(newValue, client.meta); return; } diff --git a/src/client/dom/css.ts b/src/client/dom/css.ts index 21884eb..cae8bb0 100644 --- a/src/client/dom/css.ts +++ b/src/client/dom/css.ts @@ -18,7 +18,7 @@ export default function (client: ScramjetClient) { client.Proxy("CSSStyleDeclaration.prototype.setProperty", { apply(ctx) { if (cssProperties.includes(ctx.args[0])) - ctx.args[1] = rewriteCss(ctx.args[1]); + ctx.args[1] = rewriteCss(ctx.args[1], client.meta); }, }); } diff --git a/src/client/dom/element.ts b/src/client/dom/element.ts index 2f9f521..d58082c 100644 --- a/src/client/dom/element.ts +++ b/src/client/dom/element.ts @@ -63,11 +63,11 @@ export default function (client: ScramjetClient, self: typeof window) { } else if ( ["src", "data", "href", "action", "formaction"].includes(attr) ) { - value = encodeUrl(value); + value = encodeUrl(value, client.meta); } else if (attr === "srcdoc") { value = rewriteHtml(value, client.cookieStore, undefined, true); } else if (["srcset", "imagesrcset"].includes(attr)) { - value = rewriteSrcset(value); + value = rewriteSrcset(value, client.meta); } descriptor.set.call(this, value); @@ -106,6 +106,7 @@ export default function (client: ScramjetClient, self: typeof window) { client.Trap("Node.prototype.baseURI", { get() { + // TODO this should be using ownerdocument but who gaf const base = self.document.querySelector("base"); if (base) { return new URL(base.href, client.url).href; @@ -132,7 +133,7 @@ export default function (client: ScramjetClient, self: typeof window) { }); if (rule) { - ctx.args[1] = rule.fn(value, client.url, client.cookieStore); + ctx.args[1] = rule.fn(value, client.meta, client.cookieStore); ctx.fn.call(ctx.this, `data-scramjet-${ctx.args[0]}`, value); } }, @@ -152,11 +153,11 @@ export default function (client: ScramjetClient, self: typeof window) { set(ctx, value: string) { let newval; if (ctx.this instanceof self.HTMLScriptElement) { - newval = rewriteJs(value, client.url); + newval = rewriteJs(value, client.meta); } else if (ctx.this instanceof self.HTMLStyleElement) { - newval = rewriteCss(value, client.url); + newval = rewriteCss(value, client.meta); } else { - newval = rewriteHtml(value, client.cookieStore, client.url); + newval = rewriteHtml(value, client.cookieStore, client.meta); } ctx.set(newval); @@ -168,7 +169,7 @@ export default function (client: ScramjetClient, self: typeof window) { client.Trap("Element.prototype.outerHTML", { set(ctx, value: string) { - ctx.set(rewriteHtml(value, client.cookieStore, client.url)); + ctx.set(rewriteHtml(value, client.cookieStore, client.meta)); }, get(ctx) { return unrewriteHtml(ctx.get()); diff --git a/src/client/dom/fontface.ts b/src/client/dom/fontface.ts index 567c42f..7e19b85 100644 --- a/src/client/dom/fontface.ts +++ b/src/client/dom/fontface.ts @@ -4,7 +4,7 @@ import { decodeUrl, rewriteCss } from "../../shared"; export default function (client: ScramjetClient, self: typeof window) { client.Proxy("FontFace", { construct(ctx) { - ctx.args[1] = rewriteCss(ctx.args[1]); + ctx.args[1] = rewriteCss(ctx.args[1], client.meta); }, }); } diff --git a/src/client/dom/history.ts b/src/client/dom/history.ts index caaf43c..ffca87c 100644 --- a/src/client/dom/history.ts +++ b/src/client/dom/history.ts @@ -5,7 +5,7 @@ import { UrlChangeEvent } from "../events"; export default function (client: ScramjetClient, self: typeof globalThis) { client.Proxy("history.pushState", { apply(ctx) { - ctx.args[2] = encodeUrl(ctx.args[2]); + ctx.args[2] = encodeUrl(ctx.args[2], client.meta); ctx.call(); const ev = new UrlChangeEvent(client.url.href); @@ -15,7 +15,7 @@ export default function (client: ScramjetClient, self: typeof globalThis) { client.Proxy("history.replaceState", { apply(ctx) { - ctx.args[2] = encodeUrl(ctx.args[2]); + ctx.args[2] = encodeUrl(ctx.args[2], client.meta); ctx.call(); const ev = new UrlChangeEvent(client.url.href); diff --git a/src/client/dom/open.ts b/src/client/dom/open.ts index 5b9142c..3ad0bdb 100644 --- a/src/client/dom/open.ts +++ b/src/client/dom/open.ts @@ -5,7 +5,7 @@ import { SCRAMJETCLIENT } from "../../symbols"; export default function (client: ScramjetClient) { client.Proxy("window.open", { apply(ctx) { - if (ctx.args[0]) ctx.args[0] = encodeUrl(ctx.args[0]); + if (ctx.args[0]) ctx.args[0] = encodeUrl(ctx.args[0], client.meta); if (["_parent", "_top", "_unfencedTop"].includes(ctx.args[1])) ctx.args[1] = "_self"; diff --git a/src/client/dom/origin.ts b/src/client/dom/origin.ts index 1a4cb87..337fcc7 100644 --- a/src/client/dom/origin.ts +++ b/src/client/dom/origin.ts @@ -4,6 +4,7 @@ import { decodeUrl } from "../../shared"; export default function (client: ScramjetClient, self: typeof window) { client.Trap("origin", { get() { + // this isn't right!! return client.url.origin; }, set() { diff --git a/src/client/dom/serviceworker.ts b/src/client/dom/serviceworker.ts index 2d2c29c..3b111ec 100644 --- a/src/client/dom/serviceworker.ts +++ b/src/client/dom/serviceworker.ts @@ -16,7 +16,7 @@ export default function (client: ScramjetClient, self: Self) { client.Proxy("Worklet.prototype.addModule", { apply(ctx) { - ctx.args[0] = encodeUrl(ctx.args[0]); + ctx.args[0] = encodeUrl(ctx.args[0], client.meta); }, }); @@ -65,7 +65,7 @@ export default function (client: ScramjetClient, self: Self) { client.Proxy("navigator.serviceWorker.register", { apply(ctx) { if (ctx.args[0] instanceof URL) ctx.args[0] = ctx.args[0].href; - let url = encodeUrl(ctx.args[0]) + "?dest=serviceworker"; + let url = encodeUrl(ctx.args[0], client.meta) + "?dest=serviceworker"; if (ctx.args[1] && ctx.args[1].type === "module") { url += "&type=module"; } diff --git a/src/client/dom/storage.ts b/src/client/dom/storage.ts index ccd55ba..5d9f625 100644 --- a/src/client/dom/storage.ts +++ b/src/client/dom/storage.ts @@ -43,7 +43,7 @@ export default function (client: ScramjetClient, self: typeof window) { ).length; default: - if (prop in Object.prototype) { + if (prop in Object.prototype || typeof prop === "symbol") { return Reflect.get(target, prop); } console.log("GET", prop, target == realLocalStorage); diff --git a/src/client/location.ts b/src/client/location.ts index 152f8b6..4e6e51f 100644 --- a/src/client/location.ts +++ b/src/client/location.ts @@ -1,7 +1,7 @@ // @ts-nocheck import { ScramjetClient } from "./client"; import { nativeGetOwnPropertyDescriptor } from "./natives"; -import { encodeUrl, decodeUrl } from "../shared"; +import { decodeUrl, encodeUrl } from "../shared"; import { iswindow } from "."; export function createLocationProxy( @@ -76,7 +76,7 @@ export function createLocationProxy( if (self.location.assign) fakeLocation.assign = new Proxy(self.location.assign, { apply(target, thisArg, args) { - args[0] = encodeUrl(args[0]); + args[0] = encodeUrl(args[0], client.meta); Reflect.apply(target, self.location, args); }, }); @@ -89,7 +89,7 @@ export function createLocationProxy( if (self.location.replace) fakeLocation.replace = new Proxy(self.location.replace, { apply(target, thisArg, args) { - args[0] = encodeUrl(args[0]); + args[0] = encodeUrl(args[0], client.meta); Reflect.apply(target, self.location, args); }, }); diff --git a/src/client/shared/err.ts b/src/client/shared/err.ts index 2658a48..7ce4320 100644 --- a/src/client/shared/err.ts +++ b/src/client/shared/err.ts @@ -2,25 +2,34 @@ import { config } from "../../shared"; import { ScramjetClient } from "../client"; export const enabled = () => config.flags.captureErrors; +export function argdbg(arg, recurse = []) { + switch (typeof arg) { + case "string": + if (arg.includes("localhost:1337/scramjet/") && arg.includes("m3u8")) + debugger; + break; + case "object": + // if (arg instanceof Location) debugger; + if ( + arg && + arg[Symbol.iterator] && + typeof arg[Symbol.iterator] === "function" + ) + for (let prop in arg) { + // make sure it's not a getter + let desc = Object.getOwnPropertyDescriptor(arg, prop); + if (desc && desc.get) continue; + + const ar = arg[prop]; + if (recurse.includes(ar)) continue; + recurse.push(ar); + argdbg(ar, recurse); + } + break; + } +} export default function (client: ScramjetClient, self: typeof globalThis) { - function argdbg(arg) { - switch (typeof arg) { - case "string": - if (arg.includes("scramjet") && !arg.includes("\n")) debugger; - break; - case "object": - if (arg instanceof Location) debugger; - if ( - arg && - arg[Symbol.iterator] && - typeof arg[Symbol.iterator] === "function" - ) - for (let ar of arg) argdbg(ar); - break; - } - } - self.$scramerr = function scramerr(e) { console.warn("CAUGHT ERROR", e); }; diff --git a/src/client/shared/eval.ts b/src/client/shared/eval.ts index cacd1ca..c269400 100644 --- a/src/client/shared/eval.ts +++ b/src/client/shared/eval.ts @@ -8,7 +8,7 @@ export default function (client: ScramjetClient, self: Self) { value: function (js: any) { if (typeof js !== "string") return js; - const rewritten = rewriteJs(js, client.url); + const rewritten = rewriteJs(js, client.meta); return rewritten; }, @@ -23,5 +23,5 @@ export function indirectEval(this: ScramjetClient, js: any) { const indirection = this.global.eval; - return indirection(rewriteJs(js, this.url) as string); + return indirection(rewriteJs(js, this.meta) as string); } diff --git a/src/client/shared/function.ts b/src/client/shared/function.ts index f8b17cb..b1359b5 100644 --- a/src/client/shared/function.ts +++ b/src/client/shared/function.ts @@ -1,19 +1,19 @@ import { ScramjetClient, ProxyCtx, Proxy } from "../client"; import { rewriteJs } from "../../shared"; -function rewriteFunction(ctx: ProxyCtx) { +function rewriteFunction(ctx: ProxyCtx, client: ScramjetClient) { const stringifiedFunction = ctx.call().toString(); - ctx.return(ctx.fn(`return ${rewriteJs(stringifiedFunction)}`)()); + ctx.return(ctx.fn(`return ${rewriteJs(stringifiedFunction, client.meta)}`)()); } export default function (client: ScramjetClient, self: Self) { const handler: Proxy = { apply(ctx) { - rewriteFunction(ctx); + rewriteFunction(ctx, client); }, construct(ctx) { - rewriteFunction(ctx); + rewriteFunction(ctx, client); }, }; diff --git a/src/client/shared/import.ts b/src/client/shared/import.ts index 4325d99..545923d 100644 --- a/src/client/shared/import.ts +++ b/src/client/shared/import.ts @@ -1,5 +1,6 @@ import { ScramjetClient } from "../client"; -import { config, encodeUrl } from "../../shared"; +import { config } from "../../shared"; +import { encodeUrl } from "../../shared/rewriters/url"; export default function (client: ScramjetClient, self: Self) { const Function = client.natives.Function; @@ -8,7 +9,7 @@ export default function (client: ScramjetClient, self: Self) { return function (url: string) { const resolved = new URL(url, base).href; - return Function(`return import("${encodeUrl(resolved)}")`)(); + return Function(`return import("${encodeUrl(resolved, client.meta)}")`)(); }; }; diff --git a/src/client/shared/requests/fetch.ts b/src/client/shared/requests/fetch.ts index e4576f2..9719817 100644 --- a/src/client/shared/requests/fetch.ts +++ b/src/client/shared/requests/fetch.ts @@ -9,7 +9,7 @@ export default function (client: ScramjetClient, self: typeof globalThis) { client.Proxy("fetch", { apply(ctx) { if (typeof ctx.args[0] === "string" || ctx.args[0] instanceof URL) { - ctx.args[0] = encodeUrl(ctx.args[0].toString()); + ctx.args[0] = encodeUrl(ctx.args[0].toString(), client.meta); if (isemulatedsw) ctx.args[0] += "?from=swruntime"; } @@ -25,7 +25,7 @@ export default function (client: ScramjetClient, self: typeof globalThis) { client.Proxy("Request", { construct(ctx) { if (typeof ctx.args[0] === "string" || ctx.args[0] instanceof URL) { - ctx.args[0] = encodeUrl(ctx.args[0].toString()); + ctx.args[0] = encodeUrl(ctx.args[0].toString(), client.meta); if (isemulatedsw) ctx.args[0] += "?from=swruntime"; } diff --git a/src/client/shared/requests/xmlhttprequest.ts b/src/client/shared/requests/xmlhttprequest.ts index 70498fd..d77f3ed 100644 --- a/src/client/shared/requests/xmlhttprequest.ts +++ b/src/client/shared/requests/xmlhttprequest.ts @@ -1,16 +1,17 @@ import { decodeUrl, encodeUrl, rewriteHeaders } from "../../../shared"; +import { ScramjetClient } from "../../client"; -export default function (client, self) { +export default function (client: ScramjetClient, self: Self) { client.Proxy("XMLHttpRequest.prototype.open", { apply(ctx) { - if (ctx.args[1]) ctx.args[1] = encodeUrl(ctx.args[1]); + if (ctx.args[1]) ctx.args[1] = encodeUrl(ctx.args[1], client.meta); }, }); client.Proxy("XMLHttpRequest.prototype.setRequestHeader", { apply(ctx) { let headerObject = Object.fromEntries([ctx.args]); - headerObject = rewriteHeaders(headerObject); + headerObject = rewriteHeaders(headerObject, client.meta); ctx.args = Object.entries(headerObject)[0]; }, @@ -18,7 +19,7 @@ export default function (client, self) { client.Trap("XMLHttpRequest.prototype.responseURL", { get(ctx) { - return decodeUrl(ctx.get()); + return decodeUrl(ctx.get() as string); }, }); } diff --git a/src/client/shared/worker.ts b/src/client/shared/worker.ts index 9a7c574..a03b8ac 100644 --- a/src/client/shared/worker.ts +++ b/src/client/shared/worker.ts @@ -1,4 +1,5 @@ -import { encodeUrl, BareMuxConnection } from "../../shared"; +import { BareMuxConnection } from "../../shared"; +import { encodeUrl } from "../../shared/rewriters/url"; import type { MessageC2W } from "../../worker"; import { ScramjetClient } from "../client"; @@ -23,7 +24,7 @@ export default function (client: ScramjetClient, self: typeof globalThis) { id, } as MessageC2W); } else { - args[0] = encodeUrl(args[0]) + "?dest=worker"; + args[0] = encodeUrl(args[0], client.meta) + "?dest=worker"; if (args[1] && args[1].type === "module") { args[0] += "&type=module"; diff --git a/src/client/shared/wrap.ts b/src/client/shared/wrap.ts index 5379ffd..4e54f4e 100644 --- a/src/client/shared/wrap.ts +++ b/src/client/shared/wrap.ts @@ -2,6 +2,7 @@ import { iswindow, isworker } from ".."; import { SCRAMJETCLIENT } from "../../symbols"; import { ScramjetClient } from "../client"; import { config } from "../../shared"; +import { argdbg } from "./err"; export function createWrapFn(client: ScramjetClient, self: typeof globalThis) { return function (identifier: any, args: any) { diff --git a/src/client/swruntime.ts b/src/client/swruntime.ts index c701f21..6921ff2 100644 --- a/src/client/swruntime.ts +++ b/src/client/swruntime.ts @@ -1,5 +1,5 @@ import { ScramjetClient } from "./client"; -import { decodeUrl, encodeUrl } from "../shared"; +import { decodeUrl } from "../shared"; export class ScramjetServiceWorkerRuntime { recvport: MessagePort; diff --git a/src/client/worker/importScripts.ts b/src/client/worker/importScripts.ts index ca62ec5..d85299a 100644 --- a/src/client/worker/importScripts.ts +++ b/src/client/worker/importScripts.ts @@ -1,10 +1,11 @@ import { encodeUrl } from "../../shared"; +import { ScramjetClient } from "../client"; -export default function (client, self) { +export default function (client: ScramjetClient, self: Self) { client.Proxy("importScripts", { apply(ctx) { for (const i in ctx.args) { - ctx.args[i] = encodeUrl(ctx.args[i]); + ctx.args[i] = encodeUrl(ctx.args[i], client.meta); } }, }); diff --git a/src/controller/index.ts b/src/controller/index.ts index 1a894a0..bb83c19 100644 --- a/src/controller/index.ts +++ b/src/controller/index.ts @@ -26,7 +26,7 @@ export class ScramjetController { client: "/scramjet.client.js", codecs: "/scramjet.codecs.js", flags: { - serviceworkers: true, + serviceworkers: false, naiiveRewriter: false, captureErrors: false, }, diff --git a/src/shared/cookie.ts b/src/shared/cookie.ts index e818e34..8fdf768 100644 --- a/src/shared/cookie.ts +++ b/src/shared/cookie.ts @@ -1,3 +1,4 @@ +// thnank you node unblocker guy import parse from "set-cookie-parser"; export type Cookie = { diff --git a/src/shared/rewriters/css.ts b/src/shared/rewriters/css.ts index de78be6..c7337fe 100644 --- a/src/shared/rewriters/css.ts +++ b/src/shared/rewriters/css.ts @@ -1,9 +1,9 @@ // This CSS rewriter uses code from Meteor // You can find the original source code at https://github.com/MeteorProxy/Meteor -import { encodeUrl } from "./url"; +import { URLMeta, encodeUrl } from "./url"; -export function rewriteCss(css: string, origin?: URL) { +export function rewriteCss(css: string, meta: URLMeta) { const regex = /(@import\s+(?!url\())?\s*url\(\s*(['"]?)([^'")]+)\2\s*\)|@import\s+(['"])([^'"]+)\4/g; @@ -18,7 +18,7 @@ export function rewriteCss(css: string, origin?: URL) { importContent ) => { const url = urlContent || importContent; - const encodedUrl = encodeUrl(url.trim(), origin); + const encodedUrl = encodeUrl(url.trim(), meta); if (importStatement) { return `@import url(${urlQuote}${encodedUrl}${urlQuote})`; diff --git a/src/shared/rewriters/headers.ts b/src/shared/rewriters/headers.ts index 9dca828..c686f38 100644 --- a/src/shared/rewriters/headers.ts +++ b/src/shared/rewriters/headers.ts @@ -1,4 +1,6 @@ -import { encodeUrl } from "./url"; +// TODO this whole file should be inlined and deleted it's a weird relic from ssd era + +import { URLMeta, encodeUrl } from "./url"; import { BareHeaders } from "@mercuryworkshop/bare-mux"; const cspHeaders = [ "cross-origin-embedder-policy", @@ -24,11 +26,11 @@ const cspHeaders = [ const urlHeaders = ["location", "content-location", "referer"]; -function rewriteLinkHeader(link: string, origin?: URL) { - return link.replace(/<(.*)>/gi, (match) => encodeUrl(match, origin)); +function rewriteLinkHeader(link: string, meta: URLMeta) { + return link.replace(/<(.*)>/gi, (match) => encodeUrl(match, meta)); } -export function rewriteHeaders(rawHeaders: BareHeaders, origin?: URL) { +export function rewriteHeaders(rawHeaders: BareHeaders, meta: URLMeta) { const headers = {}; for (const key in rawHeaders) { @@ -41,17 +43,14 @@ export function rewriteHeaders(rawHeaders: BareHeaders, origin?: URL) { urlHeaders.forEach((header) => { if (headers[header]) - headers[header] = encodeUrl( - headers[header]?.toString() as string, - origin - ); + headers[header] = encodeUrl(headers[header]?.toString() as string, meta); }); if (typeof headers["link"] === "string") { - headers["link"] = rewriteLinkHeader(headers["link"], origin); + headers["link"] = rewriteLinkHeader(headers["link"], meta); } else if (Array.isArray(headers["link"])) { headers["link"] = headers["link"].map((link) => - rewriteLinkHeader(link, origin) + rewriteLinkHeader(link, meta) ); } diff --git a/src/shared/rewriters/html.ts b/src/shared/rewriters/html.ts index 5119ce7..7ae35be 100644 --- a/src/shared/rewriters/html.ts +++ b/src/shared/rewriters/html.ts @@ -1,7 +1,7 @@ import { ElementType, Parser } from "htmlparser2"; import { ChildNode, DomHandler, Element, Node, Text } from "domhandler"; import render from "dom-serializer"; -import { encodeUrl } from "./url"; +import { URLMeta, encodeUrl } from "./url"; import { rewriteCss } from "./css"; import { rewriteJs } from "./js"; import { CookieStore } from "../cookie"; @@ -9,7 +9,7 @@ import { CookieStore } from "../cookie"; export function rewriteHtml( html: string, cookieStore: CookieStore, - origin?: URL, + meta: URLMeta, fromTop: boolean = false ) { const handler = new DomHandler((err, dom) => dom); @@ -17,7 +17,7 @@ export function rewriteHtml( parser.write(html); parser.end(); - traverseParsedHtml(handler.root, cookieStore, origin); + traverseParsedHtml(handler.root, cookieStore, meta); function findhead(node) { if (node.type === ElementType.Tag && node.name === "head") { @@ -62,6 +62,11 @@ export function rewriteHtml( return render(handler.root); } +type ParseState = { + base: string; + origin?: URL; +}; + export function unrewriteHtml(html: string) { const handler = new DomHandler((err, dom) => dom); const parser = new Parser(handler); @@ -93,17 +98,11 @@ export function unrewriteHtml(html: string) { export const htmlRules: { [key: string]: "*" | string[] | Function; - fn: ( - value: string, - origin: URL | null, - cookieStore: CookieStore - ) => string | null; + fn: (value: string, meta: URLMeta, cookieStore: CookieStore) => string | null; }[] = [ { - fn: (value: string, origin: URL) => { - if (["_parent", "_top", "_unfencedTop"].includes(value)) return "_self"; - - return encodeUrl(value, origin); + fn: (value: string, meta: URLMeta) => { + return encodeUrl(value, meta); }, // url rewrites @@ -133,34 +132,51 @@ export const htmlRules: { csp: ["iframe"], }, { - fn: (value: string, origin?: URL) => rewriteSrcset(value, origin), + fn: (value: string, meta: URLMeta) => rewriteSrcset(value, meta), // srcset srcset: ["img", "source"], imagesrcset: ["link"], }, { - fn: (value: string, origin: URL, cookieStore: CookieStore) => - rewriteHtml(value, cookieStore, origin, true), + 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, origin?: URL) => rewriteCss(value, origin), + fn: (value: string, meta: URLMeta) => rewriteCss(value, meta), style: "*", }, { fn: (value: string) => { if (["_parent", "_top", "_unfencedTop"].includes(value)) return "_self"; }, - target: ["a"], + target: ["a", "base"], }, ]; // i need to add the attributes in during rewriting -function traverseParsedHtml(node: any, cookieStore: CookieStore, origin?: URL) { +function traverseParsedHtml( + node: any, + cookieStore: CookieStore, + meta: URLMeta +) { + if (node.name === "base" && node.attribs.href !== undefined) { + meta.base = new URL(node.attribs.href, meta.origin); + } + if (node.attribs) for (const rule of htmlRules) { for (const attr in rule) { @@ -170,7 +186,7 @@ function traverseParsedHtml(node: any, cookieStore: CookieStore, origin?: URL) { if (sel === "*" || sel.includes(node.name)) { if (node.attribs[attr] !== undefined) { const value = node.attribs[attr]; - let v = rule.fn(value, origin, cookieStore); + let v = rule.fn(value, meta, cookieStore); if (v === null) delete node.attribs[attr]; else { @@ -183,7 +199,7 @@ function traverseParsedHtml(node: any, cookieStore: CookieStore, origin?: URL) { } if (node.name === "style" && node.children[0] !== undefined) - node.children[0].data = rewriteCss(node.children[0].data, origin); + node.children[0].data = rewriteCss(node.children[0].data, meta); if ( node.name === "script" && @@ -195,7 +211,7 @@ function traverseParsedHtml(node: any, cookieStore: CookieStore, origin?: URL) { let js = node.children[0].data; const htmlcomment = //g; js = js.replace(htmlcomment, ""); - node.children[0].data = rewriteJs(js, origin); + node.children[0].data = rewriteJs(js, meta); } if (node.name === "meta" && node.attribs["http-equiv"] != undefined) { @@ -209,7 +225,7 @@ function traverseParsedHtml(node: any, cookieStore: CookieStore, origin?: URL) { ) { const contentArray = node.attribs.content.split("url="); if (contentArray[1]) - contentArray[1] = encodeUrl(contentArray[1].trim(), origin); + contentArray[1] = encodeUrl(contentArray[1].trim(), meta); node.attribs.content = contentArray.join("url="); } } @@ -219,7 +235,7 @@ function traverseParsedHtml(node: any, cookieStore: CookieStore, origin?: URL) { node.childNodes[childNode] = traverseParsedHtml( node.childNodes[childNode], cookieStore, - origin + meta ); } } @@ -227,14 +243,14 @@ function traverseParsedHtml(node: any, cookieStore: CookieStore, origin?: URL) { return node; } -export function rewriteSrcset(srcset: string, origin?: URL) { +export function rewriteSrcset(srcset: string, meta: URLMeta) { const urls = srcset.split(/ [0-9]+x,? ?/g); if (!urls) return ""; const sufixes = srcset.match(/ [0-9]+x,? ?/g); if (!sufixes) return ""; const rewrittenUrls = urls.map((url, i) => { if (url && sufixes[i]) { - return encodeUrl(url, origin) + sufixes[i]; + return encodeUrl(url, meta) + sufixes[i]; } }); diff --git a/src/shared/rewriters/js.ts b/src/shared/rewriters/js.ts index ac912f5..2d46673 100644 --- a/src/shared/rewriters/js.ts +++ b/src/shared/rewriters/js.ts @@ -1,4 +1,4 @@ -import { decodeUrl } from "./url"; +import { URLMeta, decodeUrl } from "./url"; // i am a cat. i like to be petted. i like to be fed. i like to be import { @@ -18,25 +18,21 @@ init(); Error.stackTraceLimit = 50; -global.rws = rewriteJs; -export function rewriteJs(js: string | ArrayBuffer, origin?: URL) { - if ("window" in globalThis) - origin = origin ?? new URL(decodeUrl(location.href)); - +export function rewriteJs(js: string | ArrayBuffer, meta: URLMeta) { if (self.$scramjet.config.flags.naiiveRewriter) { const text = typeof js === "string" ? js : new TextDecoder().decode(js); - return rewriteJsNaiive(text, origin); + return rewriteJsNaiive(text); } const before = performance.now(); if (typeof js === "string") { js = new TextDecoder().decode( - rewrite_js(js, origin.toString(), self.$scramjet) + rewrite_js(js, meta.base.href, self.$scramjet) ); } else { js = rewrite_js_from_arraybuffer( new Uint8Array(js), - origin.toString(), + meta.base.href, self.$scramjet ); } @@ -53,10 +49,7 @@ export function rewriteJs(js: string | ArrayBuffer, origin?: URL) { // 4. i think the global state can get clobbered somehow // // if you can ensure all the preconditions are met this is faster than full rewrites -export function rewriteJsNaiive(js: string | ArrayBuffer, origin?: URL) { - if ("window" in globalThis) - origin = origin ?? new URL(decodeUrl(location.href)); - +export function rewriteJsNaiive(js: string | ArrayBuffer) { if (typeof js !== "string") { js = new TextDecoder().decode(js); } diff --git a/src/shared/rewriters/url.ts b/src/shared/rewriters/url.ts index 81149d3..535f3e3 100644 --- a/src/shared/rewriters/url.ts +++ b/src/shared/rewriters/url.ts @@ -1,5 +1,10 @@ import { rewriteJs } from "./js"; +export type URLMeta = { + origin: URL; + base: URL; +}; + function tryCanParseURL(url: string, origin?: string | URL): URL | null { try { return new URL(url, origin); @@ -9,36 +14,34 @@ function tryCanParseURL(url: string, origin?: string | URL): URL | null { } // something is broken with this but i didn't debug it -export function encodeUrl(url: string | URL, origin?: URL) { +export function encodeUrl(url: string | URL, meta: URLMeta) { if (url instanceof URL) { url = url.href; } - if (!origin) { - if (location.pathname.startsWith(self.$scramjet.config.prefix + "worker")) { - origin = new URL(new URL(location.href).searchParams.get("origin")); - } else - origin = new URL( - self.$scramjet.codec.decode( - location.href.slice( - (location.origin + self.$scramjet.config.prefix).length - ) - ) - ); - } - - // is this the correct behavior? - if (!url) url = origin.href; + // if (!origin) { + // if (location.pathname.startsWith(self.$scramjet.config.prefix + "worker")) { + // origin = new URL(new URL(location.href).searchParams.get("origin")); + // } else + // origin = new URL( + // self.$scramjet.codec.decode( + // location.href.slice( + // (location.origin + self.$scramjet.config.prefix).length + // ) + // ) + // ); + // } if (url.startsWith("javascript:")) { - return "javascript:" + rewriteJs(url.slice("javascript:".length), origin); + return "javascript:" + rewriteJs(url.slice("javascript:".length), meta); } else if (/^(#|mailto|about|data|blob)/.test(url)) { + // TODO this regex is jank but i'm not fixing it return url; - } else if (tryCanParseURL(url, origin)) { + } else { return ( location.origin + self.$scramjet.config.prefix + - self.$scramjet.codec.encode(new URL(url, origin).href) + self.$scramjet.codec.encode(new URL(url, meta.base).href) ); } } diff --git a/src/shared/rewriters/worker.ts b/src/shared/rewriters/worker.ts index e92180f..009b73c 100644 --- a/src/shared/rewriters/worker.ts +++ b/src/shared/rewriters/worker.ts @@ -1,13 +1,12 @@ import { rewriteJs } from "./js"; +import { URLMeta } from "./url"; const clientscripts = ["wasm", "shared", "client"]; export function rewriteWorkers( js: string | ArrayBuffer, type: string, - origin?: URL + meta: URLMeta ) { - origin.search = ""; - let str = ""; str += `self.$scramjet = {}; self.$scramjet.config = ${JSON.stringify(self.$scramjet.config)}; @@ -29,7 +28,7 @@ self.$scramjet.codec = self.$scramjet.codecs[self.$scramjet.config.codec]; } } - let rewritten = rewriteJs(js, origin); + let rewritten = rewriteJs(js, meta); if (rewritten instanceof Uint8Array) { rewritten = new TextDecoder().decode(rewritten); } diff --git a/src/worker/fetch.ts b/src/worker/fetch.ts index 7c7d7b5..602d77f 100644 --- a/src/worker/fetch.ts +++ b/src/worker/fetch.ts @@ -16,6 +16,15 @@ import { rewriteWorkers, } from "../shared"; +import type { URLMeta } from "../shared/rewriters/url"; + +function newmeta(url: URL): URLMeta { + return { + origin: url, + base: url, + }; +} + export async function swfetch( this: ScramjetServiceWorker, request: Request, @@ -25,7 +34,7 @@ export async function swfetch( if (urlParam.has("url")) { return Response.redirect( - encodeUrl(urlParam.get("url"), new URL(urlParam.get("url"))) + encodeUrl(urlParam.get("url"), newmeta(new URL(urlParam.get("url")))) ); } @@ -124,7 +133,7 @@ async function handleResponse( client: Client ): Promise { let responseBody: string | ArrayBuffer | ReadableStream; - const responseHeaders = rewriteHeaders(response.rawHeaders, url); + const responseHeaders = rewriteHeaders(response.rawHeaders, newmeta(url)); let maybeHeaders = responseHeaders["set-cookie"] || []; for (const cookie in maybeHeaders) { @@ -155,7 +164,7 @@ async function handleResponse( responseBody = rewriteHtml( await response.text(), cookieStore, - url, + newmeta(url), true ); } else { @@ -163,19 +172,19 @@ async function handleResponse( } break; case "script": - responseBody = rewriteJs(await response.arrayBuffer(), url); + responseBody = rewriteJs(await response.arrayBuffer(), newmeta(url)); // Disable threading for now, it's causing issues. // responseBody = await this.threadpool.rewriteJs(await responseBody.arrayBuffer(), url.toString()); break; case "style": - responseBody = rewriteCss(await response.text(), url); + responseBody = rewriteCss(await response.text(), newmeta(url)); break; case "sharedworker": case "worker": responseBody = rewriteWorkers( await response.arrayBuffer(), workertype, - url + newmeta(url) ); break; default: diff --git a/src/worker/index.ts b/src/worker/index.ts index 8078d02..ce33288 100644 --- a/src/worker/index.ts +++ b/src/worker/index.ts @@ -145,7 +145,10 @@ export class ScramjetServiceWorker { const data = await promise.promise; delete this.dataworkerpromises[id]; - const rewritten = rewriteWorkers(data, type, new URL(origin)); + const rewritten = rewriteWorkers(data, type, { + origin: new URL(origin), + base: new URL(origin), + }); return new Response(rewritten, { headers: { diff --git a/static/ui.js b/static/ui.js index 8245123..85fae47 100644 --- a/static/ui.js +++ b/static/ui.js @@ -25,25 +25,6 @@ scramjet.init("./sw.js"); // } // }); -navigator.serviceWorker.onmessage = ({ data }) => { - if (data.scramjet$type === "getLocalStorage") { - const pairs = Object.entries(localStorage); - navigator.serviceWorker.controller.postMessage({ - scramjet$type: "getLocalStorage", - scramjet$token: data.scramjet$token, - data: pairs, - }); - } else if (data.scramjet$type === "setLocalStorage") { - for (const [key, value] of data.data) { - localStorage.setItem(key, value); - } - navigator.serviceWorker.controller.postMessage({ - scramjet$type: "setLocalStorage", - scramjet$token: data.scramjet$token, - }); - } -}; - const connection = new BareMux.BareMuxConnection("/baremux/worker.js"); const flex = css` display: flex; @@ -150,12 +131,23 @@ function App() { background-color: #313131; } `; + this.url = store.url; const frame = scramjet.createFrame(); frame.addEventListener("urlchange", (e) => { + if (!e.url) return; this.url = e.url; }); + frame.frame.addEventListener("load", () => { + let url = frame.frame.contentWindow.location.href; + if (!url) return; + if (url === "about:blank") return; + + this.url = $scramjet.codecs.plain.decode( + url.substring((location.href + "/scramjet").length) + ); + }); return html`
@@ -193,7 +185,7 @@ function App() { e ) => { this.url = e.target.value; - }} on:keyup=${(e) => e.keyCode == 13 && frame.go(e.target.value) && (store.url = this.url)}> + }} on:keyup=${(e) => e.keyCode == 13 && (store.url = this.url) && frame.go(e.target.value)}>
${frame.frame}