capnweb 0.9.0 → 0.10.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.
@@ -964,6 +964,12 @@ var PromiseStubHook = class PromiseStubHook extends StubHook {
964
964
 
965
965
  //#endregion
966
966
  //#region src/serialize.ts
967
+ const DEFAULT_MAX_DEPTH = 256;
968
+ const DEFAULT_LIMITS = {
969
+ maxBigIntDigits: 16384,
970
+ maxDepth: 256,
971
+ maxMessageSize: 32 * 1024 * 1024
972
+ };
967
973
  var NullExporter = class {
968
974
  exportStub(stub) {
969
975
  throw new Error("Cannot serialize RPC stubs without an RPC session.");
@@ -983,7 +989,7 @@ async function streamToBlob(stream, type) {
983
989
  let b = await new Response(stream).blob();
984
990
  return b.type === type ? b : b.slice(0, b.size, type);
985
991
  }
986
- const ERROR_TYPES = {
992
+ const ERROR_TYPES = Object.assign(Object.create(null), {
987
993
  Error,
988
994
  EvalError,
989
995
  RangeError,
@@ -992,7 +998,7 @@ const ERROR_TYPES = {
992
998
  TypeError,
993
999
  URIError,
994
1000
  AggregateError
995
- };
1001
+ });
996
1002
  var Devaluator = class Devaluator {
997
1003
  exporter;
998
1004
  source;
@@ -1015,7 +1021,7 @@ var Devaluator = class Devaluator {
1015
1021
  }
1016
1022
  exports;
1017
1023
  devaluateImpl(value, parent, depth) {
1018
- if (depth >= 64) throw new Error("Serialization exceeded maximum allowed depth. (Does the message contain cycles?)");
1024
+ if (depth >= 256) throw new Error("Serialization exceeded maximum allowed depth. (Does the message contain cycles?)");
1019
1025
  switch (typeForRpc(value)) {
1020
1026
  case "unsupported": {
1021
1027
  let msg;
@@ -1249,6 +1255,9 @@ var NullImporter = class {
1249
1255
  getPipeReadable(exportId) {
1250
1256
  throw new Error("Cannot retrieve pipe readable without an RPC session.");
1251
1257
  }
1258
+ getLimits() {
1259
+ return DEFAULT_LIMITS;
1260
+ }
1252
1261
  };
1253
1262
  const NULL_IMPORTER = new NullImporter();
1254
1263
  function fixBrokenRequestBody(request, body) {
@@ -1266,16 +1275,21 @@ function streamToBlobPromise(stream, type) {
1266
1275
  var Evaluator = class Evaluator {
1267
1276
  importer;
1268
1277
  encodingLevel;
1278
+ limits;
1269
1279
  constructor(importer, encodingLevel = "string") {
1270
1280
  this.importer = importer;
1271
1281
  this.encodingLevel = encodingLevel;
1282
+ this.limits = importer.getLimits();
1272
1283
  }
1273
1284
  hooks = [];
1274
1285
  promises = [];
1275
1286
  evaluate(value) {
1287
+ return this.evaluateWithDepth(value, 0);
1288
+ }
1289
+ evaluateWithDepth(value, depth) {
1276
1290
  let payload = RpcPayload.forEvaluate(this.hooks, this.promises);
1277
1291
  try {
1278
- payload.value = this.evaluateImpl(value, payload, "value");
1292
+ payload.value = this.evaluateImpl(value, payload, "value", depth);
1279
1293
  return payload;
1280
1294
  } catch (err) {
1281
1295
  payload.dispose();
@@ -1285,18 +1299,25 @@ var Evaluator = class Evaluator {
1285
1299
  evaluateCopy(value) {
1286
1300
  return this.evaluate(structuredClone(value));
1287
1301
  }
1288
- evaluateImpl(value, parent, property) {
1302
+ evaluateImpl(value, parent, property, depth) {
1303
+ let maxDepth = this.limits.maxDepth;
1304
+ if (depth >= maxDepth) throw new TypeError(`Deserialization exceeded maximum allowed message depth of ${maxDepth}.`);
1289
1305
  if (this.encodingLevel === "structuredClonable") {
1290
1306
  if (value instanceof Date || typeof value === "bigint") return value;
1291
1307
  }
1292
1308
  if (value instanceof Array) {
1293
1309
  if (value.length == 1 && value[0] instanceof Array) {
1294
1310
  let result = value[0];
1295
- for (let i = 0; i < result.length; i++) result[i] = this.evaluateImpl(result[i], result, i);
1311
+ for (let i = 0; i < result.length; i++) result[i] = this.evaluateImpl(result[i], result, i, depth + 1);
1296
1312
  return result;
1297
1313
  } else switch (value[0]) {
1298
1314
  case "bigint":
1299
- if (typeof value[1] == "string") return BigInt(value[1]);
1315
+ if (typeof value[1] == "string") {
1316
+ let digits = value[1];
1317
+ let maxBigIntDigits = this.limits.maxBigIntDigits;
1318
+ if (digits.length > maxBigIntDigits) throw new TypeError(`Deserialized bigint exceeds maximum length of ${maxBigIntDigits} digits.`);
1319
+ return BigInt(digits);
1320
+ }
1300
1321
  break;
1301
1322
  case "date":
1302
1323
  if (value[1] === null) return /* @__PURE__ */ new Date(NaN);
@@ -1326,7 +1347,11 @@ var Evaluator = class Evaluator {
1326
1347
  let propsObj = props;
1327
1348
  for (let key of Object.keys(propsObj)) {
1328
1349
  if (key === "name" || key === "message" || key === "stack") continue;
1329
- anyResult[key] = this.evaluateImpl(propsObj[key], result, key);
1350
+ if (key in Object.prototype || key === "toJSON") {
1351
+ this.evaluateImpl(propsObj[key], result, key, depth + 1);
1352
+ continue;
1353
+ }
1354
+ anyResult[key] = this.evaluateImpl(propsObj[key], result, key, depth + 1);
1330
1355
  }
1331
1356
  }
1332
1357
  return result;
@@ -1347,11 +1372,11 @@ var Evaluator = class Evaluator {
1347
1372
  let init = value[2];
1348
1373
  if (typeof init !== "object" || init === null) break;
1349
1374
  if (init.body) {
1350
- init.body = this.evaluateImpl(init.body, init, "body");
1375
+ init.body = this.evaluateImpl(init.body, init, "body", depth + 1);
1351
1376
  if (init.body === null || typeof init.body === "string" || init.body instanceof Uint8Array || init.body instanceof ReadableStream) {} else throw new TypeError("Request body must be of type ReadableStream.");
1352
1377
  }
1353
1378
  if (init.signal) {
1354
- init.signal = this.evaluateImpl(init.signal, init, "signal");
1379
+ init.signal = this.evaluateImpl(init.signal, init, "signal", depth + 1);
1355
1380
  if (!(init.signal instanceof AbortSignal)) throw new TypeError("Request siganl must be of type AbortSignal.");
1356
1381
  }
1357
1382
  if (init.headers && !(init.headers instanceof Array)) throw new TypeError("Request headers must be serialized as an array of pairs.");
@@ -1368,7 +1393,7 @@ var Evaluator = class Evaluator {
1368
1393
  }
1369
1394
  case "response": {
1370
1395
  if (value.length !== 3) break;
1371
- let body = this.evaluateImpl(value[1], parent, property);
1396
+ let body = this.evaluateImpl(value[1], parent, property, depth + 1);
1372
1397
  if (body === null || typeof body === "string" || body instanceof Uint8Array || body instanceof ReadableStream) {} else throw new TypeError("Response body must be of type ReadableStream.");
1373
1398
  let init = value[2];
1374
1399
  if (typeof init !== "object" || init === null) break;
@@ -1379,7 +1404,7 @@ var Evaluator = class Evaluator {
1379
1404
  case "blob": {
1380
1405
  if (value.length !== 3 || typeof value[1] !== "string") break;
1381
1406
  let contentType = value[1];
1382
- let content = this.evaluateImpl(value[2], parent, property);
1407
+ let content = this.evaluateImpl(value[2], parent, property, depth + 1);
1383
1408
  if (!(content instanceof ReadableStream)) throw new TypeError("Blob content must be serialized as a ReadableStream.");
1384
1409
  let promise = streamToBlobPromise(content, contentType);
1385
1410
  this.promises.push({
@@ -1420,7 +1445,7 @@ var Evaluator = class Evaluator {
1420
1445
  if (value.length == 3) return addStub(hook.get(path));
1421
1446
  let args = value[3];
1422
1447
  if (!(args instanceof Array)) break;
1423
- args = new Evaluator(this.importer).evaluate([args]);
1448
+ args = new Evaluator(this.importer).evaluateWithDepth([args], depth);
1424
1449
  return addStub(hook.call(path, args));
1425
1450
  }
1426
1451
  case "remap": {
@@ -1486,9 +1511,9 @@ var Evaluator = class Evaluator {
1486
1511
  } else if (value instanceof Object) {
1487
1512
  let result = value;
1488
1513
  for (let key in result) if (key in Object.prototype || key === "toJSON") {
1489
- this.evaluateImpl(result[key], result, key);
1514
+ this.evaluateImpl(result[key], result, key, depth + 1);
1490
1515
  delete result[key];
1491
- } else result[key] = this.evaluateImpl(result[key], result, key);
1516
+ } else result[key] = this.evaluateImpl(result[key], result, key, depth + 1);
1492
1517
  return result;
1493
1518
  } else return value;
1494
1519
  }
@@ -1559,6 +1584,10 @@ var ImportTableEntry = class {
1559
1584
  resolution;
1560
1585
  onBrokenRegistrations;
1561
1586
  resolve(resolution) {
1587
+ if (this.resolution) {
1588
+ resolution.dispose();
1589
+ return;
1590
+ }
1562
1591
  if (this.localRefcount == 0) {
1563
1592
  resolution.dispose();
1564
1593
  return;
@@ -1711,6 +1740,7 @@ var RpcSessionImpl = class {
1711
1740
  pullCount = 0;
1712
1741
  onBrokenCallbacks = [];
1713
1742
  encodingLevel;
1743
+ limits;
1714
1744
  constructor(transport, mainHook, options) {
1715
1745
  this.transport = transport;
1716
1746
  this.options = options;
@@ -1723,6 +1753,10 @@ var RpcSessionImpl = class {
1723
1753
  }
1724
1754
  }
1725
1755
  this.encodingLevel = level;
1756
+ this.limits = {
1757
+ ...DEFAULT_LIMITS,
1758
+ ...options.limits
1759
+ };
1726
1760
  this.exports.push({
1727
1761
  hook: mainHook,
1728
1762
  refcount: 1
@@ -1865,6 +1899,9 @@ var RpcSessionImpl = class {
1865
1899
  entry.pipeReadable = void 0;
1866
1900
  return readable;
1867
1901
  }
1902
+ getLimits() {
1903
+ return this.limits;
1904
+ }
1868
1905
  createPipe(readable, readableHook) {
1869
1906
  if (this.abortReason) throw this.abortReason;
1870
1907
  this.send(["pipe"]);
@@ -2024,6 +2061,7 @@ var RpcSessionImpl = class {
2024
2061
  } finally {
2025
2062
  if (this.cancelReadLoop === readCanceled.reject) this.cancelReadLoop = void 0;
2026
2063
  }
2064
+ if (this.encodingLevel === "string" && raw.length > this.limits.maxMessageSize) throw new TypeError(`Incoming message exceeds maximum size of ${this.limits.maxMessageSize} UTF-16 code units.`);
2027
2065
  if (this.abortReason) break;
2028
2066
  let msg = this.encodingLevel === "string" ? JSON.parse(raw) : raw;
2029
2067
  if (msg instanceof Array) switch (msg[0]) {
@@ -2098,7 +2136,7 @@ var RpcSessionImpl = class {
2098
2136
  case "abort": {
2099
2137
  let payload = new Evaluator(this, this.encodingLevel).evaluate(msg[1]);
2100
2138
  payload.dispose();
2101
- this.abort(payload, false);
2139
+ this.abort(payload.value, false);
2102
2140
  break;
2103
2141
  }
2104
2142
  }
@@ -2224,6 +2262,8 @@ var WebSocketTransport = class {
2224
2262
  let message;
2225
2263
  if (reason instanceof Error) message = reason.message;
2226
2264
  else message = `${reason}`;
2265
+ let reasonBytes = new TextEncoder().encode(message);
2266
+ if (reasonBytes.length > 123) message = new TextDecoder().decode(reasonBytes.subarray(0, 123), { stream: true });
2227
2267
  this.#webSocket.close(3e3, message);
2228
2268
  if (!this.#error) this.#error = reason;
2229
2269
  }
@@ -2340,7 +2380,11 @@ async function newHttpBatchRpcResponse(request, localMain, options) {
2340
2380
  * @param options Optional RPC session options. You can also pass headers to set on the response.
2341
2381
  */
2342
2382
  async function nodeHttpBatchRpcResponse(request, response, localMain, options) {
2343
- if (request.method !== "POST") response.writeHead(405, "This endpoint only accepts POST requests.");
2383
+ if (request.method !== "POST") {
2384
+ response.writeHead(405, "This endpoint only accepts POST requests.", options?.headers);
2385
+ response.end();
2386
+ return;
2387
+ }
2344
2388
  let body = await new Promise((resolve, reject) => {
2345
2389
  let chunks = [];
2346
2390
  request.on("data", (chunk) => {
@@ -2605,6 +2649,9 @@ var MapApplicator = class {
2605
2649
  getPipeReadable(exportId) {
2606
2650
  throw new Error("A mapper function cannot use pipe readables.");
2607
2651
  }
2652
+ getLimits() {
2653
+ return DEFAULT_LIMITS;
2654
+ }
2608
2655
  };
2609
2656
  function applyMapToElement(input, parent, owner, captures, instructions) {
2610
2657
  let mapper = new MapApplicator(captures, new PayloadStubHook(RpcPayload.deepCopyFrom(input, parent, owner)));
@@ -2943,16 +2990,18 @@ let newMessagePortRpcSession = newMessagePortRpcSession$1;
2943
2990
  * credentials as parameters and returns the authorized API), then cross-origin requests should
2944
2991
  * be safe.
2945
2992
  */
2946
- async function newWorkersRpcResponse(request, localMain) {
2993
+ async function newWorkersRpcResponse(request, localMain, options) {
2947
2994
  if (request.method === "POST") {
2948
- let response = await newHttpBatchRpcResponse(request, localMain);
2995
+ let response = await newHttpBatchRpcResponse(request, localMain, options);
2949
2996
  response.headers.set("Access-Control-Allow-Origin", "*");
2950
2997
  return response;
2951
- } else if (request.headers.get("Upgrade")?.toLowerCase() === "websocket") return newWorkersWebSocketRpcResponse(request, localMain);
2998
+ } else if (request.headers.get("Upgrade")?.toLowerCase() === "websocket") return newWorkersWebSocketRpcResponse(request, localMain, options);
2952
2999
  else return new Response("This endpoint only accepts POST or WebSocket requests.", { status: 400 });
2953
3000
  }
2954
3001
 
2955
3002
  //#endregion
3003
+ exports.DEFAULT_LIMITS = DEFAULT_LIMITS;
3004
+ exports.DEFAULT_MAX_DEPTH = DEFAULT_MAX_DEPTH;
2956
3005
  exports.RpcPromise = RpcPromise;
2957
3006
  exports.RpcSession = RpcSession;
2958
3007
  exports.RpcStub = RpcStub;