object-input-stream 0.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/LICENSE +21 -0
- package/README.md +303 -0
- package/dist/ast.d.ts +278 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +3 -0
- package/dist/ast.js.map +1 -0
- package/dist/classes.d.ts +86 -0
- package/dist/classes.d.ts.map +1 -0
- package/dist/classes.js +193 -0
- package/dist/classes.js.map +1 -0
- package/dist/example.d.ts +2 -0
- package/dist/example.d.ts.map +1 -0
- package/dist/example.js +36 -0
- package/dist/example.js.map +1 -0
- package/dist/exceptions.d.ts +66 -0
- package/dist/exceptions.d.ts.map +1 -0
- package/dist/exceptions.js +154 -0
- package/dist/exceptions.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +12 -0
- package/dist/internal.js.map +1 -0
- package/dist/object-input-stream.d.ts +226 -0
- package/dist/object-input-stream.d.ts.map +1 -0
- package/dist/object-input-stream.js +1022 -0
- package/dist/object-input-stream.js.map +1 -0
- package/dist/ois-ast.d.ts +21 -0
- package/dist/ois-ast.d.ts.map +1 -0
- package/dist/ois-ast.js +229 -0
- package/dist/ois-ast.js.map +1 -0
- package/package.json +39 -0
- package/src/ast.ts +260 -0
- package/src/classes.ts +175 -0
- package/src/exceptions.ts +84 -0
- package/src/index.ts +19 -0
- package/src/internal.ts +10 -0
- package/src/object-input-stream.ts +1187 -0
- package/src/ois-ast.ts +147 -0
|
@@ -0,0 +1,1187 @@
|
|
|
1
|
+
import * as exc from './exceptions';
|
|
2
|
+
import { builtinSerializables, builtinExternalizables, builtinEnums, builtinClasses } from './classes';
|
|
3
|
+
|
|
4
|
+
export type OisOptions = {
|
|
5
|
+
initialClasses?: {
|
|
6
|
+
serializable?: Map<string, SerializableCtor>,
|
|
7
|
+
externalizable?: Map<string, ExternalizableCtor>,
|
|
8
|
+
enum?: Map<string, Enum>,
|
|
9
|
+
general?: Map<string, any>,
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ObjectInputStream {
|
|
14
|
+
// ========== CONSTANTS ==========
|
|
15
|
+
readonly baseWireHandle = 0x7E0000;
|
|
16
|
+
readonly PROTOCOL_VERSION_1 = 1;
|
|
17
|
+
readonly PROTOCOL_VERSION_2 = 2;
|
|
18
|
+
readonly STREAM_MAGIC = 0xACED;
|
|
19
|
+
readonly STREAM_VERSION = 0x0005;
|
|
20
|
+
readonly SC_WRITE_METHOD = 0x01;
|
|
21
|
+
readonly SC_BLOCK_DATA = 0x08;
|
|
22
|
+
readonly SC_SERIALIZABLE = 0x02;
|
|
23
|
+
readonly SC_EXTERNALIZABLE = 0x04;
|
|
24
|
+
readonly SC_ENUM = 0x10;
|
|
25
|
+
readonly TC_BASE = 0x70;
|
|
26
|
+
readonly TC_MAX = 0x7E;
|
|
27
|
+
readonly TC_NULL = 0x70;
|
|
28
|
+
readonly TC_REFERENCE = 0x71;
|
|
29
|
+
readonly TC_CLASSDESC = 0x72;
|
|
30
|
+
readonly TC_OBJECT = 0x73;
|
|
31
|
+
readonly TC_STRING = 0x74;
|
|
32
|
+
readonly TC_ARRAY = 0x75;
|
|
33
|
+
readonly TC_CLASS = 0x76;
|
|
34
|
+
readonly TC_BLOCKDATA = 0x77;
|
|
35
|
+
readonly TC_ENDBLOCKDATA = 0x78;
|
|
36
|
+
readonly TC_RESET = 0x79;
|
|
37
|
+
readonly TC_BLOCKDATALONG = 0x7A;
|
|
38
|
+
readonly TC_EXCEPTION = 0x7B;
|
|
39
|
+
readonly TC_LONGSTRING = 0x7C;
|
|
40
|
+
readonly TC_PROXYCLASSDESC = 0x7D;
|
|
41
|
+
readonly TC_ENUM = 0x7E;
|
|
42
|
+
|
|
43
|
+
static readonly baseWireHandle = 0x7E0000;
|
|
44
|
+
static readonly PROTOCOL_VERSION_1 = 1;
|
|
45
|
+
static readonly PROTOCOL_VERSION_2 = 2;
|
|
46
|
+
static readonly STREAM_MAGIC = 0xACED;
|
|
47
|
+
static readonly STREAM_VERSION = 0x0005;
|
|
48
|
+
static readonly SC_WRITE_METHOD = 0x01;
|
|
49
|
+
static readonly SC_BLOCK_DATA = 0x08;
|
|
50
|
+
static readonly SC_SERIALIZABLE = 0x02;
|
|
51
|
+
static readonly SC_EXTERNALIZABLE = 0x04;
|
|
52
|
+
static readonly SC_ENUM = 0x10;
|
|
53
|
+
static readonly TC_BASE = 0x70;
|
|
54
|
+
static readonly TC_MAX = 0x7E;
|
|
55
|
+
static readonly TC_NULL = 0x70;
|
|
56
|
+
static readonly TC_REFERENCE = 0x71;
|
|
57
|
+
static readonly TC_CLASSDESC = 0x72;
|
|
58
|
+
static readonly TC_OBJECT = 0x73;
|
|
59
|
+
static readonly TC_STRING = 0x74;
|
|
60
|
+
static readonly TC_ARRAY = 0x75;
|
|
61
|
+
static readonly TC_CLASS = 0x76;
|
|
62
|
+
static readonly TC_BLOCKDATA = 0x77;
|
|
63
|
+
static readonly TC_ENDBLOCKDATA = 0x78;
|
|
64
|
+
static readonly TC_RESET = 0x79;
|
|
65
|
+
static readonly TC_BLOCKDATALONG = 0x7A;
|
|
66
|
+
static readonly TC_EXCEPTION = 0x7B;
|
|
67
|
+
static readonly TC_LONGSTRING = 0x7C;
|
|
68
|
+
static readonly TC_PROXYCLASSDESC = 0x7D;
|
|
69
|
+
static readonly TC_ENUM = 0x7E;
|
|
70
|
+
|
|
71
|
+
// Full raw stream bytes
|
|
72
|
+
protected data: Uint8Array;
|
|
73
|
+
// Current position in this.data
|
|
74
|
+
protected offset: number;
|
|
75
|
+
// If false, byte and primitive read methods read directly from the raw stream.
|
|
76
|
+
// If true, they read from block data: handle block boundaries and EOF if next element is not a block.
|
|
77
|
+
protected blockDataMode: boolean;
|
|
78
|
+
// Bytes remaining in the current block. -1 if blockDataMode=false
|
|
79
|
+
protected remainingInBlock: number;
|
|
80
|
+
// Handle table for back-references (prevObject, TC_REFERENCE)
|
|
81
|
+
protected handleTable: HandleTable;
|
|
82
|
+
// Classes registered by the user
|
|
83
|
+
protected registeredClasses: Map<string, [ClassType, any]>;
|
|
84
|
+
// Cache of proxy classes
|
|
85
|
+
protected proxyClasses: Map<string, typeof BaseProxy>;
|
|
86
|
+
// Context if inside a readObject method. Null otherwise
|
|
87
|
+
protected curContext: CallbackContext | null;
|
|
88
|
+
|
|
89
|
+
constructor(data: Uint8Array, options?: OisOptions) {
|
|
90
|
+
this.data = data;
|
|
91
|
+
this.offset = 0;
|
|
92
|
+
this.blockDataMode = false;
|
|
93
|
+
this.remainingInBlock = -1;
|
|
94
|
+
this.handleTable = new HandleTable();
|
|
95
|
+
this.registeredClasses = new Map();
|
|
96
|
+
this.proxyClasses = new Map();
|
|
97
|
+
this.curContext = null;
|
|
98
|
+
|
|
99
|
+
if (this.readUnsignedShort() !== this.STREAM_MAGIC)
|
|
100
|
+
throw new exc.StreamCorruptedException("Missing STREAM_MAGIC");
|
|
101
|
+
if (this.readUnsignedShort() !== this.STREAM_VERSION)
|
|
102
|
+
throw new exc.StreamCorruptedException("Missing STREAM_VERSION");
|
|
103
|
+
this.setBlockDataMode(true);
|
|
104
|
+
|
|
105
|
+
const initialSerializables = options?.initialClasses?.serializable ?? builtinSerializables;
|
|
106
|
+
const initialExternalizables = options?.initialClasses?.externalizable ?? builtinExternalizables;
|
|
107
|
+
const initialEnums = options?.initialClasses?.enum ?? builtinEnums;
|
|
108
|
+
const initialClasses = options?.initialClasses?.general ?? builtinClasses;
|
|
109
|
+
|
|
110
|
+
for (const [name, ctor] of initialSerializables)
|
|
111
|
+
this.registerSerializable(name, ctor);
|
|
112
|
+
for (const [name, ctor] of initialExternalizables)
|
|
113
|
+
this.registerExternalizable(name, ctor);
|
|
114
|
+
for (const [name, enum_] of initialEnums)
|
|
115
|
+
this.registerEnum(name, enum_);
|
|
116
|
+
for (const [name, ctor] of initialClasses)
|
|
117
|
+
this.registerClass(name, ctor);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ========== PROTECTED BYTE READ METHODS ==========
|
|
121
|
+
protected setBlockDataMode(newMode: boolean): boolean {
|
|
122
|
+
if (this.blockDataMode === newMode) {
|
|
123
|
+
return this.blockDataMode;
|
|
124
|
+
}
|
|
125
|
+
this.remainingInBlock = newMode ? 0 : -1;
|
|
126
|
+
if (!newMode && this.remainingInBlock > 0) {
|
|
127
|
+
throw new exc.IllegalStateException("unread block data");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.blockDataMode = newMode;
|
|
131
|
+
return !this.blockDataMode;
|
|
132
|
+
}
|
|
133
|
+
protected peek1(): number {
|
|
134
|
+
if (!this.blockDataMode)
|
|
135
|
+
return (this.offset < this.data.length) ? this.data[this.offset] : -1;
|
|
136
|
+
|
|
137
|
+
if (this.remainingInBlock === 0)
|
|
138
|
+
this.refillBlockData();
|
|
139
|
+
if (this.remainingInBlock > 0) {
|
|
140
|
+
this.remainingInBlock;
|
|
141
|
+
return this.data[this.offset];
|
|
142
|
+
} else {
|
|
143
|
+
return -1;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
protected peekByte(): number {
|
|
147
|
+
const result = this.peek1();
|
|
148
|
+
if (result < 0)
|
|
149
|
+
throw new exc.EOFException();
|
|
150
|
+
return result >= 128 ? 256 - result : result;
|
|
151
|
+
}
|
|
152
|
+
protected readBlockHeader(): number {
|
|
153
|
+
if (!this.blockDataMode)
|
|
154
|
+
throw new exc.IllegalStateException("readBlockHeader in normal mode");
|
|
155
|
+
|
|
156
|
+
if (this.curContext?.defaultEndData)
|
|
157
|
+
// Fix for 4360508
|
|
158
|
+
return -1;
|
|
159
|
+
|
|
160
|
+
const oldMode = this.setBlockDataMode(false);
|
|
161
|
+
let len: number | null = null;
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
while (len === null) {
|
|
165
|
+
const tc = this.peek1();
|
|
166
|
+
switch (tc) {
|
|
167
|
+
case this.TC_BLOCKDATA:
|
|
168
|
+
this.readByte();
|
|
169
|
+
len = this.readUnsignedByte();
|
|
170
|
+
break;
|
|
171
|
+
|
|
172
|
+
case this.TC_BLOCKDATALONG:
|
|
173
|
+
this.readByte();
|
|
174
|
+
len = this.readInt();
|
|
175
|
+
if (len < 0)
|
|
176
|
+
throw new exc.StreamCorruptedException("illegal block data header length: " + len);
|
|
177
|
+
break;
|
|
178
|
+
|
|
179
|
+
case this.TC_RESET:
|
|
180
|
+
this.readReset();
|
|
181
|
+
break;
|
|
182
|
+
|
|
183
|
+
default:
|
|
184
|
+
if (tc >= 0 && (tc < this.TC_BASE || tc > this.TC_MAX))
|
|
185
|
+
throw new exc.StreamCorruptedException("invalid block type code: " + tcHex(tc));
|
|
186
|
+
len = -1;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
this.setBlockDataMode(oldMode);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return len;
|
|
196
|
+
}
|
|
197
|
+
protected refillBlockData(): void {
|
|
198
|
+
if (!this.blockDataMode)
|
|
199
|
+
throw new exc.IllegalStateException("refill in normal mode");
|
|
200
|
+
if (this.remainingInBlock > 0)
|
|
201
|
+
return;
|
|
202
|
+
|
|
203
|
+
while (true) {
|
|
204
|
+
const len = this.readBlockHeader();
|
|
205
|
+
if (len < 0)
|
|
206
|
+
return;
|
|
207
|
+
if (len > 0){
|
|
208
|
+
this.remainingInBlock = len;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ========== BYTE READ METHODS ==========
|
|
215
|
+
read1(): number {
|
|
216
|
+
if (!this.blockDataMode)
|
|
217
|
+
return (this.offset < this.data.length) ? this.data[this.offset++] : -1;
|
|
218
|
+
|
|
219
|
+
if (this.remainingInBlock === 0)
|
|
220
|
+
this.refillBlockData();
|
|
221
|
+
if (this.remainingInBlock > 0) {
|
|
222
|
+
this.remainingInBlock--;
|
|
223
|
+
return this.data[this.offset++];
|
|
224
|
+
} else {
|
|
225
|
+
return -1;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
read(len: number): Uint8Array {
|
|
230
|
+
len = Math.max(len, 0);
|
|
231
|
+
len = Math.min(len, this.data.length - this.offset);
|
|
232
|
+
|
|
233
|
+
if (!this.blockDataMode) {
|
|
234
|
+
const result = new Uint8Array(this.data.slice(this.offset, this.offset + len));
|
|
235
|
+
this.offset += len;
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const blocks: Uint8Array[] = [];
|
|
240
|
+
|
|
241
|
+
let left = len;
|
|
242
|
+
while (left > 0 && this.peek1() >= 0) {
|
|
243
|
+
const toRead = Math.min(left, this.remainingInBlock);
|
|
244
|
+
const block = this.data.subarray(this.offset, this.offset + toRead);
|
|
245
|
+
this.offset += toRead;
|
|
246
|
+
this.remainingInBlock -= toRead;
|
|
247
|
+
left -= toRead;
|
|
248
|
+
|
|
249
|
+
blocks.push(block);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const result = new Uint8Array(blocks.reduce((sum, cur) => sum + cur.length, 0));
|
|
253
|
+
let offset = 0;
|
|
254
|
+
for (const block of blocks) {
|
|
255
|
+
result.set(block, offset);
|
|
256
|
+
offset += block.length;
|
|
257
|
+
}
|
|
258
|
+
if (offset !== result.length)
|
|
259
|
+
throw new exc.InternalError();
|
|
260
|
+
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
readFully(len: number): Uint8Array {
|
|
265
|
+
const result = this.read(len);
|
|
266
|
+
if (result.length < len)
|
|
267
|
+
throw new exc.EOFException();
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ========== PROTECTED PRIMITIVE READ METHODS ==========
|
|
272
|
+
protected readLongUTF(): string {
|
|
273
|
+
const length = this.readLong();
|
|
274
|
+
if (length > Number.MAX_SAFE_INTEGER) {
|
|
275
|
+
throw new exc.NotImplementedError("string longer than Number.MAX_SAFE_INTEGER bytes");
|
|
276
|
+
}
|
|
277
|
+
return this.readUTFBody(Number(length));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#readUTF--
|
|
281
|
+
protected readUTFBody(byteLength: number): string {
|
|
282
|
+
const bytes = this.readFully(byteLength);
|
|
283
|
+
|
|
284
|
+
const resultChars = new Uint16Array(bytes.length);
|
|
285
|
+
let resultCharsOffset = 0;
|
|
286
|
+
for (let i=0; i<bytes.length; resultCharsOffset++) {
|
|
287
|
+
const a = bytes[i++];
|
|
288
|
+
|
|
289
|
+
// Single-byte group
|
|
290
|
+
if ((a & 0b1000_0000) === 0b0000_0000) {
|
|
291
|
+
resultChars[resultCharsOffset] = a;
|
|
292
|
+
}
|
|
293
|
+
// Two-byte group
|
|
294
|
+
else if ((a & 0b1110_0000) === 0b1100_0000) {
|
|
295
|
+
if (i+1 > bytes.length) throw new exc.UTFDataFormatException();
|
|
296
|
+
const b = bytes[i++];
|
|
297
|
+
if ((b & 0b1100_0000) !== 0b1000_0000) throw new exc.UTFDataFormatException();
|
|
298
|
+
resultChars[resultCharsOffset] = (((a & 0x1F) << 6) | (b & 0x3F));
|
|
299
|
+
}
|
|
300
|
+
// Three-byte group
|
|
301
|
+
else if ((a & 0b1111_0000) === 0b1110_0000) {
|
|
302
|
+
if (i+2 > bytes.length) throw new exc.UTFDataFormatException();
|
|
303
|
+
const b = bytes[i++];
|
|
304
|
+
const c = bytes[i++];
|
|
305
|
+
if ((b & 0b1100_0000) !== 0b1000_0000) throw new exc.UTFDataFormatException();
|
|
306
|
+
if ((c & 0b1100_0000) !== 0b1000_0000) throw new exc.UTFDataFormatException();
|
|
307
|
+
resultChars[resultCharsOffset] = (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
|
|
308
|
+
}
|
|
309
|
+
// Encoding error
|
|
310
|
+
else {
|
|
311
|
+
throw new exc.UTFDataFormatException();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return Array.from(resultChars.subarray(0, resultCharsOffset), c => String.fromCharCode(c)).join("");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
protected readTC(): number { return this.readByte(); }
|
|
319
|
+
|
|
320
|
+
// ========== PRIMITIVE READ METHODS ==========
|
|
321
|
+
readBoolean(): boolean {
|
|
322
|
+
return ByteArray.getBoolean(this.readFully(1));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
readByte(): number {
|
|
326
|
+
return ByteArray.getByte(this.readFully(1));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
readUnsignedByte(): number {
|
|
330
|
+
return ByteArray.getUnsignedByte(this.readFully(1));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
readChar(): string {
|
|
334
|
+
return ByteArray.getChar(this.readFully(2));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
readShort(): number {
|
|
338
|
+
return ByteArray.getShort(this.readFully(2));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
readUnsignedShort(): number {
|
|
342
|
+
return ByteArray.getUnsignedShort(this.readFully(2));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
readInt(): number {
|
|
346
|
+
return ByteArray.getInt(this.readFully(4));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
readLong(): bigint {
|
|
350
|
+
return ByteArray.getLong(this.readFully(8));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
readFloat(): number {
|
|
354
|
+
return ByteArray.getFloat(this.readFully(4));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
readDouble(): number {
|
|
358
|
+
return ByteArray.getDouble(this.readFully(8));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
readUTF(): string {
|
|
362
|
+
return this.readUTFBody(this.readUnsignedShort());
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ========== PROTECTED OBJECT READ METHODS ==========
|
|
366
|
+
protected readReset(): void {
|
|
367
|
+
if (this.readTC() !== this.TC_RESET) throw new exc.InternalError();
|
|
368
|
+
// TODO when depth > 0
|
|
369
|
+
this.handleTable.reset();
|
|
370
|
+
}
|
|
371
|
+
protected readNull(): null {
|
|
372
|
+
if (this.readTC() !== this.TC_NULL) throw new exc.InternalError();
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
protected readHandle(): any {
|
|
376
|
+
if (this.readTC() !== this.TC_REFERENCE) throw new exc.InternalError();
|
|
377
|
+
const handle = this.readInt();
|
|
378
|
+
return this.handleTable.getObject(handle);
|
|
379
|
+
}
|
|
380
|
+
protected readClass(): any {
|
|
381
|
+
if (this.readTC() !== this.TC_CLASS) throw new exc.InternalError();
|
|
382
|
+
const classDesc = this.readClassDesc();
|
|
383
|
+
if (classDesc === null)
|
|
384
|
+
throw new exc.NullPointerException();
|
|
385
|
+
|
|
386
|
+
const result = classDesc.cl;
|
|
387
|
+
const handle = this.handleTable.newHandle(result);
|
|
388
|
+
return result;
|
|
389
|
+
}
|
|
390
|
+
protected readClassDesc(): ObjectStreamClass | null {
|
|
391
|
+
const tc = this.peekByte();
|
|
392
|
+
|
|
393
|
+
switch (tc) {
|
|
394
|
+
case this.TC_NULL:
|
|
395
|
+
return this.readNull();
|
|
396
|
+
case this.TC_PROXYCLASSDESC:
|
|
397
|
+
return this.readProxyDesc();
|
|
398
|
+
case this.TC_CLASSDESC:
|
|
399
|
+
return this.readNonProxyDesc();
|
|
400
|
+
case this.TC_REFERENCE:
|
|
401
|
+
const d = this.readHandle();
|
|
402
|
+
if (!(d instanceof ObjectStreamClass))
|
|
403
|
+
throw new exc.ClassCastException(d);
|
|
404
|
+
d.checkInitialized();
|
|
405
|
+
return d;
|
|
406
|
+
default:
|
|
407
|
+
throw new exc.StreamCorruptedException("invalid classDesc type code: " + tcHex(tc));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
protected readProxyDesc(): ObjectStreamClass<true> {
|
|
411
|
+
if (this.readTC() !== this.TC_PROXYCLASSDESC) throw new exc.InternalError();
|
|
412
|
+
const desc = new ObjectStreamClass(null, null, true);
|
|
413
|
+
const handle = this.handleTable.newHandle(desc);
|
|
414
|
+
|
|
415
|
+
const proxyInterfaces: string[] = [];
|
|
416
|
+
const numIfaces = this.readInt();
|
|
417
|
+
for (let i=0; i<numIfaces; i++) {
|
|
418
|
+
proxyInterfaces.push(this.readUTF());
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const cl = this.resolveProxyClass(proxyInterfaces);
|
|
422
|
+
const annotation = this.readAnnotation();
|
|
423
|
+
const superDesc = this.readClassDesc();
|
|
424
|
+
desc.initProxy(cl, proxyInterfaces, annotation, superDesc);
|
|
425
|
+
|
|
426
|
+
return desc;
|
|
427
|
+
}
|
|
428
|
+
protected resolveProxyClass(proxyInterfaces: string[]): typeof BaseProxy {
|
|
429
|
+
const ifacesStr = proxyInterfaces.join(",");
|
|
430
|
+
|
|
431
|
+
if (!this.proxyClasses.has(ifacesStr)) {
|
|
432
|
+
const classId = this.proxyClasses.size.toString();
|
|
433
|
+
const className = "JavaProxy" + classId;
|
|
434
|
+
const newClass = {[className]: class extends BaseProxy {
|
|
435
|
+
static readonly proxyInterfaces = proxyInterfaces;
|
|
436
|
+
}}[className];
|
|
437
|
+
|
|
438
|
+
this.proxyClasses.set(ifacesStr, newClass);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return this.proxyClasses.get(ifacesStr)!;
|
|
442
|
+
}
|
|
443
|
+
protected readNonProxyDesc(): ObjectStreamClass<false> {
|
|
444
|
+
if (this.readTC() !== this.TC_CLASSDESC) throw new exc.InternalError();
|
|
445
|
+
|
|
446
|
+
const name = this.readUTF();
|
|
447
|
+
const suid = this.readLong();
|
|
448
|
+
const desc = new ObjectStreamClass(name, suid, false);
|
|
449
|
+
const handle = this.handleTable.newHandle(desc);
|
|
450
|
+
|
|
451
|
+
const flags = this.readUnsignedByte();
|
|
452
|
+
|
|
453
|
+
const numFields = this.readShort();
|
|
454
|
+
const fields: FieldDesc[] = [];
|
|
455
|
+
for (let i=0; i<numFields; i++) {
|
|
456
|
+
const typecode = String.fromCodePoint(this.readUnsignedByte());
|
|
457
|
+
const fieldName = this.readUTF();
|
|
458
|
+
|
|
459
|
+
switch (typecode) {
|
|
460
|
+
case 'L': case '[':
|
|
461
|
+
const className = this.readString();
|
|
462
|
+
fields.push({typecode, name: fieldName, className});
|
|
463
|
+
break;
|
|
464
|
+
|
|
465
|
+
case 'B': case 'C': case 'D': case 'F': case 'I': case 'J': case 'S': case 'Z':
|
|
466
|
+
fields.push({typecode, name: fieldName});
|
|
467
|
+
break;
|
|
468
|
+
|
|
469
|
+
default:
|
|
470
|
+
throw new exc.InvalidClassException(name, "invalid typecode for field " + fieldName + ": " + typecode);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const annotation = this.readAnnotation();
|
|
475
|
+
const superDesc = this.readClassDesc();
|
|
476
|
+
const cl = this.resolveClass(name, this.getClassType(flags), desc, superDesc);
|
|
477
|
+
desc.initNonProxy(cl, flags, fields, annotation, superDesc);
|
|
478
|
+
|
|
479
|
+
return desc;
|
|
480
|
+
}
|
|
481
|
+
protected getClassType(flags: number): ClassType {
|
|
482
|
+
if (flags & this.SC_ENUM) return "enum";
|
|
483
|
+
else if (flags & this.SC_SERIALIZABLE) return "serializable";
|
|
484
|
+
else if (flags & this.SC_EXTERNALIZABLE) return "externalizable";
|
|
485
|
+
else return "general";
|
|
486
|
+
}
|
|
487
|
+
protected resolveClass(
|
|
488
|
+
name: string,
|
|
489
|
+
type: ClassType,
|
|
490
|
+
desc: ObjectStreamClass,
|
|
491
|
+
superDesc: ObjectStreamClass | null,
|
|
492
|
+
): any {
|
|
493
|
+
if (this.registeredClasses.has(name))
|
|
494
|
+
return this.registeredClasses.get(name)![1];
|
|
495
|
+
|
|
496
|
+
const fallbackSuperClass = {
|
|
497
|
+
"general": BaseFallbackClass,
|
|
498
|
+
"serializable": BaseFallbackSerializable,
|
|
499
|
+
"externalizable": BaseFallbackExternalizable,
|
|
500
|
+
"enum": BaseFallbackEnum,
|
|
501
|
+
}[type];
|
|
502
|
+
const superClass = superDesc !== null ? (superDesc.cl as new () => any) : fallbackSuperClass;
|
|
503
|
+
|
|
504
|
+
const cl = {[name]: class extends superClass {
|
|
505
|
+
static $desc = desc
|
|
506
|
+
}}[name];
|
|
507
|
+
// @ts-expect-error
|
|
508
|
+
cl.displayName = name
|
|
509
|
+
|
|
510
|
+
return cl;
|
|
511
|
+
}
|
|
512
|
+
protected readString(): string {
|
|
513
|
+
const tc = this.readTC();
|
|
514
|
+
let str: string;
|
|
515
|
+
switch (tc) {
|
|
516
|
+
case this.TC_STRING:
|
|
517
|
+
str = this.readUTF();
|
|
518
|
+
break;
|
|
519
|
+
|
|
520
|
+
case this.TC_LONGSTRING:
|
|
521
|
+
str = this.readLongUTF();
|
|
522
|
+
break;
|
|
523
|
+
|
|
524
|
+
case this.TC_REFERENCE:
|
|
525
|
+
str = this.readHandle();
|
|
526
|
+
if (typeof str !== "string")
|
|
527
|
+
throw new exc.ClassCastException("string reference is not a string: " + str);
|
|
528
|
+
return str;
|
|
529
|
+
|
|
530
|
+
default:
|
|
531
|
+
throw new exc.StreamCorruptedException("invalid string type code: " + tcHex(tc));
|
|
532
|
+
}
|
|
533
|
+
this.handleTable.newHandle(str);
|
|
534
|
+
return str;
|
|
535
|
+
}
|
|
536
|
+
protected readArray(): any[] {
|
|
537
|
+
if (this.readTC() !== this.TC_ARRAY) throw new exc.InternalError();
|
|
538
|
+
|
|
539
|
+
const desc = this.readClassDesc();
|
|
540
|
+
const len = this.readInt();
|
|
541
|
+
if (len < 0)
|
|
542
|
+
throw new exc.StreamCorruptedException("Array length is negative");
|
|
543
|
+
|
|
544
|
+
const result: any[] = [];
|
|
545
|
+
const handle = this.handleTable.newHandle(result);
|
|
546
|
+
|
|
547
|
+
if (!desc?.name?.startsWith("[") || desc.name.length < 2)
|
|
548
|
+
throw new exc.StreamCorruptedException("Invalid array desc name: " + desc?.name);
|
|
549
|
+
const typecode = desc.name.charAt(1);
|
|
550
|
+
|
|
551
|
+
for (let i=0; i<len; i++) {
|
|
552
|
+
result.push(this.readValue(typecode));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return result;
|
|
556
|
+
}
|
|
557
|
+
protected readValue(typecode: string): any {
|
|
558
|
+
switch (typecode) {
|
|
559
|
+
case '[':
|
|
560
|
+
case 'L':
|
|
561
|
+
// TODO: type checking
|
|
562
|
+
return this.readObject();
|
|
563
|
+
case 'B': return this.readByte();
|
|
564
|
+
case 'C': return this.readChar();
|
|
565
|
+
case 'D': return this.readDouble();
|
|
566
|
+
case 'F': return this.readFloat();
|
|
567
|
+
case 'I': return this.readInt();
|
|
568
|
+
case 'J': return this.readLong();
|
|
569
|
+
case 'S': return this.readShort();
|
|
570
|
+
case 'Z': return this.readBoolean();
|
|
571
|
+
default:
|
|
572
|
+
throw new exc.StreamCorruptedException("Unkown field value typecode: " + typecode);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
protected readEnum(): Enum {
|
|
576
|
+
if (this.readTC() !== this.TC_ENUM) throw new exc.InternalError();
|
|
577
|
+
|
|
578
|
+
const desc = this.readClassDesc();
|
|
579
|
+
if (!desc?.isEnum)
|
|
580
|
+
throw new exc.InvalidClassException(desc?.name ?? null, "non-enum class");
|
|
581
|
+
|
|
582
|
+
const handle = this.handleTable.newHandle(null);
|
|
583
|
+
const constantName = this.readString();
|
|
584
|
+
|
|
585
|
+
if (!(constantName in desc.cl))
|
|
586
|
+
throw new exc.InvalidClassException(desc.name, "enum constant name doesn't exist: " + constantName);
|
|
587
|
+
// @ts-expect-error
|
|
588
|
+
const result = desc.cl[constantName];
|
|
589
|
+
|
|
590
|
+
this.handleTable.replaceObject(handle, null, result);
|
|
591
|
+
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
protected readOrdinaryObject(): any {
|
|
595
|
+
if (this.readTC() !== this.TC_OBJECT) throw new exc.InternalError();
|
|
596
|
+
|
|
597
|
+
const desc = this.readClassDesc();
|
|
598
|
+
if (desc === null)
|
|
599
|
+
throw new exc.NullPointerException();
|
|
600
|
+
|
|
601
|
+
const result = new desc.cl();
|
|
602
|
+
const handle = this.handleTable.newHandle(result);
|
|
603
|
+
|
|
604
|
+
if (desc.externalizable) {
|
|
605
|
+
this.readExternalData(result, desc);
|
|
606
|
+
} else if (desc.serializable) {
|
|
607
|
+
this.readSerialData(result, desc);
|
|
608
|
+
} else {
|
|
609
|
+
throw new exc.InvalidClassException(desc.name, "not serializable and not externalizable");
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (typeof result.readResolve === "function") {
|
|
613
|
+
const replaced = result.readResolve();
|
|
614
|
+
this.handleTable.replaceObject(handle, result, replaced);
|
|
615
|
+
return replaced;
|
|
616
|
+
} else {
|
|
617
|
+
return result;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
protected readExternalData(obj: Externalizable, desc: ObjectStreamClass): void {
|
|
621
|
+
const registered = this.registeredClasses.get(desc.name as any)?.[1];
|
|
622
|
+
if (!desc.hasBlockExternalData && desc.cl !== registered)
|
|
623
|
+
throw new exc.ClassNotFoundException("Cannot deserialize instance of Externalizable class " + desc.name + " written using PROTOCOL_VERSION_1, without a matching JS-side class");
|
|
624
|
+
|
|
625
|
+
const oldContext = this.curContext;
|
|
626
|
+
this.curContext = null;
|
|
627
|
+
try {
|
|
628
|
+
if (desc.hasBlockExternalData)
|
|
629
|
+
this.setBlockDataMode(true);
|
|
630
|
+
try {
|
|
631
|
+
obj.readExternal(this);
|
|
632
|
+
} finally {
|
|
633
|
+
if (desc.hasBlockExternalData)
|
|
634
|
+
this.readAnnotation();
|
|
635
|
+
}
|
|
636
|
+
} finally {
|
|
637
|
+
this.curContext = oldContext;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
protected readSerialData(obj: Serializable, objDesc: ObjectStreamClass): void {
|
|
641
|
+
const descs = this.getClassDescHierarchy(objDesc);
|
|
642
|
+
|
|
643
|
+
for (const curDesc of descs) {
|
|
644
|
+
const curClass = curDesc.cl as SerializableCtor;
|
|
645
|
+
|
|
646
|
+
let readMethod: NonNullable<Serializable["readObject"]> | null = null;
|
|
647
|
+
if (obj instanceof curClass) {
|
|
648
|
+
if (curClass.serialVersionUID !== undefined && curClass.serialVersionUID !== curDesc.suid)
|
|
649
|
+
throw new exc.InvalidClassException(curDesc.name, "stream suid " + curDesc.suid + " doesn't match available suid " + curClass.serialVersionUID);
|
|
650
|
+
|
|
651
|
+
if (typeof curClass.prototype.readObject === "function") {
|
|
652
|
+
readMethod = curClass.prototype.readObject;
|
|
653
|
+
} else {
|
|
654
|
+
readMethod = defaultReadMethod;
|
|
655
|
+
}
|
|
656
|
+
} else {
|
|
657
|
+
if (curClass.prototype instanceof BaseFallbackClass) {
|
|
658
|
+
readMethod = defaultReadMethod;
|
|
659
|
+
} else {
|
|
660
|
+
throw new exc.ClassNotFoundException(curDesc.name + " parent of " + objDesc.name + " in java but not in javascript");
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const oldContext = this.curContext;
|
|
665
|
+
this.curContext = {
|
|
666
|
+
desc: curDesc,
|
|
667
|
+
obj,
|
|
668
|
+
alreadyReadFields: false,
|
|
669
|
+
defaultEndData: false,
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
this.setBlockDataMode(true);
|
|
673
|
+
readMethod!.apply(obj, [this]);
|
|
674
|
+
if (curDesc.hasWriteObjectData)
|
|
675
|
+
this.readAnnotation();
|
|
676
|
+
|
|
677
|
+
this.curContext = oldContext;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
private getClassDescHierarchy(classDesc: ObjectStreamClass): ObjectStreamClass[] {
|
|
681
|
+
const hierarchy = [];
|
|
682
|
+
let currClass: ObjectStreamClass | null = classDesc;
|
|
683
|
+
while (currClass !== null) {
|
|
684
|
+
hierarchy.push(currClass);
|
|
685
|
+
currClass = currClass.superDesc;
|
|
686
|
+
}
|
|
687
|
+
return hierarchy.reverse();
|
|
688
|
+
}
|
|
689
|
+
protected readFatalException(): any {
|
|
690
|
+
if (this.readTC() !== this.TC_EXCEPTION) throw new exc.InternalError();
|
|
691
|
+
|
|
692
|
+
this.handleTable.reset();
|
|
693
|
+
|
|
694
|
+
const oldMode = this.setBlockDataMode(false);
|
|
695
|
+
const tc = this.peekByte()
|
|
696
|
+
this.setBlockDataMode(oldMode);
|
|
697
|
+
|
|
698
|
+
if (tc !== this.TC_OBJECT && tc !== this.TC_REFERENCE)
|
|
699
|
+
throw new exc.StreamCorruptedException("invalid exception type code: " + tcHex(tc));
|
|
700
|
+
|
|
701
|
+
const result = this.readObject();
|
|
702
|
+
|
|
703
|
+
// This line is required by the spec and implemented in OpenJDK's ObjectOutputStream,
|
|
704
|
+
// but not in OpenJDK's ObjectInputStream. This is a bug in OpenJDK.
|
|
705
|
+
this.handleTable.reset();
|
|
706
|
+
|
|
707
|
+
return result;
|
|
708
|
+
}
|
|
709
|
+
protected readAnnotation(): any[] {
|
|
710
|
+
const result = [];
|
|
711
|
+
|
|
712
|
+
while (true) {
|
|
713
|
+
if (this.blockDataMode) {
|
|
714
|
+
const block = this.read(Infinity);
|
|
715
|
+
if (block.length > 0) result.push(block);
|
|
716
|
+
this.setBlockDataMode(false);
|
|
717
|
+
}
|
|
718
|
+
switch (this.peekByte()) {
|
|
719
|
+
case this.TC_BLOCKDATA:
|
|
720
|
+
case this.TC_BLOCKDATALONG:
|
|
721
|
+
this.setBlockDataMode(true);
|
|
722
|
+
break;
|
|
723
|
+
|
|
724
|
+
case this.TC_ENDBLOCKDATA:
|
|
725
|
+
this.readByte();
|
|
726
|
+
return result;
|
|
727
|
+
|
|
728
|
+
default:
|
|
729
|
+
result.push(this.readObject());
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// ========== OBJECT READ METHODS ==========
|
|
736
|
+
readObject(): any {
|
|
737
|
+
const oldMode = this.blockDataMode;
|
|
738
|
+
if (this.blockDataMode) {
|
|
739
|
+
this.peek1();
|
|
740
|
+
if (this.remainingInBlock > 0) {
|
|
741
|
+
throw new exc.OptionalDataException(this.remainingInBlock);
|
|
742
|
+
} else if (this.curContext?.defaultEndData) {
|
|
743
|
+
// Fix for 4360508
|
|
744
|
+
throw new exc.OptionalDataException(true);
|
|
745
|
+
}
|
|
746
|
+
this.setBlockDataMode(false);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
let tc;
|
|
750
|
+
|
|
751
|
+
// Skip resets
|
|
752
|
+
while ((tc = this.peekByte()) === this.TC_RESET)
|
|
753
|
+
this.readReset();
|
|
754
|
+
|
|
755
|
+
try {
|
|
756
|
+
switch (tc) {
|
|
757
|
+
case this.TC_NULL:
|
|
758
|
+
return this.readNull();
|
|
759
|
+
|
|
760
|
+
case this.TC_REFERENCE:
|
|
761
|
+
return this.readHandle();
|
|
762
|
+
|
|
763
|
+
case this.TC_CLASS:
|
|
764
|
+
return this.readClass();
|
|
765
|
+
|
|
766
|
+
case this.TC_CLASSDESC:
|
|
767
|
+
case this.TC_PROXYCLASSDESC:
|
|
768
|
+
return this.readClassDesc();
|
|
769
|
+
|
|
770
|
+
case this.TC_STRING:
|
|
771
|
+
case this.TC_LONGSTRING:
|
|
772
|
+
return this.readString();
|
|
773
|
+
|
|
774
|
+
case this.TC_ARRAY:
|
|
775
|
+
return this.readArray();
|
|
776
|
+
|
|
777
|
+
case this.TC_ENUM:
|
|
778
|
+
return this.readEnum();
|
|
779
|
+
|
|
780
|
+
case this.TC_OBJECT:
|
|
781
|
+
return this.readOrdinaryObject();
|
|
782
|
+
|
|
783
|
+
case this.TC_EXCEPTION:
|
|
784
|
+
const ex = this.readFatalException();
|
|
785
|
+
throw new exc.WriteAbortedException("writing aborted", ex);
|
|
786
|
+
|
|
787
|
+
case this.TC_ENDBLOCKDATA:
|
|
788
|
+
if (oldMode) {
|
|
789
|
+
throw new exc.OptionalDataException(true);
|
|
790
|
+
} else {
|
|
791
|
+
throw new exc.StreamCorruptedException("unexpected end of block data");
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
default:
|
|
795
|
+
throw new exc.StreamCorruptedException("invalid object type code: " + tcHex(tc));
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
finally {
|
|
799
|
+
this.setBlockDataMode(oldMode);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
readFields(): Map<string, any> {
|
|
804
|
+
if (this.curContext === null)
|
|
805
|
+
throw new exc.NotActiveException("Not inside a readObject method");
|
|
806
|
+
|
|
807
|
+
if (this.curContext.alreadyReadFields)
|
|
808
|
+
throw new exc.NotActiveException("Fields already read");
|
|
809
|
+
|
|
810
|
+
const fields = this.curContext.desc.fields;
|
|
811
|
+
if (fields === null)
|
|
812
|
+
throw new exc.InternalError();
|
|
813
|
+
|
|
814
|
+
this.setBlockDataMode(false);
|
|
815
|
+
const result = new Map();
|
|
816
|
+
for (const field of fields) {
|
|
817
|
+
result.set(field.name, this.readValue(field.typecode));
|
|
818
|
+
}
|
|
819
|
+
this.setBlockDataMode(true);
|
|
820
|
+
|
|
821
|
+
this.curContext.alreadyReadFields = true;
|
|
822
|
+
if (!this.curContext.desc.hasWriteObjectData)
|
|
823
|
+
this.curContext.defaultEndData = true;
|
|
824
|
+
|
|
825
|
+
return result;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
defaultReadObject(): void {
|
|
829
|
+
const fields = this.readFields();
|
|
830
|
+
|
|
831
|
+
const obj = this.curContext?.obj!;
|
|
832
|
+
for (const [k, v] of fields.entries()) {
|
|
833
|
+
// @ts-expect-error
|
|
834
|
+
obj[k] = v;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
readEverything(): any[] {
|
|
839
|
+
const result = [];
|
|
840
|
+
|
|
841
|
+
try {
|
|
842
|
+
while (true) {
|
|
843
|
+
const block = this.read(Infinity);
|
|
844
|
+
if (block.length > 0) result.push(block);
|
|
845
|
+
result.push(this.readObject());
|
|
846
|
+
}
|
|
847
|
+
} catch (e) {
|
|
848
|
+
if (!(e instanceof exc.OptionalDataException || e instanceof exc.EOFException))
|
|
849
|
+
throw e;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
return result;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// ========== PROTECTED CLASS REGISTRATION METHODS ==========
|
|
856
|
+
protected registerClass0(name: string, clazz: any, type: ClassType): void{
|
|
857
|
+
this.registeredClasses.set(name, [type, clazz]);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// ========== CLASS REGISTRATION METHODS ==========
|
|
861
|
+
registerSerializable(name: string, ctor: SerializableCtor): void {
|
|
862
|
+
return this.registerClass0(name, ctor, "serializable");
|
|
863
|
+
}
|
|
864
|
+
registerExternalizable(name: string, ctor: ExternalizableCtor): void {
|
|
865
|
+
return this.registerClass0(name, ctor, "externalizable");
|
|
866
|
+
}
|
|
867
|
+
registerEnum(name: string, enum_: Enum): void {
|
|
868
|
+
return this.registerClass0(name, enum_, "enum");
|
|
869
|
+
}
|
|
870
|
+
registerClass(name: string, clazz: any): void {
|
|
871
|
+
return this.registerClass0(name, clazz, "general");
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
export default ObjectInputStream;
|
|
875
|
+
|
|
876
|
+
export class ByteArray {
|
|
877
|
+
protected static getIntegral(arr: Uint8Array, numBytes: number, signed: boolean): bigint {
|
|
878
|
+
if (arr.length < numBytes) {
|
|
879
|
+
throw new exc.IndexOutOfBoundsException();
|
|
880
|
+
}
|
|
881
|
+
if (numBytes <= 0) {
|
|
882
|
+
return 0n;
|
|
883
|
+
}
|
|
884
|
+
const bytes = arr.subarray(0, numBytes);
|
|
885
|
+
let result = 0n;
|
|
886
|
+
for (const byte of bytes) {
|
|
887
|
+
result <<= 8n;
|
|
888
|
+
result += BigInt(byte);
|
|
889
|
+
}
|
|
890
|
+
if (signed) {
|
|
891
|
+
const topBit = 1n << BigInt(numBytes * 8 - 1);
|
|
892
|
+
if ((result & topBit) !== 0n) {
|
|
893
|
+
result -= 1n << BigInt(numBytes * 8);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return result;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
static getBoolean(arr: Uint8Array): boolean {
|
|
900
|
+
if (arr.length < 1) throw new exc.IndexOutOfBoundsException();
|
|
901
|
+
return arr[0] !== 0;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
static getByte(arr: Uint8Array): number {
|
|
905
|
+
return Number(this.getIntegral(arr, 1, true));
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
static getUnsignedByte(arr: Uint8Array): number {
|
|
909
|
+
return Number(this.getIntegral(arr, 1, false))
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
static getChar(arr: Uint8Array): string {
|
|
913
|
+
return String.fromCharCode(Number(this.getIntegral(arr, 2, false)));
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
static getShort(arr: Uint8Array): number {
|
|
917
|
+
return Number(this.getIntegral(arr, 2, true));
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
static getUnsignedShort(arr: Uint8Array): number {
|
|
921
|
+
return Number(this.getIntegral(arr, 2, false));
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
static getInt(arr: Uint8Array): number {
|
|
925
|
+
return Number(this.getIntegral(arr, 4, true));
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
static getLong(arr: Uint8Array): bigint {
|
|
929
|
+
return this.getIntegral(arr, 8, true);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
static getFloat(arr: Uint8Array): number {
|
|
933
|
+
if (arr.length < 4) throw new exc.IndexOutOfBoundsException();
|
|
934
|
+
return new DataView(arr.subarray(0, 4).buffer).getFloat32(0, false);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
static getDouble(arr: Uint8Array): number {
|
|
938
|
+
if (arr.length < 8) throw new exc.IndexOutOfBoundsException();
|
|
939
|
+
return new DataView(arr.subarray(0, 8).buffer).getFloat64(0, false);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
export class HandleTable {
|
|
944
|
+
private table: Map<number, any> = new Map();
|
|
945
|
+
private currHandle = ObjectInputStream.baseWireHandle;
|
|
946
|
+
|
|
947
|
+
reset(): void {
|
|
948
|
+
this.table.clear();
|
|
949
|
+
this.currHandle = ObjectInputStream.baseWireHandle;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
newHandle(obj: any): number {
|
|
953
|
+
const handle = this.currHandle++;
|
|
954
|
+
this.table.set(handle, obj);
|
|
955
|
+
return handle;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
getObject(handle: number): any {
|
|
959
|
+
if (!this.table.has(handle))
|
|
960
|
+
throw new exc.StreamCorruptedException("Object handle doesn't exist: " + handle);
|
|
961
|
+
return this.table.get(handle)!;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
replaceObject(handle: number, oldObj: any, newObj: any): void {
|
|
965
|
+
if (this.table.get(handle) !== oldObj)
|
|
966
|
+
throw new exc.IllegalStateException(`Replaced handle ${handle} doesn't refer to object ${oldObj}`);
|
|
967
|
+
|
|
968
|
+
this.table.set(handle, newObj);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function tcHex(tc: number): string {
|
|
973
|
+
return tc.toString(16).padStart(2, '0');
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
export class ObjectStreamClass<IS_PROXY extends boolean = boolean> {
|
|
977
|
+
cl: new () => any
|
|
978
|
+
name: IS_PROXY extends false ? string : null
|
|
979
|
+
suid: IS_PROXY extends false ? bigint : null
|
|
980
|
+
fields: IS_PROXY extends false ? FieldDesc[] : null
|
|
981
|
+
|
|
982
|
+
isProxy: IS_PROXY
|
|
983
|
+
proxyInterfaces: IS_PROXY extends true ? string[] : null
|
|
984
|
+
|
|
985
|
+
isEnum: boolean
|
|
986
|
+
serializable: boolean
|
|
987
|
+
externalizable: boolean
|
|
988
|
+
hasWriteObjectData: boolean
|
|
989
|
+
hasBlockExternalData: boolean
|
|
990
|
+
|
|
991
|
+
annotation: any[]
|
|
992
|
+
|
|
993
|
+
superDesc: ObjectStreamClass | null
|
|
994
|
+
|
|
995
|
+
initialized: boolean
|
|
996
|
+
|
|
997
|
+
constructor(
|
|
998
|
+
name: IS_PROXY extends false ? string : null,
|
|
999
|
+
suid: IS_PROXY extends false ? bigint : null,
|
|
1000
|
+
isProxy: IS_PROXY
|
|
1001
|
+
) {
|
|
1002
|
+
this.initialized = false;
|
|
1003
|
+
this.name = name;
|
|
1004
|
+
this.suid = suid
|
|
1005
|
+
this.isProxy = isProxy;
|
|
1006
|
+
|
|
1007
|
+
// Set default values
|
|
1008
|
+
this.cl = Object;
|
|
1009
|
+
this.fields = (isProxy ? null : []) as any;
|
|
1010
|
+
this.proxyInterfaces = (isProxy ? [] : null) as any;
|
|
1011
|
+
this.isEnum = false;
|
|
1012
|
+
this.serializable = false;
|
|
1013
|
+
this.externalizable = false;
|
|
1014
|
+
this.hasWriteObjectData = false;
|
|
1015
|
+
this.hasBlockExternalData = false;
|
|
1016
|
+
this.annotation = [];
|
|
1017
|
+
this.superDesc = null;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
initNonProxy(this: ObjectStreamClass<false>,
|
|
1021
|
+
cl: any,
|
|
1022
|
+
flags: number,
|
|
1023
|
+
fields: FieldDesc[],
|
|
1024
|
+
annotation: any[],
|
|
1025
|
+
superDesc: ObjectStreamClass | null,
|
|
1026
|
+
) {
|
|
1027
|
+
if (this.isProxy)
|
|
1028
|
+
throw new exc.IllegalStateException("initialize non-proxy on a proxy class");
|
|
1029
|
+
if (this.initialized)
|
|
1030
|
+
throw new exc.IllegalStateException("already initialized non-proxy");
|
|
1031
|
+
|
|
1032
|
+
this.cl = cl;
|
|
1033
|
+
|
|
1034
|
+
this.isEnum = (flags & ObjectInputStream.SC_ENUM) !== 0;
|
|
1035
|
+
this.serializable = (flags & ObjectInputStream.SC_SERIALIZABLE) !== 0;
|
|
1036
|
+
this.externalizable = (flags & ObjectInputStream.SC_EXTERNALIZABLE) !== 0;
|
|
1037
|
+
this.hasWriteObjectData = (flags & ObjectInputStream.SC_WRITE_METHOD) !== 0;
|
|
1038
|
+
this.hasBlockExternalData = (flags & ObjectInputStream.SC_BLOCK_DATA) !== 0;
|
|
1039
|
+
|
|
1040
|
+
this.fields = fields;
|
|
1041
|
+
this.annotation = annotation;
|
|
1042
|
+
this.superDesc = superDesc;
|
|
1043
|
+
|
|
1044
|
+
if (this.serializable && this.externalizable)
|
|
1045
|
+
throw new exc.InvalidClassException(this.name, "serializable and externalizable flags conflict");
|
|
1046
|
+
if (this.isEnum && this.suid !== 0n)
|
|
1047
|
+
throw new exc.InvalidClassException(this.name, "enum descriptor has non-zero serialVersionUID: " + this.suid);
|
|
1048
|
+
if (this.isEnum && this.fields.length > 0)
|
|
1049
|
+
throw new exc.InvalidClassException(this.name, "enum descriptor has non-zero field count: " + this.fields.length);
|
|
1050
|
+
|
|
1051
|
+
this.initialized = true;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
initProxy(this: ObjectStreamClass<true>,
|
|
1055
|
+
cl: any,
|
|
1056
|
+
proxyInterfaces: string[],
|
|
1057
|
+
annotation: any[],
|
|
1058
|
+
superDesc: ObjectStreamClass | null,
|
|
1059
|
+
) {
|
|
1060
|
+
if (!this.isProxy)
|
|
1061
|
+
throw new exc.IllegalStateException("initialize proxy on a non-proxy class");
|
|
1062
|
+
if (this.initialized)
|
|
1063
|
+
throw new exc.IllegalStateException("already initialized proxy");
|
|
1064
|
+
|
|
1065
|
+
this.cl = cl;
|
|
1066
|
+
this.proxyInterfaces = proxyInterfaces;
|
|
1067
|
+
this.annotation = annotation;
|
|
1068
|
+
this.superDesc = superDesc;
|
|
1069
|
+
|
|
1070
|
+
this.initialized = true;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
checkInitialized(): void {
|
|
1074
|
+
if (!this.initialized)
|
|
1075
|
+
throw new exc.InvalidClassException(this.name, "Class descriptor should be initialized");
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
export type FieldDesc = {
|
|
1080
|
+
typecode: 'B' | 'C' | 'D' | 'F' | 'I' | 'J' | 'S' | 'Z',
|
|
1081
|
+
name: string,
|
|
1082
|
+
} | {
|
|
1083
|
+
typecode: '[' | 'L',
|
|
1084
|
+
name: string,
|
|
1085
|
+
className: string,
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
export interface InvocationHandler {
|
|
1089
|
+
invoke: (proxy: BaseProxy, method: string, args: any[]) => any
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
export abstract class BaseProxy {
|
|
1093
|
+
static readonly proxyInterfaces: string[] = []
|
|
1094
|
+
|
|
1095
|
+
h?: InvocationHandler
|
|
1096
|
+
|
|
1097
|
+
constructor(h?: InvocationHandler) {
|
|
1098
|
+
this.h = h;
|
|
1099
|
+
|
|
1100
|
+
return new Proxy(this, {get: (target, prop, receiver) => {
|
|
1101
|
+
if (typeof prop !== "string")
|
|
1102
|
+
return Reflect.get(target, prop, receiver);
|
|
1103
|
+
|
|
1104
|
+
if (this.h === undefined || typeof this.h.invoke !== "function")
|
|
1105
|
+
throw new TypeError("invocation handler doesn't have invoke method");
|
|
1106
|
+
|
|
1107
|
+
const h = this.h;
|
|
1108
|
+
return (...args: any[]) => {
|
|
1109
|
+
return h.invoke(target, prop, args);
|
|
1110
|
+
}
|
|
1111
|
+
}})
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
static getInterfaces(): string[] {
|
|
1115
|
+
return this.proxyInterfaces;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
export interface Serializable {
|
|
1120
|
+
readObject?(ois: ObjectInputStream): void
|
|
1121
|
+
readResolve?(): any
|
|
1122
|
+
[key: string | symbol]: any // To prevent ts(2559)
|
|
1123
|
+
}
|
|
1124
|
+
export interface SerializableCtor {
|
|
1125
|
+
serialVersionUID?: bigint
|
|
1126
|
+
new (): Serializable
|
|
1127
|
+
}
|
|
1128
|
+
export interface Externalizable {
|
|
1129
|
+
readExternal(ois: ObjectInputStream): void
|
|
1130
|
+
readResolve?(): any
|
|
1131
|
+
}
|
|
1132
|
+
export interface ExternalizableCtor {
|
|
1133
|
+
new (): Externalizable
|
|
1134
|
+
}
|
|
1135
|
+
export interface Enum {
|
|
1136
|
+
[key: string]: any
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
export abstract class BaseFallbackClass {
|
|
1140
|
+
static $desc: ObjectStreamClass<false>
|
|
1141
|
+
}
|
|
1142
|
+
export abstract class BaseFallbackSerializable extends BaseFallbackClass implements Serializable {
|
|
1143
|
+
[field: string]: any
|
|
1144
|
+
$annotation: any[][] = []
|
|
1145
|
+
|
|
1146
|
+
readObject(ois: ObjectInputStream): void {
|
|
1147
|
+
ois.defaultReadObject();
|
|
1148
|
+
this.$annotation.push(ois.readEverything());
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
export abstract class BaseFallbackExternalizable extends BaseFallbackClass implements Externalizable {
|
|
1152
|
+
$annotation: any[] = []
|
|
1153
|
+
|
|
1154
|
+
readExternal(ois: ObjectInputStream): void {
|
|
1155
|
+
this.$annotation = ois.readEverything();
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
export abstract class BaseFallbackEnum extends BaseFallbackClass {
|
|
1159
|
+
[key: string]: string
|
|
1160
|
+
|
|
1161
|
+
constructor() {
|
|
1162
|
+
super();
|
|
1163
|
+
return new Proxy(this, {
|
|
1164
|
+
get: (target, prop, receiver) => {
|
|
1165
|
+
if (typeof prop !== "string")
|
|
1166
|
+
return Reflect.get(target, prop, receiver);
|
|
1167
|
+
return prop;
|
|
1168
|
+
},
|
|
1169
|
+
has(target, prop) {
|
|
1170
|
+
if (typeof prop === "string") return true;
|
|
1171
|
+
return prop in target;
|
|
1172
|
+
},
|
|
1173
|
+
})
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
type ClassType = "serializable" | "externalizable" | "enum" | "general"
|
|
1178
|
+
type CallbackContext = {
|
|
1179
|
+
obj: Serializable | Externalizable,
|
|
1180
|
+
desc: ObjectStreamClass,
|
|
1181
|
+
alreadyReadFields: boolean,
|
|
1182
|
+
defaultEndData: boolean,
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function defaultReadMethod(ois: ObjectInputStream) {
|
|
1186
|
+
ois.defaultReadObject();
|
|
1187
|
+
}
|