From bd08461dbed2594583683380bbc84c937e8b2117 Mon Sep 17 00:00:00 2001 From: velzie Date: Wed, 17 Jul 2024 19:59:14 -0400 Subject: [PATCH] refactor: split swfetch function --- src/worker/fetch.ts | 264 +++++++++++++++++++++++--------------------- 1 file changed, 139 insertions(+), 125 deletions(-) diff --git a/src/worker/fetch.ts b/src/worker/fetch.ts index f657d8c..c6af2a6 100644 --- a/src/worker/fetch.ts +++ b/src/worker/fetch.ts @@ -4,15 +4,17 @@ import { ParseResultType } from "parse-domain"; import { ScramjetServiceWorker } from "."; import { renderError } from "./error"; +const { encodeUrl, decodeUrl } = self.$scramjet.shared.url; +const { rewriteHeaders, rewriteHtml, rewriteJs, rewriteCss, rewriteWorkers } = + self.$scramjet.shared.rewrite; +const { parseDomain } = self.$scramjet.shared.util; + export async function swfetch( this: ScramjetServiceWorker, { request }: FetchEvent ) { const urlParam = new URLSearchParams(new URL(request.url).search); - const { encodeUrl, decodeUrl } = self.$scramjet.shared.url; - const { rewriteHeaders, rewriteHtml, rewriteJs, rewriteCss, rewriteWorkers } = - self.$scramjet.shared.rewrite; - const { parseDomain } = self.$scramjet.shared.util; + if (urlParam.has("url")) { return Response.redirect( @@ -23,11 +25,6 @@ export async function swfetch( try { const url = new URL(decodeUrl(request.url)); - const cookieStore = new IDBMap(url.host, { - durability: "relaxed", - prefix: "Cookies", - }); - const response: BareResponseFetch = await this.client.fetch(url, { method: request.method, body: request.body, @@ -40,123 +37,8 @@ export async function swfetch( duplex: "half", }); - let responseBody; - const responseHeaders = rewriteHeaders(response.rawHeaders, url); - for (const cookie of (responseHeaders["set-cookie"] || []) as string[]) { - let cookieParsed = cookie.split(";").map((x) => x.trim().split("=")); - - let [key, value] = cookieParsed.shift(); - if (!value) continue; - value = value.replace('"', ""); - - const hostArg = cookieParsed.find((x) => x[0] === "Domain"); - cookieParsed = cookieParsed.filter((x) => x[0] !== "Domain"); - let host = hostArg ? hostArg[1] : undefined; - - if (url.protocol === "http" && cookieParsed.includes(["Secure"])) - continue; - if ( - cookieParsed.includes(["SameSite", "None"]) && - !cookieParsed.includes(["Secure"]) - ) - continue; - - if (host && host !== url.host) { - if (host.startsWith(".")) host = host.slice(1); - const urlDomain = parseDomain(url.hostname); - - if (urlDomain.type === ParseResultType.Listed) { - const { subDomains: domain, topLevelDomains } = urlDomain; - if (!host.endsWith([domain, ...topLevelDomains].join("."))) continue; - } else { - continue; - } - - const realCookieStore = new IDBMap(host, { - durability: "relaxed", - prefix: "Cookies", - }); - realCookieStore.set(key, { - value: value, - args: cookieParsed, - subdomain: true, - }); - } else { - cookieStore.set(key, { - value: value, - args: cookieParsed, - subdomain: false, - }); - } - } - - for (const header in responseHeaders) { - // flatten everything past here - if (Array.isArray(responseHeaders[header])) - responseHeaders[header] = responseHeaders[header][0]; - } - - if (response.body) { - switch (request.destination) { - case "iframe": - case "document": - if ( - responseHeaders["content-type"]?.toString()?.startsWith("text/html") - ) { - responseBody = rewriteHtml(await response.text(), url); - } else { - responseBody = response.body; - } - break; - case "script": - responseBody = rewriteJs(await response.arrayBuffer(), url); - // Disable threading for now, it's causing issues. - // responseBody = await this.threadpool.rewriteJs(await responseBody.arrayBuffer(), url.toString()); - break; - case "style": - responseBody = rewriteCss(await response.text(), url); - break; - case "sharedworker": - case "worker": - responseBody = rewriteWorkers(await response.text(), url); - break; - default: - responseBody = response.body; - break; - } - } - // downloads - if (["document", "iframe"].includes(request.destination)) { - const header = responseHeaders["content-disposition"]; - - // validate header and test for filename - 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 it"s invalid then we can still possibly test for the attachment/inline type - const type = /^\s*?attachment/i.test(header) ? "attachment" : "inline"; - - // set the filename - const [filename] = new URL(response.finalURL).pathname - .split("/") - .slice(-1); - - responseHeaders["content-disposition"] = - `${type}; filename=${JSON.stringify(filename)}`; - } - } - if (responseHeaders["accept"] === "text/event-stream") { - responseHeaders["content-type"] = "text/event-stream"; - } - if (crossOriginIsolated) { - responseHeaders["Cross-Origin-Embedder-Policy"] = "require-corp"; - } - - return new Response(responseBody, { - headers: responseHeaders as HeadersInit, - status: response.status, - statusText: response.statusText, - }); + return await handleResponse(url, request.destination, response); } catch (err) { console.error("ERROR FROM SERVICE WORKER FETCH", err); if (!["document", "iframe"].includes(request.destination)) @@ -165,3 +47,135 @@ export async function swfetch( return renderError(err, decodeUrl(request.url)); } } + + +async function handleResponse(url: URL, destination: RequestDestination, response: BareResponseFetch): Promise { + let responseBody: string | ArrayBuffer | ReadableStream; + const responseHeaders = rewriteHeaders(response.rawHeaders, url); + + + await handleCookies(url, (responseHeaders["set-cookie"] || []) as string[]) + + for (const header in responseHeaders) { + // flatten everything past here + if (Array.isArray(responseHeaders[header])) + responseHeaders[header] = responseHeaders[header][0]; + } + + if (response.body) { + switch (destination) { + case "iframe": + case "document": + if ( + responseHeaders["content-type"]?.toString()?.startsWith("text/html") + ) { + responseBody = rewriteHtml(await response.text(), url); + } else { + responseBody = response.body; + } + break; + case "script": + responseBody = rewriteJs(await response.arrayBuffer(), url); + // Disable threading for now, it's causing issues. + // responseBody = await this.threadpool.rewriteJs(await responseBody.arrayBuffer(), url.toString()); + break; + case "style": + responseBody = rewriteCss(await response.text(), url); + break; + case "sharedworker": + case "worker": + responseBody = rewriteWorkers(await response.text(), url); + break; + default: + responseBody = response.body; + break; + } + } + // downloads + if (["document", "iframe"].includes(destination)) { + const header = responseHeaders["content-disposition"]; + + // validate header and test for filename + 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 it"s invalid then we can still possibly test for the attachment/inline type + const type = /^\s*?attachment/i.test(header) ? "attachment" : "inline"; + + // set the filename + const [filename] = new URL(response.finalURL).pathname + .split("/") + .slice(-1); + + responseHeaders["content-disposition"] = + `${type}; filename=${JSON.stringify(filename)}`; + } + } + if (responseHeaders["accept"] === "text/event-stream") { + responseHeaders["content-type"] = "text/event-stream"; + } + if (crossOriginIsolated) { + responseHeaders["Cross-Origin-Embedder-Policy"] = "require-corp"; + } + + return new Response(responseBody, { + headers: responseHeaders as HeadersInit, + status: response.status, + statusText: response.statusText, + }); +} + + +async function handleCookies(url: URL, headers: string[]) { + const cookieStore = new IDBMap(url.host, { + durability: "relaxed", + prefix: "Cookies", + }); + + for (const cookie of headers) { + let cookieParsed = cookie.split(";").map((x) => x.trim().split("=")); + + let [key, value] = cookieParsed.shift(); + if (!value) continue; + value = value.replace('"', ""); + + const hostArg = cookieParsed.find((x) => x[0] === "Domain"); + cookieParsed = cookieParsed.filter((x) => x[0] !== "Domain"); + let host = hostArg ? hostArg[1] : undefined; + + if (url.protocol === "http" && cookieParsed.includes(["Secure"])) + continue; + if ( + cookieParsed.includes(["SameSite", "None"]) && + !cookieParsed.includes(["Secure"]) + ) + continue; + + if (host && host !== url.host) { + if (host.startsWith(".")) host = host.slice(1); + const urlDomain = parseDomain(url.hostname); + + if (urlDomain.type === ParseResultType.Listed) { + const { subDomains: domain, topLevelDomains } = urlDomain; + if (!host.endsWith([domain, ...topLevelDomains].join("."))) continue; + } else { + continue; + } + + const realCookieStore = new IDBMap(host, { + durability: "relaxed", + prefix: "Cookies", + }); + realCookieStore.set(key, { + value: value, + args: cookieParsed, + subdomain: true, + }); + } else { + cookieStore.set(key, { + value: value, + args: cookieParsed, + subdomain: false, + }); + } + } +}