@ai-ecoverse/slicc-kernel 1.89.0 → 1.91.0
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.
- package/README.md +33 -5
- package/dist/driver-worker.js +89 -1
- package/dist/driver-worker.js.map +3 -3
- package/dist/index.js +135 -5
- package/dist/index.js.map +3 -3
- package/dist/kernel-worker.js +767 -95
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +89 -1
- package/dist/node-driver-worker.js.map +3 -3
- package/dist/node-process-worker.js +192 -87
- package/dist/node-process-worker.js.map +4 -4
- package/dist/node.js +1801 -1056
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +154 -49
- package/dist/process-worker.js.map +3 -3
- package/dist/testing.js.map +1 -1
- package/package.json +3 -3
package/dist/node.js
CHANGED
|
@@ -92,6 +92,100 @@ function portCdpHook(ask, gone) {
|
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
// src/facade/port.ts
|
|
96
|
+
var FACADE_GONE = "the facade host is gone";
|
|
97
|
+
function serveFacadePort(port, hook, { name: name2, stamp }, onDone) {
|
|
98
|
+
let conn;
|
|
99
|
+
let done4 = false;
|
|
100
|
+
const end = () => {
|
|
101
|
+
done4 = true;
|
|
102
|
+
port.close();
|
|
103
|
+
onDone?.();
|
|
104
|
+
};
|
|
105
|
+
const finish = () => {
|
|
106
|
+
if (done4) return;
|
|
107
|
+
port.postMessage({ close: true });
|
|
108
|
+
end();
|
|
109
|
+
};
|
|
110
|
+
port.addEventListener(
|
|
111
|
+
"message",
|
|
112
|
+
({ data }) => {
|
|
113
|
+
if (data.b !== void 0) conn?.send(data.b);
|
|
114
|
+
else if (data.close && !done4) {
|
|
115
|
+
end();
|
|
116
|
+
conn?.close(typeof data.close === "string" ? data.close : void 0);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
port.start?.();
|
|
121
|
+
new Promise((resolve2) => resolve2(hook(stamp, { name: name2 }))).then(
|
|
122
|
+
(opened2) => {
|
|
123
|
+
conn = opened2;
|
|
124
|
+
if (done4) return opened2.close();
|
|
125
|
+
opened2.onmessage = (bytes) => {
|
|
126
|
+
if (done4) return;
|
|
127
|
+
const copy = bytes.slice();
|
|
128
|
+
port.postMessage({ b: copy }, [copy.buffer]);
|
|
129
|
+
};
|
|
130
|
+
opened2.onclose = () => finish();
|
|
131
|
+
port.postMessage({ open: true });
|
|
132
|
+
},
|
|
133
|
+
(e) => {
|
|
134
|
+
if (done4) return;
|
|
135
|
+
port.postMessage({ error: e instanceof Error ? e.message : String(e) });
|
|
136
|
+
end();
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
return () => {
|
|
140
|
+
if (done4) return;
|
|
141
|
+
conn?.close();
|
|
142
|
+
finish();
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function portFacadeHook(ask, gone) {
|
|
146
|
+
return (stamp, { name: name2 }) => new Promise((resolve2, reject) => {
|
|
147
|
+
const { port1, port2 } = new MessageChannel();
|
|
148
|
+
let closed = false;
|
|
149
|
+
const shut = () => {
|
|
150
|
+
closed = true;
|
|
151
|
+
port1.close();
|
|
152
|
+
};
|
|
153
|
+
const conn = {
|
|
154
|
+
send: (bytes) => {
|
|
155
|
+
if (closed) return;
|
|
156
|
+
const copy = bytes.slice();
|
|
157
|
+
port1.postMessage({ b: copy }, [copy.buffer]);
|
|
158
|
+
},
|
|
159
|
+
onmessage: null,
|
|
160
|
+
onclose: null,
|
|
161
|
+
close: (reason) => {
|
|
162
|
+
if (closed) return;
|
|
163
|
+
port1.postMessage({ close: reason ?? true });
|
|
164
|
+
shut();
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
port1.addEventListener("message", ({ data }) => {
|
|
168
|
+
if ("b" in data) conn.onmessage?.(data.b);
|
|
169
|
+
else if ("open" in data) resolve2(conn);
|
|
170
|
+
else if ("error" in data) {
|
|
171
|
+
shut();
|
|
172
|
+
reject(new Error(data.error));
|
|
173
|
+
} else if (!closed) {
|
|
174
|
+
shut();
|
|
175
|
+
conn.onclose?.();
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
port1.start();
|
|
179
|
+
void gone?.then(() => {
|
|
180
|
+
if (closed) return;
|
|
181
|
+
shut();
|
|
182
|
+
reject(new Error(FACADE_GONE));
|
|
183
|
+
conn.onclose?.(FACADE_GONE);
|
|
184
|
+
});
|
|
185
|
+
ask({ name: name2, stamp }, port2);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
95
189
|
// src/fs/types.ts
|
|
96
190
|
var DEFAULT_USERS = "enforce";
|
|
97
191
|
function rangedOps(fs) {
|
|
@@ -376,8 +470,8 @@ async function access(fs, cred, path, want, options = {}) {
|
|
|
376
470
|
if (await present(fs, walked.path, false)) return { ...walked, denied: walked.path };
|
|
377
471
|
return { ...walked, missing: true };
|
|
378
472
|
}
|
|
379
|
-
function refused(path,
|
|
380
|
-
return fsError(
|
|
473
|
+
function refused(path, errno2 = "EACCES") {
|
|
474
|
+
return fsError(errno2, path);
|
|
381
475
|
}
|
|
382
476
|
|
|
383
477
|
// src/fs/dac.ts
|
|
@@ -437,9 +531,9 @@ var Created = class {
|
|
|
437
531
|
function linked(g, options) {
|
|
438
532
|
return g.links === void 0 ? options : { ...options, links: g.links };
|
|
439
533
|
}
|
|
440
|
-
function deny(g, op, path,
|
|
534
|
+
function deny(g, op, path, errno2 = "EACCES") {
|
|
441
535
|
const tag = g.tag === void 0 ? {} : { tag: g.tag };
|
|
442
|
-
g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno, ...tag });
|
|
536
|
+
g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno: errno2, ...tag });
|
|
443
537
|
}
|
|
444
538
|
var TTY_MODE = 400;
|
|
445
539
|
function ttyOpenable(g, tty) {
|
|
@@ -1106,7 +1200,7 @@ var WASI_SIGNAL_TO_POSIX = Object.fromEntries(
|
|
|
1106
1200
|
|
|
1107
1201
|
// src/process/wasi-errno.ts
|
|
1108
1202
|
var WASI_ERRNO = {
|
|
1109
|
-
...Object.fromEntries(Object.entries(E).map(([name2,
|
|
1203
|
+
...Object.fromEntries(Object.entries(E).map(([name2, errno2]) => [`E${name2}`, errno2])),
|
|
1110
1204
|
EWOULDBLOCK: E.AGAIN,
|
|
1111
1205
|
ENOTSUP: 138,
|
|
1112
1206
|
EOPNOTSUPP: 138,
|
|
@@ -1203,7 +1297,7 @@ function dialError(e, timedOut, closed) {
|
|
|
1203
1297
|
if (timedOut) return new KernelError("ETIMEDOUT");
|
|
1204
1298
|
if (closed) return new KernelError("ECONNABORTED");
|
|
1205
1299
|
const code = e?.code;
|
|
1206
|
-
const known = DIAL_ERRORS.find((
|
|
1300
|
+
const known = DIAL_ERRORS.find((errno2) => errno2 === code);
|
|
1207
1301
|
return new KernelError(known ?? "EHOSTUNREACH");
|
|
1208
1302
|
}
|
|
1209
1303
|
function isLoopback(host) {
|
|
@@ -1260,6 +1354,7 @@ var KernelSocket = class _KernelSocket {
|
|
|
1260
1354
|
local;
|
|
1261
1355
|
peer;
|
|
1262
1356
|
peerCred;
|
|
1357
|
+
peerStamp;
|
|
1263
1358
|
listenerCred;
|
|
1264
1359
|
link;
|
|
1265
1360
|
readShut = false;
|
|
@@ -1330,10 +1425,10 @@ var KernelSocket = class _KernelSocket {
|
|
|
1330
1425
|
for (const wait2 of waits) wait2.catch(() => void 0);
|
|
1331
1426
|
return pipeCall(() => Promise.race(waits));
|
|
1332
1427
|
}
|
|
1333
|
-
bind(addr) {
|
|
1428
|
+
bind(addr, kernel = false) {
|
|
1334
1429
|
if (this.state !== "open" || this.local) throw new KernelError("EINVAL");
|
|
1335
1430
|
if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
|
|
1336
|
-
this.local = this.net.bind(this, addr);
|
|
1431
|
+
this.local = this.net.bind(this, addr, kernel);
|
|
1337
1432
|
this.boundAt = this.local;
|
|
1338
1433
|
}
|
|
1339
1434
|
listen(backlog, cred) {
|
|
@@ -1362,7 +1457,7 @@ var KernelSocket = class _KernelSocket {
|
|
|
1362
1457
|
this.changes.wake();
|
|
1363
1458
|
return true;
|
|
1364
1459
|
}
|
|
1365
|
-
connect(addr, viaUplink = true, cred) {
|
|
1460
|
+
connect(addr, viaUplink = true, cred, stamp) {
|
|
1366
1461
|
if (this.state === "connected") throw new KernelError("EISCONN");
|
|
1367
1462
|
if (this.state === "connecting") throw new KernelError("EALREADY");
|
|
1368
1463
|
if (this.state !== "open") throw new KernelError("EINVAL");
|
|
@@ -1381,6 +1476,7 @@ var KernelSocket = class _KernelSocket {
|
|
|
1381
1476
|
server.local = addr.family === "inet" ? { ...addr, host: canonicalHost(addr.host) } : addr;
|
|
1382
1477
|
server.peer = this.local;
|
|
1383
1478
|
server.peerCred = cred;
|
|
1479
|
+
server.peerStamp = stamp;
|
|
1384
1480
|
this.peer = server.local;
|
|
1385
1481
|
this.peerCred = target.listenerCred;
|
|
1386
1482
|
link(this, server);
|
|
@@ -1521,6 +1617,8 @@ var LoopbackNet = class {
|
|
|
1521
1617
|
bound = /* @__PURE__ */ new Map();
|
|
1522
1618
|
activators = /* @__PURE__ */ new Map();
|
|
1523
1619
|
internal = /* @__PURE__ */ new Set();
|
|
1620
|
+
reserved = /* @__PURE__ */ new Set();
|
|
1621
|
+
facades = /* @__PURE__ */ new Map();
|
|
1524
1622
|
nextEphemeral = EPHEMERAL_FIRST;
|
|
1525
1623
|
route;
|
|
1526
1624
|
useUplink(route) {
|
|
@@ -1535,6 +1633,18 @@ var LoopbackNet = class {
|
|
|
1535
1633
|
if (reserve) this.internal.add(port);
|
|
1536
1634
|
return this.internal.has(port);
|
|
1537
1635
|
}
|
|
1636
|
+
reserve(port) {
|
|
1637
|
+
this.reserved.add(port);
|
|
1638
|
+
}
|
|
1639
|
+
facade(port, name2) {
|
|
1640
|
+
this.reserve(port);
|
|
1641
|
+
this.kernelOnly(port, true);
|
|
1642
|
+
this.facades.set(port, name2);
|
|
1643
|
+
}
|
|
1644
|
+
facadeAt(addr) {
|
|
1645
|
+
if (addr.family !== "inet" || !isLoopback(canonicalHost(addr.host))) return void 0;
|
|
1646
|
+
return this.facades.get(addr.port);
|
|
1647
|
+
}
|
|
1538
1648
|
activate(addr, start2) {
|
|
1539
1649
|
this.activators.set(keyOf(addr), start2);
|
|
1540
1650
|
}
|
|
@@ -1543,7 +1653,7 @@ var LoopbackNet = class {
|
|
|
1543
1653
|
}
|
|
1544
1654
|
listen(addr, backlog = 128) {
|
|
1545
1655
|
const socket = this.socket(addr.family);
|
|
1546
|
-
socket.bind(addr);
|
|
1656
|
+
socket.bind(addr, true);
|
|
1547
1657
|
socket.listen(backlog);
|
|
1548
1658
|
return socket;
|
|
1549
1659
|
}
|
|
@@ -1552,11 +1662,13 @@ var LoopbackNet = class {
|
|
|
1552
1662
|
void socket.connect(addr, false);
|
|
1553
1663
|
return socket;
|
|
1554
1664
|
}
|
|
1555
|
-
bind(socket, addr) {
|
|
1665
|
+
bind(socket, addr, kernel = false) {
|
|
1556
1666
|
if (addr.family === "unix") {
|
|
1557
1667
|
if (addr.path === "") throw new KernelError("EINVAL");
|
|
1558
1668
|
} else if (addr.host !== "0.0.0.0" && !isLoopback(addr.host)) {
|
|
1559
1669
|
throw new KernelError("EADDRNOTAVAIL");
|
|
1670
|
+
} else if (!kernel && this.reserved.has(addr.port)) {
|
|
1671
|
+
throw new KernelError("EADDRINUSE");
|
|
1560
1672
|
}
|
|
1561
1673
|
const named = addr.family === "inet" && addr.port === 0 ? { ...addr, port: this.ephemeral() } : addr;
|
|
1562
1674
|
const key = keyOf(named);
|
|
@@ -1583,7 +1695,7 @@ var LoopbackNet = class {
|
|
|
1583
1695
|
for (let i = 0; i < span; i++) {
|
|
1584
1696
|
const port = this.nextEphemeral;
|
|
1585
1697
|
this.nextEphemeral = port === EPHEMERAL_LAST ? EPHEMERAL_FIRST : port + 1;
|
|
1586
|
-
if (!this.bound.has(`inet:${port}`)) return port;
|
|
1698
|
+
if (!this.bound.has(`inet:${port}`) && !this.reserved.has(port)) return port;
|
|
1587
1699
|
}
|
|
1588
1700
|
throw new KernelError("EADDRINUSE");
|
|
1589
1701
|
}
|
|
@@ -2041,11 +2153,11 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
|
|
|
2041
2153
|
};
|
|
2042
2154
|
const sockets = /* @__PURE__ */ new Map();
|
|
2043
2155
|
const credit = /* @__PURE__ */ new Map();
|
|
2044
|
-
const refill = (nid, n,
|
|
2156
|
+
const refill = (nid, n, closing2 = false) => {
|
|
2045
2157
|
const flow = credit.get(nid);
|
|
2046
2158
|
if (!flow) return;
|
|
2047
2159
|
flow.inflight -= n;
|
|
2048
|
-
if (
|
|
2160
|
+
if (closing2 && !flow.closing) {
|
|
2049
2161
|
flow.closing = true;
|
|
2050
2162
|
flow.deadline();
|
|
2051
2163
|
}
|
|
@@ -2068,12 +2180,12 @@ function serveTransport(peer, transport, { closeWaitMs = CLOSE_WAIT_MS } = {}) {
|
|
|
2068
2180
|
}
|
|
2069
2181
|
sockets.set(call.nid, socket);
|
|
2070
2182
|
peer.postMessage({ net: "ws-opened", nid: call.nid, protocol: socket.protocol });
|
|
2071
|
-
void
|
|
2183
|
+
void relay2(call.nid, socket);
|
|
2072
2184
|
} catch (e) {
|
|
2073
2185
|
fail3(call.nid, e);
|
|
2074
2186
|
}
|
|
2075
2187
|
};
|
|
2076
|
-
const
|
|
2188
|
+
const relay2 = async (nid, socket) => {
|
|
2077
2189
|
let ending;
|
|
2078
2190
|
let late;
|
|
2079
2191
|
const unanswered = new Promise((resolve2) => {
|
|
@@ -2509,7 +2621,7 @@ var UserDb = class {
|
|
|
2509
2621
|
};
|
|
2510
2622
|
|
|
2511
2623
|
// src/client/protocol.ts
|
|
2512
|
-
var PROTOCOL = [1,
|
|
2624
|
+
var PROTOCOL = [1, 11];
|
|
2513
2625
|
var FS_METHODS = [
|
|
2514
2626
|
"readFile",
|
|
2515
2627
|
"writeFile",
|
|
@@ -2734,6 +2846,45 @@ function cdpRegistry(send, gone) {
|
|
|
2734
2846
|
}
|
|
2735
2847
|
};
|
|
2736
2848
|
}
|
|
2849
|
+
function facadeRegistry(send, gone) {
|
|
2850
|
+
const registered = /* @__PURE__ */ new Map();
|
|
2851
|
+
const drop = (id2) => {
|
|
2852
|
+
registered.get(id2)?.();
|
|
2853
|
+
return registered.delete(id2);
|
|
2854
|
+
};
|
|
2855
|
+
return {
|
|
2856
|
+
serve(l, req) {
|
|
2857
|
+
const hook = portFacadeHook(
|
|
2858
|
+
(request, facade) => send({ facadeOpen: { ...request, registration: req.id }, port: facade }, [
|
|
2859
|
+
facade
|
|
2860
|
+
]),
|
|
2861
|
+
gone
|
|
2862
|
+
);
|
|
2863
|
+
registered.set(req.id, l.facades.register(req.name, hook));
|
|
2864
|
+
return req.id;
|
|
2865
|
+
},
|
|
2866
|
+
drop,
|
|
2867
|
+
close: () => {
|
|
2868
|
+
for (const id2 of [...registered.keys()]) drop(id2);
|
|
2869
|
+
}
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2872
|
+
function hostRegistries(send, gone) {
|
|
2873
|
+
const cdps = cdpRegistry(send, gone);
|
|
2874
|
+
const facades = facadeRegistry(send, gone);
|
|
2875
|
+
return {
|
|
2876
|
+
answer(l, req) {
|
|
2877
|
+
if (req.op === "serve-cdp") return cdps.serve(l, req);
|
|
2878
|
+
if (req.op === "unserve-cdp") return cdps.drop(req.registration);
|
|
2879
|
+
if (req.op === "serve-facade") return facades.serve(l, req);
|
|
2880
|
+
return facades.drop(req.registration);
|
|
2881
|
+
},
|
|
2882
|
+
close() {
|
|
2883
|
+
cdps.close();
|
|
2884
|
+
facades.close();
|
|
2885
|
+
}
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2737
2888
|
function visibleTo(g, notify) {
|
|
2738
2889
|
if (!g?.dac.enforcing) return notify;
|
|
2739
2890
|
let queue = Promise.resolve();
|
|
@@ -2777,7 +2928,7 @@ function serveClient(port, host) {
|
|
|
2777
2928
|
if (state !== "closed") port.postMessage(message3, transfer);
|
|
2778
2929
|
};
|
|
2779
2930
|
const reply = (id2, body, transfer) => send({ id: id2, ...body }, transfer);
|
|
2780
|
-
const
|
|
2931
|
+
const hosts = hostRegistries(send, ended.promise);
|
|
2781
2932
|
function detach(kill = false, bye = "the slicc-kernel detached this client") {
|
|
2782
2933
|
if (state === "closed") return;
|
|
2783
2934
|
send({ bye });
|
|
@@ -2785,7 +2936,7 @@ function serveClient(port, host) {
|
|
|
2785
2936
|
net?.close();
|
|
2786
2937
|
for (const unwatch of watches.values()) unwatch();
|
|
2787
2938
|
watches.clear();
|
|
2788
|
-
|
|
2939
|
+
hosts.close();
|
|
2789
2940
|
for (const session of terminals.values()) session.close();
|
|
2790
2941
|
terminals.clear();
|
|
2791
2942
|
if (kill) killGroups(host, [...groups]);
|
|
@@ -2882,16 +3033,16 @@ function serveClient(port, host) {
|
|
|
2882
3033
|
case "mount":
|
|
2883
3034
|
return { result: await l.mountAs(clientCred(self), req.spec) };
|
|
2884
3035
|
case "umount":
|
|
2885
|
-
await l.umountAs(clientCred(self), req.target);
|
|
2886
|
-
return { result: true };
|
|
3036
|
+
return { result: await l.umountAs(clientCred(self), req.target).then(() => true) };
|
|
2887
3037
|
case "mounts":
|
|
2888
3038
|
return { result: l.mounts.list() };
|
|
2889
3039
|
case "dial":
|
|
2890
3040
|
return dialFor(l, req, watches);
|
|
2891
3041
|
case "serve-cdp":
|
|
2892
|
-
return { result: cdps.serve(l, req) };
|
|
2893
3042
|
case "unserve-cdp":
|
|
2894
|
-
|
|
3043
|
+
case "serve-facade":
|
|
3044
|
+
case "unserve-facade":
|
|
3045
|
+
return { result: hosts.answer(l, req) };
|
|
2895
3046
|
case "detach":
|
|
2896
3047
|
return { result: true };
|
|
2897
3048
|
default:
|
|
@@ -4646,15 +4797,15 @@ var UnlinkedKeeper = class {
|
|
|
4646
4797
|
wrap(fs = this.fs) {
|
|
4647
4798
|
const keeper = this;
|
|
4648
4799
|
const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
|
|
4649
|
-
const
|
|
4800
|
+
const counted3 = (st) => {
|
|
4650
4801
|
const hidden = keeper.links.get(`${st.dev ?? 0}:${st.ino}`) ?? 0;
|
|
4651
4802
|
return hidden === 0 || st.nlink === void 0 ? st : { ...st, nlink: Math.max(0, st.nlink - hidden) };
|
|
4652
4803
|
};
|
|
4653
4804
|
return {
|
|
4654
4805
|
...fs,
|
|
4655
4806
|
links: (path) => mayLink(fs, path),
|
|
4656
|
-
stat: async (path) =>
|
|
4657
|
-
lstat: async (path) =>
|
|
4807
|
+
stat: async (path) => counted3(await fs.stat(path)),
|
|
4808
|
+
lstat: async (path) => counted3(await fs.lstat(path)),
|
|
4658
4809
|
async rm(path, options) {
|
|
4659
4810
|
const target = fs.resolvePath("/", path);
|
|
4660
4811
|
const pinned = await keeper.pin(target);
|
|
@@ -4668,7 +4819,7 @@ var UnlinkedKeeper = class {
|
|
|
4668
4819
|
await keeper.settle(pinned, () => fs.rename(from, to));
|
|
4669
4820
|
},
|
|
4670
4821
|
readdir: async (path) => visible(await fs.readdir(path)),
|
|
4671
|
-
readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st &&
|
|
4822
|
+
readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR).map(([name2, st]) => [name2, st && counted3(st)])
|
|
4672
4823
|
};
|
|
4673
4824
|
}
|
|
4674
4825
|
async settle(pinned, op) {
|
|
@@ -5095,6 +5246,9 @@ async function acceptKey(headers) {
|
|
|
5095
5246
|
}
|
|
5096
5247
|
const key = field(headers, "sec-websocket-key") ?? "";
|
|
5097
5248
|
if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) throw new HttpError(400, "bad Sec-WebSocket-Key");
|
|
5249
|
+
return acceptFor(key);
|
|
5250
|
+
}
|
|
5251
|
+
async function acceptFor(key) {
|
|
5098
5252
|
const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(key + GUID));
|
|
5099
5253
|
return btoa(String.fromCharCode(...new Uint8Array(digest)));
|
|
5100
5254
|
}
|
|
@@ -5118,6 +5272,19 @@ function frame(opcode, payload) {
|
|
|
5118
5272
|
out.set(payload, size);
|
|
5119
5273
|
return out;
|
|
5120
5274
|
}
|
|
5275
|
+
function clientFrame(opcode, payload) {
|
|
5276
|
+
const plain = frame(opcode, payload);
|
|
5277
|
+
const head = plain.length - payload.length;
|
|
5278
|
+
const mask = crypto.getRandomValues(new Uint8Array(4));
|
|
5279
|
+
const out = new Uint8Array(plain.length + 4);
|
|
5280
|
+
out.set(plain.subarray(0, head));
|
|
5281
|
+
out[1] = out[1] | 128;
|
|
5282
|
+
out.set(mask, head);
|
|
5283
|
+
for (let i = 0; i < payload.length; i++) {
|
|
5284
|
+
out[head + 4 + i] = payload[i] ^ mask[i & 3];
|
|
5285
|
+
}
|
|
5286
|
+
return out;
|
|
5287
|
+
}
|
|
5121
5288
|
function closeFrame(code, reason = "") {
|
|
5122
5289
|
let bytes = new TextEncoder().encode(reason);
|
|
5123
5290
|
if (bytes.length > 123) {
|
|
@@ -5157,10 +5324,12 @@ var WsReader = class {
|
|
|
5157
5324
|
continuing = false;
|
|
5158
5325
|
binaryMessage = false;
|
|
5159
5326
|
binary;
|
|
5160
|
-
|
|
5327
|
+
fromServer;
|
|
5328
|
+
constructor(incoming, max = MAX_MESSAGE, binary, fromServer = false) {
|
|
5161
5329
|
this.incoming = incoming;
|
|
5162
5330
|
this.max = max;
|
|
5163
5331
|
this.binary = binary;
|
|
5332
|
+
this.fromServer = fromServer;
|
|
5164
5333
|
}
|
|
5165
5334
|
async next(signal) {
|
|
5166
5335
|
for (; ; ) {
|
|
@@ -5172,7 +5341,7 @@ var WsReader = class {
|
|
|
5172
5341
|
const fin = (head[0] & 128) !== 0;
|
|
5173
5342
|
const opcode = head[0] & 15;
|
|
5174
5343
|
if ((head[0] & 112) !== 0) throw new WsError(CLOSE.protocol, "reserved bits are set");
|
|
5175
|
-
|
|
5344
|
+
const masked = this.masking(head[1]);
|
|
5176
5345
|
const length2 = await this.length(head[1] & 127, signal);
|
|
5177
5346
|
const control = opcode >= 8;
|
|
5178
5347
|
if (control && (length2 > 125 || !fin)) {
|
|
@@ -5181,15 +5350,19 @@ var WsReader = class {
|
|
|
5181
5350
|
if (!control && this.size + length2 > this.max) {
|
|
5182
5351
|
throw new WsError(CLOSE.tooBig, `a message is over ${this.max} bytes`);
|
|
5183
5352
|
}
|
|
5184
|
-
const
|
|
5185
|
-
await fill(this.incoming, mask, signal);
|
|
5186
|
-
const payload = new Uint8Array(length2);
|
|
5187
|
-
await fill(this.incoming, payload, signal);
|
|
5188
|
-
for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
|
|
5353
|
+
const payload = await this.payload(length2, masked, signal);
|
|
5189
5354
|
const event = this.take(opcode, fin, payload);
|
|
5190
5355
|
if (event) return event;
|
|
5191
5356
|
}
|
|
5192
5357
|
}
|
|
5358
|
+
masking(second) {
|
|
5359
|
+
const masked = (second & 128) !== 0;
|
|
5360
|
+
if (masked !== this.fromServer) return masked;
|
|
5361
|
+
throw new WsError(
|
|
5362
|
+
CLOSE.protocol,
|
|
5363
|
+
this.fromServer ? "server frames must not be masked" : "client frames must be masked"
|
|
5364
|
+
);
|
|
5365
|
+
}
|
|
5193
5366
|
data(opcode, fin, payload) {
|
|
5194
5367
|
if (opcode === OP.continuation !== this.continuing) {
|
|
5195
5368
|
throw new WsError(CLOSE.protocol, "unexpected continuation frame");
|
|
@@ -5204,6 +5377,14 @@ var WsReader = class {
|
|
|
5204
5377
|
this.size = 0;
|
|
5205
5378
|
return this.binaryMessage ? { kind: "binary", bytes: whole } : { kind: "text", text: decode(whole) };
|
|
5206
5379
|
}
|
|
5380
|
+
async payload(length2, masked, signal) {
|
|
5381
|
+
const mask = new Uint8Array(4);
|
|
5382
|
+
if (masked) await fill(this.incoming, mask, signal);
|
|
5383
|
+
const payload = new Uint8Array(length2);
|
|
5384
|
+
await fill(this.incoming, payload, signal);
|
|
5385
|
+
if (masked) for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
|
|
5386
|
+
return payload;
|
|
5387
|
+
}
|
|
5207
5388
|
async length(short, signal) {
|
|
5208
5389
|
if (short < 126) return short;
|
|
5209
5390
|
const ext = new Uint8Array(short === 126 ? 2 : 8);
|
|
@@ -5478,6 +5659,7 @@ var Relay = class {
|
|
|
5478
5659
|
function enableCdp(net, hosts) {
|
|
5479
5660
|
let facade;
|
|
5480
5661
|
net.kernelOnly(CDP_PORT, true);
|
|
5662
|
+
net.reserve(CDP_PORT);
|
|
5481
5663
|
net.activate({ family: "inet", host: "127.0.0.1", port: CDP_PORT }, () => {
|
|
5482
5664
|
const started = new CdpFacade({ net, hosts });
|
|
5483
5665
|
facade = started;
|
|
@@ -5792,9 +5974,9 @@ async function scanFilesystems(fs, modules) {
|
|
|
5792
5974
|
} catch {
|
|
5793
5975
|
continue;
|
|
5794
5976
|
}
|
|
5795
|
-
const
|
|
5796
|
-
if (!
|
|
5797
|
-
for (const [type, entry] of Object.entries(
|
|
5977
|
+
const declared2 = manifest.slicc?.filesystems;
|
|
5978
|
+
if (!declared2 || typeof declared2 !== "object") continue;
|
|
5979
|
+
for (const [type, entry] of Object.entries(declared2)) {
|
|
5798
5980
|
const module = inside(pkg, entry?.module);
|
|
5799
5981
|
if (NAME2.test(type) && module && !found.has(type)) found.set(type, module);
|
|
5800
5982
|
}
|
|
@@ -5812,9 +5994,9 @@ async function scanBinfmts(fs, modules, warn = () => {
|
|
|
5812
5994
|
} catch {
|
|
5813
5995
|
continue;
|
|
5814
5996
|
}
|
|
5815
|
-
const
|
|
5816
|
-
if (!
|
|
5817
|
-
for (const [suffix, command] of Object.entries(
|
|
5997
|
+
const declared2 = manifest.slicc?.binfmt;
|
|
5998
|
+
if (!declared2 || typeof declared2 !== "object") continue;
|
|
5999
|
+
for (const [suffix, command] of Object.entries(declared2)) {
|
|
5818
6000
|
if (!SUFFIX.test(suffix) || typeof command !== "string" || !NAME2.test(command)) continue;
|
|
5819
6001
|
const owner = owners.get(suffix);
|
|
5820
6002
|
if (owner === void 0) {
|
|
@@ -5844,94 +6026,831 @@ function binfmtOf(binfmts, path) {
|
|
|
5844
6026
|
return best === void 0 ? void 0 : binfmts.get(best);
|
|
5845
6027
|
}
|
|
5846
6028
|
|
|
5847
|
-
// src/
|
|
5848
|
-
var
|
|
5849
|
-
var
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
6029
|
+
// src/kernel/net/loopback-names.ts
|
|
6030
|
+
var HOST_LOOPBACK = "host.slicc.internal";
|
|
6031
|
+
var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
|
|
6032
|
+
var PAGE_LOOPBACK = "kernel.localhost";
|
|
6033
|
+
var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
|
|
6034
|
+
function toHostLoopback(href) {
|
|
6035
|
+
const url = new URL(href);
|
|
6036
|
+
if (url.hostname.replace(/\.$/, "").toLowerCase() !== HOST_LOOPBACK) return href;
|
|
6037
|
+
url.hostname = "127.0.0.1";
|
|
6038
|
+
return url.href;
|
|
5856
6039
|
}
|
|
5857
|
-
|
|
5858
|
-
|
|
6040
|
+
var LOOPBACK_V4 = /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
|
6041
|
+
var LOOPBACK_V6 = /* @__PURE__ */ new Set(["::1", "::", "0:0:0:0:0:0:0:1"]);
|
|
6042
|
+
var MAPPED_V4 = /^::ffff:7f[0-9a-f]{2}:[0-9a-f]{1,4}$/;
|
|
6043
|
+
var KERNEL_PORT = /^(\d{1,5})\.kernel\.localhost$/;
|
|
6044
|
+
var SCHEMES = {
|
|
6045
|
+
"http:": 80,
|
|
6046
|
+
"ws:": 80,
|
|
6047
|
+
"https:": 443,
|
|
6048
|
+
"wss:": 443
|
|
6049
|
+
};
|
|
6050
|
+
function isKernelLoopbackHost(hostname) {
|
|
6051
|
+
const host = hostname.replace(/^\[(.*)\]$/, "$1").replace(/\.$/, "").toLowerCase();
|
|
6052
|
+
return host === "localhost" || host.endsWith(".localhost") || host === "0.0.0.0" || LOOPBACK_V4.test(host) || LOOPBACK_V6.has(host) || MAPPED_V4.test(host);
|
|
5859
6053
|
}
|
|
5860
|
-
function
|
|
5861
|
-
|
|
6054
|
+
function kernelLoopbackPort(href) {
|
|
6055
|
+
let url;
|
|
6056
|
+
try {
|
|
6057
|
+
url = new URL(href);
|
|
6058
|
+
} catch {
|
|
6059
|
+
return void 0;
|
|
6060
|
+
}
|
|
6061
|
+
const fallback = SCHEMES[url.protocol];
|
|
6062
|
+
if (fallback === void 0 || !isKernelLoopbackHost(url.hostname)) return void 0;
|
|
6063
|
+
const named = KERNEL_PORT.exec(url.hostname.replace(/\.$/, "").toLowerCase())?.[1];
|
|
6064
|
+
const port = Number(named ?? (url.port || fallback));
|
|
6065
|
+
return port >= 1 && port <= 65535 ? port : void 0;
|
|
5862
6066
|
}
|
|
5863
|
-
|
|
5864
|
-
|
|
6067
|
+
var DEFAULT_HOSTNAME = "slicc";
|
|
6068
|
+
var HOSTNAME_ADDRESS = "127.0.1.1";
|
|
6069
|
+
var LIBC_HOSTNAMES = ["emscripten", "wasmer.sh"];
|
|
6070
|
+
var LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
|
|
6071
|
+
function reserved(name2) {
|
|
6072
|
+
const lower = name2.toLowerCase();
|
|
6073
|
+
return lower === "localhost" || lower.endsWith(".localhost") || lower === HOST_LOOPBACK || lower === "wasmer.sh" || /^\d+(\.\d+){3}$/.test(lower);
|
|
5865
6074
|
}
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
);
|
|
5870
|
-
return Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
6075
|
+
function isHostname(name2) {
|
|
6076
|
+
if (typeof name2 !== "string" || name2.length > 253 || reserved(name2)) return false;
|
|
6077
|
+
return name2.split(".").every((l) => LABEL.test(l));
|
|
5871
6078
|
}
|
|
5872
|
-
function
|
|
5873
|
-
|
|
5874
|
-
return {
|
|
5875
|
-
path,
|
|
5876
|
-
bytes,
|
|
5877
|
-
uid,
|
|
5878
|
-
gid,
|
|
5879
|
-
mode,
|
|
5880
|
-
ino: st.ino,
|
|
5881
|
-
mtimeMs: st.mtime.getTime(),
|
|
5882
|
-
sha256: digest,
|
|
5883
|
-
users: o.users,
|
|
5884
|
-
onRootFs: true,
|
|
5885
|
-
parents
|
|
5886
|
-
};
|
|
6079
|
+
function ownNames(hostname) {
|
|
6080
|
+
return [.../* @__PURE__ */ new Set([hostname.toLowerCase(), ...LIBC_HOSTNAMES])];
|
|
5887
6081
|
}
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
6082
|
+
|
|
6083
|
+
// src/kernel/net/proxy-service.ts
|
|
6084
|
+
var SEND_BUFFER2 = 1024 * 1024;
|
|
6085
|
+
var TLS_HANDSHAKE = 22;
|
|
6086
|
+
var TOKEN2 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
6087
|
+
var NO_WEBSOCKET2 = "WebSocket not supported by this transport (fetchTransport needs { webSocket: true })";
|
|
6088
|
+
var REALM_PROXY_PORT = 3128;
|
|
6089
|
+
var DEFAULT_LIMITS = {
|
|
6090
|
+
maxConnections: 64,
|
|
6091
|
+
maxHead: 64 * 1024,
|
|
6092
|
+
idleMs: 12e4,
|
|
6093
|
+
bodyBudget: 128 * 1024 * 1024,
|
|
6094
|
+
closeWaitMs: 5e3
|
|
6095
|
+
};
|
|
6096
|
+
var HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
6097
|
+
"connection",
|
|
6098
|
+
"keep-alive",
|
|
6099
|
+
"proxy-connection",
|
|
6100
|
+
"proxy-authenticate",
|
|
6101
|
+
"proxy-authorization",
|
|
6102
|
+
"te",
|
|
6103
|
+
"trailer",
|
|
6104
|
+
"transfer-encoding",
|
|
6105
|
+
"upgrade"
|
|
6106
|
+
]);
|
|
6107
|
+
var REQUEST_OWN = /* @__PURE__ */ new Set(["host", "content-length", "expect"]);
|
|
6108
|
+
var Budget = class {
|
|
6109
|
+
free;
|
|
6110
|
+
waiters = [];
|
|
6111
|
+
constructor(free) {
|
|
6112
|
+
this.free = free;
|
|
6113
|
+
}
|
|
6114
|
+
async acquire(n, signal) {
|
|
6115
|
+
while (this.free < n) {
|
|
6116
|
+
signal.throwIfAborted();
|
|
6117
|
+
await new Promise((resolve2) => {
|
|
6118
|
+
const done4 = () => {
|
|
6119
|
+
signal.removeEventListener("abort", done4);
|
|
6120
|
+
resolve2();
|
|
6121
|
+
};
|
|
6122
|
+
this.waiters.push(done4);
|
|
6123
|
+
signal.addEventListener("abort", done4, { once: true });
|
|
6124
|
+
});
|
|
5897
6125
|
}
|
|
5898
|
-
|
|
6126
|
+
this.free -= n;
|
|
5899
6127
|
}
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
6128
|
+
release(n) {
|
|
6129
|
+
this.free += n;
|
|
6130
|
+
const waiters = this.waiters;
|
|
6131
|
+
this.waiters = [];
|
|
6132
|
+
for (const wake of waiters) wake();
|
|
5905
6133
|
}
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
for (let tries = 0; ; tries++) {
|
|
5914
|
-
const before = await fs.lstat(path);
|
|
5915
|
-
if (before.isSymbolicLink) refuse("EACCES", path, "is a symlink");
|
|
5916
|
-
if (!before.isFile)
|
|
5917
|
-
refuse(before.isDirectory ? "EISDIR" : "EINVAL", path, "is not a regular file");
|
|
5918
|
-
if (before.size > ATTEST_MAX) refuse("EFBIG", path, `is over ${ATTEST_MAX} bytes`);
|
|
5919
|
-
const bytes = (await fs.readFileBuffer(path)).slice();
|
|
5920
|
-
const after = await fs.lstat(path);
|
|
5921
|
-
if (unchanged(before, after, bytes)) {
|
|
5922
|
-
return attested(path, after, bytes, await sha2562(bytes), o, parents);
|
|
5923
|
-
}
|
|
5924
|
-
if (tries >= 1) refuse("EAGAIN", path, "changed while it was read");
|
|
6134
|
+
};
|
|
6135
|
+
function canonicalHost2(host) {
|
|
6136
|
+
const bare = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
6137
|
+
try {
|
|
6138
|
+
return new URL(`http://${bare}/`).hostname;
|
|
6139
|
+
} catch {
|
|
6140
|
+
return void 0;
|
|
5925
6141
|
}
|
|
5926
6142
|
}
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
6143
|
+
function localV4(a, b) {
|
|
6144
|
+
return a === 127 || a === 0 || a === 169 && b === 254;
|
|
6145
|
+
}
|
|
6146
|
+
function hextets(address) {
|
|
6147
|
+
const [head, tail, extra] = address.split("::");
|
|
6148
|
+
if (extra !== void 0) return void 0;
|
|
6149
|
+
const part = (text) => text ? text.split(":").map((h) => Number.parseInt(h, 16)) : [];
|
|
6150
|
+
const front = part(head);
|
|
6151
|
+
const back = part(tail);
|
|
6152
|
+
const groups = tail === void 0 ? front : [...front, ...new Array(8 - front.length - back.length).fill(0), ...back];
|
|
6153
|
+
return groups.length === 8 && groups.every((g) => g >= 0 && g <= 65535) ? groups : void 0;
|
|
6154
|
+
}
|
|
6155
|
+
function localV6(address) {
|
|
6156
|
+
const g = hextets(address);
|
|
6157
|
+
if (!g) return true;
|
|
6158
|
+
if ((g[0] & 65472) === 65152) return true;
|
|
6159
|
+
const zeroPrefix = g.slice(0, 5).every((x) => x === 0);
|
|
6160
|
+
if (!zeroPrefix) return false;
|
|
6161
|
+
if (g[5] === 65535 || g[5] === 0 && g[6] !== 0) return localV4(g[6] >> 8, g[6] & 255);
|
|
6162
|
+
return g[5] === 0 && g[6] === 0;
|
|
6163
|
+
}
|
|
6164
|
+
function isLoopbackHost(hostname) {
|
|
6165
|
+
const canonical = canonicalHost2(hostname);
|
|
6166
|
+
if (canonical === void 0) return false;
|
|
6167
|
+
const host = canonical.replace(/\.+$/, "");
|
|
6168
|
+
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
6169
|
+
if (host.startsWith("[")) return localV6(host.slice(1, -1));
|
|
6170
|
+
const v4 = /^(\d+)\.(\d+)\.\d+\.\d+$/.exec(host);
|
|
6171
|
+
return v4 ? localV4(Number(v4[1]), Number(v4[2])) : false;
|
|
6172
|
+
}
|
|
6173
|
+
function dropped(headers) {
|
|
6174
|
+
return /* @__PURE__ */ new Set([...HOP_BY_HOP, ...fieldTokens(headers, "connection")]);
|
|
6175
|
+
}
|
|
6176
|
+
function forwardRequestHeaders(headers) {
|
|
6177
|
+
const drop = dropped(headers);
|
|
6178
|
+
return headers.filter(([name2]) => {
|
|
6179
|
+
const lower = name2.toLowerCase();
|
|
6180
|
+
return !drop.has(lower) && !REQUEST_OWN.has(lower);
|
|
6181
|
+
});
|
|
6182
|
+
}
|
|
6183
|
+
function forwardResponseHeaders(headers, opts) {
|
|
6184
|
+
const drop = dropped(headers);
|
|
6185
|
+
if (!opts.encodedBodies) drop.add("content-encoding");
|
|
6186
|
+
if (!(opts.encodedBodies && opts.bodiless)) drop.add("content-length");
|
|
6187
|
+
return headers.filter(([name2]) => !drop.has(name2.toLowerCase()));
|
|
6188
|
+
}
|
|
6189
|
+
function keepsAlive(req) {
|
|
6190
|
+
const tokens = fieldTokens(req.headers, "connection");
|
|
6191
|
+
if (tokens.includes("close")) return false;
|
|
6192
|
+
return req.minor >= 1 || tokens.includes("keep-alive");
|
|
6193
|
+
}
|
|
6194
|
+
function tunnelRequestUrl(req, origin) {
|
|
6195
|
+
const base = new URL(origin);
|
|
6196
|
+
let url;
|
|
6197
|
+
try {
|
|
6198
|
+
url = new URL(req.target, req.target.startsWith("/") ? base : void 0);
|
|
6199
|
+
} catch {
|
|
6200
|
+
throw new HttpError(400, "malformed request target");
|
|
6201
|
+
}
|
|
6202
|
+
const hosts = fieldValues(req.headers, "host");
|
|
6203
|
+
const hostOk = hosts.every(
|
|
6204
|
+
(h) => URL.canParse(`https://${h}`) && new URL(`https://${h}`).host === base.host
|
|
6205
|
+
);
|
|
6206
|
+
if (url.origin !== base.origin || !hostOk) {
|
|
6207
|
+
throw new HttpError(421, `this tunnel is for ${base.host}`);
|
|
6208
|
+
}
|
|
6209
|
+
return url.href;
|
|
6210
|
+
}
|
|
6211
|
+
function requestUrl(req) {
|
|
6212
|
+
if (req.target.startsWith("/")) {
|
|
6213
|
+
throw new HttpError(400, "this is a proxy: send the absolute URL (GET http://host/path)");
|
|
6214
|
+
}
|
|
6215
|
+
let url;
|
|
6216
|
+
try {
|
|
6217
|
+
url = new URL(req.target);
|
|
6218
|
+
} catch {
|
|
6219
|
+
throw new HttpError(400, "malformed request target");
|
|
6220
|
+
}
|
|
6221
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
6222
|
+
throw new HttpError(400, `unsupported scheme ${url.protocol}`);
|
|
6223
|
+
}
|
|
6224
|
+
if (isLoopbackHost(url.hostname)) {
|
|
6225
|
+
throw new HttpError(
|
|
6226
|
+
403,
|
|
6227
|
+
`${url.hostname} is the realm's own loopback, which the proxy does not reach: list it in no_proxy`
|
|
6228
|
+
);
|
|
6229
|
+
}
|
|
6230
|
+
return url.href;
|
|
6231
|
+
}
|
|
6232
|
+
function tunnelTarget(req) {
|
|
6233
|
+
const match = /^([A-Za-z0-9.-]+|\[[0-9A-Fa-f:.]+\]):(\d{1,5})$/.exec(req.target);
|
|
6234
|
+
const port = Number(match?.[2]);
|
|
6235
|
+
if (!match || port < 1 || port > 65535) {
|
|
6236
|
+
throw new HttpError(400, "CONNECT needs host:port");
|
|
6237
|
+
}
|
|
6238
|
+
const host = canonicalHost2(match[1]);
|
|
6239
|
+
if (host === void 0) throw new HttpError(400, "CONNECT needs host:port");
|
|
6240
|
+
if (isLoopbackHost(host)) {
|
|
6241
|
+
throw new HttpError(
|
|
6242
|
+
403,
|
|
6243
|
+
`${host} is the realm's own loopback, which the proxy does not reach: list it in no_proxy`
|
|
6244
|
+
);
|
|
6245
|
+
}
|
|
6246
|
+
return { host, port };
|
|
6247
|
+
}
|
|
6248
|
+
async function watchHangup(conn, abort, until) {
|
|
6249
|
+
while (!until.aborted && !abort.signal.aborted) {
|
|
6250
|
+
if (conn.poll().hangup) {
|
|
6251
|
+
abort.abort(new KernelError("EPIPE"));
|
|
6252
|
+
return;
|
|
6253
|
+
}
|
|
6254
|
+
try {
|
|
6255
|
+
await conn.changed(until);
|
|
6256
|
+
} catch {
|
|
6257
|
+
return;
|
|
6258
|
+
}
|
|
6259
|
+
}
|
|
6260
|
+
}
|
|
6261
|
+
function untilAborted(response, signal) {
|
|
6262
|
+
return new Promise((resolve2, reject) => {
|
|
6263
|
+
const onAbort = () => {
|
|
6264
|
+
reject(signal.reason ?? new KernelError("EINTR"));
|
|
6265
|
+
response.then((late) => late.cancel()).catch(() => void 0);
|
|
6266
|
+
};
|
|
6267
|
+
if (signal.aborted) return onAbort();
|
|
6268
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
6269
|
+
response.then(resolve2, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
6270
|
+
});
|
|
6271
|
+
}
|
|
6272
|
+
function plainResponse(status, message3) {
|
|
6273
|
+
const body = latin1Bytes(`slicc realm proxy: ${message3}
|
|
6274
|
+
`);
|
|
6275
|
+
return {
|
|
6276
|
+
status,
|
|
6277
|
+
statusText: REASON[status] ?? "",
|
|
6278
|
+
headers: [["Content-Type", "text/plain; charset=utf-8"]],
|
|
6279
|
+
body: (async function* () {
|
|
6280
|
+
yield body;
|
|
6281
|
+
})(),
|
|
6282
|
+
cancel: async () => void 0
|
|
6283
|
+
};
|
|
6284
|
+
}
|
|
6285
|
+
function timedSource(conn, idle, stop) {
|
|
6286
|
+
return {
|
|
6287
|
+
read: (max, signal) => {
|
|
6288
|
+
const own = signal ? [stop, signal] : [stop];
|
|
6289
|
+
if (Number.isFinite(idle.ms)) own.push(AbortSignal.timeout(idle.ms));
|
|
6290
|
+
return conn.read(max, AbortSignal.any(own));
|
|
6291
|
+
}
|
|
6292
|
+
};
|
|
6293
|
+
}
|
|
6294
|
+
function webSocketTarget(req) {
|
|
6295
|
+
return { ...req, target: req.target.replace(/^ws(s?):/i, (_, s) => `http${s.toLowerCase()}:`) };
|
|
6296
|
+
}
|
|
6297
|
+
function wireCode(code) {
|
|
6298
|
+
if (code === 1005) return 1e3;
|
|
6299
|
+
return sendableCode(code) ? code : 1011;
|
|
6300
|
+
}
|
|
6301
|
+
function quietly(ms) {
|
|
6302
|
+
return new Promise((resolve2) => {
|
|
6303
|
+
AbortSignal.timeout(ms).addEventListener("abort", () => resolve2(), { once: true });
|
|
6304
|
+
});
|
|
6305
|
+
}
|
|
6306
|
+
var RealmProxy = class {
|
|
6307
|
+
options;
|
|
6308
|
+
listener;
|
|
6309
|
+
stop = new AbortController();
|
|
6310
|
+
limits;
|
|
6311
|
+
slots;
|
|
6312
|
+
bodies;
|
|
6313
|
+
connections = /* @__PURE__ */ new Set();
|
|
6314
|
+
warnedWebSocket = false;
|
|
6315
|
+
closed;
|
|
6316
|
+
port;
|
|
6317
|
+
constructor(options) {
|
|
6318
|
+
this.options = options;
|
|
6319
|
+
this.limits = { ...DEFAULT_LIMITS, ...options.limits };
|
|
6320
|
+
this.slots = new Budget(this.limits.maxConnections);
|
|
6321
|
+
this.bodies = new Budget(this.limits.bodyBudget);
|
|
6322
|
+
this.listener = options.net.listen({
|
|
6323
|
+
family: "inet",
|
|
6324
|
+
host: options.host ?? "127.0.0.1",
|
|
6325
|
+
port: options.port ?? REALM_PROXY_PORT
|
|
6326
|
+
});
|
|
6327
|
+
this.port = this.listener.local?.family === "inet" ? this.listener.local.port : 0;
|
|
6328
|
+
this.closed = this.acceptLoop();
|
|
6329
|
+
}
|
|
6330
|
+
get stopped() {
|
|
6331
|
+
return this.stop.signal.aborted;
|
|
6332
|
+
}
|
|
6333
|
+
close() {
|
|
6334
|
+
if (this.stop.signal.aborted) return;
|
|
6335
|
+
this.stop.abort();
|
|
6336
|
+
this.listener.close();
|
|
6337
|
+
for (const conn of this.connections) conn.close();
|
|
6338
|
+
}
|
|
6339
|
+
async acceptLoop() {
|
|
6340
|
+
const served = /* @__PURE__ */ new Set();
|
|
6341
|
+
try {
|
|
6342
|
+
for (; ; ) {
|
|
6343
|
+
await this.slots.acquire(1, this.stop.signal);
|
|
6344
|
+
let conn;
|
|
6345
|
+
try {
|
|
6346
|
+
conn = await this.listener.accept(this.stop.signal);
|
|
6347
|
+
} catch {
|
|
6348
|
+
this.slots.release(1);
|
|
6349
|
+
break;
|
|
6350
|
+
}
|
|
6351
|
+
const done4 = this.serve(conn).finally(() => {
|
|
6352
|
+
this.slots.release(1);
|
|
6353
|
+
served.delete(done4);
|
|
6354
|
+
});
|
|
6355
|
+
served.add(done4);
|
|
6356
|
+
}
|
|
6357
|
+
} catch {
|
|
6358
|
+
}
|
|
6359
|
+
await Promise.allSettled([...served]);
|
|
6360
|
+
}
|
|
6361
|
+
async serve(conn) {
|
|
6362
|
+
this.connections.add(conn);
|
|
6363
|
+
const idle = { ms: this.limits.idleMs };
|
|
6364
|
+
const incoming = new Incoming(timedSource(conn, idle, this.stop.signal));
|
|
6365
|
+
try {
|
|
6366
|
+
await this.requests({ sink: conn, incoming, conn, socket: conn, idle });
|
|
6367
|
+
} finally {
|
|
6368
|
+
this.connections.delete(conn);
|
|
6369
|
+
conn.close();
|
|
6370
|
+
}
|
|
6371
|
+
}
|
|
6372
|
+
async requests(ctx) {
|
|
6373
|
+
try {
|
|
6374
|
+
for (; ; ) {
|
|
6375
|
+
const head = await ctx.incoming.head(this.limits.maxHead);
|
|
6376
|
+
if (!head || !await this.exchange(ctx, head)) break;
|
|
6377
|
+
}
|
|
6378
|
+
} catch (e) {
|
|
6379
|
+
if (e instanceof HttpError) await this.refuse(ctx.sink, e);
|
|
6380
|
+
}
|
|
6381
|
+
}
|
|
6382
|
+
async refuse(sink, error) {
|
|
6383
|
+
try {
|
|
6384
|
+
await this.relay(sink, "GET", 1, plainResponse(error.status, error.message), false);
|
|
6385
|
+
} catch {
|
|
6386
|
+
}
|
|
6387
|
+
}
|
|
6388
|
+
async exchange(ctx, head) {
|
|
6389
|
+
const { sink: conn, incoming } = ctx;
|
|
6390
|
+
const req = parseRequestHead(head);
|
|
6391
|
+
if (req.method === "CONNECT") {
|
|
6392
|
+
if (!ctx.conn) throw new HttpError(400, "CONNECT inside a tunnel");
|
|
6393
|
+
await this.connect(ctx.conn, incoming, req, ctx.idle);
|
|
6394
|
+
return false;
|
|
6395
|
+
}
|
|
6396
|
+
if (req.method === "GET" && isUpgrade(req.headers)) {
|
|
6397
|
+
await this.websocket(ctx, req);
|
|
6398
|
+
return false;
|
|
6399
|
+
}
|
|
6400
|
+
const url = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(req);
|
|
6401
|
+
const keep2 = keepsAlive(req);
|
|
6402
|
+
const framing = requestFraming(req.headers);
|
|
6403
|
+
const reserve = framing.kind === "length" ? framing.length : framing.kind === "chunked" ? this.cap() : 0;
|
|
6404
|
+
if (reserve > this.cap()) throw new HttpError(413, `request body over ${this.cap()} bytes`);
|
|
6405
|
+
await this.expectContinue(conn, req, framing.kind !== "none");
|
|
6406
|
+
await this.bodies.acquire(reserve, this.stop.signal);
|
|
6407
|
+
let response;
|
|
6408
|
+
const abort = new AbortController();
|
|
6409
|
+
const stopExchange = () => abort.abort();
|
|
6410
|
+
this.stop.signal.addEventListener("abort", stopExchange, { once: true });
|
|
6411
|
+
try {
|
|
6412
|
+
try {
|
|
6413
|
+
const body = await readBody(incoming, framing, this.cap());
|
|
6414
|
+
const waiting = new AbortController();
|
|
6415
|
+
void watchHangup(ctx.socket, abort, waiting.signal);
|
|
6416
|
+
try {
|
|
6417
|
+
response = await this.upstream(req, url, body, abort.signal, ctx.socket.peerCred?.pid);
|
|
6418
|
+
} finally {
|
|
6419
|
+
waiting.abort();
|
|
6420
|
+
}
|
|
6421
|
+
} finally {
|
|
6422
|
+
this.bodies.release(reserve);
|
|
6423
|
+
}
|
|
6424
|
+
return await this.relay(conn, req.method, req.minor, response, keep2);
|
|
6425
|
+
} finally {
|
|
6426
|
+
this.stop.signal.removeEventListener("abort", stopExchange);
|
|
6427
|
+
abort.abort();
|
|
6428
|
+
}
|
|
6429
|
+
}
|
|
6430
|
+
cap() {
|
|
6431
|
+
return Math.min(this.options.transport.traits.maxRequestBody, this.limits.bodyBudget);
|
|
6432
|
+
}
|
|
6433
|
+
async expectContinue(conn, req, hasBody) {
|
|
6434
|
+
const expect = fieldTokens(req.headers, "expect");
|
|
6435
|
+
if (expect.length === 0) return;
|
|
6436
|
+
if (expect.length !== 1 || expect[0] !== "100-continue") {
|
|
6437
|
+
throw new HttpError(417, `unsupported expectation: ${expect.join(", ")}`);
|
|
6438
|
+
}
|
|
6439
|
+
if (hasBody && req.minor >= 1) {
|
|
6440
|
+
await conn.write(latin1Bytes("HTTP/1.1 100 Continue\r\n\r\n"), this.stop.signal);
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6443
|
+
async upstream(req, url, body, signal, owner) {
|
|
6444
|
+
try {
|
|
6445
|
+
const fetching = this.options.transport.fetch(
|
|
6446
|
+
{
|
|
6447
|
+
url: toHostLoopback(url),
|
|
6448
|
+
method: req.method.toUpperCase(),
|
|
6449
|
+
headers: forwardRequestHeaders(req.headers),
|
|
6450
|
+
body: req.method === "GET" || req.method === "HEAD" ? void 0 : body,
|
|
6451
|
+
signal
|
|
6452
|
+
},
|
|
6453
|
+
owner
|
|
6454
|
+
);
|
|
6455
|
+
return await untilAborted(fetching, signal);
|
|
6456
|
+
} catch (e) {
|
|
6457
|
+
if (signal.aborted) throw e;
|
|
6458
|
+
const message3 = e instanceof Error ? e.message : String(e);
|
|
6459
|
+
const status = e?.status;
|
|
6460
|
+
const own = typeof status === "number" && status >= 400 && status <= 599 ? status : 502;
|
|
6461
|
+
return plainResponse(own, message3 || "upstream request failed");
|
|
6462
|
+
}
|
|
6463
|
+
}
|
|
6464
|
+
async relay(conn, method, minor, response, keepAlive) {
|
|
6465
|
+
const status = response.status;
|
|
6466
|
+
const bodiless2 = method === "HEAD" || status === 204 || status === 304 || status < 200;
|
|
6467
|
+
const headers = forwardResponseHeaders(response.headers, {
|
|
6468
|
+
encodedBodies: this.options.transport.traits.encodedBodies,
|
|
6469
|
+
bodiless: bodiless2
|
|
6470
|
+
});
|
|
6471
|
+
const chunked3 = !bodiless2 && minor >= 1;
|
|
6472
|
+
const keep2 = keepAlive && (bodiless2 || chunked3);
|
|
6473
|
+
headers.push(["Connection", keep2 ? "keep-alive" : "close"]);
|
|
6474
|
+
if (chunked3) headers.push(["Transfer-Encoding", "chunked"]);
|
|
6475
|
+
const signal = this.stop.signal;
|
|
6476
|
+
await conn.write(
|
|
6477
|
+
responseHead(status, response.statusText || REASON[status] || "", headers),
|
|
6478
|
+
signal
|
|
6479
|
+
);
|
|
6480
|
+
if (bodiless2) {
|
|
6481
|
+
await response.cancel();
|
|
6482
|
+
return keep2;
|
|
6483
|
+
}
|
|
6484
|
+
try {
|
|
6485
|
+
for await (const piece of response.body) {
|
|
6486
|
+
if (piece.length > 0) await conn.write(chunked3 ? chunk(piece) : piece, signal);
|
|
6487
|
+
}
|
|
6488
|
+
} catch {
|
|
6489
|
+
await response.cancel().catch(() => void 0);
|
|
6490
|
+
return false;
|
|
6491
|
+
}
|
|
6492
|
+
if (chunked3) await conn.write(LAST_CHUNK, signal);
|
|
6493
|
+
return keep2;
|
|
6494
|
+
}
|
|
6495
|
+
async connect(conn, incoming, req, idle) {
|
|
6496
|
+
const target = tunnelTarget(req);
|
|
6497
|
+
const refused2 = target.port === 443 ? await this.tunnelReady(target) : void 0;
|
|
6498
|
+
if (refused2) throw refused2;
|
|
6499
|
+
await conn.write(latin1Bytes("HTTP/1.1 200 Connection Established\r\n\r\n"), this.stop.signal);
|
|
6500
|
+
try {
|
|
6501
|
+
const first = await incoming.peek(this.stop.signal);
|
|
6502
|
+
if (first === void 0) return;
|
|
6503
|
+
if (first !== TLS_HANDSHAKE) {
|
|
6504
|
+
const origin = `http://${target.port === 80 ? target.host : `${target.host}:${target.port}`}`;
|
|
6505
|
+
await this.requests({ sink: conn, incoming, origin, socket: conn, idle });
|
|
6506
|
+
return;
|
|
6507
|
+
}
|
|
6508
|
+
const tunnel = this.options.tunnel;
|
|
6509
|
+
if (!tunnel || target.port !== 443 && await this.tunnelReady(target)) return;
|
|
6510
|
+
await tunnel(
|
|
6511
|
+
conn,
|
|
6512
|
+
incoming,
|
|
6513
|
+
target,
|
|
6514
|
+
this.stop.signal,
|
|
6515
|
+
(source, sink, origin) => this.requests({ sink, incoming: new Incoming(source), origin, socket: conn, idle })
|
|
6516
|
+
);
|
|
6517
|
+
} catch {
|
|
6518
|
+
}
|
|
6519
|
+
}
|
|
6520
|
+
async tunnelReady(target) {
|
|
6521
|
+
const name2 = `CONNECT ${target.host}:${target.port}`;
|
|
6522
|
+
if (!this.options.tunnel) return new HttpError(501, `${name2}: no tunnels through this proxy`);
|
|
6523
|
+
try {
|
|
6524
|
+
await this.options.tunnelReady?.();
|
|
6525
|
+
return void 0;
|
|
6526
|
+
} catch (e) {
|
|
6527
|
+
return new HttpError(501, `${name2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
6528
|
+
}
|
|
6529
|
+
}
|
|
6530
|
+
async websocket(ctx, req) {
|
|
6531
|
+
const href = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(webSocketTarget(req));
|
|
6532
|
+
const { transport } = this.options;
|
|
6533
|
+
if (!transport.traits.websocket || !transport.websocket) {
|
|
6534
|
+
if (!this.warnedWebSocket) {
|
|
6535
|
+
this.warnedWebSocket = true;
|
|
6536
|
+
console.warn(`slicc-kernel: a program asked for a WebSocket: ${NO_WEBSOCKET2}`);
|
|
6537
|
+
}
|
|
6538
|
+
throw new HttpError(501, NO_WEBSOCKET2);
|
|
6539
|
+
}
|
|
6540
|
+
const accept2 = await acceptKey(req.headers);
|
|
6541
|
+
const protocols = fieldValues(req.headers, "sec-websocket-protocol").flatMap((v) => v.split(",")).map((t) => t.trim()).filter((t) => t !== "");
|
|
6542
|
+
const hangup = new AbortController();
|
|
6543
|
+
const waiting = new AbortController();
|
|
6544
|
+
void watchHangup(ctx.socket, hangup, waiting.signal);
|
|
6545
|
+
let socket;
|
|
6546
|
+
try {
|
|
6547
|
+
socket = await transport.websocket({
|
|
6548
|
+
url: toHostLoopback(href).replace(/^http/, "ws"),
|
|
6549
|
+
protocols,
|
|
6550
|
+
headers: forwardRequestHeaders(req.headers),
|
|
6551
|
+
signal: AbortSignal.any([this.stop.signal, hangup.signal])
|
|
6552
|
+
});
|
|
6553
|
+
} catch (e) {
|
|
6554
|
+
waiting.abort();
|
|
6555
|
+
throw new HttpError(502, e instanceof Error ? e.message : String(e));
|
|
6556
|
+
}
|
|
6557
|
+
ctx.idle.ms = Number.POSITIVE_INFINITY;
|
|
6558
|
+
try {
|
|
6559
|
+
if (hangup.signal.aborted) throw hangup.signal.reason;
|
|
6560
|
+
if (socket.protocol && !(protocols.includes(socket.protocol) && TOKEN2.test(socket.protocol))) {
|
|
6561
|
+
throw new HttpError(502, "the far end chose a subprotocol the client did not offer");
|
|
6562
|
+
}
|
|
6563
|
+
await ctx.sink.write(switchingHead(accept2, socket.protocol), this.stop.signal);
|
|
6564
|
+
await this.bridge(ctx, socket, hangup.signal);
|
|
6565
|
+
} catch (e) {
|
|
6566
|
+
socket.close(1001, "the client went away");
|
|
6567
|
+
throw e;
|
|
6568
|
+
} finally {
|
|
6569
|
+
waiting.abort();
|
|
6570
|
+
}
|
|
6571
|
+
}
|
|
6572
|
+
async bridge(ctx, socket, hangup) {
|
|
6573
|
+
const stop = this.stop.signal;
|
|
6574
|
+
let chain2 = Promise.resolve();
|
|
6575
|
+
const toGuest = (bytes) => {
|
|
6576
|
+
chain2 = chain2.then(() => ctx.sink.write(bytes, stop));
|
|
6577
|
+
return chain2;
|
|
6578
|
+
};
|
|
6579
|
+
const done4 = new AbortController();
|
|
6580
|
+
const reading = AbortSignal.any([stop, done4.signal, hangup]);
|
|
6581
|
+
const stopping = () => socket.close(1001, "the proxy is stopping");
|
|
6582
|
+
stop.addEventListener("abort", stopping, { once: true });
|
|
6583
|
+
let guestDone = false;
|
|
6584
|
+
let closeSent = false;
|
|
6585
|
+
const outbound2 = (async () => {
|
|
6586
|
+
for await (const data of socket.messages) {
|
|
6587
|
+
if (guestDone) continue;
|
|
6588
|
+
const text = typeof data === "string";
|
|
6589
|
+
await toGuest(
|
|
6590
|
+
frame(text ? OP_TEXT : OP_BINARY, text ? new TextEncoder().encode(data) : data)
|
|
6591
|
+
);
|
|
6592
|
+
}
|
|
6593
|
+
const closed = await socket.closed;
|
|
6594
|
+
if (guestDone) return;
|
|
6595
|
+
closeSent = true;
|
|
6596
|
+
await toGuest(closeFrame(wireCode(closed.code), closed.reason));
|
|
6597
|
+
AbortSignal.timeout(this.limits.closeWaitMs).addEventListener("abort", () => done4.abort(), {
|
|
6598
|
+
once: true
|
|
6599
|
+
});
|
|
6600
|
+
})().catch(() => done4.abort());
|
|
6601
|
+
const reader = new WsReader(ctx.incoming, MESSAGE_LIMIT, true);
|
|
6602
|
+
try {
|
|
6603
|
+
for (; ; ) {
|
|
6604
|
+
const event = await reader.next(reading);
|
|
6605
|
+
if (!event) break;
|
|
6606
|
+
if (event.kind === "ping") {
|
|
6607
|
+
await toGuest(pongFrame(event.payload));
|
|
6608
|
+
continue;
|
|
6609
|
+
}
|
|
6610
|
+
if (event.kind === "close") {
|
|
6611
|
+
guestDone = true;
|
|
6612
|
+
if (closeSent) break;
|
|
6613
|
+
const code = wireCode(event.code);
|
|
6614
|
+
socket.close(code, event.reason);
|
|
6615
|
+
const reply = await Promise.race([
|
|
6616
|
+
socket.closed,
|
|
6617
|
+
quietly(this.limits.closeWaitMs).then(() => ({ code, reason: event.reason }))
|
|
6618
|
+
]);
|
|
6619
|
+
await toGuest(closeFrame(wireCode(reply.code), reply.reason));
|
|
6620
|
+
break;
|
|
6621
|
+
}
|
|
6622
|
+
while (socket.buffered > SEND_BUFFER2 && !reading.aborted) await quietly(10);
|
|
6623
|
+
socket.send(event.kind === "text" ? event.text : event.bytes);
|
|
6624
|
+
}
|
|
6625
|
+
} catch (e) {
|
|
6626
|
+
if (e instanceof WsError) {
|
|
6627
|
+
guestDone = true;
|
|
6628
|
+
socket.close(e.code, e.message);
|
|
6629
|
+
await toGuest(closeFrame(e.code, e.message)).catch(() => void 0);
|
|
6630
|
+
}
|
|
6631
|
+
} finally {
|
|
6632
|
+
guestDone = true;
|
|
6633
|
+
socket.close(1e3, "the client went away");
|
|
6634
|
+
stop.removeEventListener("abort", stopping);
|
|
6635
|
+
await Promise.race([outbound2, quietly(this.limits.closeWaitMs)]);
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6638
|
+
};
|
|
6639
|
+
|
|
6640
|
+
// src/facade/types.ts
|
|
6641
|
+
var FACADE_NAME = /^[a-z][a-z0-9-]*$/;
|
|
6642
|
+
function facadeEnvName(name2) {
|
|
6643
|
+
return `SLICC_${name2.toUpperCase().replaceAll("-", "_")}_URL`;
|
|
6644
|
+
}
|
|
6645
|
+
function facadeUrl(port) {
|
|
6646
|
+
return `tcp://127.0.0.1:${port}`;
|
|
6647
|
+
}
|
|
6648
|
+
|
|
6649
|
+
// src/facade/hosts.ts
|
|
6650
|
+
var TAKEN_PORTS = /* @__PURE__ */ new Set([CDP_PORT, REALM_PROXY_PORT]);
|
|
6651
|
+
var READ_CHUNK = 64 * 1024;
|
|
6652
|
+
var FACADE_MAX_PENDING = 16 * 1024 * 1024;
|
|
6653
|
+
var FACADE_FLOODED = `slicc-kernel: more than ${FACADE_MAX_PENDING} bytes are waiting for the program to read`;
|
|
6654
|
+
function declared(specs) {
|
|
6655
|
+
const out = /* @__PURE__ */ new Map();
|
|
6656
|
+
const ports = /* @__PURE__ */ new Set();
|
|
6657
|
+
for (const [name2, spec] of Object.entries(specs ?? {})) {
|
|
6658
|
+
if (!FACADE_NAME.test(name2)) throw new TypeError(`facades: not a facade name: ${name2}`);
|
|
6659
|
+
const port = spec?.port;
|
|
6660
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535 || TAKEN_PORTS.has(port)) {
|
|
6661
|
+
throw new TypeError(`facades.${name2}: not a free port: ${String(port)}`);
|
|
6662
|
+
}
|
|
6663
|
+
if (ports.has(port)) throw new TypeError(`facades.${name2}: port ${port} is taken twice`);
|
|
6664
|
+
if (spec.hook !== void 0 && typeof spec.hook !== "function") {
|
|
6665
|
+
throw new TypeError(`facades.${name2}: hook must be a function`);
|
|
6666
|
+
}
|
|
6667
|
+
ports.add(port);
|
|
6668
|
+
out.set(name2, { port, embedder: spec.hook, clients: [] });
|
|
6669
|
+
}
|
|
6670
|
+
return out;
|
|
6671
|
+
}
|
|
6672
|
+
var FacadeHosts = class {
|
|
6673
|
+
facades;
|
|
6674
|
+
constructor(specs) {
|
|
6675
|
+
this.facades = declared(specs);
|
|
6676
|
+
}
|
|
6677
|
+
env() {
|
|
6678
|
+
const env = {};
|
|
6679
|
+
for (const [name2, { port }] of this.facades) env[facadeEnvName(name2)] = facadeUrl(port);
|
|
6680
|
+
return env;
|
|
6681
|
+
}
|
|
6682
|
+
enable(net) {
|
|
6683
|
+
for (const [name2, { port }] of this.facades) {
|
|
6684
|
+
net.facade(port, name2);
|
|
6685
|
+
net.activate({ family: "inet", host: "127.0.0.1", port }, () => {
|
|
6686
|
+
const listener = net.listen({ family: "inet", host: "127.0.0.1", port });
|
|
6687
|
+
void acceptAll(listener, (socket) => this.serve(name2, socket));
|
|
6688
|
+
});
|
|
6689
|
+
}
|
|
6690
|
+
}
|
|
6691
|
+
register(name2, hook) {
|
|
6692
|
+
const entry = this.facades.get(name2);
|
|
6693
|
+
if (!entry || typeof hook !== "function") throw new KernelError("EINVAL");
|
|
6694
|
+
entry.clients.push(hook);
|
|
6695
|
+
return () => {
|
|
6696
|
+
const at = entry.clients.indexOf(hook);
|
|
6697
|
+
if (at >= 0) entry.clients.splice(at, 1);
|
|
6698
|
+
};
|
|
6699
|
+
}
|
|
6700
|
+
async serve(name2, socket) {
|
|
6701
|
+
const entry = this.facades.get(name2);
|
|
6702
|
+
const hook = entry.clients.at(-1) ?? entry.embedder;
|
|
6703
|
+
const stamp = socket.peerStamp;
|
|
6704
|
+
if (!hook || !stamp) return socket.close();
|
|
6705
|
+
let conn;
|
|
6706
|
+
try {
|
|
6707
|
+
conn = await hook(structuredClone(stamp), { name: name2 });
|
|
6708
|
+
} catch {
|
|
6709
|
+
return socket.close();
|
|
6710
|
+
}
|
|
6711
|
+
await relay(socket, conn);
|
|
6712
|
+
}
|
|
6713
|
+
};
|
|
6714
|
+
async function acceptAll(listener, serve) {
|
|
6715
|
+
for (; ; ) {
|
|
6716
|
+
const socket = await listener.accept();
|
|
6717
|
+
void serve(socket);
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
async function relay(socket, conn) {
|
|
6721
|
+
let ended = false;
|
|
6722
|
+
let pending = 0;
|
|
6723
|
+
let writes = Promise.resolve();
|
|
6724
|
+
const end = () => {
|
|
6725
|
+
if (ended) return;
|
|
6726
|
+
ended = true;
|
|
6727
|
+
socket.close();
|
|
6728
|
+
};
|
|
6729
|
+
conn.onmessage = (bytes) => {
|
|
6730
|
+
if (ended) return;
|
|
6731
|
+
const chunk2 = bytes.slice();
|
|
6732
|
+
pending += chunk2.length;
|
|
6733
|
+
if (pending > FACADE_MAX_PENDING) {
|
|
6734
|
+
end();
|
|
6735
|
+
conn.close(FACADE_FLOODED);
|
|
6736
|
+
return;
|
|
6737
|
+
}
|
|
6738
|
+
writes = writes.then(async () => {
|
|
6739
|
+
if (ended) return;
|
|
6740
|
+
let at = 0;
|
|
6741
|
+
while (at < chunk2.length) at += await socket.write(chunk2.subarray(at));
|
|
6742
|
+
pending -= chunk2.length;
|
|
6743
|
+
});
|
|
6744
|
+
writes.catch(() => {
|
|
6745
|
+
end();
|
|
6746
|
+
conn.close();
|
|
6747
|
+
});
|
|
6748
|
+
};
|
|
6749
|
+
conn.onclose = () => {
|
|
6750
|
+
writes.then(end, end);
|
|
6751
|
+
};
|
|
6752
|
+
try {
|
|
6753
|
+
for (; ; ) {
|
|
6754
|
+
const bytes = await socket.read(READ_CHUNK);
|
|
6755
|
+
if (bytes.length === 0 || ended) break;
|
|
6756
|
+
conn.send(bytes);
|
|
6757
|
+
}
|
|
6758
|
+
} catch {
|
|
6759
|
+
}
|
|
6760
|
+
if (!ended) {
|
|
6761
|
+
end();
|
|
6762
|
+
conn.close();
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
6765
|
+
|
|
6766
|
+
// src/fs/attest.ts
|
|
6767
|
+
var ATTEST_MAX = 1024 * 1024;
|
|
6768
|
+
var UNOWNED = 1e3;
|
|
6769
|
+
function refuse(code, path, why) {
|
|
6770
|
+
throw Object.assign(new Error(`attest ${path}: ${why}`), { code });
|
|
6771
|
+
}
|
|
6772
|
+
function chainOf(path) {
|
|
6773
|
+
const parts2 = path.split("/").filter(Boolean);
|
|
6774
|
+
return ["/", ...parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`)];
|
|
6775
|
+
}
|
|
6776
|
+
function under2(path, target) {
|
|
6777
|
+
return path === target || path.startsWith(`${target}/`);
|
|
6778
|
+
}
|
|
6779
|
+
function owned(path, st) {
|
|
6780
|
+
return { path, uid: st.uid ?? UNOWNED, gid: st.gid ?? UNOWNED, mode: st.mode };
|
|
6781
|
+
}
|
|
6782
|
+
function unchanged(a, b, bytes) {
|
|
6783
|
+
return a.ino === b.ino && a.mtime.getTime() === b.mtime.getTime() && a.size === b.size && bytes.length === b.size;
|
|
6784
|
+
}
|
|
6785
|
+
async function sha2562(bytes) {
|
|
6786
|
+
const digest = new Uint8Array(
|
|
6787
|
+
await crypto.subtle.digest("SHA-256", bytes)
|
|
6788
|
+
);
|
|
6789
|
+
return Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
6790
|
+
}
|
|
6791
|
+
function attested(path, st, bytes, digest, o, parents) {
|
|
6792
|
+
const { uid, gid, mode } = owned(path, st);
|
|
6793
|
+
return {
|
|
6794
|
+
path,
|
|
6795
|
+
bytes,
|
|
6796
|
+
uid,
|
|
6797
|
+
gid,
|
|
6798
|
+
mode,
|
|
6799
|
+
ino: st.ino,
|
|
6800
|
+
mtimeMs: st.mtime.getTime(),
|
|
6801
|
+
sha256: digest,
|
|
6802
|
+
users: o.users,
|
|
6803
|
+
onRootFs: true,
|
|
6804
|
+
parents
|
|
6805
|
+
};
|
|
6806
|
+
}
|
|
6807
|
+
async function trustedParents(fs, path, o) {
|
|
6808
|
+
const parents = [];
|
|
6809
|
+
for (const dir of chainOf(path).slice(0, -1)) {
|
|
6810
|
+
const st = await fs.lstat(dir);
|
|
6811
|
+
if (st.isSymbolicLink) refuse("EACCES", path, `${dir} is a symlink`);
|
|
6812
|
+
if (!st.isDirectory) refuse("ENOTDIR", path, `${dir} is not a directory`);
|
|
6813
|
+
const perm = owned(dir, st);
|
|
6814
|
+
if (o.users !== "off" && (perm.uid !== 0 || (perm.mode & 18) !== 0)) {
|
|
6815
|
+
refuse("EACCES", path, `${dir} is not root-owned or is writable by group or others`);
|
|
6816
|
+
}
|
|
6817
|
+
parents.push(perm);
|
|
6818
|
+
}
|
|
6819
|
+
return parents;
|
|
6820
|
+
}
|
|
6821
|
+
async function attest(fs, asked, o) {
|
|
6822
|
+
if (typeof asked !== "string" || !asked.startsWith("/")) {
|
|
6823
|
+
refuse("EINVAL", String(asked), "not an absolute path");
|
|
6824
|
+
}
|
|
6825
|
+
const path = normalizePath(asked);
|
|
6826
|
+
const mount = o.mounts.find((target) => {
|
|
6827
|
+
const at = normalizePath(target);
|
|
6828
|
+
return at === "/" || under2(path, at);
|
|
6829
|
+
});
|
|
6830
|
+
if (mount !== void 0) refuse("EACCES", path, `under the mount ${mount}`);
|
|
6831
|
+
const parents = await trustedParents(fs, path, o);
|
|
6832
|
+
for (let tries = 0; ; tries++) {
|
|
6833
|
+
const before = await fs.lstat(path);
|
|
6834
|
+
if (before.isSymbolicLink) refuse("EACCES", path, "is a symlink");
|
|
6835
|
+
if (!before.isFile)
|
|
6836
|
+
refuse(before.isDirectory ? "EISDIR" : "EINVAL", path, "is not a regular file");
|
|
6837
|
+
if (before.size > ATTEST_MAX) refuse("EFBIG", path, `is over ${ATTEST_MAX} bytes`);
|
|
6838
|
+
const bytes = (await fs.readFileBuffer(path)).slice();
|
|
6839
|
+
const after = await fs.lstat(path);
|
|
6840
|
+
if (unchanged(before, after, bytes)) {
|
|
6841
|
+
return attested(path, after, bytes, await sha2562(bytes), o, parents);
|
|
6842
|
+
}
|
|
6843
|
+
if (tries >= 1) refuse("EAGAIN", path, "changed while it was read");
|
|
6844
|
+
}
|
|
6845
|
+
}
|
|
6846
|
+
var WATCH_DEBOUNCE_MS = 50;
|
|
6847
|
+
var FileWatches = class {
|
|
6848
|
+
live = /* @__PURE__ */ new Set();
|
|
6849
|
+
local;
|
|
6850
|
+
remote;
|
|
6851
|
+
constructor(local, remote) {
|
|
6852
|
+
this.local = local;
|
|
6853
|
+
this.remote = remote;
|
|
5935
6854
|
}
|
|
5936
6855
|
announce() {
|
|
5937
6856
|
return this.remote.announceWatch?.([...new Set([...this.live].flat())]) ?? Promise.resolve();
|
|
@@ -8868,58 +9787,31 @@ var ProcessClocks = class {
|
|
|
8868
9787
|
}
|
|
8869
9788
|
freeze() {
|
|
8870
9789
|
for (const c of this.all()) c.freeze();
|
|
8871
|
-
}
|
|
8872
|
-
times() {
|
|
8873
|
-
if (!this.enabled) return void 0;
|
|
8874
|
-
const sum = { ...this.ended };
|
|
8875
|
-
for (const c of this.all()) {
|
|
8876
|
-
const t = c.times();
|
|
8877
|
-
sum.utime += t.utime;
|
|
8878
|
-
sum.stime += t.stime;
|
|
8879
|
-
}
|
|
8880
|
-
return sum;
|
|
8881
|
-
}
|
|
8882
|
-
running() {
|
|
8883
|
-
return this.enabled ? this.all().filter((c) => c.running()).length : 0;
|
|
8884
|
-
}
|
|
8885
|
-
timed(clock, dispatch2) {
|
|
8886
|
-
const c = clock ?? this.main;
|
|
8887
|
-
return (req) => {
|
|
8888
|
-
c.enter(req.op);
|
|
8889
|
-
return dispatch2(req).finally(() => c.leave());
|
|
8890
|
-
};
|
|
8891
|
-
}
|
|
8892
|
-
all() {
|
|
8893
|
-
return [this.main, ...this.threads.values()];
|
|
8894
|
-
}
|
|
8895
|
-
};
|
|
8896
|
-
|
|
8897
|
-
// src/kernel/net/loopback-names.ts
|
|
8898
|
-
var HOST_LOOPBACK = "host.slicc.internal";
|
|
8899
|
-
var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
|
|
8900
|
-
var PAGE_LOOPBACK = "kernel.localhost";
|
|
8901
|
-
var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
|
|
8902
|
-
function toHostLoopback(href) {
|
|
8903
|
-
const url = new URL(href);
|
|
8904
|
-
if (url.hostname.replace(/\.$/, "").toLowerCase() !== HOST_LOOPBACK) return href;
|
|
8905
|
-
url.hostname = "127.0.0.1";
|
|
8906
|
-
return url.href;
|
|
8907
|
-
}
|
|
8908
|
-
var DEFAULT_HOSTNAME = "slicc";
|
|
8909
|
-
var HOSTNAME_ADDRESS = "127.0.1.1";
|
|
8910
|
-
var LIBC_HOSTNAMES = ["emscripten", "wasmer.sh"];
|
|
8911
|
-
var LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
|
|
8912
|
-
function reserved(name2) {
|
|
8913
|
-
const lower = name2.toLowerCase();
|
|
8914
|
-
return lower === "localhost" || lower.endsWith(".localhost") || lower === HOST_LOOPBACK || lower === "wasmer.sh" || /^\d+(\.\d+){3}$/.test(lower);
|
|
8915
|
-
}
|
|
8916
|
-
function isHostname(name2) {
|
|
8917
|
-
if (typeof name2 !== "string" || name2.length > 253 || reserved(name2)) return false;
|
|
8918
|
-
return name2.split(".").every((l) => LABEL.test(l));
|
|
8919
|
-
}
|
|
8920
|
-
function ownNames(hostname) {
|
|
8921
|
-
return [.../* @__PURE__ */ new Set([hostname.toLowerCase(), ...LIBC_HOSTNAMES])];
|
|
8922
|
-
}
|
|
9790
|
+
}
|
|
9791
|
+
times() {
|
|
9792
|
+
if (!this.enabled) return void 0;
|
|
9793
|
+
const sum = { ...this.ended };
|
|
9794
|
+
for (const c of this.all()) {
|
|
9795
|
+
const t = c.times();
|
|
9796
|
+
sum.utime += t.utime;
|
|
9797
|
+
sum.stime += t.stime;
|
|
9798
|
+
}
|
|
9799
|
+
return sum;
|
|
9800
|
+
}
|
|
9801
|
+
running() {
|
|
9802
|
+
return this.enabled ? this.all().filter((c) => c.running()).length : 0;
|
|
9803
|
+
}
|
|
9804
|
+
timed(clock, dispatch2) {
|
|
9805
|
+
const c = clock ?? this.main;
|
|
9806
|
+
return (req) => {
|
|
9807
|
+
c.enter(req.op);
|
|
9808
|
+
return dispatch2(req).finally(() => c.leave());
|
|
9809
|
+
};
|
|
9810
|
+
}
|
|
9811
|
+
all() {
|
|
9812
|
+
return [this.main, ...this.threads.values()];
|
|
9813
|
+
}
|
|
9814
|
+
};
|
|
8923
9815
|
|
|
8924
9816
|
// src/fs/pages.ts
|
|
8925
9817
|
var PAGE = 64 * 1024;
|
|
@@ -9172,7 +10064,7 @@ function mountTable(mounts = []) {
|
|
|
9172
10064
|
}
|
|
9173
10065
|
|
|
9174
10066
|
// src/version.ts
|
|
9175
|
-
var KERNEL_VERSION = true ? "1.
|
|
10067
|
+
var KERNEL_VERSION = true ? "1.91.0" : "0.0.0-dev";
|
|
9176
10068
|
var OS_RELEASE = "6.0.0-slicc";
|
|
9177
10069
|
function procVersion() {
|
|
9178
10070
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -9427,893 +10319,669 @@ var ProcTree = class {
|
|
|
9427
10319
|
}
|
|
9428
10320
|
if (leaf === void 0 && top === "mounts") {
|
|
9429
10321
|
return { kind: "file", content: () => mountTable(this.source.mountList()) };
|
|
9430
|
-
}
|
|
9431
|
-
if (leaf === void 0 && SYSTEM_NAMES.includes(top)) {
|
|
9432
|
-
return {
|
|
9433
|
-
kind: "file",
|
|
9434
|
-
content: () => systemFile(top, this.listing(), Date.now(), this.memory)
|
|
9435
|
-
};
|
|
9436
|
-
}
|
|
9437
|
-
if (!/^[1-9]\d*$/.test(top) || rest2.length > 0 || !this.alive(Number(top))) return void 0;
|
|
9438
|
-
if (leaf === void 0) return { kind: "dir", names: () => [...PID_NAMES] };
|
|
9439
|
-
if (!PID_NAMES.includes(leaf)) return void 0;
|
|
9440
|
-
return {
|
|
9441
|
-
kind: "file",
|
|
9442
|
-
content: () => {
|
|
9443
|
-
const listing = this.listing();
|
|
9444
|
-
const info = listing.processes.find((p) => p.pid === Number(top));
|
|
9445
|
-
if (!info) throw syncError("ENOENT", `/proc/${top}/${leaf}`);
|
|
9446
|
-
return pidFile(leaf, info, listing.boot);
|
|
9447
|
-
}
|
|
9448
|
-
};
|
|
9449
|
-
}
|
|
9450
|
-
parts(path, follow2) {
|
|
9451
|
-
const parts2 = path.split("/").filter((p) => p !== "").slice(1);
|
|
9452
|
-
if (parts2[0] === "self" && (follow2 || parts2.length > 1)) parts2[0] = String(this.pid);
|
|
9453
|
-
return parts2;
|
|
9454
|
-
}
|
|
9455
|
-
find(path, follow2) {
|
|
9456
|
-
const node = this.node(this.parts(path, follow2));
|
|
9457
|
-
if (!node) throw syncError("ENOENT", path);
|
|
9458
|
-
return node;
|
|
9459
|
-
}
|
|
9460
|
-
stat(path, follow2 = true) {
|
|
9461
|
-
const node = this.find(path, follow2);
|
|
9462
|
-
const base = {
|
|
9463
|
-
isFile: false,
|
|
9464
|
-
isDirectory: false,
|
|
9465
|
-
size: 0,
|
|
9466
|
-
mtimeMs: Date.now(),
|
|
9467
|
-
readonly: true
|
|
9468
|
-
};
|
|
9469
|
-
if (node.kind === "dir") return { ...base, isDirectory: true, mode: DIR_MODE };
|
|
9470
|
-
if (node.kind === "link") {
|
|
9471
|
-
return { ...base, isSymbolicLink: true, mode: LINK_MODE, size: node.target.length };
|
|
9472
|
-
}
|
|
9473
|
-
return { ...base, isFile: true, mode: FILE_MODE, size: this.bytes(node).length };
|
|
9474
|
-
}
|
|
9475
|
-
bytes(node) {
|
|
9476
|
-
return new TextEncoder().encode(node.kind === "file" ? node.content() : "");
|
|
9477
|
-
}
|
|
9478
|
-
read(path) {
|
|
9479
|
-
const node = this.find(path, true);
|
|
9480
|
-
if (node.kind !== "file") throw syncError("EISDIR", path);
|
|
9481
|
-
return this.bytes(node);
|
|
9482
|
-
}
|
|
9483
|
-
readdir(path) {
|
|
9484
|
-
const node = this.find(path, true);
|
|
9485
|
-
if (node.kind !== "dir") throw syncError("ENOTDIR", path);
|
|
9486
|
-
return node.names();
|
|
9487
|
-
}
|
|
9488
|
-
readlink(path) {
|
|
9489
|
-
const node = this.find(path, false);
|
|
9490
|
-
if (node.kind !== "link") throw syncError("EINVAL", path);
|
|
9491
|
-
return node.target;
|
|
9492
|
-
}
|
|
9493
|
-
exists(path) {
|
|
9494
|
-
try {
|
|
9495
|
-
this.find(path, true);
|
|
9496
|
-
return true;
|
|
9497
|
-
} catch {
|
|
9498
|
-
return false;
|
|
9499
|
-
}
|
|
9500
|
-
}
|
|
9501
|
-
};
|
|
9502
|
-
var MAX_HOPS3 = 40;
|
|
9503
|
-
function* procWalk(path, follow2) {
|
|
9504
|
-
let done4 = [];
|
|
9505
|
-
let todo = path.split("/").filter((part) => part !== "");
|
|
9506
|
-
let hops = 0;
|
|
9507
|
-
let broken;
|
|
9508
|
-
while (todo.length > 0) {
|
|
9509
|
-
const part = todo.shift();
|
|
9510
|
-
if (part === ".") continue;
|
|
9511
|
-
if (part === "..") {
|
|
9512
|
-
done4.pop();
|
|
9513
|
-
continue;
|
|
9514
|
-
}
|
|
9515
|
-
const at = `/${[...done4, part].join("/")}`;
|
|
9516
|
-
const whole = normalize(`/${[...done4, part, ...todo].join("/")}`);
|
|
9517
|
-
if (inProc(at) && inProc(whole)) return broken ?? whole;
|
|
9518
|
-
if (!follow2 && todo.length === 0) return void 0;
|
|
9519
|
-
const target = yield { at, check: todo.find((next2) => next2 !== ".") === ".." };
|
|
9520
|
-
if (typeof target === "string") {
|
|
9521
|
-
if (++hops > MAX_HOPS3) return void 0;
|
|
9522
|
-
[done4, todo] = throughLink(target, done4, todo);
|
|
9523
|
-
} else {
|
|
9524
|
-
broken ??= refusalOf(target);
|
|
9525
|
-
done4.push(part);
|
|
9526
|
-
}
|
|
9527
|
-
}
|
|
9528
|
-
return void 0;
|
|
9529
|
-
}
|
|
9530
|
-
function failed(err) {
|
|
9531
|
-
const code = err?.code;
|
|
9532
|
-
return { refused: typeof code === "string" ? code : "ENOENT" };
|
|
9533
|
-
}
|
|
9534
|
-
function refusalOf(found) {
|
|
9535
|
-
if (found === true) return void 0;
|
|
9536
|
-
return found === false ? { refused: "ENOTDIR" } : found;
|
|
9537
|
-
}
|
|
9538
|
-
function throughLink(target, done4, todo) {
|
|
9539
|
-
const parts2 = target.split("/").filter((part) => part !== "");
|
|
9540
|
-
return [target.startsWith("/") ? [] : done4, [...parts2, ...todo]];
|
|
9541
|
-
}
|
|
9542
|
-
async function procAlias(fs, path, follow2) {
|
|
9543
|
-
const { readlink, lstat } = fs;
|
|
9544
|
-
if (!readlink || !lstat) return void 0;
|
|
9545
|
-
const physical = await lstat.call(fs, "/proc").then(
|
|
9546
|
-
() => true,
|
|
9547
|
-
() => false
|
|
9548
|
-
);
|
|
9549
|
-
if (!physical) return void 0;
|
|
9550
|
-
const walk2 = procWalk(path, follow2);
|
|
9551
|
-
let step = walk2.next();
|
|
9552
|
-
while (!step.done) {
|
|
9553
|
-
const { at, check } = step.value;
|
|
9554
|
-
const link2 = await readlink.call(fs, at).catch(() => void 0);
|
|
9555
|
-
const isDir = (st) => st.isDirectory === true;
|
|
9556
|
-
const target = link2 ?? (check ? await lstat.call(fs, at).then(isDir, failed) : true);
|
|
9557
|
-
step = walk2.next(target);
|
|
9558
|
-
}
|
|
9559
|
-
return step.value;
|
|
9560
|
-
}
|
|
9561
|
-
|
|
9562
|
-
// src/kernel/host-ops.ts
|
|
9563
|
-
var HOST_OPS = [
|
|
9564
|
-
"lock",
|
|
9565
|
-
"unlock",
|
|
9566
|
-
"async-submit",
|
|
9567
|
-
"async-wait",
|
|
9568
|
-
"async-take",
|
|
9569
|
-
"async-resolve",
|
|
9570
|
-
"async-hold",
|
|
9571
|
-
"async-cancel",
|
|
9572
|
-
"async-settle",
|
|
9573
|
-
"async-close"
|
|
9574
|
-
];
|
|
9575
|
-
var json3 = (value) => ({ ok: true, kind: "json", json: value });
|
|
9576
|
-
var done2 = { ok: true, kind: "void" };
|
|
9577
|
-
var LockTable = class {
|
|
9578
|
-
held = /* @__PURE__ */ new Map();
|
|
9579
|
-
lock(pid, path, exclusive, fd) {
|
|
9580
|
-
const holders = this.held.get(path) ?? /* @__PURE__ */ new Map();
|
|
9581
|
-
for (const [other, h] of holders) if (other !== pid && (exclusive || h.exclusive)) return false;
|
|
9582
|
-
const mine = holders.get(pid) ?? { exclusive, fds: /* @__PURE__ */ new Set() };
|
|
9583
|
-
mine.exclusive = exclusive;
|
|
9584
|
-
mine.fds.add(fd);
|
|
9585
|
-
holders.set(pid, mine);
|
|
9586
|
-
this.held.set(path, holders);
|
|
9587
|
-
return true;
|
|
9588
|
-
}
|
|
9589
|
-
unlock(pid, path) {
|
|
9590
|
-
const holders = this.held.get(path);
|
|
9591
|
-
holders?.delete(pid);
|
|
9592
|
-
if (holders?.size === 0) this.held.delete(path);
|
|
9593
|
-
}
|
|
9594
|
-
closed(pid, fd) {
|
|
9595
|
-
for (const [path, holders] of [...this.held]) {
|
|
9596
|
-
const mine = holders.get(pid);
|
|
9597
|
-
if (mine?.fds.delete(fd) && mine.fds.size === 0) this.unlock(pid, path);
|
|
9598
|
-
}
|
|
9599
|
-
}
|
|
9600
|
-
release(pid) {
|
|
9601
|
-
for (const path of [...this.held.keys()]) this.unlock(pid, path);
|
|
9602
|
-
}
|
|
9603
|
-
};
|
|
9604
|
-
var KEPT_OPS = /* @__PURE__ */ new Set(["fd-write"]);
|
|
9605
|
-
var unknownSettled = false;
|
|
9606
|
-
var AsyncOps = class {
|
|
9607
|
-
next = 1;
|
|
9608
|
-
results = /* @__PURE__ */ new Map();
|
|
9609
|
-
ready = [];
|
|
9610
|
-
running = /* @__PURE__ */ new Map();
|
|
9611
|
-
waiters = /* @__PURE__ */ new Set();
|
|
9612
|
-
taken = /* @__PURE__ */ new Map();
|
|
9613
|
-
kept = /* @__PURE__ */ new Set();
|
|
9614
|
-
closed = false;
|
|
9615
|
-
run;
|
|
9616
|
-
onReady;
|
|
9617
|
-
constructor(run2, onReady) {
|
|
9618
|
-
this.run = run2;
|
|
9619
|
-
this.onReady = onReady;
|
|
9620
|
-
}
|
|
9621
|
-
submit(req) {
|
|
9622
|
-
const id2 = this.hold();
|
|
9623
|
-
if (KEPT_OPS.has(req.op)) this.kept.add(id2);
|
|
9624
|
-
const running = new AbortController();
|
|
9625
|
-
this.running.set(id2, running);
|
|
9626
|
-
this.run(req, running.signal).then(
|
|
9627
|
-
(result) => this.complete(id2, result),
|
|
9628
|
-
(err) => this.complete(id2, { ok: false, errno: "EIO", message: String(err) })
|
|
9629
|
-
);
|
|
9630
|
-
return id2;
|
|
9631
|
-
}
|
|
9632
|
-
resolve(value) {
|
|
9633
|
-
const id2 = this.hold();
|
|
9634
|
-
this.complete(id2, json3(value));
|
|
9635
|
-
return id2;
|
|
9636
|
-
}
|
|
9637
|
-
hold() {
|
|
9638
|
-
const id2 = this.next++;
|
|
9639
|
-
this.results.set(id2, void 0);
|
|
9640
|
-
return id2;
|
|
9641
|
-
}
|
|
9642
|
-
take(id2) {
|
|
9643
|
-
if (!this.results.has(id2)) throw new KernelError("EINVAL");
|
|
9644
|
-
const result = this.results.get(id2);
|
|
9645
|
-
if (!result) throw new KernelError("EAGAIN");
|
|
9646
|
-
this.forget(id2);
|
|
9647
|
-
if (this.kept.delete(id2)) this.taken.set(id2, result);
|
|
9648
|
-
return result;
|
|
9649
|
-
}
|
|
9650
|
-
ack(ids) {
|
|
9651
|
-
for (const id2 of ids) this.taken.delete(id2);
|
|
9652
|
-
}
|
|
9653
|
-
async settle(ids) {
|
|
9654
|
-
const never = new AbortController().signal;
|
|
9655
|
-
while (ids.some((id2) => this.running.has(id2))) await this.sleep(void 0, never);
|
|
9656
|
-
return ids.map((id2) => {
|
|
9657
|
-
const result = this.results.get(id2) ?? this.taken.get(id2) ?? null;
|
|
9658
|
-
if (!result && !unknownSettled) {
|
|
9659
|
-
unknownSettled = true;
|
|
9660
|
-
console.warn(`async-settle: no result for op ${id2}; its write counts as made`);
|
|
10322
|
+
}
|
|
10323
|
+
if (leaf === void 0 && SYSTEM_NAMES.includes(top)) {
|
|
10324
|
+
return {
|
|
10325
|
+
kind: "file",
|
|
10326
|
+
content: () => systemFile(top, this.listing(), Date.now(), this.memory)
|
|
10327
|
+
};
|
|
10328
|
+
}
|
|
10329
|
+
if (!/^[1-9]\d*$/.test(top) || rest2.length > 0 || !this.alive(Number(top))) return void 0;
|
|
10330
|
+
if (leaf === void 0) return { kind: "dir", names: () => [...PID_NAMES] };
|
|
10331
|
+
if (!PID_NAMES.includes(leaf)) return void 0;
|
|
10332
|
+
return {
|
|
10333
|
+
kind: "file",
|
|
10334
|
+
content: () => {
|
|
10335
|
+
const listing = this.listing();
|
|
10336
|
+
const info = listing.processes.find((p) => p.pid === Number(top));
|
|
10337
|
+
if (!info) throw syncError("ENOENT", `/proc/${top}/${leaf}`);
|
|
10338
|
+
return pidFile(leaf, info, listing.boot);
|
|
9661
10339
|
}
|
|
9662
|
-
|
|
9663
|
-
});
|
|
10340
|
+
};
|
|
9664
10341
|
}
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
10342
|
+
parts(path, follow2) {
|
|
10343
|
+
const parts2 = path.split("/").filter((p) => p !== "").slice(1);
|
|
10344
|
+
if (parts2[0] === "self" && (follow2 || parts2.length > 1)) parts2[0] = String(this.pid);
|
|
10345
|
+
return parts2;
|
|
9669
10346
|
}
|
|
9670
|
-
|
|
9671
|
-
this.
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
if (at >= 0) this.ready.splice(at, 1);
|
|
10347
|
+
find(path, follow2) {
|
|
10348
|
+
const node = this.node(this.parts(path, follow2));
|
|
10349
|
+
if (!node) throw syncError("ENOENT", path);
|
|
10350
|
+
return node;
|
|
9675
10351
|
}
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
10352
|
+
stat(path, follow2 = true) {
|
|
10353
|
+
const node = this.find(path, follow2);
|
|
10354
|
+
const base = {
|
|
10355
|
+
isFile: false,
|
|
10356
|
+
isDirectory: false,
|
|
10357
|
+
size: 0,
|
|
10358
|
+
mtimeMs: Date.now(),
|
|
10359
|
+
readonly: true
|
|
10360
|
+
};
|
|
10361
|
+
if (node.kind === "dir") return { ...base, isDirectory: true, mode: DIR_MODE };
|
|
10362
|
+
if (node.kind === "link") {
|
|
10363
|
+
return { ...base, isSymbolicLink: true, mode: LINK_MODE, size: node.target.length };
|
|
9686
10364
|
}
|
|
10365
|
+
return { ...base, isFile: true, mode: FILE_MODE, size: this.bytes(node).length };
|
|
9687
10366
|
}
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
this.wake();
|
|
9691
|
-
}
|
|
9692
|
-
sleep(timeoutMs, signal) {
|
|
9693
|
-
return new Promise((resolve2, reject) => {
|
|
9694
|
-
let timer;
|
|
9695
|
-
const cleanup = () => {
|
|
9696
|
-
clearTimeout(timer);
|
|
9697
|
-
this.waiters.delete(wake);
|
|
9698
|
-
signal.removeEventListener("abort", abort);
|
|
9699
|
-
};
|
|
9700
|
-
const wake = () => {
|
|
9701
|
-
cleanup();
|
|
9702
|
-
resolve2(true);
|
|
9703
|
-
};
|
|
9704
|
-
const abort = () => {
|
|
9705
|
-
cleanup();
|
|
9706
|
-
reject(new KernelError("EINTR"));
|
|
9707
|
-
};
|
|
9708
|
-
this.waiters.add(wake);
|
|
9709
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
9710
|
-
if (timeoutMs !== void 0) {
|
|
9711
|
-
timer = setTimeout(() => {
|
|
9712
|
-
cleanup();
|
|
9713
|
-
resolve2(false);
|
|
9714
|
-
}, timeoutMs);
|
|
9715
|
-
}
|
|
9716
|
-
});
|
|
9717
|
-
}
|
|
9718
|
-
complete(id2, result) {
|
|
9719
|
-
this.running.delete(id2);
|
|
9720
|
-
if (!this.results.has(id2)) return;
|
|
9721
|
-
this.results.set(id2, result);
|
|
9722
|
-
this.ready.push(id2);
|
|
9723
|
-
this.wake();
|
|
9724
|
-
this.onReady?.();
|
|
10367
|
+
bytes(node) {
|
|
10368
|
+
return new TextEncoder().encode(node.kind === "file" ? node.content() : "");
|
|
9725
10369
|
}
|
|
9726
|
-
|
|
9727
|
-
|
|
10370
|
+
read(path) {
|
|
10371
|
+
const node = this.find(path, true);
|
|
10372
|
+
if (node.kind !== "file") throw syncError("EISDIR", path);
|
|
10373
|
+
return this.bytes(node);
|
|
9728
10374
|
}
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
if (!ctx.locks) throw new KernelError("ENOSYS");
|
|
9734
|
-
if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
|
|
9735
|
-
throw new KernelError("EAGAIN");
|
|
9736
|
-
return done2;
|
|
9737
|
-
case "unlock":
|
|
9738
|
-
ctx.locks?.unlock(ctx.pid, req.path);
|
|
9739
|
-
return done2;
|
|
9740
|
-
case "async-submit":
|
|
9741
|
-
if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
|
|
9742
|
-
if (req.ack) ctx.ops.ack(req.ack);
|
|
9743
|
-
return json3(ctx.ops.submit(req.req));
|
|
9744
|
-
case "async-wait":
|
|
9745
|
-
return json3(await ctx.ops.wait(req.timeoutMs, ctx.blocking));
|
|
9746
|
-
case "async-take":
|
|
9747
|
-
return ctx.ops.take(req.id);
|
|
9748
|
-
case "async-resolve":
|
|
9749
|
-
return json3(ctx.ops.resolve(req.value));
|
|
9750
|
-
case "async-hold":
|
|
9751
|
-
return json3(ctx.ops.hold());
|
|
9752
|
-
case "async-cancel":
|
|
9753
|
-
ctx.ops.cancel(req.id);
|
|
9754
|
-
return done2;
|
|
9755
|
-
case "async-settle":
|
|
9756
|
-
return json3(await ctx.ops.settle(req.ids));
|
|
9757
|
-
case "async-close":
|
|
9758
|
-
ctx.ops.close();
|
|
9759
|
-
return done2;
|
|
10375
|
+
readdir(path) {
|
|
10376
|
+
const node = this.find(path, true);
|
|
10377
|
+
if (node.kind !== "dir") throw syncError("ENOTDIR", path);
|
|
10378
|
+
return node.names();
|
|
9760
10379
|
}
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
var TLS_HANDSHAKE = 22;
|
|
9766
|
-
var TOKEN2 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
9767
|
-
var NO_WEBSOCKET2 = "WebSocket not supported by this transport (fetchTransport needs { webSocket: true })";
|
|
9768
|
-
var REALM_PROXY_PORT = 3128;
|
|
9769
|
-
var DEFAULT_LIMITS = {
|
|
9770
|
-
maxConnections: 64,
|
|
9771
|
-
maxHead: 64 * 1024,
|
|
9772
|
-
idleMs: 12e4,
|
|
9773
|
-
bodyBudget: 128 * 1024 * 1024,
|
|
9774
|
-
closeWaitMs: 5e3
|
|
9775
|
-
};
|
|
9776
|
-
var HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
9777
|
-
"connection",
|
|
9778
|
-
"keep-alive",
|
|
9779
|
-
"proxy-connection",
|
|
9780
|
-
"proxy-authenticate",
|
|
9781
|
-
"proxy-authorization",
|
|
9782
|
-
"te",
|
|
9783
|
-
"trailer",
|
|
9784
|
-
"transfer-encoding",
|
|
9785
|
-
"upgrade"
|
|
9786
|
-
]);
|
|
9787
|
-
var REQUEST_OWN = /* @__PURE__ */ new Set(["host", "content-length", "expect"]);
|
|
9788
|
-
var Budget = class {
|
|
9789
|
-
free;
|
|
9790
|
-
waiters = [];
|
|
9791
|
-
constructor(free) {
|
|
9792
|
-
this.free = free;
|
|
10380
|
+
readlink(path) {
|
|
10381
|
+
const node = this.find(path, false);
|
|
10382
|
+
if (node.kind !== "link") throw syncError("EINVAL", path);
|
|
10383
|
+
return node.target;
|
|
9793
10384
|
}
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
resolve2();
|
|
9801
|
-
};
|
|
9802
|
-
this.waiters.push(done4);
|
|
9803
|
-
signal.addEventListener("abort", done4, { once: true });
|
|
9804
|
-
});
|
|
10385
|
+
exists(path) {
|
|
10386
|
+
try {
|
|
10387
|
+
this.find(path, true);
|
|
10388
|
+
return true;
|
|
10389
|
+
} catch {
|
|
10390
|
+
return false;
|
|
9805
10391
|
}
|
|
9806
|
-
this.free -= n;
|
|
9807
|
-
}
|
|
9808
|
-
release(n) {
|
|
9809
|
-
this.free += n;
|
|
9810
|
-
const waiters = this.waiters;
|
|
9811
|
-
this.waiters = [];
|
|
9812
|
-
for (const wake of waiters) wake();
|
|
9813
10392
|
}
|
|
9814
10393
|
};
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
return g[5] === 0 && g[6] === 0;
|
|
9843
|
-
}
|
|
9844
|
-
function isLoopbackHost(hostname) {
|
|
9845
|
-
const canonical = canonicalHost2(hostname);
|
|
9846
|
-
if (canonical === void 0) return false;
|
|
9847
|
-
const host = canonical.replace(/\.+$/, "");
|
|
9848
|
-
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
9849
|
-
if (host.startsWith("[")) return localV6(host.slice(1, -1));
|
|
9850
|
-
const v4 = /^(\d+)\.(\d+)\.\d+\.\d+$/.exec(host);
|
|
9851
|
-
return v4 ? localV4(Number(v4[1]), Number(v4[2])) : false;
|
|
9852
|
-
}
|
|
9853
|
-
function dropped(headers) {
|
|
9854
|
-
return /* @__PURE__ */ new Set([...HOP_BY_HOP, ...fieldTokens(headers, "connection")]);
|
|
10394
|
+
var MAX_HOPS3 = 40;
|
|
10395
|
+
function* procWalk(path, follow2) {
|
|
10396
|
+
let done4 = [];
|
|
10397
|
+
let todo = path.split("/").filter((part) => part !== "");
|
|
10398
|
+
let hops = 0;
|
|
10399
|
+
let broken;
|
|
10400
|
+
while (todo.length > 0) {
|
|
10401
|
+
const part = todo.shift();
|
|
10402
|
+
if (part === ".") continue;
|
|
10403
|
+
if (part === "..") {
|
|
10404
|
+
done4.pop();
|
|
10405
|
+
continue;
|
|
10406
|
+
}
|
|
10407
|
+
const at = `/${[...done4, part].join("/")}`;
|
|
10408
|
+
const whole = normalize(`/${[...done4, part, ...todo].join("/")}`);
|
|
10409
|
+
if (inProc(at) && inProc(whole)) return broken ?? whole;
|
|
10410
|
+
if (!follow2 && todo.length === 0) return void 0;
|
|
10411
|
+
const target = yield { at, check: todo.find((next2) => next2 !== ".") === ".." };
|
|
10412
|
+
if (typeof target === "string") {
|
|
10413
|
+
if (++hops > MAX_HOPS3) return void 0;
|
|
10414
|
+
[done4, todo] = throughLink(target, done4, todo);
|
|
10415
|
+
} else {
|
|
10416
|
+
broken ??= refusalOf(target);
|
|
10417
|
+
done4.push(part);
|
|
10418
|
+
}
|
|
10419
|
+
}
|
|
10420
|
+
return void 0;
|
|
9855
10421
|
}
|
|
9856
|
-
function
|
|
9857
|
-
const
|
|
9858
|
-
return
|
|
9859
|
-
const lower = name2.toLowerCase();
|
|
9860
|
-
return !drop.has(lower) && !REQUEST_OWN.has(lower);
|
|
9861
|
-
});
|
|
10422
|
+
function failed(err) {
|
|
10423
|
+
const code = err?.code;
|
|
10424
|
+
return { refused: typeof code === "string" ? code : "ENOENT" };
|
|
9862
10425
|
}
|
|
9863
|
-
function
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
if (!(opts.encodedBodies && opts.bodiless)) drop.add("content-length");
|
|
9867
|
-
return headers.filter(([name2]) => !drop.has(name2.toLowerCase()));
|
|
10426
|
+
function refusalOf(found) {
|
|
10427
|
+
if (found === true) return void 0;
|
|
10428
|
+
return found === false ? { refused: "ENOTDIR" } : found;
|
|
9868
10429
|
}
|
|
9869
|
-
function
|
|
9870
|
-
const
|
|
9871
|
-
|
|
9872
|
-
return req.minor >= 1 || tokens.includes("keep-alive");
|
|
10430
|
+
function throughLink(target, done4, todo) {
|
|
10431
|
+
const parts2 = target.split("/").filter((part) => part !== "");
|
|
10432
|
+
return [target.startsWith("/") ? [] : done4, [...parts2, ...todo]];
|
|
9873
10433
|
}
|
|
9874
|
-
function
|
|
9875
|
-
const
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
throw new HttpError(400, "malformed request target");
|
|
9881
|
-
}
|
|
9882
|
-
const hosts = fieldValues(req.headers, "host");
|
|
9883
|
-
const hostOk = hosts.every(
|
|
9884
|
-
(h) => URL.canParse(`https://${h}`) && new URL(`https://${h}`).host === base.host
|
|
10434
|
+
async function procAlias(fs, path, follow2) {
|
|
10435
|
+
const { readlink, lstat } = fs;
|
|
10436
|
+
if (!readlink || !lstat) return void 0;
|
|
10437
|
+
const physical = await lstat.call(fs, "/proc").then(
|
|
10438
|
+
() => true,
|
|
10439
|
+
() => false
|
|
9885
10440
|
);
|
|
9886
|
-
if (
|
|
9887
|
-
|
|
10441
|
+
if (!physical) return void 0;
|
|
10442
|
+
const walk2 = procWalk(path, follow2);
|
|
10443
|
+
let step = walk2.next();
|
|
10444
|
+
while (!step.done) {
|
|
10445
|
+
const { at, check } = step.value;
|
|
10446
|
+
const link2 = await readlink.call(fs, at).catch(() => void 0);
|
|
10447
|
+
const isDir = (st) => st.isDirectory === true;
|
|
10448
|
+
const target = link2 ?? (check ? await lstat.call(fs, at).then(isDir, failed) : true);
|
|
10449
|
+
step = walk2.next(target);
|
|
9888
10450
|
}
|
|
9889
|
-
return
|
|
10451
|
+
return step.value;
|
|
9890
10452
|
}
|
|
9891
|
-
|
|
9892
|
-
|
|
9893
|
-
|
|
10453
|
+
|
|
10454
|
+
// src/kernel/host-ops.ts
|
|
10455
|
+
var HOST_OPS = [
|
|
10456
|
+
"lock",
|
|
10457
|
+
"unlock",
|
|
10458
|
+
"async-submit",
|
|
10459
|
+
"async-wait",
|
|
10460
|
+
"async-take",
|
|
10461
|
+
"async-resolve",
|
|
10462
|
+
"async-hold",
|
|
10463
|
+
"async-cancel",
|
|
10464
|
+
"async-settle",
|
|
10465
|
+
"async-close"
|
|
10466
|
+
];
|
|
10467
|
+
var json3 = (value) => ({ ok: true, kind: "json", json: value });
|
|
10468
|
+
var done2 = { ok: true, kind: "void" };
|
|
10469
|
+
var LockTable = class {
|
|
10470
|
+
held = /* @__PURE__ */ new Map();
|
|
10471
|
+
lock(pid, path, exclusive, fd) {
|
|
10472
|
+
const holders = this.held.get(path) ?? /* @__PURE__ */ new Map();
|
|
10473
|
+
for (const [other, h] of holders) if (other !== pid && (exclusive || h.exclusive)) return false;
|
|
10474
|
+
const mine = holders.get(pid) ?? { exclusive, fds: /* @__PURE__ */ new Set() };
|
|
10475
|
+
mine.exclusive = exclusive;
|
|
10476
|
+
mine.fds.add(fd);
|
|
10477
|
+
holders.set(pid, mine);
|
|
10478
|
+
this.held.set(path, holders);
|
|
10479
|
+
return true;
|
|
9894
10480
|
}
|
|
9895
|
-
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
throw new HttpError(400, "malformed request target");
|
|
10481
|
+
unlock(pid, path) {
|
|
10482
|
+
const holders = this.held.get(path);
|
|
10483
|
+
holders?.delete(pid);
|
|
10484
|
+
if (holders?.size === 0) this.held.delete(path);
|
|
9900
10485
|
}
|
|
9901
|
-
|
|
9902
|
-
|
|
10486
|
+
closed(pid, fd) {
|
|
10487
|
+
for (const [path, holders] of [...this.held]) {
|
|
10488
|
+
const mine = holders.get(pid);
|
|
10489
|
+
if (mine?.fds.delete(fd) && mine.fds.size === 0) this.unlock(pid, path);
|
|
10490
|
+
}
|
|
9903
10491
|
}
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
403,
|
|
9907
|
-
`${url.hostname} is the realm's own loopback, which the proxy does not reach: list it in no_proxy`
|
|
9908
|
-
);
|
|
10492
|
+
release(pid) {
|
|
10493
|
+
for (const path of [...this.held.keys()]) this.unlock(pid, path);
|
|
9909
10494
|
}
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
10495
|
+
};
|
|
10496
|
+
var KEPT_OPS = /* @__PURE__ */ new Set(["fd-write"]);
|
|
10497
|
+
var unknownSettled = false;
|
|
10498
|
+
var AsyncOps = class {
|
|
10499
|
+
next = 1;
|
|
10500
|
+
results = /* @__PURE__ */ new Map();
|
|
10501
|
+
ready = [];
|
|
10502
|
+
running = /* @__PURE__ */ new Map();
|
|
10503
|
+
waiters = /* @__PURE__ */ new Set();
|
|
10504
|
+
taken = /* @__PURE__ */ new Map();
|
|
10505
|
+
kept = /* @__PURE__ */ new Set();
|
|
10506
|
+
closed = false;
|
|
10507
|
+
run;
|
|
10508
|
+
onReady;
|
|
10509
|
+
constructor(run2, onReady) {
|
|
10510
|
+
this.run = run2;
|
|
10511
|
+
this.onReady = onReady;
|
|
9917
10512
|
}
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
10513
|
+
submit(req) {
|
|
10514
|
+
const id2 = this.hold();
|
|
10515
|
+
if (KEPT_OPS.has(req.op)) this.kept.add(id2);
|
|
10516
|
+
const running = new AbortController();
|
|
10517
|
+
this.running.set(id2, running);
|
|
10518
|
+
this.run(req, running.signal).then(
|
|
10519
|
+
(result) => this.complete(id2, result),
|
|
10520
|
+
(err) => this.complete(id2, { ok: false, errno: "EIO", message: String(err) })
|
|
9924
10521
|
);
|
|
10522
|
+
return id2;
|
|
9925
10523
|
}
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
if (conn.poll().hangup) {
|
|
9931
|
-
abort.abort(new KernelError("EPIPE"));
|
|
9932
|
-
return;
|
|
9933
|
-
}
|
|
9934
|
-
try {
|
|
9935
|
-
await conn.changed(until);
|
|
9936
|
-
} catch {
|
|
9937
|
-
return;
|
|
9938
|
-
}
|
|
10524
|
+
resolve(value) {
|
|
10525
|
+
const id2 = this.hold();
|
|
10526
|
+
this.complete(id2, json3(value));
|
|
10527
|
+
return id2;
|
|
9939
10528
|
}
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
reject(signal.reason ?? new KernelError("EINTR"));
|
|
9945
|
-
response.then((late) => late.cancel()).catch(() => void 0);
|
|
9946
|
-
};
|
|
9947
|
-
if (signal.aborted) return onAbort();
|
|
9948
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
9949
|
-
response.then(resolve2, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
9950
|
-
});
|
|
9951
|
-
}
|
|
9952
|
-
function plainResponse(status, message3) {
|
|
9953
|
-
const body = latin1Bytes(`slicc realm proxy: ${message3}
|
|
9954
|
-
`);
|
|
9955
|
-
return {
|
|
9956
|
-
status,
|
|
9957
|
-
statusText: REASON[status] ?? "",
|
|
9958
|
-
headers: [["Content-Type", "text/plain; charset=utf-8"]],
|
|
9959
|
-
body: (async function* () {
|
|
9960
|
-
yield body;
|
|
9961
|
-
})(),
|
|
9962
|
-
cancel: async () => void 0
|
|
9963
|
-
};
|
|
9964
|
-
}
|
|
9965
|
-
function timedSource(conn, idle, stop) {
|
|
9966
|
-
return {
|
|
9967
|
-
read: (max, signal) => {
|
|
9968
|
-
const own = signal ? [stop, signal] : [stop];
|
|
9969
|
-
if (Number.isFinite(idle.ms)) own.push(AbortSignal.timeout(idle.ms));
|
|
9970
|
-
return conn.read(max, AbortSignal.any(own));
|
|
9971
|
-
}
|
|
9972
|
-
};
|
|
9973
|
-
}
|
|
9974
|
-
function webSocketTarget(req) {
|
|
9975
|
-
return { ...req, target: req.target.replace(/^ws(s?):/i, (_, s) => `http${s.toLowerCase()}:`) };
|
|
9976
|
-
}
|
|
9977
|
-
function wireCode(code) {
|
|
9978
|
-
if (code === 1005) return 1e3;
|
|
9979
|
-
return sendableCode(code) ? code : 1011;
|
|
9980
|
-
}
|
|
9981
|
-
function quietly(ms) {
|
|
9982
|
-
return new Promise((resolve2) => {
|
|
9983
|
-
AbortSignal.timeout(ms).addEventListener("abort", () => resolve2(), { once: true });
|
|
9984
|
-
});
|
|
9985
|
-
}
|
|
9986
|
-
var RealmProxy = class {
|
|
9987
|
-
options;
|
|
9988
|
-
listener;
|
|
9989
|
-
stop = new AbortController();
|
|
9990
|
-
limits;
|
|
9991
|
-
slots;
|
|
9992
|
-
bodies;
|
|
9993
|
-
connections = /* @__PURE__ */ new Set();
|
|
9994
|
-
warnedWebSocket = false;
|
|
9995
|
-
closed;
|
|
9996
|
-
port;
|
|
9997
|
-
constructor(options) {
|
|
9998
|
-
this.options = options;
|
|
9999
|
-
this.limits = { ...DEFAULT_LIMITS, ...options.limits };
|
|
10000
|
-
this.slots = new Budget(this.limits.maxConnections);
|
|
10001
|
-
this.bodies = new Budget(this.limits.bodyBudget);
|
|
10002
|
-
this.listener = options.net.listen({
|
|
10003
|
-
family: "inet",
|
|
10004
|
-
host: options.host ?? "127.0.0.1",
|
|
10005
|
-
port: options.port ?? REALM_PROXY_PORT
|
|
10006
|
-
});
|
|
10007
|
-
this.port = this.listener.local?.family === "inet" ? this.listener.local.port : 0;
|
|
10008
|
-
this.closed = this.acceptLoop();
|
|
10529
|
+
hold() {
|
|
10530
|
+
const id2 = this.next++;
|
|
10531
|
+
this.results.set(id2, void 0);
|
|
10532
|
+
return id2;
|
|
10009
10533
|
}
|
|
10010
|
-
|
|
10011
|
-
|
|
10534
|
+
take(id2) {
|
|
10535
|
+
if (!this.results.has(id2)) throw new KernelError("EINVAL");
|
|
10536
|
+
const result = this.results.get(id2);
|
|
10537
|
+
if (!result) throw new KernelError("EAGAIN");
|
|
10538
|
+
this.forget(id2);
|
|
10539
|
+
if (this.kept.delete(id2)) this.taken.set(id2, result);
|
|
10540
|
+
return result;
|
|
10012
10541
|
}
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
this.stop.abort();
|
|
10016
|
-
this.listener.close();
|
|
10017
|
-
for (const conn of this.connections) conn.close();
|
|
10542
|
+
ack(ids) {
|
|
10543
|
+
for (const id2 of ids) this.taken.delete(id2);
|
|
10018
10544
|
}
|
|
10019
|
-
async
|
|
10020
|
-
const
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
} catch {
|
|
10028
|
-
this.slots.release(1);
|
|
10029
|
-
break;
|
|
10030
|
-
}
|
|
10031
|
-
const done4 = this.serve(conn).finally(() => {
|
|
10032
|
-
this.slots.release(1);
|
|
10033
|
-
served.delete(done4);
|
|
10034
|
-
});
|
|
10035
|
-
served.add(done4);
|
|
10545
|
+
async settle(ids) {
|
|
10546
|
+
const never = new AbortController().signal;
|
|
10547
|
+
while (ids.some((id2) => this.running.has(id2))) await this.sleep(void 0, never);
|
|
10548
|
+
return ids.map((id2) => {
|
|
10549
|
+
const result = this.results.get(id2) ?? this.taken.get(id2) ?? null;
|
|
10550
|
+
if (!result && !unknownSettled) {
|
|
10551
|
+
unknownSettled = true;
|
|
10552
|
+
console.warn(`async-settle: no result for op ${id2}; its write counts as made`);
|
|
10036
10553
|
}
|
|
10037
|
-
|
|
10038
|
-
}
|
|
10039
|
-
await Promise.allSettled([...served]);
|
|
10554
|
+
return result;
|
|
10555
|
+
});
|
|
10040
10556
|
}
|
|
10041
|
-
|
|
10042
|
-
this.
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
try {
|
|
10046
|
-
await this.requests({ sink: conn, incoming, conn, socket: conn, idle });
|
|
10047
|
-
} finally {
|
|
10048
|
-
this.connections.delete(conn);
|
|
10049
|
-
conn.close();
|
|
10050
|
-
}
|
|
10557
|
+
cancel(id2) {
|
|
10558
|
+
this.kept.delete(id2);
|
|
10559
|
+
this.running.get(id2)?.abort();
|
|
10560
|
+
this.forget(id2);
|
|
10051
10561
|
}
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
}
|
|
10058
|
-
} catch (e) {
|
|
10059
|
-
if (e instanceof HttpError) await this.refuse(ctx.sink, e);
|
|
10060
|
-
}
|
|
10562
|
+
forget(id2) {
|
|
10563
|
+
this.running.delete(id2);
|
|
10564
|
+
this.results.delete(id2);
|
|
10565
|
+
const at = this.ready.indexOf(id2);
|
|
10566
|
+
if (at >= 0) this.ready.splice(at, 1);
|
|
10061
10567
|
}
|
|
10062
|
-
async
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10568
|
+
async wait(timeoutMs, blocking) {
|
|
10569
|
+
let signal;
|
|
10570
|
+
for (; ; ) {
|
|
10571
|
+
if (this.closed) return -1;
|
|
10572
|
+
const id2 = this.ready.shift();
|
|
10573
|
+
if (id2 !== void 0) return id2;
|
|
10574
|
+
if (timeoutMs !== void 0 && timeoutMs <= 0) return 0;
|
|
10575
|
+
signal ??= blocking();
|
|
10576
|
+
const woke = await this.sleep(timeoutMs, signal);
|
|
10577
|
+
if (!woke) return 0;
|
|
10066
10578
|
}
|
|
10067
10579
|
}
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
void watchHangup(ctx.socket, abort, waiting.signal);
|
|
10096
|
-
try {
|
|
10097
|
-
response = await this.upstream(req, url, body, abort.signal, ctx.socket.peerCred?.pid);
|
|
10098
|
-
} finally {
|
|
10099
|
-
waiting.abort();
|
|
10100
|
-
}
|
|
10101
|
-
} finally {
|
|
10102
|
-
this.bodies.release(reserve);
|
|
10580
|
+
close() {
|
|
10581
|
+
this.closed = true;
|
|
10582
|
+
this.wake();
|
|
10583
|
+
}
|
|
10584
|
+
sleep(timeoutMs, signal) {
|
|
10585
|
+
return new Promise((resolve2, reject) => {
|
|
10586
|
+
let timer;
|
|
10587
|
+
const cleanup = () => {
|
|
10588
|
+
clearTimeout(timer);
|
|
10589
|
+
this.waiters.delete(wake);
|
|
10590
|
+
signal.removeEventListener("abort", abort);
|
|
10591
|
+
};
|
|
10592
|
+
const wake = () => {
|
|
10593
|
+
cleanup();
|
|
10594
|
+
resolve2(true);
|
|
10595
|
+
};
|
|
10596
|
+
const abort = () => {
|
|
10597
|
+
cleanup();
|
|
10598
|
+
reject(new KernelError("EINTR"));
|
|
10599
|
+
};
|
|
10600
|
+
this.waiters.add(wake);
|
|
10601
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
10602
|
+
if (timeoutMs !== void 0) {
|
|
10603
|
+
timer = setTimeout(() => {
|
|
10604
|
+
cleanup();
|
|
10605
|
+
resolve2(false);
|
|
10606
|
+
}, timeoutMs);
|
|
10103
10607
|
}
|
|
10104
|
-
|
|
10105
|
-
} finally {
|
|
10106
|
-
this.stop.signal.removeEventListener("abort", stopExchange);
|
|
10107
|
-
abort.abort();
|
|
10108
|
-
}
|
|
10608
|
+
});
|
|
10109
10609
|
}
|
|
10110
|
-
|
|
10111
|
-
|
|
10610
|
+
complete(id2, result) {
|
|
10611
|
+
this.running.delete(id2);
|
|
10612
|
+
if (!this.results.has(id2)) return;
|
|
10613
|
+
this.results.set(id2, result);
|
|
10614
|
+
this.ready.push(id2);
|
|
10615
|
+
this.wake();
|
|
10616
|
+
this.onReady?.();
|
|
10112
10617
|
}
|
|
10113
|
-
|
|
10114
|
-
const
|
|
10115
|
-
if (expect.length === 0) return;
|
|
10116
|
-
if (expect.length !== 1 || expect[0] !== "100-continue") {
|
|
10117
|
-
throw new HttpError(417, `unsupported expectation: ${expect.join(", ")}`);
|
|
10118
|
-
}
|
|
10119
|
-
if (hasBody && req.minor >= 1) {
|
|
10120
|
-
await conn.write(latin1Bytes("HTTP/1.1 100 Continue\r\n\r\n"), this.stop.signal);
|
|
10121
|
-
}
|
|
10618
|
+
wake() {
|
|
10619
|
+
for (const wake of [...this.waiters]) wake();
|
|
10122
10620
|
}
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
return
|
|
10142
|
-
|
|
10621
|
+
};
|
|
10622
|
+
async function hostSyscall(req, ctx) {
|
|
10623
|
+
switch (req.op) {
|
|
10624
|
+
case "lock":
|
|
10625
|
+
if (!ctx.locks) throw new KernelError("ENOSYS");
|
|
10626
|
+
if (!ctx.locks.lock(ctx.pid, req.path, req.exclusive, req.fd))
|
|
10627
|
+
throw new KernelError("EAGAIN");
|
|
10628
|
+
return done2;
|
|
10629
|
+
case "unlock":
|
|
10630
|
+
ctx.locks?.unlock(ctx.pid, req.path);
|
|
10631
|
+
return done2;
|
|
10632
|
+
case "async-submit":
|
|
10633
|
+
if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
|
|
10634
|
+
if (req.ack) ctx.ops.ack(req.ack);
|
|
10635
|
+
return json3(ctx.ops.submit(req.req));
|
|
10636
|
+
case "async-wait":
|
|
10637
|
+
return json3(await ctx.ops.wait(req.timeoutMs, ctx.blocking));
|
|
10638
|
+
case "async-take":
|
|
10639
|
+
return ctx.ops.take(req.id);
|
|
10640
|
+
case "async-resolve":
|
|
10641
|
+
return json3(ctx.ops.resolve(req.value));
|
|
10642
|
+
case "async-hold":
|
|
10643
|
+
return json3(ctx.ops.hold());
|
|
10644
|
+
case "async-cancel":
|
|
10645
|
+
ctx.ops.cancel(req.id);
|
|
10646
|
+
return done2;
|
|
10647
|
+
case "async-settle":
|
|
10648
|
+
return json3(await ctx.ops.settle(req.ids));
|
|
10649
|
+
case "async-close":
|
|
10650
|
+
ctx.ops.close();
|
|
10651
|
+
return done2;
|
|
10143
10652
|
}
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
|
|
10653
|
+
}
|
|
10654
|
+
|
|
10655
|
+
// src/kernel/net/loopback-transport.ts
|
|
10656
|
+
var LOOPBACK_TRAITS = {
|
|
10657
|
+
manualRedirects: true,
|
|
10658
|
+
encodedBodies: true,
|
|
10659
|
+
maxRequestBody: 64 * 1024 * 1024,
|
|
10660
|
+
crossOrigin: "any",
|
|
10661
|
+
websocket: true
|
|
10662
|
+
};
|
|
10663
|
+
var MAX_HEAD2 = 64 * 1024;
|
|
10664
|
+
var OWN_HEADERS = /* @__PURE__ */ new Set([
|
|
10665
|
+
"host",
|
|
10666
|
+
"connection",
|
|
10667
|
+
"content-length",
|
|
10668
|
+
"transfer-encoding",
|
|
10669
|
+
"upgrade",
|
|
10670
|
+
"sec-websocket-key",
|
|
10671
|
+
"sec-websocket-version",
|
|
10672
|
+
"sec-websocket-protocol",
|
|
10673
|
+
"sec-websocket-extensions"
|
|
10674
|
+
]);
|
|
10675
|
+
var OP_CLOSE = 8;
|
|
10676
|
+
var OP_PONG = 10;
|
|
10677
|
+
var TOKEN3 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
10678
|
+
var SAFE_VALUE = /^[^\r\n\0]*$/;
|
|
10679
|
+
var CLOSE_WAIT_MS3 = 5e3;
|
|
10680
|
+
var MAX_UNREAD = 16 * 1024 * 1024;
|
|
10681
|
+
function errno(code, message3) {
|
|
10682
|
+
return Object.assign(new Error(`${code}: ${message3}`), { code });
|
|
10683
|
+
}
|
|
10684
|
+
function requestHead(method, url, headers, extra) {
|
|
10685
|
+
if (!TOKEN3.test(method)) throw errno("EINVAL", `method ${method}`);
|
|
10686
|
+
const lines2 = [`${method} ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`];
|
|
10687
|
+
for (const [name2, value] of headers) {
|
|
10688
|
+
if (OWN_HEADERS.has(name2.toLowerCase())) continue;
|
|
10689
|
+
if (!TOKEN3.test(name2) || !SAFE_VALUE.test(value)) throw errno("EINVAL", `header ${name2}`);
|
|
10690
|
+
lines2.push(`${name2}: ${value}`);
|
|
10691
|
+
}
|
|
10692
|
+
for (const [name2, value] of extra) lines2.push(`${name2}: ${value}`);
|
|
10693
|
+
return latin1Bytes(`${lines2.join("\r\n")}\r
|
|
10694
|
+
\r
|
|
10695
|
+
`);
|
|
10696
|
+
}
|
|
10697
|
+
function parseHead(bytes) {
|
|
10698
|
+
const [statusLine = "", ...lines2] = latin1(bytes).split(/\r?\n/);
|
|
10699
|
+
const match = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
|
|
10700
|
+
if (!match) throw errno("EPROTO", `not an HTTP/1.x response: ${statusLine.slice(0, 80)}`);
|
|
10701
|
+
const headers = [];
|
|
10702
|
+
for (const line of lines2) {
|
|
10703
|
+
const colon = line.indexOf(":");
|
|
10704
|
+
if (colon > 0) headers.push([line.slice(0, colon).trim(), line.slice(colon + 1).trim()]);
|
|
10705
|
+
}
|
|
10706
|
+
return { status: Number(match[1]), statusText: match[2] ?? "", headers };
|
|
10707
|
+
}
|
|
10708
|
+
async function readHead(incoming, signal) {
|
|
10709
|
+
for (; ; ) {
|
|
10710
|
+
let raw;
|
|
10164
10711
|
try {
|
|
10165
|
-
|
|
10166
|
-
|
|
10167
|
-
|
|
10168
|
-
|
|
10169
|
-
await response.cancel().catch(() => void 0);
|
|
10170
|
-
return false;
|
|
10712
|
+
raw = await incoming.head(MAX_HEAD2, signal);
|
|
10713
|
+
} catch (err) {
|
|
10714
|
+
if (err instanceof KernelError) throw err;
|
|
10715
|
+
throw errno("EPROTO", "the response head is malformed or too large");
|
|
10171
10716
|
}
|
|
10172
|
-
if (
|
|
10173
|
-
|
|
10717
|
+
if (!raw) throw errno("ECONNRESET", "the connection closed before a response");
|
|
10718
|
+
const head = parseHead(raw);
|
|
10719
|
+
if (head.status < 100 || head.status >= 200 || head.status === 101) return head;
|
|
10174
10720
|
}
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
await this.requests({ sink: conn, incoming, origin, socket: conn, idle });
|
|
10186
|
-
return;
|
|
10187
|
-
}
|
|
10188
|
-
const tunnel = this.options.tunnel;
|
|
10189
|
-
if (!tunnel || target.port !== 443 && await this.tunnelReady(target)) return;
|
|
10190
|
-
await tunnel(
|
|
10191
|
-
conn,
|
|
10192
|
-
incoming,
|
|
10193
|
-
target,
|
|
10194
|
-
this.stop.signal,
|
|
10195
|
-
(source, sink, origin) => this.requests({ sink, incoming: new Incoming(source), origin, socket: conn, idle })
|
|
10196
|
-
);
|
|
10197
|
-
} catch {
|
|
10721
|
+
}
|
|
10722
|
+
async function* chunked(incoming, signal) {
|
|
10723
|
+
for (; ; ) {
|
|
10724
|
+
const line = await incoming.line(4096, signal);
|
|
10725
|
+
const size = /^([0-9A-Fa-f]{1,12})[ \t]*(;.*)?$/.exec(line);
|
|
10726
|
+
if (!size) throw errno("EPROTO", "a malformed chunk size");
|
|
10727
|
+
const n = Number.parseInt(size[1], 16);
|
|
10728
|
+
if (n === 0) {
|
|
10729
|
+
while (await incoming.line(MAX_HEAD2, signal) !== "") ;
|
|
10730
|
+
return;
|
|
10198
10731
|
}
|
|
10732
|
+
yield await incoming.exactly(n, signal);
|
|
10733
|
+
if (await incoming.line(2, signal) !== "") throw errno("EPROTO", "a malformed chunk");
|
|
10199
10734
|
}
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10735
|
+
}
|
|
10736
|
+
async function* counted(incoming, length2, signal) {
|
|
10737
|
+
for (let left = length2; left > 0; ) {
|
|
10738
|
+
const data = await incoming.some(Math.min(left, 64 * 1024), signal);
|
|
10739
|
+
if (data.length === 0) throw errno("EPROTO", "the body ended early");
|
|
10740
|
+
left -= data.length;
|
|
10741
|
+
yield data;
|
|
10742
|
+
}
|
|
10743
|
+
}
|
|
10744
|
+
async function* untilClose(incoming, signal) {
|
|
10745
|
+
for (; ; ) {
|
|
10746
|
+
const data = await incoming.some(64 * 1024, signal);
|
|
10747
|
+
if (data.length === 0) return;
|
|
10748
|
+
yield data;
|
|
10749
|
+
}
|
|
10750
|
+
}
|
|
10751
|
+
function bodyOf(incoming, head, method, signal) {
|
|
10752
|
+
if (method === "HEAD" || head.status === 204 || head.status === 304) return [];
|
|
10753
|
+
if (fieldTokens(head.headers, "transfer-encoding").includes("chunked")) {
|
|
10754
|
+
return chunked(incoming, signal);
|
|
10755
|
+
}
|
|
10756
|
+
const lengths = fieldValues(head.headers, "content-length");
|
|
10757
|
+
if (lengths.length === 0) return untilClose(incoming, signal);
|
|
10758
|
+
const length2 = lengths[0]?.trim() ?? "";
|
|
10759
|
+
if (!/^\d{1,15}$/.test(length2) || lengths.some((l) => l.trim() !== length2)) {
|
|
10760
|
+
throw errno("EPROTO", `a malformed Content-Length: ${lengths.join(", ")}`);
|
|
10761
|
+
}
|
|
10762
|
+
return counted(incoming, Number(length2), signal);
|
|
10763
|
+
}
|
|
10764
|
+
var KernelLoopback = class {
|
|
10765
|
+
net;
|
|
10766
|
+
cred;
|
|
10767
|
+
constructor(net, cred) {
|
|
10768
|
+
this.net = net;
|
|
10769
|
+
this.cred = cred;
|
|
10770
|
+
}
|
|
10771
|
+
port(url) {
|
|
10772
|
+
return kernelLoopbackPort(url);
|
|
10773
|
+
}
|
|
10774
|
+
connect(url, port, signal) {
|
|
10775
|
+
if (url.protocol === "https:" || url.protocol === "wss:") {
|
|
10776
|
+
throw errno("ENOTSUP", `TLS to the kernel's loopback is not supported: ${url.origin}`);
|
|
10777
|
+
}
|
|
10778
|
+
signal.throwIfAborted();
|
|
10779
|
+
const socket = this.net.socket("inet");
|
|
10203
10780
|
try {
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10781
|
+
void socket.connect({ family: "inet", host: "127.0.0.1", port }, false, this.cred());
|
|
10782
|
+
} catch (err) {
|
|
10783
|
+
socket.close();
|
|
10784
|
+
const refused2 = err instanceof KernelError && err.code === "ECONNREFUSED";
|
|
10785
|
+
throw refused2 ? errno("ECONNREFUSED", `nothing listens on the kernel's port ${port}`) : err;
|
|
10208
10786
|
}
|
|
10787
|
+
signal.addEventListener("abort", () => socket.close(), { once: true });
|
|
10788
|
+
return socket;
|
|
10209
10789
|
}
|
|
10210
|
-
async
|
|
10211
|
-
const
|
|
10212
|
-
const
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
console.warn(`slicc-kernel: a program asked for a WebSocket: ${NO_WEBSOCKET2}`);
|
|
10217
|
-
}
|
|
10218
|
-
throw new HttpError(501, NO_WEBSOCKET2);
|
|
10219
|
-
}
|
|
10220
|
-
const accept2 = await acceptKey(req.headers);
|
|
10221
|
-
const protocols = fieldValues(req.headers, "sec-websocket-protocol").flatMap((v) => v.split(",")).map((t) => t.trim()).filter((t) => t !== "");
|
|
10222
|
-
const hangup = new AbortController();
|
|
10223
|
-
const waiting = new AbortController();
|
|
10224
|
-
void watchHangup(ctx.socket, hangup, waiting.signal);
|
|
10225
|
-
let socket;
|
|
10790
|
+
async fetch(req, port) {
|
|
10791
|
+
const url = new URL(req.url);
|
|
10792
|
+
const body = req.body ?? (/^(POST|PUT|PATCH)$/i.test(req.method) ? new Uint8Array(0) : void 0);
|
|
10793
|
+
const length2 = body ? [["Content-Length", String(body.length)]] : [];
|
|
10794
|
+
const head = requestHead(req.method, url, req.headers, [...length2, ["Connection", "close"]]);
|
|
10795
|
+
const socket = this.connect(url, port, req.signal);
|
|
10226
10796
|
try {
|
|
10227
|
-
socket
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10797
|
+
await writeAll2(socket, body ? concat([head, body]) : head, req.signal);
|
|
10798
|
+
const incoming = new Incoming(socket);
|
|
10799
|
+
const response = await readHead(incoming, req.signal);
|
|
10800
|
+
const chunks2 = bodyOf(incoming, response, req.method, req.signal);
|
|
10801
|
+
return {
|
|
10802
|
+
url: req.url,
|
|
10803
|
+
status: response.status,
|
|
10804
|
+
statusText: response.statusText,
|
|
10805
|
+
headers: response.headers,
|
|
10806
|
+
body: closing(chunks2, socket),
|
|
10807
|
+
cancel: async () => socket.close()
|
|
10808
|
+
};
|
|
10809
|
+
} catch (err) {
|
|
10810
|
+
socket.close();
|
|
10811
|
+
throw err;
|
|
10236
10812
|
}
|
|
10237
|
-
|
|
10813
|
+
}
|
|
10814
|
+
async websocket(req, port) {
|
|
10815
|
+
const url = new URL(req.url);
|
|
10816
|
+
const socket = this.connect(url, port, req.signal);
|
|
10238
10817
|
try {
|
|
10239
|
-
|
|
10240
|
-
|
|
10241
|
-
|
|
10818
|
+
const key = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))));
|
|
10819
|
+
const extra = [
|
|
10820
|
+
["Upgrade", "websocket"],
|
|
10821
|
+
["Connection", "Upgrade"],
|
|
10822
|
+
["Sec-WebSocket-Key", key],
|
|
10823
|
+
["Sec-WebSocket-Version", "13"]
|
|
10824
|
+
];
|
|
10825
|
+
if (req.protocols.length > 0)
|
|
10826
|
+
extra.push(["Sec-WebSocket-Protocol", req.protocols.join(", ")]);
|
|
10827
|
+
await writeAll2(socket, requestHead("GET", url, req.headers, extra), req.signal);
|
|
10828
|
+
const incoming = new Incoming(socket);
|
|
10829
|
+
const head = await readHead(incoming, req.signal);
|
|
10830
|
+
if (head.status !== 101) {
|
|
10831
|
+
throw errno(
|
|
10832
|
+
"EPROTO",
|
|
10833
|
+
`the server answered ${head.status} ${head.statusText}, not an upgrade`
|
|
10834
|
+
);
|
|
10242
10835
|
}
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10836
|
+
const upgraded = fieldTokens(head.headers, "upgrade").includes("websocket") && fieldTokens(head.headers, "connection").includes("upgrade");
|
|
10837
|
+
const accept2 = fieldValues(head.headers, "sec-websocket-accept")[0];
|
|
10838
|
+
if (!upgraded || accept2 !== await acceptFor(key)) {
|
|
10839
|
+
throw errno("EPROTO", "the server did not accept the WebSocket key");
|
|
10840
|
+
}
|
|
10841
|
+
const protocol = fieldValues(head.headers, "sec-websocket-protocol")[0] ?? "";
|
|
10842
|
+
return new LoopbackWebSocket(socket, incoming, protocol);
|
|
10843
|
+
} catch (err) {
|
|
10844
|
+
socket.close();
|
|
10845
|
+
throw err;
|
|
10250
10846
|
}
|
|
10251
10847
|
}
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10848
|
+
};
|
|
10849
|
+
async function writeAll2(socket, bytes, signal) {
|
|
10850
|
+
for (let at = 0; at < bytes.length; ) at += await socket.write(bytes.subarray(at), signal);
|
|
10851
|
+
}
|
|
10852
|
+
async function* closing(chunks2, socket) {
|
|
10853
|
+
try {
|
|
10854
|
+
yield* chunks2;
|
|
10855
|
+
} catch (err) {
|
|
10856
|
+
if (typeof err?.code === "string") throw err;
|
|
10857
|
+
throw errno("EPROTO", err instanceof Error ? err.message : String(err));
|
|
10858
|
+
} finally {
|
|
10859
|
+
socket.close();
|
|
10860
|
+
}
|
|
10861
|
+
}
|
|
10862
|
+
var LoopbackWebSocket = class {
|
|
10863
|
+
protocol;
|
|
10864
|
+
messages;
|
|
10865
|
+
closed;
|
|
10866
|
+
pending = 0;
|
|
10867
|
+
writes = Promise.resolve();
|
|
10868
|
+
sentClose = false;
|
|
10869
|
+
stop = new AbortController();
|
|
10870
|
+
socket;
|
|
10871
|
+
settle;
|
|
10872
|
+
unread = 0;
|
|
10873
|
+
room;
|
|
10874
|
+
constructor(socket, incoming, protocol) {
|
|
10875
|
+
this.socket = socket;
|
|
10876
|
+
this.protocol = protocol;
|
|
10877
|
+
this.closed = new Promise((resolve2) => {
|
|
10878
|
+
this.settle = resolve2;
|
|
10879
|
+
});
|
|
10880
|
+
this.stop.signal.addEventListener("abort", () => this.room?.(), { once: true });
|
|
10881
|
+
const queue = [];
|
|
10882
|
+
let wake;
|
|
10883
|
+
let ended = false;
|
|
10884
|
+
void this.pump(new WsReader(incoming, void 0, true, true), (message3) => {
|
|
10885
|
+
queue.push(message3);
|
|
10886
|
+
this.unread += message3.length;
|
|
10887
|
+
wake?.();
|
|
10888
|
+
}).finally(() => {
|
|
10889
|
+
ended = true;
|
|
10890
|
+
wake?.();
|
|
10891
|
+
});
|
|
10892
|
+
const taken = (message3) => {
|
|
10893
|
+
this.unread -= message3.length;
|
|
10894
|
+
const room = this.room;
|
|
10895
|
+
this.room = void 0;
|
|
10896
|
+
room?.();
|
|
10258
10897
|
};
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10898
|
+
this.messages = {
|
|
10899
|
+
[Symbol.asyncIterator]: async function* () {
|
|
10900
|
+
for (; ; ) {
|
|
10901
|
+
const next2 = queue.shift();
|
|
10902
|
+
if (next2 !== void 0) {
|
|
10903
|
+
taken(next2);
|
|
10904
|
+
yield next2;
|
|
10905
|
+
continue;
|
|
10906
|
+
}
|
|
10907
|
+
if (ended) return;
|
|
10908
|
+
await new Promise((resolve2) => {
|
|
10909
|
+
wake = resolve2;
|
|
10910
|
+
});
|
|
10911
|
+
wake = void 0;
|
|
10912
|
+
}
|
|
10272
10913
|
}
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10914
|
+
};
|
|
10915
|
+
}
|
|
10916
|
+
async space() {
|
|
10917
|
+
while (this.unread > MAX_UNREAD && !this.stop.signal.aborted) {
|
|
10918
|
+
await new Promise((resolve2) => {
|
|
10919
|
+
this.room = resolve2;
|
|
10279
10920
|
});
|
|
10280
|
-
}
|
|
10281
|
-
|
|
10921
|
+
}
|
|
10922
|
+
}
|
|
10923
|
+
get buffered() {
|
|
10924
|
+
return this.pending;
|
|
10925
|
+
}
|
|
10926
|
+
write(bytes) {
|
|
10927
|
+
this.pending += bytes.length;
|
|
10928
|
+
const previous = this.writes;
|
|
10929
|
+
this.writes = (async () => {
|
|
10930
|
+
await previous;
|
|
10931
|
+
try {
|
|
10932
|
+
await writeAll2(this.socket, bytes, this.stop.signal);
|
|
10933
|
+
} catch {
|
|
10934
|
+
this.stop.abort();
|
|
10935
|
+
}
|
|
10936
|
+
this.pending -= bytes.length;
|
|
10937
|
+
})();
|
|
10938
|
+
}
|
|
10939
|
+
async pump(reader, deliver) {
|
|
10940
|
+
let close = { code: 1006, reason: "" };
|
|
10282
10941
|
try {
|
|
10283
10942
|
for (; ; ) {
|
|
10284
|
-
|
|
10943
|
+
await this.space();
|
|
10944
|
+
const event = await reader.next(this.stop.signal);
|
|
10285
10945
|
if (!event) break;
|
|
10286
|
-
if (event.kind === "
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
if (closeSent) break;
|
|
10293
|
-
const code = wireCode(event.code);
|
|
10294
|
-
socket.close(code, event.reason);
|
|
10295
|
-
const reply = await Promise.race([
|
|
10296
|
-
socket.closed,
|
|
10297
|
-
quietly(this.limits.closeWaitMs).then(() => ({ code, reason: event.reason }))
|
|
10298
|
-
]);
|
|
10299
|
-
await toGuest(closeFrame(wireCode(reply.code), reply.reason));
|
|
10946
|
+
if (event.kind === "text") deliver(event.text);
|
|
10947
|
+
else if (event.kind === "binary") deliver(event.bytes);
|
|
10948
|
+
else if (event.kind === "ping") this.write(clientFrame(OP_PONG, event.payload));
|
|
10949
|
+
else {
|
|
10950
|
+
close = { code: event.code, reason: event.reason };
|
|
10951
|
+
if (!this.sentClose) this.sendClose(event.code === 1005 ? 1e3 : event.code, "");
|
|
10300
10952
|
break;
|
|
10301
10953
|
}
|
|
10302
|
-
while (socket.buffered > SEND_BUFFER2 && !reading.aborted) await quietly(10);
|
|
10303
|
-
socket.send(event.kind === "text" ? event.text : event.bytes);
|
|
10304
|
-
}
|
|
10305
|
-
} catch (e) {
|
|
10306
|
-
if (e instanceof WsError) {
|
|
10307
|
-
guestDone = true;
|
|
10308
|
-
socket.close(e.code, e.message);
|
|
10309
|
-
await toGuest(closeFrame(e.code, e.message)).catch(() => void 0);
|
|
10310
10954
|
}
|
|
10955
|
+
} catch (err) {
|
|
10956
|
+
if (err instanceof WsError) this.sendClose(err.code, err.message);
|
|
10957
|
+
close = { code: err instanceof WsError ? err.code : 1006, reason: "" };
|
|
10311
10958
|
} finally {
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10959
|
+
const late = setTimeout(() => this.stop.abort(), CLOSE_WAIT_MS3);
|
|
10960
|
+
await this.writes;
|
|
10961
|
+
clearTimeout(late);
|
|
10962
|
+
this.socket.close();
|
|
10963
|
+
this.settle(close);
|
|
10964
|
+
}
|
|
10965
|
+
}
|
|
10966
|
+
sendClose(code, reason) {
|
|
10967
|
+
if (this.sentClose) return;
|
|
10968
|
+
this.sentClose = true;
|
|
10969
|
+
const late = setTimeout(() => this.stop.abort(), CLOSE_WAIT_MS3);
|
|
10970
|
+
void this.closed.then(() => clearTimeout(late));
|
|
10971
|
+
const bytes = new TextEncoder().encode(reason).subarray(0, 123);
|
|
10972
|
+
const payload = new Uint8Array(2 + bytes.length);
|
|
10973
|
+
new DataView(payload.buffer).setUint16(0, sendableCode(code) ? code : CLOSE.error);
|
|
10974
|
+
payload.set(bytes, 2);
|
|
10975
|
+
this.write(clientFrame(OP_CLOSE, payload));
|
|
10976
|
+
}
|
|
10977
|
+
send(data) {
|
|
10978
|
+
if (this.sentClose) throw errno("EPIPE", "the websocket is closing");
|
|
10979
|
+
const text = typeof data === "string";
|
|
10980
|
+
const payload = text ? new TextEncoder().encode(data) : data;
|
|
10981
|
+
this.write(clientFrame(text ? OP_TEXT : OP_BINARY, payload));
|
|
10982
|
+
}
|
|
10983
|
+
close(code = 1e3, reason = "") {
|
|
10984
|
+
this.sendClose(code, reason);
|
|
10317
10985
|
}
|
|
10318
10986
|
};
|
|
10319
10987
|
|
|
@@ -10990,7 +11658,7 @@ var WS_OPS = [
|
|
|
10990
11658
|
"net-ws-send",
|
|
10991
11659
|
"net-ws-close"
|
|
10992
11660
|
];
|
|
10993
|
-
var
|
|
11661
|
+
var TOKEN4 = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
10994
11662
|
function message(e) {
|
|
10995
11663
|
return e instanceof Error ? e.message : String(e);
|
|
10996
11664
|
}
|
|
@@ -10999,16 +11667,18 @@ function errnoOf(e) {
|
|
|
10999
11667
|
const code = e?.code;
|
|
11000
11668
|
return typeof code === "string" && ERRNO.test(code) ? code : "ECONNREFUSED";
|
|
11001
11669
|
}
|
|
11002
|
-
function fail(
|
|
11003
|
-
return { ok: false, errno, message: text };
|
|
11670
|
+
function fail(errno2, text) {
|
|
11671
|
+
return { ok: false, errno: errno2, message: text };
|
|
11004
11672
|
}
|
|
11005
11673
|
var WsHandles = class {
|
|
11006
11674
|
transport;
|
|
11007
11675
|
open = /* @__PURE__ */ new Map();
|
|
11008
11676
|
next;
|
|
11009
|
-
|
|
11677
|
+
local;
|
|
11678
|
+
constructor(transport, next2, local) {
|
|
11010
11679
|
this.transport = transport;
|
|
11011
11680
|
this.next = next2;
|
|
11681
|
+
this.local = local;
|
|
11012
11682
|
}
|
|
11013
11683
|
owns(handle) {
|
|
11014
11684
|
return this.open.has(handle);
|
|
@@ -11037,16 +11707,18 @@ var WsHandles = class {
|
|
|
11037
11707
|
return { ok: true, kind: "json", json: handle };
|
|
11038
11708
|
}
|
|
11039
11709
|
async connect(handle, req, entry) {
|
|
11040
|
-
const { transport } = this;
|
|
11041
|
-
|
|
11710
|
+
const { transport, local } = this;
|
|
11711
|
+
const port = local?.port(req.url);
|
|
11712
|
+
const open = { protocols: req.protocols, headers: req.headers, signal: entry.abort.signal };
|
|
11713
|
+
if (port === void 0 && (!transport.traits.websocket || !transport.websocket)) {
|
|
11714
|
+
return fail("ENOTSUP", NO_WEBSOCKET2);
|
|
11715
|
+
}
|
|
11042
11716
|
let socket;
|
|
11043
11717
|
try {
|
|
11044
|
-
socket = await transport.websocket({
|
|
11718
|
+
socket = port === void 0 ? await transport.websocket({
|
|
11045
11719
|
url: toHostLoopback(req.url),
|
|
11046
|
-
|
|
11047
|
-
|
|
11048
|
-
signal: entry.abort.signal
|
|
11049
|
-
});
|
|
11720
|
+
...open
|
|
11721
|
+
}) : await local.websocket({ url: req.url, ...open }, port);
|
|
11050
11722
|
} catch (e) {
|
|
11051
11723
|
return fail(errnoOf(e), message(e));
|
|
11052
11724
|
}
|
|
@@ -11054,7 +11726,7 @@ var WsHandles = class {
|
|
|
11054
11726
|
socket.close(1001, "the program went away");
|
|
11055
11727
|
return fail("EBADF", `no websocket ${handle}`);
|
|
11056
11728
|
}
|
|
11057
|
-
if (socket.protocol && !(req.protocols.includes(socket.protocol) &&
|
|
11729
|
+
if (socket.protocol && !(req.protocols.includes(socket.protocol) && TOKEN4.test(socket.protocol))) {
|
|
11058
11730
|
socket.close(1002, "subprotocol not offered");
|
|
11059
11731
|
return fail("EPROTO", "the far end chose a subprotocol the program did not offer");
|
|
11060
11732
|
}
|
|
@@ -11117,6 +11789,10 @@ var EMPTY = new Uint8Array(0);
|
|
|
11117
11789
|
function message2(e) {
|
|
11118
11790
|
return e instanceof Error ? e.message : String(e);
|
|
11119
11791
|
}
|
|
11792
|
+
function codeOf2(e) {
|
|
11793
|
+
const code = e?.code;
|
|
11794
|
+
return typeof code === "string" && /^E[A-Z]+$/.test(code) ? code : "EIO";
|
|
11795
|
+
}
|
|
11120
11796
|
async function* once(bytes) {
|
|
11121
11797
|
yield bytes;
|
|
11122
11798
|
}
|
|
@@ -11126,10 +11802,12 @@ var HttpHandles = class {
|
|
|
11126
11802
|
nextHandle = 0;
|
|
11127
11803
|
ws;
|
|
11128
11804
|
owner;
|
|
11129
|
-
|
|
11805
|
+
local;
|
|
11806
|
+
constructor(transport, owner, local) {
|
|
11130
11807
|
this.transport = transport;
|
|
11131
11808
|
this.owner = owner;
|
|
11132
|
-
this.
|
|
11809
|
+
this.local = local;
|
|
11810
|
+
this.ws = new WsHandles(transport, () => ++this.nextHandle, local);
|
|
11133
11811
|
}
|
|
11134
11812
|
syscall(req) {
|
|
11135
11813
|
if (req.op.startsWith("net-ws-")) return this.ws.syscall(req);
|
|
@@ -11137,10 +11815,13 @@ var HttpHandles = class {
|
|
|
11137
11815
|
if (req.op === "net-open") return Promise.resolve(this.opened(req));
|
|
11138
11816
|
if (req.op === "net-head") return this.head(req.handle);
|
|
11139
11817
|
if (req.op === "net-read") return this.read(req.handle, req.max);
|
|
11140
|
-
if (req.op === "net-traits") return Promise.resolve(this.traits());
|
|
11818
|
+
if (req.op === "net-traits") return Promise.resolve(this.traits(req.url));
|
|
11141
11819
|
return this.close(req.handle);
|
|
11142
11820
|
}
|
|
11143
|
-
traits() {
|
|
11821
|
+
traits(url) {
|
|
11822
|
+
if (url !== void 0 && this.local?.port(url) !== void 0) {
|
|
11823
|
+
return { ok: true, kind: "json", json: LOOPBACK_TRAITS };
|
|
11824
|
+
}
|
|
11144
11825
|
const { unavailable, crossOrigin = "any", ...rest2 } = this.transport.traits;
|
|
11145
11826
|
if (unavailable) return { ok: false, errno: "ENETUNREACH", message: NO_TRANSPORT };
|
|
11146
11827
|
return { ok: true, kind: "json", json: { ...rest2, crossOrigin } };
|
|
@@ -11171,28 +11852,30 @@ var HttpHandles = class {
|
|
|
11171
11852
|
return { handle, head: entry.head };
|
|
11172
11853
|
}
|
|
11173
11854
|
async respond(handle, req, entry) {
|
|
11855
|
+
const port = this.local?.port(req.url);
|
|
11174
11856
|
try {
|
|
11175
|
-
const
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
);
|
|
11857
|
+
const target = port === void 0 ? toHostLoopback(req.url) : req.url;
|
|
11858
|
+
const request = {
|
|
11859
|
+
url: target,
|
|
11860
|
+
method: req.method,
|
|
11861
|
+
headers: req.headers,
|
|
11862
|
+
...req.body ? { body: req.body } : {},
|
|
11863
|
+
signal: entry.abort.signal
|
|
11864
|
+
};
|
|
11865
|
+
const response = port === void 0 ? await this.transport.fetch(request, this.owner) : await this.local.fetch(request, port);
|
|
11185
11866
|
entry.body = response.body[Symbol.asyncIterator]();
|
|
11186
11867
|
entry.cancel = () => response.cancel();
|
|
11187
11868
|
if (!this.open.has(handle)) await entry.cancel().catch(() => void 0);
|
|
11188
|
-
const
|
|
11869
|
+
const shown = response.url && response.url !== target ? response.url : req.url;
|
|
11870
|
+
const head = this.headOf(handle, shown, response);
|
|
11189
11871
|
return response.redirected ? { ...head, json: { ...head.json, redirected: true } } : head;
|
|
11190
11872
|
} catch (e) {
|
|
11191
11873
|
const text = message2(e);
|
|
11874
|
+
if (port !== void 0) return { ok: false, errno: codeOf2(e), message: text };
|
|
11192
11875
|
const status = e.status;
|
|
11193
11876
|
if (text === NO_TRANSPORT || typeof status !== "number" || status === 502) {
|
|
11194
|
-
const
|
|
11195
|
-
return { ok: false, errno, message: text };
|
|
11877
|
+
const errno2 = text === NO_TRANSPORT ? "ENETUNREACH" : "ECONNREFUSED";
|
|
11878
|
+
return { ok: false, errno: errno2, message: text };
|
|
11196
11879
|
}
|
|
11197
11880
|
entry.body = once(new TextEncoder().encode(text))[Symbol.asyncIterator]();
|
|
11198
11881
|
return this.headOf(handle, req.url, {
|
|
@@ -12133,7 +12816,9 @@ async function socketSyscall(req, proc) {
|
|
|
12133
12816
|
case "sock-accept":
|
|
12134
12817
|
return ok(await accept(proc, req.fd, req.nonblock));
|
|
12135
12818
|
case "sock-connect": {
|
|
12136
|
-
const
|
|
12819
|
+
const socket = socketAt(fds, req.fd);
|
|
12820
|
+
const stamp = net.facadeAt(req.addr) && await proc.stamp?.();
|
|
12821
|
+
const dialing = socket.connect(req.addr, true, proc.cred?.(), stamp || void 0);
|
|
12137
12822
|
if (req.nonblock) {
|
|
12138
12823
|
dialing?.catch(() => void 0);
|
|
12139
12824
|
throw new KernelError("EINPROGRESS");
|
|
@@ -13312,6 +13997,9 @@ var WasmProcess = class {
|
|
|
13312
13997
|
if (channel) channel.stamp = stamp;
|
|
13313
13998
|
}
|
|
13314
13999
|
}
|
|
14000
|
+
connectorStamp() {
|
|
14001
|
+
return this.sealedCaller();
|
|
14002
|
+
}
|
|
13315
14003
|
async sealedCaller() {
|
|
13316
14004
|
const shown = await this.options.identity?.();
|
|
13317
14005
|
const broker = this.options.sealed;
|
|
@@ -13650,7 +14338,8 @@ var WasmProcess = class {
|
|
|
13650
14338
|
net: this.net,
|
|
13651
14339
|
blocking: () => this.blockingSignal(cancelled),
|
|
13652
14340
|
resolve: (name2, family, blocking) => resolver.resolve(name2, family, blocking),
|
|
13653
|
-
cred: () => ({ pid: this.pid, uid: this.cred.euid, gid: this.cred.egid })
|
|
14341
|
+
cred: () => ({ pid: this.pid, uid: this.cred.euid, gid: this.cred.egid }),
|
|
14342
|
+
stamp: () => this.connectorStamp()
|
|
13654
14343
|
});
|
|
13655
14344
|
}
|
|
13656
14345
|
tty(fd) {
|
|
@@ -15022,7 +15711,7 @@ function scrubberOf(secrets) {
|
|
|
15022
15711
|
const usable = secrets.filter((s) => encoder5.encode(s.value).length >= MIN_VALUE);
|
|
15023
15712
|
return usable.length > 0 ? compile(usable, { idleMs: SCRUB_IDLE_MS }) : void 0;
|
|
15024
15713
|
}
|
|
15025
|
-
async function
|
|
15714
|
+
async function writeAll3(file, bytes) {
|
|
15026
15715
|
const write = file.write;
|
|
15027
15716
|
for (let at = 0; at < bytes.length; ) at += await write.call(file, bytes.subarray(at));
|
|
15028
15717
|
}
|
|
@@ -15056,7 +15745,7 @@ function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
|
|
|
15056
15745
|
const later = (bytes) => {
|
|
15057
15746
|
if (bytes.length === 0) return;
|
|
15058
15747
|
pending += 1;
|
|
15059
|
-
tail = tail.then(() =>
|
|
15748
|
+
tail = tail.then(() => writeAll3(file, bytes)).catch((err) => {
|
|
15060
15749
|
if (failed3 === void 0) failed3 = err;
|
|
15061
15750
|
}).finally(() => {
|
|
15062
15751
|
pending -= 1;
|
|
@@ -15687,8 +16376,8 @@ var FSA_CAPABILITIES = {
|
|
|
15687
16376
|
ranges: true,
|
|
15688
16377
|
sessions: true
|
|
15689
16378
|
};
|
|
15690
|
-
var
|
|
15691
|
-
var denied = (err) =>
|
|
16379
|
+
var codeOf3 = (err) => err?.code;
|
|
16380
|
+
var denied = (err) => codeOf3(err) === "EACCES" || err?.name === "NotAllowedError";
|
|
15692
16381
|
function attrOf(st) {
|
|
15693
16382
|
return {
|
|
15694
16383
|
kind: st.isDirectory ? "directory" : st.isSymbolicLink ? "symlink" : "file",
|
|
@@ -15710,7 +16399,7 @@ function kernelFsHandlers(fs) {
|
|
|
15710
16399
|
try {
|
|
15711
16400
|
return await fs.stat(path);
|
|
15712
16401
|
} catch (err) {
|
|
15713
|
-
if (
|
|
16402
|
+
if (codeOf3(err) === "ENOENT") return void 0;
|
|
15714
16403
|
throw err;
|
|
15715
16404
|
}
|
|
15716
16405
|
}
|
|
@@ -15903,7 +16592,7 @@ function parseFstab(text) {
|
|
|
15903
16592
|
});
|
|
15904
16593
|
return lines2;
|
|
15905
16594
|
}
|
|
15906
|
-
function
|
|
16595
|
+
function codeOf4(err) {
|
|
15907
16596
|
const code = err?.code;
|
|
15908
16597
|
return typeof code === "string" ? code : "EIO";
|
|
15909
16598
|
}
|
|
@@ -15941,18 +16630,18 @@ async function mountLine({ line, spec, invalid }, mount, signal, retries, discar
|
|
|
15941
16630
|
break;
|
|
15942
16631
|
} catch (err) {
|
|
15943
16632
|
failed3 = err;
|
|
15944
|
-
if (FINAL.has(
|
|
16633
|
+
if (FINAL.has(codeOf4(err)) || attempt === retries.length) break;
|
|
15945
16634
|
await wait(retries[attempt], signal);
|
|
15946
16635
|
}
|
|
15947
16636
|
}
|
|
15948
|
-
const code =
|
|
16637
|
+
const code = codeOf4(failed3);
|
|
15949
16638
|
return { line, target: spec.target, code, error: String(failed3?.message ?? code) };
|
|
15950
16639
|
}
|
|
15951
16640
|
|
|
15952
16641
|
// src/mount/hostfs.ts
|
|
15953
16642
|
var HOSTFS_IO = 16 * 1024 * 1024;
|
|
15954
|
-
var
|
|
15955
|
-
var unreachable = (err) =>
|
|
16643
|
+
var codeOf5 = (err) => err?.code;
|
|
16644
|
+
var unreachable = (err) => codeOf5(err) === "ENOTCONN";
|
|
15956
16645
|
function rel(path) {
|
|
15957
16646
|
return path.replace(/^\/+/, "");
|
|
15958
16647
|
}
|
|
@@ -16012,9 +16701,9 @@ var HostfsClient = class {
|
|
|
16012
16701
|
throw fsError2("ESTALE", "the folder was granted again, so its open files are gone");
|
|
16013
16702
|
return this.send(path, init, false, true);
|
|
16014
16703
|
}
|
|
16015
|
-
const
|
|
16704
|
+
const errno2 = response.headers.get("X-Hostfs-Errno") ?? "EIO";
|
|
16016
16705
|
const body = await response.json().catch(() => ({}));
|
|
16017
|
-
throw fsError2(
|
|
16706
|
+
throw fsError2(errno2, typeof body.message === "string" ? body.message : errno2);
|
|
16018
16707
|
}
|
|
16019
16708
|
async call(op, body = {}, handle = false) {
|
|
16020
16709
|
const response = await this.send(
|
|
@@ -16129,7 +16818,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
|
|
|
16129
16818
|
try {
|
|
16130
16819
|
return { opened: await opening2(), restore: void 0 };
|
|
16131
16820
|
} catch (err) {
|
|
16132
|
-
if (!flags2.write ||
|
|
16821
|
+
if (!flags2.write || codeOf5(err) !== "EACCES") throw err;
|
|
16133
16822
|
const was = await client.call("stat", { path: rel(path) });
|
|
16134
16823
|
const mode = was.mode ?? 0;
|
|
16135
16824
|
if (mode & 128) throw err;
|
|
@@ -16554,7 +17243,7 @@ var PortTransport = class {
|
|
|
16554
17243
|
}
|
|
16555
17244
|
};
|
|
16556
17245
|
var errorOf = (err) => ({
|
|
16557
|
-
code: typeof
|
|
17246
|
+
code: typeof codeOf5(err) === "string" ? codeOf5(err) : "EIO",
|
|
16558
17247
|
message: err?.message ?? String(err)
|
|
16559
17248
|
});
|
|
16560
17249
|
function serveHostfsPort(port, transport) {
|
|
@@ -17145,8 +17834,8 @@ var RULE = new RegExp(
|
|
|
17145
17834
|
var TERM = /^[A-Za-z0-9._+-]+$/;
|
|
17146
17835
|
var PLAIN = /^[A-Za-z0-9_@%+=:,./-]+$/;
|
|
17147
17836
|
var USAGE = "usage: sudo [-n] [-u user] [--] command [args...]\n sudo -l";
|
|
17148
|
-
function failed2(
|
|
17149
|
-
return { error, errno };
|
|
17837
|
+
function failed2(errno2, error) {
|
|
17838
|
+
return { error, errno: errno2 };
|
|
17150
17839
|
}
|
|
17151
17840
|
function unescaped(double) {
|
|
17152
17841
|
return double.replace(/\\(["\\$`])/g, "$1");
|
|
@@ -17757,6 +18446,7 @@ var Launcher = class {
|
|
|
17757
18446
|
);
|
|
17758
18447
|
net = new LoopbackNet();
|
|
17759
18448
|
cdp;
|
|
18449
|
+
facades;
|
|
17760
18450
|
ca;
|
|
17761
18451
|
transport;
|
|
17762
18452
|
uplink;
|
|
@@ -17831,10 +18521,16 @@ var Launcher = class {
|
|
|
17831
18521
|
this.modulesDir = options.modules ?? "/node_modules";
|
|
17832
18522
|
this.appended = appendedRoots(options.commands);
|
|
17833
18523
|
this.cdp = new CdpHosts(options.cdp);
|
|
18524
|
+
this.facades = new FacadeHosts(options.facades);
|
|
17834
18525
|
const hostname = options.hostname ?? DEFAULT_HOSTNAME;
|
|
17835
18526
|
if (!isHostname(hostname)) throw new Error(`not a host name: ${String(hostname)}`);
|
|
17836
18527
|
this.hostname = hostname;
|
|
17837
|
-
this.env = {
|
|
18528
|
+
this.env = {
|
|
18529
|
+
...networkEnv(hostname),
|
|
18530
|
+
SLICC_CDP_URL: this.cdp.url,
|
|
18531
|
+
...this.facades.env(),
|
|
18532
|
+
...options.env
|
|
18533
|
+
};
|
|
17838
18534
|
this.ca = kernelCa(options.caStore ?? memoryCaStore());
|
|
17839
18535
|
this.transport = replayingHops(options.transport ?? missingTransport());
|
|
17840
18536
|
this.announcedTraits = JSON.stringify(this.transport.traits);
|
|
@@ -17849,6 +18545,7 @@ var Launcher = class {
|
|
|
17849
18545
|
ca: this.ca
|
|
17850
18546
|
});
|
|
17851
18547
|
enableCdp(this.net, this.cdp);
|
|
18548
|
+
this.facades.enable(this.net);
|
|
17852
18549
|
const fs = withCommandDirs(
|
|
17853
18550
|
this.base,
|
|
17854
18551
|
async () => new Set((await this.commands()).keys()),
|
|
@@ -18432,6 +19129,12 @@ var Launcher = class {
|
|
|
18432
19129
|
groupSession(pgid) {
|
|
18433
19130
|
return this.jobs.sessionOf(pgid);
|
|
18434
19131
|
}
|
|
19132
|
+
loopbackFor(pid) {
|
|
19133
|
+
return new KernelLoopback(this.net, () => {
|
|
19134
|
+
const cred = this.credOf(pid);
|
|
19135
|
+
return { pid, uid: cred.euid, gid: cred.egid };
|
|
19136
|
+
});
|
|
19137
|
+
}
|
|
18435
19138
|
treeScrubs(req) {
|
|
18436
19139
|
const inherited2 = req.ppid !== void 0 ? this.scrubs.get(req.ppid) : void 0;
|
|
18437
19140
|
return req.scrub ? [...inherited2 ?? [], req.scrub] : inherited2;
|
|
@@ -18509,7 +19212,7 @@ var Launcher = class {
|
|
|
18509
19212
|
jobs: this.jobs,
|
|
18510
19213
|
ptys: this.ptys,
|
|
18511
19214
|
net: this.net,
|
|
18512
|
-
http: new HttpHandles(this.transport, pid),
|
|
19215
|
+
http: new HttpHandles(this.transport, pid, this.loopbackFor(pid)),
|
|
18513
19216
|
locks: this.locks,
|
|
18514
19217
|
resolver: this.resolver,
|
|
18515
19218
|
onReap: (child) => this.reaped(child, pid),
|
|
@@ -18782,6 +19485,9 @@ var Launcher = class {
|
|
|
18782
19485
|
}
|
|
18783
19486
|
};
|
|
18784
19487
|
}
|
|
19488
|
+
connectorStamp(pid) {
|
|
19489
|
+
return this.sealed.stamp(this.identityOf(pid), this.credOf(pid), this.tagOf(pid));
|
|
19490
|
+
}
|
|
18785
19491
|
identityOf(pid) {
|
|
18786
19492
|
const parent = this.jobs.shownParent(pid);
|
|
18787
19493
|
const alive = parent !== void 0 && this.running(parent);
|
|
@@ -19688,7 +20394,7 @@ import { connect as tls } from "node:tls";
|
|
|
19688
20394
|
var PROBE_MS = 3e3;
|
|
19689
20395
|
var PASSED = /* @__PURE__ */ new Set(["ECONNREFUSED", "EHOSTUNREACH", "ENETUNREACH", "ETIMEDOUT", "ECONNRESET"]);
|
|
19690
20396
|
var NO_NAME = /* @__PURE__ */ new Set(["ENOTFOUND", "EAI_AGAIN", "EAI_NONAME", "EAI_FAIL", "ENODATA"]);
|
|
19691
|
-
function
|
|
20397
|
+
function codeOf6(e) {
|
|
19692
20398
|
const code = e?.code;
|
|
19693
20399
|
return typeof code === "string" ? code : void 0;
|
|
19694
20400
|
}
|
|
@@ -19703,7 +20409,7 @@ function probe(secure, host, port, ms) {
|
|
|
19703
20409
|
const timer = setTimeout(() => done4("ETIMEDOUT"), ms);
|
|
19704
20410
|
socket.once(secure ? "secureConnect" : "connect", () => done4("EPROTO"));
|
|
19705
20411
|
socket.once("error", (e) => {
|
|
19706
|
-
const code =
|
|
20412
|
+
const code = codeOf6(e) ?? "";
|
|
19707
20413
|
done4(PASSED.has(code) ? code : "EPROTO");
|
|
19708
20414
|
});
|
|
19709
20415
|
});
|
|
@@ -19716,7 +20422,7 @@ async function diagnose(url, ms = PROBE_MS) {
|
|
|
19716
20422
|
try {
|
|
19717
20423
|
await lookup2(host);
|
|
19718
20424
|
} catch (e) {
|
|
19719
|
-
return NO_NAME.has(
|
|
20425
|
+
return NO_NAME.has(codeOf6(e) ?? "") ? "EAI_NONAME" : void 0;
|
|
19720
20426
|
}
|
|
19721
20427
|
return probe(secure, host, port, ms);
|
|
19722
20428
|
}
|
|
@@ -19732,7 +20438,7 @@ function diagnosing(transport, check = diagnose) {
|
|
|
19732
20438
|
try {
|
|
19733
20439
|
return await open(request);
|
|
19734
20440
|
} catch (e) {
|
|
19735
|
-
if (request.signal.aborted || !(e instanceof Error) ||
|
|
20441
|
+
if (request.signal.aborted || !(e instanceof Error) || codeOf6(e)) throw e;
|
|
19736
20442
|
const code = await check(request.url).catch(() => void 0);
|
|
19737
20443
|
throw code ? Object.assign(e, { code }) : e;
|
|
19738
20444
|
}
|
|
@@ -19990,7 +20696,7 @@ function dialStream(port, onClose) {
|
|
|
19990
20696
|
}
|
|
19991
20697
|
|
|
19992
20698
|
// src/client/loopback-fetch.ts
|
|
19993
|
-
var
|
|
20699
|
+
var OWN_HEADERS2 = /* @__PURE__ */ new Set(["host", "connection", "content-length", "transfer-encoding"]);
|
|
19994
20700
|
var encoder8 = new TextEncoder();
|
|
19995
20701
|
var latin12 = new TextDecoder("latin1");
|
|
19996
20702
|
var ByteSource = class {
|
|
@@ -20037,7 +20743,7 @@ function indexOf(haystack, needle) {
|
|
|
20037
20743
|
}
|
|
20038
20744
|
return -1;
|
|
20039
20745
|
}
|
|
20040
|
-
function
|
|
20746
|
+
function parseHead2(bytes) {
|
|
20041
20747
|
const [statusLine = "", ...lines2] = latin12.decode(bytes).split("\r\n");
|
|
20042
20748
|
const match = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
|
|
20043
20749
|
if (!match) throw errorWithCode(`EPROTO: not an HTTP/1.x response: ${statusLine.slice(0, 80)}`);
|
|
@@ -20051,7 +20757,7 @@ function parseHead(bytes) {
|
|
|
20051
20757
|
function header3(head, name2) {
|
|
20052
20758
|
return head.headers.find(([key]) => key.toLowerCase() === name2)?.[1];
|
|
20053
20759
|
}
|
|
20054
|
-
async function*
|
|
20760
|
+
async function* chunked2(source) {
|
|
20055
20761
|
for (; ; ) {
|
|
20056
20762
|
const line = await source.until("\r\n");
|
|
20057
20763
|
if (!line) throw errorWithCode("EPROTO: the chunked body ended early");
|
|
@@ -20063,7 +20769,7 @@ async function* chunked(source) {
|
|
|
20063
20769
|
yield data;
|
|
20064
20770
|
}
|
|
20065
20771
|
}
|
|
20066
|
-
async function*
|
|
20772
|
+
async function* counted2(source, length2) {
|
|
20067
20773
|
let left = length2;
|
|
20068
20774
|
while (left > 0) {
|
|
20069
20775
|
const data = await source.some(left);
|
|
@@ -20072,19 +20778,19 @@ async function* counted(source, length2) {
|
|
|
20072
20778
|
yield data;
|
|
20073
20779
|
}
|
|
20074
20780
|
}
|
|
20075
|
-
async function*
|
|
20781
|
+
async function* untilClose2(source) {
|
|
20076
20782
|
for (let data = await source.some(65536); data; data = await source.some(65536)) yield data;
|
|
20077
20783
|
}
|
|
20078
|
-
function
|
|
20784
|
+
function bodyOf2(source, head, method) {
|
|
20079
20785
|
if (method === "HEAD" || head.status === 204 || head.status === 304) return void 0;
|
|
20080
|
-
if (header3(head, "transfer-encoding")?.toLowerCase().includes("chunked")) return
|
|
20786
|
+
if (header3(head, "transfer-encoding")?.toLowerCase().includes("chunked")) return chunked2(source);
|
|
20081
20787
|
const length2 = header3(head, "content-length");
|
|
20082
|
-
if (length2 === void 0) return
|
|
20788
|
+
if (length2 === void 0) return untilClose2(source);
|
|
20083
20789
|
const size = Number(length2);
|
|
20084
20790
|
if (!/^\d+$/.test(length2.trim()) || !Number.isSafeInteger(size)) {
|
|
20085
20791
|
throw errorWithCode(`EPROTO: a malformed Content-Length: ${length2}`);
|
|
20086
20792
|
}
|
|
20087
|
-
return
|
|
20793
|
+
return counted2(source, size);
|
|
20088
20794
|
}
|
|
20089
20795
|
function stream(chunks2, socket, signal) {
|
|
20090
20796
|
return new ReadableStream({
|
|
@@ -20113,7 +20819,7 @@ async function requestBytes(request) {
|
|
|
20113
20819
|
const body = request.body ? new Uint8Array(await request.arrayBuffer()) : void 0;
|
|
20114
20820
|
const lines2 = [`${request.method} ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`];
|
|
20115
20821
|
request.headers.forEach((value, name2) => {
|
|
20116
|
-
if (!
|
|
20822
|
+
if (!OWN_HEADERS2.has(name2)) lines2.push(`${name2}: ${value}`);
|
|
20117
20823
|
});
|
|
20118
20824
|
if (body) lines2.push(`Content-Length: ${body.length}`);
|
|
20119
20825
|
lines2.push("Connection: close", "", "");
|
|
@@ -20141,9 +20847,9 @@ async function loopbackFetch(dial, input, options) {
|
|
|
20141
20847
|
do {
|
|
20142
20848
|
const raw = await source.until("\r\n\r\n");
|
|
20143
20849
|
if (!raw) throw errorWithCode("ECONNRESET: the connection closed before a response");
|
|
20144
|
-
head =
|
|
20850
|
+
head = parseHead2(raw);
|
|
20145
20851
|
} while (head.status >= 100 && head.status < 200);
|
|
20146
|
-
const chunks2 =
|
|
20852
|
+
const chunks2 = bodyOf2(source, head, request.method);
|
|
20147
20853
|
if (!chunks2) socket.close();
|
|
20148
20854
|
return new Response(chunks2 ? stream(chunks2, socket, options.signal) : null, {
|
|
20149
20855
|
status: head.status,
|
|
@@ -20211,6 +20917,8 @@ var Channel = class {
|
|
|
20211
20917
|
watching = /* @__PURE__ */ new Map();
|
|
20212
20918
|
cdpHooks = /* @__PURE__ */ new Map();
|
|
20213
20919
|
cdpOpen = /* @__PURE__ */ new Set();
|
|
20920
|
+
facadeHooks = /* @__PURE__ */ new Map();
|
|
20921
|
+
facadeOpen = /* @__PURE__ */ new Set();
|
|
20214
20922
|
protocol = PROTOCOL;
|
|
20215
20923
|
nextId = 0;
|
|
20216
20924
|
port;
|
|
@@ -20233,6 +20941,8 @@ var Channel = class {
|
|
|
20233
20941
|
this.remote?.fail(error);
|
|
20234
20942
|
for (const close of [...this.cdpOpen]) close();
|
|
20235
20943
|
this.cdpOpen.clear();
|
|
20944
|
+
for (const close of [...this.facadeOpen]) close();
|
|
20945
|
+
this.facadeOpen.clear();
|
|
20236
20946
|
for (const call of this.pending.values()) call.reject(error);
|
|
20237
20947
|
this.pending.clear();
|
|
20238
20948
|
this.release();
|
|
@@ -20262,6 +20972,10 @@ var Channel = class {
|
|
|
20262
20972
|
this.openCdp(data);
|
|
20263
20973
|
return;
|
|
20264
20974
|
}
|
|
20975
|
+
if (data.facadeOpen && data.port) {
|
|
20976
|
+
this.openFacade(data);
|
|
20977
|
+
return;
|
|
20978
|
+
}
|
|
20265
20979
|
if (data.watch !== void 0) {
|
|
20266
20980
|
this.watching.get(data.watch)?.(data.change);
|
|
20267
20981
|
return;
|
|
@@ -20304,7 +21018,36 @@ var Channel = class {
|
|
|
20304
21018
|
const close = serveCdpPort(port, hook, request, () => this.cdpOpen.delete(close));
|
|
20305
21019
|
this.cdpOpen.add(close);
|
|
20306
21020
|
}
|
|
21021
|
+
openFacade({ facadeOpen: { registration, ...request }, port }) {
|
|
21022
|
+
const hook = registration === void 0 ? void 0 : this.facadeHooks.get(registration);
|
|
21023
|
+
if (!hook || this.failure) {
|
|
21024
|
+
port.postMessage({ error: "this client no longer serves the facade" });
|
|
21025
|
+
port.close();
|
|
21026
|
+
return;
|
|
21027
|
+
}
|
|
21028
|
+
const close = serveFacadePort(port, hook, request, () => this.facadeOpen.delete(close));
|
|
21029
|
+
this.facadeOpen.add(close);
|
|
21030
|
+
}
|
|
20307
21031
|
};
|
|
21032
|
+
async function serveFacadeOn(channel, name2, hook) {
|
|
21033
|
+
if (typeof hook !== "function") throw new TypeError("serveFacade: hook must be a function");
|
|
21034
|
+
await since(channel, 11, "facade serving", async () => {
|
|
21035
|
+
});
|
|
21036
|
+
const { id: id2, done: done4 } = channel.request({ op: "serve-facade", name: name2 });
|
|
21037
|
+
channel.facadeHooks.set(id2, hook);
|
|
21038
|
+
try {
|
|
21039
|
+
await done4;
|
|
21040
|
+
} catch (error) {
|
|
21041
|
+
channel.facadeHooks.delete(id2);
|
|
21042
|
+
throw error;
|
|
21043
|
+
}
|
|
21044
|
+
return {
|
|
21045
|
+
close: () => {
|
|
21046
|
+
if (!channel.facadeHooks.delete(id2)) return;
|
|
21047
|
+
void channel.call({ op: "unserve-facade", registration: id2 }).catch(() => void 0);
|
|
21048
|
+
}
|
|
21049
|
+
};
|
|
21050
|
+
}
|
|
20308
21051
|
async function serveCdpOn(channel, hook, options) {
|
|
20309
21052
|
await since(channel, 5, "CDP serving", async () => {
|
|
20310
21053
|
});
|
|
@@ -20538,6 +21281,7 @@ async function attachKernel(port, options = {}) {
|
|
|
20538
21281
|
loopbackFetch: (input, options2) => loopbackFetch((o) => dialOn(channel, o), input, options2),
|
|
20539
21282
|
fs: fsOn(channel),
|
|
20540
21283
|
serveCdp: (hook, options2 = {}) => serveCdpOn(channel, hook, options2),
|
|
21284
|
+
serveFacade: (name2, hook) => serveFacadeOn(channel, name2, hook),
|
|
20541
21285
|
onTraits(listener) {
|
|
20542
21286
|
channel.traitsListeners.add(listener);
|
|
20543
21287
|
return () => channel.traitsListeners.delete(listener);
|
|
@@ -20654,6 +21398,7 @@ async function createNodeKernel(options = {}) {
|
|
|
20654
21398
|
...options.hostfs ? { hostfs: options.hostfs } : {},
|
|
20655
21399
|
...hostfsFetchOf(options),
|
|
20656
21400
|
...options.cdp ? { cdp: options.cdp } : {},
|
|
21401
|
+
...options.facades ? { facades: options.facades } : {},
|
|
20657
21402
|
...options.hostname !== void 0 ? { hostname: options.hostname } : {},
|
|
20658
21403
|
users: options.users === void 0 ? DEFAULT_USERS : options.users,
|
|
20659
21404
|
...options.onDenial ? { onDenial: options.onDenial } : {},
|