mirror of
https://github.com/MercuryWorkshop/scramjet.git
synced 2025-05-12 14:00:01 -04:00
cookies
This commit is contained in:
parent
7b8d5d09b2
commit
3bc8dd34b9
14 changed files with 144 additions and 93 deletions
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
76
src/shared/cookie.ts
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
3
src/types.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ export class ScramjetServiceWorker {
|
|||
syncPool: Record<number, (val?: any) => void> = {};
|
||||
synctoken = 0;
|
||||
|
||||
cookieStore = new self.$scramjet.shared.CookieStore();
|
||||
|
||||
serviceWorkers: FakeServiceWorker[] = [];
|
||||
|
||||
constructor() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue