This commit is contained in:
velzie 2024-08-10 15:03:54 -04:00
parent 7b8d5d09b2
commit 3bc8dd34b9
No known key found for this signature in database
GPG key ID: 048413F95F0DDE1F
14 changed files with 144 additions and 93 deletions

View file

@ -1,5 +1,5 @@
import { createLocationProxy } from "./location";
import { decodeUrl } from "./shared";
import { CookieStore, decodeUrl } from "./shared";
import { createDocumentProxy, createWindowProxy } from "./window";
declare global {
@ -46,6 +46,8 @@ export class ScramjetClient {
windowProxy: any;
locationProxy: any;
cookieStore = new CookieStore();
eventcallbacks: Map<
any,
[
@ -68,13 +70,17 @@ export class ScramjetClient {
global[ScramjetClient.SCRAMJET] = this;
}
loadcookies(cookiestr: string) {
this.cookieStore.load(cookiestr);
}
hook() {
// @ts-ignore
const context = import.meta.webpackContext(".", {
recursive: true,
});
let modules = [];
const modules = [];
for (const key of context.keys()) {
const module = context(key);

View file

@ -1,37 +1,12 @@
import { parse } from "set-cookie-parser";
import { ScramjetClient } from "../client";
import IDBMapSync from "@webreflection/idb-map/sync";
export default function (client: ScramjetClient, self: typeof window) {
let cookieStore = new IDBMapSync(client.url.host, {
durability: "relaxed",
prefix: "Cookies",
});
client.Trap("Document.prototype.cookie", {
get(ctx) {
let cookies = cookieStore.entries();
if (client.url.protocol !== "https:") {
cookies = cookies.filter(([_k, v]) => !v.args.includes(["Secure"]));
}
cookies = cookies.filter(([_k, v]) => !v.args.includes(["HttpOnly"]));
cookies = Array.from(cookies.map(([k, v]) => `${k}=${v.value}`));
return cookies.join("; ");
get() {
return client.cookieStore.getCookies(client.url, true);
},
set(ctx, value: string) {
// dbg.debug("setting cookie", value);
const cookie = parse(value)[0];
let date = new Date();
let expires = cookie.expires;
// dbg.error("expires", expires);
// if (expires instanceof Date) {
// if (isNaN(expires.getTime())) return;
// if (expires.getTime() < date.getTime()) return;
// }
// set.call(document, `${cookie.name}=${cookie.value}`);
client.cookieStore.setCookies([value], client.url);
},
});

View file

@ -65,7 +65,7 @@ export default function (client: ScramjetClient, self: typeof window) {
) {
value = encodeUrl(value);
} else if (attr === "srcdoc") {
value = rewriteHtml(value);
value = rewriteHtml(value, client.cookieStore);
} else if (["srcset", "imagesrcset"].includes(attr)) {
value = rewriteSrcset(value);
}
@ -114,7 +114,7 @@ export default function (client: ScramjetClient, self: typeof window) {
argArray[1] = encodeUrl(argArray[1]);
} else if (argArray[0] === "srcdoc") {
// TODO: this will rewrite with the wrong url in mind for iframes!!
argArray[1] = rewriteHtml(argArray[1]);
argArray[1] = rewriteHtml(argArray[1], client.cookieStore);
} else if (["srcset", "imagesrcset"].includes(argArray[0])) {
argArray[1] = rewriteSrcset(argArray[1]);
} else if (argArray[1] === "style") {
@ -139,7 +139,7 @@ export default function (client: ScramjetClient, self: typeof window) {
} else if (this instanceof self.HTMLStyleElement) {
value = rewriteCss(value);
} else {
value = rewriteHtml(value);
value = rewriteHtml(value, client.cookieStore);
}
return innerHTML.set.call(this, value);

View file

@ -15,6 +15,10 @@ dbg.log("scrammin");
// if it already exists, that means the handlers have probably already been setup by the parent document
if (!(ScramjetClient.SCRAMJET in self)) {
const client = new ScramjetClient(self);
client.loadcookies(self.COOKIE);
delete self.COOKIE;
client.hook();
if (isemulatedsw) {

View file

@ -9,6 +9,7 @@ export const {
rewriteHeaders,
rewriteWorkers,
},
CookieStore,
} = self.$scramjet.shared;
export const config = self.$scramjet.config;

76
src/shared/cookie.ts Normal file
View file

@ -0,0 +1,76 @@
import parse from "set-cookie-parser";
export type Cookie = {
name: string;
value: string;
path?: string;
expires?: string;
maxAge?: number;
domain?: string;
secure?: boolean;
httpOnly?: boolean;
sameSite?: "strict" | "lax" | "none";
};
export class CookieStore {
private cookies: Record<string, Cookie> = {};
async setCookies(cookies: string[], url: URL) {
for (const str of cookies) {
const parsed = parse(str);
const domain = parsed.domain;
const sameSite = parsed.sameSite;
const cookie: Cookie = {
domain,
sameSite,
...parsed[0],
};
dbg.log("cookie", cookie);
if (!cookie.domain) cookie.domain = "." + url.hostname;
if (!cookie.domain.startsWith(".")) cookie.domain = "." + cookie.domain;
if (!cookie.path) cookie.path = "/";
if (!cookie.sameSite) cookie.sameSite = "lax";
if (cookie.expires) cookie.expires = cookie.expires.toString();
const id = `${cookie.domain}@${cookie.path}@${cookie.name}`;
this.cookies[id] = cookie;
}
}
getCookies(url: URL, fromJs: boolean): string {
const now = new Date();
const cookies = Object.values(this.cookies);
const validCookies: Cookie[] = [];
for (const cookie of cookies) {
if (cookie.expires && new Date(cookie.expires) < now) {
delete this.cookies[`${cookie.domain}@${cookie.path}@${cookie.name}`];
continue;
}
if (cookie.secure && url.protocol !== "https:") continue;
if (cookie.httpOnly && fromJs) continue;
if (!url.pathname.startsWith(cookie.path)) continue;
if (cookie.domain.startsWith(".")) {
if (!url.hostname.endsWith(cookie.domain.slice(1))) continue;
}
validCookies.push(cookie);
}
return validCookies
.map((cookie) => `${cookie.name}=${cookie.value}`)
.join("; ");
}
load(cookies: string) {
this.cookies = JSON.parse(cookies);
}
dump(): string {
return JSON.stringify(this.cookies);
}
}

View file

@ -8,6 +8,7 @@ import { isScramjetFile } from "./rewriters/html";
import { BareClient } from "@mercuryworkshop/bare-mux";
import { parseDomain } from "parse-domain";
import { ScramjetHeaders } from "./headers";
import { CookieStore } from "./cookie";
self.$scramjet.shared = {
util: {
@ -28,6 +29,7 @@ self.$scramjet.shared = {
rewriteHeaders,
rewriteWorkers,
},
CookieStore,
};
if ("document" in self && document.currentScript) {

View file

@ -43,11 +43,11 @@ export function rewriteHeaders(rawHeaders: BareHeaders, origin?: URL) {
);
});
if (headers["link"]) {
headers["link"] = headers["link"].replace(/<(.*?)>/gi, (match) =>
encodeUrl(match, origin)
);
}
// if (headers["link"]) {
// headers["link"] = headers["link"].replace(/<(.*?)>/gi, (match) =>
// encodeUrl(match, origin)
// );
// }
return headers;
}

View file

@ -5,6 +5,7 @@ import render from "dom-serializer";
import { encodeUrl } from "./url";
import { rewriteCss } from "./css";
import { rewriteJs } from "./js";
import { CookieStore } from "../cookie";
export function isScramjetFile(src: string) {
let bool = false;
@ -15,19 +16,23 @@ export function isScramjetFile(src: string) {
return bool;
}
export function rewriteHtml(html: string, origin?: URL) {
export function rewriteHtml(
html: string,
cookieStore: CookieStore,
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));
return render(traverseParsedHtml(handler.root, cookieStore, origin));
}
// i need to add the attributes in during rewriting
function traverseParsedHtml(node, origin?: URL) {
function traverseParsedHtml(node, cookieStore: CookieStore, origin?: URL) {
/* csp attributes */
for (const cspAttr of ["nonce", "integrity", "csp"]) {
if (hasAttrib(node, cspAttr)) {
@ -86,7 +91,7 @@ function traverseParsedHtml(node, origin?: URL) {
}
if (hasAttrib(node, "srcdoc"))
node.attribs.srcdoc = rewriteHtml(node.attribs.srcdoc, origin);
node.attribs.srcdoc = rewriteHtml(node.attribs.srcdoc, cookieStore, origin);
if (hasAttrib(node, "style"))
node.attribs.style = rewriteCss(node.attribs.style, origin);
@ -122,6 +127,8 @@ function traverseParsedHtml(node, origin?: URL) {
if (node.name === "head") {
const scripts = [];
const dump = JSON.stringify(cookieStore.dump());
scripts.push(
new Element("script", {
src: self.$scramjet.config["wasm"],
@ -137,6 +144,7 @@ function traverseParsedHtml(node, origin?: URL) {
"data:application/javascript;base64," +
btoa(
`
self.COOKIE = ${dump};
self.$scramjet.config = ${JSON.stringify(self.$scramjet.config)};
self.$scramjet.codec = self.$scramjet.codecs[self.$scramjet.config.codec];
if ("document" in self && document.currentScript) {
@ -164,6 +172,7 @@ function traverseParsedHtml(node, origin?: URL) {
for (const childNode in node.childNodes) {
node.childNodes[childNode] = traverseParsedHtml(
node.childNodes[childNode],
cookieStore,
origin
);
}

View file

@ -36,7 +36,7 @@ export function rewriteJs(js: string | ArrayBuffer, origin?: URL) {
}
const after = performance.now();
dbg.debug("Rewrite took", Math.floor((after - before) * 10) / 10, "ms");
// dbg.debug("Rewrite took", Math.floor((after - before) * 10) / 10, "ms");
return js;
}

3
src/types.d.ts vendored
View file

@ -10,6 +10,7 @@ import type { Codec } from "./codecs";
import { BareClient } from "@mercuryworkshop/bare-mux";
import { parseDomain } from "parse-domain";
import { ScramjetHeaders } from "./shared/headers";
import { CookieStore } from "./shared/cookie";
interface ScramjetConfig {
prefix: string;
@ -49,6 +50,7 @@ declare global {
isScramjetFile: typeof isScramjetFile;
parseDomain: typeof parseDomain;
};
CookieStore: typeof CookieStore;
};
config: ScramjetConfig;
codecs: {
@ -59,6 +61,7 @@ declare global {
};
codec: Codec;
};
COOKIE: string;
WASM: string;
ScramjetController: typeof ScramjetController;
}

View file

@ -1,21 +0,0 @@
export type Cookie = {
name: string;
value: string;
path?: string;
expires?: Date;
maxAge?: number;
domain?: string;
secure?: boolean;
httpOnly?: boolean;
sameSite?: "strict" | "lax" | "none";
};
class CookieStore {
private cookies: Cookie[] = [];
async load() {
return this.cookies;
}
}
export const cookieStore = new CookieStore();

View file

@ -4,8 +4,7 @@ import { ParseResultType } from "parse-domain";
import { ScramjetServiceWorker } from ".";
import { renderError } from "./error";
import { FakeServiceWorker } from "./fakesw";
import parse from "set-cookie-parser";
import { cookieStore } from "./cookie";
import { CookieStore } from "../shared/cookie";
const { encodeUrl, decodeUrl } = self.$scramjet.shared.url;
const { rewriteHeaders, rewriteHtml, rewriteJs, rewriteCss, rewriteWorkers } =
@ -75,23 +74,10 @@ export async function swfetch(
headers.set("Referer", decodeUrl(request.referrer));
let cookies = [...(await cookieStore.entries())];
console.log("cookies", cookies);
// if (url.protocol !== "https:") {
// cookies = cookies.filter(([_k, v]) => !v.args.includes(["Secure"]));
// }
cookies = cookies.filter(
([_k, v]) =>
v.args.domain.includes(url.hostname) ||
url.hostname.includes(v.args.domain)
);
cookies = cookies.filter(([_k, v]) => v.value !== "");
cookies = Array.from(cookies.map(([k, v]) => `${k}=${v.value}`));
const cookies = this.cookieStore.getCookies(url, false);
if (cookies.length) {
headers.set("Cookie", cookies.join("; "));
headers.set("Cookie", cookies);
}
// TODO this is wrong somehow
@ -112,7 +98,12 @@ export async function swfetch(
duplex: "half",
});
return await handleResponse(url, request.destination, response);
return await handleResponse(
url,
request.destination,
response,
this.cookieStore
);
} catch (err) {
console.error("ERROR FROM SERVICE WORKER FETCH", err);
if (!["document", "iframe"].includes(request.destination))
@ -125,12 +116,17 @@ export async function swfetch(
async function handleResponse(
url: URL,
destination: RequestDestination,
response: BareResponseFetch
response: BareResponseFetch,
cookieStore: CookieStore
): Promise<Response> {
let responseBody: string | ArrayBuffer | ReadableStream;
const responseHeaders = rewriteHeaders(response.rawHeaders, url);
await handleCookies(url, (responseHeaders["set-cookie"] || []) as string[]);
await handleCookies(
url,
cookieStore,
(responseHeaders["set-cookie"] || []) as string[]
);
for (const header in responseHeaders) {
// flatten everything past here
@ -143,7 +139,7 @@ async function handleResponse(
case "iframe":
case "document":
if (responseHeaders["content-type"]?.startsWith("text/html")) {
responseBody = rewriteHtml(await response.text(), url);
responseBody = rewriteHtml(await response.text(), cookieStore, url);
} else {
responseBody = response.body;
}
@ -202,14 +198,12 @@ async function handleResponse(
});
}
async function handleCookies(url: URL, maybeHeaders: string[] | string) {
const cookies = await cookieStore.load();
async function handleCookies(
url: URL,
cookieStore: CookieStore,
maybeHeaders: string[] | string
) {
const headers = maybeHeaders instanceof Array ? maybeHeaders : [maybeHeaders];
for (const cookie of headers) {
const parsed = parse(cookie)[0];
console.error("set-cookie", parsed);
cookies.push(parsed);
}
await cookieStore.setCookies(headers, url);
}

View file

@ -17,6 +17,8 @@ export class ScramjetServiceWorker {
syncPool: Record<number, (val?: any) => void> = {};
synctoken = 0;
cookieStore = new self.$scramjet.shared.CookieStore();
serviceWorkers: FakeServiceWorker[] = [];
constructor() {