Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- addEventListener("fetch", e => e.respondWith(handle(e.request)))
- async function handle(request) {
- if (request.method === "OPTIONS") {
- return new Response(null, { status: 204, headers: corsHeaders() })
- }
- const reqUrl = new URL(request.url)
- const proxyBase = `${reqUrl.origin}${reqUrl.pathname}`
- let target = reqUrl.searchParams.get("url")
- if (!target || typeof target !== "string") {
- return new Response("Missing or invalid `url` param", {
- status: 400,
- headers: corsHeaders()
- })
- }
- // unwrap nested proxies
- while (true) {
- let tmp
- try {
- tmp = new URL(target)
- } catch {
- break
- }
- if (tmp.origin === reqUrl.origin && tmp.pathname === reqUrl.pathname) {
- const inner = tmp.searchParams.get("url")
- if (!inner) break
- target = inner
- continue
- }
- break
- }
- // validate upstream URL
- let upstream
- try {
- upstream = new URL(target)
- } catch {
- return new Response("Invalid `url` param", {
- status: 400,
- headers: corsHeaders()
- })
- }
- // forward request
- const init = {
- method: request.method,
- headers: overrideHost(request.headers, upstream.host),
- redirect: "follow"
- }
- if (!["GET","HEAD"].includes(request.method)) {
- init.body = await request.clone().arrayBuffer()
- }
- const res = await fetch(upstream.toString(), init)
- const contentType = res.headers.get("Content-Type") || ""
- const runtimePatcher = buildRuntimePatcher(proxyBase, upstream.origin)
- // HTML
- if (contentType.includes("text/html")) {
- const dir = upstream.pathname.replace(/\/[^/]*$/, "/")
- const baseHref = proxyBase + "?url=" + encodeURIComponent(upstream.origin + dir)
- const transformed = new HTMLRewriter()
- .on("head", {
- element(el) {
- el.prepend(
- `<base href="${baseHref}">` +
- `<script>${runtimePatcher}</script>`,
- { html: true }
- )
- }
- })
- .on("meta[http-equiv='Content-Security-Policy']", { element(el){ el.remove() } })
- .on("a[href]", new AttrRewriter("href", proxyBase, upstream.origin))
- .on("script[src]", new AttrRewriter("src", proxyBase, upstream.origin))
- .on("link[href]", new AttrRewriter("href", proxyBase, upstream.origin))
- .on("img[src]", new AttrRewriter("src", proxyBase, upstream.origin))
- .on("img[srcset]", new SrcsetRewriter(proxyBase, upstream.origin))
- .on("source[src]", new AttrRewriter("src", proxyBase, upstream.origin))
- .on("video[poster]",new AttrRewriter("poster", proxyBase, upstream.origin))
- .on("iframe[src]", new AttrRewriter("src", proxyBase, upstream.origin))
- .on("form[action]", new AttrRewriter("action", proxyBase, upstream.origin))
- .transform(res)
- return new Response(transformed.body, {
- status: transformed.status,
- headers: mergeHeaders(
- stripCSP(new Headers(transformed.headers)),
- corsHeaders(),
- { "Content-Type": "text/html; charset=UTF-8" }
- )
- })
- }
- // CSS
- if (contentType.includes("text/css")) {
- const css = await res.text()
- const body = css
- .replace(/url\((['"]?)(https?:\/\/[^'")]+)\1\)/g,
- (_,q,u)=>`url(${q}${makeProxy(u,proxyBase)}${q})`)
- .replace(/url\((['"]?)(\/[^'")]+)\1\)/g,
- (_,q,p)=>`url(${q}${makeProxy(upstream.origin+p,proxyBase)}${q})`)
- return new Response(body, {
- status: res.status,
- headers: mergeHeaders(
- stripCSP(new Headers({ "Content-Type":"text/css; charset=UTF-8" })),
- corsHeaders()
- )
- })
- }
- // JavaScript
- if (contentType.includes("javascript")) {
- const raw = await res.text()
- const isModule = /^\s*import\s+[\w{]/.test(raw)
- let js = isModule ? raw : runtimePatcher + "\n" + raw
- js = js
- .replace(/(['"`])\/([^'"`]+)\1/g,
- (_,q,p)=>q+makeProxy(upstream.origin+"/"+p,proxyBase)+q)
- .replace(/(['"`])\/\/([^'"`]+)\1/g,
- (_,q,h)=>q+makeProxy("https://"+h,proxyBase)+q)
- .replace(/(['"`])(https?:\/\/[^'"`]+)\1/g,
- (_,q,u)=>q+makeProxy(u,proxyBase)+q)
- return new Response(js, {
- status: res.status,
- headers: mergeHeaders(
- stripCSP(new Headers({ "Content-Type":"application/javascript; charset=UTF-8" })),
- corsHeaders()
- )
- })
- }
- // Others
- if (!contentType.includes("text/html") &&
- !contentType.includes("text/css") &&
- !contentType.includes("javascript")) {
- return res
- }
- }
- /**–– Helpers ––**/
- function makeProxy(raw, base) {
- return `${base}?url=${encodeURIComponent(raw)}`
- }
- function overrideHost(orig, host) {
- const h = new Headers(orig)
- h.set("host", host)
- return h
- }
- function corsHeaders() {
- return {
- "Access-Control-Allow-Origin": "*",
- "Access-Control-Allow-Methods": "GET,POST,PUT,PATCH,DELETE,OPTIONS",
- "Access-Control-Allow-Headers": "*"
- }
- }
- function stripCSP(h) {
- const out = new Headers(h)
- out.delete("content-security-policy")
- out.delete("content-security-policy-report-only")
- return out
- }
- function mergeHeaders(...sources) {
- const h = new Headers()
- for (const src of sources) {
- if (src instanceof Headers) {
- for (const [k,v] of src.entries()) h.set(k,v)
- } else {
- for (const [k,v] of Object.entries(src)) h.set(k,v)
- }
- }
- return h
- }
- /** rewrite HTML attributes */
- class AttrRewriter {
- constructor(attr, proxyBase, origin) {
- this.attr = attr
- this.proxyBase = proxyBase
- this.origin = origin
- }
- element(el) {
- const raw = el.getAttribute(this.attr)
- if (!raw) return
- let abs
- try { abs = new URL(raw, this.origin).toString() }
- catch { return }
- if (abs.startsWith(this.proxyBase)) return
- el.setAttribute(this.attr, makeProxy(abs, this.proxyBase))
- }
- }
- /** rewrite srcset lists */
- class SrcsetRewriter {
- constructor(proxyBase, origin) {
- this.proxyBase = proxyBase
- this.origin = origin
- }
- element(el) {
- const srcset = el.getAttribute("srcset")
- if (!srcset) return
- const parts = srcset.split(/\s*,\s*/)
- const out = parts.map(p=>{
- const [url, desc] = p.split(/\s+/,2)
- let abs
- try { abs = new URL(url, this.origin).toString() }
- catch { return p }
- const prox = makeProxy(abs, this.proxyBase)
- return desc ? `${prox} ${desc}` : prox
- })
- el.setAttribute("srcset", out.join(", "))
- }
- }
- /** build a script that forces all in-page network calls through your proxy */
- function buildRuntimePatcher(proxyBase, ORG) {
- const PB = JSON.stringify(proxyBase)
- const O = JSON.stringify(ORG)
- return `
- ;(function(){
- const PB=${PB}, ORG=${O}, enc=encodeURIComponent;
- function shouldProxy(u){
- return typeof u==="string" && /^https?:/.test(u) && !u.startsWith(PB)
- }
- // patch fetch
- const _fetch = window.fetch
- window.fetch = function(input, init){
- try {
- let u = input instanceof Request ? input.url : input
- if (typeof u !== "string") return _fetch.call(this, input, init)
- if (u.startsWith("/")) u = ORG + u
- if (shouldProxy(u)) u = PB + "?url=" + enc(u)
- const req = input instanceof Request
- ? new Request(u, input)
- : u
- return _fetch.call(this, req, init)
- } catch (err) {
- console.error("Proxy fetch error:", err)
- return _fetch.call(this, input, init)
- }
- }
- // patch XHR.open
- const _xo = XMLHttpRequest.prototype.open
- XMLHttpRequest.prototype.open = function(m, u, ...a){
- try {
- if (typeof u==="string" && u.startsWith("/")) u = ORG + u
- if (shouldProxy(u)) u = PB + "?url=" + enc(u)
- } catch (_) {}
- return _xo.call(this, m, u, ...a)
- }
- // patch WebSocket
- const _ws = window.WebSocket
- window.WebSocket = function(u, ...a){
- let url = u
- try {
- if (typeof url==="string" && url.startsWith("/")) url = ORG + url
- if (shouldProxy(url)) url = PB + "?url=" + enc(url)
- } catch (_) {}
- return new _ws(url, ...a)
- }
- // patch EventSource
- const _es = window.EventSource
- window.EventSource = function(u, opts){
- let url = u
- try {
- if (typeof url==="string" && url.startsWith("/")) url = ORG + url
- if (shouldProxy(url)) url = PB + "?url=" + enc(url)
- } catch (_) {}
- return new _es(url, opts)
- }
- // patch Worker & SharedWorker
- const _Worker = window.Worker
- window.Worker = function(u, opts){
- let url = u
- try {
- if (typeof url==="string" && url.startsWith("/")) url = ORG + url
- if (shouldProxy(url)) url = PB + "?url=" + enc(url)
- } catch (_) {}
- return new _Worker(url, opts)
- }
- if (window.SharedWorker) {
- const _SW = window.SharedWorker
- window.SharedWorker = function(u, name){
- let url = u
- try {
- if (typeof url==="string" && url.startsWith("/")) url = ORG + url
- if (shouldProxy(url)) url = PB + "?url=" + enc(url)
- } catch (_) {}
- return new _SW(url, name)
- }
- }
- // patch importScripts
- if (typeof importScripts==="function") {
- const _is = importScripts
- importScripts = function(...urls){
- urls = urls.map(u=>{
- try {
- if (u.startsWith("/")) u = ORG + u
- if (shouldProxy(u)) u = PB + "?url=" + enc(u)
- } catch(_) {}
- return u
- })
- return _is.apply(this, urls)
- }
- }
- // patch element.src & href
- [
- ["src", [HTMLScriptElement, HTMLImageElement, HTMLIFrameElement,
- HTMLVideoElement, HTMLAudioElement]],
- ["href",[HTMLAnchorElement, HTMLLinkElement]]
- ].forEach(([prop, ctors])=>{
- ctors.forEach(C=>{
- const desc = Object.getOwnPropertyDescriptor(C.prototype, prop)
- Object.defineProperty(C.prototype, prop, {
- get: desc.get,
- set(v){
- let url = v
- try {
- if (typeof url==="string" && url.startsWith("/")) url = ORG + url
- if (shouldProxy(url)) url = PB + "?url=" + enc(url)
- } catch(_) {}
- desc.set.call(this, url)
- }
- })
- })
- })
- })();`.trim()
- }
Advertisement
Add Comment
Please, Sign In to add comment