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.
@@ -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