@cloudpss/ubjson 0.5.41 → 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.
- package/dist/base/decoder.d.ts +3 -1
- package/dist/base/decoder.d.ts.map +1 -1
- package/dist/base/decoder.js +3 -1
- package/dist/base/decoder.js.map +1 -1
- package/dist/helper/decode-ae.d.ts +4 -1
- package/dist/helper/decode-ae.d.ts.map +1 -1
- package/dist/helper/decode-ae.js +15 -9
- package/dist/helper/decode-ae.js.map +1 -1
- package/dist/helper/decode.d.ts +8 -1
- package/dist/helper/decode.d.ts.map +1 -1
- package/dist/helper/decode.js +39 -3
- package/dist/helper/decode.js.map +1 -1
- package/dist/helper/string-decoder.d.ts.map +1 -1
- package/dist/helper/string-decoder.js +92 -114
- package/dist/helper/string-decoder.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +17 -1
- package/dist/options.d.ts.map +1 -1
- package/dist/rxjs/decoder.d.ts +2 -1
- package/dist/rxjs/decoder.d.ts.map +1 -1
- package/dist/rxjs/decoder.js +82 -83
- package/dist/rxjs/decoder.js.map +1 -1
- package/dist/rxjs/index.d.ts +1 -1
- package/dist/rxjs/index.d.ts.map +1 -1
- package/dist/stream/decoder.d.ts +2 -1
- package/dist/stream/decoder.d.ts.map +1 -1
- package/dist/stream/decoder.js +2 -2
- package/dist/stream/decoder.js.map +1 -1
- package/dist/stream/index.d.ts +5 -5
- package/dist/stream/index.d.ts.map +1 -1
- package/dist/stream/index.js +6 -6
- package/dist/stream/index.js.map +1 -1
- package/package.json +2 -2
- package/src/base/decoder.ts +5 -1
- package/src/helper/decode-ae.ts +18 -9
- package/src/helper/decode.ts +46 -3
- package/src/helper/string-decoder.ts +87 -102
- package/src/index.ts +6 -6
- package/src/options.ts +18 -1
- package/src/rxjs/decoder.ts +7 -6
- package/src/rxjs/index.ts +1 -1
- package/src/stream/decoder.ts +3 -2
- package/src/stream/index.ts +8 -8
- package/tests/.utils.js +9 -1
- package/tests/decode.js +52 -0
- package/tests/e2e/.data.js +11 -0
- package/dist/stream-helper/decoder.d.ts +0 -12
- package/dist/stream-helper/decoder.d.ts.map +0 -1
- package/dist/stream-helper/decoder.js +0 -18
- package/dist/stream-helper/decoder.js.map +0 -1
- package/src/stream-helper/decoder.ts +0 -21
package/src/helper/decode.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DecodeOptions } from '../options.js';
|
|
1
2
|
import { constants } from './constants.js';
|
|
2
3
|
import { UnexpectedEofError, unsupportedType } from './errors.js';
|
|
3
4
|
import { decode } from './string-decoder.js';
|
|
@@ -15,10 +16,12 @@ export interface DecodeCursor {
|
|
|
15
16
|
offset: number;
|
|
16
17
|
/** 读取到末尾时调用 */
|
|
17
18
|
eof(): never;
|
|
19
|
+
/** 选项 */
|
|
20
|
+
readonly options: DecodeOptions | undefined;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
/** 创建数据包装 */
|
|
21
|
-
export function DecodeCursor(data: BinaryData): DecodeCursor {
|
|
24
|
+
export function DecodeCursor(data: BinaryData, options?: DecodeOptions): DecodeCursor {
|
|
22
25
|
const d = toUint8Array(data);
|
|
23
26
|
const v = new DataView(d.buffer, d.byteOffset, d.byteLength);
|
|
24
27
|
return {
|
|
@@ -28,6 +31,7 @@ export function DecodeCursor(data: BinaryData): DecodeCursor {
|
|
|
28
31
|
eof() {
|
|
29
32
|
throw new UnexpectedEofError();
|
|
30
33
|
},
|
|
34
|
+
options,
|
|
31
35
|
};
|
|
32
36
|
}
|
|
33
37
|
|
|
@@ -160,6 +164,35 @@ export function read(cursor: DecodeCursor): unknown {
|
|
|
160
164
|
return readData(cursor, marker);
|
|
161
165
|
}
|
|
162
166
|
|
|
167
|
+
/** 处理 `__proto__` */
|
|
168
|
+
export function protoAction(cursor: Pick<DecodeCursor, 'options'>, obj: Record<string, unknown>, value: unknown): void {
|
|
169
|
+
if (cursor.options?.protoAction === 'error') {
|
|
170
|
+
throw new Error('Unexpected "__proto__"');
|
|
171
|
+
} else if (cursor.options?.protoAction === 'allow') {
|
|
172
|
+
defineProperty(obj, '__proto__', {
|
|
173
|
+
value,
|
|
174
|
+
enumerable: true,
|
|
175
|
+
configurable: true,
|
|
176
|
+
writable: true,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** 处理 `constructor` */
|
|
182
|
+
export function constructorAction(
|
|
183
|
+
cursor: Pick<DecodeCursor, 'options'>,
|
|
184
|
+
obj: Record<string, unknown>,
|
|
185
|
+
value: unknown,
|
|
186
|
+
): void {
|
|
187
|
+
if (cursor.options?.constructorAction === 'error') {
|
|
188
|
+
throw new Error('Unexpected "constructor"');
|
|
189
|
+
} else if (cursor.options?.constructorAction === 'remove') {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
193
|
+
obj['constructor'] = value;
|
|
194
|
+
}
|
|
195
|
+
|
|
163
196
|
/** 读取优化对象数据 */
|
|
164
197
|
function readObjectOptimizedData(cursor: DecodeCursor, marker: OptimizedFormatMarkers): unknown {
|
|
165
198
|
const { count, type } = marker;
|
|
@@ -168,7 +201,12 @@ function readObjectOptimizedData(cursor: DecodeCursor, marker: OptimizedFormatMa
|
|
|
168
201
|
const key = readKey(cursor);
|
|
169
202
|
const value = readData(cursor, type ?? readMarker(cursor));
|
|
170
203
|
if (key === '__proto__') {
|
|
171
|
-
|
|
204
|
+
protoAction(cursor, object, value);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (key === 'constructor') {
|
|
208
|
+
constructorAction(cursor, object, value);
|
|
209
|
+
continue;
|
|
172
210
|
}
|
|
173
211
|
object[key] = value;
|
|
174
212
|
}
|
|
@@ -198,7 +236,12 @@ export function readData(cursor: DecodeCursor, marker: number): unknown {
|
|
|
198
236
|
const key = readKey(cursor);
|
|
199
237
|
const value = read(cursor);
|
|
200
238
|
if (key === '__proto__') {
|
|
201
|
-
|
|
239
|
+
protoAction(cursor, object, value);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (key === 'constructor') {
|
|
243
|
+
constructorAction(cursor, object, value);
|
|
244
|
+
continue;
|
|
202
245
|
}
|
|
203
246
|
object[key] = value;
|
|
204
247
|
}
|
|
@@ -69,118 +69,103 @@ export function shortStringInJS(buf: Uint8Array, begin: number, length: number):
|
|
|
69
69
|
if (length < 4) {
|
|
70
70
|
if (length < 2) {
|
|
71
71
|
if (length === 0) return '';
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if ((a & 0x80) > 1) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
return fromCharCode(a);
|
|
78
|
-
}
|
|
79
|
-
} else {
|
|
80
|
-
const a = buf[begin++]!;
|
|
81
|
-
const b = buf[begin++]!;
|
|
82
|
-
if ((a & 0x80) > 0 || (b & 0x80) > 0) {
|
|
72
|
+
const a = buf[begin]!;
|
|
73
|
+
if ((a & 0x80) > 0) {
|
|
83
74
|
return;
|
|
84
75
|
}
|
|
85
|
-
|
|
86
|
-
const c = buf[begin++]!;
|
|
87
|
-
if ((c & 0x80) > 0) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
return fromCharCode(a, b, c);
|
|
76
|
+
return fromCharCode(a);
|
|
91
77
|
}
|
|
92
|
-
} else {
|
|
93
78
|
const a = buf[begin++]!;
|
|
94
79
|
const b = buf[begin++]!;
|
|
80
|
+
if ((a & 0x80) > 0 || (b & 0x80) > 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (length < 3) return fromCharCode(a, b);
|
|
95
84
|
const c = buf[begin++]!;
|
|
96
|
-
|
|
97
|
-
if ((a & 0x80) > 0 || (b & 0x80) > 0 || (c & 0x80) > 0 || (d & 0x80) > 0) {
|
|
85
|
+
if ((c & 0x80) > 0) {
|
|
98
86
|
return;
|
|
99
87
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
if (length < 7) return fromCharCode(a, b, c, d, e, f);
|
|
116
|
-
const g = buf[begin++]!;
|
|
117
|
-
if ((g & 0x80) > 0) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
return fromCharCode(a, b, c, d, e, f, g);
|
|
121
|
-
} else {
|
|
122
|
-
const e = buf[begin++]!;
|
|
123
|
-
const f = buf[begin++]!;
|
|
124
|
-
const g = buf[begin++]!;
|
|
125
|
-
const h = buf[begin++]!;
|
|
126
|
-
if ((e & 0x80) > 0 || (f & 0x80) > 0 || (g & 0x80) > 0 || (h & 0x80) > 0) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (length < 10) {
|
|
130
|
-
if (length === 8) return fromCharCode(a, b, c, d, e, f, g, h);
|
|
131
|
-
else {
|
|
132
|
-
const i = buf[begin++]!;
|
|
133
|
-
if ((i & 0x80) > 0) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
return fromCharCode(a, b, c, d, e, f, g, h, i);
|
|
137
|
-
}
|
|
138
|
-
} else if (length < 12) {
|
|
139
|
-
const i = buf[begin++]!;
|
|
140
|
-
const j = buf[begin++]!;
|
|
141
|
-
if ((i & 0x80) > 0 || (j & 0x80) > 0) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (length < 11) return fromCharCode(a, b, c, d, e, f, g, h, i, j);
|
|
145
|
-
const k = buf[begin++]!;
|
|
146
|
-
if ((k & 0x80) > 0) {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k);
|
|
150
|
-
} else {
|
|
151
|
-
const i = buf[begin++]!;
|
|
152
|
-
const j = buf[begin++]!;
|
|
153
|
-
const k = buf[begin++]!;
|
|
154
|
-
const l = buf[begin++]!;
|
|
155
|
-
if ((i & 0x80) > 0 || (j & 0x80) > 0 || (k & 0x80) > 0 || (l & 0x80) > 0) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
if (length < 14) {
|
|
159
|
-
if (length === 12) return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l);
|
|
160
|
-
else {
|
|
161
|
-
const m = buf[begin++]!;
|
|
162
|
-
if ((m & 0x80) > 0) {
|
|
163
|
-
begin -= 13;
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m);
|
|
167
|
-
}
|
|
168
|
-
} else {
|
|
169
|
-
const m = buf[begin++]!;
|
|
170
|
-
const n = buf[begin++]!;
|
|
171
|
-
if ((m & 0x80) > 0 || (n & 0x80) > 0) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (length < 15) return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n);
|
|
175
|
-
const o = buf[begin++]!;
|
|
176
|
-
if ((o & 0x80) > 0) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
88
|
+
return fromCharCode(a, b, c);
|
|
89
|
+
}
|
|
90
|
+
const a = buf[begin++]!;
|
|
91
|
+
const b = buf[begin++]!;
|
|
92
|
+
const c = buf[begin++]!;
|
|
93
|
+
const d = buf[begin++]!;
|
|
94
|
+
if ((a & 0x80) > 0 || (b & 0x80) > 0 || (c & 0x80) > 0 || (d & 0x80) > 0) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (length < 6) {
|
|
98
|
+
if (length === 4) return fromCharCode(a, b, c, d);
|
|
99
|
+
const e = buf[begin++]!;
|
|
100
|
+
if ((e & 0x80) > 0) {
|
|
101
|
+
return;
|
|
182
102
|
}
|
|
103
|
+
return fromCharCode(a, b, c, d, e);
|
|
104
|
+
}
|
|
105
|
+
const e = buf[begin++]!;
|
|
106
|
+
const f = buf[begin++]!;
|
|
107
|
+
if ((e & 0x80) > 0 || (f & 0x80) > 0) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (length < 8) {
|
|
111
|
+
if (length < 7) return fromCharCode(a, b, c, d, e, f);
|
|
112
|
+
const g = buf[begin++]!;
|
|
113
|
+
if ((g & 0x80) > 0) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
return fromCharCode(a, b, c, d, e, f, g);
|
|
117
|
+
}
|
|
118
|
+
const g = buf[begin++]!;
|
|
119
|
+
const h = buf[begin++]!;
|
|
120
|
+
if ((g & 0x80) > 0 || (h & 0x80) > 0) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (length < 10) {
|
|
124
|
+
if (length === 8) return fromCharCode(a, b, c, d, e, f, g, h);
|
|
125
|
+
const i = buf[begin++]!;
|
|
126
|
+
if ((i & 0x80) > 0) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
return fromCharCode(a, b, c, d, e, f, g, h, i);
|
|
130
|
+
}
|
|
131
|
+
const i = buf[begin++]!;
|
|
132
|
+
const j = buf[begin++]!;
|
|
133
|
+
if ((i & 0x80) > 0 || (j & 0x80) > 0) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (length < 12) {
|
|
137
|
+
if (length < 11) return fromCharCode(a, b, c, d, e, f, g, h, i, j);
|
|
138
|
+
const k = buf[begin++]!;
|
|
139
|
+
if ((k & 0x80) > 0) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k);
|
|
143
|
+
}
|
|
144
|
+
const k = buf[begin++]!;
|
|
145
|
+
const l = buf[begin++]!;
|
|
146
|
+
if ((k & 0x80) > 0 || (l & 0x80) > 0) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (length < 14) {
|
|
150
|
+
if (length === 12) return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l);
|
|
151
|
+
const m = buf[begin++]!;
|
|
152
|
+
if ((m & 0x80) > 0) {
|
|
153
|
+
begin -= 13;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m);
|
|
157
|
+
}
|
|
158
|
+
const m = buf[begin++]!;
|
|
159
|
+
const n = buf[begin++]!;
|
|
160
|
+
if ((m & 0x80) > 0 || (n & 0x80) > 0) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (length < 15) return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n);
|
|
164
|
+
const o = buf[begin++]!;
|
|
165
|
+
if ((o & 0x80) > 0) {
|
|
166
|
+
return;
|
|
183
167
|
}
|
|
168
|
+
return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o);
|
|
184
169
|
}
|
|
185
170
|
|
|
186
171
|
let TEXT_DECODER: TextDecoder | null;
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { getEncoder } from './encoder.js';
|
|
2
2
|
import { Decoder } from './decoder.js';
|
|
3
|
-
import type { EncodeOptions } from './options.js';
|
|
3
|
+
import type { EncodeOptions, DecodeOptions } from './options.js';
|
|
4
4
|
|
|
5
5
|
export { UnexpectedEofError as UnexpectedEof } from './helper/errors.js';
|
|
6
|
-
export type { EncodeOptions };
|
|
6
|
+
export type { EncodeOptions, DecodeOptions };
|
|
7
7
|
|
|
8
8
|
/** 编码为 UBJSON */
|
|
9
9
|
export function encode(value: unknown, options?: EncodeOptions): Uint8Array {
|
|
@@ -16,14 +16,14 @@ export function encodeMany(value: Iterable<unknown>, options?: EncodeOptions): U
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/** 解码 UBJSON */
|
|
19
|
-
export function decode(value: BinaryData): unknown {
|
|
20
|
-
const decoder = new Decoder(value);
|
|
19
|
+
export function decode(value: BinaryData, options?: DecodeOptions): unknown {
|
|
20
|
+
const decoder = new Decoder(value, options);
|
|
21
21
|
return decoder.decode();
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/** 解码 UBJSON */
|
|
25
|
-
export function* decodeMany(value: BinaryData): Iterable<unknown> {
|
|
26
|
-
const decoder = new Decoder(value);
|
|
25
|
+
export function* decodeMany(value: BinaryData, options?: DecodeOptions): Iterable<unknown> {
|
|
26
|
+
const decoder = new Decoder(value, options);
|
|
27
27
|
while (!decoder.ended) {
|
|
28
28
|
yield decoder.decode();
|
|
29
29
|
}
|
package/src/options.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
/** 序列化选项 */
|
|
2
2
|
export interface EncodeOptions {
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* 序列化对象时排序属性
|
|
5
|
+
* @default false
|
|
6
|
+
*/
|
|
4
7
|
sortObjectKeys?: boolean;
|
|
5
8
|
}
|
|
9
|
+
|
|
10
|
+
/** 反序列化选项 */
|
|
11
|
+
export interface DecodeOptions {
|
|
12
|
+
/**
|
|
13
|
+
* 解析对象时对 `__proto__` 属性的处理方式
|
|
14
|
+
* @default 'remove'
|
|
15
|
+
*/
|
|
16
|
+
protoAction?: 'error' | 'allow' | 'remove';
|
|
17
|
+
/**
|
|
18
|
+
* 解析对象时对 `constructor` 属性的处理方式
|
|
19
|
+
* @default 'allow'
|
|
20
|
+
*/
|
|
21
|
+
constructorAction?: 'error' | 'allow' | 'remove';
|
|
22
|
+
}
|
package/src/rxjs/decoder.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { Observable, type OperatorFunction } from 'rxjs';
|
|
2
|
-
import { UnexpectedEof } from '../stream-helper/decoder.js';
|
|
3
2
|
import { toUint8Array } from '../helper/utils.js';
|
|
4
3
|
import { read } from '../helper/decode-ae.js';
|
|
4
|
+
import type { DecodeOptions } from '../options.js';
|
|
5
|
+
import { UnexpectedEofError } from '../helper/errors.js';
|
|
5
6
|
|
|
6
7
|
const EMPTY_BUFFER = new Uint8Array(0);
|
|
7
8
|
const EMPTY_VIEW = new DataView(EMPTY_BUFFER.buffer);
|
|
8
9
|
|
|
9
10
|
/** 流式解码 UBJSON */
|
|
10
|
-
export function decode(): OperatorFunction<BinaryData, unknown> {
|
|
11
|
-
return (observable) =>
|
|
12
|
-
|
|
11
|
+
export function decode(options?: DecodeOptions): OperatorFunction<BinaryData, unknown> {
|
|
12
|
+
return (observable) =>
|
|
13
|
+
new Observable<unknown>((subscriber) => {
|
|
13
14
|
const data = EMPTY_BUFFER;
|
|
14
15
|
const cursor = {
|
|
15
16
|
view: EMPTY_VIEW,
|
|
@@ -17,6 +18,7 @@ export function decode(): OperatorFunction<BinaryData, unknown> {
|
|
|
17
18
|
capacity: 0,
|
|
18
19
|
size: 0,
|
|
19
20
|
offset: 0,
|
|
21
|
+
options,
|
|
20
22
|
};
|
|
21
23
|
/** reader 返回的还需接收的字节数 */
|
|
22
24
|
let required = 1;
|
|
@@ -84,7 +86,7 @@ export function decode(): OperatorFunction<BinaryData, unknown> {
|
|
|
84
86
|
},
|
|
85
87
|
complete() {
|
|
86
88
|
if (cursor.size > cursor.offset) {
|
|
87
|
-
subscriber.error(new
|
|
89
|
+
subscriber.error(new UnexpectedEofError());
|
|
88
90
|
} else {
|
|
89
91
|
subscriber.complete();
|
|
90
92
|
}
|
|
@@ -93,5 +95,4 @@ export function decode(): OperatorFunction<BinaryData, unknown> {
|
|
|
93
95
|
},
|
|
94
96
|
});
|
|
95
97
|
});
|
|
96
|
-
};
|
|
97
98
|
}
|
package/src/rxjs/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { encode } from './encoder.js';
|
|
2
2
|
export { decode } from './decoder.js';
|
|
3
3
|
export { UnexpectedEofError as UnexpectedEof } from '../helper/errors.js';
|
|
4
|
-
export type { EncodeOptions } from '../options.js';
|
|
4
|
+
export type { EncodeOptions, DecodeOptions } from '../options.js';
|
package/src/stream/decoder.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { Transform, type TransformCallback } from 'node:stream';
|
|
2
2
|
import { Subject } from 'rxjs';
|
|
3
3
|
import { decode } from '../rxjs/decoder.js';
|
|
4
|
+
import type { DecodeOptions } from '../options.js';
|
|
4
5
|
|
|
5
6
|
/** 流式解码 UBJSON */
|
|
6
7
|
export class StreamDecoder extends Transform {
|
|
7
|
-
constructor() {
|
|
8
|
+
constructor(options?: DecodeOptions) {
|
|
8
9
|
super({
|
|
9
10
|
readableObjectMode: true,
|
|
10
11
|
writableObjectMode: false,
|
|
11
12
|
});
|
|
12
13
|
this.buffer = new Subject<BinaryData>();
|
|
13
|
-
this.buffer.pipe(decode()).subscribe({
|
|
14
|
+
this.buffer.pipe(decode(options)).subscribe({
|
|
14
15
|
next: (value) => {
|
|
15
16
|
// null is not allowed in a stream
|
|
16
17
|
if (value == null) return;
|
package/src/stream/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Readable, type Transform } from 'node:stream';
|
|
2
2
|
import { StreamEncoder } from './encoder.js';
|
|
3
3
|
import { StreamDecoder } from './decoder.js';
|
|
4
|
-
import type { EncodeOptions } from '../options.js';
|
|
4
|
+
import type { EncodeOptions, DecodeOptions } from '../options.js';
|
|
5
5
|
|
|
6
6
|
export { UnexpectedEofError as UnexpectedEof } from '../helper/errors.js';
|
|
7
|
-
export type { EncodeOptions };
|
|
7
|
+
export type { EncodeOptions, DecodeOptions };
|
|
8
8
|
|
|
9
9
|
/** 编码为 UBJSON */
|
|
10
10
|
export function encode(value: unknown, options?: EncodeOptions): Readable {
|
|
@@ -39,9 +39,9 @@ export function encoder(options?: EncodeOptions): Transform {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/** 解码 UBJSON */
|
|
42
|
-
export async function decode(stream: NodeJS.ReadableStream): Promise<unknown> {
|
|
42
|
+
export async function decode(stream: NodeJS.ReadableStream, options?: DecodeOptions): Promise<unknown> {
|
|
43
43
|
return new Promise((resolve, reject) => {
|
|
44
|
-
const decoder = new StreamDecoder();
|
|
44
|
+
const decoder = new StreamDecoder(options);
|
|
45
45
|
decoder.on('error', reject);
|
|
46
46
|
decoder.on('end', resolve);
|
|
47
47
|
decoder.on('data', resolve);
|
|
@@ -50,12 +50,12 @@ export async function decode(stream: NodeJS.ReadableStream): Promise<unknown> {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/** 解码 UBJSON */
|
|
53
|
-
export function decodeMany(stream: NodeJS.ReadableStream): AsyncIterable<unknown> {
|
|
54
|
-
const decoder = new StreamDecoder();
|
|
53
|
+
export function decodeMany(stream: NodeJS.ReadableStream, options?: DecodeOptions): AsyncIterable<unknown> {
|
|
54
|
+
const decoder = new StreamDecoder(options);
|
|
55
55
|
return stream.pipe(decoder);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/** 解码 UBJSON */
|
|
59
|
-
export function decoder(): Transform {
|
|
60
|
-
return new StreamDecoder();
|
|
59
|
+
export function decoder(options?: DecodeOptions): Transform {
|
|
60
|
+
return new StreamDecoder(options);
|
|
61
61
|
}
|
package/tests/.utils.js
CHANGED
|
@@ -34,5 +34,13 @@ export function toArray(...args) {
|
|
|
34
34
|
* @returns {Uint8Array} Uint8Array
|
|
35
35
|
*/
|
|
36
36
|
export function toBuffer(...args) {
|
|
37
|
-
|
|
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);
|
|
38
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
|
+
});
|
package/tests/e2e/.data.js
CHANGED
|
@@ -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 };
|
|
@@ -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';
|