@marcuspuchalla/nachos 0.1.3 → 0.2.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/CHANGELOG.md +75 -0
- package/dist/{chunk-PTWN7K3Y.cjs → chunk-3Z45RBZP.cjs} +469 -244
- package/dist/chunk-3Z45RBZP.cjs.map +1 -0
- package/dist/{chunk-2MTLSQ7E.js → chunk-EDXZTSIA.js} +224 -166
- package/dist/chunk-EDXZTSIA.js.map +1 -0
- package/dist/{chunk-R62CQQNI.cjs → chunk-HMUA5KLG.cjs} +239 -181
- package/dist/chunk-HMUA5KLG.cjs.map +1 -0
- package/dist/{chunk-ZDZ2B5PE.js → chunk-JESIF5IF.js} +7 -3
- package/dist/chunk-JESIF5IF.js.map +1 -0
- package/dist/{chunk-5A5T56JB.js → chunk-LWNWC2O7.js} +442 -217
- package/dist/chunk-LWNWC2O7.js.map +1 -0
- package/dist/{chunk-PD72MVTX.cjs → chunk-P6A2OOIY.cjs} +7 -3
- package/dist/chunk-P6A2OOIY.cjs.map +1 -0
- package/dist/encoder/index.cjs +14 -14
- package/dist/encoder/index.d.cts +5 -4
- package/dist/encoder/index.d.ts +5 -4
- package/dist/encoder/index.js +2 -2
- package/dist/index.cjs +58 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -21
- package/dist/index.d.ts +40 -21
- package/dist/index.js +37 -17
- package/dist/index.js.map +1 -1
- package/dist/metafile-cjs.json +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/parser/index.cjs +21 -21
- package/dist/parser/index.d.cts +4 -2
- package/dist/parser/index.d.ts +4 -2
- package/dist/parser/index.js +2 -2
- package/dist/{types-DvNlfbKB.d.cts → types-eG2qalpr.d.cts} +27 -1
- package/dist/{types-DvNlfbKB.d.ts → types-eG2qalpr.d.ts} +27 -1
- package/dist/{useCborSimpleEncoder-TVxzNJ_9.d.ts → useCborSimpleEncoder-CamvS-_N.d.ts} +7 -3
- package/dist/{useCborSimpleEncoder-ButVU988.d.cts → useCborSimpleEncoder-DXgPx62d.d.cts} +7 -3
- package/dist/{useCborTag-xV2Pz2VY.d.ts → useCborTag-D4d7xG3-.d.cts} +9 -4
- package/dist/{useCborTag-Cs1CZuXZ.d.cts → useCborTag-TYst1KR6.d.ts} +9 -4
- package/package.json +1 -1
- package/src/__tests__/audit-fixes.test.ts +141 -0
- package/src/__tests__/public-api.test.ts +153 -0
- package/src/__tests__/roundtrip.test.ts +5 -6
- package/src/encoder/__tests__/cbor-collection-encoder.test.ts +103 -5
- package/src/encoder/__tests__/cbor-encoder-errors.test.ts +40 -5
- package/src/encoder/__tests__/cbor-simple-encoder.test.ts +126 -0
- package/src/encoder/composables/useCborCollectionEncoder.ts +30 -26
- package/src/encoder/composables/useCborEncoder.ts +40 -0
- package/src/encoder/composables/useCborSimpleEncoder.ts +40 -9
- package/src/encoder/types.ts +9 -4
- package/src/encoder/utils.ts +33 -1
- package/src/index.ts +39 -20
- package/src/parser/__tests__/buffer-native-parsing.test.ts +338 -0
- package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +97 -7
- package/src/parser/__tests__/cbor-security-dos-protection.test.ts +164 -31
- package/src/parser/__tests__/cbor-standard-tags.test.ts +75 -7
- package/src/parser/__tests__/cbor-tag-reparse-fix.test.ts +268 -0
- package/src/parser/__tests__/utils-errors.test.ts +11 -3
- package/src/parser/composables/useCborCollection.ts +51 -45
- package/src/parser/composables/useCborDiagnostic.ts +28 -0
- package/src/parser/composables/useCborFloat.ts +2 -1
- package/src/parser/composables/useCborInteger.ts +24 -10
- package/src/parser/composables/useCborParser.ts +448 -208
- package/src/parser/composables/useCborTag.ts +53 -38
- package/src/parser/types.ts +32 -1
- package/src/parser/utils.ts +52 -0
- package/dist/chunk-2MTLSQ7E.js.map +0 -1
- package/dist/chunk-5A5T56JB.js.map +0 -1
- package/dist/chunk-PD72MVTX.cjs.map +0 -1
- package/dist/chunk-PTWN7K3Y.cjs.map +0 -1
- package/dist/chunk-R62CQQNI.cjs.map +0 -1
- package/dist/chunk-ZDZ2B5PE.js.map +0 -1
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import { useCborByteString, useCborTextString, DEFAULT_LIMITS, INDEFINITE_SYMBOL, ALL_ENTRIES_SYMBOL, DEFAULT_OPTIONS } from './chunk-
|
|
1
|
+
import { useCborByteString, useCborTextString, DEFAULT_LIMITS, INDEFINITE_SYMBOL, ALL_ENTRIES_SYMBOL, DEFAULT_OPTIONS } from './chunk-JESIF5IF.js';
|
|
2
2
|
|
|
3
3
|
// src/parser/utils.ts
|
|
4
4
|
var hexToBytes = (hex) => {
|
|
5
|
+
if (hex.length === 0) {
|
|
6
|
+
return new Uint8Array(0);
|
|
7
|
+
}
|
|
8
|
+
if (hex.length % 2 !== 0) {
|
|
9
|
+
throw new Error("Hex string must have even length");
|
|
10
|
+
}
|
|
11
|
+
if (!/^[0-9a-fA-F]+$/.test(hex)) {
|
|
12
|
+
throw new Error(`Invalid hex character in: ${hex}`);
|
|
13
|
+
}
|
|
5
14
|
const bytes = hex.match(/.{1,2}/g);
|
|
6
15
|
if (!bytes) return new Uint8Array(0);
|
|
7
16
|
return new Uint8Array(bytes.map((byte) => parseInt(byte, 16)));
|
|
@@ -30,6 +39,9 @@ var readUint = (buffer, offset, length) => {
|
|
|
30
39
|
for (let i = 0; i < length; i++) {
|
|
31
40
|
result = result * 256 + readByte(buffer, offset + i);
|
|
32
41
|
}
|
|
42
|
+
if (result > Number.MAX_SAFE_INTEGER) {
|
|
43
|
+
throw new Error(`Value at offset ${offset} (${length} bytes) exceeds MAX_SAFE_INTEGER; use readBigUint`);
|
|
44
|
+
}
|
|
33
45
|
return result;
|
|
34
46
|
};
|
|
35
47
|
var readBigUint = (buffer, offset, length) => {
|
|
@@ -196,6 +208,23 @@ function compareBytes(a, b) {
|
|
|
196
208
|
}
|
|
197
209
|
return 0;
|
|
198
210
|
}
|
|
211
|
+
function compareBytesLexicographic(a, b) {
|
|
212
|
+
if (!a || !b) {
|
|
213
|
+
throw new Error("compareBytesLexicographic: arguments cannot be null or undefined");
|
|
214
|
+
}
|
|
215
|
+
const min = Math.min(a.length, b.length);
|
|
216
|
+
for (let i = 0; i < min; i++) {
|
|
217
|
+
const byteA = a[i];
|
|
218
|
+
const byteB = b[i];
|
|
219
|
+
if (byteA !== byteB) {
|
|
220
|
+
return byteA - byteB;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return a.length - b.length;
|
|
224
|
+
}
|
|
225
|
+
function compareMapKeys(a, b, order = "length-first") {
|
|
226
|
+
return order === "bytewise" ? compareBytesLexicographic(a, b) : compareBytes(a, b);
|
|
227
|
+
}
|
|
199
228
|
function serializeValueForComparison(value) {
|
|
200
229
|
if (value === null) return "null";
|
|
201
230
|
if (value === void 0) return "undefined";
|
|
@@ -233,9 +262,8 @@ function hasDuplicates(items) {
|
|
|
233
262
|
|
|
234
263
|
// src/parser/composables/useCborInteger.ts
|
|
235
264
|
function useCborInteger() {
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
const initialByte = readByte(buffer, 0);
|
|
265
|
+
const parseIntegerFromBuffer = (buffer, offset, options) => {
|
|
266
|
+
const initialByte = readByte(buffer, offset);
|
|
239
267
|
const { majorType, additionalInfo } = extractCborHeader(initialByte);
|
|
240
268
|
let rawValue;
|
|
241
269
|
let bytesRead;
|
|
@@ -243,16 +271,16 @@ function useCborInteger() {
|
|
|
243
271
|
rawValue = additionalInfo;
|
|
244
272
|
bytesRead = 1;
|
|
245
273
|
} else if (additionalInfo === 24) {
|
|
246
|
-
rawValue = readByte(buffer, 1);
|
|
274
|
+
rawValue = readByte(buffer, offset + 1);
|
|
247
275
|
bytesRead = 2;
|
|
248
276
|
} else if (additionalInfo === 25) {
|
|
249
|
-
rawValue = readUint(buffer, 1, 2);
|
|
277
|
+
rawValue = readUint(buffer, offset + 1, 2);
|
|
250
278
|
bytesRead = 3;
|
|
251
279
|
} else if (additionalInfo === 26) {
|
|
252
|
-
rawValue = readUint(buffer, 1, 4);
|
|
280
|
+
rawValue = readUint(buffer, offset + 1, 4);
|
|
253
281
|
bytesRead = 5;
|
|
254
282
|
} else if (additionalInfo === 27) {
|
|
255
|
-
const bigValue = readBigUint(buffer, 1, 8);
|
|
283
|
+
const bigValue = readBigUint(buffer, offset + 1, 8);
|
|
256
284
|
if (bigValue <= BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
257
285
|
rawValue = Number(bigValue);
|
|
258
286
|
} else {
|
|
@@ -292,8 +320,13 @@ function useCborInteger() {
|
|
|
292
320
|
bytesRead
|
|
293
321
|
};
|
|
294
322
|
};
|
|
323
|
+
const parseInteger = (hexString, options) => {
|
|
324
|
+
const buffer = hexToBytes(hexString);
|
|
325
|
+
return parseIntegerFromBuffer(buffer, 0, options);
|
|
326
|
+
};
|
|
295
327
|
return {
|
|
296
|
-
parseInteger
|
|
328
|
+
parseInteger,
|
|
329
|
+
parseIntegerFromBuffer
|
|
297
330
|
};
|
|
298
331
|
}
|
|
299
332
|
|
|
@@ -702,29 +735,34 @@ function useCborFloat() {
|
|
|
702
735
|
return {
|
|
703
736
|
parse,
|
|
704
737
|
parseFloat,
|
|
705
|
-
parseSimple
|
|
738
|
+
parseSimple,
|
|
739
|
+
parseFromBuffer
|
|
706
740
|
};
|
|
707
741
|
}
|
|
708
742
|
|
|
709
743
|
// src/parser/composables/useCborTag.ts
|
|
710
744
|
function useCborTag() {
|
|
711
|
-
const {
|
|
745
|
+
const { parseIntegerFromBuffer } = useCborInteger();
|
|
712
746
|
const { parseByteString, parseTextString } = useCborString();
|
|
713
|
-
const {
|
|
747
|
+
const { parseFromBuffer: parseFloatOrSimpleFromBuffer } = useCborFloat();
|
|
748
|
+
let parseStartTime = 0;
|
|
714
749
|
const parseItem = (buffer, offset, options, tagDepth = 0, collectionDepth = 0) => {
|
|
750
|
+
if (parseStartTime > 0 && options?.limits?.maxParseTime) {
|
|
751
|
+
const elapsed = Date.now() - parseStartTime;
|
|
752
|
+
if (elapsed > options.limits.maxParseTime) {
|
|
753
|
+
throw new Error(`Parse timeout: exceeded ${options.limits.maxParseTime}ms limit`);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
715
756
|
if (offset >= buffer.length) {
|
|
716
757
|
throw new Error(`Unexpected end of buffer at offset ${offset}`);
|
|
717
758
|
}
|
|
718
759
|
const initialByte = readByte(buffer, offset);
|
|
719
|
-
const { majorType
|
|
760
|
+
const { majorType } = extractCborHeader(initialByte);
|
|
720
761
|
switch (majorType) {
|
|
721
762
|
case 0:
|
|
722
763
|
// Unsigned integer
|
|
723
|
-
case 1:
|
|
724
|
-
|
|
725
|
-
const result = parseInteger(intHex, options);
|
|
726
|
-
return { value: result.value, bytesRead: result.bytesRead };
|
|
727
|
-
}
|
|
764
|
+
case 1:
|
|
765
|
+
return parseIntegerFromBuffer(buffer, offset, options);
|
|
728
766
|
case 2:
|
|
729
767
|
return parseByteString(buffer, offset, options);
|
|
730
768
|
case 3:
|
|
@@ -735,16 +773,8 @@ function useCborTag() {
|
|
|
735
773
|
return parseMapInternal(buffer, offset, options, collectionDepth);
|
|
736
774
|
case 6:
|
|
737
775
|
return parseTagFromBuffer(buffer, offset, options, tagDepth);
|
|
738
|
-
case 7:
|
|
739
|
-
|
|
740
|
-
if (additionalInfo >= 25 && additionalInfo <= 27) {
|
|
741
|
-
const result = parseFloat(simpleHex, options);
|
|
742
|
-
return { value: result.value, bytesRead: result.bytesRead };
|
|
743
|
-
} else {
|
|
744
|
-
const result = parseSimple(simpleHex, options);
|
|
745
|
-
return { value: result.value, bytesRead: result.bytesRead };
|
|
746
|
-
}
|
|
747
|
-
}
|
|
776
|
+
case 7:
|
|
777
|
+
return parseFloatOrSimpleFromBuffer(buffer, offset, options);
|
|
748
778
|
default:
|
|
749
779
|
throw new Error(`Unknown major type: ${majorType}`);
|
|
750
780
|
}
|
|
@@ -1008,11 +1038,11 @@ function useCborTag() {
|
|
|
1008
1038
|
if (value.length !== 2) {
|
|
1009
1039
|
throw new Error(`Tag 4 (decimal fraction) array must have exactly 2 elements [exponent, mantissa], got ${value.length}`);
|
|
1010
1040
|
}
|
|
1011
|
-
if (typeof value[0] !== "number" && typeof value[0] !== "bigint") {
|
|
1012
|
-
throw new Error(`Tag 4 (decimal fraction) exponent must be an integer, got ${
|
|
1041
|
+
if (typeof value[0] !== "number" && typeof value[0] !== "bigint" || typeof value[0] === "number" && !Number.isInteger(value[0])) {
|
|
1042
|
+
throw new Error(`Tag 4 (decimal fraction) exponent must be an integer, got ${value[0]}`);
|
|
1013
1043
|
}
|
|
1014
|
-
if (typeof value[1] !== "number" && typeof value[1] !== "bigint") {
|
|
1015
|
-
throw new Error(`Tag 4 (decimal fraction) mantissa must be an integer, got ${
|
|
1044
|
+
if (typeof value[1] !== "number" && typeof value[1] !== "bigint" || typeof value[1] === "number" && !Number.isInteger(value[1])) {
|
|
1045
|
+
throw new Error(`Tag 4 (decimal fraction) mantissa must be an integer, got ${value[1]}`);
|
|
1016
1046
|
}
|
|
1017
1047
|
break;
|
|
1018
1048
|
case 5:
|
|
@@ -1023,11 +1053,11 @@ function useCborTag() {
|
|
|
1023
1053
|
if (value.length !== 2) {
|
|
1024
1054
|
throw new Error(`Tag 5 (bigfloat) array must have exactly 2 elements [exponent, mantissa], got ${value.length}`);
|
|
1025
1055
|
}
|
|
1026
|
-
if (typeof value[0] !== "number" && typeof value[0] !== "bigint") {
|
|
1027
|
-
throw new Error(`Tag 5 (bigfloat) exponent must be an integer, got ${
|
|
1056
|
+
if (typeof value[0] !== "number" && typeof value[0] !== "bigint" || typeof value[0] === "number" && !Number.isInteger(value[0])) {
|
|
1057
|
+
throw new Error(`Tag 5 (bigfloat) exponent must be an integer, got ${value[0]}`);
|
|
1028
1058
|
}
|
|
1029
|
-
if (typeof value[1] !== "number" && typeof value[1] !== "bigint") {
|
|
1030
|
-
throw new Error(`Tag 5 (bigfloat) mantissa must be an integer, got ${
|
|
1059
|
+
if (typeof value[1] !== "number" && typeof value[1] !== "bigint" || typeof value[1] === "number" && !Number.isInteger(value[1])) {
|
|
1060
|
+
throw new Error(`Tag 5 (bigfloat) mantissa must be an integer, got ${value[1]}`);
|
|
1031
1061
|
}
|
|
1032
1062
|
break;
|
|
1033
1063
|
case 32:
|
|
@@ -1192,6 +1222,9 @@ function useCborTag() {
|
|
|
1192
1222
|
throw new Error(`Tag nesting depth ${tagDepth} exceeds limit of ${maxTagDepth}`);
|
|
1193
1223
|
}
|
|
1194
1224
|
const { tagNumber, bytesConsumed } = parseTagNumber(buffer, offset + 1, additionalInfo);
|
|
1225
|
+
if (options?.validateCanonical) {
|
|
1226
|
+
validateCanonicalInteger(tagNumber, additionalInfo);
|
|
1227
|
+
}
|
|
1195
1228
|
let currentOffset = offset + 1 + bytesConsumed;
|
|
1196
1229
|
if (currentOffset >= buffer.length) {
|
|
1197
1230
|
throw new Error(`Unexpected end of buffer after tag ${tagNumber}`);
|
|
@@ -1231,12 +1264,25 @@ function useCborTag() {
|
|
|
1231
1264
|
const parseTag = (hexString, options) => {
|
|
1232
1265
|
const cleanHex = hexString.replace(/\s+/g, "");
|
|
1233
1266
|
const buffer = hexToBytes(cleanHex);
|
|
1234
|
-
|
|
1267
|
+
const isTopLevel = parseStartTime === 0;
|
|
1268
|
+
if (isTopLevel && options?.limits?.maxParseTime) {
|
|
1269
|
+
parseStartTime = Date.now();
|
|
1270
|
+
}
|
|
1271
|
+
try {
|
|
1272
|
+
return parseTagFromBuffer(buffer, 0, options);
|
|
1273
|
+
} finally {
|
|
1274
|
+
if (isTopLevel) {
|
|
1275
|
+
parseStartTime = 0;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1235
1278
|
};
|
|
1236
1279
|
const parse = parseTag;
|
|
1237
1280
|
return {
|
|
1238
1281
|
parseTag,
|
|
1239
|
-
parse
|
|
1282
|
+
parse,
|
|
1283
|
+
parseTagFromBuffer,
|
|
1284
|
+
validateTagSemantics,
|
|
1285
|
+
decodePlutusConstructor
|
|
1240
1286
|
};
|
|
1241
1287
|
}
|
|
1242
1288
|
|
|
@@ -1295,17 +1341,18 @@ var logger = {
|
|
|
1295
1341
|
|
|
1296
1342
|
// src/parser/composables/useCborCollection.ts
|
|
1297
1343
|
function useCborCollection() {
|
|
1298
|
-
const {
|
|
1344
|
+
const { parseIntegerFromBuffer } = useCborInteger();
|
|
1299
1345
|
const { parseByteString, parseTextString } = useCborString();
|
|
1300
|
-
const {
|
|
1301
|
-
const {
|
|
1302
|
-
|
|
1303
|
-
if (key instanceof Uint8Array) {
|
|
1304
|
-
return bytesToHex(key);
|
|
1305
|
-
}
|
|
1306
|
-
return String(key);
|
|
1307
|
-
};
|
|
1346
|
+
const { parseFromBuffer: parseFloatOrSimpleFromBuffer } = useCborFloat();
|
|
1347
|
+
const { parseTagFromBuffer } = useCborTag();
|
|
1348
|
+
let parseStartTime = 0;
|
|
1308
1349
|
const parseItem = (buffer, offset, options, depth = 0) => {
|
|
1350
|
+
if (parseStartTime > 0 && options?.limits?.maxParseTime) {
|
|
1351
|
+
const elapsed = Date.now() - parseStartTime;
|
|
1352
|
+
if (elapsed > options.limits.maxParseTime) {
|
|
1353
|
+
throw new Error(`Parse timeout: exceeded ${options.limits.maxParseTime}ms limit`);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1309
1356
|
if (offset >= buffer.length) {
|
|
1310
1357
|
throw new Error(`Unexpected end of buffer at offset ${offset}`);
|
|
1311
1358
|
}
|
|
@@ -1314,11 +1361,8 @@ function useCborCollection() {
|
|
|
1314
1361
|
switch (majorType) {
|
|
1315
1362
|
case 0:
|
|
1316
1363
|
// Unsigned integer
|
|
1317
|
-
case 1:
|
|
1318
|
-
|
|
1319
|
-
const result = parseInteger(intHex, options);
|
|
1320
|
-
return { value: result.value, bytesRead: result.bytesRead };
|
|
1321
|
-
}
|
|
1364
|
+
case 1:
|
|
1365
|
+
return parseIntegerFromBuffer(buffer, offset, options);
|
|
1322
1366
|
case 2:
|
|
1323
1367
|
return parseByteString(buffer, offset, options);
|
|
1324
1368
|
case 3:
|
|
@@ -1327,16 +1371,10 @@ function useCborCollection() {
|
|
|
1327
1371
|
return parseArrayFromBuffer(buffer, offset, options, depth);
|
|
1328
1372
|
case 5:
|
|
1329
1373
|
return parseMapFromBuffer(buffer, offset, options, depth);
|
|
1330
|
-
case 6:
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
return
|
|
1334
|
-
}
|
|
1335
|
-
case 7: {
|
|
1336
|
-
const floatHex = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1337
|
-
const result = parseFloatOrSimple(floatHex, options);
|
|
1338
|
-
return { value: result.value, bytesRead: result.bytesRead };
|
|
1339
|
-
}
|
|
1374
|
+
case 6:
|
|
1375
|
+
return parseTagFromBuffer(buffer, offset, options);
|
|
1376
|
+
case 7:
|
|
1377
|
+
return parseFloatOrSimpleFromBuffer(buffer, offset, options);
|
|
1340
1378
|
default:
|
|
1341
1379
|
throw new Error(`Unknown major type: ${majorType}`);
|
|
1342
1380
|
}
|
|
@@ -1478,7 +1516,7 @@ function useCborCollection() {
|
|
|
1478
1516
|
}
|
|
1479
1517
|
const valueResult = parseItem(buffer, currentOffset, options, depth + 1);
|
|
1480
1518
|
currentOffset += valueResult.bytesRead;
|
|
1481
|
-
const keyString =
|
|
1519
|
+
const keyString = serializeValueForComparison(keyResult.value);
|
|
1482
1520
|
if (seenKeys.has(keyString)) {
|
|
1483
1521
|
const mode = options?.dupMapKeyMode || "allow";
|
|
1484
1522
|
if (mode === "reject") {
|
|
@@ -1516,7 +1554,7 @@ function useCborCollection() {
|
|
|
1516
1554
|
}
|
|
1517
1555
|
const valueResult = parseItem(buffer, currentOffset, options, depth + 1);
|
|
1518
1556
|
currentOffset += valueResult.bytesRead;
|
|
1519
|
-
const keyString =
|
|
1557
|
+
const keyString = serializeValueForComparison(keyResult.value);
|
|
1520
1558
|
if (seenKeys.has(keyString)) {
|
|
1521
1559
|
const mode = options?.dupMapKeyMode || "allow";
|
|
1522
1560
|
if (mode === "reject") {
|
|
@@ -1532,14 +1570,15 @@ function useCborCollection() {
|
|
|
1532
1570
|
}
|
|
1533
1571
|
map[ALL_ENTRIES_SYMBOL] = allEntries;
|
|
1534
1572
|
if (options?.validateCanonical && keyBytes.length > 1) {
|
|
1573
|
+
const keyOrder = options?.mapKeyOrder ?? "length-first";
|
|
1535
1574
|
for (let i = 1; i < keyBytes.length; i++) {
|
|
1536
1575
|
const prevKey = keyBytes[i - 1];
|
|
1537
1576
|
const currKey = keyBytes[i];
|
|
1538
1577
|
if (prevKey && currKey) {
|
|
1539
|
-
const cmp =
|
|
1578
|
+
const cmp = compareMapKeys(prevKey, currKey, keyOrder);
|
|
1540
1579
|
if (cmp > 0) {
|
|
1541
1580
|
throw new Error(
|
|
1542
|
-
`Map keys are not in canonical order: key at index ${i} should come before key at index ${i - 1}`
|
|
1581
|
+
`Map keys are not in canonical order (${keyOrder}): key at index ${i} should come before key at index ${i - 1}`
|
|
1543
1582
|
);
|
|
1544
1583
|
}
|
|
1545
1584
|
if (cmp === 0) {
|
|
@@ -1556,12 +1595,26 @@ function useCborCollection() {
|
|
|
1556
1595
|
const parseArray = (hexString, options) => {
|
|
1557
1596
|
const cleanHex = hexString.replace(/\s+/g, "");
|
|
1558
1597
|
const buffer = hexToBytes(cleanHex);
|
|
1559
|
-
|
|
1598
|
+
if (options?.limits?.maxParseTime) {
|
|
1599
|
+
parseStartTime = Date.now();
|
|
1600
|
+
}
|
|
1601
|
+
try {
|
|
1602
|
+
return parseArrayFromBuffer(buffer, 0, options, 0);
|
|
1603
|
+
} finally {
|
|
1604
|
+
parseStartTime = 0;
|
|
1605
|
+
}
|
|
1560
1606
|
};
|
|
1561
1607
|
const parseMap = (hexString, options) => {
|
|
1562
1608
|
const cleanHex = hexString.replace(/\s+/g, "");
|
|
1563
1609
|
const buffer = hexToBytes(cleanHex);
|
|
1564
|
-
|
|
1610
|
+
if (options?.limits?.maxParseTime) {
|
|
1611
|
+
parseStartTime = Date.now();
|
|
1612
|
+
}
|
|
1613
|
+
try {
|
|
1614
|
+
return parseMapFromBuffer(buffer, 0, options, 0);
|
|
1615
|
+
} finally {
|
|
1616
|
+
parseStartTime = 0;
|
|
1617
|
+
}
|
|
1565
1618
|
};
|
|
1566
1619
|
return {
|
|
1567
1620
|
parseArray,
|
|
@@ -1585,6 +1638,9 @@ function useCborParser() {
|
|
|
1585
1638
|
validateSetUniqueness: options.validateSetUniqueness ?? (options.strict ? true : DEFAULT_OPTIONS.validateSetUniqueness),
|
|
1586
1639
|
validateTagSemantics: options.validateTagSemantics ?? (options.strict ? true : DEFAULT_OPTIONS.validateTagSemantics),
|
|
1587
1640
|
validatePlutusSemantics: options.validatePlutusSemantics ?? (options.strict ? true : DEFAULT_OPTIONS.validatePlutusSemantics),
|
|
1641
|
+
mapKeyOrder: options.mapKeyOrder ?? DEFAULT_OPTIONS.mapKeyOrder,
|
|
1642
|
+
// Strict mode rejects trailing data after the top-level item (well-formedness).
|
|
1643
|
+
allowTrailingData: options.allowTrailingData ?? (options.strict ? false : DEFAULT_OPTIONS.allowTrailingData),
|
|
1588
1644
|
limits: {
|
|
1589
1645
|
maxInputSize: options.limits?.maxInputSize ?? DEFAULT_LIMITS.maxInputSize,
|
|
1590
1646
|
maxOutputSize: options.limits?.maxOutputSize ?? DEFAULT_LIMITS.maxOutputSize,
|
|
@@ -1605,13 +1661,25 @@ function useCborParser() {
|
|
|
1605
1661
|
throw new Error(`Parse timeout: exceeded ${ctx.options.limits.maxParseTime}ms limit (elapsed: ${elapsed}ms)`);
|
|
1606
1662
|
}
|
|
1607
1663
|
};
|
|
1608
|
-
const { parseInteger } = useCborInteger();
|
|
1609
|
-
const { parseString } = useCborString();
|
|
1664
|
+
const { parseInteger, parseIntegerFromBuffer: integerFromBuffer } = useCborInteger();
|
|
1665
|
+
const { parseString, parseByteString: byteStringFromBuffer, parseTextString: textStringFromBuffer } = useCborString();
|
|
1610
1666
|
const { parseArray, parseMap } = useCborCollection();
|
|
1611
|
-
const { parseTag } = useCborTag();
|
|
1612
|
-
const { parse: parseFloatOrSimple } = useCborFloat();
|
|
1613
|
-
const parse = (
|
|
1614
|
-
const
|
|
1667
|
+
const { parseTag, validateTagSemantics, decodePlutusConstructor } = useCborTag();
|
|
1668
|
+
const { parse: parseFloatOrSimple, parseFromBuffer: floatOrSimpleFromBuffer } = useCborFloat();
|
|
1669
|
+
const parse = (input, options) => {
|
|
1670
|
+
const mergedOptions = mergeOptions(options);
|
|
1671
|
+
if (input instanceof Uint8Array) {
|
|
1672
|
+
if (input.length === 0) {
|
|
1673
|
+
throw new Error("Empty input");
|
|
1674
|
+
}
|
|
1675
|
+
if (mergedOptions.limits?.maxInputSize && input.length > mergedOptions.limits.maxInputSize) {
|
|
1676
|
+
throw new Error(`Input size ${input.length} bytes exceeds limit of ${mergedOptions.limits.maxInputSize} bytes`);
|
|
1677
|
+
}
|
|
1678
|
+
const bufResult = dispatchFromBuffer(input, 0, mergedOptions);
|
|
1679
|
+
checkTrailingData(bufResult.bytesRead, input.length, mergedOptions);
|
|
1680
|
+
return bufResult;
|
|
1681
|
+
}
|
|
1682
|
+
const cleanHex = input.replace(/\s+/g, "");
|
|
1615
1683
|
if (!cleanHex || cleanHex.length === 0) {
|
|
1616
1684
|
throw new Error("Empty hex string");
|
|
1617
1685
|
}
|
|
@@ -1621,7 +1689,6 @@ function useCborParser() {
|
|
|
1621
1689
|
if (!/^[0-9a-fA-F]+$/.test(cleanHex)) {
|
|
1622
1690
|
throw new Error(`Invalid hex character in: ${cleanHex}`);
|
|
1623
1691
|
}
|
|
1624
|
-
const mergedOptions = mergeOptions(options);
|
|
1625
1692
|
const inputSize = cleanHex.length / 2;
|
|
1626
1693
|
if (mergedOptions.limits?.maxInputSize && inputSize > mergedOptions.limits.maxInputSize) {
|
|
1627
1694
|
throw new Error(`Input size ${inputSize} bytes exceeds limit of ${mergedOptions.limits.maxInputSize} bytes`);
|
|
@@ -1629,47 +1696,77 @@ function useCborParser() {
|
|
|
1629
1696
|
const buffer = hexToBytes(cleanHex);
|
|
1630
1697
|
const initialByte = readByte(buffer, 0);
|
|
1631
1698
|
const { majorType } = extractCborHeader(initialByte);
|
|
1699
|
+
let result;
|
|
1632
1700
|
switch (majorType) {
|
|
1633
1701
|
case 0:
|
|
1634
1702
|
// Unsigned integer
|
|
1635
1703
|
case 1:
|
|
1636
|
-
|
|
1704
|
+
result = parseInteger(cleanHex, mergedOptions);
|
|
1705
|
+
break;
|
|
1637
1706
|
case 2:
|
|
1638
1707
|
// Byte string
|
|
1639
1708
|
case 3:
|
|
1640
|
-
|
|
1709
|
+
result = parseString(cleanHex, mergedOptions);
|
|
1710
|
+
break;
|
|
1641
1711
|
case 4:
|
|
1642
|
-
|
|
1712
|
+
result = parseArray(cleanHex, mergedOptions);
|
|
1713
|
+
break;
|
|
1643
1714
|
case 5:
|
|
1644
|
-
|
|
1715
|
+
result = parseMap(cleanHex, mergedOptions);
|
|
1716
|
+
break;
|
|
1645
1717
|
case 6:
|
|
1646
|
-
|
|
1718
|
+
result = parseTag(cleanHex, mergedOptions);
|
|
1719
|
+
break;
|
|
1647
1720
|
case 7:
|
|
1648
|
-
|
|
1721
|
+
result = parseFloatOrSimple(cleanHex, mergedOptions);
|
|
1722
|
+
break;
|
|
1649
1723
|
default:
|
|
1650
1724
|
throw new Error(`Unknown major type: ${majorType}`);
|
|
1651
1725
|
}
|
|
1726
|
+
checkTrailingData(result.bytesRead, buffer.length, mergedOptions);
|
|
1727
|
+
return result;
|
|
1652
1728
|
};
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
if (cleanHex.length % 2 !== 0) {
|
|
1659
|
-
throw new Error("Hex string must have even length");
|
|
1660
|
-
}
|
|
1661
|
-
if (!/^[0-9a-fA-F]+$/.test(cleanHex)) {
|
|
1662
|
-
throw new Error(`Invalid hex character in: ${cleanHex}`);
|
|
1729
|
+
const checkTrailingData = (bytesRead, totalLength, opts) => {
|
|
1730
|
+
if (!opts.allowTrailingData && bytesRead < totalLength) {
|
|
1731
|
+
throw new Error(
|
|
1732
|
+
`Trailing data: ${totalLength - bytesRead} byte(s) remain after the top-level CBOR item (bytesRead=${bytesRead}, length=${totalLength}). Use parseSequence to decode multiple items.`
|
|
1733
|
+
);
|
|
1663
1734
|
}
|
|
1735
|
+
};
|
|
1736
|
+
const parseWithSourceMap = (input, options) => {
|
|
1664
1737
|
const mergedOptions = mergeOptions(options);
|
|
1665
|
-
|
|
1666
|
-
if (
|
|
1667
|
-
|
|
1738
|
+
let buffer;
|
|
1739
|
+
if (input instanceof Uint8Array) {
|
|
1740
|
+
if (input.length === 0) {
|
|
1741
|
+
throw new Error("Empty input");
|
|
1742
|
+
}
|
|
1743
|
+
if (mergedOptions.limits?.maxInputSize && input.length > mergedOptions.limits.maxInputSize) {
|
|
1744
|
+
throw new Error(`Input size ${input.length} bytes exceeds limit of ${mergedOptions.limits.maxInputSize} bytes`);
|
|
1745
|
+
}
|
|
1746
|
+
buffer = input;
|
|
1747
|
+
} else {
|
|
1748
|
+
const cleanHex = input.replace(/\s+/g, "");
|
|
1749
|
+
if (!cleanHex || cleanHex.length === 0) {
|
|
1750
|
+
throw new Error("Empty hex string");
|
|
1751
|
+
}
|
|
1752
|
+
if (cleanHex.length % 2 !== 0) {
|
|
1753
|
+
throw new Error("Hex string must have even length");
|
|
1754
|
+
}
|
|
1755
|
+
if (!/^[0-9a-fA-F]+$/.test(cleanHex)) {
|
|
1756
|
+
throw new Error(`Invalid hex character in: ${cleanHex}`);
|
|
1757
|
+
}
|
|
1758
|
+
const inputSize = cleanHex.length / 2;
|
|
1759
|
+
if (mergedOptions.limits?.maxInputSize && inputSize > mergedOptions.limits.maxInputSize) {
|
|
1760
|
+
throw new Error(`Input size ${inputSize} bytes exceeds limit of ${mergedOptions.limits.maxInputSize} bytes`);
|
|
1761
|
+
}
|
|
1762
|
+
buffer = hexToBytes(cleanHex);
|
|
1668
1763
|
}
|
|
1669
|
-
const buffer = hexToBytes(cleanHex);
|
|
1670
1764
|
const sourceMap = [];
|
|
1671
1765
|
const ctx = {
|
|
1672
1766
|
buffer,
|
|
1767
|
+
offset: 0,
|
|
1768
|
+
sourceMap,
|
|
1769
|
+
currentDepth: 0,
|
|
1673
1770
|
startTime: Date.now(),
|
|
1674
1771
|
bytesAllocated: 0,
|
|
1675
1772
|
options: mergedOptions
|
|
@@ -1812,19 +1909,57 @@ function useCborParser() {
|
|
|
1812
1909
|
}
|
|
1813
1910
|
return result;
|
|
1814
1911
|
};
|
|
1912
|
+
const dispatchFromBuffer = (buffer, offset, options) => {
|
|
1913
|
+
const initialByte = readByte(buffer, offset);
|
|
1914
|
+
const { majorType } = extractCborHeader(initialByte);
|
|
1915
|
+
switch (majorType) {
|
|
1916
|
+
case 0:
|
|
1917
|
+
// Unsigned integer
|
|
1918
|
+
case 1:
|
|
1919
|
+
return integerFromBuffer(buffer, offset, options);
|
|
1920
|
+
case 2:
|
|
1921
|
+
return byteStringFromBuffer(buffer, offset, options);
|
|
1922
|
+
case 3:
|
|
1923
|
+
return textStringFromBuffer(buffer, offset, options);
|
|
1924
|
+
case 4: {
|
|
1925
|
+
const hexString = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1926
|
+
return parseArray(hexString, options);
|
|
1927
|
+
}
|
|
1928
|
+
case 5: {
|
|
1929
|
+
const hexString = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1930
|
+
return parseMap(hexString, options);
|
|
1931
|
+
}
|
|
1932
|
+
case 6: {
|
|
1933
|
+
const hexString = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1934
|
+
return parseTag(hexString, options);
|
|
1935
|
+
}
|
|
1936
|
+
case 7:
|
|
1937
|
+
return floatOrSimpleFromBuffer(buffer, offset, options);
|
|
1938
|
+
default:
|
|
1939
|
+
throw new Error(`Unknown major type: ${majorType}`);
|
|
1940
|
+
}
|
|
1941
|
+
};
|
|
1815
1942
|
const parseIntegerFromBuffer = (buffer, offset, options) => {
|
|
1816
|
-
|
|
1817
|
-
return parseInteger(hexString, options);
|
|
1943
|
+
return integerFromBuffer(buffer, offset, options);
|
|
1818
1944
|
};
|
|
1819
1945
|
const parseStringFromBuffer = (buffer, offset, options) => {
|
|
1820
|
-
const
|
|
1821
|
-
|
|
1946
|
+
const initialByte = readByte(buffer, offset);
|
|
1947
|
+
const { majorType } = extractCborHeader(initialByte);
|
|
1948
|
+
if (majorType === 2) {
|
|
1949
|
+
return byteStringFromBuffer(buffer, offset, options);
|
|
1950
|
+
}
|
|
1951
|
+
return textStringFromBuffer(buffer, offset, options);
|
|
1822
1952
|
};
|
|
1823
1953
|
const parseFloatFromBuffer = (buffer, offset, options) => {
|
|
1824
|
-
|
|
1825
|
-
return parseFloatOrSimple(hexString, options);
|
|
1954
|
+
return floatOrSimpleFromBuffer(buffer, offset, options);
|
|
1826
1955
|
};
|
|
1827
1956
|
const parseArrayWithMap = (ctx, offset, path, sourceMap) => {
|
|
1957
|
+
const previousDepth = ctx.currentDepth ?? 0;
|
|
1958
|
+
const maxDepth = ctx.options?.limits?.maxDepth;
|
|
1959
|
+
if (maxDepth !== void 0 && previousDepth >= maxDepth) {
|
|
1960
|
+
throw new Error(`Maximum nesting depth ${maxDepth} exceeded`);
|
|
1961
|
+
}
|
|
1962
|
+
ctx.currentDepth = previousDepth + 1;
|
|
1828
1963
|
const startOffset = offset;
|
|
1829
1964
|
const initialByte = readByte(ctx.buffer, offset);
|
|
1830
1965
|
const { additionalInfo } = extractCborHeader(initialByte);
|
|
@@ -1851,6 +1986,10 @@ function useCborParser() {
|
|
|
1851
1986
|
length = Number(bigLength);
|
|
1852
1987
|
currentOffset += 8;
|
|
1853
1988
|
} else if (additionalInfo === 31) {
|
|
1989
|
+
const isIndefiniteAllowed = ctx.options?.allowIndefinite ?? !(ctx.options?.validateCanonical || ctx.options?.strict);
|
|
1990
|
+
if (!isIndefiniteAllowed) {
|
|
1991
|
+
throw new Error("Indefinite-length encoding is not allowed (strict/canonical mode)");
|
|
1992
|
+
}
|
|
1854
1993
|
isIndefinite = true;
|
|
1855
1994
|
length = 0;
|
|
1856
1995
|
} else {
|
|
@@ -1867,49 +2006,70 @@ function useCborParser() {
|
|
|
1867
2006
|
isHeader: true,
|
|
1868
2007
|
headerEnd
|
|
1869
2008
|
});
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
currentOffset
|
|
1877
|
-
|
|
2009
|
+
try {
|
|
2010
|
+
const childPaths = [];
|
|
2011
|
+
if (isIndefinite) {
|
|
2012
|
+
let index = 0;
|
|
2013
|
+
let foundBreak = false;
|
|
2014
|
+
while (currentOffset < ctx.buffer.length) {
|
|
2015
|
+
const nextByte = readByte(ctx.buffer, currentOffset);
|
|
2016
|
+
if (nextByte === 255) {
|
|
2017
|
+
currentOffset++;
|
|
2018
|
+
foundBreak = true;
|
|
2019
|
+
break;
|
|
2020
|
+
}
|
|
2021
|
+
if (ctx.options?.limits?.maxArrayLength && index >= ctx.options.limits.maxArrayLength) {
|
|
2022
|
+
throw new Error(`Array length exceeds limit of ${ctx.options.limits.maxArrayLength}`);
|
|
2023
|
+
}
|
|
2024
|
+
const elementPath = `${path}[${index}]`;
|
|
2025
|
+
childPaths.push(elementPath);
|
|
2026
|
+
const elementResult = parseValueWithMap(ctx, currentOffset, elementPath, sourceMap);
|
|
2027
|
+
items.push(elementResult.value);
|
|
2028
|
+
currentOffset += elementResult.bytesRead;
|
|
2029
|
+
const elementEntry = sourceMap.find((e) => e.path === elementPath);
|
|
2030
|
+
if (elementEntry) {
|
|
2031
|
+
elementEntry.parent = path;
|
|
2032
|
+
}
|
|
2033
|
+
index++;
|
|
1878
2034
|
}
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
const elementResult = parseValueWithMap(ctx, currentOffset, elementPath, sourceMap);
|
|
1882
|
-
items.push(elementResult.value);
|
|
1883
|
-
currentOffset += elementResult.bytesRead;
|
|
1884
|
-
const elementEntry = sourceMap.find((e) => e.path === elementPath);
|
|
1885
|
-
if (elementEntry) {
|
|
1886
|
-
elementEntry.parent = path;
|
|
2035
|
+
if (!foundBreak) {
|
|
2036
|
+
throw new Error("Indefinite-length array missing break code (0xFF)");
|
|
1887
2037
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
elementEntry
|
|
2038
|
+
} else {
|
|
2039
|
+
if (ctx.options?.limits?.maxArrayLength && length > ctx.options.limits.maxArrayLength) {
|
|
2040
|
+
throw new Error(`Array length ${length} exceeds limit of ${ctx.options.limits.maxArrayLength}`);
|
|
2041
|
+
}
|
|
2042
|
+
for (let i = 0; i < length; i++) {
|
|
2043
|
+
const elementPath = `${path}[${i}]`;
|
|
2044
|
+
childPaths.push(elementPath);
|
|
2045
|
+
const elementResult = parseValueWithMap(ctx, currentOffset, elementPath, sourceMap);
|
|
2046
|
+
items.push(elementResult.value);
|
|
2047
|
+
currentOffset += elementResult.bytesRead;
|
|
2048
|
+
const elementEntry = sourceMap.find((e) => e.path === elementPath);
|
|
2049
|
+
if (elementEntry) {
|
|
2050
|
+
elementEntry.parent = path;
|
|
2051
|
+
}
|
|
1900
2052
|
}
|
|
1901
2053
|
}
|
|
2054
|
+
const bytesRead = currentOffset - offset;
|
|
2055
|
+
if (childPaths.length > 0 && sourceMap[arrayEntryIndex]) {
|
|
2056
|
+
sourceMap[arrayEntryIndex].children = childPaths;
|
|
2057
|
+
}
|
|
2058
|
+
return {
|
|
2059
|
+
value: items,
|
|
2060
|
+
bytesRead
|
|
2061
|
+
};
|
|
2062
|
+
} finally {
|
|
2063
|
+
ctx.currentDepth = previousDepth;
|
|
1902
2064
|
}
|
|
1903
|
-
const bytesRead = currentOffset - offset;
|
|
1904
|
-
if (childPaths.length > 0 && sourceMap[arrayEntryIndex]) {
|
|
1905
|
-
sourceMap[arrayEntryIndex].children = childPaths;
|
|
1906
|
-
}
|
|
1907
|
-
return {
|
|
1908
|
-
value: items,
|
|
1909
|
-
bytesRead
|
|
1910
|
-
};
|
|
1911
2065
|
};
|
|
1912
2066
|
const parseMapWithMap = (ctx, offset, path, sourceMap) => {
|
|
2067
|
+
const previousDepth = ctx.currentDepth ?? 0;
|
|
2068
|
+
const maxDepth = ctx.options?.limits?.maxDepth;
|
|
2069
|
+
if (maxDepth !== void 0 && previousDepth >= maxDepth) {
|
|
2070
|
+
throw new Error(`Maximum nesting depth ${maxDepth} exceeded`);
|
|
2071
|
+
}
|
|
2072
|
+
ctx.currentDepth = previousDepth + 1;
|
|
1913
2073
|
const startOffset = offset;
|
|
1914
2074
|
const initialByte = readByte(ctx.buffer, offset);
|
|
1915
2075
|
const { additionalInfo } = extractCborHeader(initialByte);
|
|
@@ -1936,6 +2096,10 @@ function useCborParser() {
|
|
|
1936
2096
|
length = Number(bigLength);
|
|
1937
2097
|
currentOffset += 8;
|
|
1938
2098
|
} else if (additionalInfo === 31) {
|
|
2099
|
+
const isIndefiniteAllowed = ctx.options?.allowIndefinite ?? !(ctx.options?.validateCanonical || ctx.options?.strict);
|
|
2100
|
+
if (!isIndefiniteAllowed) {
|
|
2101
|
+
throw new Error("Indefinite-length encoding is not allowed (strict/canonical mode)");
|
|
2102
|
+
}
|
|
1939
2103
|
isIndefinite = true;
|
|
1940
2104
|
length = 0;
|
|
1941
2105
|
} else {
|
|
@@ -1952,72 +2116,91 @@ function useCborParser() {
|
|
|
1952
2116
|
isHeader: true,
|
|
1953
2117
|
headerEnd
|
|
1954
2118
|
});
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2119
|
+
try {
|
|
2120
|
+
const childPaths = [];
|
|
2121
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
2122
|
+
if (isIndefinite) {
|
|
2123
|
+
let count = 0;
|
|
2124
|
+
let foundBreak = false;
|
|
2125
|
+
while (currentOffset < ctx.buffer.length) {
|
|
2126
|
+
const nextByte = readByte(ctx.buffer, currentOffset);
|
|
2127
|
+
if (nextByte === 255) {
|
|
2128
|
+
currentOffset++;
|
|
2129
|
+
foundBreak = true;
|
|
2130
|
+
break;
|
|
2131
|
+
}
|
|
2132
|
+
if (ctx.options?.limits?.maxMapSize && count >= ctx.options.limits.maxMapSize) {
|
|
2133
|
+
throw new Error(`Map size exceeds limit of ${ctx.options.limits.maxMapSize}`);
|
|
2134
|
+
}
|
|
2135
|
+
const keyPath = `${path}${path ? "." : ""}#key`;
|
|
2136
|
+
const keyResult = parseValueWithMap(ctx, currentOffset, keyPath, sourceMap);
|
|
2137
|
+
currentOffset += keyResult.bytesRead;
|
|
2138
|
+
const keyForDupCheck = serializeValueForComparison(keyResult.value);
|
|
2139
|
+
const keyString = keyResult.value instanceof Uint8Array ? Array.from(keyResult.value).map((b) => b.toString(16).padStart(2, "0")).join("") : String(keyResult.value);
|
|
2140
|
+
if (seenKeys.has(keyForDupCheck)) {
|
|
2141
|
+
const mode = ctx.options?.dupMapKeyMode || "allow";
|
|
2142
|
+
if (mode === "reject") {
|
|
2143
|
+
throw new Error(`Duplicate map key detected: ${keyString} at offset ${currentOffset}`);
|
|
2144
|
+
} else if (mode === "warn") {
|
|
2145
|
+
logger.warn(`Duplicate map key detected: ${keyString} at offset ${currentOffset}`);
|
|
2146
|
+
}
|
|
1974
2147
|
}
|
|
2148
|
+
seenKeys.add(keyForDupCheck);
|
|
2149
|
+
const valuePath = path ? `${path}.${keyString}` : `.${keyString}`;
|
|
2150
|
+
childPaths.push(valuePath);
|
|
2151
|
+
const valueResult = parseValueWithMap(ctx, currentOffset, valuePath, sourceMap);
|
|
2152
|
+
map.set(keyResult.value, valueResult.value);
|
|
2153
|
+
currentOffset += valueResult.bytesRead;
|
|
2154
|
+
const valueEntry = sourceMap.find((e) => e.path === valuePath);
|
|
2155
|
+
if (valueEntry) {
|
|
2156
|
+
valueEntry.parent = path;
|
|
2157
|
+
}
|
|
2158
|
+
count++;
|
|
1975
2159
|
}
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
childPaths.push(valuePath);
|
|
1979
|
-
const valueResult = parseValueWithMap(ctx, currentOffset, valuePath, sourceMap);
|
|
1980
|
-
map.set(keyResult.value, valueResult.value);
|
|
1981
|
-
currentOffset += valueResult.bytesRead;
|
|
1982
|
-
const valueEntry = sourceMap.find((e) => e.path === valuePath);
|
|
1983
|
-
if (valueEntry) {
|
|
1984
|
-
valueEntry.parent = path;
|
|
2160
|
+
if (!foundBreak) {
|
|
2161
|
+
throw new Error("Indefinite-length map missing break code (0xFF)");
|
|
1985
2162
|
}
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
const
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
2163
|
+
} else {
|
|
2164
|
+
if (ctx.options?.limits?.maxMapSize && length > ctx.options.limits.maxMapSize) {
|
|
2165
|
+
throw new Error(`Map size ${length} exceeds limit of ${ctx.options.limits.maxMapSize}`);
|
|
2166
|
+
}
|
|
2167
|
+
for (let i = 0; i < length; i++) {
|
|
2168
|
+
const keyPath = `${path}${path ? "." : ""}#key${i}`;
|
|
2169
|
+
const keyResult = parseValueWithMap(ctx, currentOffset, keyPath, sourceMap);
|
|
2170
|
+
currentOffset += keyResult.bytesRead;
|
|
2171
|
+
const keyForDupCheck = serializeValueForComparison(keyResult.value);
|
|
2172
|
+
const keyString = keyResult.value instanceof Uint8Array ? Array.from(keyResult.value).map((b) => b.toString(16).padStart(2, "0")).join("") : String(keyResult.value);
|
|
2173
|
+
if (seenKeys.has(keyForDupCheck)) {
|
|
2174
|
+
const mode = ctx.options?.dupMapKeyMode || "allow";
|
|
2175
|
+
if (mode === "reject") {
|
|
2176
|
+
throw new Error(`Duplicate map key detected: ${keyString} at offset ${currentOffset}`);
|
|
2177
|
+
} else if (mode === "warn") {
|
|
2178
|
+
logger.warn(`Duplicate map key detected: ${keyString} at offset ${currentOffset}`);
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
seenKeys.add(keyForDupCheck);
|
|
2182
|
+
const valuePath = path ? `${path}.${keyString}` : `.${keyString}`;
|
|
2183
|
+
childPaths.push(valuePath);
|
|
2184
|
+
const valueResult = parseValueWithMap(ctx, currentOffset, valuePath, sourceMap);
|
|
2185
|
+
map.set(keyResult.value, valueResult.value);
|
|
2186
|
+
currentOffset += valueResult.bytesRead;
|
|
2187
|
+
const valueEntry = sourceMap.find((e) => e.path === valuePath);
|
|
2188
|
+
if (valueEntry) {
|
|
2189
|
+
valueEntry.parent = path;
|
|
1999
2190
|
}
|
|
2000
|
-
}
|
|
2001
|
-
seenKeys.add(keyString);
|
|
2002
|
-
const valuePath = path ? `${path}.${keyString}` : `.${keyString}`;
|
|
2003
|
-
childPaths.push(valuePath);
|
|
2004
|
-
const valueResult = parseValueWithMap(ctx, currentOffset, valuePath, sourceMap);
|
|
2005
|
-
map.set(keyResult.value, valueResult.value);
|
|
2006
|
-
currentOffset += valueResult.bytesRead;
|
|
2007
|
-
const valueEntry = sourceMap.find((e) => e.path === valuePath);
|
|
2008
|
-
if (valueEntry) {
|
|
2009
|
-
valueEntry.parent = path;
|
|
2010
2191
|
}
|
|
2011
2192
|
}
|
|
2193
|
+
const bytesRead = currentOffset - offset;
|
|
2194
|
+
if (sourceMap[mapEntryIndex]) {
|
|
2195
|
+
sourceMap[mapEntryIndex].children = childPaths;
|
|
2196
|
+
}
|
|
2197
|
+
return {
|
|
2198
|
+
value: map,
|
|
2199
|
+
bytesRead
|
|
2200
|
+
};
|
|
2201
|
+
} finally {
|
|
2202
|
+
ctx.currentDepth = previousDepth;
|
|
2012
2203
|
}
|
|
2013
|
-
const bytesRead = currentOffset - offset;
|
|
2014
|
-
if (sourceMap[mapEntryIndex]) {
|
|
2015
|
-
sourceMap[mapEntryIndex].children = childPaths;
|
|
2016
|
-
}
|
|
2017
|
-
return {
|
|
2018
|
-
value: map,
|
|
2019
|
-
bytesRead
|
|
2020
|
-
};
|
|
2021
2204
|
};
|
|
2022
2205
|
const parseTagNumberHelper = (buffer, offset, ai) => {
|
|
2023
2206
|
if (ai < 24) {
|
|
@@ -2045,6 +2228,12 @@ function useCborParser() {
|
|
|
2045
2228
|
}
|
|
2046
2229
|
};
|
|
2047
2230
|
const parseTagWithMap = (ctx, offset, path, sourceMap) => {
|
|
2231
|
+
const previousTagDepth = ctx.currentTagDepth ?? 0;
|
|
2232
|
+
const maxTagDepth = ctx.options?.limits?.maxTagDepth ?? DEFAULT_LIMITS.maxTagDepth;
|
|
2233
|
+
if (previousTagDepth >= maxTagDepth) {
|
|
2234
|
+
throw new Error(`Tag nesting depth ${previousTagDepth} exceeds limit of ${maxTagDepth}`);
|
|
2235
|
+
}
|
|
2236
|
+
ctx.currentTagDepth = previousTagDepth + 1;
|
|
2048
2237
|
const startOffset = offset;
|
|
2049
2238
|
const initialByte = readByte(ctx.buffer, offset);
|
|
2050
2239
|
const { additionalInfo } = extractCborHeader(initialByte);
|
|
@@ -2053,6 +2242,9 @@ function useCborParser() {
|
|
|
2053
2242
|
offset + 1,
|
|
2054
2243
|
additionalInfo
|
|
2055
2244
|
);
|
|
2245
|
+
if (ctx.options?.validateCanonical) {
|
|
2246
|
+
validateCanonicalInteger(tagNumber, additionalInfo);
|
|
2247
|
+
}
|
|
2056
2248
|
let currentOffset = offset + 1 + bytesConsumed;
|
|
2057
2249
|
const headerEnd = currentOffset;
|
|
2058
2250
|
const tagEntryIndex = sourceMap.length;
|
|
@@ -2076,10 +2268,30 @@ function useCborParser() {
|
|
|
2076
2268
|
if (valueEntry) {
|
|
2077
2269
|
valueEntry.parent = path;
|
|
2078
2270
|
}
|
|
2079
|
-
|
|
2080
|
-
|
|
2271
|
+
let finalValue = valueResult.value;
|
|
2272
|
+
if ((tagNumber === 2 || tagNumber === 3) && finalValue instanceof Uint8Array) {
|
|
2273
|
+
const maxBignumBytes = ctx.options?.limits?.maxBignumBytes ?? DEFAULT_LIMITS.maxBignumBytes;
|
|
2274
|
+
if (finalValue.length > maxBignumBytes) {
|
|
2275
|
+
throw new Error(
|
|
2276
|
+
`Bignum (tag ${tagNumber}) size ${finalValue.length} bytes exceeds limit of ${maxBignumBytes} bytes`
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
let bigintValue = 0n;
|
|
2280
|
+
for (let i = 0; i < finalValue.length; i++) {
|
|
2281
|
+
bigintValue = bigintValue << 8n | BigInt(finalValue[i]);
|
|
2282
|
+
}
|
|
2283
|
+
finalValue = tagNumber === 2 ? bigintValue : -1n - bigintValue;
|
|
2284
|
+
}
|
|
2285
|
+
validateTagSemantics(tagNumber, finalValue, ctx.options);
|
|
2286
|
+
const plutusConstr = decodePlutusConstructor(tagNumber, finalValue);
|
|
2287
|
+
const taggedValue = {
|
|
2288
|
+
tag: tagNumber,
|
|
2289
|
+
value: finalValue,
|
|
2290
|
+
...plutusConstr && { plutus: plutusConstr }
|
|
2291
|
+
};
|
|
2292
|
+
ctx.currentTagDepth = previousTagDepth;
|
|
2081
2293
|
return {
|
|
2082
|
-
value:
|
|
2294
|
+
value: taggedValue,
|
|
2083
2295
|
bytesRead: currentOffset - startOffset
|
|
2084
2296
|
};
|
|
2085
2297
|
};
|
|
@@ -2094,28 +2306,42 @@ function useCborParser() {
|
|
|
2094
2306
|
if (ai < 20) return `Simple Value ${ai}`;
|
|
2095
2307
|
return "Simple Value";
|
|
2096
2308
|
};
|
|
2097
|
-
const parseSequence = (
|
|
2098
|
-
const cleanHex = hexString.replace(/\s+/g, "");
|
|
2099
|
-
if (!cleanHex || cleanHex.length === 0) {
|
|
2100
|
-
return [];
|
|
2101
|
-
}
|
|
2102
|
-
if (cleanHex.length % 2 !== 0) {
|
|
2103
|
-
throw new Error("Hex string must have even length");
|
|
2104
|
-
}
|
|
2105
|
-
if (!/^[0-9a-fA-F]+$/.test(cleanHex)) {
|
|
2106
|
-
throw new Error(`Invalid hex character in: ${cleanHex}`);
|
|
2107
|
-
}
|
|
2309
|
+
const parseSequence = (input, options) => {
|
|
2108
2310
|
const mergedOptions = mergeOptions(options);
|
|
2109
|
-
|
|
2311
|
+
let buffer;
|
|
2312
|
+
if (input instanceof Uint8Array) {
|
|
2313
|
+
if (input.length === 0) {
|
|
2314
|
+
return [];
|
|
2315
|
+
}
|
|
2316
|
+
buffer = input;
|
|
2317
|
+
} else {
|
|
2318
|
+
const cleanHex = input.replace(/\s+/g, "");
|
|
2319
|
+
if (!cleanHex || cleanHex.length === 0) {
|
|
2320
|
+
return [];
|
|
2321
|
+
}
|
|
2322
|
+
if (cleanHex.length % 2 !== 0) {
|
|
2323
|
+
throw new Error("Hex string must have even length");
|
|
2324
|
+
}
|
|
2325
|
+
if (!/^[0-9a-fA-F]+$/.test(cleanHex)) {
|
|
2326
|
+
throw new Error(`Invalid hex character in: ${cleanHex}`);
|
|
2327
|
+
}
|
|
2328
|
+
buffer = hexToBytes(cleanHex);
|
|
2329
|
+
}
|
|
2110
2330
|
const results = [];
|
|
2111
2331
|
let offset = 0;
|
|
2332
|
+
const sequenceStartTime = mergedOptions.limits?.maxParseTime ? Date.now() : 0;
|
|
2112
2333
|
while (offset < buffer.length) {
|
|
2334
|
+
if (sequenceStartTime > 0 && mergedOptions.limits?.maxParseTime) {
|
|
2335
|
+
const elapsed = Date.now() - sequenceStartTime;
|
|
2336
|
+
if (elapsed > mergedOptions.limits.maxParseTime) {
|
|
2337
|
+
throw new Error(`Parse timeout: exceeded ${mergedOptions.limits.maxParseTime}ms limit`);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2113
2340
|
const byte = readByte(buffer, offset);
|
|
2114
2341
|
if (byte === 255) {
|
|
2115
2342
|
throw new Error(`Unexpected break code (0xff) at offset ${offset} - not inside indefinite-length item`);
|
|
2116
2343
|
}
|
|
2117
|
-
const
|
|
2118
|
-
const result = parse(remainingHex, mergedOptions);
|
|
2344
|
+
const result = dispatchFromBuffer(buffer, offset, mergedOptions);
|
|
2119
2345
|
results.push(result.value);
|
|
2120
2346
|
offset += result.bytesRead;
|
|
2121
2347
|
}
|
|
@@ -2142,8 +2368,7 @@ function useCborParser() {
|
|
|
2142
2368
|
if (byte === 255) {
|
|
2143
2369
|
throw new Error(`Unexpected break code (0xff) at offset ${offset}`);
|
|
2144
2370
|
}
|
|
2145
|
-
const
|
|
2146
|
-
const result = parseWithSourceMap(remainingHex, mergedOptions);
|
|
2371
|
+
const result = parseWithSourceMap(buffer.subarray(offset), mergedOptions);
|
|
2147
2372
|
const adjustedSourceMap = result.sourceMap.map((entry) => ({
|
|
2148
2373
|
...entry,
|
|
2149
2374
|
start: entry.start + offset,
|
|
@@ -2164,5 +2389,5 @@ function useCborParser() {
|
|
|
2164
2389
|
}
|
|
2165
2390
|
|
|
2166
2391
|
export { bytesToHex, extractCborHeader, hexToBytes, readBigUint, readByte, readUint, useCborCollection, useCborFloat, useCborInteger, useCborParser, useCborString, useCborTag, validateUtf8Strict };
|
|
2167
|
-
//# sourceMappingURL=chunk-
|
|
2168
|
-
//# sourceMappingURL=chunk-
|
|
2392
|
+
//# sourceMappingURL=chunk-LWNWC2O7.js.map
|
|
2393
|
+
//# sourceMappingURL=chunk-LWNWC2O7.js.map
|