Initial commit
This commit is contained in:
574
node_modules/cbor/lib/decoder.js
generated
vendored
Normal file
574
node_modules/cbor/lib/decoder.js
generated
vendored
Normal file
@@ -0,0 +1,574 @@
|
||||
'use strict'
|
||||
|
||||
const stream = require('stream')
|
||||
const BinaryParseStream = require('../vendor/binary-parse-stream')
|
||||
const Tagged = require('./tagged')
|
||||
const Simple = require('./simple')
|
||||
const utils = require('./utils')
|
||||
const bignumber = require('bignumber.js').BigNumber
|
||||
const NoFilter = require('nofilter')
|
||||
const constants = require('./constants')
|
||||
// Do not fix this if you want to support node v4
|
||||
const MT = constants.MT
|
||||
const NUMBYTES = constants.NUMBYTES
|
||||
const SIMPLE = constants.SIMPLE
|
||||
const SYMS = constants.SYMS
|
||||
|
||||
const BI = utils.bigIntize(constants.BI)
|
||||
const BN = constants.BN
|
||||
const COUNT = Symbol('count')
|
||||
const PENDING_KEY = Symbol('pending_key')
|
||||
const MAJOR = Symbol('major type')
|
||||
const ERROR = Symbol('error')
|
||||
const NOT_FOUND = Symbol('not found')
|
||||
|
||||
function parentArray(parent, typ, count) {
|
||||
const a = []
|
||||
a[COUNT] = count
|
||||
a[SYMS.PARENT] = parent
|
||||
a[MAJOR] = typ
|
||||
return a
|
||||
}
|
||||
|
||||
function parentBufferStream(parent, typ) {
|
||||
const b = new NoFilter()
|
||||
b[COUNT] = -1
|
||||
b[SYMS.PARENT] = parent
|
||||
b[MAJOR] = typ
|
||||
return b
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Buffer} v
|
||||
* @private
|
||||
*/
|
||||
function _tag_2(v) {
|
||||
return utils.bufferToBigInt(v)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BigInt} v
|
||||
* @private
|
||||
*/
|
||||
function _tag_3(v) {
|
||||
// avoid syntax error on old runtimes
|
||||
return BI.MINUS_ONE - utils.bufferToBigInt(v)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {object} [options={}]
|
||||
* @param {number} [options.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.
|
||||
* @param {object} [options.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.
|
||||
* @param {boolean} [options.bigint=false] generate JavaScript BigInt's
|
||||
* instead of BigNumbers, when possible. TODO: when BigInt's are
|
||||
* more prevalent, change this to default to the value of cbor.hasBigInt.
|
||||
*/
|
||||
constructor(options) {
|
||||
options = options || {}
|
||||
const tags = options.tags
|
||||
delete options.tags
|
||||
const max_depth = (options.max_depth != null) ? options.max_depth : -1
|
||||
delete options.max_depth
|
||||
const bigI = utils.hasBigInt ? (!!options.bigint) : false
|
||||
delete options.bigint
|
||||
super(options)
|
||||
|
||||
this.running = true
|
||||
this.max_depth = max_depth
|
||||
this.tags = tags
|
||||
if (bigI) {
|
||||
if (this.tags == null) {
|
||||
this.tags = {}
|
||||
}
|
||||
if (this.tags[2] == null) {
|
||||
this.tags[2] = _tag_2
|
||||
}
|
||||
if (this.tags[3] == null) {
|
||||
this.tags[3] = _tag_3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given value for a symbol encoding a NULL or UNDEFINED value in
|
||||
* the CBOR stream.
|
||||
*
|
||||
* @static
|
||||
* @param {any} val - the value to check
|
||||
* @returns {any} the corrected value
|
||||
*
|
||||
* @example
|
||||
* myDecoder.on('data', function(val) {
|
||||
* val = Decoder.nullcheck(val);
|
||||
* ...
|
||||
* });
|
||||
*/
|
||||
static nullcheck(val) {
|
||||
switch (val) {
|
||||
case SYMS.NULL:
|
||||
return null
|
||||
case SYMS.UNDEFINED:
|
||||
return undefined
|
||||
case NOT_FOUND:
|
||||
throw new Error('Value not found')
|
||||
default:
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef DecodeOptions
|
||||
* @property {string} [encoding='hex'] - The encoding of the input.
|
||||
* Ignored if input is a Buffer.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Decode the first CBOR item in the input, synchronously. This will throw an
|
||||
* exception if the input is not valid CBOR.
|
||||
*
|
||||
* @static
|
||||
* @param {string|Buffer|stream.Readable} input
|
||||
* @param {DecodeOptions|string} [options] Options
|
||||
* @returns {any} - the decoded value
|
||||
*/
|
||||
static decodeFirstSync(input, options) {
|
||||
options = options || { encoding: 'hex' }
|
||||
let opts = {}
|
||||
let encod
|
||||
switch (typeof options) {
|
||||
case 'string':
|
||||
encod = options
|
||||
break
|
||||
case 'object':
|
||||
opts = utils.extend({}, options)
|
||||
encod = opts.encoding
|
||||
delete opts.encoding
|
||||
break
|
||||
}
|
||||
const c = new Decoder(opts)
|
||||
const s = (input instanceof stream.Readable) ? input : new NoFilter(
|
||||
input,
|
||||
encod != null ? encod : utils.guessEncoding(input))
|
||||
|
||||
// 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
|
||||
const parser = c._parse()
|
||||
let state = parser.next()
|
||||
while (!state.done) {
|
||||
const b = s.read(state.value)
|
||||
if ((b == null) || (b.length !== state.value)) {
|
||||
throw new Error('Insufficient data')
|
||||
}
|
||||
state = parser.next(b)
|
||||
}
|
||||
const val = Decoder.nullcheck(state.value)
|
||||
if (s.length > 0) {
|
||||
const nextByte = s.read(1)
|
||||
s.unshift(nextByte)
|
||||
const er = new Error(
|
||||
'Unexpected data: 0x' + nextByte[0].toString(16))
|
||||
er.value = val
|
||||
throw er
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @static
|
||||
* @param {string|Buffer|stream.Readable} input
|
||||
* @param {DecodeOptions|string} [options] Options or encoding string
|
||||
* @returns {Array} - Array of all found items
|
||||
*/
|
||||
static decodeAllSync(input, options) {
|
||||
options = options || { encoding: 'hex' }
|
||||
let opts = {}
|
||||
let encod
|
||||
switch (typeof options) {
|
||||
case 'string':
|
||||
encod = options
|
||||
break
|
||||
case 'object':
|
||||
opts = utils.extend({}, options)
|
||||
encod = opts.encoding
|
||||
delete opts.encoding
|
||||
}
|
||||
const c = new Decoder(opts)
|
||||
const s = (input instanceof stream.Readable) ? input : new NoFilter(
|
||||
input,
|
||||
encod != null ? encod : utils.guessEncoding(input))
|
||||
const res = []
|
||||
while (s.length > 0) {
|
||||
const parser = c._parse()
|
||||
let state = parser.next()
|
||||
while (!state.done) {
|
||||
const b = s.read(state.value)
|
||||
if ((b == null) || (b.length !== state.value)) {
|
||||
throw new Error('Insufficient data')
|
||||
}
|
||||
state = parser.next(b)
|
||||
}
|
||||
res.push(Decoder.nullcheck(state.value))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback decodeCallback
|
||||
* @param {Error} [error] - if one was generated
|
||||
* @param {any} [value] - the decoded value
|
||||
*/
|
||||
|
||||
/**
|
||||
* Decode the first CBOR item in the input. This will error if there are more
|
||||
* bytes left over at the end, 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.
|
||||
*
|
||||
* @static
|
||||
* @param {string|Buffer} input - the input to parse
|
||||
* @param {DecodeOptions|decodeCallback|string} [options] - options
|
||||
* @param {decodeCallback} [cb] callback
|
||||
* @returns {Promise<any>} returned even if callback is specified
|
||||
*/
|
||||
static decodeFirst(input, options, cb) {
|
||||
let opts = {}
|
||||
let required = false
|
||||
let encod = 'hex'
|
||||
switch (typeof options) {
|
||||
case 'function':
|
||||
cb = options
|
||||
encod = utils.guessEncoding(input)
|
||||
break
|
||||
case 'string':
|
||||
encod = options
|
||||
break
|
||||
case 'object':
|
||||
opts = utils.extend({}, options)
|
||||
encod = (opts.encoding != null) ?
|
||||
opts.encoding : utils.guessEncoding(input)
|
||||
delete opts.encoding
|
||||
required = (opts.required != null) ? opts.required : false
|
||||
delete opts.required
|
||||
}
|
||||
const c = new Decoder(opts)
|
||||
let v = NOT_FOUND
|
||||
const p = new Promise((resolve, reject) => {
|
||||
c.on('data', (val) => {
|
||||
v = Decoder.nullcheck(val)
|
||||
c.close()
|
||||
})
|
||||
c.once('error', (er) => {
|
||||
if (v !== NOT_FOUND) {
|
||||
er.value = v
|
||||
}
|
||||
v = ERROR
|
||||
c.close()
|
||||
return reject(er)
|
||||
})
|
||||
c.once('end', () => {
|
||||
switch (v) {
|
||||
case NOT_FOUND:
|
||||
if (required) {
|
||||
return reject(new Error('No CBOR found'))
|
||||
} else {
|
||||
return resolve(v)
|
||||
}
|
||||
case ERROR:
|
||||
return void 0
|
||||
default:
|
||||
return resolve(v)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (typeof cb === 'function') {
|
||||
p.then(v => cb(null, v), cb)
|
||||
}
|
||||
c.end(input, encod)
|
||||
return p
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback decodeAllCallback
|
||||
* @param {Error} error - if one was generated
|
||||
* @param {Array} 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.
|
||||
*
|
||||
* @static
|
||||
* @param {(string|Buffer)} input - the input to parse
|
||||
* @param {(string|Object)} options - Decoding options.
|
||||
* If string, the input encoding.
|
||||
* @param {decodeAllCallback} cb callback
|
||||
* @returns {Promise<Array>} even if callback is specified
|
||||
*/
|
||||
static decodeAll(input, options, cb) {
|
||||
let opts = {}
|
||||
let encod = 'hex'
|
||||
switch (typeof options) {
|
||||
case 'function':
|
||||
cb = options
|
||||
encod = utils.guessEncoding(input)
|
||||
break
|
||||
case 'string':
|
||||
encod = options
|
||||
break
|
||||
case 'object':
|
||||
opts = utils.extend({}, options)
|
||||
encod = (opts.encoding != null) ?
|
||||
opts.encoding : utils.guessEncoding(input)
|
||||
delete opts.encoding
|
||||
}
|
||||
const c = new Decoder(opts)
|
||||
const vals = []
|
||||
c.on('data', (val) => {
|
||||
return vals.push(Decoder.nullcheck(val))
|
||||
})
|
||||
const p = new Promise((resolve, reject) => {
|
||||
c.on('error', reject)
|
||||
c.on('end', () => resolve(vals))
|
||||
})
|
||||
if (typeof cb === 'function') {
|
||||
p.then(v => cb(null, v), cb)
|
||||
}
|
||||
c.end(input, encod)
|
||||
return p
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop processing
|
||||
*/
|
||||
close() {
|
||||
this.running = false
|
||||
this.__fresh = true
|
||||
}
|
||||
|
||||
*_parse() {
|
||||
let parent = null
|
||||
let depth = 0
|
||||
let val = null
|
||||
while (true) {
|
||||
if ((this.max_depth >= 0) && (depth > this.max_depth)) {
|
||||
throw new Error('Maximum depth ' + this.max_depth + ' exceeded')
|
||||
}
|
||||
const octet = (yield 1)[0]
|
||||
if (!this.running) {
|
||||
throw new Error('Unexpected data: 0x' + (octet.toString(16)))
|
||||
}
|
||||
const mt = octet >> 5
|
||||
const ai = octet & 0x1f
|
||||
const parent_major = (parent != null) ? parent[MAJOR] : undefined
|
||||
const parent_length = (parent != null) ? parent.length : undefined
|
||||
switch (ai) {
|
||||
case NUMBYTES.ONE:
|
||||
this.emit('more-bytes', mt, 1, parent_major, parent_length)
|
||||
val = (yield 1)[0]
|
||||
break
|
||||
case NUMBYTES.TWO:
|
||||
case NUMBYTES.FOUR:
|
||||
case NUMBYTES.EIGHT:
|
||||
const numbytes = 1 << (ai - 24)
|
||||
this.emit('more-bytes', mt, numbytes, parent_major, parent_length)
|
||||
const buf = yield numbytes
|
||||
val = (mt === MT.SIMPLE_FLOAT) ? buf : utils.parseCBORint(ai, buf)
|
||||
break
|
||||
case 28:
|
||||
case 29:
|
||||
case 30:
|
||||
this.running = false
|
||||
throw new Error('Additional info not implemented: ' + ai)
|
||||
case NUMBYTES.INDEFINITE:
|
||||
switch (mt) {
|
||||
case MT.POS_INT:
|
||||
case MT.NEG_INT:
|
||||
case MT.TAG:
|
||||
throw new Error(`Invalid indefinite encoding for MT ${mt}`)
|
||||
}
|
||||
val = -1
|
||||
break
|
||||
default:
|
||||
val = ai
|
||||
}
|
||||
switch (mt) {
|
||||
case MT.POS_INT:
|
||||
// val already decoded
|
||||
break
|
||||
case MT.NEG_INT:
|
||||
if (val === Number.MAX_SAFE_INTEGER) {
|
||||
val = BN.NEG_MAX
|
||||
} else if (val instanceof bignumber) {
|
||||
val = BN.MINUS_ONE.minus(val)
|
||||
} else {
|
||||
val = -1 - val
|
||||
}
|
||||
break
|
||||
case MT.BYTE_STRING:
|
||||
case MT.UTF8_STRING:
|
||||
switch (val) {
|
||||
case 0:
|
||||
this.emit('start-string', mt, val, parent_major, parent_length)
|
||||
val = (mt === MT.BYTE_STRING) ? Buffer.allocUnsafe(0) : ''
|
||||
break
|
||||
case -1:
|
||||
this.emit('start', mt, SYMS.STREAM, parent_major, parent_length)
|
||||
parent = parentBufferStream(parent, mt)
|
||||
depth++
|
||||
continue
|
||||
default:
|
||||
this.emit('start-string', mt, val, parent_major, parent_length)
|
||||
val = yield val
|
||||
if (mt === MT.UTF8_STRING) {
|
||||
val = utils.utf8(val)
|
||||
}
|
||||
}
|
||||
break
|
||||
case MT.ARRAY:
|
||||
case MT.MAP:
|
||||
switch (val) {
|
||||
case 0:
|
||||
val = (mt === MT.MAP) ? {} : []
|
||||
break
|
||||
case -1:
|
||||
this.emit('start', mt, SYMS.STREAM, parent_major, parent_length)
|
||||
parent = parentArray(parent, mt, -1)
|
||||
depth++
|
||||
continue
|
||||
default:
|
||||
this.emit('start', mt, val, parent_major, parent_length)
|
||||
parent = parentArray(parent, mt, val * (mt - 3))
|
||||
depth++
|
||||
continue
|
||||
}
|
||||
break
|
||||
case MT.TAG:
|
||||
this.emit('start', mt, val, parent_major, parent_length)
|
||||
parent = parentArray(parent, mt, 1)
|
||||
parent.push(val)
|
||||
depth++
|
||||
continue
|
||||
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}`
|
||||
)
|
||||
}
|
||||
const hasParent = (parent != null)
|
||||
val = Simple.decode(
|
||||
val,
|
||||
hasParent,
|
||||
hasParent && (parent[COUNT] < 0))
|
||||
} else {
|
||||
val = utils.parseCBORfloat(val)
|
||||
}
|
||||
}
|
||||
this.emit('value', val, parent_major, parent_length, ai)
|
||||
let again = false
|
||||
while (parent != null) {
|
||||
switch (false) {
|
||||
case val !== SYMS.BREAK:
|
||||
parent[COUNT] = 1
|
||||
break
|
||||
case !Array.isArray(parent):
|
||||
parent.push(val)
|
||||
break
|
||||
case !(parent instanceof NoFilter):
|
||||
const pm = parent[MAJOR]
|
||||
if ((pm != null) && (pm !== mt)) {
|
||||
this.running = false
|
||||
throw new Error('Invalid major type in indefinite encoding')
|
||||
}
|
||||
parent.write(val)
|
||||
}
|
||||
if ((--parent[COUNT]) !== 0) {
|
||||
again = true
|
||||
break
|
||||
}
|
||||
--depth
|
||||
delete parent[COUNT]
|
||||
this.emit('stop', parent[MAJOR])
|
||||
|
||||
if (Array.isArray(parent)) {
|
||||
switch (parent[MAJOR]) {
|
||||
case MT.ARRAY:
|
||||
val = parent
|
||||
break
|
||||
case MT.MAP:
|
||||
let allstrings = true
|
||||
if ((parent.length % 2) !== 0) {
|
||||
throw new Error('Invalid map length: ' + parent.length)
|
||||
}
|
||||
for (let i = 0, len = parent.length; i < len; i += 2) {
|
||||
if (typeof parent[i] !== 'string') {
|
||||
allstrings = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (allstrings) {
|
||||
val = {}
|
||||
for (let i = 0, len = parent.length; i < len; i += 2) {
|
||||
val[parent[i]] = parent[i + 1]
|
||||
}
|
||||
} else {
|
||||
val = new Map
|
||||
for (let i = 0, len = parent.length; i < len; i += 2) {
|
||||
val.set(parent[i], parent[i + 1])
|
||||
}
|
||||
}
|
||||
break
|
||||
case MT.TAG:
|
||||
const t = new Tagged(parent[0], parent[1])
|
||||
val = t.convert(this.tags)
|
||||
break
|
||||
}
|
||||
} else if (parent instanceof NoFilter) {
|
||||
switch (parent[MAJOR]) {
|
||||
case MT.BYTE_STRING:
|
||||
val = parent.slice()
|
||||
break
|
||||
case MT.UTF8_STRING:
|
||||
val = parent.toString('utf-8')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const old = parent
|
||||
parent = parent[SYMS.PARENT]
|
||||
delete old[SYMS.PARENT]
|
||||
delete old[MAJOR]
|
||||
}
|
||||
if (!again) {
|
||||
return val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Decoder.NOT_FOUND = NOT_FOUND
|
||||
module.exports = Decoder
|
||||
Reference in New Issue
Block a user