This commit is contained in:
velzie 2024-07-14 19:17:49 -04:00
parent 4b0337db0c
commit 55b6666229
No known key found for this signature in database
GPG key ID: 048413F95F0DDE1F
43 changed files with 4693 additions and 2712 deletions

View file

@ -1,28 +1,30 @@
{ {
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"], "plugins": ["@typescript-eslint"],
"rules": { "rules": {
"no-await-in-loop": "warn", "no-await-in-loop": "warn",
"no-unused-labels": "error", "no-unused-labels": "error",
"no-unused-vars": "error", "no-unused-vars": "error",
"quotes": ["error", "double"], "quotes": ["error", "double"],
"max-lines-per-function": ["error", { "max-lines-per-function": [
"max": 200, "error",
"skipComments": true {
}], "max": 200,
"getter-return": "error", "skipComments": true
"newline-before-return": "error", }
"no-multiple-empty-lines": "error", ],
"no-var": "error", "getter-return": "error",
"indent": ["warn", "tab"], "newline-before-return": "error",
"no-this-before-super": "warn", "no-multiple-empty-lines": "error",
"no-useless-return": "error", "no-var": "error",
"no-shadow": "error", "no-this-before-super": "warn",
"prefer-const": "warn", "no-useless-return": "error",
"no-unreachable": "warn", "no-shadow": "error",
"no-undef": "off", "prefer-const": "warn",
"@typescript-eslint/no-explicit-any": "off", "no-unreachable": "warn",
"@typescript-eslint/ban-ts-comment": "off" "no-undef": "off",
} "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-ts-comment": "off"
}
} }

View file

@ -1,22 +1,22 @@
# Scramjet # Scramjet
Scramjet is an experimental web proxy that aims to be the successor to Ultraviolet. Scramjet is an experimental web proxy that aims to be the successor to Ultraviolet.
It currently does not support most websites due to it being very early in the development stage. It currently does not support most websites due to it being very early in the development stage.
The UI is not finalized and only used as a means to test the web proxy. The UI is not finalized and only used as a means to test the web proxy.
## How to build ## How to build
Running `pnpm dev` will build Scramjet and start a dev server on localhost:1337. If you only want to build the proxy without using the dev server, run `pnpm build`.
Running `pnpm dev` will build Scramjet and start a dev server on localhost:1337. If you only want to build the proxy without using the dev server, run `pnpm build`.
## TODO
## TODO
- Finish HTML rewriting - Finish HTML rewriting
- `<script type="importmap"></script>` rewriting - `<script type="importmap"></script>` rewriting
- Make an array of all possible import values and pass the array onto the JS rewriter, then rewrite all the URLs inside of it - Make an array of all possible import values and pass the array onto the JS rewriter, then rewrite all the URLs inside of it
- Finish JS rewriting - Finish JS rewriting
- Check imports/exports for values contained in the `importmap` array, don't rewrite the node value if present - Check imports/exports for values contained in the `importmap` array, don't rewrite the node value if present
- Write client APIs - Write client APIs
- Fix `Illegal Invocation` when calling `addEventListener()` on the window proxy - Fix `Illegal Invocation` when calling `addEventListener()` on the window proxy
- Get rid of ESM builds and pollute the global namespace (maybe?) - Get rid of ESM builds and pollute the global namespace (maybe?)

View file

@ -1,7 +1,7 @@
"use strict"; "use strict";
const { resolve } = require("node:path"); const { resolve } = require("node:path");
const scramjetPath = resolve(__dirname, "..", "dist"); const scramjetPath = resolve(__dirname, "..", "dist");
exports.scramjetPath = scramjetPath; exports.scramjetPath = scramjetPath;

6
lib/index.d.ts vendored
View file

@ -1,3 +1,3 @@
declare const scramjetPath: string; declare const scramjetPath: string;
export { scramjetPath }; export { scramjetPath };

4846
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

6
prettier.json Normal file
View file

@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"useTabs": true,
"semi": false,
"singleQuote": false
}

View file

@ -6,48 +6,48 @@ import { fileURLToPath } from "url";
const __dirname = fileURLToPath(new URL(".", import.meta.url)); const __dirname = fileURLToPath(new URL(".", import.meta.url));
export default defineConfig({ export default defineConfig({
// change to production when needed // change to production when needed
mode: "development", mode: "development",
entry: { entry: {
shared: join(__dirname, "src/shared/index.ts"), shared: join(__dirname, "src/shared/index.ts"),
worker: join(__dirname, "src/worker/index.ts"), worker: join(__dirname, "src/worker/index.ts"),
client: join(__dirname, "src/client/index.ts"), client: join(__dirname, "src/client/index.ts"),
config: join(__dirname, "src/scramjet.config.ts"), config: join(__dirname, "src/scramjet.config.ts"),
codecs: join(__dirname, "src/codecs/index.ts"), codecs: join(__dirname, "src/codecs/index.ts"),
}, },
resolve: { resolve: {
extensions: [".ts", ".js"], extensions: [".ts", ".js"],
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.ts$/, test: /\.ts$/,
use: "builtin:swc-loader", use: "builtin:swc-loader",
exclude: ["/node_modules/"], exclude: ["/node_modules/"],
options: { options: {
jsc: { jsc: {
parser: { parser: {
syntax: "typescript" syntax: "typescript",
} },
} },
}, },
type: "javascript/auto" type: "javascript/auto",
} },
]
},
output: {
filename: "scramjet.[name].js",
path: join(__dirname, "dist"),
iife: true,
clean: true
},
plugins: [
// new RsdoctorRspackPlugin({
// supports: {
// parseBundle: true,
// banner: true
// }
// })
], ],
watch: true },
}); output: {
filename: "scramjet.[name].js",
path: join(__dirname, "dist"),
iife: true,
clean: true,
},
plugins: [
// new RsdoctorRspackPlugin({
// supports: {
// parseBundle: true,
// banner: true
// }
// })
],
watch: true,
});

164
server.js
View file

@ -1,80 +1,84 @@
// Dev server imports // Dev server imports
import { createBareServer } from "@tomphttp/bare-server-node"; import { createBareServer } from "@tomphttp/bare-server-node";
import { createServer } from "http"; import { createServer } from "http";
import Fastify from "fastify"; import Fastify from "fastify";
import fastifyStatic from "@fastify/static"; import fastifyStatic from "@fastify/static";
import { join } from "node:path"; import { join } from "node:path";
import { spawn } from "node:child_process" import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
//transports //transports
import { baremuxPath } from "@mercuryworkshop/bare-mux/node" import { baremuxPath } from "@mercuryworkshop/bare-mux/node";
import { epoxyPath } from "@mercuryworkshop/epoxy-transport" import { epoxyPath } from "@mercuryworkshop/epoxy-transport";
import { libcurlPath } from "@mercuryworkshop/libcurl-transport" import { libcurlPath } from "@mercuryworkshop/libcurl-transport";
import { bareModulePath } from "@mercuryworkshop/bare-as-module3" import { bareModulePath } from "@mercuryworkshop/bare-as-module3";
const bare = createBareServer("/bare/", { const bare = createBareServer("/bare/", {
logErrors: true logErrors: true,
}); });
const fastify = Fastify({ const fastify = Fastify({
serverFactory: (handler) => { serverFactory: (handler) => {
return createServer() return createServer()
.on("request", (req, res) => { .on("request", (req, res) => {
if (bare.shouldRoute(req)) { if (bare.shouldRoute(req)) {
bare.routeRequest(req, res); bare.routeRequest(req, res);
} else { } else {
handler(req, res); handler(req, res);
} }
}).on("upgrade", (req, socket, head) => { })
if (bare.shouldRoute(req)) { .on("upgrade", (req, socket, head) => {
bare.routeUpgrade(req, socket, head); if (bare.shouldRoute(req)) {
} else { bare.routeUpgrade(req, socket, head);
socket.end(); } else {
} socket.end();
}) }
} });
}); },
});
fastify.register(fastifyStatic, {
root: join(fileURLToPath(new URL(".", import.meta.url)), "./static"), fastify.register(fastifyStatic, {
decorateReply: false root: join(fileURLToPath(new URL(".", import.meta.url)), "./static"),
}); decorateReply: false,
fastify.register(fastifyStatic, { });
root: join(fileURLToPath(new URL(".", import.meta.url)), "./dist"), fastify.register(fastifyStatic, {
prefix: "/scram/", root: join(fileURLToPath(new URL(".", import.meta.url)), "./dist"),
decorateReply: false prefix: "/scram/",
}) decorateReply: false,
fastify.register(fastifyStatic, { });
root: baremuxPath, fastify.register(fastifyStatic, {
prefix: "/baremux/", root: baremuxPath,
decorateReply: false prefix: "/baremux/",
}) decorateReply: false,
fastify.register(fastifyStatic, { });
root: epoxyPath, fastify.register(fastifyStatic, {
prefix: "/epoxy/", root: epoxyPath,
decorateReply: false prefix: "/epoxy/",
}) decorateReply: false,
fastify.register(fastifyStatic, { });
root: libcurlPath, fastify.register(fastifyStatic, {
prefix: "/libcurl/", root: libcurlPath,
decorateReply: false prefix: "/libcurl/",
}) decorateReply: false,
fastify.register(fastifyStatic, { });
root: bareModulePath, fastify.register(fastifyStatic, {
prefix: "/baremod/", root: bareModulePath,
decorateReply: false prefix: "/baremod/",
}) decorateReply: false,
fastify.listen({ });
port: process.env.PORT || 1337 fastify.listen({
}); port: process.env.PORT || 1337,
});
const watch = spawn("pnpm", ["rspack", "-w"], { detached: true, cwd: process.cwd() });
const watch = spawn("pnpm", ["rspack", "-w"], {
watch.stdout.on("data", (data) => { detached: true,
console.log(`${data}`); cwd: process.cwd(),
}); });
watch.stderr.on("data", (data) => { watch.stdout.on("data", (data) => {
console.log(`${data}`); console.log(`${data}`);
}); });
watch.stderr.on("data", (data) => {
console.log(`${data}`);
});

View file

@ -1,9 +1,9 @@
import { encodeUrl } from "./shared"; import { encodeUrl } from "./shared";
navigator.sendBeacon = new Proxy(navigator.sendBeacon, { navigator.sendBeacon = new Proxy(navigator.sendBeacon, {
apply(target, thisArg, argArray) { apply(target, thisArg, argArray) {
argArray[0] = encodeUrl(argArray[0]); argArray[0] = encodeUrl(argArray[0]);
return Reflect.apply(target, thisArg, argArray); return Reflect.apply(target, thisArg, argArray);
}, },
}); });

View file

@ -1,12 +1,26 @@
import { rewriteCss } from "./shared"; import { rewriteCss } from "./shared";
const cssProperties = ["background", "background-image", "mask", "mask-image", "list-style", "list-style-image", "border-image", "border-image-source", "cursor"]; 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"]; // const jsProperties = ["background", "backgroundImage", "mask", "maskImage", "listStyle", "listStyleImage", "borderImage", "borderImageSource", "cursor"];
CSSStyleDeclaration.prototype.setProperty = new Proxy(CSSStyleDeclaration.prototype.setProperty, { CSSStyleDeclaration.prototype.setProperty = new Proxy(
CSSStyleDeclaration.prototype.setProperty,
{
apply(target, thisArg, argArray) { apply(target, thisArg, argArray) {
if (cssProperties.includes(argArray[0])) argArray[1] = rewriteCss(argArray[1]); if (cssProperties.includes(argArray[0]))
argArray[1] = rewriteCss(argArray[1]);
return Reflect.apply(target, thisArg, argArray); return Reflect.apply(target, thisArg, argArray);
}, },
}); },
);

View file

@ -1,115 +1,133 @@
import { decodeUrl } from "../shared/rewriters/url"; import { decodeUrl } from "../shared/rewriters/url";
import { encodeUrl, rewriteCss, rewriteHtml, rewriteJs, rewriteSrcset } from "./shared"; import {
encodeUrl,
rewriteCss,
rewriteHtml,
rewriteJs,
rewriteSrcset,
} from "./shared";
const attrObject = { const attrObject = {
"nonce": [HTMLElement], nonce: [HTMLElement],
"integrity": [HTMLScriptElement, HTMLLinkElement], integrity: [HTMLScriptElement, HTMLLinkElement],
"csp": [HTMLIFrameElement], csp: [HTMLIFrameElement],
"src": [HTMLImageElement, HTMLMediaElement, HTMLIFrameElement, HTMLEmbedElement, HTMLScriptElement], src: [
"href": [HTMLAnchorElement, HTMLLinkElement], HTMLImageElement,
"data": [HTMLObjectElement], HTMLMediaElement,
"action": [HTMLFormElement], HTMLIFrameElement,
"formaction": [HTMLButtonElement, HTMLInputElement], HTMLEmbedElement,
"srcdoc": [HTMLIFrameElement], HTMLScriptElement,
"srcset": [HTMLImageElement, HTMLSourceElement], ],
"imagesrcset": [HTMLLinkElement] href: [HTMLAnchorElement, HTMLLinkElement],
} data: [HTMLObjectElement],
action: [HTMLFormElement],
formaction: [HTMLButtonElement, HTMLInputElement],
srcdoc: [HTMLIFrameElement],
srcset: [HTMLImageElement, HTMLSourceElement],
imagesrcset: [HTMLLinkElement],
};
const attrs = Object.keys(attrObject); const attrs = Object.keys(attrObject);
for (const attr of attrs) { for (const attr of attrs) {
for (const element of attrObject[attr]) { for (const element of attrObject[attr]) {
const descriptor = Object.getOwnPropertyDescriptor(element.prototype, attr); const descriptor = Object.getOwnPropertyDescriptor(element.prototype, attr);
Object.defineProperty(element.prototype, attr, { Object.defineProperty(element.prototype, attr, {
get() { get() {
if (/src|href|data|action|formaction/.test(attr)) { if (/src|href|data|action|formaction/.test(attr)) {
return decodeUrl(descriptor.get.call(this)); return decodeUrl(descriptor.get.call(this));
} }
if (this.__origattrs[attr]) { if (this.__origattrs[attr]) {
return this.__origattrs[attr]; return this.__origattrs[attr];
} }
return descriptor.get.call(this); return descriptor.get.call(this);
}, },
set(value) { set(value) {
this.__origattrs[attr] = value; this.__origattrs[attr] = value;
if (/nonce|integrity|csp/.test(attr)) { if (/nonce|integrity|csp/.test(attr)) {
return; return;
} else if (/src|href|data|action|formaction/.test(attr)) { } else if (/src|href|data|action|formaction/.test(attr)) {
// @ts-expect-error // @ts-expect-error
if (value instanceof TrustedScriptURL) { if (value instanceof TrustedScriptURL) {
return; return;
} }
value = encodeUrl(value); value = encodeUrl(value);
} else if (attr === "srcdoc") { } else if (attr === "srcdoc") {
value = rewriteHtml(value); value = rewriteHtml(value);
} else if (/(image)?srcset/.test(attr)) { } else if (/(image)?srcset/.test(attr)) {
value = rewriteSrcset(value); value = rewriteSrcset(value);
} }
descriptor.set.call(this, value); descriptor.set.call(this, value);
}, },
}); });
} }
} }
declare global { declare global {
interface Element { interface Element {
__origattrs: Record<string, string>; __origattrs: Record<string, string>;
} }
} }
Element.prototype.__origattrs = {}; Element.prototype.__origattrs = {};
Element.prototype.getAttribute = new Proxy(Element.prototype.getAttribute, { Element.prototype.getAttribute = new Proxy(Element.prototype.getAttribute, {
apply(target, thisArg, argArray) { apply(target, thisArg, argArray) {
if (attrs.includes(argArray[0]) && thisArg.__origattrs[argArray[0]]) { if (attrs.includes(argArray[0]) && thisArg.__origattrs[argArray[0]]) {
return thisArg.__origattrs[argArray[0]]; return thisArg.__origattrs[argArray[0]];
} }
return Reflect.apply(target, thisArg, argArray); return Reflect.apply(target, thisArg, argArray);
}, },
}); });
Element.prototype.setAttribute = new Proxy(Element.prototype.setAttribute, { Element.prototype.setAttribute = new Proxy(Element.prototype.setAttribute, {
apply(target, thisArg, argArray) { apply(target, thisArg, argArray) {
if (attrs.includes(argArray[0])) { if (attrs.includes(argArray[0])) {
thisArg.__origattrs[argArray[0]] = argArray[1]; thisArg.__origattrs[argArray[0]] = argArray[1];
if (/nonce|integrity|csp/.test(argArray[0])) { if (/nonce|integrity|csp/.test(argArray[0])) {
return; return;
} else if (/src|href|data|action|formaction/.test(argArray[0])) { } else if (/src|href|data|action|formaction/.test(argArray[0])) {
argArray[1] = encodeUrl(argArray[1]); argArray[1] = encodeUrl(argArray[1]);
} else if (argArray[0] === "srcdoc") { } else if (argArray[0] === "srcdoc") {
argArray[1] = rewriteHtml(argArray[1]); argArray[1] = rewriteHtml(argArray[1]);
} else if (/(image)?srcset/.test(argArray[0])) { } else if (/(image)?srcset/.test(argArray[0])) {
argArray[1] = rewriteSrcset(argArray[1]); argArray[1] = rewriteSrcset(argArray[1]);
} else if (argArray[1] === "style") { } else if (argArray[1] === "style") {
argArray[1] = rewriteCss(argArray[1]); argArray[1] = rewriteCss(argArray[1]);
} }
} }
return Reflect.apply(target, thisArg, argArray); return Reflect.apply(target, thisArg, argArray);
}, },
}); });
const innerHTML = Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML"); const innerHTML = Object.getOwnPropertyDescriptor(
Element.prototype,
"innerHTML",
);
Object.defineProperty(Element.prototype, "innerHTML", { Object.defineProperty(Element.prototype, "innerHTML", {
set(value) { set(value) {
// @ts-expect-error // @ts-expect-error
if (this instanceof HTMLScriptElement && !(value instanceof TrustedScript)) { if (
value = rewriteJs(value); this instanceof HTMLScriptElement &&
} else if (this instanceof HTMLStyleElement) { !(value instanceof TrustedScript)
value = rewriteCss(value); ) {
// @ts-expect-error value = rewriteJs(value);
} else if (!(value instanceof TrustedHTML)) { } else if (this instanceof HTMLStyleElement) {
value = rewriteHtml(value); value = rewriteCss(value);
} // @ts-expect-error
} else if (!(value instanceof TrustedHTML)) {
value = rewriteHtml(value);
}
return innerHTML.set.call(this, value); return innerHTML.set.call(this, value);
}, },
}) });

View file

@ -4,7 +4,7 @@
// window.addEventListener = new Proxy(window.addEventListener, { // window.addEventListener = new Proxy(window.addEventListener, {
// apply (target, thisArg, argArray) { // apply (target, thisArg, argArray) {
// // // //
// return Reflect.apply(target, thisArg, argArray); // return Reflect.apply(target, thisArg, argArray);
// } // }
// }) // })

View file

@ -1,18 +1,17 @@
import { decodeUrl } from "./shared"; import { decodeUrl } from "./shared";
window.history.pushState = new Proxy(window.history.pushState, { window.history.pushState = new Proxy(window.history.pushState, {
apply(target, thisArg, argArray) { apply(target, thisArg, argArray) {
argArray[3] = decodeUrl(argArray[3]); argArray[3] = decodeUrl(argArray[3]);
return Reflect.apply(target, thisArg, argArray); return Reflect.apply(target, thisArg, argArray);
}, },
}); });
window.history.replaceState = new Proxy(window.history.replaceState, { window.history.replaceState = new Proxy(window.history.replaceState, {
apply(target, thisArg, argArray) { apply(target, thisArg, argArray) {
argArray[3] = decodeUrl(argArray[3]); argArray[3] = decodeUrl(argArray[3]);
return Reflect.apply(target, thisArg, argArray); return Reflect.apply(target, thisArg, argArray);
}, },
}); });

View file

@ -6,16 +6,16 @@ import "./location.ts";
import "./trustedTypes.ts"; import "./trustedTypes.ts";
import "./requests/fetch.ts"; import "./requests/fetch.ts";
import "./requests/xmlhttprequest.ts"; import "./requests/xmlhttprequest.ts";
import "./requests/websocket.ts" import "./requests/websocket.ts";
import "./element.ts"; import "./element.ts";
import "./storage.ts"; import "./storage.ts";
import "./css.ts"; import "./css.ts";
import "./history.ts" import "./history.ts";
import "./worker.ts"; import "./worker.ts";
import "./url.ts"; import "./url.ts";
declare global { declare global {
interface Window { interface Window {
$s: any; $s: any;
} }
} }

View file

@ -2,32 +2,31 @@
import { encodeUrl, decodeUrl } from "./shared"; import { encodeUrl, decodeUrl } from "./shared";
function createLocation() { function createLocation() {
const loc = new URL(decodeUrl(location.href)); const loc = new URL(decodeUrl(location.href));
loc.assign = (url: string) => location.assign(encodeUrl(url)); loc.assign = (url: string) => location.assign(encodeUrl(url));
loc.reload = () => location.reload(); loc.reload = () => location.reload();
loc.replace = (url: string) => location.replace(encodeUrl(url)); loc.replace = (url: string) => location.replace(encodeUrl(url));
loc.toString = () => loc.href; loc.toString = () => loc.href;
return loc; return loc;
} }
export const locationProxy = new Proxy(window.location, { export const locationProxy = new Proxy(window.location, {
get(target, prop) { get(target, prop) {
const loc = createLocation(); const loc = createLocation();
return loc[prop]; return loc[prop];
}, },
set(obj, prop, value) { set(obj, prop, value) {
const loc = createLocation(); const loc = createLocation();
if (prop === "href") {
location.href = encodeUrl(value);
} else {
loc[prop] = value;
}
return true; if (prop === "href") {
location.href = encodeUrl(value);
} else {
loc[prop] = value;
} }
})
return true;
},
});

View file

@ -1,28 +1,34 @@
import { rewriteJs } from "../shared"; import { rewriteJs } from "../shared";
const FunctionProxy = new Proxy(Function, { const FunctionProxy = new Proxy(Function, {
construct(target, argArray) { construct(target, argArray) {
if (argArray.length === 1) { if (argArray.length === 1) {
return Reflect.construct(target, rewriteJs(argArray[0])); return Reflect.construct(target, rewriteJs(argArray[0]));
} else { } else {
return Reflect.construct(target, rewriteJs(argArray[argArray.length - 1])) return Reflect.construct(
} target,
}, rewriteJs(argArray[argArray.length - 1]),
apply(target, thisArg, argArray) { );
if (argArray.length === 1) { }
return Reflect.apply(target, undefined, [rewriteJs(argArray[0])]); },
} else { apply(target, thisArg, argArray) {
return Reflect.apply(target, undefined, [...argArray.map((x, index) => index === argArray.length - 1), rewriteJs(argArray[argArray.length - 1])]) if (argArray.length === 1) {
} return Reflect.apply(target, undefined, [rewriteJs(argArray[0])]);
}, } else {
return Reflect.apply(target, undefined, [
...argArray.map((x, index) => index === argArray.length - 1),
rewriteJs(argArray[argArray.length - 1]),
]);
}
},
}); });
delete window.Function; delete window.Function;
window.Function = FunctionProxy; window.Function = FunctionProxy;
window.eval = new Proxy(window.eval, { window.eval = new Proxy(window.eval, {
apply(target, thisArg, argArray) { apply(target, thisArg, argArray) {
return Reflect.apply(target, thisArg, [rewriteJs(argArray[0])]); return Reflect.apply(target, thisArg, [rewriteJs(argArray[0])]);
}, },
}) });

View file

@ -3,33 +3,33 @@
import { encodeUrl, rewriteHeaders } from "../shared"; import { encodeUrl, rewriteHeaders } from "../shared";
window.fetch = new Proxy(window.fetch, { window.fetch = new Proxy(window.fetch, {
apply(target, thisArg, argArray) { apply(target, thisArg, argArray) {
argArray[0] = encodeUrl(argArray[0]); argArray[0] = encodeUrl(argArray[0]);
return Reflect.apply(target, thisArg, argArray); return Reflect.apply(target, thisArg, argArray);
}, },
}); });
Headers = new Proxy(Headers, { Headers = new Proxy(Headers, {
construct(target, argArray, newTarget) { construct(target, argArray, newTarget) {
argArray[0] = rewriteHeaders(argArray[0]); argArray[0] = rewriteHeaders(argArray[0]);
return Reflect.construct(target, argArray, newTarget); return Reflect.construct(target, argArray, newTarget);
}, },
}) });
Request = new Proxy(Request, { Request = new Proxy(Request, {
construct(target, argArray, newTarget) { construct(target, argArray, newTarget) {
if (typeof argArray[0] === "string") argArray[0] = encodeUrl(argArray[0]); if (typeof argArray[0] === "string") argArray[0] = encodeUrl(argArray[0]);
return Reflect.construct(target, argArray, newTarget); return Reflect.construct(target, argArray, newTarget);
}, },
}); });
Response.redirect = new Proxy(Response.redirect, { Response.redirect = new Proxy(Response.redirect, {
apply(target, thisArg, argArray) { apply(target, thisArg, argArray) {
argArray[0] = encodeUrl(argArray[0]); argArray[0] = encodeUrl(argArray[0]);
return Reflect.apply(target, thisArg, argArray); return Reflect.apply(target, thisArg, argArray);
}, },
}); });

View file

@ -2,15 +2,15 @@ import { BareClient } from "../shared";
const client = new BareClient(); const client = new BareClient();
WebSocket = new Proxy(WebSocket, { WebSocket = new Proxy(WebSocket, {
construct(target, args) { construct(target, args) {
return client.createWebSocket( return client.createWebSocket(
args[0], args[0],
args[1], args[1],
target, target,
{ {
"User-Agent": navigator.userAgent "User-Agent": navigator.userAgent,
}, },
ArrayBuffer.prototype ArrayBuffer.prototype,
) );
} },
}) });

View file

@ -1,20 +1,23 @@
import { encodeUrl, rewriteHeaders } from "../shared"; import { encodeUrl, rewriteHeaders } from "../shared";
XMLHttpRequest.prototype.open = new Proxy(XMLHttpRequest.prototype.open, { XMLHttpRequest.prototype.open = new Proxy(XMLHttpRequest.prototype.open, {
apply(target, thisArg, argArray) { apply(target, thisArg, argArray) {
if (argArray[1]) argArray[1] = encodeUrl(argArray[1]); if (argArray[1]) argArray[1] = encodeUrl(argArray[1]);
return Reflect.apply(target, thisArg, argArray); return Reflect.apply(target, thisArg, argArray);
}, },
}); });
XMLHttpRequest.prototype.setRequestHeader = new Proxy(XMLHttpRequest.prototype.setRequestHeader, { XMLHttpRequest.prototype.setRequestHeader = new Proxy(
XMLHttpRequest.prototype.setRequestHeader,
{
apply(target, thisArg, argArray) { apply(target, thisArg, argArray) {
let headerObject = Object.fromEntries([argArray]); let headerObject = Object.fromEntries([argArray]);
headerObject = rewriteHeaders(headerObject); headerObject = rewriteHeaders(headerObject);
argArray = Object.entries(headerObject)[0]; argArray = Object.entries(headerObject)[0];
return Reflect.apply(target, thisArg, argArray); return Reflect.apply(target, thisArg, argArray);
}, },
}); },
);

View file

@ -2,13 +2,13 @@ import { locationProxy } from "./location";
import { windowProxy } from "./window"; import { windowProxy } from "./window";
function scope(identifier: any) { function scope(identifier: any) {
if (identifier instanceof Window) { if (identifier instanceof Window) {
return windowProxy; return windowProxy;
} else if (identifier instanceof Location) { } else if (identifier instanceof Location) {
return locationProxy; return locationProxy;
} }
return identifier; return identifier;
} }
// shorthand because this can get out of hand reall quickly // shorthand because this can get out of hand reall quickly

View file

@ -1,18 +1,12 @@
export const { export const {
util: { util: { isScramjetFile, BareClient },
isScramjetFile, url: { encodeUrl, decodeUrl },
BareClient rewrite: {
}, rewriteCss,
url: { rewriteHtml,
encodeUrl, rewriteSrcset,
decodeUrl, rewriteJs,
}, rewriteHeaders,
rewrite: { rewriteWorkers,
rewriteCss, },
rewriteHtml, } = self.$scramjet.shared;
rewriteSrcset,
rewriteJs,
rewriteHeaders,
rewriteWorkers,
}
} = self.$scramjet.shared;

View file

@ -2,63 +2,62 @@ import IDBMapSync from "@webreflection/idb-map/sync";
import { locationProxy } from "./location"; import { locationProxy } from "./location";
const store = new IDBMapSync(locationProxy.host, { const store = new IDBMapSync(locationProxy.host, {
prefix: "Storage", prefix: "Storage",
durability: "relaxed" durability: "relaxed",
}); });
await store.sync(); await store.sync();
function storageProxy(scope: Storage): Storage { function storageProxy(scope: Storage): Storage {
return new Proxy(scope, {
get(target, prop) {
switch (prop) {
case "getItem":
return (key: string) => {
return store.get(key);
};
return new Proxy(scope, { case "setItem":
get(target, prop) { return (key: string, value: string) => {
switch (prop) { store.set(key, value);
case "getItem": store.sync();
return (key: string) => { };
return store.get(key);
}
case "setItem": case "removeItem":
return (key: string, value: string) => { return (key: string) => {
store.set(key, value); store.delete(key);
store.sync(); store.sync();
} };
case "removeItem": case "clear":
return (key: string) => { return () => {
store.delete(key); store.clear();
store.sync(); store.sync();
} };
case "clear": case "key":
return () => { return (index: number) => {
store.clear(); store.keys()[index];
store.sync(); };
} case "length":
return store.size;
default:
return store.get(prop);
}
},
case "key": //@ts-ignore
return (index: number) => { set(target, prop, value) {
store.keys()[index]; store.set(prop, value);
} store.sync();
case "length": },
return store.size;
default:
return store.get(prop);
}
},
//@ts-ignore defineProperty(target, property, attributes) {
set(target, prop, value) { store.set(property as string, attributes.value);
store.set(prop, value);
store.sync();
},
defineProperty(target, property, attributes) { return true;
store.set(property as string, attributes.value); },
});
return true;
},
})
} }
const localStorageProxy = storageProxy(window.localStorage); const localStorageProxy = storageProxy(window.localStorage);

View file

@ -2,31 +2,31 @@ import { rewriteHtml, rewriteJs, encodeUrl } from "./shared";
// @ts-expect-error // @ts-expect-error
trustedTypes.createPolicy = new Proxy(trustedTypes.createPolicy, { trustedTypes.createPolicy = new Proxy(trustedTypes.createPolicy, {
apply(target, thisArg, argArray) { apply(target, thisArg, argArray) {
if (argArray[1].createHTML) { if (argArray[1].createHTML) {
argArray[1].createHTML = new Proxy(argArray[1].createHTML, { argArray[1].createHTML = new Proxy(argArray[1].createHTML, {
apply(target1, thisArg1, argArray1) { apply(target1, thisArg1, argArray1) {
return rewriteHtml(target1(...argArray1)); return rewriteHtml(target1(...argArray1));
}, },
}); });
} }
if (argArray[1].createScript) { if (argArray[1].createScript) {
argArray[1].createScript = new Proxy(argArray[1].createScript, { argArray[1].createScript = new Proxy(argArray[1].createScript, {
apply(target1, thisArg1, argArray1) { apply(target1, thisArg1, argArray1) {
return rewriteJs(target1(...argArray1)); return rewriteJs(target1(...argArray1));
}, },
}); });
} }
if (argArray[1].createScriptURL) { if (argArray[1].createScriptURL) {
argArray[1].createScriptURL = new Proxy(argArray[1].createScriptURL, { argArray[1].createScriptURL = new Proxy(argArray[1].createScriptURL, {
apply(target1, thisArg1, argArray1) { apply(target1, thisArg1, argArray1) {
return encodeUrl(target1(...argArray1)); return encodeUrl(target1(...argArray1));
}, },
}) });
} }
return Reflect.apply(target, thisArg, argArray); return Reflect.apply(target, thisArg, argArray);
}, },
}) });

View file

@ -2,12 +2,12 @@ import { encodeUrl } from "../shared/rewriters/url";
export const URL = globalThis.URL; export const URL = globalThis.URL;
if (globalThis.window) { if (globalThis.window) {
window.URL = new Proxy(URL, { window.URL = new Proxy(URL, {
construct(target, argArray, newTarget) { construct(target, argArray, newTarget) {
if (typeof argArray[0] === "string") argArray[0] = encodeUrl(argArray[0]); if (typeof argArray[0] === "string") argArray[0] = encodeUrl(argArray[0]);
if (typeof argArray[1] === "string") argArray[1] = encodeUrl(argArray[1]); if (typeof argArray[1] === "string") argArray[1] = encodeUrl(argArray[1]);
return Reflect.construct(target, argArray, newTarget); return Reflect.construct(target, argArray, newTarget);
}, },
}) });
} }

View file

@ -1,39 +1,47 @@
import { locationProxy } from "./location"; import { locationProxy } from "./location";
export const windowProxy = new Proxy(window, { export const windowProxy = new Proxy(window, {
get(target, prop) { get(target, prop) {
const propIsString = typeof prop === "string"; const propIsString = typeof prop === "string";
if (propIsString && prop === "location") { if (propIsString && prop === "location") {
return locationProxy; return locationProxy;
} else if (propIsString && ["window", "top", "parent", "self", "globalThis"].includes(prop)) { } else if (
return windowProxy; propIsString &&
} else if (propIsString && prop === "$scramjet") { ["window", "top", "parent", "self", "globalThis"].includes(prop)
return; ) {
} else if (propIsString && prop === "addEventListener") { return windowProxy;
console.log("addEventListener getteetetetetet") } else if (propIsString && prop === "$scramjet") {
return;
} else if (propIsString && prop === "addEventListener") {
console.log("addEventListener getteetetetetet");
return new Proxy(window.addEventListener, { return new Proxy(window.addEventListener, {
apply(target1, thisArg, argArray) { apply(target1, thisArg, argArray) {
window.addEventListener(argArray[0], argArray[1]); window.addEventListener(argArray[0], argArray[1]);
}, },
}) });
} }
const value = Reflect.get(target, prop); const value = Reflect.get(target, prop);
if (typeof value === "function") { if (typeof value === "function") {
return value.bind(target); return value.bind(target);
} }
return value; return value;
}, },
set(target, prop, newValue) { set(target, prop, newValue) {
// ensures that no apis are overwritten // ensures that no apis are overwritten
if (typeof prop === "string" && ["window", "top", "parent", "self", "globalThis", "location"].includes(prop)) { if (
return false; typeof prop === "string" &&
} ["window", "top", "parent", "self", "globalThis", "location"].includes(
prop,
)
) {
return false;
}
return Reflect.set(target, prop, newValue); return Reflect.set(target, prop, newValue);
}, },
}); });

View file

@ -1,25 +1,24 @@
import { encodeUrl } from "./shared"; import { encodeUrl } from "./shared";
Worker = new Proxy(Worker, { Worker = new Proxy(Worker, {
construct(target, argArray) { construct(target, argArray) {
argArray[0] = encodeUrl(argArray[0]); argArray[0] = encodeUrl(argArray[0]);
// target is a reference to the object that you are proxying // target is a reference to the object that you are proxying
// Reflect.construct is just a wrapper for calling target // Reflect.construct is just a wrapper for calling target
// you could do new target(...argArray) and it would work the same effectively // you could do new target(...argArray) and it would work the same effectively
return Reflect.construct(target, argArray);
}
})
Worklet.prototype.addModule = new Proxy(Worklet.prototype.addModule, { return Reflect.construct(target, argArray);
apply(target, thisArg, argArray) { },
argArray[0] = encodeUrl(argArray[0])
return Reflect.apply(target, thisArg, argArray);
},
}); });
Worklet.prototype.addModule = new Proxy(Worklet.prototype.addModule, {
apply(target, thisArg, argArray) {
argArray[0] = encodeUrl(argArray[0]);
return Reflect.apply(target, thisArg, argArray);
},
});
// broken // broken
@ -31,4 +30,4 @@ Worklet.prototype.addModule = new Proxy(Worklet.prototype.addModule, {
// return Reflect.apply(target, thisArg, argArray); // return Reflect.apply(target, thisArg, argArray);
// }, // },
// }); // });

View file

@ -9,14 +9,14 @@ function enc_utf8(s) {
} catch (e) { } catch (e) {
throw "Error on UTF-8 encode"; throw "Error on UTF-8 encode";
} }
}; }
function dec_utf8(s) { function dec_utf8(s) {
try { try {
return decodeURIComponent(escape(s)); return decodeURIComponent(escape(s));
} catch (e) { } catch (e) {
throw "Bad Key"; throw "Bad Key";
} }
}; }
function padBlock(byteArr) { function padBlock(byteArr) {
var array = [], var array = [],
cpad, cpad,
@ -46,7 +46,7 @@ function padBlock(byteArr) {
array[i] = byteArr[i]; array[i] = byteArr[i];
} }
return array; return array;
}; }
function block2s(block, lastBlock) { function block2s(block, lastBlock) {
var string = "", var string = "",
padding, padding,
@ -68,7 +68,7 @@ function block2s(block, lastBlock) {
} }
} }
return string; return string;
}; }
function a2h(numArr) { function a2h(numArr) {
var string = "", var string = "",
i; i;
@ -76,14 +76,14 @@ function a2h(numArr) {
string += (numArr[i] < 16 ? "0" : "") + numArr[i].toString(16); string += (numArr[i] < 16 ? "0" : "") + numArr[i].toString(16);
} }
return string; return string;
}; }
function h2a(s) { function h2a(s) {
var ret = []; var ret = [];
s.replace(/(..)/g, function (s) { s.replace(/(..)/g, function (s) {
ret.push(parseInt(s, 16)); ret.push(parseInt(s, 16));
}); });
return ret; return ret;
}; }
function s2a(string, binary) { function s2a(string, binary) {
var array = [], var array = [],
i; i;
@ -97,7 +97,7 @@ function s2a(string, binary) {
} }
return array; return array;
}; }
function size(newsize) { function size(newsize) {
switch (newsize) { switch (newsize) {
case 128: case 128:
@ -115,7 +115,7 @@ function size(newsize) {
default: default:
throw "Invalid Key Size Specified:" + newsize; throw "Invalid Key Size Specified:" + newsize;
} }
}; }
function randArr(num) { function randArr(num) {
var result = [], var result = [],
i; i;
@ -123,7 +123,7 @@ function randArr(num) {
result = result.concat(Math.floor(Math.random() * 256)); result = result.concat(Math.floor(Math.random() * 256));
} }
return result; return result;
}; }
function openSSLKey(passwordArr, saltArr) { function openSSLKey(passwordArr, saltArr) {
var rounds = Nr >= 12 ? 3 : 2, var rounds = Nr >= 12 ? 3 : 2,
key = [], key = [],
@ -144,7 +144,7 @@ function openSSLKey(passwordArr, saltArr) {
key: key, key: key,
iv: iv, iv: iv,
}; };
}; }
function rawEncrypt(plaintext, key, iv) { function rawEncrypt(plaintext, key, iv) {
key = expandKey(key); key = expandKey(key);
var numBlocks = Math.ceil(plaintext.length / 16), var numBlocks = Math.ceil(plaintext.length / 16),
@ -168,7 +168,7 @@ function rawEncrypt(plaintext, key, iv) {
cipherBlocks[i] = encryptBlock(blocks[i], key); cipherBlocks[i] = encryptBlock(blocks[i], key);
} }
return cipherBlocks; return cipherBlocks;
}; }
function rawDecrypt(cryptArr, key, iv, binary) { function rawDecrypt(cryptArr, key, iv, binary) {
key = expandKey(key); key = expandKey(key);
var numBlocks = cryptArr.length / 16, var numBlocks = cryptArr.length / 16,
@ -191,7 +191,7 @@ function rawDecrypt(cryptArr, key, iv, binary) {
} }
string += block2s(plainBlocks[i], true); string += block2s(plainBlocks[i], true);
return binary ? string : dec_utf8(string); return binary ? string : dec_utf8(string);
}; }
function encryptBlock(block, words) { function encryptBlock(block, words) {
Decrypt = false; Decrypt = false;
var state = addRoundKey(block, words, 0), var state = addRoundKey(block, words, 0),
@ -206,7 +206,7 @@ function encryptBlock(block, words) {
} }
return state; return state;
}; }
function decryptBlock(block, words) { function decryptBlock(block, words) {
Decrypt = true; Decrypt = true;
var state = addRoundKey(block, words, Nr), var state = addRoundKey(block, words, Nr),
@ -221,7 +221,7 @@ function decryptBlock(block, words) {
} }
return state; return state;
}; }
function subBytes(state) { function subBytes(state) {
var S = Decrypt ? SBoxInv : SBox, var S = Decrypt ? SBoxInv : SBox,
temp = [], temp = [],
@ -230,7 +230,7 @@ function subBytes(state) {
temp[i] = S[state[i]]; temp[i] = S[state[i]];
} }
return temp; return temp;
}; }
function shiftRows(state) { function shiftRows(state) {
var temp = [], var temp = [],
shiftBy = Decrypt shiftBy = Decrypt
@ -241,7 +241,7 @@ function shiftRows(state) {
temp[i] = state[shiftBy[i]]; temp[i] = state[shiftBy[i]];
} }
return temp; return temp;
}; }
function mixColumns(state) { function mixColumns(state) {
var t = [], var t = [],
c; c;
@ -294,7 +294,7 @@ function mixColumns(state) {
} }
return t; return t;
}; }
function addRoundKey(state, words, round) { function addRoundKey(state, words, round) {
var temp = [], var temp = [],
i; i;
@ -302,7 +302,7 @@ function addRoundKey(state, words, round) {
temp[i] = state[i] ^ words[round][i]; temp[i] = state[i] ^ words[round][i];
} }
return temp; return temp;
}; }
function xorBlocks(block1, block2) { function xorBlocks(block1, block2) {
var temp = [], var temp = [],
i; i;
@ -310,7 +310,7 @@ function xorBlocks(block1, block2) {
temp[i] = block1[i] ^ block2[i]; temp[i] = block1[i] ^ block2[i];
} }
return temp; return temp;
}; }
function expandKey(key) { function expandKey(key) {
var w = [], var w = [],
temp = [], temp = [],
@ -347,18 +347,18 @@ function expandKey(key) {
w[i * 4 + j][0], w[i * 4 + j][0],
w[i * 4 + j][1], w[i * 4 + j][1],
w[i * 4 + j][2], w[i * 4 + j][2],
w[i * 4 + j][3] w[i * 4 + j][3],
); );
} }
} }
return flat; return flat;
}; }
function subWord(w) { function subWord(w) {
for (var i = 0; i < 4; i++) { for (var i = 0; i < 4; i++) {
w[i] = SBox[w[i]]; w[i] = SBox[w[i]];
} }
return w; return w;
}; }
function rotWord(w) { function rotWord(w) {
var tmp = w[0], var tmp = w[0],
i; i;
@ -367,7 +367,7 @@ function rotWord(w) {
} }
w[3] = tmp; w[3] = tmp;
return w; return w;
}; }
function strhex(str, size) { function strhex(str, size) {
var i, var i,
ret = []; ret = [];
@ -375,7 +375,7 @@ function strhex(str, size) {
ret[i / size] = parseInt(str.substr(i, size), 16); ret[i / size] = parseInt(str.substr(i, size), 16);
} }
return ret; return ret;
}; }
function invertArr(arr) { function invertArr(arr) {
var i, var i,
ret = []; ret = [];
@ -383,7 +383,7 @@ function invertArr(arr) {
ret[arr[i]] = i; ret[arr[i]] = i;
} }
return ret; return ret;
}; }
function Gxx(a, b) { function Gxx(a, b) {
var i, ret; var i, ret;
@ -395,7 +395,7 @@ function Gxx(a, b) {
} }
return ret; return ret;
}; }
function Gx(x) { function Gx(x) {
var i, var i,
r = []; r = [];
@ -403,15 +403,15 @@ function Gx(x) {
r[i] = Gxx(x, i); r[i] = Gxx(x, i);
} }
return r; return r;
}; }
var SBox = strhex( var SBox = strhex(
"637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b27509832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cfd0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdbe0323a0a4906245cc2d3ac629195e479e7c8376d8dd54ea96c56f4ea657aae08ba78252e1ca6b4c6e8dd741f4bbd8b8a703eb5664803f60e613557b986c11d9ee1f8981169d98e949b1e87e9ce5528df8ca1890dbfe6426841992d0fb054bb16", "637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b27509832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cfd0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdbe0323a0a4906245cc2d3ac629195e479e7c8376d8dd54ea96c56f4ea657aae08ba78252e1ca6b4c6e8dd741f4bbd8b8a703eb5664803f60e613557b986c11d9ee1f8981169d98e949b1e87e9ce5528df8ca1890dbfe6426841992d0fb054bb16",
2 2,
); );
var SBoxInv = invertArr(SBox); var SBoxInv = invertArr(SBox);
var Rcon = strhex( var Rcon = strhex(
"01020408102040801b366cd8ab4d9a2f5ebc63c697356ad4b37dfaefc591", "01020408102040801b366cd8ab4d9a2f5ebc63c697356ad4b37dfaefc591",
2 2,
); );
var G2X = Gx(2); var G2X = Gx(2);
var G3X = Gx(3); var G3X = Gx(3);
@ -431,7 +431,7 @@ function enc(string, pass, binary) {
cipherBlocks = saltBlock.concat(cipherBlocks); cipherBlocks = saltBlock.concat(cipherBlocks);
return Base64.encode(cipherBlocks); return Base64.encode(cipherBlocks);
}; }
function dec(string, pass, binary) { function dec(string, pass, binary) {
var cryptArr = Base64.decode(string), var cryptArr = Base64.decode(string),
salt = cryptArr.slice(8, 16), salt = cryptArr.slice(8, 16),
@ -442,7 +442,7 @@ function dec(string, pass, binary) {
string = rawDecrypt(cryptArr, key, iv, binary); string = rawDecrypt(cryptArr, key, iv, binary);
return string; return string;
}; }
function MD5(numArr) { function MD5(numArr) {
function rotateLeft(lValue, iShiftBits) { function rotateLeft(lValue, iShiftBits) {
return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits)); return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
@ -550,7 +550,7 @@ function MD5(numArr) {
d, d,
rnd = strhex( rnd = strhex(
"67452301efcdab8998badcfe10325476d76aa478e8c7b756242070dbc1bdceeef57c0faf4787c62aa8304613fd469501698098d88b44f7afffff5bb1895cd7be6b901122fd987193a679438e49b40821f61e2562c040b340265e5a51e9b6c7aad62f105d02441453d8a1e681e7d3fbc821e1cde6c33707d6f4d50d87455a14eda9e3e905fcefa3f8676f02d98d2a4c8afffa39428771f6816d9d6122fde5380ca4beea444bdecfa9f6bb4b60bebfbc70289b7ec6eaa127fad4ef308504881d05d9d4d039e6db99e51fa27cf8c4ac5665f4292244432aff97ab9423a7fc93a039655b59c38f0ccc92ffeff47d85845dd16fa87e4ffe2ce6e0a30143144e0811a1f7537e82bd3af2352ad7d2bbeb86d391", "67452301efcdab8998badcfe10325476d76aa478e8c7b756242070dbc1bdceeef57c0faf4787c62aa8304613fd469501698098d88b44f7afffff5bb1895cd7be6b901122fd987193a679438e49b40821f61e2562c040b340265e5a51e9b6c7aad62f105d02441453d8a1e681e7d3fbc821e1cde6c33707d6f4d50d87455a14eda9e3e905fcefa3f8676f02d98d2a4c8afffa39428771f6816d9d6122fde5380ca4beea444bdecfa9f6bb4b60bebfbc70289b7ec6eaa127fad4ef308504881d05d9d4d039e6db99e51fa27cf8c4ac5665f4292244432aff97ab9423a7fc93a039655b59c38f0ccc92ffeff47d85845dd16fa87e4ffe2ce6e0a30143144e0811a1f7537e82bd3af2352ad7d2bbeb86d391",
8 8,
); );
x = convertToWordArray(numArr); x = convertToWordArray(numArr);
@ -636,7 +636,7 @@ function MD5(numArr) {
} }
return wordToHex(a).concat(wordToHex(b), wordToHex(c), wordToHex(d)); return wordToHex(a).concat(wordToHex(b), wordToHex(c), wordToHex(d));
}; }
// function encString (plaintext, key, iv) { // function encString (plaintext, key, iv) {
// var i; // var i;
// plaintext = s2a(plaintext, false); // plaintext = s2a(plaintext, false);
@ -745,7 +745,7 @@ function MD5(numArr) {
const Base64 = { const Base64 = {
encode: function (b) { encode: function (b) {
var _chars = var _chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
chars = _chars.split(""), chars = _chars.split(""),
flatArr = [], flatArr = [],
b64 = "", b64 = "",
@ -780,7 +780,7 @@ const Base64 = {
decode: function (string) { decode: function (string) {
string = string.replace(/\n/g, ""); string = string.replace(/\n/g, "");
var _chars = var _chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
chars = _chars.split(""), chars = _chars.split(""),
flatArr = [], flatArr = [],
c = [], c = [],
@ -799,7 +799,7 @@ const Base64 = {
} }
flatArr = flatArr.slice(0, flatArr.length - (flatArr.length % 16)); flatArr = flatArr.slice(0, flatArr.length - (flatArr.length % 16));
return flatArr; return flatArr;
} },
} };
export { enc, dec }; export { enc, dec };

View file

@ -2,37 +2,47 @@ import { enc, dec } from "./aes";
// for some reason eslint was parsing the type inside of the function params as a variable // for some reason eslint was parsing the type inside of the function params as a variable
export interface Codec { export interface Codec {
// eslint-disable-next-line // eslint-disable-next-line
encode: (str: string | undefined) => string; encode: (str: string | undefined) => string;
// eslint-disable-next-line // eslint-disable-next-line
decode: (str: string | undefined) => string; decode: (str: string | undefined) => string;
} }
const xor = { const xor = {
encode: (str: string | undefined, key: number = 2) => { encode: (str: string | undefined, key: number = 2) => {
if (!str) return str; if (!str) return str;
return encodeURIComponent(str.split("").map((e, i) => i % key ? String.fromCharCode(e.charCodeAt(0) ^ key) : e).join("")); return encodeURIComponent(
}, str
decode: (str: string | undefined, key: number = 2) => { .split("")
if (!str) return str; .map((e, i) =>
i % key ? String.fromCharCode(e.charCodeAt(0) ^ key) : e,
)
.join(""),
);
},
decode: (str: string | undefined, key: number = 2) => {
if (!str) return str;
return decodeURIComponent(str).split("").map((e, i) => i % key ? String.fromCharCode(e.charCodeAt(0) ^ key) : e).join(""); return decodeURIComponent(str)
} .split("")
} .map((e, i) => (i % key ? String.fromCharCode(e.charCodeAt(0) ^ key) : e))
.join("");
},
};
const plain = { const plain = {
encode: (str: string | undefined) => { encode: (str: string | undefined) => {
if (!str) return str; if (!str) return str;
return encodeURIComponent(str); return encodeURIComponent(str);
}, },
decode: (str: string | undefined) => { decode: (str: string | undefined) => {
if (!str) return str; if (!str) return str;
return decodeURIComponent(str); return decodeURIComponent(str);
} },
} };
/* /*
const aes = { const aes = {
@ -50,27 +60,30 @@ const aes = {
*/ */
const none = { const none = {
encode: (str: string | undefined) => str, encode: (str: string | undefined) => str,
decode: (str: string | undefined) => str, decode: (str: string | undefined) => str,
} };
const base64 = { const base64 = {
encode: (str: string | undefined) => { encode: (str: string | undefined) => {
if (!str) return str; if (!str) return str;
return decodeURIComponent(btoa(str)); return decodeURIComponent(btoa(str));
}, },
decode: (str: string | undefined) => { decode: (str: string | undefined) => {
if (!str) return str; if (!str) return str;
return atob(str); return atob(str);
} },
} };
if (!self.$scramjet) { if (!self.$scramjet) {
//@ts-expect-error really dumb workaround //@ts-expect-error really dumb workaround
self.$scramjet = {} self.$scramjet = {};
} }
self.$scramjet.codecs = { self.$scramjet.codecs = {
none, plain, base64, xor none,
} plain,
base64,
xor,
};

View file

@ -1,13 +1,13 @@
if (!self.$scramjet) { if (!self.$scramjet) {
//@ts-expect-error really dumb workaround //@ts-expect-error really dumb workaround
self.$scramjet = {} self.$scramjet = {};
} }
self.$scramjet.config = { self.$scramjet.config = {
prefix: "/scramjet/", prefix: "/scramjet/",
codec: self.$scramjet.codecs.plain, codec: self.$scramjet.codecs.plain,
config: "/scram/scramjet.config.js", config: "/scram/scramjet.config.js",
shared: "/scram/scramjet.shared.js", shared: "/scram/scramjet.shared.js",
worker: "/scram/scramjet.worker.js", worker: "/scram/scramjet.worker.js",
client: "/scram/scramjet.client.js", client: "/scram/scramjet.client.js",
codecs: "/scram/scramjet.codecs.js" codecs: "/scram/scramjet.codecs.js",
} };

View file

@ -8,24 +8,24 @@ import { isScramjetFile } from "./rewriters/html";
import { BareClient } from "@mercuryworkshop/bare-mux"; import { BareClient } from "@mercuryworkshop/bare-mux";
if (!self.$scramjet) { if (!self.$scramjet) {
//@ts-expect-error really dumb workaround //@ts-expect-error really dumb workaround
self.$scramjet = {} self.$scramjet = {};
} }
self.$scramjet.shared = { self.$scramjet.shared = {
util: { util: {
isScramjetFile, isScramjetFile,
BareClient BareClient,
}, },
url: { url: {
encodeUrl, encodeUrl,
decodeUrl, decodeUrl,
}, },
rewrite: { rewrite: {
rewriteCss, rewriteCss,
rewriteHtml, rewriteHtml,
rewriteSrcset, rewriteSrcset,
rewriteJs, rewriteJs,
rewriteHeaders, rewriteHeaders,
rewriteWorkers, rewriteWorkers,
} },
} };

View file

@ -4,32 +4,31 @@
import { encodeUrl } from "./url"; import { encodeUrl } from "./url";
export function rewriteCss(css: string, origin?: URL) { export function rewriteCss(css: string, origin?: URL) {
const regex = const regex =
/(@import\s+(?!url\())?\s*url\(\s*(['"]?)([^'")]+)\2\s*\)|@import\s+(['"])([^'"]+)\4/g /(@import\s+(?!url\())?\s*url\(\s*(['"]?)([^'")]+)\2\s*\)|@import\s+(['"])([^'"]+)\4/g;
return css.replace( return css.replace(
regex, regex,
( (
match, match,
importStatement, importStatement,
urlQuote, urlQuote,
urlContent, urlContent,
importQuote, importQuote,
importContent importContent,
) => { ) => {
const url = urlContent || importContent const url = urlContent || importContent;
const encodedUrl = encodeUrl(url.trim(), origin) const encodedUrl = encodeUrl(url.trim(), origin);
if (importStatement) { if (importStatement) {
return `@import url(${urlQuote}${encodedUrl}${urlQuote})` return `@import url(${urlQuote}${encodedUrl}${urlQuote})`;
} }
if (importQuote) { if (importQuote) {
return `@import ${importQuote}${encodedUrl}${importQuote}` return `@import ${importQuote}${encodedUrl}${importQuote}`;
} }
return `url(${urlQuote}${encodedUrl}${urlQuote})`
}
)
return `url(${urlQuote}${encodedUrl}${urlQuote})`;
},
);
} }

View file

@ -1,52 +1,50 @@
import { encodeUrl } from "./url"; import { encodeUrl } from "./url";
import { BareHeaders } from "@mercuryworkshop/bare-mux"; import { BareHeaders } from "@mercuryworkshop/bare-mux";
const cspHeaders = [ const cspHeaders = [
"cross-origin-embedder-policy", "cross-origin-embedder-policy",
"cross-origin-opener-policy", "cross-origin-opener-policy",
"cross-origin-resource-policy", "cross-origin-resource-policy",
"content-security-policy", "content-security-policy",
"content-security-policy-report-only", "content-security-policy-report-only",
"expect-ct", "expect-ct",
"feature-policy", "feature-policy",
"origin-isolation", "origin-isolation",
"strict-transport-security", "strict-transport-security",
"upgrade-insecure-requests", "upgrade-insecure-requests",
"x-content-type-options", "x-content-type-options",
"x-download-options", "x-download-options",
"x-frame-options", "x-frame-options",
"x-permitted-cross-domain-policies", "x-permitted-cross-domain-policies",
"x-powered-by", "x-powered-by",
"x-xss-protection", "x-xss-protection",
// This needs to be emulated, but for right now it isn't that important of a feature to be worried about // 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 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data
"clear-site-data" "clear-site-data",
]; ];
const urlHeaders = [ const urlHeaders = ["location", "content-location", "referer"];
"location",
"content-location",
"referer"
];
export function rewriteHeaders(rawHeaders: BareHeaders, origin?: URL) { export function rewriteHeaders(rawHeaders: BareHeaders, origin?: URL) {
const headers = {}; const headers = {};
for (const key in rawHeaders) { for (const key in rawHeaders) {
headers[key.toLowerCase()] = rawHeaders[key]; headers[key.toLowerCase()] = rawHeaders[key];
} }
cspHeaders.forEach((header) => { cspHeaders.forEach((header) => {
delete headers[header]; delete headers[header];
}); });
urlHeaders.forEach((header) => { urlHeaders.forEach((header) => {
if (headers[header]) if (headers[header])
headers[header] = encodeUrl(headers[header] as string, origin); headers[header] = encodeUrl(headers[header] as string, origin);
}); });
if (headers["link"]) { if (headers["link"]) {
headers["link"] = headers["link"].replace(/<(.*?)>/gi, (match) => encodeUrl(match, origin)); headers["link"] = headers["link"].replace(/<(.*?)>/gi, (match) =>
} encodeUrl(match, origin),
);
}
return headers; return headers;
} }

View file

@ -7,99 +7,117 @@ import { rewriteCss } from "./css";
import { rewriteJs } from "./js"; import { rewriteJs } from "./js";
export function isScramjetFile(src: string) { export function isScramjetFile(src: string) {
let bool = false; let bool = false;
["codecs", "client", "shared", "worker", "config"].forEach((file) => { ["codecs", "client", "shared", "worker", "config"].forEach((file) => {
if (src === self.$scramjet.config[file]) bool = true; if (src === self.$scramjet.config[file]) bool = true;
}); });
return bool; return bool;
} }
export function rewriteHtml(html: string, origin?: URL) { export function rewriteHtml(html: string, origin?: URL) {
const handler = new DomHandler((err, dom) => dom); const handler = new DomHandler((err, dom) => dom);
const parser = new Parser(handler); const parser = new Parser(handler);
parser.write(html); parser.write(html);
parser.end(); parser.end();
return render(traverseParsedHtml(handler.root, origin)); return render(traverseParsedHtml(handler.root, origin));
} }
// i need to add the attributes in during rewriting // i need to add the attributes in during rewriting
function traverseParsedHtml(node, origin?: URL) { function traverseParsedHtml(node, origin?: URL) {
/* csp attributes */ /* csp attributes */
for (const cspAttr of ["nonce", "integrity", "csp"]) { for (const cspAttr of ["nonce", "integrity", "csp"]) {
if (hasAttrib(node, cspAttr)) { if (hasAttrib(node, cspAttr)) {
node.attribs[`data-${cspAttr}`] = node.attribs[cspAttr]; node.attribs[`data-${cspAttr}`] = node.attribs[cspAttr];
delete node.attribs[cspAttr]; delete node.attribs[cspAttr];
}
} }
}
/* url attributes */ /* url attributes */
for (const urlAttr of ["src", "href", "data", "action", "formaction"]) { for (const urlAttr of ["src", "href", "data", "action", "formaction"]) {
if (hasAttrib(node, urlAttr) && !isScramjetFile(node.attribs[urlAttr])) { if (hasAttrib(node, urlAttr) && !isScramjetFile(node.attribs[urlAttr])) {
const value = node.attribs[urlAttr]; const value = node.attribs[urlAttr];
node.attribs[`data-${urlAttr}`] = value; node.attribs[`data-${urlAttr}`] = value;
node.attribs[urlAttr] = encodeUrl(value, origin); node.attribs[urlAttr] = encodeUrl(value, origin);
}
} }
}
/* other */
for (const srcsetAttr of ["srcset", "imagesrcset"]) { /* other */
if (hasAttrib(node, srcsetAttr)) { for (const srcsetAttr of ["srcset", "imagesrcset"]) {
const value = node.attribs[srcsetAttr]; if (hasAttrib(node, srcsetAttr)) {
node.attribs[`data-${srcsetAttr}`] = value; const value = node.attribs[srcsetAttr];
node.attribs[srcsetAttr] = rewriteSrcset(value, origin); 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, "srcdoc"))
if (hasAttrib(node, "style")) node.attribs.style = rewriteCss(node.attribs.style, origin); 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 === "style" && node.children[0] !== undefined)
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); node.children[0].data = rewriteCss(node.children[0].data, origin);
if (node.name === "meta" && hasAttrib(node, "http-equiv")) { if (
if (node.attribs["http-equiv"] === "content-security-policy") { node.name === "script" &&
node = {}; /(application|text)\/javascript|importmap|undefined/.test(
} else if (node.attribs["http-equiv"] === "refresh" && node.attribs.content.includes("url")) { node.attribs.type,
const contentArray = node.attribs.content.split("url="); ) &&
contentArray[1] = encodeUrl(contentArray[1].trim(), origin); node.children[0] !== undefined
node.attribs.content = contentArray.join("url="); )
} 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") { if (node.name === "head") {
const scramjetScripts = []; const scramjetScripts = [];
["codecs", "config", "shared", "client"].forEach((script) => { ["codecs", "config", "shared", "client"].forEach((script) => {
scramjetScripts.push(new Element("script", { scramjetScripts.push(
src: self.$scramjet.config[script], new Element("script", {
"data-scramjet": "" src: self.$scramjet.config[script],
})); "data-scramjet": "",
}); }),
);
});
node.children.unshift(...scramjetScripts); node.children.unshift(...scramjetScripts);
}
if (node.childNodes) {
for (const childNode in node.childNodes) {
node.childNodes[childNode] = traverseParsedHtml(
node.childNodes[childNode],
origin,
);
} }
}
if (node.childNodes) { return node;
for (const childNode in node.childNodes) {
node.childNodes[childNode] = traverseParsedHtml(node.childNodes[childNode], origin);
}
}
return node;
} }
export function rewriteSrcset(srcset: string, origin?: URL) { export function rewriteSrcset(srcset: string, origin?: URL) {
const urls = srcset.split(/ [0-9]+x,? ?/g); const urls = srcset.split(/ [0-9]+x,? ?/g);
if (!urls) return ""; if (!urls) return "";
const sufixes = srcset.match(/ [0-9]+x,? ?/g); const sufixes = srcset.match(/ [0-9]+x,? ?/g);
if (!sufixes) return ""; if (!sufixes) return "";
const rewrittenUrls = urls.map((url, i) => { const rewrittenUrls = urls.map((url, i) => {
if (url && sufixes[i]) { if (url && sufixes[i]) {
return encodeUrl(url, origin) + sufixes[i]; return encodeUrl(url, origin) + sufixes[i];
} }
}); });
return rewrittenUrls.join(""); return rewrittenUrls.join("");
} }

View file

@ -16,81 +16,94 @@ import * as ESTree from "estree";
// top // top
// parent // parent
export function rewriteJs(js: string, origin?: URL) { export function rewriteJs(js: string, origin?: URL) {
const htmlcomment = /<!--[\s\S]*?-->/g; const htmlcomment = /<!--[\s\S]*?-->/g;
js = js.replace(htmlcomment, ""); js = js.replace(htmlcomment, "");
try { try {
const ast = parseModule(js, { const ast = parseModule(js, {
module: true, module: true,
webcompat: true webcompat: true,
}); });
const identifierList = [ const identifierList = [
"window", "window",
"self", "self",
"globalThis", "globalThis",
"this", "this",
"parent", "parent",
"top", "top",
"location" "location",
] ];
const customTraveler = makeTraveler({ const customTraveler = makeTraveler({
ImportDeclaration: (node: ESTree.ImportDeclaration) => { ImportDeclaration: (node: ESTree.ImportDeclaration) => {
node.source.value = encodeUrl(node.source.value as string, origin); node.source.value = encodeUrl(node.source.value as string, origin);
}, },
ImportExpression: (node: ESTree.ImportExpression) => { ImportExpression: (node: ESTree.ImportExpression) => {
if (node.source.type === "Literal") { if (node.source.type === "Literal") {
node.source.value = encodeUrl(node.source.value as string, origin); node.source.value = encodeUrl(node.source.value as string, origin);
} else if (node.source.type === "Identifier") { } else if (node.source.type === "Identifier") {
// this is for things that import something like // this is for things that import something like
// const moduleName = "name"; // const moduleName = "name";
// await import(moduleName); // await import(moduleName);
node.source.name = `__wrapImport(${node.source.name})`; node.source.name = `__wrapImport(${node.source.name})`;
} }
}, },
ExportAllDeclaration: (node: ESTree.ExportAllDeclaration) => { ExportAllDeclaration: (node: ESTree.ExportAllDeclaration) => {
node.source.value = encodeUrl(node.source.value as string, origin); node.source.value = encodeUrl(node.source.value as string, origin);
}, },
ExportNamedDeclaration: (node: ESTree.ExportNamedDeclaration) => { ExportNamedDeclaration: (node: ESTree.ExportNamedDeclaration) => {
// strings are Literals in ESTree syntax but these will always be strings // 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); if (node.source)
}, node.source.value = encodeUrl(node.source.value as string, origin);
},
MemberExpression: (node: ESTree.MemberExpression) => { MemberExpression: (node: ESTree.MemberExpression) => {
if (node.object.type === "Identifier" && identifierList.includes(node.object.name)) { if (
node.object.name = `$s(${node.object.name})`; node.object.type === "Identifier" &&
} identifierList.includes(node.object.name)
}, ) {
node.object.name = `$s(${node.object.name})`;
}
},
AssignmentExpression: (node: ESTree.AssignmentExpression) => { AssignmentExpression: (node: ESTree.AssignmentExpression) => {
if (node.left.type === "Identifier" && identifierList.includes(node.left.name)) { if (
node.left.name = `$s(${node.left.name})`; node.left.type === "Identifier" &&
} identifierList.includes(node.left.name)
) {
node.left.name = `$s(${node.left.name})`;
}
if (node.right.type === "Identifier" && identifierList.includes(node.right.name)) { if (
node.right.name = `$s(${node.right.name})`; node.right.type === "Identifier" &&
} identifierList.includes(node.right.name)
}, ) {
node.right.name = `$s(${node.right.name})`;
}
},
VariableDeclarator: (node: ESTree.VariableDeclarator) => { VariableDeclarator: (node: ESTree.VariableDeclarator) => {
if (node.init && node.init.type === "Identifier" && identifierList.includes(node.init.name)) { if (
node.init.name = `$s(${node.init.name})`; node.init &&
} node.init.type === "Identifier" &&
} identifierList.includes(node.init.name)
}); ) {
node.init.name = `$s(${node.init.name})`;
}
},
});
customTraveler.go(ast); customTraveler.go(ast);
return generate(ast); return generate(ast);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
console.log(js); console.log(js);
return js; return js;
} }
} }

View file

@ -2,45 +2,57 @@ import { URL } from "../../client/url";
import { rewriteJs } from "./js"; import { rewriteJs } from "./js";
function canParseUrl(url: string, origin?: URL) { function canParseUrl(url: string, origin?: URL) {
try { try {
new URL(url, origin); new URL(url, origin);
return true; return true;
} catch { } catch {
return false; return false;
} }
} }
// something is broken with this but i didn't debug it // 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, origin?: URL) {
if (url instanceof URL) { if (url instanceof URL) {
return url.toString(); return url.toString();
} }
if (!origin) { if (!origin) {
origin = new URL(self.$scramjet.config.codec.decode(location.href.slice((location.origin + self.$scramjet.config.prefix).length))); origin = new URL(
} self.$scramjet.config.codec.decode(
location.href.slice(
(location.origin + self.$scramjet.config.prefix).length,
),
),
);
}
if (url.startsWith("javascript:")) { if (url.startsWith("javascript:")) {
return "javascript:" + rewriteJs(url.slice("javascript:".length)); return "javascript:" + rewriteJs(url.slice("javascript:".length));
} else if (/^(#|mailto|about|data)/.test(url)) { } else if (/^(#|mailto|about|data)/.test(url)) {
return url; return url;
} else if (canParseUrl(url, origin)) { } else if (canParseUrl(url, origin)) {
return location.origin + self.$scramjet.config.prefix + self.$scramjet.config.codec.encode(new URL(url, origin).href); 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 // something is also broken with this but i didn't debug it
export function decodeUrl(url: string | URL) { export function decodeUrl(url: string | URL) {
if (url instanceof URL) { if (url instanceof URL) {
return url.toString(); return url.toString();
} }
if (/^(#|about|data|mailto|javascript)/.test(url)) { if (/^(#|about|data|mailto|javascript)/.test(url)) {
return url; return url;
} else if (canParseUrl(url)) { } else if (canParseUrl(url)) {
return self.$scramjet.config.codec.decode(url.slice((location.origin + self.$scramjet.config.prefix).length)) return self.$scramjet.config.codec.decode(
} else { url.slice((location.origin + self.$scramjet.config.prefix).length),
return url; );
} } else {
return url;
}
} }

View file

@ -1,11 +1,12 @@
import { rewriteJs } from "./js"; import { rewriteJs } from "./js";
export function rewriteWorkers(js: string, origin?: URL) { export function rewriteWorkers(js: string, origin?: URL) {
let str = new String().toString() let str = new String().toString()[
//@ts-expect-error //@ts-expect-error
["codecs", "config", "shared", "client"].forEach((script) => { ("codecs", "config", "shared", "client")
str += `import "${self.$scramjet.config[script]}"\n` ].forEach((script) => {
}) str += `import "${self.$scramjet.config[script]}"\n`;
str += rewriteJs(js, origin); });
str += rewriteJs(js, origin);
return str; return str;
} }

78
src/types.d.ts vendored
View file

@ -5,45 +5,45 @@ import { rewriteJs } from "./shared/rewriters/js";
import { rewriteHeaders } from "./shared/rewriters/headers"; import { rewriteHeaders } from "./shared/rewriters/headers";
import { rewriteWorkers } from "./shared/rewriters/worker"; import { rewriteWorkers } from "./shared/rewriters/worker";
import { isScramjetFile } from "./shared/rewriters/html"; import { isScramjetFile } from "./shared/rewriters/html";
import type { Codec } from "./codecs" import type { Codec } from "./codecs";
import { BareClient } from "@mercuryworkshop/bare-mux"; import { BareClient } from "@mercuryworkshop/bare-mux";
declare global { declare global {
interface Window { interface Window {
$scramjet: { $scramjet: {
shared: { shared: {
url: { url: {
encodeUrl: typeof encodeUrl, encodeUrl: typeof encodeUrl;
decodeUrl: typeof decodeUrl, decodeUrl: typeof decodeUrl;
} };
rewrite: { rewrite: {
rewriteCss: typeof rewriteCss, rewriteCss: typeof rewriteCss;
rewriteHtml: typeof rewriteHtml, rewriteHtml: typeof rewriteHtml;
rewriteSrcset: typeof rewriteSrcset, rewriteSrcset: typeof rewriteSrcset;
rewriteJs: typeof rewriteJs, rewriteJs: typeof rewriteJs;
rewriteHeaders: typeof rewriteHeaders, rewriteHeaders: typeof rewriteHeaders;
rewriteWorkers: typeof rewriteWorkers, rewriteWorkers: typeof rewriteWorkers;
} };
util: { util: {
BareClient: typeof BareClient, BareClient: typeof BareClient;
isScramjetFile: typeof isScramjetFile, isScramjetFile: typeof isScramjetFile;
} };
} };
config: { config: {
prefix: string; prefix: string;
codec: Codec codec: Codec;
config: string; config: string;
shared: string; shared: string;
worker: string; worker: string;
client: string; client: string;
codecs: string; codecs: string;
}, };
codecs: { codecs: {
none: Codec; none: Codec;
plain: Codec; plain: Codec;
base64: Codec; base64: Codec;
xor: Codec; xor: Codec;
} };
} };
} }
} }

View file

@ -2,167 +2,185 @@ import { BareResponseFetch } from "@mercuryworkshop/bare-mux";
import IDBMap from "@webreflection/idb-map"; import IDBMap from "@webreflection/idb-map";
declare global { declare global {
interface Window { interface Window {
ScramjetServiceWorker; ScramjetServiceWorker;
} }
} }
self.ScramjetServiceWorker = class ScramjetServiceWorker { self.ScramjetServiceWorker = class ScramjetServiceWorker {
client: typeof self.$scramjet.shared.util.BareClient.prototype; client: typeof self.$scramjet.shared.util.BareClient.prototype;
config: typeof self.$scramjet.config; config: typeof self.$scramjet.config;
constructor(config = self.$scramjet.config) { constructor(config = self.$scramjet.config) {
this.client = new self.$scramjet.shared.util.BareClient(); this.client = new self.$scramjet.shared.util.BareClient();
if (!config.prefix) config.prefix = "/scramjet/"; if (!config.prefix) config.prefix = "/scramjet/";
this.config = config; this.config = config;
} }
route({ request }: FetchEvent) { route({ request }: FetchEvent) {
if (request.url.startsWith(location.origin + this.config.prefix)) return true; if (request.url.startsWith(location.origin + this.config.prefix))
else return false; return true;
} else return false;
}
async fetch({ request }: FetchEvent) { async fetch({ request }: FetchEvent) {
const urlParam = new URLSearchParams(new URL(request.url).search); const urlParam = new URLSearchParams(new URL(request.url).search);
const { encodeUrl, decodeUrl } = self.$scramjet.shared.url; const { encodeUrl, decodeUrl } = self.$scramjet.shared.url;
const { rewriteHeaders, rewriteHtml, rewriteJs, rewriteCss, rewriteWorkers } = self.$scramjet.shared.rewrite; const {
rewriteHeaders,
rewriteHtml,
rewriteJs,
rewriteCss,
rewriteWorkers,
} = self.$scramjet.shared.rewrite;
if (urlParam.has("url")) { if (urlParam.has("url")) {
return Response.redirect(encodeUrl(urlParam.get("url"), new URL(urlParam.get("url")))) return Response.redirect(
} encodeUrl(urlParam.get("url"), new URL(urlParam.get("url"))),
);
}
try { try {
const url = new URL(decodeUrl(request.url)); const url = new URL(decodeUrl(request.url));
const cookieStore = new IDBMap(url.host, { durability: "relaxed", prefix: "Cookies" }); const cookieStore = new IDBMap(url.host, {
durability: "relaxed",
prefix: "Cookies",
});
const response: BareResponseFetch = await this.client.fetch(url, { const response: BareResponseFetch = await this.client.fetch(url, {
method: request.method, method: request.method,
body: request.body, body: request.body,
headers: request.headers, headers: request.headers,
credentials: "omit", credentials: "omit",
mode: request.mode === "cors" ? request.mode : "same-origin", mode: request.mode === "cors" ? request.mode : "same-origin",
cache: request.cache, cache: request.cache,
redirect: request.redirect, redirect: request.redirect,
//@ts-ignore why the fuck is this not typed mircosoft //@ts-ignore why the fuck is this not typed mircosoft
duplex: "half", duplex: "half",
}); });
let responseBody; let responseBody;
const responseHeaders = rewriteHeaders(response.rawHeaders, url); const responseHeaders = rewriteHeaders(response.rawHeaders, url);
for (const cookie of (responseHeaders["set-cookie"] || []) as string[]) { for (const cookie of (responseHeaders["set-cookie"] || []) as string[]) {
let cookieParsed = cookie.split(";").map(x => x.trim().split("=")); let cookieParsed = cookie.split(";").map((x) => x.trim().split("="));
let [key, value] = cookieParsed.shift(); let [key, value] = cookieParsed.shift();
value = value.replace("\"", ""); value = value.replace('"', "");
const hostArg = cookieParsed.find(x => x[0].toLowerCase() === "domain"); const hostArg = cookieParsed.find(
cookieParsed = cookieParsed.filter(x => x[0].toLowerCase() !== "domain"); (x) => x[0].toLowerCase() === "domain",
let host = hostArg ? hostArg[1] : undefined; );
cookieParsed = cookieParsed.filter(
(x) => x[0].toLowerCase() !== "domain",
);
let host = hostArg ? hostArg[1] : undefined;
if (host && host !== url.host) { if (host && host !== url.host) {
if (host.startsWith(".")) host = host.slice(1); if (host.startsWith(".")) host = host.slice(1);
const cookieStore = new IDBMap(host, { durability: "relaxed", prefix: "Cookies" }); const cookieStore = new IDBMap(host, {
cookieStore.set(key, { value: value, args: cookieParsed }); durability: "relaxed",
} else { prefix: "Cookies",
cookieStore.set(key, { value: value, args: cookieParsed }); });
} cookieStore.set(key, { value: value, args: cookieParsed });
} } else {
cookieStore.set(key, { value: value, args: cookieParsed });
}
}
for (let header in responseHeaders) { for (let header in responseHeaders) {
// flatten everything past here // flatten everything past here
if (responseHeaders[header] instanceof Array) responseHeaders[header] = responseHeaders[header][0]; if (responseHeaders[header] instanceof Array)
} responseHeaders[header] = responseHeaders[header][0];
}
if (response.body) { if (response.body) {
switch (request.destination) { switch (request.destination) {
case "iframe": case "iframe":
case "document": case "document":
if (responseHeaders["content-type"]?.toString()?.startsWith("text/html")) { if (
responseBody = rewriteHtml(await response.text(), url); responseHeaders["content-type"]
} else { ?.toString()
responseBody = response.body; ?.startsWith("text/html")
} ) {
break; responseBody = rewriteHtml(await response.text(), url);
case "script": } else {
responseBody = rewriteJs(await response.text(), url); responseBody = response.body;
break; }
case "style": break;
responseBody = rewriteCss(await response.text(), url); case "script":
break; responseBody = rewriteJs(await response.text(), url);
case "sharedworker": break;
case "worker": case "style":
responseBody = rewriteWorkers(await response.text(), url); responseBody = rewriteCss(await response.text(), url);
break; break;
default: case "sharedworker":
responseBody = response.body; case "worker":
break; responseBody = rewriteWorkers(await response.text(), url);
} break;
} default:
// downloads responseBody = response.body;
if (["document", "iframe"].includes(request.destination)) { break;
const header = responseHeaders["content-disposition"]; }
}
// downloads
if (["document", "iframe"].includes(request.destination)) {
const header = responseHeaders["content-disposition"];
// validate header and test for filename // validate header and test for filename
if (!/\s*?((inline|attachment);\s*?)filename=/i.test(header)) { if (!/\s*?((inline|attachment);\s*?)filename=/i.test(header)) {
// if filename= wasn"t specified then maybe the remote specified to download this as an attachment? // if filename= wasn"t specified then maybe the remote specified to download this as an attachment?
// if it"s invalid then we can still possibly test for the attachment/inline type // if it"s invalid then we can still possibly test for the attachment/inline type
const type = /^\s*?attachment/i.test(header) const type = /^\s*?attachment/i.test(header)
? "attachment" ? "attachment"
: "inline"; : "inline";
// set the filename // set the filename
const [filename] = new URL(response.finalURL).pathname const [filename] = new URL(response.finalURL).pathname
.split("/") .split("/")
.slice(-1); .slice(-1);
responseHeaders[ responseHeaders["content-disposition"] =
"content-disposition" `${type}; filename=${JSON.stringify(filename)}`;
] = `${type}; filename=${JSON.stringify(filename)}`; }
} }
} if (responseHeaders["accept"] === "text/event-stream") {
if (responseHeaders["accept"] === "text/event-stream") { responseHeaders["content-type"] = "text/event-stream";
responseHeaders["content-type"] = "text/event-stream"; }
} if (crossOriginIsolated) {
if (crossOriginIsolated) { responseHeaders["Cross-Origin-Embedder-Policy"] = "require-corp";
responseHeaders["Cross-Origin-Embedder-Policy"] = "require-corp"; }
}
return new Response(responseBody, { return new Response(responseBody, {
headers: responseHeaders as HeadersInit, headers: responseHeaders as HeadersInit,
status: response.status, status: response.status,
statusText: response.statusText statusText: response.statusText,
}) });
} catch (err) { } catch (err) {
if (!["document", "iframe"].includes(request.destination)) if (!["document", "iframe"].includes(request.destination))
return new Response(undefined, { status: 500 }); return new Response(undefined, { status: 500 });
console.error(err); console.error(err);
return renderError(err, decodeUrl(request.url)); return renderError(err, decodeUrl(request.url));
} }
} }
} };
function errorTemplate(trace: string, fetchedURL: string) {
function errorTemplate( // turn script into a data URI so we don"t have to escape any HTML values
trace: string, const script = `
fetchedURL: string,
) {
// turn script into a data URI so we don"t have to escape any HTML values
const script = `
errorTrace.value = ${JSON.stringify(trace)}; errorTrace.value = ${JSON.stringify(trace)};
fetchedURL.textContent = ${JSON.stringify(fetchedURL)}; fetchedURL.textContent = ${JSON.stringify(fetchedURL)};
for (const node of document.querySelectorAll("#hostname")) node.textContent = ${JSON.stringify( for (const node of document.querySelectorAll("#hostname")) node.textContent = ${JSON.stringify(
location.hostname location.hostname,
)}; )};
reload.addEventListener("click", () => location.reload()); reload.addEventListener("click", () => location.reload());
version.textContent = "0.0.1"; version.textContent = "0.0.1";
` `;
return ( return `<!DOCTYPE html>
`<!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@ -194,12 +212,12 @@ function errorTemplate(
<button id="reload">Reload</button> <button id="reload">Reload</button>
<hr /> <hr />
<p><i>Scramjet v<span id="version"></span></i></p> <p><i>Scramjet v<span id="version"></span></i></p>
<script src="${"data:application/javascript," + encodeURIComponent(script) <script src="${
}"></script> "data:application/javascript," + encodeURIComponent(script)
}"></script>
</body> </body>
</html> </html>
` `;
);
} }
/** /**
@ -208,22 +226,15 @@ function errorTemplate(
* @param {string} fetchedURL * @param {string} fetchedURL
*/ */
function renderError(err, fetchedURL) { function renderError(err, fetchedURL) {
const headers = { const headers = {
"content-type": "text/html", "content-type": "text/html",
}; };
if (crossOriginIsolated) { if (crossOriginIsolated) {
headers["Cross-Origin-Embedder-Policy"] = "require-corp"; headers["Cross-Origin-Embedder-Policy"] = "require-corp";
} }
return new Response( return new Response(errorTemplate(String(err), fetchedURL), {
errorTemplate( status: 500,
String(err), headers: headers,
fetchedURL });
),
{
status: 500,
headers: headers
}
);
} }

View file

@ -1,29 +1,39 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title> <title>Document</title>
<link rel="prefetch" href="/scram/scramjet.worker.js"> <link rel="prefetch" href="/scram/scramjet.worker.js" />
<link rel="prefetch" href="/scram/scramjet.shared.js"> <link rel="prefetch" href="/scram/scramjet.shared.js" />
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&amp;family=Inter+Tight:ital,wght@0,100..900;1,100..900&amp;family=Inter:wght@100..900&amp;display=swap&amp;" rel="stylesheet"> <link
<style> href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&amp;family=Inter+Tight:ital,wght@0,100..900;1,100..900&amp;family=Inter:wght@100..900&amp;display=swap&amp;"
body, html, #app { rel="stylesheet"
font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, sans-serif; />
width:100vw; <style>
height:100vh; body,
margin: 0; html,
padding: 0; #app {
background-color:#121212; font-family:
overflow: hidden; "Inter",
} system-ui,
</style> -apple-system,
</head> BlinkMacSystemFont,
<body> sans-serif;
<script src="https://unpkg.com/dreamland"></script> width: 100vw;
<script src="/baremux/index.js" defer></script> height: 100vh;
<script src="/scram/scramjet.codecs.js" defer></script> margin: 0;
<script src="/scram/scramjet.config.js" defer></script> padding: 0;
<script src="ui.js" defer></script> background-color: #121212;
</body> overflow: hidden;
</html> }
</style>
</head>
<body>
<script src="https://unpkg.com/dreamland"></script>
<script src="/baremux/index.js" defer></script>
<script src="/scram/scramjet.codecs.js" defer></script>
<script src="/scram/scramjet.config.js" defer></script>
<script src="ui.js" defer></script>
</body>
</html>

View file

@ -1,20 +1,20 @@
importScripts( importScripts(
"/scram/scramjet.codecs.js", "/scram/scramjet.codecs.js",
"/scram/scramjet.config.js", "/scram/scramjet.config.js",
"/scram/scramjet.shared.js", "/scram/scramjet.shared.js",
"/scram/scramjet.worker.js" "/scram/scramjet.worker.js",
); );
const scramjet = new ScramjetServiceWorker(); const scramjet = new ScramjetServiceWorker();
async function handleRequest(event) { async function handleRequest(event) {
if (scramjet.route(event)) { if (scramjet.route(event)) {
return scramjet.fetch(event); return scramjet.fetch(event);
} }
return fetch(event.request) return fetch(event.request);
} }
self.addEventListener("fetch", (event) => { self.addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event)); event.respondWith(handleRequest(event));
}); });

View file

@ -1,101 +1,114 @@
navigator.serviceWorker.register("./sw.js", { navigator.serviceWorker
scope: $scramjet.config.prefix .register("./sw.js", {
}).then((reg) => { scope: $scramjet.config.prefix,
reg.update(); })
}); .then((reg) => {
const connection = new BareMux.BareMuxConnection("/baremux/worker.js") reg.update();
const flex = css`display: flex;`; });
const col = css`flex-direction: column;`; const connection = new BareMux.BareMuxConnection("/baremux/worker.js");
const store = $store({ const flex = css`
url: "https://google.com", display: flex;
wispurl: "wss://wisp.mercurywork.shop/", `;
bareurl: (location.protocol === "https:" ? "https" : "http") + "://" + location.host + "/bare/", const col = css`
}, { ident: "settings", backing: "localstorage", autosave: "auto" }); flex-direction: column;
connection.setTransport("/baremod/index.mjs", [store.bareurl]) `;
function App() { const store = $store(
this.urlencoded = ""; {
this.css = ` url: "https://google.com",
width: 100%; wispurl: "wss://wisp.mercurywork.shop/",
height: 100%; bareurl:
color: #e0def4; (location.protocol === "https:" ? "https" : "http") +
display: flex; "://" +
align-items: center; location.host +
justify-content: center; "/bare/",
flex-direction: column; },
input, { ident: "settings", backing: "localstorage", autosave: "auto" },
button { );
font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, connection.setTransport("/baremod/index.mjs", [store.bareurl]);
sans-serif; function App() {
} this.urlencoded = "";
h1 { this.css = `
font-family: "Inter Tight", "Inter", system-ui, -apple-system, BlinkMacSystemFont, width: 100%;
sans-serif; height: 100%;
margin-bottom: 0; color: #e0def4;
} display: flex;
iframe { align-items: center;
border: 4px solid #313131; justify-content: center;
background-color: #121212; flex-direction: column;
border-radius: 1rem; input,
margin: 2em; button {
margin-top: 0.5em; font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont,
width: calc(100% - 4em); sans-serif;
height: calc(100% - 8em); }
} h1 {
font-family: "Inter Tight", "Inter", system-ui, -apple-system, BlinkMacSystemFont,
input.bar { sans-serif;
border: none; margin-bottom: 0;
outline: none; }
color: #fff; iframe {
height: 2em; border: 4px solid #313131;
width: 60%; background-color: #121212;
text-align: center; border-radius: 1rem;
border-radius: 0.75em; margin: 2em;
background-color: #313131; margin-top: 0.5em;
padding: 0.45em; width: calc(100% - 4em);
} height: calc(100% - 8em);
.cfg * { }
margin: 2px;
} input.bar {
.buttons button { border: none;
border: 2px solid #4c8bf5; outline: none;
background-color: #313131; color: #fff;
border-radius: 0.75em; height: 2em;
color: #fff; width: 60%;
padding: 0.45em; text-align: center;
} border-radius: 0.75em;
.cfg input { background-color: #313131;
border: none; padding: 0.45em;
background-color: #313131; }
border-radius: 0.75em; .cfg * {
color: #fff; margin: 2px;
outline: none; }
padding: 0.45em; .buttons button {
} border: 2px solid #4c8bf5;
`; background-color: #313131;
border-radius: 0.75em;
return html` color: #fff;
<div> padding: 0.45em;
<h1>Percury Unblocker</h1> }
<p>surf the unblocked and mostly buggy web</p> .cfg input {
border: none;
<div class=${`${flex} ${col} cfg`}> background-color: #313131;
<input bind:value=${use(store.wispurl)}></input> border-radius: 0.75em;
<input bind:value=${use(store.bareurl)}></input> color: #fff;
outline: none;
padding: 0.45em;
<div class=${`${flex} buttons`}> }
<button on:click=${() => connection.setTransport("/baremod/index.mjs", [store.bareurl])}>use bare server 3</button> `;
<button on:click=${() => connection.setTransport("/libcurl/index.mjs", [{ wisp: store.wispurl }])}>use libcurl.js</button>
<button on:click=${() => connection.setTransport("/epoxy/index.mjs", [{ wisp: store.wispurl }])}>use epoxy</button> return html`
<button on:click=${() => window.open(this.urlencoded)}>open in fullscreen</button> <div>
</div> <h1>Percury Unblocker</h1>
</div> <p>surf the unblocked and mostly buggy web</p>
<input class="bar" bind:value=${use(store.url)} on:input=${(e) => (store.url = e.target.value)} on:keyup=${(e) => e.keyCode == 13 && console.log(this.urlencoded = $scramjet.config.prefix + $scramjet.config.codec.encode(e.target.value))}></input>
<iframe src=${use(this.urlencoded)}></iframe> <div class=${`${flex} ${col} cfg`}>
</div> <input bind:value=${use(store.wispurl)}></input>
` <input bind:value=${use(store.bareurl)}></input>
}
window.addEventListener("load", () => { <div class=${`${flex} buttons`}>
document.body.appendChild(h(App)) <button on:click=${() => connection.setTransport("/baremod/index.mjs", [store.bareurl])}>use bare server 3</button>
}) <button on:click=${() => connection.setTransport("/libcurl/index.mjs", [{ wisp: store.wispurl }])}>use libcurl.js</button>
<button on:click=${() => connection.setTransport("/epoxy/index.mjs", [{ wisp: store.wispurl }])}>use epoxy</button>
<button on:click=${() => window.open(this.urlencoded)}>open in fullscreen</button>
</div>
</div>
<input class="bar" bind:value=${use(store.url)} on:input=${(e) => (store.url = e.target.value)} on:keyup=${(e) => e.keyCode == 13 && console.log((this.urlencoded = $scramjet.config.prefix + $scramjet.config.codec.encode(e.target.value)))}></input>
<iframe src=${use(this.urlencoded)}></iframe>
</div>
`;
}
window.addEventListener("load", () => {
document.body.appendChild(h(App));
});

View file

@ -4,4 +4,4 @@
location = "http://www.google.com"; location = "http://www.google.com";
} }
</script> </script>
<button onclick='f()'>Google</button> <button onclick="f()">Google</button>

View file

@ -1,10 +1,10 @@
{ {
"compilerOptions": { "compilerOptions": {
// "allowJs": true, // "allowJs": true,
"rootDir": "./src", "rootDir": "./src",
"target": "ES2022", "target": "ES2022",
"moduleResolution": "Bundler", "moduleResolution": "Bundler",
"module": "ES2022", "module": "ES2022"
}, },
"include": ["src"], "include": ["src"]
} }