support url base

This commit is contained in:
velzie 2024-09-01 13:26:24 -04:00
parent a1ce4e33b3
commit 7a9c990b01
No known key found for this signature in database
GPG key ID: 048413F95F0DDE1F
32 changed files with 213 additions and 158 deletions

View file

@ -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

View file

@ -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;
}

View file

@ -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);
},
});
}

View file

@ -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());

View file

@ -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);
},
});
}

View file

@ -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);

View file

@ -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";

View file

@ -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() {

View file

@ -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";
}

View file

@ -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);

View file

@ -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);
},
});

View file

@ -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);
};

View file

@ -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);
}

View file

@ -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);
},
};

View file

@ -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)}")`)();
};
};

View file

@ -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";
}

View file

@ -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);
},
});
}

View file

@ -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";

View file

@ -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) {

View file

@ -1,5 +1,5 @@
import { ScramjetClient } from "./client";
import { decodeUrl, encodeUrl } from "../shared";
import { decodeUrl } from "../shared";
export class ScramjetServiceWorkerRuntime {
recvport: MessagePort;

View file

@ -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);
}
},
});

View file

@ -26,7 +26,7 @@ export class ScramjetController {
client: "/scramjet.client.js",
codecs: "/scramjet.codecs.js",
flags: {
serviceworkers: true,
serviceworkers: false,
naiiveRewriter: false,
captureErrors: false,
},

View file

@ -1,3 +1,4 @@
// thnank you node unblocker guy
import parse from "set-cookie-parser";
export type Cookie = {

View file

@ -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})`;

View file

@ -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)
);
}

View file

@ -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];
}
});

View file

@ -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);
}

View file

@ -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)
);
}
}

View file

@ -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);
}

View file

@ -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:

View file

@ -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: {

View file

@ -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()}>-&gt;</button>
</div>
${frame.frame}