@cloudpss/ubjson 0.5.40 → 0.5.42

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.
Files changed (83) hide show
  1. package/dist/base/decoder.d.ts +3 -1
  2. package/dist/base/decoder.d.ts.map +1 -1
  3. package/dist/base/decoder.js +3 -1
  4. package/dist/base/decoder.js.map +1 -1
  5. package/dist/base/encoder.d.ts +2 -0
  6. package/dist/base/encoder.d.ts.map +1 -1
  7. package/dist/base/encoder.js +6 -1
  8. package/dist/base/encoder.js.map +1 -1
  9. package/dist/encoder.d.ts +2 -1
  10. package/dist/encoder.d.ts.map +1 -1
  11. package/dist/encoder.js +2 -1
  12. package/dist/encoder.js.map +1 -1
  13. package/dist/helper/decode-ae.d.ts +53 -0
  14. package/dist/helper/decode-ae.d.ts.map +1 -0
  15. package/dist/helper/decode-ae.js +590 -0
  16. package/dist/helper/decode-ae.js.map +1 -0
  17. package/dist/helper/decode.d.ts +8 -1
  18. package/dist/helper/decode.d.ts.map +1 -1
  19. package/dist/helper/decode.js +46 -8
  20. package/dist/helper/decode.js.map +1 -1
  21. package/dist/helper/string-decoder.d.ts.map +1 -1
  22. package/dist/helper/string-decoder.js +92 -114
  23. package/dist/helper/string-decoder.js.map +1 -1
  24. package/dist/index.d.ts +6 -4
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +8 -8
  27. package/dist/index.js.map +1 -1
  28. package/dist/options.d.ts +22 -0
  29. package/dist/options.d.ts.map +1 -0
  30. package/dist/options.js +2 -0
  31. package/dist/options.js.map +1 -0
  32. package/dist/rxjs/decoder.d.ts +2 -1
  33. package/dist/rxjs/decoder.d.ts.map +1 -1
  34. package/dist/rxjs/decoder.js +84 -59
  35. package/dist/rxjs/decoder.js.map +1 -1
  36. package/dist/rxjs/encoder.d.ts +2 -1
  37. package/dist/rxjs/encoder.d.ts.map +1 -1
  38. package/dist/rxjs/encoder.js +2 -2
  39. package/dist/rxjs/encoder.js.map +1 -1
  40. package/dist/rxjs/index.d.ts +1 -0
  41. package/dist/rxjs/index.d.ts.map +1 -1
  42. package/dist/stream/decoder.d.ts +2 -1
  43. package/dist/stream/decoder.d.ts.map +1 -1
  44. package/dist/stream/decoder.js +2 -2
  45. package/dist/stream/decoder.js.map +1 -1
  46. package/dist/stream/encoder.d.ts +3 -2
  47. package/dist/stream/encoder.d.ts.map +1 -1
  48. package/dist/stream/encoder.js +2 -2
  49. package/dist/stream/encoder.js.map +1 -1
  50. package/dist/stream/index.d.ts +8 -6
  51. package/dist/stream/index.d.ts.map +1 -1
  52. package/dist/stream/index.js +12 -12
  53. package/dist/stream/index.js.map +1 -1
  54. package/dist/stream-helper/encoder.d.ts +2 -1
  55. package/dist/stream-helper/encoder.d.ts.map +1 -1
  56. package/dist/stream-helper/encoder.js +2 -1
  57. package/dist/stream-helper/encoder.js.map +1 -1
  58. package/package.json +3 -3
  59. package/src/base/decoder.ts +5 -1
  60. package/src/base/encoder.ts +6 -1
  61. package/src/encoder.ts +3 -1
  62. package/src/helper/decode-ae.ts +630 -0
  63. package/src/helper/decode.ts +52 -8
  64. package/src/helper/string-decoder.ts +87 -102
  65. package/src/index.ts +11 -8
  66. package/src/options.ts +22 -0
  67. package/src/rxjs/decoder.ts +72 -45
  68. package/src/rxjs/encoder.ts +3 -2
  69. package/src/rxjs/index.ts +1 -0
  70. package/src/stream/decoder.ts +3 -2
  71. package/src/stream/encoder.ts +4 -3
  72. package/src/stream/index.ts +14 -12
  73. package/src/stream-helper/encoder.ts +6 -1
  74. package/tests/.utils.js +10 -1
  75. package/tests/decode.js +52 -0
  76. package/tests/e2e/.data.js +11 -0
  77. package/tests/e2e/stream.js +13 -1
  78. package/tests/encode.js +1 -1
  79. package/dist/stream-helper/decoder.d.ts +0 -12
  80. package/dist/stream-helper/decoder.d.ts.map +0 -1
  81. package/dist/stream-helper/decoder.js +0 -18
  82. package/dist/stream-helper/decoder.js.map +0 -1
  83. package/src/stream-helper/decoder.ts +0 -21
@@ -2,6 +2,7 @@ import { constants } from '../helper/constants.js';
2
2
  import { encode, stringByteLength } from '../helper/string-encoder.js';
3
3
  import { EncoderBase } from '../base/encoder.js';
4
4
  import type { TypedArrayType } from '../helper/encode.js';
5
+ import type { EncodeOptions } from '../options.js';
5
6
 
6
7
  const BLOCK_SIZE = 1024 * 64; // 64 KiB
7
8
  const MAX_SIZE = 1024 * 1024 * 32; // 32 MiB
@@ -30,8 +31,12 @@ function free(buf: Uint8Array): boolean {
30
31
 
31
32
  /** 流式编码 UBJSON */
32
33
  export class StreamEncoderHelper extends EncoderBase {
33
- constructor(protected readonly onChunk: (chunk: Uint8Array) => void) {
34
+ constructor(
35
+ options: EncodeOptions | null | undefined,
36
+ protected readonly onChunk: (chunk: Uint8Array) => void,
37
+ ) {
34
38
  super();
39
+ this.sortObjectKeys = options?.sortObjectKeys ?? false;
35
40
  this.data = alloc(BLOCK_SIZE);
36
41
  this.view = new DataView(this.data.buffer);
37
42
  }
package/tests/.utils.js CHANGED
@@ -2,6 +2,7 @@ import { resetEnv as resetDecoderEnv } from '../dist/helper/string-decoder.js';
2
2
  import { resetEnv as resetEncoderEnv } from '../dist/helper/string-encoder.js';
3
3
  import { resetEncoder } from '../dist/encoder.js';
4
4
  import '../dist/helper/constants.js';
5
+ import '../dist/options.js';
5
6
 
6
7
  /**
7
8
  * 重设所有环境
@@ -33,5 +34,13 @@ export function toArray(...args) {
33
34
  * @returns {Uint8Array} Uint8Array
34
35
  */
35
36
  export function toBuffer(...args) {
36
- return Uint8Array.from(args, (x) => (typeof x == 'number' ? x : x.charCodeAt(0)));
37
+ const data = [];
38
+ for (const x of args) {
39
+ if (typeof x == 'number') {
40
+ data.push(x);
41
+ } else {
42
+ data.push(...Buffer.from(x, 'ascii'));
43
+ }
44
+ }
45
+ return Uint8Array.from(data);
37
46
  }
package/tests/decode.js CHANGED
@@ -504,3 +504,55 @@ test('decode (eof at marker)', () => {
504
504
  test('decode (eof at key)', () => {
505
505
  expect(() => decode(toBuffer('{', 'i', 2, 'a'))).toThrow(UnexpectedEof);
506
506
  });
507
+
508
+ describe('proto poisoning attack', () => {
509
+ it('should remove __proto__ key', () => {
510
+ const obj = /** @type {Record<string, unknown>} */ (
511
+ decode(toBuffer('{', 'i', 9, '__proto__', '{', 'i', 1, 'a', 'S', 'i', 3, 'abc', '}', '}'))
512
+ );
513
+ expect(Object.hasOwn(obj, '__proto__')).toBe(false);
514
+ expect(obj['__proto__']).toBe(Object.prototype);
515
+ });
516
+ it('should allow __proto__ key', () => {
517
+ const obj = /** @type {Record<string, unknown>} */ (
518
+ decode(toBuffer('{', 'i', 9, '__proto__', '{', 'i', 1, 'a', 'S', 'i', 3, 'abc', '}', '}'), {
519
+ protoAction: 'allow',
520
+ })
521
+ );
522
+ expect(Object.hasOwn(obj, '__proto__')).toBe(true);
523
+ expect(obj['__proto__']).toEqual({ a: 'abc' });
524
+ });
525
+ it('should throw on __proto__ key', () => {
526
+ expect(() =>
527
+ decode(toBuffer('{', 'i', 9, '__proto__', '{', 'i', 1, 'a', 'S', 'i', 3, 'abc', '}', '}'), {
528
+ protoAction: 'error',
529
+ }),
530
+ ).toThrow(`Unexpected "__proto__"`);
531
+ });
532
+ });
533
+
534
+ describe('constructor poisoning attack', () => {
535
+ it('should remove constructor key', () => {
536
+ const obj = /** @type {Record<string, unknown>} */ (
537
+ decode(toBuffer('{', 'i', 11, 'constructor', '{', 'i', 1, 'a', 'S', 'i', 3, 'abc', '}', '}'), {
538
+ constructorAction: 'remove',
539
+ })
540
+ );
541
+ expect(Object.hasOwn(obj, 'constructor')).toBe(false);
542
+ expect(obj.constructor).toBe(Object);
543
+ });
544
+ it('should allow constructor key', () => {
545
+ const obj = /** @type {Record<string, unknown>} */ (
546
+ decode(toBuffer('{', 'i', 11, 'constructor', '{', 'i', 1, 'a', 'S', 'i', 3, 'abc', '}', '}'))
547
+ );
548
+ expect(Object.hasOwn(obj, 'constructor')).toBe(true);
549
+ expect(obj.constructor).toEqual({ a: 'abc' });
550
+ });
551
+ it('should throw on constructor key', () => {
552
+ expect(() =>
553
+ decode(toBuffer('{', 'i', 11, 'constructor', '{', 'i', 1, 'a', 'S', 'i', 3, 'abc', '}', '}'), {
554
+ constructorAction: 'error',
555
+ }),
556
+ ).toThrow(`Unexpected "constructor"`);
557
+ });
558
+ });
@@ -465,6 +465,17 @@ EXPECTED['object with undefined values'] = { a: 1, c: { d: 2, f: null } };
465
465
  INPUTS['array with undefined values'] = [1, undefined, 2, undefined, 3];
466
466
  EXPECTED['array with undefined values'] = [1, null, 2, null, 3];
467
467
 
468
+ INPUTS['inject __proto__'] = JSON.parse('{"__proto__": {"a":1}}');
469
+ EXPECTED['inject __proto__'] = {};
470
+
468
471
  INPUTS['invalid __proto__'] = JSON.parse('{"__proto__":"xxx"}');
472
+ EXPECTED['invalid __proto__'] = {};
469
473
 
470
474
  INPUTS['null __proto__'] = JSON.parse('{"__proto__":null}');
475
+ EXPECTED['null __proto__'] = {};
476
+
477
+ INPUTS['inject constructor'] = { constructor: { prototype: { a: 1 } } };
478
+ INPUTS['invalid constructor prototype'] = { constructor: { prototype: 'xxx' } };
479
+ INPUTS['null constructor prototype'] = { constructor: { prototype: null } };
480
+ INPUTS['invalid constructor'] = { constructor: 'xxx' };
481
+ INPUTS['null constructor'] = { constructor: null };
@@ -13,7 +13,19 @@ describe('stream', () => {
13
13
  expect(decoded).toEqual(expected);
14
14
  }).rejects.toThrow(expected);
15
15
  } else {
16
- const decoded = await decodeStream(encodeStream(input));
16
+ const encoded = await encodeStream(input).toArray();
17
+ const data = encoded.flatMap((/** @type {Buffer} */ chunk) => {
18
+ // split to random chunks
19
+ const chunks = [];
20
+ let offset = 0;
21
+ while (offset < chunk.length) {
22
+ const size = Math.floor(Math.random() * chunk.length);
23
+ chunks.push(chunk.subarray(offset, offset + size));
24
+ offset += size;
25
+ }
26
+ return chunks;
27
+ });
28
+ const decoded = await decodeStream(Readable.from(data));
17
29
  expect(decoded).toEqual(expected);
18
30
  }
19
31
  });
package/tests/encode.js CHANGED
@@ -408,7 +408,7 @@ test('encode object', () => {
408
408
  });
409
409
 
410
410
  test('encode object (keep order)', () => {
411
- expect(toArray(encode({ b: 2, a: 1, c: 3 }))).toEqual(
411
+ expect(toArray(encode({ b: 2, a: 1, c: 3 }, { sortObjectKeys: true }))).toEqual(
412
412
  toArray('{', 'i', 1, 'a', 'U', 1, 'i', 1, 'b', 'U', 2, 'i', 1, 'c', 'U', 3, '}'),
413
413
  );
414
414
  // @ts-expect-error Access private property
@@ -1,12 +0,0 @@
1
- import { DecoderBase } from '../base/decoder.js';
2
- /** 未结束的流 */
3
- export declare const kEof: unique symbol;
4
- /** 流式解码 UBJSON */
5
- export declare class StreamDecoderHelper extends DecoderBase {
6
- /** @inheritdoc */
7
- protected eof(): never;
8
- /** 读取的字节数 */
9
- get readLength(): number;
10
- }
11
- export { UnexpectedEofError as UnexpectedEof } from '../helper/errors.js';
12
- //# sourceMappingURL=decoder.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"decoder.d.ts","sourceRoot":"","sources":["../../src/stream-helper/decoder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,YAAY;AACZ,eAAO,MAAM,IAAI,eAAgB,CAAC;AAElC,kBAAkB;AAClB,qBAAa,mBAAoB,SAAQ,WAAW;IAChD,kBAAkB;cACC,GAAG,IAAI,KAAK;IAM/B,aAAa;IACb,IAAI,UAAU,IAAI,MAAM,CAEvB;CACJ;AAED,OAAO,EAAE,kBAAkB,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
@@ -1,18 +0,0 @@
1
- import { DecoderBase } from '../base/decoder.js';
2
- /** 未结束的流 */
3
- export const kEof = Symbol('EOF');
4
- /** 流式解码 UBJSON */
5
- export class StreamDecoderHelper extends DecoderBase {
6
- /** @inheritdoc */
7
- eof() {
8
- // 性能优化,避免 new Error 的开销
9
- // eslint-disable-next-line @typescript-eslint/only-throw-error
10
- throw kEof;
11
- }
12
- /** 读取的字节数 */
13
- get readLength() {
14
- return this.offset;
15
- }
16
- }
17
- export { UnexpectedEofError as UnexpectedEof } from '../helper/errors.js';
18
- //# sourceMappingURL=decoder.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"decoder.js","sourceRoot":"","sources":["../../src/stream-helper/decoder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,YAAY;AACZ,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;AAElC,kBAAkB;AAClB,MAAM,OAAO,mBAAoB,SAAQ,WAAW;IAChD,kBAAkB;IACC,GAAG;QAClB,wBAAwB;QACxB,+DAA+D;QAC/D,MAAM,IAAI,CAAC;IACf,CAAC;IAED,aAAa;IACb,IAAI,UAAU;QACV,OAAO,IAAI,CAAC,MAAM,CAAC;IACvB,CAAC;CACJ;AAED,OAAO,EAAE,kBAAkB,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
@@ -1,21 +0,0 @@
1
- import { DecoderBase } from '../base/decoder.js';
2
-
3
- /** 未结束的流 */
4
- export const kEof = Symbol('EOF');
5
-
6
- /** 流式解码 UBJSON */
7
- export class StreamDecoderHelper extends DecoderBase {
8
- /** @inheritdoc */
9
- protected override eof(): never {
10
- // 性能优化,避免 new Error 的开销
11
- // eslint-disable-next-line @typescript-eslint/only-throw-error
12
- throw kEof;
13
- }
14
-
15
- /** 读取的字节数 */
16
- get readLength(): number {
17
- return this.offset;
18
- }
19
- }
20
-
21
- export { UnexpectedEofError as UnexpectedEof } from '../helper/errors.js';