Files
codeql-action/node_modules/cbor/lib/decoder.js
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

681 lines
21 KiB
JavaScript
Raw Normal View History

2025-07-15 07:28:14 +00:00
'use strict';
const BinaryParseStream = require('../vendor/binary-parse-stream');
const Tagged = require('./tagged');
const Simple = require('./simple');
const utils = require('./utils');
const NoFilter = require('nofilter');
const constants = require('./constants');
const {MT, NUMBYTES, SYMS, BI} = constants;
const {Buffer} = require('buffer');
const COUNT = Symbol('count');
const MAJOR = Symbol('major type');
const ERROR = Symbol('error');
const NOT_FOUND = Symbol('not found');
2022-02-01 18:01:11 +00:00
function parentArray(parent, typ, count) {
2025-07-15 07:28:14 +00:00
const a = [];
2022-02-01 18:01:11 +00:00
2025-07-15 07:28:14 +00:00
a[COUNT] = count;
a[SYMS.PARENT] = parent;
a[MAJOR] = typ;
return a;
2022-02-01 18:01:11 +00:00
}
function parentBufferStream(parent, typ) {
2025-07-15 07:28:14 +00:00
const b = new NoFilter();
2022-02-01 18:01:11 +00:00
2025-07-15 07:28:14 +00:00
b[COUNT] = -1;
b[SYMS.PARENT] = parent;
b[MAJOR] = typ;
return b;
2022-02-01 18:01:11 +00:00
}
class UnexpectedDataError extends Error {
constructor(byte, value) {
2025-07-15 07:28:14 +00:00
super(`Unexpected data: 0x${byte.toString(16)}`);
this.name = 'UnexpectedDataError';
this.byte = byte;
this.value = value;
2022-02-01 18:01:11 +00:00
}
}
/**
* Things that can act as inputs, from which a NoFilter can be created.
*
2025-07-15 07:28:14 +00:00
* @typedef {string|Buffer|ArrayBuffer|ArrayBufferView
* |DataView|import('stream').Readable} BufferLike
2022-02-01 18:01:11 +00:00
*/
/**
* @typedef ExtendedResults
* @property {any} value The value that was found.
* @property {number} length The number of bytes of the original input that
* were read.
* @property {Buffer} bytes The bytes of the original input that were used
* to produce the value.
* @property {Buffer} [unused] The bytes that were left over from the original
* input. This property only exists if {@linkcode Decoder.decodeFirst} or
* {@linkcode Decoder.decodeFirstSync} was called.
*/
/**
* @typedef DecoderOptions
* @property {number} [max_depth=-1] The maximum depth to parse.
* Use -1 for "until you run out of memory". Set this to a finite
* positive number for un-trusted inputs. Most standard inputs won't nest
* more than 100 or so levels; I've tested into the millions before
* running out of memory.
* @property {Tagged.TagMap} [tags] Mapping from tag number to function(v),
* where v is the decoded value that comes after the tag, and where the
* function returns the correctly-created value for that tag.
2025-07-15 07:28:14 +00:00
* @property {boolean} [preferMap=false] If true, prefer to generate Map
* instances to plain objects, even if there are no entries in the map
* or if all of the keys are strings.
2022-02-01 18:01:11 +00:00
* @property {boolean} [preferWeb=false] If true, prefer Uint8Arrays to
* be generated instead of node Buffers. This might turn on some more
* changes in the future, so forward-compatibility is not guaranteed yet.
* @property {BufferEncoding} [encoding='hex'] The encoding of the input.
* Ignored if input is a Buffer.
* @property {boolean} [required=false] Should an error be thrown when no
* data is in the input?
* @property {boolean} [extendedResults=false] If true, emit extended
* results, which will be an object with shape {@link ExtendedResults}.
* The value will already have been null-checked.
* @property {boolean} [preventDuplicateKeys=false] If true, error is
* thrown if a map has duplicate keys.
*/
/**
* @callback decodeCallback
* @param {Error} [error] If one was generated.
* @param {any} [value] The decoded value.
* @returns {void}
*/
/**
* @param {DecoderOptions|decodeCallback|string} opts Options,
* the callback, or input incoding.
* @param {decodeCallback} [cb] Called on completion.
* @returns {{options: DecoderOptions, cb: decodeCallback}} Normalized.
* @throws {TypeError} On unknown option type.
* @private
*/
function normalizeOptions(opts, cb) {
switch (typeof opts) {
case 'function':
2025-07-15 07:28:14 +00:00
return {options: {}, cb: /** @type {decodeCallback} */ (opts)};
2022-02-01 18:01:11 +00:00
case 'string':
2025-07-15 07:28:14 +00:00
return {options: {encoding: /** @type {BufferEncoding} */ (opts)}, cb};
2022-02-01 18:01:11 +00:00
case 'object':
2025-07-15 07:28:14 +00:00
return {options: opts || {}, cb};
2022-02-01 18:01:11 +00:00
default:
2025-07-15 07:28:14 +00:00
throw new TypeError('Unknown option type');
2022-02-01 18:01:11 +00:00
}
}
/**
* Decode a stream of CBOR bytes by transforming them into equivalent
* JavaScript data. Because of the limitations of Node object streams,
* special symbols are emitted instead of NULL or UNDEFINED. Fix those
* up by calling {@link Decoder.nullcheck}.
*
* @extends BinaryParseStream
*/
class Decoder extends BinaryParseStream {
/**
* Create a parsing stream.
*
* @param {DecoderOptions} [options={}] Options.
*/
constructor(options = {}) {
const {
tags = {},
max_depth = -1,
2025-07-15 07:28:14 +00:00
preferMap = false,
2022-02-01 18:01:11 +00:00
preferWeb = false,
required = false,
encoding = 'hex',
extendedResults = false,
preventDuplicateKeys = false,
...superOpts
2025-07-15 07:28:14 +00:00
} = options;
2022-02-01 18:01:11 +00:00
2025-07-15 07:28:14 +00:00
super({defaultEncoding: encoding, ...superOpts});
2022-02-01 18:01:11 +00:00
2025-07-15 07:28:14 +00:00
this.running = true;
this.max_depth = max_depth;
this.tags = tags;
this.preferMap = preferMap;
this.preferWeb = preferWeb;
this.extendedResults = extendedResults;
this.required = required;
this.preventDuplicateKeys = preventDuplicateKeys;
2022-02-01 18:01:11 +00:00
if (extendedResults) {
2025-07-15 07:28:14 +00:00
this.bs.on('read', this._onRead.bind(this));
this.valueBytes = /** @type {NoFilter} */ (new NoFilter());
2022-02-01 18:01:11 +00:00
}
}
/**
* Check the given value for a symbol encoding a NULL or UNDEFINED value in
* the CBOR stream.
*
* @param {any} val The value to check.
* @returns {any} The corrected value.
* @throws {Error} Nothing was found.
2025-07-15 07:28:14 +00:00
* @static
2022-02-01 18:01:11 +00:00
* @example
* myDecoder.on('data', val => {
* val = Decoder.nullcheck(val)
* // ...
* })
*/
static nullcheck(val) {
switch (val) {
case SYMS.NULL:
2025-07-15 07:28:14 +00:00
return null;
2022-02-01 18:01:11 +00:00
case SYMS.UNDEFINED:
2025-07-15 07:28:14 +00:00
return undefined;
2022-02-01 18:01:11 +00:00
// Leaving this in for now as belt-and-suspenders, but I'm pretty sure
// it can't happen.
/* istanbul ignore next */
case NOT_FOUND:
/* istanbul ignore next */
2025-07-15 07:28:14 +00:00
throw new Error('Value not found');
2022-02-01 18:01:11 +00:00
default:
2025-07-15 07:28:14 +00:00
return val;
2022-02-01 18:01:11 +00:00
}
}
/**
* Decode the first CBOR item in the input, synchronously. This will throw
* an exception if the input is not valid CBOR, or if there are more bytes
* left over at the end (if options.extendedResults is not true).
*
* @param {BufferLike} input If a Readable stream, must have
* received the `readable` event already, or you will get an error
* claiming "Insufficient data".
* @param {DecoderOptions|string} [options={}] Options or encoding for input.
* @returns {ExtendedResults|any} The decoded value.
* @throws {UnexpectedDataError} Data is left over after decoding.
* @throws {Error} Insufficient data.
*/
static decodeFirstSync(input, options = {}) {
if (input == null) {
2025-07-15 07:28:14 +00:00
throw new TypeError('input required');
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
({options} = normalizeOptions(options));
const {encoding = 'hex', ...opts} = options;
const c = new Decoder(opts);
const s = utils.guessEncoding(input, encoding);
2022-02-01 18:01:11 +00:00
// For/of doesn't work when you need to call next() with a value
// generator created by parser will be "done" after each CBOR entity
// parser will yield numbers of bytes that it wants
2025-07-15 07:28:14 +00:00
const parser = c._parse();
let state = parser.next();
2022-02-01 18:01:11 +00:00
while (!state.done) {
2025-07-15 07:28:14 +00:00
const b = s.read(state.value);
2022-02-01 18:01:11 +00:00
if ((b == null) || (b.length !== state.value)) {
2025-07-15 07:28:14 +00:00
throw new Error('Insufficient data');
2022-02-01 18:01:11 +00:00
}
if (c.extendedResults) {
2025-07-15 07:28:14 +00:00
c.valueBytes.write(b);
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
state = parser.next(b);
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
let val = null;
2022-02-01 18:01:11 +00:00
if (c.extendedResults) {
2025-07-15 07:28:14 +00:00
val = state.value;
val.unused = s.read();
2022-02-01 18:01:11 +00:00
} else {
2025-07-15 07:28:14 +00:00
val = Decoder.nullcheck(state.value);
2022-02-01 18:01:11 +00:00
if (s.length > 0) {
2025-07-15 07:28:14 +00:00
const nextByte = s.read(1);
2022-02-01 18:01:11 +00:00
2025-07-15 07:28:14 +00:00
s.unshift(nextByte);
throw new UnexpectedDataError(nextByte[0], val);
2022-02-01 18:01:11 +00:00
}
}
2025-07-15 07:28:14 +00:00
return val;
2022-02-01 18:01:11 +00:00
}
/**
* Decode all of the CBOR items in the input into an array. This will throw
* an exception if the input is not valid CBOR; a zero-length input will
* return an empty array.
*
* @param {BufferLike} input What to parse?
* @param {DecoderOptions|string} [options={}] Options or encoding
* for input.
* @returns {Array<ExtendedResults>|Array<any>} Array of all found items.
* @throws {TypeError} No input provided.
* @throws {Error} Insufficient data provided.
*/
static decodeAllSync(input, options = {}) {
if (input == null) {
2025-07-15 07:28:14 +00:00
throw new TypeError('input required');
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
({options} = normalizeOptions(options));
const {encoding = 'hex', ...opts} = options;
const c = new Decoder(opts);
const s = utils.guessEncoding(input, encoding);
const res = [];
2022-02-01 18:01:11 +00:00
while (s.length > 0) {
2025-07-15 07:28:14 +00:00
const parser = c._parse();
let state = parser.next();
2022-02-01 18:01:11 +00:00
while (!state.done) {
2025-07-15 07:28:14 +00:00
const b = s.read(state.value);
2022-02-01 18:01:11 +00:00
if ((b == null) || (b.length !== state.value)) {
2025-07-15 07:28:14 +00:00
throw new Error('Insufficient data');
2022-02-01 18:01:11 +00:00
}
if (c.extendedResults) {
2025-07-15 07:28:14 +00:00
c.valueBytes.write(b);
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
state = parser.next(b);
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
res.push(Decoder.nullcheck(state.value));
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
return res;
2022-02-01 18:01:11 +00:00
}
/**
* Decode the first CBOR item in the input. This will error if there are
* more bytes left over at the end (if options.extendedResults is not true),
* and optionally if there were no valid CBOR bytes in the input. Emits the
* {Decoder.NOT_FOUND} Symbol in the callback if no data was found and the
* `required` option is false.
*
* @param {BufferLike} input What to parse?
* @param {DecoderOptions|decodeCallback|string} [options={}] Options, the
* callback, or input encoding.
* @param {decodeCallback} [cb] Callback.
* @returns {Promise<ExtendedResults|any>} Returned even if callback is
* specified.
* @throws {TypeError} No input provided.
*/
static decodeFirst(input, options = {}, cb = null) {
if (input == null) {
2025-07-15 07:28:14 +00:00
throw new TypeError('input required');
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
({options, cb} = normalizeOptions(options, cb));
const {encoding = 'hex', required = false, ...opts} = options;
2022-02-01 18:01:11 +00:00
2025-07-15 07:28:14 +00:00
const c = new Decoder(opts);
let v = /** @type {any} */ (NOT_FOUND);
const s = utils.guessEncoding(input, encoding);
2022-02-01 18:01:11 +00:00
const p = new Promise((resolve, reject) => {
c.on('data', val => {
2025-07-15 07:28:14 +00:00
v = Decoder.nullcheck(val);
c.close();
});
2022-02-01 18:01:11 +00:00
c.once('error', er => {
if (c.extendedResults && (er instanceof UnexpectedDataError)) {
2025-07-15 07:28:14 +00:00
v.unused = c.bs.slice();
return resolve(v);
2022-02-01 18:01:11 +00:00
}
if (v !== NOT_FOUND) {
// Typescript work-around
// eslint-disable-next-line dot-notation
2025-07-15 07:28:14 +00:00
er['value'] = v;
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
v = ERROR;
c.close();
return reject(er);
});
2022-02-01 18:01:11 +00:00
c.once('end', () => {
switch (v) {
case NOT_FOUND:
if (required) {
2025-07-15 07:28:14 +00:00
return reject(new Error('No CBOR found'));
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
return resolve(v);
2022-02-01 18:01:11 +00:00
// Pretty sure this can't happen, but not *certain*.
/* istanbul ignore next */
case ERROR:
/* istanbul ignore next */
2025-07-15 07:28:14 +00:00
return undefined;
2022-02-01 18:01:11 +00:00
default:
2025-07-15 07:28:14 +00:00
return resolve(v);
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
});
});
2022-02-01 18:01:11 +00:00
if (typeof cb === 'function') {
2025-07-15 07:28:14 +00:00
p.then(val => cb(null, val), cb);
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
s.pipe(c);
return p;
2022-02-01 18:01:11 +00:00
}
/**
* @callback decodeAllCallback
* @param {Error} error If one was generated.
* @param {Array<ExtendedResults>|Array<any>} value All of the decoded
* values, wrapped in an Array.
*/
/**
* Decode all of the CBOR items in the input. This will error if there are
* more bytes left over at the end.
*
* @param {BufferLike} input What to parse?
* @param {DecoderOptions|decodeAllCallback|string} [options={}]
* Decoding options, the callback, or the input encoding.
* @param {decodeAllCallback} [cb] Callback.
* @returns {Promise<Array<ExtendedResults>|Array<any>>} Even if callback
* is specified.
* @throws {TypeError} No input specified.
*/
static decodeAll(input, options = {}, cb = null) {
if (input == null) {
2025-07-15 07:28:14 +00:00
throw new TypeError('input required');
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
({options, cb} = normalizeOptions(options, cb));
const {encoding = 'hex', ...opts} = options;
2022-02-01 18:01:11 +00:00
2025-07-15 07:28:14 +00:00
const c = new Decoder(opts);
const vals = [];
2022-02-01 18:01:11 +00:00
2025-07-15 07:28:14 +00:00
c.on('data', val => vals.push(Decoder.nullcheck(val)));
2022-02-01 18:01:11 +00:00
const p = new Promise((resolve, reject) => {
2025-07-15 07:28:14 +00:00
c.on('error', reject);
c.on('end', () => resolve(vals));
});
2022-02-01 18:01:11 +00:00
if (typeof cb === 'function') {
2025-07-15 07:28:14 +00:00
p.then(v => cb(undefined, v), er => cb(er, undefined));
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
utils.guessEncoding(input, encoding).pipe(c);
return p;
2022-02-01 18:01:11 +00:00
}
/**
* Stop processing.
*/
close() {
2025-07-15 07:28:14 +00:00
this.running = false;
this.__fresh = true;
2022-02-01 18:01:11 +00:00
}
/**
* Only called if extendedResults is true.
*
* @ignore
*/
_onRead(data) {
2025-07-15 07:28:14 +00:00
this.valueBytes.write(data);
2022-02-01 18:01:11 +00:00
}
/**
* @returns {Generator<number, any, Buffer>} Yields a number of bytes,
* returns anything, next returns a Buffer.
* @throws {Error} Maximum depth exceeded.
2025-07-15 07:28:14 +00:00
* @yields {number} Number of bytes to read.
2022-02-01 18:01:11 +00:00
* @ignore
*/
*_parse() {
2025-07-15 07:28:14 +00:00
let parent = null;
let depth = 0;
let val = null;
2022-02-01 18:01:11 +00:00
while (true) {
if ((this.max_depth >= 0) && (depth > this.max_depth)) {
2025-07-15 07:28:14 +00:00
throw new Error(`Maximum depth ${this.max_depth} exceeded`);
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
const [octet] = yield 1;
2022-02-01 18:01:11 +00:00
if (!this.running) {
2025-07-15 07:28:14 +00:00
this.bs.unshift(Buffer.from([octet]));
throw new UnexpectedDataError(octet);
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
const mt = octet >> 5;
const ai = octet & 0x1f;
const parent_major = (parent == null) ? undefined : parent[MAJOR];
const parent_length = (parent == null) ? undefined : parent.length;
2022-02-01 18:01:11 +00:00
switch (ai) {
case NUMBYTES.ONE:
2025-07-15 07:28:14 +00:00
this.emit('more-bytes', mt, 1, parent_major, parent_length);
[val] = yield 1;
break;
2022-02-01 18:01:11 +00:00
case NUMBYTES.TWO:
case NUMBYTES.FOUR:
case NUMBYTES.EIGHT: {
2025-07-15 07:28:14 +00:00
const numbytes = 1 << (ai - 24);
2022-02-01 18:01:11 +00:00
2025-07-15 07:28:14 +00:00
this.emit('more-bytes', mt, numbytes, parent_major, parent_length);
const buf = yield numbytes;
2022-02-01 18:01:11 +00:00
val = (mt === MT.SIMPLE_FLOAT) ?
buf :
2025-07-15 07:28:14 +00:00
utils.parseCBORint(ai, buf);
break;
2022-02-01 18:01:11 +00:00
}
case 28:
case 29:
case 30:
2025-07-15 07:28:14 +00:00
this.running = false;
throw new Error(`Additional info not implemented: ${ai}`);
2022-02-01 18:01:11 +00:00
case NUMBYTES.INDEFINITE:
switch (mt) {
case MT.POS_INT:
case MT.NEG_INT:
case MT.TAG:
2025-07-15 07:28:14 +00:00
throw new Error(`Invalid indefinite encoding for MT ${mt}`);
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
val = -1;
break;
2022-02-01 18:01:11 +00:00
default:
2025-07-15 07:28:14 +00:00
val = ai;
2022-02-01 18:01:11 +00:00
}
switch (mt) {
case MT.POS_INT:
// Val already decoded
2025-07-15 07:28:14 +00:00
break;
2022-02-01 18:01:11 +00:00
case MT.NEG_INT:
if (val === Number.MAX_SAFE_INTEGER) {
2025-07-15 07:28:14 +00:00
val = BI.NEG_MAX;
2022-02-01 18:01:11 +00:00
} else {
2025-07-15 07:28:14 +00:00
val = (typeof val === 'bigint') ? BI.MINUS_ONE - val : -1 - val;
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
break;
2022-02-01 18:01:11 +00:00
case MT.BYTE_STRING:
case MT.UTF8_STRING:
switch (val) {
case 0:
2025-07-15 07:28:14 +00:00
this.emit('start-string', mt, val, parent_major, parent_length);
2022-02-01 18:01:11 +00:00
if (mt === MT.UTF8_STRING) {
2025-07-15 07:28:14 +00:00
val = '';
2022-02-01 18:01:11 +00:00
} else {
2025-07-15 07:28:14 +00:00
val = this.preferWeb ?
new Uint8Array(0) :
Buffer.allocUnsafe(0);
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
break;
2022-02-01 18:01:11 +00:00
case -1:
2025-07-15 07:28:14 +00:00
this.emit('start', mt, SYMS.STREAM, parent_major, parent_length);
parent = parentBufferStream(parent, mt);
depth++;
continue;
2022-02-01 18:01:11 +00:00
default:
2025-07-15 07:28:14 +00:00
this.emit('start-string', mt, val, parent_major, parent_length);
val = yield val;
2022-02-01 18:01:11 +00:00
if (mt === MT.UTF8_STRING) {
2025-07-15 07:28:14 +00:00
val = utils.utf8(val);
2022-02-01 18:01:11 +00:00
} else if (this.preferWeb) {
2025-07-15 07:28:14 +00:00
val = new Uint8Array(val.buffer, val.byteOffset, val.length);
2022-02-01 18:01:11 +00:00
}
}
2025-07-15 07:28:14 +00:00
break;
2022-02-01 18:01:11 +00:00
case MT.ARRAY:
case MT.MAP:
switch (val) {
case 0:
2025-07-15 07:28:14 +00:00
if (mt === MT.MAP) {
val = (this.preferMap) ? new Map() : {};
} else {
val = [];
}
break;
2022-02-01 18:01:11 +00:00
case -1:
2025-07-15 07:28:14 +00:00
this.emit('start', mt, SYMS.STREAM, parent_major, parent_length);
parent = parentArray(parent, mt, -1);
depth++;
continue;
2022-02-01 18:01:11 +00:00
default:
2025-07-15 07:28:14 +00:00
this.emit('start', mt, val, parent_major, parent_length);
parent = parentArray(parent, mt, val * (mt - 3));
depth++;
continue;
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
break;
2022-02-01 18:01:11 +00:00
case MT.TAG:
2025-07-15 07:28:14 +00:00
this.emit('start', mt, val, parent_major, parent_length);
parent = parentArray(parent, mt, 1);
parent.push(val);
depth++;
continue;
2022-02-01 18:01:11 +00:00
case MT.SIMPLE_FLOAT:
if (typeof val === 'number') {
if ((ai === NUMBYTES.ONE) && (val < 32)) {
throw new Error(
`Invalid two-byte encoding of simple value ${val}`
2025-07-15 07:28:14 +00:00
);
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
const hasParent = (parent != null);
2022-02-01 18:01:11 +00:00
val = Simple.decode(
val,
hasParent,
hasParent && (parent[COUNT] < 0)
2025-07-15 07:28:14 +00:00
);
2022-02-01 18:01:11 +00:00
} else {
2025-07-15 07:28:14 +00:00
val = utils.parseCBORfloat(val);
2022-02-01 18:01:11 +00:00
}
}
2025-07-15 07:28:14 +00:00
this.emit('value', val, parent_major, parent_length, ai);
let again = false;
2022-02-01 18:01:11 +00:00
while (parent != null) {
if (val === SYMS.BREAK) {
2025-07-15 07:28:14 +00:00
parent[COUNT] = 1;
2022-02-01 18:01:11 +00:00
} else if (Array.isArray(parent)) {
2025-07-15 07:28:14 +00:00
parent.push(val);
2022-02-01 18:01:11 +00:00
} else {
// Assert: parent instanceof NoFilter
2025-07-15 07:28:14 +00:00
const pm = parent[MAJOR];
2022-02-01 18:01:11 +00:00
if ((pm != null) && (pm !== mt)) {
2025-07-15 07:28:14 +00:00
this.running = false;
throw new Error('Invalid major type in indefinite encoding');
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
parent.write(val);
2022-02-01 18:01:11 +00:00
}
if ((--parent[COUNT]) !== 0) {
2025-07-15 07:28:14 +00:00
again = true;
break;
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
--depth;
delete parent[COUNT];
2022-02-01 18:01:11 +00:00
if (Array.isArray(parent)) {
switch (parent[MAJOR]) {
case MT.ARRAY:
2025-07-15 07:28:14 +00:00
val = parent;
break;
2022-02-01 18:01:11 +00:00
case MT.MAP: {
2025-07-15 07:28:14 +00:00
let allstrings = !this.preferMap;
2022-02-01 18:01:11 +00:00
if ((parent.length % 2) !== 0) {
2025-07-15 07:28:14 +00:00
throw new Error(`Invalid map length: ${parent.length}`);
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
for (
let i = 0, len = parent.length;
allstrings && (i < len);
i += 2
) {
2022-02-01 18:01:11 +00:00
if ((typeof parent[i] !== 'string') ||
(parent[i] === '__proto__')) {
2025-07-15 07:28:14 +00:00
allstrings = false;
break;
2022-02-01 18:01:11 +00:00
}
}
if (allstrings) {
2025-07-15 07:28:14 +00:00
val = {};
2022-02-01 18:01:11 +00:00
for (let i = 0, len = parent.length; i < len; i += 2) {
if (this.preventDuplicateKeys &&
Object.prototype.hasOwnProperty.call(val, parent[i])) {
2025-07-15 07:28:14 +00:00
throw new Error('Duplicate keys in a map');
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
val[parent[i]] = parent[i + 1];
2022-02-01 18:01:11 +00:00
}
} else {
2025-07-15 07:28:14 +00:00
val = new Map();
2022-02-01 18:01:11 +00:00
for (let i = 0, len = parent.length; i < len; i += 2) {
if (this.preventDuplicateKeys && val.has(parent[i])) {
2025-07-15 07:28:14 +00:00
throw new Error('Duplicate keys in a map');
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
val.set(parent[i], parent[i + 1]);
2022-02-01 18:01:11 +00:00
}
}
2025-07-15 07:28:14 +00:00
break;
2022-02-01 18:01:11 +00:00
}
case MT.TAG: {
2025-07-15 07:28:14 +00:00
const t = new Tagged(parent[0], parent[1]);
2022-02-01 18:01:11 +00:00
2025-07-15 07:28:14 +00:00
val = t.convert(this.tags);
break;
2022-02-01 18:01:11 +00:00
}
}
} else /* istanbul ignore else */ if (parent instanceof NoFilter) {
// Only parent types are Array and NoFilter for (Array/Map) and
// (bytes/string) respectively.
switch (parent[MAJOR]) {
case MT.BYTE_STRING:
2025-07-15 07:28:14 +00:00
val = parent.slice();
2022-02-01 18:01:11 +00:00
if (this.preferWeb) {
val = new Uint8Array(
/** @type {Buffer} */ (val).buffer,
/** @type {Buffer} */ (val).byteOffset,
/** @type {Buffer} */ (val).length
2025-07-15 07:28:14 +00:00
);
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
break;
2022-02-01 18:01:11 +00:00
case MT.UTF8_STRING:
2025-07-15 07:28:14 +00:00
val = parent.toString('utf-8');
break;
2022-02-01 18:01:11 +00:00
}
}
2025-07-15 07:28:14 +00:00
this.emit('stop', parent[MAJOR]);
2022-02-01 18:01:11 +00:00
2025-07-15 07:28:14 +00:00
const old = parent;
parent = parent[SYMS.PARENT];
delete old[SYMS.PARENT];
delete old[MAJOR];
2022-02-01 18:01:11 +00:00
}
if (!again) {
if (this.extendedResults) {
2025-07-15 07:28:14 +00:00
const bytes = this.valueBytes.slice();
2022-02-01 18:01:11 +00:00
const ret = {
value: Decoder.nullcheck(val),
bytes,
length: bytes.length,
2025-07-15 07:28:14 +00:00
};
2022-02-01 18:01:11 +00:00
2025-07-15 07:28:14 +00:00
this.valueBytes = new NoFilter();
return ret;
2022-02-01 18:01:11 +00:00
}
2025-07-15 07:28:14 +00:00
return val;
2022-02-01 18:01:11 +00:00
}
}
}
}
2025-07-15 07:28:14 +00:00
Decoder.NOT_FOUND = NOT_FOUND;
module.exports = Decoder;