diff --git a/rollup.config.js b/rollup.config.js index 241f5ce..fb615a0 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -19,7 +19,7 @@ export default { treeshake: "recommended", input: { client: "./src/client/index.ts", - bundle: "./src/bundle/index.ts", + shared: "./src/shared/index.ts", worker: "./src/worker/index.ts", codecs: "./src/codecs/index.ts", config: "./src/scramjet.config.ts" diff --git a/src/client/beacon.ts b/src/client/beacon.ts index e50c72f..f8d7b60 100644 --- a/src/client/beacon.ts +++ b/src/client/beacon.ts @@ -1,4 +1,4 @@ -import { encodeUrl } from "../bundle"; +import { encodeUrl } from "../shared"; navigator.sendBeacon = new Proxy(navigator.sendBeacon, { apply(target, thisArg, argArray) { diff --git a/src/client/css.ts b/src/client/css.ts index 520f762..cb95cef 100644 --- a/src/client/css.ts +++ b/src/client/css.ts @@ -1,4 +1,4 @@ -import { rewriteCss } from "../bundle"; +import { rewriteCss } from "../shared"; const cssProperties = ["background", "background-image", "mask", "mask-image", "list-style", "list-style-image", "border-image", "border-image-source", "cursor"]; const jsProperties = ["background", "backgroundImage", "mask", "maskImage", "listStyle", "listStyleImage", "borderImage", "borderImageSource", "cursor"]; diff --git a/src/client/element.ts b/src/client/element.ts index a03451e..7f06a9d 100644 --- a/src/client/element.ts +++ b/src/client/element.ts @@ -1,4 +1,4 @@ -import { encodeUrl, rewriteCss, rewriteHtml, rewriteJs, rewriteSrcset } from "../bundle"; +import { encodeUrl, rewriteCss, rewriteHtml, rewriteJs, rewriteSrcset } from "../shared"; const attrObject = { "nonce": [HTMLElement], diff --git a/src/client/event.ts b/src/client/event.ts new file mode 100644 index 0000000..d27006b --- /dev/null +++ b/src/client/event.ts @@ -0,0 +1,10 @@ +// idk what shit has to be done on here but it has to be done +// i'm going to temporarily disable rewriting if a MemberExpression detects addEventListener + +// window.addEventListener = new Proxy(window.addEventListener, { +// apply (target, thisArg, argArray) { +// // + +// return Reflect.apply(target, thisArg, argArray); +// } +// }) \ No newline at end of file diff --git a/src/client/history.ts b/src/client/history.ts index d0b8aa7..240e476 100644 --- a/src/client/history.ts +++ b/src/client/history.ts @@ -1 +1,19 @@ -// forgot aobut this api + +import { encodeUrl } from "../shared"; + +window.history.pushState = new Proxy(window.history.pushState, { + apply(target, thisArg, argArray) { + argArray[3] = encodeUrl(argArray[3]); + + return Reflect.apply(target, thisArg, argArray); + }, +}); + + +window.history.replaceState = new Proxy(window.history.replaceState, { + apply(target, thisArg, argArray) { + argArray[3] = encodeUrl(argArray[3]); + + return Reflect.apply(target, thisArg, argArray); + }, +}); diff --git a/src/client/index.ts b/src/client/index.ts index f44b3bd..16d37db 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -1,4 +1,5 @@ import "./window.ts"; +import "./event.ts"; import "./native/eval.ts"; import "./location.ts"; import "./trustedTypes.ts"; @@ -8,11 +9,15 @@ import "./requests/websocket.ts" import "./element.ts"; import "./storage.ts"; import "./css.ts"; +import "./history.ts" import "./worker.ts"; +import "./scope.ts"; declare global { interface Window { __location: Location; __window: Window; + //@ts-ignore scope function cant be typed + __s: any; } } diff --git a/src/client/location.ts b/src/client/location.ts index a78561c..87068b3 100644 --- a/src/client/location.ts +++ b/src/client/location.ts @@ -1,5 +1,5 @@ // @ts-nocheck -import { encodeUrl, decodeUrl } from "../bundle"; +import { encodeUrl, decodeUrl } from "../shared"; function urlLocation() { const loc = new URL(decodeUrl(location.href)); diff --git a/src/client/native/eval.ts b/src/client/native/eval.ts index 35a4783..988de77 100644 --- a/src/client/native/eval.ts +++ b/src/client/native/eval.ts @@ -1,4 +1,4 @@ -import { rewriteJs } from "../../bundle"; +import { rewriteJs } from "../../shared"; const FunctionProxy = new Proxy(Function, { construct(target, argArray) { diff --git a/src/client/requests/fetch.ts b/src/client/requests/fetch.ts index 372e682..a9244c3 100644 --- a/src/client/requests/fetch.ts +++ b/src/client/requests/fetch.ts @@ -1,6 +1,6 @@ // ts throws an error if you dont do window.fetch -import { encodeUrl, rewriteHeaders } from "../../bundle"; +import { encodeUrl, rewriteHeaders } from "../../shared"; window.fetch = new Proxy(window.fetch, { apply(target, thisArg, argArray) { diff --git a/src/client/requests/xmlhttprequest.ts b/src/client/requests/xmlhttprequest.ts index 463623b..490df69 100644 --- a/src/client/requests/xmlhttprequest.ts +++ b/src/client/requests/xmlhttprequest.ts @@ -1,4 +1,4 @@ -import { encodeUrl, rewriteHeaders } from "../../bundle"; +import { encodeUrl, rewriteHeaders } from "../../shared"; XMLHttpRequest.prototype.open = new Proxy(XMLHttpRequest.prototype.open, { apply(target, thisArg, argArray) { diff --git a/src/client/scope.ts b/src/client/scope.ts new file mode 100644 index 0000000..7d4ee6f --- /dev/null +++ b/src/client/scope.ts @@ -0,0 +1,12 @@ +function scope(identifier: any) { + if (identifier instanceof Window) { + return window.__window; + } else if (identifier instanceof Location) { + return window.__location; + } + + return identifier; +} + +// shorthand because this can get out of hand reall quickly +window.__s = scope; \ No newline at end of file diff --git a/src/client/trustedTypes.ts b/src/client/trustedTypes.ts index 09f45dc..654b43b 100644 --- a/src/client/trustedTypes.ts +++ b/src/client/trustedTypes.ts @@ -1,4 +1,4 @@ -import { rewriteHtml, rewriteJs, encodeUrl } from "../bundle"; +import { rewriteHtml, rewriteJs, encodeUrl } from "../shared"; // @ts-expect-error trustedTypes.createPolicy = new Proxy(trustedTypes.createPolicy, { diff --git a/src/client/window.ts b/src/client/window.ts index 7478745..d80702f 100644 --- a/src/client/window.ts +++ b/src/client/window.ts @@ -10,8 +10,13 @@ const windowProxy = new Proxy(window, { return target[prop]; }, - set(target, p, newValue, receiver) { - return Reflect.set(target, p, newValue, receiver); + set(target, prop, newValue) { + // ensures that no apis are overwritten + if (typeof prop === "string" && ["window", "top", "parent", "self", "globalThis", "location"].includes(prop)) { + return false; + } + + return Reflect.set(target, prop, newValue); }, }); diff --git a/src/client/worker.ts b/src/client/worker.ts index 0d16c7b..ccd5c39 100644 --- a/src/client/worker.ts +++ b/src/client/worker.ts @@ -1,7 +1,13 @@ -import { encodeUrl } from "../bundle"; -const RealWorker = Worker +import { encodeUrl } from "../shared"; + Worker = new Proxy(Worker, { - construct(_target, args) { - return new RealWorker(encodeUrl(args[0]), args[1]) + construct(target, argArray) { + argArray[0] = encodeUrl(argArray[0]); + + // target is a reference to the object that you are proxying + // Reflect.construct is just a wrapper for calling target + // you could do new target(...argArray) and it would work the same effectively + + return Reflect.construct(target, argArray); } }) \ No newline at end of file diff --git a/src/scramjet.config.ts b/src/scramjet.config.ts index 99632a0..923ed86 100644 --- a/src/scramjet.config.ts +++ b/src/scramjet.config.ts @@ -6,7 +6,7 @@ declare global { prefix: string; codec: Codec config: string; - bundle: string; + shared: string; worker: string; client: string; codecs: string; @@ -18,7 +18,7 @@ self.__scramjet$config = { prefix: "/scramjet/", codec: self.__scramjet$codecs.plain, config: "/scram/scramjet.config.js", - bundle: "/scram/scramjet.bundle.js", + shared: "/scram/scramjet.shared.js", worker: "/scram/scramjet.worker.js", client: "/scram/scramjet.client.js", codecs: "/scram/scramjet.codecs.js" diff --git a/src/bundle/index.ts b/src/shared/index.ts similarity index 78% rename from src/bundle/index.ts rename to src/shared/index.ts index 6df878a..dc110fd 100644 --- a/src/bundle/index.ts +++ b/src/shared/index.ts @@ -1,14 +1,15 @@ -export { encodeUrl, decodeUrl } from "./rewriters/url"; -export { rewriteCss } from "./rewriters/css"; -export { rewriteHtml, rewriteSrcset } from "./rewriters/html"; -export { rewriteJs } from "./rewriters/js"; -export { rewriteHeaders } from "./rewriters/headers"; - -export function isScramjetFile(src: string) { - let bool = false; - ["codecs", "client", "bundle", "worker", "config"].forEach((file) => { - if (src === self.__scramjet$config[file]) bool = true; - }); - - return bool; +export { encodeUrl, decodeUrl } from "./rewriters/url"; +export { rewriteCss } from "./rewriters/css"; +export { rewriteHtml, rewriteSrcset } from "./rewriters/html"; +export { rewriteJs } from "./rewriters/js"; +export { rewriteHeaders } from "./rewriters/headers"; +export { BareClient } from "@mercuryworkshop/bare-mux" + +export function isScramjetFile(src: string) { + let bool = false; + ["codecs", "client", "shared", "worker", "config"].forEach((file) => { + if (src === self.__scramjet$config[file]) bool = true; + }); + + return bool; } \ No newline at end of file diff --git a/src/bundle/rewriters/css.ts b/src/shared/rewriters/css.ts similarity index 96% rename from src/bundle/rewriters/css.ts rename to src/shared/rewriters/css.ts index 088d9c8..17b9ecf 100644 --- a/src/bundle/rewriters/css.ts +++ b/src/shared/rewriters/css.ts @@ -1,35 +1,35 @@ -// This CSS rewriter uses code from Meteor -// You can find the original source code at https://github.com/MeteorProxy/Meteor - -import { encodeUrl } from "./url"; - -export function rewriteCss(css: string, origin?: URL) { - const regex = - /(@import\s+(?!url\())?\s*url\(\s*(['"]?)([^'")]+)\2\s*\)|@import\s+(['"])([^'"]+)\4/g - - return css.replace( - regex, - ( - match, - importStatement, - urlQuote, - urlContent, - importQuote, - importContent - ) => { - const url = urlContent || importContent - const encodedUrl = encodeUrl(url.trim(), origin) - - if (importStatement) { - return `@import url(${urlQuote}${encodedUrl}${urlQuote})` - } - - if (importQuote) { - return `@import ${importQuote}${encodedUrl}${importQuote}` - } - - return `url(${urlQuote}${encodedUrl}${urlQuote})` - } - ) - -} +// This CSS rewriter uses code from Meteor +// You can find the original source code at https://github.com/MeteorProxy/Meteor + +import { encodeUrl } from "./url"; + +export function rewriteCss(css: string, origin?: URL) { + const regex = + /(@import\s+(?!url\())?\s*url\(\s*(['"]?)([^'")]+)\2\s*\)|@import\s+(['"])([^'"]+)\4/g + + return css.replace( + regex, + ( + match, + importStatement, + urlQuote, + urlContent, + importQuote, + importContent + ) => { + const url = urlContent || importContent + const encodedUrl = encodeUrl(url.trim(), origin) + + if (importStatement) { + return `@import url(${urlQuote}${encodedUrl}${urlQuote})` + } + + if (importQuote) { + return `@import ${importQuote}${encodedUrl}${importQuote}` + } + + return `url(${urlQuote}${encodedUrl}${urlQuote})` + } + ) + +} diff --git a/src/bundle/rewriters/headers.ts b/src/shared/rewriters/headers.ts similarity index 96% rename from src/bundle/rewriters/headers.ts rename to src/shared/rewriters/headers.ts index ef74ccc..8f927be 100644 --- a/src/bundle/rewriters/headers.ts +++ b/src/shared/rewriters/headers.ts @@ -1,52 +1,52 @@ -import { encodeUrl } from "./url"; -import { BareHeaders } from "@mercuryworkshop/bare-mux"; -const cspHeaders = [ - "cross-origin-embedder-policy", - "cross-origin-opener-policy", - "cross-origin-resource-policy", - "content-security-policy", - "content-security-policy-report-only", - "expect-ct", - "feature-policy", - "origin-isolation", - "strict-transport-security", - "upgrade-insecure-requests", - "x-content-type-options", - "x-download-options", - "x-frame-options", - "x-permitted-cross-domain-policies", - "x-powered-by", - "x-xss-protection", - // This needs to be emulated, but for right now it isn't that important of a feature to be worried about - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data - "clear-site-data" -]; - -const urlHeaders = [ - "location", - "content-location", - "referer" -]; - -export function rewriteHeaders(rawHeaders: BareHeaders, origin?: URL) { - const headers = {}; - - for (const key in rawHeaders) { - headers[key.toLowerCase()] = rawHeaders[key]; - } - - cspHeaders.forEach((header) => { - delete headers[header]; - }); - - urlHeaders.forEach((header) => { - if (headers[header]) - headers[header] = encodeUrl(headers[header] as string, origin); - }); - - if (headers["link"]) { - headers["link"] = headers["link"].replace(/<(.*?)>/gi, (match) => encodeUrl(match, origin)); - } - - return headers; +import { encodeUrl } from "./url"; +import { BareHeaders } from "@mercuryworkshop/bare-mux"; +const cspHeaders = [ + "cross-origin-embedder-policy", + "cross-origin-opener-policy", + "cross-origin-resource-policy", + "content-security-policy", + "content-security-policy-report-only", + "expect-ct", + "feature-policy", + "origin-isolation", + "strict-transport-security", + "upgrade-insecure-requests", + "x-content-type-options", + "x-download-options", + "x-frame-options", + "x-permitted-cross-domain-policies", + "x-powered-by", + "x-xss-protection", + // This needs to be emulated, but for right now it isn't that important of a feature to be worried about + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data + "clear-site-data" +]; + +const urlHeaders = [ + "location", + "content-location", + "referer" +]; + +export function rewriteHeaders(rawHeaders: BareHeaders, origin?: URL) { + const headers = {}; + + for (const key in rawHeaders) { + headers[key.toLowerCase()] = rawHeaders[key]; + } + + cspHeaders.forEach((header) => { + delete headers[header]; + }); + + urlHeaders.forEach((header) => { + if (headers[header]) + headers[header] = encodeUrl(headers[header] as string, origin); + }); + + if (headers["link"]) { + headers["link"] = headers["link"].replace(/<(.*?)>/gi, (match) => encodeUrl(match, origin)); + } + + return headers; } \ No newline at end of file diff --git a/src/bundle/rewriters/html.ts b/src/shared/rewriters/html.ts similarity index 94% rename from src/bundle/rewriters/html.ts rename to src/shared/rewriters/html.ts index 388360f..6b35fe9 100644 --- a/src/bundle/rewriters/html.ts +++ b/src/shared/rewriters/html.ts @@ -1,97 +1,97 @@ -import { Parser } from "htmlparser2"; -import { DomHandler, Element } from "domhandler"; -import { hasAttrib } from "domutils"; -import render from "dom-serializer"; -import { encodeUrl } from "./url"; -import { rewriteCss } from "./css"; -import { rewriteJs } from "./js"; -import { isScramjetFile } from "../"; - -export function rewriteHtml(html: string, origin?: URL) { - const handler = new DomHandler((err, dom) => dom); - const parser = new Parser(handler); - - parser.write(html); - parser.end(); - - return render(traverseParsedHtml(handler.root, origin)); -} - -// i need to add the attributes in during rewriting - -function traverseParsedHtml(node, origin?: URL) { - /* csp attributes */ - for (const cspAttr of ["nonce", "integrity", "csp"]) { - if (hasAttrib(node, cspAttr)) { - node.attribs[`data-${cspAttr}`] = node.attribs[cspAttr]; - delete node.attribs[cspAttr]; - } - } - - /* url attributes */ - for (const urlAttr of ["src", "href", "data", "action", "formaction"]) { - if (hasAttrib(node, urlAttr) && !isScramjetFile(node.attribs[urlAttr])) { - const value = node.attribs[urlAttr]; - node.attribs[`data-${urlAttr}`] = value; - node.attribs[urlAttr] = encodeUrl(value, origin); - } - } - - /* other */ - for (const srcsetAttr of ["srcset", "imagesrcset"]) { - if (hasAttrib(node, srcsetAttr)) { - const value = node.attribs[srcsetAttr]; - node.attribs[`data-${srcsetAttr}`] = value; - node.attribs[srcsetAttr] = rewriteSrcset(value, origin); - } - } - - if (hasAttrib(node, "srcdoc")) node.attribs.srcdoc = rewriteHtml(node.attribs.srcdoc, origin); - if (hasAttrib(node, "style")) node.attribs.style = rewriteCss(node.attribs.style, origin); - - if (node.name === "style" && node.children[0] !== undefined) node.children[0].data = rewriteCss(node.children[0].data, origin); - if (node.name === "script" && /(application|text)\/javascript|importmap|undefined/.test(node.attribs.type) && node.children[0] !== undefined) node.children[0].data = rewriteJs(node.children[0].data, origin); - if (node.name === "meta" && hasAttrib(node, "http-equiv")) { - if (node.attribs["http-equiv"] === "content-security-policy") { - node = {}; - } else if (node.attribs["http-equiv"] === "refresh" && node.attribs.content.includes("url")) { - const contentArray = node.attribs.content.split("url="); - contentArray[1] = encodeUrl(contentArray[1].trim(), origin); - node.attribs.content = contentArray.join("url="); - } - } - - if (node.name === "head") { - const scramjetScripts = []; - ["codecs", "config", "bundle", "client"].forEach((script) => { - scramjetScripts.push(new Element("script", { - src: self.__scramjet$config[script], - type: "module" - })); - }); - - node.children.unshift(...scramjetScripts); - } - - if (node.childNodes) { - for (const childNode in node.childNodes) { - node.childNodes[childNode] = traverseParsedHtml(node.childNodes[childNode], origin); - } - } - - return node; -} - -export function rewriteSrcset(srcset: string, origin?: URL) { - 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 rewrittenUrls.join(""); +import { Parser } from "htmlparser2"; +import { DomHandler, Element } from "domhandler"; +import { hasAttrib } from "domutils"; +import render from "dom-serializer"; +import { encodeUrl } from "./url"; +import { rewriteCss } from "./css"; +import { rewriteJs } from "./js"; +import { isScramjetFile } from ".."; + +export function rewriteHtml(html: string, origin?: URL) { + const handler = new DomHandler((err, dom) => dom); + const parser = new Parser(handler); + + parser.write(html); + parser.end(); + + return render(traverseParsedHtml(handler.root, origin)); +} + +// i need to add the attributes in during rewriting + +function traverseParsedHtml(node, origin?: URL) { + /* csp attributes */ + for (const cspAttr of ["nonce", "integrity", "csp"]) { + if (hasAttrib(node, cspAttr)) { + node.attribs[`data-${cspAttr}`] = node.attribs[cspAttr]; + delete node.attribs[cspAttr]; + } + } + + /* url attributes */ + for (const urlAttr of ["src", "href", "data", "action", "formaction"]) { + if (hasAttrib(node, urlAttr) && !isScramjetFile(node.attribs[urlAttr])) { + const value = node.attribs[urlAttr]; + node.attribs[`data-${urlAttr}`] = value; + node.attribs[urlAttr] = encodeUrl(value, origin); + } + } + + /* other */ + for (const srcsetAttr of ["srcset", "imagesrcset"]) { + if (hasAttrib(node, srcsetAttr)) { + const value = node.attribs[srcsetAttr]; + node.attribs[`data-${srcsetAttr}`] = value; + node.attribs[srcsetAttr] = rewriteSrcset(value, origin); + } + } + + if (hasAttrib(node, "srcdoc")) node.attribs.srcdoc = rewriteHtml(node.attribs.srcdoc, origin); + if (hasAttrib(node, "style")) node.attribs.style = rewriteCss(node.attribs.style, origin); + + if (node.name === "style" && node.children[0] !== undefined) node.children[0].data = rewriteCss(node.children[0].data, origin); + if (node.name === "script" && /(application|text)\/javascript|importmap|undefined/.test(node.attribs.type) && node.children[0] !== undefined) node.children[0].data = rewriteJs(node.children[0].data, origin); + if (node.name === "meta" && hasAttrib(node, "http-equiv")) { + if (node.attribs["http-equiv"] === "content-security-policy") { + node = {}; + } else if (node.attribs["http-equiv"] === "refresh" && node.attribs.content.includes("url")) { + const contentArray = node.attribs.content.split("url="); + contentArray[1] = encodeUrl(contentArray[1].trim(), origin); + node.attribs.content = contentArray.join("url="); + } + } + + if (node.name === "head") { + const scramjetScripts = []; + ["codecs", "config", "shared", "client"].forEach((script) => { + scramjetScripts.push(new Element("script", { + src: self.__scramjet$config[script], + type: "module" + })); + }); + + node.children.unshift(...scramjetScripts); + } + + if (node.childNodes) { + for (const childNode in node.childNodes) { + node.childNodes[childNode] = traverseParsedHtml(node.childNodes[childNode], origin); + } + } + + return node; +} + +export function rewriteSrcset(srcset: string, origin?: URL) { + 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 rewrittenUrls.join(""); } \ No newline at end of file diff --git a/src/bundle/rewriters/js.ts b/src/shared/rewriters/js.ts similarity index 94% rename from src/bundle/rewriters/js.ts rename to src/shared/rewriters/js.ts index 1e9cf1f..f8c5eff 100644 --- a/src/bundle/rewriters/js.ts +++ b/src/shared/rewriters/js.ts @@ -1,78 +1,79 @@ -import { parseModule } from "meriyah"; -import { generate } from "astring"; -import { makeTraveler } from "astravel"; -import { encodeUrl } from "./url"; -import * as ESTree from "estree"; - -// i am a cat. i like to be petted. i like to be fed. i like to be - -// js rewiter is NOT finished - -// location -// window -// self -// globalThis -// this -// top -// parent - - -export function rewriteJs(js: string, origin?: URL) { - try { - const ast = parseModule(js, { - module: true, - webcompat: true - }); - - const identifierList = [ - "window", - "self", - "globalThis", - "this", - "parent", - "top", - "location" - ] - - const customTraveler = makeTraveler({ - ImportDeclaration: (node: ESTree.ImportDeclaration) => { - node.source.value = encodeUrl(node.source.value as string, origin); - }, - - ImportExpression: (node: ESTree.ImportExpression) => { - if (node.source.type === "Literal") { - node.source.value = encodeUrl(node.source.value as string, origin); - } else if (node.source.type === "Identifier") { - // this is for things that import something like - // const moduleName = "name"; - // await import(moduleName); - node.source.name = `__wrapImport(${node.source.name})`; - } - }, - - ExportAllDeclaration: (node: ESTree.ExportAllDeclaration) => { - node.source.value = encodeUrl(node.source.value as string, origin); - }, - - ExportNamedDeclaration: (node: ESTree.ExportNamedDeclaration) => { - // strings are Literals in ESTree syntax but these will always be strings - if (node.source) node.source.value = encodeUrl(node.source.value as string, origin); - }, - - // js rweriting notrdone - MemberExpression: (node: ESTree.MemberExpression) => { - if (node.object.type === "Identifier" && identifierList.includes(node.object.name)) { - node.object.name = "__" + node.object.name; - } - } - }); - - customTraveler.go(ast); - - return generate(ast); - } catch { - console.log(js); - - return js; - } +import { parseModule } from "meriyah"; +import { generate } from "astring"; +import { makeTraveler } from "astravel"; +import { encodeUrl } from "./url"; +import * as ESTree from "estree"; + +// i am a cat. i like to be petted. i like to be fed. i like to be + +// js rewiter is NOT finished + +// location +// window +// self +// globalThis +// this +// top +// parent + + +export function rewriteJs(js: string, origin?: URL) { + try { + const ast = parseModule(js, { + module: true, + webcompat: true + }); + + const identifierList = [ + "window", + "self", + "globalThis", + "this", + "parent", + "top", + "this", + "location" + ] + + const customTraveler = makeTraveler({ + ImportDeclaration: (node: ESTree.ImportDeclaration) => { + node.source.value = encodeUrl(node.source.value as string, origin); + }, + + ImportExpression: (node: ESTree.ImportExpression) => { + if (node.source.type === "Literal") { + node.source.value = encodeUrl(node.source.value as string, origin); + } else if (node.source.type === "Identifier") { + // this is for things that import something like + // const moduleName = "name"; + // await import(moduleName); + node.source.name = `__wrapImport(${node.source.name})`; + } + }, + + ExportAllDeclaration: (node: ESTree.ExportAllDeclaration) => { + node.source.value = encodeUrl(node.source.value as string, origin); + }, + + ExportNamedDeclaration: (node: ESTree.ExportNamedDeclaration) => { + // strings are Literals in ESTree syntax but these will always be strings + if (node.source) node.source.value = encodeUrl(node.source.value as string, origin); + }, + + // js rweriting notrdone + MemberExpression: (node: ESTree.MemberExpression) => { + if (node.object.type === "Identifier" && identifierList.includes(node.object.name)) { + node.object.name = `__s${node.object.name}`; + } + } + }); + + customTraveler.go(ast); + + return generate(ast); + } catch { + console.log(js); + + return js; + } } \ No newline at end of file diff --git a/src/bundle/rewriters/url.ts b/src/shared/rewriters/url.ts similarity index 97% rename from src/bundle/rewriters/url.ts rename to src/shared/rewriters/url.ts index 47a4077..c7f8d11 100644 --- a/src/bundle/rewriters/url.ts +++ b/src/shared/rewriters/url.ts @@ -1,37 +1,37 @@ -import { rewriteJs } from "./js"; - -function canParseUrl(url: string, origin?: URL) { - try { - new URL(url, origin); - - return true; - } catch { - return false; - } -} - -// something is broken with this but i didn't debug it -export function encodeUrl(url: string, origin?: URL) { - if (!origin) { - origin = new URL(self.__scramjet$config.codec.decode(location.href.slice((location.origin + self.__scramjet$config.prefix).length))); - } - - if (url.startsWith("javascript:")) { - return "javascript:" + rewriteJs(url.slice("javascript:".length)); - } else if (/^(#|mailto|about|data)/.test(url)) { - return url; - } else if (canParseUrl(url, origin)) { - return location.origin + self.__scramjet$config.prefix + self.__scramjet$config.codec.encode(new URL(url, origin).href); - } -} - -// something is also broken with this but i didn't debug it -export function decodeUrl(url: string) { - if (/^(#|about|data|mailto|javascript)/.test(url)) { - return url; - } else if (canParseUrl(url)) { - return self.__scramjet$config.codec.decode(url.slice((location.origin + self.__scramjet$config.prefix).length)) - } else { - return url; - } +import { rewriteJs } from "./js"; + +function canParseUrl(url: string, origin?: URL) { + try { + new URL(url, origin); + + return true; + } catch { + return false; + } +} + +// something is broken with this but i didn't debug it +export function encodeUrl(url: string, origin?: URL) { + if (!origin) { + origin = new URL(self.__scramjet$config.codec.decode(location.href.slice((location.origin + self.__scramjet$config.prefix).length))); + } + + if (url.startsWith("javascript:")) { + return "javascript:" + rewriteJs(url.slice("javascript:".length)); + } else if (/^(#|mailto|about|data)/.test(url)) { + return url; + } else if (canParseUrl(url, origin)) { + return location.origin + self.__scramjet$config.prefix + self.__scramjet$config.codec.encode(new URL(url, origin).href); + } +} + +// something is also broken with this but i didn't debug it +export function decodeUrl(url: string) { + if (/^(#|about|data|mailto|javascript)/.test(url)) { + return url; + } else if (canParseUrl(url)) { + return self.__scramjet$config.codec.decode(url.slice((location.origin + self.__scramjet$config.prefix).length)) + } else { + return url; + } } \ No newline at end of file diff --git a/src/worker/index.ts b/src/worker/index.ts index d950dc3..47b5883 100644 --- a/src/worker/index.ts +++ b/src/worker/index.ts @@ -1,6 +1,6 @@ import { BareClient } from "@mercuryworkshop/bare-mux"; import { BareResponseFetch } from "@mercuryworkshop/bare-mux"; -import { encodeUrl, decodeUrl, rewriteCss, rewriteHeaders, rewriteHtml, rewriteJs } from "../bundle"; +import { encodeUrl, decodeUrl, rewriteCss, rewriteHeaders, rewriteHtml, rewriteJs } from "../shared"; declare global { interface Window {