offsite 0.0.61 → 0.0.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -2,7 +2,7 @@ import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { appendFileSync, chmodSync, closeSync, createReadStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
|
|
3
3
|
import http, { createServer } from "node:http";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { createGzip } from "node:zlib";
|
|
5
|
+
import zlib, { createGzip } from "node:zlib";
|
|
6
6
|
import { consola } from "consola";
|
|
7
7
|
import { WebSocketServer } from "ws";
|
|
8
8
|
import { z } from "zod";
|
|
@@ -25,9 +25,17 @@ import { integer, real, snakeCase, text } from "drizzle-orm/sqlite-core";
|
|
|
25
25
|
import sharp from "sharp";
|
|
26
26
|
import { promisify } from "node:util";
|
|
27
27
|
import webPush from "web-push";
|
|
28
|
-
import
|
|
29
|
-
import {
|
|
28
|
+
import dns from "node:dns";
|
|
29
|
+
import { BlockList, isIP } from "node:net";
|
|
30
|
+
import { CookieJar, getPublicSuffix } from "tough-cookie";
|
|
30
31
|
import { spawn as spawn$1 } from "@lydell/node-pty";
|
|
32
|
+
import { once } from "node:events";
|
|
33
|
+
import https from "node:https";
|
|
34
|
+
import { buffer } from "node:stream/consumers";
|
|
35
|
+
import { pipeline } from "node:stream/promises";
|
|
36
|
+
import { defaultTreeAdapter, html, parse, serialize } from "parse5";
|
|
37
|
+
import { MagicString } from "magic-string";
|
|
38
|
+
import { parseSync } from "oxc-parser";
|
|
31
39
|
// ../core/src/media.ts
|
|
32
40
|
const mediaTypes = /* @__PURE__ */ new Map([
|
|
33
41
|
[".png", "image/png"],
|
|
@@ -2771,6 +2779,7 @@ const pushSubscriptions = snakeCase.table("push_subscriptions", {
|
|
|
2771
2779
|
});
|
|
2772
2780
|
// src/services/files.ts
|
|
2773
2781
|
const FILE_LIMIT = 2e4;
|
|
2782
|
+
const SNIFF_LENGTH = 8e3;
|
|
2774
2783
|
const OUTPUT_LIMIT$1 = 262144;
|
|
2775
2784
|
const READ_LIMIT$1 = 1048576;
|
|
2776
2785
|
const MEDIA_LINK_LIFETIME = 36e5;
|
|
@@ -2882,6 +2891,18 @@ function linkProjectDownload(cwd, file) {
|
|
|
2882
2891
|
if (!statSync(full).isFile()) throw new Error(`${file} is not a file`);
|
|
2883
2892
|
return addLink(full, "application/octet-stream");
|
|
2884
2893
|
}
|
|
2894
|
+
function setBrowseHeaders(response, contentType) {
|
|
2895
|
+
response.setHeader("content-type", contentType);
|
|
2896
|
+
response.setHeader("cache-control", "no-cache");
|
|
2897
|
+
if (/^(text\/html|image\/svg|application\/xml)/.test(contentType)) response.setHeader("content-security-policy", "sandbox allow-scripts allow-forms allow-popups allow-modals allow-downloads");
|
|
2898
|
+
}
|
|
2899
|
+
function sniffType(file) {
|
|
2900
|
+
const head = Buffer.alloc(SNIFF_LENGTH);
|
|
2901
|
+
const handle = openSync(file, "r");
|
|
2902
|
+
const read = readSync(handle, head, 0, SNIFF_LENGTH, 0);
|
|
2903
|
+
closeSync(handle);
|
|
2904
|
+
return head.subarray(0, read).includes(0) ? "application/octet-stream" : "text/plain; charset=utf-8";
|
|
2905
|
+
}
|
|
2885
2906
|
function addLink(file, mediaType) {
|
|
2886
2907
|
for (const [token, link] of mediaLinks) if (link.expiresAt <= Date.now()) mediaLinks.delete(token);
|
|
2887
2908
|
const token = randomBytes(32).toString("base64url");
|
|
@@ -5553,6 +5574,114 @@ var Run = class {
|
|
|
5553
5574
|
});
|
|
5554
5575
|
}
|
|
5555
5576
|
};
|
|
5577
|
+
// src/lib/proxy/sites.ts
|
|
5578
|
+
const sites = /* @__PURE__ */ new Map();
|
|
5579
|
+
const siteNames = /* @__PURE__ */ new Map();
|
|
5580
|
+
const privateAddresses = new BlockList();
|
|
5581
|
+
for (const range of [
|
|
5582
|
+
["0.0.0.0", 8],
|
|
5583
|
+
["10.0.0.0", 8],
|
|
5584
|
+
["100.64.0.0", 10],
|
|
5585
|
+
["127.0.0.0", 8],
|
|
5586
|
+
["169.254.0.0", 16],
|
|
5587
|
+
["172.16.0.0", 12],
|
|
5588
|
+
["192.0.0.0", 24],
|
|
5589
|
+
["192.168.0.0", 16],
|
|
5590
|
+
["198.18.0.0", 15],
|
|
5591
|
+
["224.0.0.0", 4],
|
|
5592
|
+
["240.0.0.0", 4]
|
|
5593
|
+
]) privateAddresses.addSubnet(range[0], range[1], "ipv4");
|
|
5594
|
+
for (const range of [
|
|
5595
|
+
["::", 128],
|
|
5596
|
+
["::1", 128],
|
|
5597
|
+
["fc00::", 7],
|
|
5598
|
+
["fe80::", 10],
|
|
5599
|
+
["ff00::", 8]
|
|
5600
|
+
]) privateAddresses.addSubnet(range[0], range[1], "ipv6");
|
|
5601
|
+
function proxiedPath(key, target) {
|
|
5602
|
+
return `/api/proxy/${key}/${target.protocol === "https:" ? "https" : "http"}/${target.host}${target.pathname}${target.search}${target.hash}`;
|
|
5603
|
+
}
|
|
5604
|
+
function linkRewriter(key, base) {
|
|
5605
|
+
return (value) => {
|
|
5606
|
+
const text = value.trim();
|
|
5607
|
+
if (text === "" || text.startsWith("#")) return value;
|
|
5608
|
+
if (text.startsWith(`/api/proxy/${key}/`)) return text;
|
|
5609
|
+
const target = URL.parse(text, base);
|
|
5610
|
+
if (target === null || target.protocol !== "http:" && target.protocol !== "https:") return value;
|
|
5611
|
+
return proxiedPath(key, target);
|
|
5612
|
+
};
|
|
5613
|
+
}
|
|
5614
|
+
function findSite(key) {
|
|
5615
|
+
return sites.get(key) ?? null;
|
|
5616
|
+
}
|
|
5617
|
+
async function openSite(address, fromPage) {
|
|
5618
|
+
const target = new URL(address);
|
|
5619
|
+
if (target.protocol !== "http:" && target.protocol !== "https:") throw new Error(`${address} is not an http or https address`);
|
|
5620
|
+
const local = await isLocalHost(hostOf(target));
|
|
5621
|
+
if (local && fromPage) throw new Error("A website cannot open an address on this machine");
|
|
5622
|
+
const name = local ? `local ${target.hostname}` : `public ${getPublicSuffix(target.hostname) ?? target.hostname}`;
|
|
5623
|
+
const existing = siteNames.get(name);
|
|
5624
|
+
if (existing !== void 0) return proxiedPath(existing.key, target);
|
|
5625
|
+
const site = {
|
|
5626
|
+
key: randomBytes(16).toString("hex"),
|
|
5627
|
+
local,
|
|
5628
|
+
jar: new CookieJar(void 0, {
|
|
5629
|
+
allowSpecialUseDomain: true,
|
|
5630
|
+
rejectPublicSuffixes: !local
|
|
5631
|
+
}),
|
|
5632
|
+
storage: /* @__PURE__ */ new Map()
|
|
5633
|
+
};
|
|
5634
|
+
sites.set(site.key, site);
|
|
5635
|
+
siteNames.set(name, site);
|
|
5636
|
+
return proxiedPath(site.key, target);
|
|
5637
|
+
}
|
|
5638
|
+
function hostOf(target) {
|
|
5639
|
+
return target.hostname.replace(/^\[|\]$/g, "");
|
|
5640
|
+
}
|
|
5641
|
+
function checkAddress(site, address, port, ownPort) {
|
|
5642
|
+
if (!isPrivate(address)) return;
|
|
5643
|
+
if (!site.local) throw new Error("Websites cannot reach addresses on this machine");
|
|
5644
|
+
if (port === ownPort) throw new Error("Pages cannot reach Offsite itself");
|
|
5645
|
+
}
|
|
5646
|
+
function guardedLookup(site, port, ownPort) {
|
|
5647
|
+
return (hostname, options, callback) => {
|
|
5648
|
+
dns.lookup(hostname, {
|
|
5649
|
+
...options,
|
|
5650
|
+
all: true
|
|
5651
|
+
}, (error, addresses) => {
|
|
5652
|
+
if (error !== null) {
|
|
5653
|
+
callback(error, "", 0);
|
|
5654
|
+
return;
|
|
5655
|
+
}
|
|
5656
|
+
try {
|
|
5657
|
+
for (const item of addresses) checkAddress(site, item.address, port, ownPort);
|
|
5658
|
+
} catch (caught) {
|
|
5659
|
+
callback(caught, "", 0);
|
|
5660
|
+
return;
|
|
5661
|
+
}
|
|
5662
|
+
if (options.all === true) {
|
|
5663
|
+
callback(null, addresses);
|
|
5664
|
+
return;
|
|
5665
|
+
}
|
|
5666
|
+
const first = addresses[0];
|
|
5667
|
+
if (first === void 0) {
|
|
5668
|
+
callback(/* @__PURE__ */ new Error(`${hostname} has no address`), "", 0);
|
|
5669
|
+
return;
|
|
5670
|
+
}
|
|
5671
|
+
callback(null, first.address, first.family);
|
|
5672
|
+
});
|
|
5673
|
+
};
|
|
5674
|
+
}
|
|
5675
|
+
function isPrivate(address) {
|
|
5676
|
+
const family = isIP(address);
|
|
5677
|
+
if (family === 4) return privateAddresses.check(address, "ipv4");
|
|
5678
|
+
if (family === 6) return privateAddresses.check(address, "ipv6");
|
|
5679
|
+
throw new Error(`${address} is not an IP address`);
|
|
5680
|
+
}
|
|
5681
|
+
async function isLocalHost(hostname) {
|
|
5682
|
+
if (isIP(hostname) !== 0) return isPrivate(hostname);
|
|
5683
|
+
return (await dns.promises.lookup(hostname, { all: true })).some((item) => isPrivate(item.address));
|
|
5684
|
+
}
|
|
5556
5685
|
// src/services/message-search.ts
|
|
5557
5686
|
const READ_LIMIT = 30;
|
|
5558
5687
|
const CODEX_READ_LIMIT = 10;
|
|
@@ -5687,140 +5816,6 @@ function bestMatch(query, sessionId, messages) {
|
|
|
5687
5816
|
}
|
|
5688
5817
|
return best;
|
|
5689
5818
|
}
|
|
5690
|
-
// src/services/proxy.ts
|
|
5691
|
-
const proxies = /* @__PURE__ */ new Map();
|
|
5692
|
-
const pageScriptPath = "/__offsite/browser.js";
|
|
5693
|
-
function pageScript(origin) {
|
|
5694
|
-
return `(() => {
|
|
5695
|
-
if (window.parent === window || window.navigation === undefined) {
|
|
5696
|
-
return
|
|
5697
|
-
}
|
|
5698
|
-
const post = (message) =>
|
|
5699
|
-
window.parent.postMessage({ offsite: 'browser', ...message }, '*')
|
|
5700
|
-
const report = () =>
|
|
5701
|
-
post({
|
|
5702
|
-
event: 'location',
|
|
5703
|
-
url: ${JSON.stringify(origin)} + location.pathname + location.search + location.hash,
|
|
5704
|
-
proxied: location.href,
|
|
5705
|
-
back: navigation.canGoBack,
|
|
5706
|
-
forward: navigation.canGoForward,
|
|
5707
|
-
})
|
|
5708
|
-
addEventListener('pageshow', report)
|
|
5709
|
-
navigation.addEventListener('currententrychange', report)
|
|
5710
|
-
navigation.addEventListener('navigate', (event) => {
|
|
5711
|
-
if (!event.destination.sameDocument && event.downloadRequest === null) {
|
|
5712
|
-
post({ event: 'navigate' })
|
|
5713
|
-
}
|
|
5714
|
-
})
|
|
5715
|
-
addEventListener('message', (event) => {
|
|
5716
|
-
if (event.source !== window.parent || event.data?.offsite !== 'browser') {
|
|
5717
|
-
return
|
|
5718
|
-
}
|
|
5719
|
-
if (event.data.command === 'back') {
|
|
5720
|
-
navigation.back()
|
|
5721
|
-
}
|
|
5722
|
-
if (event.data.command === 'forward') {
|
|
5723
|
-
navigation.forward()
|
|
5724
|
-
}
|
|
5725
|
-
if (event.data.command === 'reload') {
|
|
5726
|
-
location.reload()
|
|
5727
|
-
}
|
|
5728
|
-
})
|
|
5729
|
-
})()
|
|
5730
|
-
`;
|
|
5731
|
-
}
|
|
5732
|
-
function proxyPort(origin) {
|
|
5733
|
-
const target = new URL(origin);
|
|
5734
|
-
if (target.protocol !== "http:" && target.protocol !== "https:") throw new Error(`${origin} is not an http or https address`);
|
|
5735
|
-
const existing = proxies.get(target.origin);
|
|
5736
|
-
if (existing !== void 0) return existing;
|
|
5737
|
-
const started = startProxy(target);
|
|
5738
|
-
proxies.set(target.origin, started);
|
|
5739
|
-
started.catch(() => proxies.delete(target.origin));
|
|
5740
|
-
return started;
|
|
5741
|
-
}
|
|
5742
|
-
function startProxy(target) {
|
|
5743
|
-
const options = {
|
|
5744
|
-
target,
|
|
5745
|
-
changeOrigin: true,
|
|
5746
|
-
ws: true,
|
|
5747
|
-
secure: false,
|
|
5748
|
-
autoRewrite: true,
|
|
5749
|
-
protocolRewrite: "http",
|
|
5750
|
-
cookieDomainRewrite: "",
|
|
5751
|
-
selfHandleResponse: true
|
|
5752
|
-
};
|
|
5753
|
-
const proxy = createProxyServer(options);
|
|
5754
|
-
proxy.on("proxyReq", (outgoing, request) => {
|
|
5755
|
-
rewriteOrigin(target, outgoing, request);
|
|
5756
|
-
if (request.headers.accept?.startsWith("text/html") === true) outgoing.removeHeader("accept-encoding");
|
|
5757
|
-
});
|
|
5758
|
-
proxy.on("proxyReqWs", (outgoing, request) => rewriteOrigin(target, outgoing, request));
|
|
5759
|
-
proxy.on("proxyRes", async (incoming, request, response) => {
|
|
5760
|
-
try {
|
|
5761
|
-
await rewriteRedirect(target, incoming, request);
|
|
5762
|
-
} catch (error) {
|
|
5763
|
-
response.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
|
|
5764
|
-
response.end(`Could not follow the redirect from ${target.origin}: ${error instanceof Error ? error.message : String(error)}`);
|
|
5765
|
-
return;
|
|
5766
|
-
}
|
|
5767
|
-
delete incoming.headers["x-frame-options"];
|
|
5768
|
-
delete incoming.headers["content-security-policy"];
|
|
5769
|
-
if (incoming.headers["set-cookie"] !== void 0) incoming.headers["set-cookie"] = incoming.headers["set-cookie"].map((cookie) => cookie.replace(/;\s*secure/i, "").replace(/;\s*samesite=none/i, ""));
|
|
5770
|
-
const page = request.method === "GET" && request.headers.accept?.startsWith("text/html") === true && incoming.statusCode !== 204 && incoming.statusCode !== 304 && /^text\/html/i.test(incoming.headers["content-type"] ?? "") && incoming.headers["content-encoding"] === void 0;
|
|
5771
|
-
if (page) delete incoming.headers["content-length"];
|
|
5772
|
-
for (const pass of Object.values(OUTGOING_PASSES)) pass(request, response, incoming, options);
|
|
5773
|
-
if (!page) {
|
|
5774
|
-
incoming.pipe(response);
|
|
5775
|
-
return;
|
|
5776
|
-
}
|
|
5777
|
-
const chunks = [];
|
|
5778
|
-
incoming.on("data", (chunk) => chunks.push(chunk));
|
|
5779
|
-
incoming.on("end", () => response.end(Buffer.from(Buffer.concat(chunks).toString("latin1").replace(/^(\xEF\xBB\xBF)?\s*(<!doctype[^>]*>)?/i, `$&<script src="${pageScriptPath}"><\/script>`), "latin1")));
|
|
5780
|
-
});
|
|
5781
|
-
proxy.on("error", (error, _request, response) => {
|
|
5782
|
-
if (!(response instanceof http.ServerResponse)) {
|
|
5783
|
-
response.destroy();
|
|
5784
|
-
return;
|
|
5785
|
-
}
|
|
5786
|
-
if (!response.headersSent) response.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
|
|
5787
|
-
response.end(`Could not reach ${target.origin}: ${error.message}`);
|
|
5788
|
-
});
|
|
5789
|
-
const server = http.createServer((request, response) => {
|
|
5790
|
-
if (request.url === pageScriptPath) {
|
|
5791
|
-
response.writeHead(200, { "content-type": "text/javascript; charset=utf-8" });
|
|
5792
|
-
response.end(pageScript(target.origin));
|
|
5793
|
-
return;
|
|
5794
|
-
}
|
|
5795
|
-
proxy.web(request, response);
|
|
5796
|
-
});
|
|
5797
|
-
server.on("upgrade", (request, socket, head) => proxy.ws(request, socket, head));
|
|
5798
|
-
return new Promise((resolve, reject) => {
|
|
5799
|
-
server.once("error", reject);
|
|
5800
|
-
server.listen(0, args.host, () => {
|
|
5801
|
-
const address = server.address();
|
|
5802
|
-
if (address === null || typeof address === "string") {
|
|
5803
|
-
reject(/* @__PURE__ */ new Error(`the proxy for ${target.origin} has no port`));
|
|
5804
|
-
return;
|
|
5805
|
-
}
|
|
5806
|
-
resolve(address.port);
|
|
5807
|
-
});
|
|
5808
|
-
});
|
|
5809
|
-
}
|
|
5810
|
-
async function rewriteRedirect(target, incoming, request) {
|
|
5811
|
-
const location = incoming.headers.location;
|
|
5812
|
-
if (location === void 0 || !/^30[1278]$/.test(String(incoming.statusCode))) return;
|
|
5813
|
-
const next = new URL(location, target);
|
|
5814
|
-
if (next.origin === target.origin || next.protocol !== "http:" && next.protocol !== "https:") return;
|
|
5815
|
-
if (request.headers.host === void 0) throw new Error("the redirected request has no host");
|
|
5816
|
-
const proxied = new URL(`http://${request.headers.host}`);
|
|
5817
|
-
proxied.port = String(await proxyPort(next.origin));
|
|
5818
|
-
incoming.headers.location = `${proxied.origin}${next.pathname}${next.search}${next.hash}`;
|
|
5819
|
-
}
|
|
5820
|
-
function rewriteOrigin(target, outgoing, request) {
|
|
5821
|
-
if (request.headers.origin !== void 0) outgoing.setHeader("origin", target.origin);
|
|
5822
|
-
if (request.headers.referer !== void 0) outgoing.setHeader("referer", request.headers.referer.replace(`http://${request.headers.host}`, target.origin));
|
|
5823
|
-
}
|
|
5824
5819
|
// src/services/runs.ts
|
|
5825
5820
|
const locks = /* @__PURE__ */ new Map();
|
|
5826
5821
|
function stopAll() {
|
|
@@ -6961,7 +6956,7 @@ function attach(client) {
|
|
|
6961
6956
|
layout: layout(),
|
|
6962
6957
|
instance
|
|
6963
6958
|
});
|
|
6964
|
-
for (const tab of tabs) forward(connection, tab.id, true, Buffer.concat(terminals.get(tab.id)?.history ?? []));
|
|
6959
|
+
for (const tab of tabs) forward$1(connection, tab.id, true, Buffer.concat(terminals.get(tab.id)?.history ?? []));
|
|
6965
6960
|
}
|
|
6966
6961
|
function resize(connection, message) {
|
|
6967
6962
|
const terminal = terminals.get(message.id);
|
|
@@ -7097,7 +7092,7 @@ function output(id, terminal, data) {
|
|
|
7097
7092
|
terminal.historySize -= terminal.history[0].length;
|
|
7098
7093
|
terminal.history.shift();
|
|
7099
7094
|
}
|
|
7100
|
-
for (const connection of connections.values()) forward(connection, id, false, data);
|
|
7095
|
+
for (const connection of connections.values()) forward$1(connection, id, false, data);
|
|
7101
7096
|
scan(id, terminal, data);
|
|
7102
7097
|
if ([...connections.values()].some((connection) => connection.client.socket.bufferedAmount > OUTPUT_LIMIT)) {
|
|
7103
7098
|
terminal.pty.pause();
|
|
@@ -7202,7 +7197,7 @@ function publish() {
|
|
|
7202
7197
|
tabs
|
|
7203
7198
|
});
|
|
7204
7199
|
}
|
|
7205
|
-
function forward(connection, id, replay, data) {
|
|
7200
|
+
function forward$1(connection, id, replay, data) {
|
|
7206
7201
|
connection.client.socket.send(encodeFrame(replay ? 3 : 2, id, data));
|
|
7207
7202
|
}
|
|
7208
7203
|
function send(connection, event) {
|
|
@@ -7433,7 +7428,7 @@ const handlers = {
|
|
|
7433
7428
|
"files.download": (params) => linkProjectDownload(projectPath(params.projectId), params.path),
|
|
7434
7429
|
"files.upload": (params) => saveUpload(params.name, Buffer.from(params.data, "base64")),
|
|
7435
7430
|
"files.browse": () => browseToken,
|
|
7436
|
-
"browser.
|
|
7431
|
+
"browser.open": (params) => openSite(params.url, params.fromPage),
|
|
7437
7432
|
"tasks.output": (params) => taskOutput(params.sessionId, params.path),
|
|
7438
7433
|
"git.status": (params) => gitRepos(projectPath(params.projectId)),
|
|
7439
7434
|
"git.diff": (params) => projectDiff(projectPath(params.projectId)),
|
|
@@ -7537,6 +7532,1155 @@ async function answer(client, text) {
|
|
|
7537
7532
|
});
|
|
7538
7533
|
}
|
|
7539
7534
|
}
|
|
7535
|
+
// src/lib/proxy/css.ts
|
|
7536
|
+
function rewriteCss(source, rewrite) {
|
|
7537
|
+
return source.replace(/url\(\s*(['"]?)(.*?)\1\s*\)/gi, (_match, quote, value) => `url(${quote}${rewrite(value)}${quote})`).replace(/@import\s+(['"])(.*?)\1/gi, (_match, quote, value) => `@import ${quote}${rewrite(value)}${quote}`);
|
|
7538
|
+
}
|
|
7539
|
+
// src/lib/proxy/script.ts
|
|
7540
|
+
const CACHE_LIMIT = 500;
|
|
7541
|
+
const MARKERS = /\b(?:location|top|parent|import)\b/;
|
|
7542
|
+
const WINDOWS = /* @__PURE__ */ new Set([
|
|
7543
|
+
"window",
|
|
7544
|
+
"self",
|
|
7545
|
+
"globalThis",
|
|
7546
|
+
"frames"
|
|
7547
|
+
]);
|
|
7548
|
+
const FUNCTIONS = /* @__PURE__ */ new Set([
|
|
7549
|
+
"FunctionDeclaration",
|
|
7550
|
+
"FunctionExpression",
|
|
7551
|
+
"ArrowFunctionExpression"
|
|
7552
|
+
]);
|
|
7553
|
+
const PATTERNS = /* @__PURE__ */ new Set([
|
|
7554
|
+
"ObjectPattern",
|
|
7555
|
+
"ArrayPattern",
|
|
7556
|
+
"RestElement",
|
|
7557
|
+
"AssignmentPattern",
|
|
7558
|
+
"Property"
|
|
7559
|
+
]);
|
|
7560
|
+
const NAMES = /* @__PURE__ */ new Set([
|
|
7561
|
+
"ImportSpecifier",
|
|
7562
|
+
"ImportDefaultSpecifier",
|
|
7563
|
+
"ImportNamespaceSpecifier",
|
|
7564
|
+
"ExportSpecifier",
|
|
7565
|
+
"LabeledStatement",
|
|
7566
|
+
"BreakStatement",
|
|
7567
|
+
"ContinueStatement"
|
|
7568
|
+
]);
|
|
7569
|
+
const KEYS = /* @__PURE__ */ new Set([
|
|
7570
|
+
"Property",
|
|
7571
|
+
"MethodDefinition",
|
|
7572
|
+
"PropertyDefinition"
|
|
7573
|
+
]);
|
|
7574
|
+
const cache = /* @__PURE__ */ new Map();
|
|
7575
|
+
function rewriteScript(source, rewrite, scope) {
|
|
7576
|
+
if (!MARKERS.test(source)) return source;
|
|
7577
|
+
const id = createHash("sha1").update(scope).update(source).digest("base64");
|
|
7578
|
+
const known = cache.get(id);
|
|
7579
|
+
if (known !== void 0) return known;
|
|
7580
|
+
const rewritten = rewriteSource(source, rewrite);
|
|
7581
|
+
if (cache.size >= CACHE_LIMIT) cache.clear();
|
|
7582
|
+
cache.set(id, rewritten);
|
|
7583
|
+
return rewritten;
|
|
7584
|
+
}
|
|
7585
|
+
function rewriteHandler(source, rewrite) {
|
|
7586
|
+
const rewritten = rewriteSource(`function handler(event){${source}
|
|
7587
|
+
}`, rewrite);
|
|
7588
|
+
return rewritten.slice(24, rewritten.length - 2);
|
|
7589
|
+
}
|
|
7590
|
+
function rewriteSource(source, rewrite) {
|
|
7591
|
+
const parsed = parseSync("page.js", source, {
|
|
7592
|
+
lang: "js",
|
|
7593
|
+
sourceType: "unambiguous"
|
|
7594
|
+
});
|
|
7595
|
+
if (parsed.errors.some((error) => error.severity === "Error")) return source;
|
|
7596
|
+
const output = new MagicString(source);
|
|
7597
|
+
walk(parsed.program, null, false, {
|
|
7598
|
+
source,
|
|
7599
|
+
output,
|
|
7600
|
+
rewrite
|
|
7601
|
+
});
|
|
7602
|
+
return output.toString();
|
|
7603
|
+
}
|
|
7604
|
+
function walk(node, parent, binding, context) {
|
|
7605
|
+
visit(node, parent, binding, context);
|
|
7606
|
+
for (const field of Object.keys(node)) {
|
|
7607
|
+
const value = node[field];
|
|
7608
|
+
const position = {
|
|
7609
|
+
node,
|
|
7610
|
+
field,
|
|
7611
|
+
parent
|
|
7612
|
+
};
|
|
7613
|
+
if (Array.isArray(value)) {
|
|
7614
|
+
for (const item of value) if (isNode(item)) walk(item, position, inBinding(node, field, binding), context);
|
|
7615
|
+
} else if (isNode(value)) walk(value, position, inBinding(node, field, binding), context);
|
|
7616
|
+
}
|
|
7617
|
+
}
|
|
7618
|
+
function visit(node, parent, binding, context) {
|
|
7619
|
+
const output = context.output;
|
|
7620
|
+
if ((node.type === "ImportDeclaration" || node.type === "ExportNamedDeclaration" || node.type === "ExportAllDeclaration") && isNode(node.source)) {
|
|
7621
|
+
const specifier = String(node.source.value);
|
|
7622
|
+
if (specifier.startsWith("/") || /^[a-z][a-z\d+.-]*:/i.test(specifier)) output.overwrite(node.source.start, node.source.end, JSON.stringify(context.rewrite(specifier)));
|
|
7623
|
+
return;
|
|
7624
|
+
}
|
|
7625
|
+
if (node.type === "ImportExpression" && isNode(node.source)) {
|
|
7626
|
+
wrap(output, node.source, "__oi");
|
|
7627
|
+
return;
|
|
7628
|
+
}
|
|
7629
|
+
if (node.type === "AssignmentExpression") {
|
|
7630
|
+
if (node.operator === "=" && isNode(node.left) && isNode(node.right) && isLocationTarget(node.left)) {
|
|
7631
|
+
const current = context.source.slice(node.left.start, node.left.end);
|
|
7632
|
+
output.appendRight(node.right.start, `__ou(${current},`);
|
|
7633
|
+
output.appendLeft(node.right.end, ")");
|
|
7634
|
+
}
|
|
7635
|
+
return;
|
|
7636
|
+
}
|
|
7637
|
+
if (node.type === "Identifier") {
|
|
7638
|
+
visitIdentifier(node, parent, binding, output);
|
|
7639
|
+
return;
|
|
7640
|
+
}
|
|
7641
|
+
if (node.type === "MemberExpression" && !binding) visitMember(node, parent, output);
|
|
7642
|
+
}
|
|
7643
|
+
function visitIdentifier(node, parent, binding, output) {
|
|
7644
|
+
const name = String(node.name);
|
|
7645
|
+
if (name !== "location" && name !== "top" && name !== "parent") return;
|
|
7646
|
+
const wrapper = name === "location" ? "__ol" : "__ow";
|
|
7647
|
+
if (parent?.node.type === "Property" && parent.field === "value" && parent.node.shorthand === true && !binding) {
|
|
7648
|
+
output.appendRight(node.start, `${name}:${wrapper}(`);
|
|
7649
|
+
output.appendLeft(node.end, ")");
|
|
7650
|
+
return;
|
|
7651
|
+
}
|
|
7652
|
+
if (binding || !isRead(node, parent)) return;
|
|
7653
|
+
wrap(output, node, wrapper);
|
|
7654
|
+
}
|
|
7655
|
+
function visitMember(node, parent, output) {
|
|
7656
|
+
if (!isNode(node.property) || !isNode(node.object)) return;
|
|
7657
|
+
if (node.computed === true) return;
|
|
7658
|
+
const name = String(node.property.name);
|
|
7659
|
+
if (name === "url" && node.object.type === "MetaProperty" && isRead(node, parent)) {
|
|
7660
|
+
wrap(output, node, "__om");
|
|
7661
|
+
return;
|
|
7662
|
+
}
|
|
7663
|
+
if (name === "location" && isRead(node, parent) && !insideChain(parent)) {
|
|
7664
|
+
wrap(output, node, "__ol");
|
|
7665
|
+
return;
|
|
7666
|
+
}
|
|
7667
|
+
if ((name === "top" || name === "parent") && node.object.type === "Identifier" && WINDOWS.has(String(node.object.name)) && isRead(node, parent) && !insideChain(parent)) wrap(output, node, "__ow");
|
|
7668
|
+
}
|
|
7669
|
+
function isRead(node, parent) {
|
|
7670
|
+
if (parent === null) return true;
|
|
7671
|
+
const owner = parent.node;
|
|
7672
|
+
const field = parent.field;
|
|
7673
|
+
if (owner.type === "MemberExpression" && field === "property") return owner.computed === true;
|
|
7674
|
+
if (KEYS.has(owner.type) && field === "key") return owner.computed === true;
|
|
7675
|
+
if (NAMES.has(owner.type)) return false;
|
|
7676
|
+
if (owner.type === "CallExpression" && field === "callee") return false;
|
|
7677
|
+
if (owner.type === "UpdateExpression") return false;
|
|
7678
|
+
if (owner.type === "AssignmentExpression" && field === "left") return false;
|
|
7679
|
+
if (owner.type === "UnaryExpression") return owner.operator !== "delete" && owner.operator !== "typeof";
|
|
7680
|
+
return true;
|
|
7681
|
+
}
|
|
7682
|
+
function inBinding(node, field, binding) {
|
|
7683
|
+
if (node.type === "VariableDeclarator") return field === "id";
|
|
7684
|
+
if (FUNCTIONS.has(node.type)) return field === "params" || field === "id";
|
|
7685
|
+
if (node.type === "ClassExpression" || node.type === "ClassDeclaration") return field === "id";
|
|
7686
|
+
if (node.type === "CatchClause") return field === "param";
|
|
7687
|
+
if (node.type === "ForInStatement" || node.type === "ForOfStatement") return field === "left";
|
|
7688
|
+
if (node.type === "AssignmentExpression") return field === "left";
|
|
7689
|
+
if (node.type === "AssignmentPattern" && field === "right") return false;
|
|
7690
|
+
if (node.type === "Property" && field === "key") return false;
|
|
7691
|
+
return binding && PATTERNS.has(node.type);
|
|
7692
|
+
}
|
|
7693
|
+
function isLocationTarget(node) {
|
|
7694
|
+
if (node.type === "Identifier") return node.name === "location";
|
|
7695
|
+
return node.type === "MemberExpression" && node.computed !== true && isNode(node.property) && node.property.name === "location";
|
|
7696
|
+
}
|
|
7697
|
+
function insideChain(parent) {
|
|
7698
|
+
return parent !== null && parent.node.type === "MemberExpression" && parent.field === "object" && hasChain(parent);
|
|
7699
|
+
}
|
|
7700
|
+
function hasChain(parent) {
|
|
7701
|
+
if (parent === null) return false;
|
|
7702
|
+
if (parent.node.type === "ChainExpression") return true;
|
|
7703
|
+
return hasChain(parent.parent);
|
|
7704
|
+
}
|
|
7705
|
+
function wrap(output, node, name) {
|
|
7706
|
+
output.appendRight(node.start, `${name}(`);
|
|
7707
|
+
output.appendLeft(node.end, ")");
|
|
7708
|
+
}
|
|
7709
|
+
function isNode(value) {
|
|
7710
|
+
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
|
|
7711
|
+
}
|
|
7712
|
+
// src/lib/proxy/html.ts
|
|
7713
|
+
const LINKS = /* @__PURE__ */ new Set([
|
|
7714
|
+
"href",
|
|
7715
|
+
"src",
|
|
7716
|
+
"action",
|
|
7717
|
+
"formaction",
|
|
7718
|
+
"poster",
|
|
7719
|
+
"data",
|
|
7720
|
+
"background"
|
|
7721
|
+
]);
|
|
7722
|
+
const LINK_TAGS = /* @__PURE__ */ new Set([
|
|
7723
|
+
"a",
|
|
7724
|
+
"area",
|
|
7725
|
+
"audio",
|
|
7726
|
+
"base",
|
|
7727
|
+
"body",
|
|
7728
|
+
"button",
|
|
7729
|
+
"embed",
|
|
7730
|
+
"form",
|
|
7731
|
+
"iframe",
|
|
7732
|
+
"img",
|
|
7733
|
+
"input",
|
|
7734
|
+
"link",
|
|
7735
|
+
"object",
|
|
7736
|
+
"script",
|
|
7737
|
+
"source",
|
|
7738
|
+
"td",
|
|
7739
|
+
"track",
|
|
7740
|
+
"video"
|
|
7741
|
+
]);
|
|
7742
|
+
const REMOVED = /* @__PURE__ */ new Set([
|
|
7743
|
+
"integrity",
|
|
7744
|
+
"crossorigin",
|
|
7745
|
+
"nonce",
|
|
7746
|
+
"ping"
|
|
7747
|
+
]);
|
|
7748
|
+
const NAVIGATING = /* @__PURE__ */ new Set([
|
|
7749
|
+
"a",
|
|
7750
|
+
"area",
|
|
7751
|
+
"base",
|
|
7752
|
+
"form"
|
|
7753
|
+
]);
|
|
7754
|
+
const SCRIPT_TYPES = /* @__PURE__ */ new Set([
|
|
7755
|
+
"",
|
|
7756
|
+
"module",
|
|
7757
|
+
"text/javascript",
|
|
7758
|
+
"application/javascript",
|
|
7759
|
+
"text/ecmascript",
|
|
7760
|
+
"application/ecmascript"
|
|
7761
|
+
]);
|
|
7762
|
+
const META_REMOVED = /* @__PURE__ */ new Set([
|
|
7763
|
+
"content-security-policy",
|
|
7764
|
+
"content-security-policy-report-only",
|
|
7765
|
+
"x-frame-options"
|
|
7766
|
+
]);
|
|
7767
|
+
function rewriteHtml(source, key, base, script) {
|
|
7768
|
+
const document = parse(source);
|
|
7769
|
+
rewriteChildren(document, {
|
|
7770
|
+
key,
|
|
7771
|
+
base,
|
|
7772
|
+
rewrite: linkRewriter(key, base)
|
|
7773
|
+
});
|
|
7774
|
+
const head = findHead(document);
|
|
7775
|
+
const runtime = defaultTreeAdapter.createElement("script", html.NS.HTML, []);
|
|
7776
|
+
defaultTreeAdapter.insertText(runtime, script);
|
|
7777
|
+
const first = head.childNodes[0];
|
|
7778
|
+
if (first === void 0) defaultTreeAdapter.appendChild(head, runtime);
|
|
7779
|
+
else defaultTreeAdapter.insertBefore(head, runtime, first);
|
|
7780
|
+
return serialize(document);
|
|
7781
|
+
}
|
|
7782
|
+
function findHead(node) {
|
|
7783
|
+
for (const child of node.childNodes) if ("tagName" in child) {
|
|
7784
|
+
if (child.tagName === "head") return child;
|
|
7785
|
+
if (child.tagName === "html") return findHead(child);
|
|
7786
|
+
}
|
|
7787
|
+
throw new Error("the page has no head");
|
|
7788
|
+
}
|
|
7789
|
+
function rewriteChildren(node, page) {
|
|
7790
|
+
const remove = [];
|
|
7791
|
+
for (const child of node.childNodes) {
|
|
7792
|
+
if (!("tagName" in child)) continue;
|
|
7793
|
+
if (rewriteElement(child, page)) {
|
|
7794
|
+
remove.push(child);
|
|
7795
|
+
continue;
|
|
7796
|
+
}
|
|
7797
|
+
rewriteChildren(child, page);
|
|
7798
|
+
if ("content" in child) rewriteChildren(child.content, page);
|
|
7799
|
+
}
|
|
7800
|
+
for (const child of remove) defaultTreeAdapter.detachNode(child);
|
|
7801
|
+
}
|
|
7802
|
+
function rewriteElement(element, page) {
|
|
7803
|
+
const tag = element.tagName;
|
|
7804
|
+
if (tag === "meta") {
|
|
7805
|
+
const equiv = attribute(element, "http-equiv")?.value.toLowerCase();
|
|
7806
|
+
if (equiv !== void 0 && META_REMOVED.has(equiv)) return true;
|
|
7807
|
+
if (attribute(element, "name")?.value.toLowerCase() === "referrer") return true;
|
|
7808
|
+
if (equiv === "refresh") rewriteRefresh(element, page);
|
|
7809
|
+
}
|
|
7810
|
+
if (tag === "base") {
|
|
7811
|
+
const href = attribute(element, "href")?.value;
|
|
7812
|
+
const next = href === void 0 ? null : URL.parse(href, page.base);
|
|
7813
|
+
if (next !== null) {
|
|
7814
|
+
page.base = next;
|
|
7815
|
+
page.rewrite = linkRewriter(page.key, next);
|
|
7816
|
+
}
|
|
7817
|
+
}
|
|
7818
|
+
element.attrs = element.attrs.filter((item) => !REMOVED.has(item.name));
|
|
7819
|
+
if (NAVIGATING.has(tag)) element.attrs = element.attrs.filter((item) => item.name !== "target" || !/^_(top|parent|blank)$/i.test(item.value.trim()));
|
|
7820
|
+
for (const item of element.attrs) if (LINKS.has(item.name) && LINK_TAGS.has(tag)) item.value = page.rewrite(item.value);
|
|
7821
|
+
else if (item.name === "srcset" || item.name === "imagesrcset") item.value = rewriteSrcset(item.value, page.rewrite);
|
|
7822
|
+
else if (item.name === "style") item.value = rewriteCss(item.value, page.rewrite);
|
|
7823
|
+
else if (item.name.startsWith("on")) item.value = rewriteHandler(item.value, page.rewrite);
|
|
7824
|
+
if (tag === "style") rewriteText(element, (text) => rewriteCss(text, page.rewrite));
|
|
7825
|
+
if (tag === "script") rewriteInlineScript(element, page);
|
|
7826
|
+
return false;
|
|
7827
|
+
}
|
|
7828
|
+
function rewriteInlineScript(element, page) {
|
|
7829
|
+
const type = (attribute(element, "type")?.value ?? "").trim().toLowerCase();
|
|
7830
|
+
if (type === "importmap") {
|
|
7831
|
+
rewriteText(element, (text) => JSON.stringify(rewriteMapLinks(JSON.parse(text), page)));
|
|
7832
|
+
return;
|
|
7833
|
+
}
|
|
7834
|
+
if (SCRIPT_TYPES.has(type)) rewriteText(element, (text) => rewriteScript(text, page.rewrite, `${page.key} ${page.base.origin}`));
|
|
7835
|
+
}
|
|
7836
|
+
function rewriteMapLinks(value, page) {
|
|
7837
|
+
if (typeof value === "string") return page.rewrite(value);
|
|
7838
|
+
if (typeof value !== "object" || value === null) return value;
|
|
7839
|
+
return Object.fromEntries(Object.entries(value).map((entry) => [entry[0], rewriteMapLinks(entry[1], page)]));
|
|
7840
|
+
}
|
|
7841
|
+
function rewriteText(element, rewrite) {
|
|
7842
|
+
for (const child of element.childNodes) if ("value" in child) child.value = rewrite(child.value);
|
|
7843
|
+
}
|
|
7844
|
+
function rewriteRefresh(element, page) {
|
|
7845
|
+
const content = attribute(element, "content");
|
|
7846
|
+
if (content === void 0) return;
|
|
7847
|
+
content.value = content.value.replace(/(url\s*=\s*['"]?)([^'"]+)/i, (_match, label, url) => `${label}${page.rewrite(url)}`);
|
|
7848
|
+
}
|
|
7849
|
+
function rewriteSrcset(value, rewrite) {
|
|
7850
|
+
return value.split(/,\s+/).map((candidate) => {
|
|
7851
|
+
const parts = candidate.trim().split(/\s+/);
|
|
7852
|
+
const url = parts[0];
|
|
7853
|
+
if (url === void 0 || url === "") return candidate;
|
|
7854
|
+
return [rewrite(url), ...parts.slice(1)].join(" ");
|
|
7855
|
+
}).join(", ");
|
|
7856
|
+
}
|
|
7857
|
+
function attribute(element, name) {
|
|
7858
|
+
return element.attrs.find((item) => item.name === name);
|
|
7859
|
+
}
|
|
7860
|
+
// src/lib/proxy/page-script.ts
|
|
7861
|
+
function pageScript(config) {
|
|
7862
|
+
const json = JSON.stringify(config).replaceAll("<", "\\u003c");
|
|
7863
|
+
return `(${PAGE_SCRIPT})(${json})`;
|
|
7864
|
+
}
|
|
7865
|
+
const PAGE_SCRIPT = String.raw`function (config) {
|
|
7866
|
+
if (self.__ol) return
|
|
7867
|
+
const inWindow = typeof document !== 'undefined'
|
|
7868
|
+
const realLocation = self.location
|
|
7869
|
+
const origin = config.origin ?? realLocation.origin
|
|
7870
|
+
const prefix = '/api/proxy/' + config.key + '/'
|
|
7871
|
+
const realFetch = self.fetch.bind(self)
|
|
7872
|
+
const native = { get: inWindow && Element.prototype.getAttribute, set: inWindow && Element.prototype.setAttribute }
|
|
7873
|
+
const realPost = inWindow && window.postMessage.bind(window)
|
|
7874
|
+
const panel = inWindow && window.parent !== window && window.parent === window.top ? window.parent : null
|
|
7875
|
+
const skipped = /^\s*(data|blob|javascript|about|mailto|tel|sms|file):/i
|
|
7876
|
+
const schemes = { 'http:': 'http', 'https:': 'https', 'ws:': 'http', 'wss:': 'https' }
|
|
7877
|
+
|
|
7878
|
+
function hide(name, value) {
|
|
7879
|
+
Object.defineProperty(self, name, { value, enumerable: false, configurable: true, writable: true })
|
|
7880
|
+
}
|
|
7881
|
+
function decode(value) {
|
|
7882
|
+
const text = String(value)
|
|
7883
|
+
const index = text.indexOf(prefix)
|
|
7884
|
+
if (index === -1 || (index !== 0 && !text.startsWith(origin))) return null
|
|
7885
|
+
const rest = text.slice(index + prefix.length)
|
|
7886
|
+
const slash = rest.indexOf('/')
|
|
7887
|
+
const scheme = rest.slice(0, slash)
|
|
7888
|
+
if (scheme !== 'http' && scheme !== 'https') return null
|
|
7889
|
+
return scheme + '://' + rest.slice(slash + 1)
|
|
7890
|
+
}
|
|
7891
|
+
function original(value) {
|
|
7892
|
+
return decode(value) ?? value
|
|
7893
|
+
}
|
|
7894
|
+
function pageUrl() {
|
|
7895
|
+
return decode(realLocation.href) ?? config.url
|
|
7896
|
+
}
|
|
7897
|
+
function baseUrl() {
|
|
7898
|
+
const base = inWindow && document.querySelector('base[href]')
|
|
7899
|
+
return base ? (decode(native.get.call(base, 'href')) ?? pageUrl()) : pageUrl()
|
|
7900
|
+
}
|
|
7901
|
+
function encode(value, base) {
|
|
7902
|
+
const text = String(value)
|
|
7903
|
+
if (text === '' || text.startsWith('#') || skipped.test(text) || decode(text) !== null) return text
|
|
7904
|
+
const url = URL.parse(text, base ?? baseUrl())
|
|
7905
|
+
if (url === null || schemes[url.protocol] === undefined) return text
|
|
7906
|
+
return prefix + schemes[url.protocol] + '/' + url.host + url.pathname + url.search + url.hash
|
|
7907
|
+
}
|
|
7908
|
+
function absolute(value) {
|
|
7909
|
+
const encoded = encode(value)
|
|
7910
|
+
return encoded.startsWith('/') ? origin + encoded : encoded
|
|
7911
|
+
}
|
|
7912
|
+
function encodeSet(value) {
|
|
7913
|
+
return String(value).split(/,\s+/).map(function (item) {
|
|
7914
|
+
const parts = item.trim().split(/\s+/)
|
|
7915
|
+
parts[0] = encode(parts[0])
|
|
7916
|
+
return parts.join(' ')
|
|
7917
|
+
}).join(', ')
|
|
7918
|
+
}
|
|
7919
|
+
function socketUrl(value) {
|
|
7920
|
+
return (origin.startsWith('https') ? 'wss://' : 'ws://') + origin.replace(/^https?:\/\//, '') + encode(value)
|
|
7921
|
+
}
|
|
7922
|
+
function postPanel(message) {
|
|
7923
|
+
if (panel) panel.postMessage(Object.assign({ offsite: 'browser' }, message), '*')
|
|
7924
|
+
}
|
|
7925
|
+
function navigate(value, replace) {
|
|
7926
|
+
const next = URL.parse(String(value), baseUrl())
|
|
7927
|
+
if (next === null) return
|
|
7928
|
+
const web = next.protocol === 'http:' || next.protocol === 'https:'
|
|
7929
|
+
if (panel && web && next.host !== new URL(pageUrl()).host) {
|
|
7930
|
+
postPanel({ event: 'open', url: next.href })
|
|
7931
|
+
return
|
|
7932
|
+
}
|
|
7933
|
+
if (replace) realLocation.replace(encode(next.href))
|
|
7934
|
+
else realLocation.assign(encode(next.href))
|
|
7935
|
+
}
|
|
7936
|
+
|
|
7937
|
+
const fakeLocation = {}
|
|
7938
|
+
for (const part of ['href', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash', 'origin']) {
|
|
7939
|
+
Object.defineProperty(fakeLocation, part, {
|
|
7940
|
+
enumerable: true,
|
|
7941
|
+
get() {
|
|
7942
|
+
return new URL(pageUrl())[part]
|
|
7943
|
+
},
|
|
7944
|
+
set(value) {
|
|
7945
|
+
if (part === 'origin') return
|
|
7946
|
+
if (part === 'hash') {
|
|
7947
|
+
realLocation.hash = value
|
|
7948
|
+
return
|
|
7949
|
+
}
|
|
7950
|
+
if (part === 'href') {
|
|
7951
|
+
navigate(value)
|
|
7952
|
+
return
|
|
7953
|
+
}
|
|
7954
|
+
const next = new URL(pageUrl())
|
|
7955
|
+
next[part] = value
|
|
7956
|
+
navigate(next.href)
|
|
7957
|
+
},
|
|
7958
|
+
})
|
|
7959
|
+
}
|
|
7960
|
+
fakeLocation.assign = function (value) { navigate(value) }
|
|
7961
|
+
fakeLocation.replace = function (value) { navigate(value, true) }
|
|
7962
|
+
fakeLocation.reload = function () { realLocation.reload() }
|
|
7963
|
+
fakeLocation.toString = function () { return pageUrl() }
|
|
7964
|
+
fakeLocation.valueOf = function () { return fakeLocation }
|
|
7965
|
+
fakeLocation[Symbol.toPrimitive] = function () { return pageUrl() }
|
|
7966
|
+
fakeLocation.ancestorOrigins = []
|
|
7967
|
+
|
|
7968
|
+
function pageTop() {
|
|
7969
|
+
if (!inWindow || window.parent === window) return self
|
|
7970
|
+
let current = window
|
|
7971
|
+
while (current.parent !== window.top) current = current.parent
|
|
7972
|
+
return current
|
|
7973
|
+
}
|
|
7974
|
+
hide('__ol', function (value) { return value === realLocation ? fakeLocation : value })
|
|
7975
|
+
hide('__ow', function (value) {
|
|
7976
|
+
if (!inWindow) return value
|
|
7977
|
+
if (value === window.top) return pageTop()
|
|
7978
|
+
return value
|
|
7979
|
+
})
|
|
7980
|
+
hide('__ou', function (current, value) {
|
|
7981
|
+
if (current === realLocation || (inWindow && current === window.top.location)) return encode(value)
|
|
7982
|
+
return value
|
|
7983
|
+
})
|
|
7984
|
+
hide('__oi', function (value) {
|
|
7985
|
+
const text = String(value)
|
|
7986
|
+
return /^(\/|https?:)/.test(text) ? absolute(text) : text
|
|
7987
|
+
})
|
|
7988
|
+
hide('__om', original)
|
|
7989
|
+
|
|
7990
|
+
function patch(owner, name, wrap) {
|
|
7991
|
+
const descriptor = Object.getOwnPropertyDescriptor(owner, name)
|
|
7992
|
+
if (descriptor === undefined) return
|
|
7993
|
+
Object.defineProperty(owner, name, Object.assign({}, descriptor, wrap(descriptor)))
|
|
7994
|
+
}
|
|
7995
|
+
function getter(owner, name, read) {
|
|
7996
|
+
patch(owner, name, function (descriptor) {
|
|
7997
|
+
return { get() { return read(descriptor.get.call(this), this) } }
|
|
7998
|
+
})
|
|
7999
|
+
}
|
|
8000
|
+
|
|
8001
|
+
self.fetch = function (input, init) {
|
|
8002
|
+
const options = Object.assign({}, init, { credentials: 'omit' })
|
|
8003
|
+
if (options.mode === 'same-origin') options.mode = 'cors'
|
|
8004
|
+
if (input instanceof RealRequest) return realFetch(input, options)
|
|
8005
|
+
return realFetch(absolute(input), options)
|
|
8006
|
+
}
|
|
8007
|
+
const RealRequest = self.Request
|
|
8008
|
+
self.Request = class Request extends RealRequest {
|
|
8009
|
+
constructor(input, init) {
|
|
8010
|
+
super(input instanceof RealRequest ? input : absolute(input), init)
|
|
8011
|
+
}
|
|
8012
|
+
}
|
|
8013
|
+
getter(RealRequest.prototype, 'url', original)
|
|
8014
|
+
getter(Response.prototype, 'url', original)
|
|
8015
|
+
const realOpen = XMLHttpRequest.prototype.open
|
|
8016
|
+
XMLHttpRequest.prototype.open = function (method, url) {
|
|
8017
|
+
const rest = Array.prototype.slice.call(arguments, 2)
|
|
8018
|
+
return realOpen.call(this, method, encode(url), ...rest)
|
|
8019
|
+
}
|
|
8020
|
+
patch(XMLHttpRequest.prototype, 'withCredentials', function () {
|
|
8021
|
+
return { get() { return false }, set() {} }
|
|
8022
|
+
})
|
|
8023
|
+
getter(XMLHttpRequest.prototype, 'responseURL', original)
|
|
8024
|
+
self.WebSocket = new Proxy(self.WebSocket, {
|
|
8025
|
+
construct(target, args) {
|
|
8026
|
+
args[0] = socketUrl(args[0])
|
|
8027
|
+
return Reflect.construct(target, args)
|
|
8028
|
+
},
|
|
8029
|
+
})
|
|
8030
|
+
if (self.EventSource) {
|
|
8031
|
+
self.EventSource = new Proxy(self.EventSource, {
|
|
8032
|
+
construct(target, args) {
|
|
8033
|
+
return new target(encode(args[0]))
|
|
8034
|
+
},
|
|
8035
|
+
})
|
|
8036
|
+
}
|
|
8037
|
+
function workerUrl(value, options) {
|
|
8038
|
+
const text = String(value)
|
|
8039
|
+
if (skipped.test(text)) return text
|
|
8040
|
+
const target = URL.parse(text, baseUrl())
|
|
8041
|
+
if (target === null) return text
|
|
8042
|
+
const runtime = origin + prefix + 'runtime.js?url=' + encodeURIComponent(target.href) + '&origin=' + encodeURIComponent(origin)
|
|
8043
|
+
const script = absolute(target.href)
|
|
8044
|
+
const code = options && options.type === 'module'
|
|
8045
|
+
? 'import ' + JSON.stringify(runtime) + ';import ' + JSON.stringify(script) + ';'
|
|
8046
|
+
: 'importScripts(' + JSON.stringify(runtime) + ');importScripts(' + JSON.stringify(script) + ');'
|
|
8047
|
+
return URL.createObjectURL(new Blob([code], { type: 'text/javascript' }))
|
|
8048
|
+
}
|
|
8049
|
+
for (const name of ['Worker', 'SharedWorker']) {
|
|
8050
|
+
if (self[name]) {
|
|
8051
|
+
self[name] = new Proxy(self[name], {
|
|
8052
|
+
construct(target, args) {
|
|
8053
|
+
args[0] = workerUrl(args[0], args[1])
|
|
8054
|
+
return Reflect.construct(target, args)
|
|
8055
|
+
},
|
|
8056
|
+
})
|
|
8057
|
+
}
|
|
8058
|
+
}
|
|
8059
|
+
if (!inWindow) {
|
|
8060
|
+
if (self.importScripts) {
|
|
8061
|
+
const realImport = self.importScripts.bind(self)
|
|
8062
|
+
self.importScripts = function () {
|
|
8063
|
+
realImport(...Array.from(arguments, function (item) { return absolute(item) }))
|
|
8064
|
+
}
|
|
8065
|
+
}
|
|
8066
|
+
return
|
|
8067
|
+
}
|
|
8068
|
+
|
|
8069
|
+
delete Navigator.prototype.serviceWorker
|
|
8070
|
+
Object.defineProperty(window, 'indexedDB', { value: undefined, configurable: true })
|
|
8071
|
+
Object.defineProperty(window, 'caches', {
|
|
8072
|
+
configurable: true,
|
|
8073
|
+
value: {
|
|
8074
|
+
keys() { return Promise.resolve([]) },
|
|
8075
|
+
has() { return Promise.resolve(false) },
|
|
8076
|
+
delete() { return Promise.resolve(false) },
|
|
8077
|
+
match() { return Promise.resolve(undefined) },
|
|
8078
|
+
open() { return Promise.reject(new DOMException('Cache storage is off', 'SecurityError')) },
|
|
8079
|
+
},
|
|
8080
|
+
})
|
|
8081
|
+
Object.defineProperty(Navigator.prototype, 'locks', {
|
|
8082
|
+
configurable: true,
|
|
8083
|
+
value: {
|
|
8084
|
+
request(name, options, callback) {
|
|
8085
|
+
const run = callback ?? options
|
|
8086
|
+
return Promise.resolve().then(function () { return run({ name, mode: 'exclusive' }) })
|
|
8087
|
+
},
|
|
8088
|
+
query() { return Promise.resolve({ held: [], pending: [] }) },
|
|
8089
|
+
},
|
|
8090
|
+
})
|
|
8091
|
+
const blankFrames = new Proxy(window, {
|
|
8092
|
+
get(target, name) {
|
|
8093
|
+
if (name === 'document') throw new DOMException('Helper frames are blocked', 'SecurityError')
|
|
8094
|
+
const value = Reflect.get(target, name)
|
|
8095
|
+
return typeof value === 'function' && value.prototype === undefined ? value.bind(target) : value
|
|
8096
|
+
},
|
|
8097
|
+
})
|
|
8098
|
+
getter(HTMLIFrameElement.prototype, 'contentWindow', function (value, frame) {
|
|
8099
|
+
const blank = native.get.call(frame, 'src') === null && native.get.call(frame, 'srcdoc') === null
|
|
8100
|
+
return blank && value !== null ? blankFrames : value
|
|
8101
|
+
})
|
|
8102
|
+
const realBeacon = navigator.sendBeacon.bind(navigator)
|
|
8103
|
+
navigator.sendBeacon = function (url, data) { return realBeacon(encode(url), data) }
|
|
8104
|
+
|
|
8105
|
+
const properties = [
|
|
8106
|
+
[HTMLAnchorElement, 'href'], [HTMLAreaElement, 'href'], [HTMLLinkElement, 'href'], [HTMLBaseElement, 'href'],
|
|
8107
|
+
[HTMLImageElement, 'src'], [HTMLScriptElement, 'src'], [HTMLIFrameElement, 'src'], [HTMLMediaElement, 'src'],
|
|
8108
|
+
[HTMLSourceElement, 'src'], [HTMLEmbedElement, 'src'], [HTMLInputElement, 'src'], [HTMLTrackElement, 'src'],
|
|
8109
|
+
[HTMLFormElement, 'action'], [HTMLButtonElement, 'formAction'], [HTMLInputElement, 'formAction'],
|
|
8110
|
+
[HTMLObjectElement, 'data'], [HTMLVideoElement, 'poster'],
|
|
8111
|
+
]
|
|
8112
|
+
for (const item of properties) {
|
|
8113
|
+
patch(item[0].prototype, item[1], function (descriptor) {
|
|
8114
|
+
return {
|
|
8115
|
+
get() { return original(descriptor.get.call(this)) },
|
|
8116
|
+
set(value) { descriptor.set.call(this, encode(value)) },
|
|
8117
|
+
}
|
|
8118
|
+
})
|
|
8119
|
+
}
|
|
8120
|
+
const RealAudio = window.Audio
|
|
8121
|
+
window.Audio = function Audio(source) {
|
|
8122
|
+
return source === undefined ? new RealAudio() : new RealAudio(encode(source))
|
|
8123
|
+
}
|
|
8124
|
+
window.Audio.prototype = RealAudio.prototype
|
|
8125
|
+
for (const owner of [HTMLImageElement, HTMLSourceElement]) {
|
|
8126
|
+
patch(owner.prototype, 'srcset', function (descriptor) {
|
|
8127
|
+
return {
|
|
8128
|
+
get() { return descriptor.get.call(this) },
|
|
8129
|
+
set(value) { descriptor.set.call(this, encodeSet(value)) },
|
|
8130
|
+
}
|
|
8131
|
+
})
|
|
8132
|
+
}
|
|
8133
|
+
for (const owner of [HTMLScriptElement, HTMLLinkElement]) {
|
|
8134
|
+
patch(owner.prototype, 'integrity', function () {
|
|
8135
|
+
return { get() { return '' }, set() {} }
|
|
8136
|
+
})
|
|
8137
|
+
}
|
|
8138
|
+
for (const part of ['protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash', 'origin']) {
|
|
8139
|
+
for (const owner of [HTMLAnchorElement, HTMLAreaElement]) {
|
|
8140
|
+
getter(owner.prototype, part, function (value, element) {
|
|
8141
|
+
const url = URL.parse(element.href)
|
|
8142
|
+
return url === null ? value : url[part]
|
|
8143
|
+
})
|
|
8144
|
+
}
|
|
8145
|
+
}
|
|
8146
|
+
|
|
8147
|
+
const linkTags = new Set(['A', 'AREA', 'AUDIO', 'BASE', 'BUTTON', 'EMBED', 'FORM', 'IFRAME', 'IMG', 'INPUT', 'LINK', 'OBJECT', 'SCRIPT', 'SOURCE', 'TRACK', 'VIDEO'])
|
|
8148
|
+
const linkNames = new Set(['href', 'src', 'action', 'formaction', 'poster', 'data'])
|
|
8149
|
+
Element.prototype.setAttribute = function (name, value) {
|
|
8150
|
+
const lower = String(name).toLowerCase()
|
|
8151
|
+
if (lower === 'integrity' || lower === 'crossorigin') return
|
|
8152
|
+
if (linkNames.has(lower) && linkTags.has(this.tagName)) {
|
|
8153
|
+
native.set.call(this, name, encode(value))
|
|
8154
|
+
return
|
|
8155
|
+
}
|
|
8156
|
+
if (lower === 'srcset') {
|
|
8157
|
+
native.set.call(this, name, encodeSet(value))
|
|
8158
|
+
return
|
|
8159
|
+
}
|
|
8160
|
+
native.set.call(this, name, value)
|
|
8161
|
+
}
|
|
8162
|
+
Element.prototype.getAttribute = function (name) {
|
|
8163
|
+
const value = native.get.call(this, name)
|
|
8164
|
+
if (value === null) return null
|
|
8165
|
+
const url = decode(value)
|
|
8166
|
+
if (url === null) return value
|
|
8167
|
+
const parsed = URL.parse(url)
|
|
8168
|
+
if (parsed === null || parsed.origin !== new URL(pageUrl()).origin) return url
|
|
8169
|
+
return parsed.pathname + parsed.search + parsed.hash
|
|
8170
|
+
}
|
|
8171
|
+
function encodeSelector(selector) {
|
|
8172
|
+
return String(selector).replace(/\[(src|href)(\^?=)(["']?)([^"'\]]+)\3\]/g, function (match, name, operator, quote, value) {
|
|
8173
|
+
if (!/^(\/|https?:\/\/)/.test(value)) return match
|
|
8174
|
+
return '[' + name + operator + '"' + encode(value) + '"]'
|
|
8175
|
+
})
|
|
8176
|
+
}
|
|
8177
|
+
for (const owner of [Document, DocumentFragment, Element]) {
|
|
8178
|
+
for (const name of ['querySelector', 'querySelectorAll']) {
|
|
8179
|
+
const realQuery = owner.prototype[name]
|
|
8180
|
+
owner.prototype[name] = function (selector) {
|
|
8181
|
+
return realQuery.call(this, encodeSelector(selector))
|
|
8182
|
+
}
|
|
8183
|
+
}
|
|
8184
|
+
}
|
|
8185
|
+
for (const name of ['matches', 'closest']) {
|
|
8186
|
+
const realMatch = Element.prototype[name]
|
|
8187
|
+
Element.prototype[name] = function (selector) {
|
|
8188
|
+
return realMatch.call(this, encodeSelector(selector))
|
|
8189
|
+
}
|
|
8190
|
+
}
|
|
8191
|
+
function encodeCss(text) {
|
|
8192
|
+
return String(text).replace(/url\(\s*(['"]?)(.*?)\1\s*\)/g, function (match, quote, url) {
|
|
8193
|
+
return 'url(' + quote + encode(url) + quote + ')'
|
|
8194
|
+
})
|
|
8195
|
+
}
|
|
8196
|
+
for (const name of ['insertRule', 'replace', 'replaceSync']) {
|
|
8197
|
+
const realRule = CSSStyleSheet.prototype[name]
|
|
8198
|
+
CSSStyleSheet.prototype[name] = function (text, index) {
|
|
8199
|
+
return realRule.call(this, encodeCss(text), index)
|
|
8200
|
+
}
|
|
8201
|
+
}
|
|
8202
|
+
function fixElement(element) {
|
|
8203
|
+
const style = native.get.call(element, 'style')
|
|
8204
|
+
if (style !== null) {
|
|
8205
|
+
const next = encodeCss(style)
|
|
8206
|
+
if (next !== style) native.set.call(element, 'style', next)
|
|
8207
|
+
}
|
|
8208
|
+
if (element.tagName === 'STYLE') {
|
|
8209
|
+
const text = element.textContent
|
|
8210
|
+
const next = encodeCss(text)
|
|
8211
|
+
if (next !== text) element.textContent = next
|
|
8212
|
+
return
|
|
8213
|
+
}
|
|
8214
|
+
if (!linkTags.has(element.tagName)) return
|
|
8215
|
+
for (const name of linkNames) {
|
|
8216
|
+
const value = native.get.call(element, name)
|
|
8217
|
+
if (value !== null) {
|
|
8218
|
+
const next = encode(value)
|
|
8219
|
+
if (next !== value) native.set.call(element, name, next)
|
|
8220
|
+
}
|
|
8221
|
+
}
|
|
8222
|
+
const set = native.get.call(element, 'srcset')
|
|
8223
|
+
if (set !== null && !set.includes(prefix)) native.set.call(element, 'srcset', encodeSet(set))
|
|
8224
|
+
}
|
|
8225
|
+
new MutationObserver(function (records) {
|
|
8226
|
+
for (const record of records) {
|
|
8227
|
+
if (record.type === 'attributes' || record.target.nodeName === 'STYLE') fixElement(record.target)
|
|
8228
|
+
for (const node of record.addedNodes) {
|
|
8229
|
+
if (node.nodeType !== 1) continue
|
|
8230
|
+
fixElement(node)
|
|
8231
|
+
for (const child of node.querySelectorAll('[src],[href],[action],[srcset],[poster],[data],[style],style')) fixElement(child)
|
|
8232
|
+
}
|
|
8233
|
+
}
|
|
8234
|
+
}).observe(document, { childList: true, subtree: true, attributes: true, attributeFilter: ['style'] })
|
|
8235
|
+
|
|
8236
|
+
getter(Document.prototype, 'URL', pageUrl)
|
|
8237
|
+
getter(Document.prototype, 'documentURI', pageUrl)
|
|
8238
|
+
getter(Node.prototype, 'baseURI', baseUrl)
|
|
8239
|
+
getter(Document.prototype, 'referrer', original)
|
|
8240
|
+
patch(Document.prototype, 'domain', function () {
|
|
8241
|
+
return { get() { return new URL(pageUrl()).hostname }, set() {} }
|
|
8242
|
+
})
|
|
8243
|
+
Object.defineProperty(window, 'origin', { get() { return new URL(pageUrl()).origin }, configurable: true })
|
|
8244
|
+
getter(MessageEvent.prototype, 'origin', function (value) {
|
|
8245
|
+
return value === 'null' ? new URL(pageUrl()).origin : value
|
|
8246
|
+
})
|
|
8247
|
+
window.postMessage = function (message, target, transfer) {
|
|
8248
|
+
if (typeof target === 'object' && target !== null) return realPost(message, Object.assign({}, target, { targetOrigin: '*' }))
|
|
8249
|
+
return realPost(message, '*', transfer)
|
|
8250
|
+
}
|
|
8251
|
+
|
|
8252
|
+
const cookies = new Map()
|
|
8253
|
+
for (const item of config.cookies.split('; ')) {
|
|
8254
|
+
const index = item.indexOf('=')
|
|
8255
|
+
if (index > 0) cookies.set(item.slice(0, index), item.slice(index + 1))
|
|
8256
|
+
}
|
|
8257
|
+
patch(Document.prototype, 'cookie', function () {
|
|
8258
|
+
return {
|
|
8259
|
+
get() { return Array.from(cookies, function (item) { return item[0] + '=' + item[1] }).join('; ') },
|
|
8260
|
+
set(value) {
|
|
8261
|
+
const text = String(value)
|
|
8262
|
+
const pair = text.split(';')[0]
|
|
8263
|
+
const index = pair.indexOf('=')
|
|
8264
|
+
const name = pair.slice(0, index).trim()
|
|
8265
|
+
const age = /;\s*max-age=(-?\d+)/i.exec(text)
|
|
8266
|
+
const expires = /;\s*expires=([^;]+)/i.exec(text)
|
|
8267
|
+
const gone = (age && Number(age[1]) <= 0) || (expires && Date.parse(expires[1]) <= Date.now())
|
|
8268
|
+
if (gone) cookies.delete(name)
|
|
8269
|
+
else cookies.set(name, pair.slice(index + 1).trim())
|
|
8270
|
+
realFetch(prefix + 'cookie', { method: 'POST', body: JSON.stringify({ url: pageUrl(), cookie: text }) })
|
|
8271
|
+
},
|
|
8272
|
+
}
|
|
8273
|
+
})
|
|
8274
|
+
|
|
8275
|
+
function storageArea(initial, save) {
|
|
8276
|
+
const data = new Map(Object.entries(initial))
|
|
8277
|
+
const area = {
|
|
8278
|
+
getItem(name) { return data.has(String(name)) ? data.get(String(name)) : null },
|
|
8279
|
+
setItem(name, value) { data.set(String(name), String(value)); save(data) },
|
|
8280
|
+
removeItem(name) { data.delete(String(name)); save(data) },
|
|
8281
|
+
clear() { data.clear(); save(data) },
|
|
8282
|
+
key(index) { return Array.from(data.keys())[index] ?? null },
|
|
8283
|
+
get length() { return data.size },
|
|
8284
|
+
}
|
|
8285
|
+
return new Proxy(area, {
|
|
8286
|
+
get(target, name) {
|
|
8287
|
+
if (name in target) return Reflect.get(target, name)
|
|
8288
|
+
return typeof name === 'string' ? data.get(name) : undefined
|
|
8289
|
+
},
|
|
8290
|
+
set(target, name, value) { target.setItem(name, value); return true },
|
|
8291
|
+
deleteProperty(target, name) { target.removeItem(name); return true },
|
|
8292
|
+
has(target, name) { return name in target || data.has(name) },
|
|
8293
|
+
ownKeys() { return Array.from(data.keys()) },
|
|
8294
|
+
getOwnPropertyDescriptor(target, name) {
|
|
8295
|
+
return data.has(name) ? { value: data.get(name), enumerable: true, configurable: true, writable: true } : undefined
|
|
8296
|
+
},
|
|
8297
|
+
})
|
|
8298
|
+
}
|
|
8299
|
+
let saveTimer = null
|
|
8300
|
+
let pending = null
|
|
8301
|
+
function sendStorage(keepalive) {
|
|
8302
|
+
if (pending === null) return
|
|
8303
|
+
const body = JSON.stringify({ origin: new URL(pageUrl()).origin, data: pending })
|
|
8304
|
+
pending = null
|
|
8305
|
+
realFetch(prefix + 'storage', { method: 'POST', body, keepalive: keepalive && body.length < 60000 })
|
|
8306
|
+
}
|
|
8307
|
+
const local = storageArea(config.storage, function (data) {
|
|
8308
|
+
pending = Object.fromEntries(data)
|
|
8309
|
+
clearTimeout(saveTimer)
|
|
8310
|
+
saveTimer = setTimeout(function () { sendStorage(false) }, 50)
|
|
8311
|
+
})
|
|
8312
|
+
const session = storageArea({}, function () {})
|
|
8313
|
+
Object.defineProperty(window, 'localStorage', { get() { return local }, configurable: true })
|
|
8314
|
+
Object.defineProperty(window, 'sessionStorage', { get() { return session }, configurable: true })
|
|
8315
|
+
addEventListener('pagehide', function () { sendStorage(true) })
|
|
8316
|
+
|
|
8317
|
+
function report(replaced) {
|
|
8318
|
+
postPanel({ event: 'location', url: pageUrl(), replaced: replaced === true })
|
|
8319
|
+
}
|
|
8320
|
+
for (const name of ['pushState', 'replaceState']) {
|
|
8321
|
+
const realHistory = History.prototype[name]
|
|
8322
|
+
History.prototype[name] = function (state, title, url) {
|
|
8323
|
+
const result = realHistory.call(this, state, title, url === undefined || url === null ? url : encode(url))
|
|
8324
|
+
report(name === 'replaceState')
|
|
8325
|
+
return result
|
|
8326
|
+
}
|
|
8327
|
+
}
|
|
8328
|
+
const realWindowOpen = window.open
|
|
8329
|
+
window.open = function (url, target, features) {
|
|
8330
|
+
if (url === undefined || url === null || url === '') return realWindowOpen.call(window, url, target, features)
|
|
8331
|
+
if (features) return realWindowOpen.call(window, encode(url), target, features)
|
|
8332
|
+
navigate(url)
|
|
8333
|
+
return window
|
|
8334
|
+
}
|
|
8335
|
+
if (panel === null) return
|
|
8336
|
+
report()
|
|
8337
|
+
addEventListener('popstate', report)
|
|
8338
|
+
addEventListener('hashchange', report)
|
|
8339
|
+
addEventListener('pageshow', report)
|
|
8340
|
+
addEventListener('pagehide', function () { postPanel({ event: 'loading' }) })
|
|
8341
|
+
addEventListener('click', function (event) {
|
|
8342
|
+
if (event.defaultPrevented || event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey) return
|
|
8343
|
+
const anchor = event.target instanceof Element ? event.target.closest('a[href]') : null
|
|
8344
|
+
if (anchor === null || anchor.hasAttribute('download')) return
|
|
8345
|
+
const next = URL.parse(anchor.href)
|
|
8346
|
+
if (next === null || !/^https?:$/.test(next.protocol)) return
|
|
8347
|
+
if (next.host === new URL(pageUrl()).host) return
|
|
8348
|
+
event.preventDefault()
|
|
8349
|
+
navigate(next.href)
|
|
8350
|
+
})
|
|
8351
|
+
addEventListener('message', function (event) {
|
|
8352
|
+
if (event.source !== panel || !event.data || event.data.offsite !== 'browser') return
|
|
8353
|
+
event.stopImmediatePropagation()
|
|
8354
|
+
if (event.data.command === 'back') history.back()
|
|
8355
|
+
if (event.data.command === 'forward') history.forward()
|
|
8356
|
+
if (event.data.command === 'reload') realLocation.reload()
|
|
8357
|
+
})
|
|
8358
|
+
}`;
|
|
8359
|
+
// src/lib/proxy/proxy.ts
|
|
8360
|
+
const ROUTE = /^\/api\/proxy\/([^/]+)\/(.*)$/;
|
|
8361
|
+
const TARGET = /^(https?)\/([^/?#]+)(.*)$/;
|
|
8362
|
+
const SANDBOX = "sandbox allow-scripts allow-forms allow-popups allow-modals allow-downloads allow-presentation allow-pointer-lock";
|
|
8363
|
+
const UPSTREAM_TIMEOUT = 6e4;
|
|
8364
|
+
const MAX_BODY = 8388608;
|
|
8365
|
+
const MAX_HEADERS = 262144;
|
|
8366
|
+
const REQUEST_HEADERS_DROPPED = /* @__PURE__ */ new Set([
|
|
8367
|
+
"accept-encoding",
|
|
8368
|
+
"cdn-loop",
|
|
8369
|
+
"connection",
|
|
8370
|
+
"cookie",
|
|
8371
|
+
"forwarded",
|
|
8372
|
+
"host",
|
|
8373
|
+
"keep-alive",
|
|
8374
|
+
"origin",
|
|
8375
|
+
"proxy-authorization",
|
|
8376
|
+
"proxy-connection",
|
|
8377
|
+
"referer",
|
|
8378
|
+
"sec-fetch-site",
|
|
8379
|
+
"sec-fetch-user",
|
|
8380
|
+
"te",
|
|
8381
|
+
"trailer",
|
|
8382
|
+
"transfer-encoding",
|
|
8383
|
+
"true-client-ip",
|
|
8384
|
+
"upgrade",
|
|
8385
|
+
"via",
|
|
8386
|
+
"x-forwarded-for",
|
|
8387
|
+
"x-forwarded-host",
|
|
8388
|
+
"x-forwarded-port",
|
|
8389
|
+
"x-forwarded-proto",
|
|
8390
|
+
"x-real-ip"
|
|
8391
|
+
]);
|
|
8392
|
+
const RESPONSE_HEADERS_DROPPED = /* @__PURE__ */ new Set([
|
|
8393
|
+
"alt-svc",
|
|
8394
|
+
"clear-site-data",
|
|
8395
|
+
"connection",
|
|
8396
|
+
"content-security-policy",
|
|
8397
|
+
"content-security-policy-report-only",
|
|
8398
|
+
"expect-ct",
|
|
8399
|
+
"feature-policy",
|
|
8400
|
+
"keep-alive",
|
|
8401
|
+
"link",
|
|
8402
|
+
"location",
|
|
8403
|
+
"nel",
|
|
8404
|
+
"origin-agent-cluster",
|
|
8405
|
+
"permissions-policy",
|
|
8406
|
+
"public-key-pins",
|
|
8407
|
+
"referrer-policy",
|
|
8408
|
+
"refresh",
|
|
8409
|
+
"report-to",
|
|
8410
|
+
"reporting-endpoints",
|
|
8411
|
+
"service-worker-allowed",
|
|
8412
|
+
"set-cookie",
|
|
8413
|
+
"strict-transport-security",
|
|
8414
|
+
"transfer-encoding",
|
|
8415
|
+
"x-frame-options"
|
|
8416
|
+
]);
|
|
8417
|
+
const pageWrite = z.object({
|
|
8418
|
+
url: z.string(),
|
|
8419
|
+
cookie: z.string()
|
|
8420
|
+
});
|
|
8421
|
+
const storageWrite = z.object({
|
|
8422
|
+
origin: z.string(),
|
|
8423
|
+
data: z.record(z.string(), z.string())
|
|
8424
|
+
});
|
|
8425
|
+
function serveProxy(request, response) {
|
|
8426
|
+
answerProxy(request, response).catch((error) => {
|
|
8427
|
+
fail(response, 502, error instanceof Error ? error.message : String(error));
|
|
8428
|
+
});
|
|
8429
|
+
}
|
|
8430
|
+
async function answerProxy(request, response) {
|
|
8431
|
+
const route = ROUTE.exec(request.url ?? "");
|
|
8432
|
+
const site = route === null ? null : findSite(route[1] ?? "");
|
|
8433
|
+
response.setHeader("access-control-allow-origin", request.headers.origin ?? "*");
|
|
8434
|
+
response.setHeader("access-control-allow-credentials", "true");
|
|
8435
|
+
response.setHeader("access-control-expose-headers", "*");
|
|
8436
|
+
if (route === null || site === null) {
|
|
8437
|
+
fail(response, 404, "This page expired. Press reload to open it again.");
|
|
8438
|
+
return;
|
|
8439
|
+
}
|
|
8440
|
+
response.setHeader("content-security-policy", pagePolicy(site, request));
|
|
8441
|
+
response.setHeader("referrer-policy", "unsafe-url");
|
|
8442
|
+
if (request.method === "OPTIONS") {
|
|
8443
|
+
response.setHeader("access-control-allow-methods", request.headers["access-control-request-method"] ?? "*");
|
|
8444
|
+
response.setHeader("access-control-allow-headers", request.headers["access-control-request-headers"] ?? "*");
|
|
8445
|
+
response.statusCode = 204;
|
|
8446
|
+
response.end();
|
|
8447
|
+
return;
|
|
8448
|
+
}
|
|
8449
|
+
const rest = route[2] ?? "";
|
|
8450
|
+
if (rest.startsWith("runtime.js")) {
|
|
8451
|
+
serveRuntime(site, rest, response);
|
|
8452
|
+
return;
|
|
8453
|
+
}
|
|
8454
|
+
if (rest === "cookie" || rest === "storage") {
|
|
8455
|
+
await savePageData(site, rest, request, response);
|
|
8456
|
+
return;
|
|
8457
|
+
}
|
|
8458
|
+
const target = readTarget(rest);
|
|
8459
|
+
if (target === null) {
|
|
8460
|
+
fail(response, 404, "not found");
|
|
8461
|
+
return;
|
|
8462
|
+
}
|
|
8463
|
+
await forward(site, target, request, response);
|
|
8464
|
+
}
|
|
8465
|
+
function proxySocket(request, socket, head) {
|
|
8466
|
+
socket.on("error", () => socket.destroy());
|
|
8467
|
+
try {
|
|
8468
|
+
connectSocket(request, socket, head);
|
|
8469
|
+
} catch {
|
|
8470
|
+
socket.destroy();
|
|
8471
|
+
}
|
|
8472
|
+
}
|
|
8473
|
+
function connectSocket(request, socket, head) {
|
|
8474
|
+
const route = ROUTE.exec(request.url ?? "");
|
|
8475
|
+
const site = route === null ? null : findSite(route[1] ?? "");
|
|
8476
|
+
const target = route === null ? null : readTarget(route[2] ?? "");
|
|
8477
|
+
if (site === null || target === null) {
|
|
8478
|
+
socket.destroy();
|
|
8479
|
+
return;
|
|
8480
|
+
}
|
|
8481
|
+
const headers = upstreamHeaders(site, target, request);
|
|
8482
|
+
headers.connection = "Upgrade";
|
|
8483
|
+
headers.upgrade = request.headers.upgrade;
|
|
8484
|
+
const upstream = openUpstream(site, target, request, headers);
|
|
8485
|
+
upstream.on("upgrade", (answer, remote, remoteHead) => {
|
|
8486
|
+
const lines = [`HTTP/1.1 101 ${answer.statusMessage}`];
|
|
8487
|
+
for (let index = 0; index < answer.rawHeaders.length; index += 2) lines.push(`${answer.rawHeaders[index]}: ${answer.rawHeaders[index + 1]}`);
|
|
8488
|
+
socket.write(`${lines.join("\r\n")}\r\n\r\n`);
|
|
8489
|
+
if (remoteHead.length > 0) socket.write(remoteHead);
|
|
8490
|
+
if (head.length > 0) remote.write(head);
|
|
8491
|
+
remote.on("error", () => socket.destroy());
|
|
8492
|
+
socket.on("close", () => remote.destroy());
|
|
8493
|
+
remote.pipe(socket).pipe(remote);
|
|
8494
|
+
});
|
|
8495
|
+
upstream.on("response", () => socket.destroy());
|
|
8496
|
+
upstream.on("error", () => socket.destroy());
|
|
8497
|
+
upstream.end();
|
|
8498
|
+
}
|
|
8499
|
+
async function forward(site, target, request, response) {
|
|
8500
|
+
const headers = upstreamHeaders(site, target, request);
|
|
8501
|
+
headers["accept-encoding"] = "gzip, deflate, br";
|
|
8502
|
+
const upstream = openUpstream(site, target, request, headers);
|
|
8503
|
+
response.on("close", () => upstream.destroy());
|
|
8504
|
+
const answer = once(upstream, "response");
|
|
8505
|
+
upstream.on("error", () => void 0);
|
|
8506
|
+
request.pipe(upstream);
|
|
8507
|
+
const incoming = (await answer)[0];
|
|
8508
|
+
upstream.setTimeout(0);
|
|
8509
|
+
for (const cookie of incoming.headers["set-cookie"] ?? []) await site.jar.setCookie(cookie, target.href, { ignoreError: true });
|
|
8510
|
+
const kind = rewriteKind(incoming, request);
|
|
8511
|
+
response.statusCode = incoming.statusCode ?? 502;
|
|
8512
|
+
copyHeaders(incoming, response, kind !== null);
|
|
8513
|
+
const location = incoming.headers.location;
|
|
8514
|
+
if (location !== void 0) {
|
|
8515
|
+
const next = URL.parse(location, target);
|
|
8516
|
+
if (next === null) throw new Error(`${target.origin} sent a bad redirect`);
|
|
8517
|
+
response.setHeader("location", proxiedPath(site.key, next));
|
|
8518
|
+
}
|
|
8519
|
+
if (kind === null) {
|
|
8520
|
+
await pipeline(incoming, response);
|
|
8521
|
+
return;
|
|
8522
|
+
}
|
|
8523
|
+
const text = decodeBody(await readBody(incoming), incoming);
|
|
8524
|
+
response.setHeader("content-type", contentType(kind));
|
|
8525
|
+
response.end(await rewriteBody(site, target, kind, text));
|
|
8526
|
+
}
|
|
8527
|
+
function openUpstream(site, target, request, headers) {
|
|
8528
|
+
const secure = target.protocol === "https:";
|
|
8529
|
+
const port = Number(target.port) || (secure ? 443 : 80);
|
|
8530
|
+
const ownPort = request.socket.localPort ?? 0;
|
|
8531
|
+
const hostname = hostOf(target);
|
|
8532
|
+
if (target.host === request.headers.host) throw new Error("Pages cannot reach Offsite itself");
|
|
8533
|
+
if (isIP(hostname) !== 0) checkAddress(site, hostname, port, ownPort);
|
|
8534
|
+
const upstream = (secure ? https : http).request(target, {
|
|
8535
|
+
method: request.method,
|
|
8536
|
+
headers,
|
|
8537
|
+
lookup: guardedLookup(site, port, ownPort),
|
|
8538
|
+
maxHeaderSize: MAX_HEADERS,
|
|
8539
|
+
timeout: UPSTREAM_TIMEOUT,
|
|
8540
|
+
rejectUnauthorized: !site.local
|
|
8541
|
+
});
|
|
8542
|
+
upstream.on("timeout", () => upstream.destroy(/* @__PURE__ */ new Error(`${target.host} took too long to answer`)));
|
|
8543
|
+
return upstream;
|
|
8544
|
+
}
|
|
8545
|
+
function upstreamHeaders(site, target, request) {
|
|
8546
|
+
const headers = {};
|
|
8547
|
+
for (const name of Object.keys(request.headers)) if (!REQUEST_HEADERS_DROPPED.has(name) && !name.startsWith("cf-")) headers[name] = request.headers[name];
|
|
8548
|
+
headers.host = target.host;
|
|
8549
|
+
const cookie = site.jar.getCookieStringSync(target.href);
|
|
8550
|
+
if (cookie !== "") headers.cookie = cookie;
|
|
8551
|
+
const referer = refererOf(site, request);
|
|
8552
|
+
if (referer !== null) headers.referer = referer;
|
|
8553
|
+
else if (request.method !== "GET" && request.method !== "HEAD") headers.referer = `${target.origin}/`;
|
|
8554
|
+
if (request.headers.origin !== void 0) headers.origin = referer === null ? target.origin : new URL(referer).origin;
|
|
8555
|
+
return headers;
|
|
8556
|
+
}
|
|
8557
|
+
function refererOf(site, request) {
|
|
8558
|
+
const referer = request.headers.referer;
|
|
8559
|
+
const prefix = `/api/proxy/${site.key}/`;
|
|
8560
|
+
if (referer === void 0 || !referer.includes(prefix)) return null;
|
|
8561
|
+
return readTarget(referer.slice(referer.indexOf(prefix) + prefix.length))?.href ?? null;
|
|
8562
|
+
}
|
|
8563
|
+
function readTarget(rest) {
|
|
8564
|
+
const match = TARGET.exec(rest);
|
|
8565
|
+
if (match === null) return null;
|
|
8566
|
+
return URL.parse(`${match[1]}://${match[2]}${match[3]}`);
|
|
8567
|
+
}
|
|
8568
|
+
function pagePolicy(site, request) {
|
|
8569
|
+
const host = request.headers.host;
|
|
8570
|
+
if (host === void 0) throw new Error("the request has no host");
|
|
8571
|
+
const sources = [
|
|
8572
|
+
"http",
|
|
8573
|
+
"https",
|
|
8574
|
+
"ws",
|
|
8575
|
+
"wss"
|
|
8576
|
+
].map((scheme) => `${scheme}://${host}/api/proxy/${site.key}/`).join(" ");
|
|
8577
|
+
return `${SANDBOX}; default-src ${sources} 'unsafe-inline' 'unsafe-eval' data: blob:; form-action ${sources}; base-uri ${sources}`;
|
|
8578
|
+
}
|
|
8579
|
+
function rewriteKind(incoming, request) {
|
|
8580
|
+
if (request.headers["sec-fetch-dest"] === "empty") return null;
|
|
8581
|
+
const type = incoming.headers["content-type"]?.split(";")[0]?.trim() ?? "";
|
|
8582
|
+
if (type === "text/html" || type === "application/xhtml+xml") return "html";
|
|
8583
|
+
if (type === "text/css") return "css";
|
|
8584
|
+
if (/\b(java|ecma)script\b/.test(type)) return "script";
|
|
8585
|
+
return null;
|
|
8586
|
+
}
|
|
8587
|
+
function contentType(kind) {
|
|
8588
|
+
if (kind === "html") return "text/html; charset=utf-8";
|
|
8589
|
+
if (kind === "css") return "text/css; charset=utf-8";
|
|
8590
|
+
return "text/javascript; charset=utf-8";
|
|
8591
|
+
}
|
|
8592
|
+
async function rewriteBody(site, target, kind, text) {
|
|
8593
|
+
const rewrite = linkRewriter(site.key, target);
|
|
8594
|
+
if (kind === "css") return rewriteCss(text, rewrite);
|
|
8595
|
+
if (kind === "script") return rewriteScript(text, rewrite, `${site.key} ${target.origin}`);
|
|
8596
|
+
const script = pageScript({
|
|
8597
|
+
key: site.key,
|
|
8598
|
+
url: target.href,
|
|
8599
|
+
cookies: site.jar.getCookieStringSync(target.href, { http: false }),
|
|
8600
|
+
storage: site.storage.get(target.origin) ?? {},
|
|
8601
|
+
origin: null
|
|
8602
|
+
});
|
|
8603
|
+
return rewriteHtml(text, site.key, target, script);
|
|
8604
|
+
}
|
|
8605
|
+
function copyHeaders(incoming, response, rewritten) {
|
|
8606
|
+
for (const name of Object.keys(incoming.headers)) {
|
|
8607
|
+
const value = incoming.headers[name];
|
|
8608
|
+
if (value === void 0 || RESPONSE_HEADERS_DROPPED.has(name) || name.startsWith("access-control-") || name.startsWith("cross-origin-")) continue;
|
|
8609
|
+
if (rewritten && (name === "content-length" || name === "content-encoding" || name === "content-md5" || name === "etag")) continue;
|
|
8610
|
+
response.setHeader(name, value);
|
|
8611
|
+
}
|
|
8612
|
+
if (rewritten) {
|
|
8613
|
+
const cache = incoming.headers["cache-control"];
|
|
8614
|
+
response.setHeader("cache-control", cache === void 0 ? "no-transform" : `${cache}, no-transform`);
|
|
8615
|
+
}
|
|
8616
|
+
}
|
|
8617
|
+
async function readBody(incoming) {
|
|
8618
|
+
const encoding = incoming.headers["content-encoding"];
|
|
8619
|
+
if (encoding === void 0 || encoding === "identity") return buffer(incoming);
|
|
8620
|
+
if (encoding === "gzip") return buffer(incoming.pipe(zlib.createGunzip()));
|
|
8621
|
+
if (encoding === "deflate") return buffer(incoming.pipe(zlib.createInflate()));
|
|
8622
|
+
if (encoding === "br") return buffer(incoming.pipe(zlib.createBrotliDecompress()));
|
|
8623
|
+
throw new Error(`the site sent unknown compression: ${encoding}`);
|
|
8624
|
+
}
|
|
8625
|
+
function decodeBody(body, incoming) {
|
|
8626
|
+
const declared = /charset=["']?([\w-]+)/i.exec(incoming.headers["content-type"] ?? "")?.[1] ?? /<meta[^>]+charset=["']?([\w-]+)/i.exec(body.subarray(0, 1024).toString("latin1"))?.[1] ?? "utf-8";
|
|
8627
|
+
try {
|
|
8628
|
+
return new TextDecoder(declared).decode(body);
|
|
8629
|
+
} catch {
|
|
8630
|
+
return new TextDecoder("utf-8").decode(body);
|
|
8631
|
+
}
|
|
8632
|
+
}
|
|
8633
|
+
function serveRuntime(site, rest, response) {
|
|
8634
|
+
const query = new URL(rest, "http://localhost").searchParams;
|
|
8635
|
+
const url = query.get("url");
|
|
8636
|
+
const origin = query.get("origin");
|
|
8637
|
+
if (url === null || origin === null) {
|
|
8638
|
+
fail(response, 400, "the runtime needs a url and an origin");
|
|
8639
|
+
return;
|
|
8640
|
+
}
|
|
8641
|
+
response.setHeader("content-type", "text/javascript; charset=utf-8");
|
|
8642
|
+
response.end(pageScript({
|
|
8643
|
+
key: site.key,
|
|
8644
|
+
url,
|
|
8645
|
+
cookies: "",
|
|
8646
|
+
storage: {},
|
|
8647
|
+
origin
|
|
8648
|
+
}));
|
|
8649
|
+
}
|
|
8650
|
+
async function savePageData(site, kind, request, response) {
|
|
8651
|
+
if (request.method !== "POST") {
|
|
8652
|
+
fail(response, 405, "use POST");
|
|
8653
|
+
return;
|
|
8654
|
+
}
|
|
8655
|
+
const body = await buffer(request);
|
|
8656
|
+
if (body.length > MAX_BODY) {
|
|
8657
|
+
fail(response, 413, "too much page data");
|
|
8658
|
+
return;
|
|
8659
|
+
}
|
|
8660
|
+
const data = JSON.parse(body.toString());
|
|
8661
|
+
if (kind === "cookie") {
|
|
8662
|
+
const write = pageWrite.parse(data);
|
|
8663
|
+
await site.jar.setCookie(write.cookie, write.url, {
|
|
8664
|
+
ignoreError: true,
|
|
8665
|
+
http: false
|
|
8666
|
+
});
|
|
8667
|
+
}
|
|
8668
|
+
if (kind === "storage") {
|
|
8669
|
+
const write = storageWrite.parse(data);
|
|
8670
|
+
site.storage.set(write.origin, write.data);
|
|
8671
|
+
}
|
|
8672
|
+
response.statusCode = 204;
|
|
8673
|
+
response.end();
|
|
8674
|
+
}
|
|
8675
|
+
function fail(response, status, message) {
|
|
8676
|
+
if (response.headersSent) {
|
|
8677
|
+
response.destroy();
|
|
8678
|
+
return;
|
|
8679
|
+
}
|
|
8680
|
+
response.statusCode = status;
|
|
8681
|
+
response.setHeader("content-type", "text/plain; charset=utf-8");
|
|
8682
|
+
response.end(message);
|
|
8683
|
+
}
|
|
7540
8684
|
// src/storage/auth.ts
|
|
7541
8685
|
const CODE_LIFETIME = 3e5;
|
|
7542
8686
|
const DEVICE_LIFETIME = 2592e6;
|
|
@@ -7588,7 +8732,6 @@ function hash(secret) {
|
|
|
7588
8732
|
// src/main.ts
|
|
7589
8733
|
const UNPAIR_CHECK = 5e3;
|
|
7590
8734
|
const TICKET_LIFETIME = 6e4;
|
|
7591
|
-
const SNIFF_LENGTH = 8e3;
|
|
7592
8735
|
const openBody = z.object({ path: z.string() });
|
|
7593
8736
|
const contentTypes = {
|
|
7594
8737
|
".html": "text/html; charset=utf-8",
|
|
@@ -7660,6 +8803,10 @@ const server = createServer((request, response) => {
|
|
|
7660
8803
|
serveBrowse(url.pathname, request, response);
|
|
7661
8804
|
return;
|
|
7662
8805
|
}
|
|
8806
|
+
if (url.pathname.startsWith("/api/proxy/")) {
|
|
8807
|
+
serveProxy(request, response);
|
|
8808
|
+
return;
|
|
8809
|
+
}
|
|
7663
8810
|
if (url.pathname === `/api/open/${openSecret}`) {
|
|
7664
8811
|
answerOpen(request, response);
|
|
7665
8812
|
return;
|
|
@@ -7682,6 +8829,10 @@ const sockets = new WebSocketServer({
|
|
|
7682
8829
|
const paired = /* @__PURE__ */ new Map();
|
|
7683
8830
|
server.on("upgrade", (request, socket, head) => {
|
|
7684
8831
|
const url = new URL(request.url ?? "/", "http://localhost");
|
|
8832
|
+
if (url.pathname.startsWith("/api/proxy/")) {
|
|
8833
|
+
proxySocket(request, socket, head);
|
|
8834
|
+
return;
|
|
8835
|
+
}
|
|
7685
8836
|
const device = args.auth ? redeemTicket(url.searchParams.get("ticket")) : null;
|
|
7686
8837
|
if (url.pathname !== "/ws" || args.auth && device === null) {
|
|
7687
8838
|
socket.destroy();
|
|
@@ -7816,19 +8967,9 @@ function serveBrowse(pathname, request, response) {
|
|
|
7816
8967
|
response.end(`${file} is not a file`);
|
|
7817
8968
|
return;
|
|
7818
8969
|
}
|
|
7819
|
-
response.
|
|
7820
|
-
response.setHeader("cache-control", "no-cache");
|
|
8970
|
+
setBrowseHeaders(response, browseTypes.get(path.extname(file).toLowerCase()) ?? sniffType(file));
|
|
7821
8971
|
streamFile(file, stats.size, request, response);
|
|
7822
8972
|
}
|
|
7823
|
-
function browseType(file) {
|
|
7824
|
-
const known = browseTypes.get(path.extname(file).toLowerCase());
|
|
7825
|
-
if (known !== void 0) return known;
|
|
7826
|
-
const head = Buffer.alloc(SNIFF_LENGTH);
|
|
7827
|
-
const handle = openSync(file, "r");
|
|
7828
|
-
const read = readSync(handle, head, 0, SNIFF_LENGTH, 0);
|
|
7829
|
-
closeSync(handle);
|
|
7830
|
-
return head.subarray(0, read).includes(0) ? "application/octet-stream" : "text/plain; charset=utf-8";
|
|
7831
|
-
}
|
|
7832
8973
|
async function answerOpen(request, response) {
|
|
7833
8974
|
if (request.method !== "POST") {
|
|
7834
8975
|
response.statusCode = 405;
|