mirror of
https://github.com/MercuryWorkshop/scramjet.git
synced 2025-05-12 22:10:01 -04:00
support url base
This commit is contained in:
parent
a1ce4e33b3
commit
7a9c990b01
32 changed files with 213 additions and 158 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -2,25 +2,34 @@ import { config } from "../../shared";
|
|||
import { ScramjetClient } from "../client";
|
||||
|
||||
export const enabled = () => config.flags.captureErrors;
|
||||
|
||||
export default function (client: ScramjetClient, self: typeof globalThis) {
|
||||
function argdbg(arg) {
|
||||
export function argdbg(arg, recurse = []) {
|
||||
switch (typeof arg) {
|
||||
case "string":
|
||||
if (arg.includes("scramjet") && !arg.includes("\n")) debugger;
|
||||
if (arg.includes("localhost:1337/scramjet/") && arg.includes("m3u8"))
|
||||
debugger;
|
||||
break;
|
||||
case "object":
|
||||
if (arg instanceof Location) debugger;
|
||||
// if (arg instanceof Location) debugger;
|
||||
if (
|
||||
arg &&
|
||||
arg[Symbol.iterator] &&
|
||||
typeof arg[Symbol.iterator] === "function"
|
||||
)
|
||||
for (let ar of arg) argdbg(ar);
|
||||
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) {
|
||||
self.$scramerr = function scramerr(e) {
|
||||
console.warn("CAUGHT ERROR", e);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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)}")`)();
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ScramjetClient } from "./client";
|
||||
import { decodeUrl, encodeUrl } from "../shared";
|
||||
import { decodeUrl } from "../shared";
|
||||
|
||||
export class ScramjetServiceWorkerRuntime {
|
||||
recvport: MessagePort;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -26,7 +26,7 @@ export class ScramjetController {
|
|||
client: "/scramjet.client.js",
|
||||
codecs: "/scramjet.codecs.js",
|
||||
flags: {
|
||||
serviceworkers: true,
|
||||
serviceworkers: false,
|
||||
naiiveRewriter: false,
|
||||
captureErrors: false,
|
||||
},
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// thnank you node unblocker guy
|
||||
import parse from "set-cookie-parser";
|
||||
|
||||
export type Cookie = {
|
||||
|
|
|
@ -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})`;
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = /<!--[\s\S]*?-->/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];
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<Response> {
|
||||
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:
|
||||
|
|
|
@ -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: {
|
||||
|
|
32
static/ui.js
32
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`
|
||||
<div>
|
||||
|
@ -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)}></input>
|
||||
}} on:keyup=${(e) => e.keyCode == 13 && (store.url = this.url) && frame.go(e.target.value)}></input>
|
||||
<button on:click=${() => frame.forward()}>-></button>
|
||||
</div>
|
||||
${frame.frame}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue