msgpack5 6.0.1 → 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 +11 -3
- 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/encoder.js +22 -1
- 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/compatibility-mode.js +33 -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
|
@@ -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
|
+
})
|
|
@@ -18,6 +18,52 @@ test('decode throws when object has forbidden __proto__ property', function (t)
|
|
|
18
18
|
t.end()
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
+
test('decode defaults protoAction with partial options', function (t) {
|
|
22
|
+
const payload = { hello: 'world' }
|
|
23
|
+
Object.defineProperty(payload, '__proto__', {
|
|
24
|
+
value: { polluted: true },
|
|
25
|
+
enumerable: true
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const encoded = msgpack().encode(payload)
|
|
29
|
+
const options = [
|
|
30
|
+
{},
|
|
31
|
+
{ forceFloat64: true },
|
|
32
|
+
{ compatibilityMode: true },
|
|
33
|
+
{ disableTimestampEncoding: true },
|
|
34
|
+
{ preferMap: false },
|
|
35
|
+
{ sortKeys: true },
|
|
36
|
+
{ protoAction: undefined }
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
options.forEach(function (opts) {
|
|
40
|
+
t.throws(function () {
|
|
41
|
+
msgpack(opts).decode(encoded)
|
|
42
|
+
}, /Object contains forbidden prototype property/)
|
|
43
|
+
})
|
|
44
|
+
t.equal({}.polluted, undefined, 'does not affect Object.prototype')
|
|
45
|
+
t.end()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('does not mutate the caller options object', function (t) {
|
|
49
|
+
const options = { forceFloat64: true }
|
|
50
|
+
msgpack(options)
|
|
51
|
+
|
|
52
|
+
t.equal(options.protoAction, undefined)
|
|
53
|
+
t.end()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('validates protoAction', function (t) {
|
|
57
|
+
const invalid = [null, false, '', 'typo']
|
|
58
|
+
|
|
59
|
+
invalid.forEach(function (protoAction) {
|
|
60
|
+
t.throws(function () {
|
|
61
|
+
msgpack({ protoAction })
|
|
62
|
+
}, /protoAction must be "error", "remove", or "ignore"/)
|
|
63
|
+
})
|
|
64
|
+
t.end()
|
|
65
|
+
})
|
|
66
|
+
|
|
21
67
|
test('decode ignores forbidden __proto__ property if protoAction is "ignore"', function (t) {
|
|
22
68
|
const encoder = msgpack({ protoAction: 'ignore' })
|
|
23
69
|
|
|
@@ -30,6 +76,9 @@ test('decode ignores forbidden __proto__ property if protoAction is "ignore"', f
|
|
|
30
76
|
const decoded = encoder.decode(encoder.encode(payload))
|
|
31
77
|
|
|
32
78
|
t.equal(decoded.polluted, true)
|
|
79
|
+
t.notEqual(Object.getPrototypeOf(decoded), Object.prototype)
|
|
80
|
+
t.notOk(Object.prototype.hasOwnProperty.call(decoded, 'polluted'))
|
|
81
|
+
t.equal({}.polluted, undefined, 'does not affect Object.prototype')
|
|
33
82
|
t.end()
|
|
34
83
|
})
|
|
35
84
|
|
|
@@ -45,5 +94,6 @@ test('decode removes forbidden __proto__ property if protoAction is "remove"', f
|
|
|
45
94
|
const decoded = encoder.decode(encoder.encode(payload))
|
|
46
95
|
|
|
47
96
|
t.equal(decoded.polluted, undefined)
|
|
97
|
+
t.equal(Object.getPrototypeOf(decoded), Object.prototype)
|
|
48
98
|
t.end()
|
|
49
99
|
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Buffer = require('safe-buffer').Buffer
|
|
4
|
+
const test = require('tape').test
|
|
5
|
+
const msgpack = require('../')
|
|
6
|
+
|
|
7
|
+
const reservedByteError = '0xc1 is a reserved MessagePack byte'
|
|
8
|
+
|
|
9
|
+
test('reserved byte is invalid rather than incomplete', function (t) {
|
|
10
|
+
t.plan(3)
|
|
11
|
+
|
|
12
|
+
const pack = msgpack()
|
|
13
|
+
let error
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
pack.decode(Buffer.from([0xc1]))
|
|
17
|
+
} catch (err) {
|
|
18
|
+
error = err
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
t.ok(error, 'must throw an error')
|
|
22
|
+
t.notOk(error instanceof pack.IncompleteBufferError, 'must not report incomplete input')
|
|
23
|
+
t.equal(error.message, reservedByteError, 'must identify the reserved byte')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('stream decoder rejects reserved byte without retaining input', function (t) {
|
|
27
|
+
t.plan(5)
|
|
28
|
+
|
|
29
|
+
const decoder = msgpack().decoder()
|
|
30
|
+
let decoded = 0
|
|
31
|
+
let errors = 0
|
|
32
|
+
|
|
33
|
+
decoder.on('data', function () {
|
|
34
|
+
decoded++
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
decoder.on('error', function (err) {
|
|
38
|
+
errors++
|
|
39
|
+
t.equal(err.message, reservedByteError, 'must emit the decoding error')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
decoder.on('close', function () {
|
|
43
|
+
t.equal(decoded, 0, 'must not emit decoded values')
|
|
44
|
+
t.equal(errors, 1, 'must emit one error')
|
|
45
|
+
t.equal(decoder._chunks.length, 0, 'must release buffered input')
|
|
46
|
+
t.ok(decoder.destroyed, 'must stop accepting input')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
decoder.write(Buffer.concat([
|
|
50
|
+
Buffer.from([0xc1]),
|
|
51
|
+
Buffer.alloc(200 * 1024, 0x01)
|
|
52
|
+
]))
|
|
53
|
+
})
|
package/test/streams.js
CHANGED
|
@@ -183,6 +183,84 @@ test('concatenated buffers work', function (t) {
|
|
|
183
183
|
encoder.end()
|
|
184
184
|
})
|
|
185
185
|
|
|
186
|
+
test('map32 header split across chunks', function (t) {
|
|
187
|
+
t.plan(2)
|
|
188
|
+
|
|
189
|
+
const decoder = msgpack().decoder()
|
|
190
|
+
|
|
191
|
+
decoder.on('data', function (value) {
|
|
192
|
+
t.deepEqual(value, {}, 'must decode the map')
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
decoder.on('error', function (err) {
|
|
196
|
+
t.fail(err.message)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
decoder.on('end', function () {
|
|
200
|
+
t.pass('must end normally')
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
const encoded = [0xdf, 0x00, 0x00, 0x00, 0x00]
|
|
204
|
+
encoded.forEach(function (byte) {
|
|
205
|
+
decoder.write(Buffer.from([byte]))
|
|
206
|
+
})
|
|
207
|
+
decoder.end()
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test('many concatenated values do not overflow the stack', function (t) {
|
|
211
|
+
t.plan(2)
|
|
212
|
+
|
|
213
|
+
const total = 50000
|
|
214
|
+
const decoder = msgpack().decoder()
|
|
215
|
+
let decoded = 0
|
|
216
|
+
|
|
217
|
+
decoder.on('data', function () {
|
|
218
|
+
decoded++
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
decoder.write(Buffer.alloc(total, 0x01), function (err) {
|
|
222
|
+
t.error(err, 'must decode without an error')
|
|
223
|
+
t.equal(decoded, total, 'must decode every value')
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
test('incomplete containers resume without decoding elements again', function (t) {
|
|
228
|
+
t.plan(3)
|
|
229
|
+
|
|
230
|
+
const pack = msgpack()
|
|
231
|
+
const decoder = pack.decoder()
|
|
232
|
+
const values = []
|
|
233
|
+
let decodeCalls = 0
|
|
234
|
+
let maxBuffered = 0
|
|
235
|
+
|
|
236
|
+
function MyType (value) {
|
|
237
|
+
this.value = value
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
pack.register(0x42, MyType, function (obj) {
|
|
241
|
+
return Buffer.from([obj.value])
|
|
242
|
+
}, function (buf) {
|
|
243
|
+
decodeCalls++
|
|
244
|
+
return buf.readUInt8(0)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
for (let i = 0; i < 100; i++) {
|
|
248
|
+
values.push(new MyType(i))
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
decoder.on('data', function (result) {
|
|
252
|
+
t.deepEqual(result, { values: values.map(function (value) { return value.value }) })
|
|
253
|
+
t.equal(decodeCalls, values.length, 'each completed element is decoded once')
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
const encoded = pack.encode({ values })
|
|
257
|
+
for (let i = 0; i < encoded.length; i++) {
|
|
258
|
+
decoder.write(encoded.slice(i, i + 1))
|
|
259
|
+
maxBuffered = Math.max(maxBuffered, decoder._chunks.length)
|
|
260
|
+
}
|
|
261
|
+
t.ok(maxBuffered <= 6, 'only the current incomplete value remains buffered')
|
|
262
|
+
decoder.end()
|
|
263
|
+
})
|
|
186
264
|
test('nil processing works', function (t) {
|
|
187
265
|
t.plan(3)
|
|
188
266
|
|