msgpack5 6.0.2 → 6.1.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/.github/workflows/ci.yml +15 -4
- package/AGENTS.md +26 -0
- package/README.md +10 -2
- package/SECURITY.md +16 -0
- package/dist/msgpack5.js +1760 -1111
- package/dist/msgpack5.min.js +1 -1
- package/example.js +6 -1
- package/index.js +16 -2
- package/lib/decoder.js +152 -28
- package/lib/streams.js +18 -17
- package/package.json +1 -1
- package/test/4-bytes-length-maps.js +31 -0
- package/test/64-bits-signed-integers.js +21 -0
- package/test/collection-length-limits.js +64 -0
- package/test/max-depth.js +95 -0
- package/test/object-prototype-poisoning.js +50 -0
- package/test/reserved-byte.js +53 -0
- package/test/streams.js +78 -0
package/example.js
CHANGED
|
@@ -8,8 +8,13 @@ const decode = msgpack.decode
|
|
|
8
8
|
|
|
9
9
|
msgpack.register(0x42, MyType, mytipeEncode, mytipeDecode)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
const hex = encode({ hello: 'world' }).toString('hex')
|
|
12
|
+
console.log(hex)
|
|
12
13
|
// 81a568656c6c6fa5776f726c64
|
|
14
|
+
const obj = decode(Buffer.from(hex, 'hex'))
|
|
15
|
+
console.log(obj)
|
|
16
|
+
// { hello: 'world' }
|
|
17
|
+
|
|
13
18
|
console.log(decode(encode({ hello: 'world' })))
|
|
14
19
|
// { hello: 'world' }
|
|
15
20
|
console.log(encode(a).toString('hex'))
|
package/index.js
CHANGED
|
@@ -13,17 +13,25 @@ function msgpack (options) {
|
|
|
13
13
|
const encodingTypes = []
|
|
14
14
|
const decodingTypes = new Map()
|
|
15
15
|
|
|
16
|
-
options =
|
|
16
|
+
options = Object.assign({
|
|
17
17
|
forceFloat64: false,
|
|
18
18
|
compatibilityMode: false,
|
|
19
19
|
// if true, skips encoding Dates using the msgpack
|
|
20
20
|
// timestamp ext format (-1)
|
|
21
21
|
disableTimestampEncoding: false,
|
|
22
22
|
preferMap: false,
|
|
23
|
-
|
|
23
|
+
maxDepth: 100,
|
|
24
24
|
protoAction: 'error'
|
|
25
|
+
}, options || {})
|
|
26
|
+
|
|
27
|
+
if (options.protoAction === undefined) options.protoAction = 'error'
|
|
28
|
+
if (options.protoAction !== 'error' && options.protoAction !== 'remove' && options.protoAction !== 'ignore') {
|
|
29
|
+
throw new TypeError('protoAction must be "error", "remove", or "ignore"')
|
|
25
30
|
}
|
|
26
31
|
|
|
32
|
+
validateMaxLength(options.maxArrayLength, 'maxArrayLength')
|
|
33
|
+
validateMaxLength(options.maxMapLength, 'maxMapLength')
|
|
34
|
+
|
|
27
35
|
decodingTypes.set(DateCodec.type, DateCodec.decode)
|
|
28
36
|
if (!options.disableTimestampEncoding) {
|
|
29
37
|
encodingTypes.push(DateCodec)
|
|
@@ -88,4 +96,10 @@ function msgpack (options) {
|
|
|
88
96
|
}
|
|
89
97
|
}
|
|
90
98
|
|
|
99
|
+
function validateMaxLength (value, name) {
|
|
100
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
|
|
101
|
+
throw new TypeError(name + ' must be a non-negative safe integer')
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
91
105
|
module.exports = msgpack
|
package/lib/decoder.js
CHANGED
|
@@ -28,9 +28,10 @@ const SIZES = {
|
|
|
28
28
|
0xd9: 2,
|
|
29
29
|
0xda: 3,
|
|
30
30
|
0xdb: 5,
|
|
31
|
-
0xde: 3,
|
|
32
31
|
0xdc: 3,
|
|
33
|
-
0xdd: 5
|
|
32
|
+
0xdd: 5,
|
|
33
|
+
0xde: 3,
|
|
34
|
+
0xdf: 5
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
function isValidDataSize (dataLength, bufLength, headerLength) {
|
|
@@ -38,30 +39,135 @@ function isValidDataSize (dataLength, bufLength, headerLength) {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
module.exports = function buildDecode (decodingTypes, options) {
|
|
41
|
-
const
|
|
42
|
+
const maxDepth = options.maxDepth === undefined ? 100 : options.maxDepth
|
|
43
|
+
if (!Number.isInteger(maxDepth) || maxDepth < 0) {
|
|
44
|
+
throw new TypeError('maxDepth must be a non-negative integer')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const context = { decodingTypes, options, maxDepth, decode }
|
|
42
48
|
return decode
|
|
43
49
|
|
|
44
|
-
function decode (buf) {
|
|
50
|
+
function decode (buf, decodeState) {
|
|
45
51
|
if (!bl.isBufferList(buf)) {
|
|
46
52
|
buf = bl(buf)
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
|
|
55
|
+
let result
|
|
56
|
+
try {
|
|
57
|
+
result = decodeState
|
|
58
|
+
? tryDecodeIncremental(buf, decodeState, context)
|
|
59
|
+
: tryDecode(buf, 0, context, 0)
|
|
60
|
+
} catch (err) {
|
|
61
|
+
if (decodeState) decodeState.stack.length = 0
|
|
62
|
+
throw err
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
// Handle worst case ASAP and keep code flat
|
|
51
66
|
if (!result) throw new IncompleteBufferError()
|
|
52
67
|
|
|
53
|
-
buf.consume(result[1])
|
|
68
|
+
if (!decodeState) buf.consume(result[1])
|
|
54
69
|
return result[0]
|
|
55
70
|
}
|
|
56
71
|
}
|
|
57
72
|
|
|
58
|
-
function
|
|
73
|
+
function tryDecodeIncremental (buf, state, context) {
|
|
74
|
+
while (buf.length > 0) {
|
|
75
|
+
const container = decodeContainerHeader(buf)
|
|
76
|
+
let value
|
|
77
|
+
|
|
78
|
+
if (container === null) return null
|
|
79
|
+
|
|
80
|
+
if (container) {
|
|
81
|
+
const depth = state.stack.length + 1
|
|
82
|
+
if (depth > context.maxDepth) {
|
|
83
|
+
throw new Error('Maximum decode depth exceeded')
|
|
84
|
+
}
|
|
85
|
+
checkCollectionLength(
|
|
86
|
+
container.type,
|
|
87
|
+
container.length,
|
|
88
|
+
container.type === 'map' ? context.options.maxMapLength : context.options.maxArrayLength
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
buf.consume(container.headerLength)
|
|
92
|
+
|
|
93
|
+
const itemCount = container.type === 'map'
|
|
94
|
+
? 2 * container.length
|
|
95
|
+
: container.length
|
|
96
|
+
|
|
97
|
+
if (itemCount > 0) {
|
|
98
|
+
state.stack.push({
|
|
99
|
+
type: container.type,
|
|
100
|
+
length: container.length,
|
|
101
|
+
itemCount,
|
|
102
|
+
result: []
|
|
103
|
+
})
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
value = container.type === 'map'
|
|
108
|
+
? buildMap([], 0, context)
|
|
109
|
+
: []
|
|
110
|
+
} else {
|
|
111
|
+
const result = tryDecode(buf, 0, context, state.stack.length)
|
|
112
|
+
if (!result) return null
|
|
113
|
+
|
|
114
|
+
buf.consume(result[1])
|
|
115
|
+
value = result[0]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const completed = completeIncrementalValue(state, value, context)
|
|
119
|
+
if (completed) return completed
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function completeIncrementalValue (state, value, context) {
|
|
126
|
+
while (state.stack.length > 0) {
|
|
127
|
+
const frame = state.stack[state.stack.length - 1]
|
|
128
|
+
frame.result.push(value)
|
|
129
|
+
|
|
130
|
+
if (frame.result.length < frame.itemCount) return null
|
|
131
|
+
|
|
132
|
+
state.stack.pop()
|
|
133
|
+
value = frame.type === 'map'
|
|
134
|
+
? buildMap(frame.result, frame.length, context)
|
|
135
|
+
: frame.result
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return [value]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function decodeContainerHeader (buf) {
|
|
142
|
+
const first = buf.readUInt8(0)
|
|
143
|
+
|
|
144
|
+
if ((first & 0xf0) === 0x80) {
|
|
145
|
+
return { type: 'map', length: first & 0x0f, headerLength: 1 }
|
|
146
|
+
}
|
|
147
|
+
if ((first & 0xf0) === 0x90) {
|
|
148
|
+
return { type: 'array', length: first & 0x0f, headerLength: 1 }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let headerLength
|
|
152
|
+
if (first === 0xdc || first === 0xde) headerLength = 3
|
|
153
|
+
if (first === 0xdd || first === 0xdf) headerLength = 5
|
|
154
|
+
if (!headerLength) return false
|
|
155
|
+
if (buf.length < headerLength) return null
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
type: first === 0xdc || first === 0xdd ? 'array' : 'map',
|
|
159
|
+
length: buf.readUIntBE(1, headerLength - 1),
|
|
160
|
+
headerLength
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function decodeItems (buf, initialOffset, length, headerLength, context, depth) {
|
|
59
165
|
let offset = initialOffset
|
|
60
166
|
const result = []
|
|
61
167
|
let i = 0
|
|
62
168
|
|
|
63
169
|
while (i++ < length) {
|
|
64
|
-
const decodeResult = tryDecode(buf, offset, context)
|
|
170
|
+
const decodeResult = tryDecode(buf, offset, context, depth)
|
|
65
171
|
if (!decodeResult) return null
|
|
66
172
|
|
|
67
173
|
result.push(decodeResult[0])
|
|
@@ -70,11 +176,32 @@ function decodeArray (buf, initialOffset, length, headerLength, context) {
|
|
|
70
176
|
return [result, headerLength + offset - initialOffset]
|
|
71
177
|
}
|
|
72
178
|
|
|
73
|
-
function
|
|
74
|
-
|
|
179
|
+
function checkCollectionLength (type, length, maxLength) {
|
|
180
|
+
if (maxLength !== undefined && length > maxLength) {
|
|
181
|
+
throw new RangeError(type + ' length ' + length + ' exceeds configured limit of ' + maxLength)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function decodeArray (buf, initialOffset, length, headerLength, context, depth) {
|
|
186
|
+
if (depth > context.maxDepth) {
|
|
187
|
+
throw new Error('Maximum decode depth exceeded')
|
|
188
|
+
}
|
|
189
|
+
checkCollectionLength('array', length, context.options.maxArrayLength)
|
|
190
|
+
return decodeItems(buf, initialOffset, length, headerLength, context, depth)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function decodeMap (buf, offset, length, headerLength, context, depth) {
|
|
194
|
+
if (depth > context.maxDepth) {
|
|
195
|
+
throw new Error('Maximum decode depth exceeded')
|
|
196
|
+
}
|
|
197
|
+
checkCollectionLength('map', length, context.options.maxMapLength)
|
|
198
|
+
const _temp = decodeItems(buf, offset, 2 * length, headerLength, context, depth)
|
|
75
199
|
if (!_temp) return null
|
|
76
200
|
const [result, consumedBytes] = _temp
|
|
201
|
+
return [buildMap(result, length, context), consumedBytes]
|
|
202
|
+
}
|
|
77
203
|
|
|
204
|
+
function buildMap (result, length, context) {
|
|
78
205
|
let isPlainObject = !context.options.preferMap
|
|
79
206
|
|
|
80
207
|
if (isPlainObject) {
|
|
@@ -104,7 +231,7 @@ function decodeMap (buf, offset, length, headerLength, context) {
|
|
|
104
231
|
|
|
105
232
|
object[key] = val
|
|
106
233
|
}
|
|
107
|
-
return
|
|
234
|
+
return object
|
|
108
235
|
} else {
|
|
109
236
|
const mapping = new Map()
|
|
110
237
|
for (let i = 0; i < 2 * length; i += 2) {
|
|
@@ -112,11 +239,11 @@ function decodeMap (buf, offset, length, headerLength, context) {
|
|
|
112
239
|
const val = result[i + 1]
|
|
113
240
|
mapping.set(key, val)
|
|
114
241
|
}
|
|
115
|
-
return
|
|
242
|
+
return mapping
|
|
116
243
|
}
|
|
117
244
|
}
|
|
118
245
|
|
|
119
|
-
function tryDecode (buf, initialOffset, context) {
|
|
246
|
+
function tryDecode (buf, initialOffset, context, depth) {
|
|
120
247
|
if (buf.length <= initialOffset) return null
|
|
121
248
|
|
|
122
249
|
const bufLength = buf.length - initialOffset
|
|
@@ -133,13 +260,13 @@ function tryDecode (buf, initialOffset, context) {
|
|
|
133
260
|
const length = first & 0x0f
|
|
134
261
|
const headerSize = offset - initialOffset
|
|
135
262
|
// we have a map with less than 15 elements
|
|
136
|
-
return decodeMap(buf, offset, length, headerSize, context)
|
|
263
|
+
return decodeMap(buf, offset, length, headerSize, context, depth + 1)
|
|
137
264
|
}
|
|
138
265
|
if ((first & 0xf0) === 0x90) {
|
|
139
266
|
const length = first & 0x0f
|
|
140
267
|
const headerSize = offset - initialOffset
|
|
141
268
|
// we have an array with less than 15 elements
|
|
142
|
-
return decodeArray(buf, offset, length, headerSize, context)
|
|
269
|
+
return decodeArray(buf, offset, length, headerSize, context, depth + 1)
|
|
143
270
|
}
|
|
144
271
|
|
|
145
272
|
if ((first & 0xe0) === 0xa0) {
|
|
@@ -149,6 +276,7 @@ function tryDecode (buf, initialOffset, context) {
|
|
|
149
276
|
const result = buf.toString('utf8', offset, offset + length)
|
|
150
277
|
return [result, length + 1]
|
|
151
278
|
}
|
|
279
|
+
if (first === 0xc1) throw new Error('0xc1 is a reserved MessagePack byte')
|
|
152
280
|
if (first >= 0xc0 && first <= 0xc3) return decodeConstants(first)
|
|
153
281
|
if (first >= 0xc4 && first <= 0xc6) {
|
|
154
282
|
const length = buf.readUIntBE(offset, size - 1)
|
|
@@ -188,7 +316,7 @@ function tryDecode (buf, initialOffset, context) {
|
|
|
188
316
|
if (first >= 0xdc && first <= 0xdd) {
|
|
189
317
|
const length = buf.readUIntBE(offset, size - 1)
|
|
190
318
|
offset += size - 1
|
|
191
|
-
return decodeArray(buf, offset, length, size, context)
|
|
319
|
+
return decodeArray(buf, offset, length, size, context, depth + 1)
|
|
192
320
|
}
|
|
193
321
|
if (first >= 0xde && first <= 0xdf) {
|
|
194
322
|
let length
|
|
@@ -198,12 +326,12 @@ function tryDecode (buf, initialOffset, context) {
|
|
|
198
326
|
length = buf.readUInt16BE(offset)
|
|
199
327
|
offset += 2
|
|
200
328
|
// console.log(offset - initialOffset)
|
|
201
|
-
return decodeMap(buf, offset, length, 3, context)
|
|
329
|
+
return decodeMap(buf, offset, length, 3, context, depth + 1)
|
|
202
330
|
|
|
203
331
|
case 0xdf:
|
|
204
332
|
length = buf.readUInt32BE(offset)
|
|
205
333
|
offset += 4
|
|
206
|
-
return decodeMap(buf, offset, length, 5, context)
|
|
334
|
+
return decodeMap(buf, offset, length, 5, context, depth + 1)
|
|
207
335
|
}
|
|
208
336
|
}
|
|
209
337
|
if (first >= 0xe0) return [first - 0x100, 1] // 5 bits negative ints
|
|
@@ -216,7 +344,7 @@ function decodeSigned (buf, offset, size) {
|
|
|
216
344
|
if (size === 1) result = buf.readInt8(offset)
|
|
217
345
|
if (size === 2) result = buf.readInt16BE(offset)
|
|
218
346
|
if (size === 4) result = buf.readInt32BE(offset)
|
|
219
|
-
if (size === 8) result = readInt64BE(buf
|
|
347
|
+
if (size === 8) result = readInt64BE(buf, offset)
|
|
220
348
|
return [result, size + 1]
|
|
221
349
|
}
|
|
222
350
|
|
|
@@ -251,18 +379,14 @@ function decodeFloat (buf, offset, size) {
|
|
|
251
379
|
}
|
|
252
380
|
|
|
253
381
|
function readInt64BE (buf, offset) {
|
|
254
|
-
|
|
382
|
+
const negate = (buf.readUInt8(offset) & 0x80) === 0x80
|
|
383
|
+
let hi = buf.readUInt32BE(offset + 0)
|
|
384
|
+
let lo = buf.readUInt32BE(offset + 4)
|
|
255
385
|
|
|
256
386
|
if (negate) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const v = (buf[i] ^ 0xff) + carry
|
|
260
|
-
buf[i] = v & 0xff
|
|
261
|
-
carry = v >> 8
|
|
262
|
-
}
|
|
387
|
+
lo = (~lo + 1) >>> 0
|
|
388
|
+
hi = (~hi + (lo === 0 ? 1 : 0)) >>> 0
|
|
263
389
|
}
|
|
264
390
|
|
|
265
|
-
const hi = buf.readUInt32BE(offset + 0)
|
|
266
|
-
const lo = buf.readUInt32BE(offset + 4)
|
|
267
391
|
return (hi * 4294967296 + lo) * (negate ? -1 : +1)
|
|
268
392
|
}
|
package/lib/streams.js
CHANGED
|
@@ -54,6 +54,7 @@ function Decoder (opts) {
|
|
|
54
54
|
Base.call(this, opts)
|
|
55
55
|
|
|
56
56
|
this._chunks = bl()
|
|
57
|
+
this._decodeState = { stack: [] }
|
|
57
58
|
this._wrap = ('wrap' in opts) && opts.wrap
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -64,26 +65,26 @@ Decoder.prototype._transform = function (buf, enc, done) {
|
|
|
64
65
|
this._chunks.append(buf)
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
68
|
+
while (this._chunks.length > 0) {
|
|
69
|
+
try {
|
|
70
|
+
let result = this._msgpack.decode(this._chunks, this._decodeState)
|
|
71
|
+
if (this._wrap) {
|
|
72
|
+
result = { value: result }
|
|
73
|
+
}
|
|
74
|
+
this.push(result)
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (err instanceof this._msgpack.IncompleteBufferError) {
|
|
77
|
+
done()
|
|
78
|
+
} else {
|
|
79
|
+
this._chunks = bl()
|
|
80
|
+
this.destroy(err)
|
|
81
|
+
done()
|
|
82
|
+
}
|
|
83
|
+
return
|
|
78
84
|
}
|
|
79
|
-
return
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
|
|
83
|
-
this._transform(null, enc, done)
|
|
84
|
-
} else {
|
|
85
|
-
done()
|
|
86
|
-
}
|
|
87
|
+
done()
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
module.exports.decoder = Decoder
|
package/package.json
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Buffer = require('safe-buffer').Buffer
|
|
4
|
+
const test = require('tape').test
|
|
5
|
+
const msgpack = require('../')
|
|
6
|
+
const bl = require('bl')
|
|
7
|
+
|
|
8
|
+
test('decoding incomplete map32 headers', function (t) {
|
|
9
|
+
const pack = msgpack()
|
|
10
|
+
|
|
11
|
+
for (let length = 1; length < 5; length++) {
|
|
12
|
+
const buf = Buffer.alloc(length)
|
|
13
|
+
buf[0] = 0xdf
|
|
14
|
+
const input = bl().append(buf)
|
|
15
|
+
|
|
16
|
+
t.throws(function () {
|
|
17
|
+
pack.decode(input)
|
|
18
|
+
}, pack.IncompleteBufferError, 'must reject a ' + length + '-byte header as incomplete')
|
|
19
|
+
t.equal(input.length, length, 'must not consume an incomplete header')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
t.end()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('decoding an empty map32', function (t) {
|
|
26
|
+
const pack = msgpack()
|
|
27
|
+
const buf = Buffer.from([0xdf, 0x00, 0x00, 0x00, 0x00])
|
|
28
|
+
|
|
29
|
+
t.deepEqual(pack.decode(buf), {}, 'must decode a complete map32 header')
|
|
30
|
+
t.end()
|
|
31
|
+
})
|
|
@@ -34,6 +34,27 @@ test('encoding/decoding 64-bits big-endian signed integers', function (t) {
|
|
|
34
34
|
t.end()
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
+
test('decoding a negative 64-bits integer does not mutate the input', function (t) {
|
|
38
|
+
const encoder = msgpack()
|
|
39
|
+
const encoded = Buffer.from([0xd3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe])
|
|
40
|
+
const expected = encoded.toString('hex')
|
|
41
|
+
|
|
42
|
+
t.equal(encoder.decode(encoded), -2, 'decodes a direct buffer')
|
|
43
|
+
t.equal(encoded.toString('hex'), expected, 'does not mutate a direct buffer')
|
|
44
|
+
t.equal(encoder.decode(encoded), -2, 'decodes the same buffer consistently')
|
|
45
|
+
t.equal(encoded.toString('hex'), expected, 'leaves the buffer unchanged after repeated decoding')
|
|
46
|
+
|
|
47
|
+
const singleChunk = Buffer.from(encoded)
|
|
48
|
+
t.equal(encoder.decode(bl().append(singleChunk)), -2, 'decodes a single-chunk BufferList')
|
|
49
|
+
t.equal(singleChunk.toString('hex'), expected, 'does not mutate the underlying chunk')
|
|
50
|
+
|
|
51
|
+
const firstChunk = Buffer.from(encoded.slice(0, 4))
|
|
52
|
+
const secondChunk = Buffer.from(encoded.slice(4))
|
|
53
|
+
t.equal(encoder.decode(bl().append(firstChunk).append(secondChunk)), -2, 'decodes a split-chunk BufferList')
|
|
54
|
+
t.equal(Buffer.concat([firstChunk, secondChunk]).toString('hex'), expected, 'does not mutate split chunks')
|
|
55
|
+
t.end()
|
|
56
|
+
})
|
|
57
|
+
|
|
37
58
|
test('decoding an incomplete 64-bits big-endian signed integer', function (t) {
|
|
38
59
|
const encoder = msgpack()
|
|
39
60
|
let buf = Buffer.allocUnsafe(8)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Buffer = require('safe-buffer').Buffer
|
|
4
|
+
const test = require('tape').test
|
|
5
|
+
const msgpack = require('../')
|
|
6
|
+
|
|
7
|
+
test('maxArrayLength limits decoded arrays', function (t) {
|
|
8
|
+
const decoder = msgpack({ maxArrayLength: 2 })
|
|
9
|
+
|
|
10
|
+
t.deepEqual(decoder.decode(Buffer.from([0x92, 0x01, 0x02])), [1, 2], 'allows the configured limit')
|
|
11
|
+
t.throws(function () {
|
|
12
|
+
decoder.decode(Buffer.from([0x93, 0x01, 0x02, 0x03]))
|
|
13
|
+
}, /array length 3 exceeds configured limit of 2/, 'rejects fixarray over the limit')
|
|
14
|
+
t.throws(function () {
|
|
15
|
+
decoder.decode(Buffer.from([0xdc, 0x00, 0x03]))
|
|
16
|
+
}, /array length 3 exceeds configured limit of 2/, 'rejects array16 before decoding its elements')
|
|
17
|
+
t.throws(function () {
|
|
18
|
+
decoder.decode(Buffer.from([0xdd, 0x00, 0x00, 0x00, 0x03]))
|
|
19
|
+
}, /array length 3 exceeds configured limit of 2/, 'rejects array32 before decoding its elements')
|
|
20
|
+
t.end()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('maxMapLength limits decoded maps', function (t) {
|
|
24
|
+
const decoder = msgpack({ maxMapLength: 1 })
|
|
25
|
+
|
|
26
|
+
t.deepEqual(decoder.decode(Buffer.from([0x81, 0xa1, 0x61, 0x01])), { a: 1 }, 'allows the configured limit')
|
|
27
|
+
t.throws(function () {
|
|
28
|
+
decoder.decode(Buffer.from([0x82, 0xa1, 0x61, 0x01, 0xa1, 0x62, 0x02]))
|
|
29
|
+
}, /map length 2 exceeds configured limit of 1/, 'rejects fixmap over the limit')
|
|
30
|
+
t.throws(function () {
|
|
31
|
+
decoder.decode(Buffer.from([0xde, 0x00, 0x02]))
|
|
32
|
+
}, /map length 2 exceeds configured limit of 1/, 'rejects map16 before decoding its entries')
|
|
33
|
+
t.throws(function () {
|
|
34
|
+
decoder.decode(Buffer.from([0xdf, 0x00, 0x00, 0x00, 0x02]))
|
|
35
|
+
}, /map length 2 exceeds configured limit of 1/, 'rejects map32 before decoding its entries')
|
|
36
|
+
t.end()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('collection limits apply independently and to nested values', function (t) {
|
|
40
|
+
const decoder = msgpack({ maxArrayLength: 1, maxMapLength: 1 })
|
|
41
|
+
|
|
42
|
+
t.deepEqual(decoder.decode(Buffer.from([0x81, 0xa1, 0x61, 0x91, 0x01])), { a: [1] }, 'allows nested collections at their limits')
|
|
43
|
+
t.throws(function () {
|
|
44
|
+
decoder.decode(Buffer.from([0x81, 0xa1, 0x61, 0x92, 0x01, 0x02]))
|
|
45
|
+
}, /array length 2 exceeds configured limit of 1/, 'rejects a nested array over its limit')
|
|
46
|
+
t.end()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('collection limits must be non-negative safe integers', function (t) {
|
|
50
|
+
const invalid = [-1, 1.5, Infinity, null, '1']
|
|
51
|
+
|
|
52
|
+
invalid.forEach(function (value) {
|
|
53
|
+
t.throws(function () {
|
|
54
|
+
msgpack({ maxArrayLength: value })
|
|
55
|
+
}, /maxArrayLength must be a non-negative safe integer/, 'rejects maxArrayLength ' + value)
|
|
56
|
+
t.throws(function () {
|
|
57
|
+
msgpack({ maxMapLength: value })
|
|
58
|
+
}, /maxMapLength must be a non-negative safe integer/, 'rejects maxMapLength ' + value)
|
|
59
|
+
})
|
|
60
|
+
t.doesNotThrow(function () {
|
|
61
|
+
msgpack({ maxArrayLength: 0, maxMapLength: Number.MAX_SAFE_INTEGER })
|
|
62
|
+
}, 'accepts boundary values')
|
|
63
|
+
t.end()
|
|
64
|
+
})
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Buffer = require('safe-buffer').Buffer
|
|
4
|
+
const test = require('tape').test
|
|
5
|
+
const msgpack = require('../')
|
|
6
|
+
|
|
7
|
+
function nestedArray (depth) {
|
|
8
|
+
return Buffer.concat([Buffer.alloc(depth, 0x91), Buffer.from([0xc0])])
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function nestedMap (depth) {
|
|
12
|
+
const prefix = Buffer.alloc(depth * 3)
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < depth; i++) {
|
|
15
|
+
prefix[i * 3] = 0x81
|
|
16
|
+
prefix[i * 3 + 1] = 0xa1
|
|
17
|
+
prefix[i * 3 + 2] = 0x78
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return Buffer.concat([prefix, Buffer.from([0xc0])])
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test('limits array and map nesting depth by default', function (t) {
|
|
24
|
+
const pack = msgpack()
|
|
25
|
+
let array = pack.decode(nestedArray(100))
|
|
26
|
+
let map = pack.decode(nestedMap(100))
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < 100; i++) {
|
|
29
|
+
array = array[0]
|
|
30
|
+
map = map.x
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
t.equal(array, null, 'decodes arrays at the limit')
|
|
34
|
+
t.equal(map, null, 'decodes maps at the limit')
|
|
35
|
+
t.throws(function () {
|
|
36
|
+
pack.decode(nestedArray(101))
|
|
37
|
+
}, /Maximum decode depth exceeded/, 'rejects arrays over the limit')
|
|
38
|
+
t.throws(function () {
|
|
39
|
+
pack.decode(nestedMap(101))
|
|
40
|
+
}, /Maximum decode depth exceeded/, 'rejects maps over the limit')
|
|
41
|
+
t.end()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('supports a custom maximum nesting depth', function (t) {
|
|
45
|
+
const pack = msgpack({ maxDepth: 2 })
|
|
46
|
+
|
|
47
|
+
t.doesNotThrow(function () {
|
|
48
|
+
pack.decode(nestedArray(2))
|
|
49
|
+
}, 'decodes input at the configured limit')
|
|
50
|
+
t.throws(function () {
|
|
51
|
+
pack.decode(nestedArray(3))
|
|
52
|
+
}, /Maximum decode depth exceeded/, 'rejects input over the configured limit')
|
|
53
|
+
t.end()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('defaults maxDepth when other options are provided', function (t) {
|
|
57
|
+
const pack = msgpack({ forceFloat64: true })
|
|
58
|
+
|
|
59
|
+
t.throws(function () {
|
|
60
|
+
pack.decode(nestedArray(101))
|
|
61
|
+
}, /Maximum decode depth exceeded/)
|
|
62
|
+
t.end()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('allows scalars but no containers when maxDepth is zero', function (t) {
|
|
66
|
+
const pack = msgpack({ maxDepth: 0 })
|
|
67
|
+
|
|
68
|
+
t.equal(pack.decode(Buffer.from([0xc0])), null, 'decodes a scalar')
|
|
69
|
+
t.throws(function () {
|
|
70
|
+
pack.decode(Buffer.from([0x90]))
|
|
71
|
+
}, /Maximum decode depth exceeded/, 'rejects an empty container')
|
|
72
|
+
t.end()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('validates maxDepth', function (t) {
|
|
76
|
+
const invalid = [-1, 1.5, Infinity, NaN, '100', null]
|
|
77
|
+
|
|
78
|
+
invalid.forEach(function (maxDepth) {
|
|
79
|
+
t.throws(function () {
|
|
80
|
+
msgpack({ maxDepth })
|
|
81
|
+
}, /maxDepth must be a non-negative integer/)
|
|
82
|
+
})
|
|
83
|
+
t.end()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('reports a controlled error from the decoder stream', function (t) {
|
|
87
|
+
t.plan(2)
|
|
88
|
+
|
|
89
|
+
const decoder = msgpack().decoder()
|
|
90
|
+
decoder.on('error', function (err) {
|
|
91
|
+
t.equal(err.message, 'Maximum decode depth exceeded')
|
|
92
|
+
t.notOk(err instanceof RangeError, 'does not exhaust the native stack')
|
|
93
|
+
})
|
|
94
|
+
decoder.end(nestedArray(101))
|
|
95
|
+
})
|