dfindexeddb 20240519__py3-none-any.whl → 20241031__py3-none-any.whl
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.
- dfindexeddb/indexeddb/chromium/v8.py +30 -10
- dfindexeddb/indexeddb/cli.py +36 -3
- dfindexeddb/indexeddb/firefox/definitions.py +143 -0
- dfindexeddb/indexeddb/firefox/gecko.py +600 -0
- dfindexeddb/indexeddb/firefox/record.py +180 -0
- dfindexeddb/indexeddb/safari/webkit.py +37 -13
- dfindexeddb/indexeddb/types.py +71 -0
- dfindexeddb/leveldb/descriptor.py +2 -1
- dfindexeddb/utils.py +1 -1
- dfindexeddb/version.py +1 -1
- {dfindexeddb-20240519.dist-info → dfindexeddb-20241031.dist-info}/METADATA +12 -6
- {dfindexeddb-20240519.dist-info → dfindexeddb-20241031.dist-info}/RECORD +17 -13
- {dfindexeddb-20240519.dist-info → dfindexeddb-20241031.dist-info}/WHEEL +1 -1
- {dfindexeddb-20240519.dist-info → dfindexeddb-20241031.dist-info}/AUTHORS +0 -0
- {dfindexeddb-20240519.dist-info → dfindexeddb-20241031.dist-info}/LICENSE +0 -0
- {dfindexeddb-20240519.dist-info → dfindexeddb-20241031.dist-info}/entry_points.txt +0 -0
- {dfindexeddb-20240519.dist-info → dfindexeddb-20241031.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright 2024 Google LLC
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
"""Parsers for Gecko encoded JavaScript values."""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import dataclasses
|
|
19
|
+
import datetime
|
|
20
|
+
import io
|
|
21
|
+
import struct
|
|
22
|
+
from typing import Any, List, Tuple, Union
|
|
23
|
+
|
|
24
|
+
import snappy
|
|
25
|
+
|
|
26
|
+
from dfindexeddb import errors
|
|
27
|
+
from dfindexeddb import utils
|
|
28
|
+
from dfindexeddb.indexeddb import types
|
|
29
|
+
from dfindexeddb.indexeddb.firefox import definitions
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclasses.dataclass
|
|
33
|
+
class IDBKey(utils.FromDecoderMixin):
|
|
34
|
+
"""An IndexedDB Key.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
offset: the offset of the key.
|
|
38
|
+
type: the type of the key.
|
|
39
|
+
value: the value of the key.
|
|
40
|
+
"""
|
|
41
|
+
offset: int
|
|
42
|
+
type: definitions.IndexedDBKeyType
|
|
43
|
+
value: Union[bytes, datetime.datetime, float, list, str, None]
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def FromDecoder(
|
|
47
|
+
cls, decoder: utils.StreamDecoder, base_offset: int = 0) -> IDBKey:
|
|
48
|
+
"""Decodes an IndexedDB key from the current position of the decoder.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
decoder: the decoder.
|
|
52
|
+
base_offset: the base offset.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The IndexedDB Key.
|
|
56
|
+
"""
|
|
57
|
+
offset, key_type, data = KeyDecoder(decoder.stream).DecodeKey()
|
|
58
|
+
return cls(offset=offset + base_offset, type=key_type, value=data)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class KeyDecoder(utils.StreamDecoder):
|
|
62
|
+
"""A helper class to decode the Gecko-encoded key value."""
|
|
63
|
+
|
|
64
|
+
def DecodeKey(self) -> Tuple[int, definitions.IndexedDBKeyType, Any]:
|
|
65
|
+
"""Decodes the key.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
a tuple comprising the offset where the key was decoded, the key type and
|
|
69
|
+
the key value.
|
|
70
|
+
"""
|
|
71
|
+
offset, key_type_bytes = self.PeekBytes(1)
|
|
72
|
+
value = self._DecodeJSValInternal(key_type_bytes[0])
|
|
73
|
+
if key_type_bytes[0] >= definitions.IndexedDBKeyType.ARRAY:
|
|
74
|
+
key_type = definitions.IndexedDBKeyType.ARRAY
|
|
75
|
+
else:
|
|
76
|
+
key_type = definitions.IndexedDBKeyType(key_type_bytes[0])
|
|
77
|
+
return offset, key_type, value
|
|
78
|
+
|
|
79
|
+
def _DecodeJSValInternal(
|
|
80
|
+
self,
|
|
81
|
+
key_type: int,
|
|
82
|
+
type_offset: int = 0,
|
|
83
|
+
recursion_depth: int = 0
|
|
84
|
+
) -> Any:
|
|
85
|
+
"""Decodes a Javascript Value.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
key_type: the key type byte value.
|
|
89
|
+
type_offset: the type_offset.
|
|
90
|
+
recursion_depth: the current recursion depth.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
the value.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
ParserError: when reaching maximum recursion depth or when an known key
|
|
97
|
+
type has been parsed.
|
|
98
|
+
"""
|
|
99
|
+
if recursion_depth == definitions.MAX_RECURSION_DEPTH:
|
|
100
|
+
raise errors.ParserError('Reached Maximum Recursion Depth')
|
|
101
|
+
|
|
102
|
+
if key_type - type_offset >= definitions.IndexedDBKeyType.ARRAY:
|
|
103
|
+
type_offset += definitions.IndexedDBKeyType.ARRAY
|
|
104
|
+
if (type_offset ==
|
|
105
|
+
definitions.IndexedDBKeyType.ARRAY * definitions.MAX_ARRAY_COLLAPSE):
|
|
106
|
+
_ = self.ReadBytes(1)
|
|
107
|
+
type_offset = 0
|
|
108
|
+
value = []
|
|
109
|
+
while (
|
|
110
|
+
self.NumRemainingBytes() and (key_type - type_offset) != (
|
|
111
|
+
definitions.IndexedDBKeyType.TERMINATOR)):
|
|
112
|
+
value.append(
|
|
113
|
+
self._DecodeJSValInternal(
|
|
114
|
+
key_type, type_offset, recursion_depth + 1))
|
|
115
|
+
type_offset = 0
|
|
116
|
+
if self.NumRemainingBytes():
|
|
117
|
+
_, key_bytes = self.PeekBytes(1)
|
|
118
|
+
key_type = key_bytes[0]
|
|
119
|
+
|
|
120
|
+
if self.NumRemainingBytes():
|
|
121
|
+
_ = self.ReadBytes(1) # consume terminator byte
|
|
122
|
+
|
|
123
|
+
elif key_type - type_offset == definitions.IndexedDBKeyType.STRING:
|
|
124
|
+
_, value = self._DecodeAsStringy()
|
|
125
|
+
value = value.decode('utf-8') # TODO: test other text encoding types
|
|
126
|
+
elif key_type - type_offset == definitions.IndexedDBKeyType.DATE:
|
|
127
|
+
_, value = self._DecodeDate()
|
|
128
|
+
elif key_type - type_offset == definitions.IndexedDBKeyType.FLOAT:
|
|
129
|
+
_, value = self._DecodeFloat()
|
|
130
|
+
elif key_type - type_offset == definitions.IndexedDBKeyType.BINARY:
|
|
131
|
+
_, value = self._DecodeAsStringy()
|
|
132
|
+
else:
|
|
133
|
+
raise errors.ParserError(
|
|
134
|
+
f'Unknown key type parsed: {key_type - type_offset}')
|
|
135
|
+
return value
|
|
136
|
+
|
|
137
|
+
def _ReadUntilNull(self) -> bytearray:
|
|
138
|
+
"""Read bytes until a null (terminator) byte is encountered."""
|
|
139
|
+
result = bytearray()
|
|
140
|
+
num_remaining_bytes = self.NumRemainingBytes()
|
|
141
|
+
while num_remaining_bytes:
|
|
142
|
+
_, c = self.ReadBytes(1)
|
|
143
|
+
if c == b'\x00':
|
|
144
|
+
break
|
|
145
|
+
result += c
|
|
146
|
+
num_remaining_bytes -= 1
|
|
147
|
+
return result
|
|
148
|
+
|
|
149
|
+
def _DecodeAsStringy(self, element_size: int = 1) -> Tuple[int, bytes]:
|
|
150
|
+
"""Decodes a string buffer.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
element_size: parse string as a multi-byte string.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
A tuple of the offset and the decoded string.
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
ParserError: if an invalid type is read or an unsupported
|
|
160
|
+
encoded byte is encountered
|
|
161
|
+
"""
|
|
162
|
+
offset, type_int = self.DecodeUint8()
|
|
163
|
+
if type_int % definitions.IndexedDBKeyType.ARRAY.value not in (
|
|
164
|
+
definitions.IndexedDBKeyType.STRING.value,
|
|
165
|
+
definitions.IndexedDBKeyType.BINARY.value):
|
|
166
|
+
raise errors.ParserError(
|
|
167
|
+
'Invalid type', hex(type_int), hex(type_int % 0x50))
|
|
168
|
+
|
|
169
|
+
i = 0
|
|
170
|
+
result = self._ReadUntilNull()
|
|
171
|
+
while i < len(result):
|
|
172
|
+
if not result[i] & 0x80:
|
|
173
|
+
result[i] -= definitions.ONE_BYTE_ADJUST
|
|
174
|
+
elif element_size == 2 or not result[i] & 0x40:
|
|
175
|
+
c = struct.unpack_from('>H', result, i)[0]
|
|
176
|
+
d = c - 0x8000 - definitions.TWO_BYTE_ADJUST
|
|
177
|
+
struct.pack_into('>H', result, i, d)
|
|
178
|
+
else:
|
|
179
|
+
raise errors.ParserError('Unsupported byte') # TODO: add support.
|
|
180
|
+
i += 1
|
|
181
|
+
return offset + 1, result
|
|
182
|
+
|
|
183
|
+
def _DecodeFloat(self) -> Tuple[int, float]:
|
|
184
|
+
"""Decodes a float.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
A tuple of the offset and a (double-precision) float value.
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
ParserError if an invalid type is read
|
|
191
|
+
"""
|
|
192
|
+
_, type_byte = self.DecodeUint8()
|
|
193
|
+
if type_byte % definitions.IndexedDBKeyType.ARRAY not in (
|
|
194
|
+
definitions.IndexedDBKeyType.FLOAT, definitions.IndexedDBKeyType.DATE):
|
|
195
|
+
raise errors.ParserError(
|
|
196
|
+
'Invalid type', hex(type_byte), hex(type_byte % 0x50))
|
|
197
|
+
|
|
198
|
+
float_offset, number_bytes = self.ReadBytes(
|
|
199
|
+
min(8, self.NumRemainingBytes()))
|
|
200
|
+
|
|
201
|
+
# pad to 64-bits
|
|
202
|
+
if len(number_bytes) != 8:
|
|
203
|
+
number_bytes += b'\x00'*(8 - len(number_bytes))
|
|
204
|
+
|
|
205
|
+
int_value = struct.unpack('>q', number_bytes)[0]
|
|
206
|
+
|
|
207
|
+
if int_value & 0x8000000000000000:
|
|
208
|
+
int_value = int_value & 0x7FFFFFFFFFFFFFFF
|
|
209
|
+
else:
|
|
210
|
+
int_value = -int_value
|
|
211
|
+
return float_offset, struct.unpack('>d', struct.pack('>q', int_value))[0]
|
|
212
|
+
|
|
213
|
+
def _DecodeBinary(self) -> Tuple[int, bytes]:
|
|
214
|
+
"""Decodes a binary value.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
A tuple of the offset and the bytearray.
|
|
218
|
+
|
|
219
|
+
Raises:
|
|
220
|
+
ParserError: when an invalid type byte is encountered.
|
|
221
|
+
"""
|
|
222
|
+
_, type_byte = self.ReadBytes(1)
|
|
223
|
+
if (type_byte[0] % definitions.IndexedDBKeyType.ARRAY !=
|
|
224
|
+
definitions.IndexedDBKeyType.BINARY):
|
|
225
|
+
raise errors.ParserError(
|
|
226
|
+
f'Invalid type {type_byte[0] % definitions.IndexedDBKeyType.ARRAY}')
|
|
227
|
+
values = bytearray()
|
|
228
|
+
offset = self.stream.tell()
|
|
229
|
+
while self.NumRemainingBytes():
|
|
230
|
+
_, value = self.ReadBytes(1)
|
|
231
|
+
if value == definitions.IndexedDBKeyType.TERMINATOR:
|
|
232
|
+
break
|
|
233
|
+
values.append(value[0])
|
|
234
|
+
return offset, values
|
|
235
|
+
|
|
236
|
+
def _DecodeDate(self) -> Tuple[int, datetime.datetime]:
|
|
237
|
+
"""Decodes a date.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
A tuple of the offset and the decoded datetime.
|
|
241
|
+
"""
|
|
242
|
+
offset, value = self._DecodeFloat()
|
|
243
|
+
return offset, datetime.datetime.fromtimestamp(
|
|
244
|
+
value/1000, tz=datetime.timezone.utc)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class JSStructuredCloneDecoder(utils.FromDecoderMixin):
|
|
248
|
+
"""Decodes a serialized JavaScript value.
|
|
249
|
+
|
|
250
|
+
Attributes:
|
|
251
|
+
decoder: for decoding the buffer.
|
|
252
|
+
all_objects: all the decoded objects.
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
def __init__(self, decoder: utils.StreamDecoder):
|
|
256
|
+
"""Initializes the reader.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
buffer: the serialized bytes.
|
|
260
|
+
"""
|
|
261
|
+
self.decoder = decoder
|
|
262
|
+
self.all_objects: List[Any] = []
|
|
263
|
+
self._object_stack: List[Any] = []
|
|
264
|
+
|
|
265
|
+
def _PeekTagAndData(self) -> tuple[int, int]:
|
|
266
|
+
"""Peeks Tag and Data values."""
|
|
267
|
+
_, pair_bytes = self.decoder.PeekBytes(8)
|
|
268
|
+
pair = int.from_bytes(pair_bytes, byteorder='little', signed=False)
|
|
269
|
+
tag = pair >> 32
|
|
270
|
+
data = pair & 0x00000000FFFFFFFF
|
|
271
|
+
return tag, data
|
|
272
|
+
|
|
273
|
+
def _DecodeTagAndData(self) -> tuple[int, int]:
|
|
274
|
+
"""Decodes Tag and Data values."""
|
|
275
|
+
_, pair = self.decoder.DecodeUint64()
|
|
276
|
+
tag = pair >> 32
|
|
277
|
+
data = pair & 0x00000000FFFFFFFF
|
|
278
|
+
return tag, data
|
|
279
|
+
|
|
280
|
+
def _DecodeString(self, data: int) -> str:
|
|
281
|
+
"""Decodes a String.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
data: the data value.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
the decoded string.
|
|
288
|
+
|
|
289
|
+
Raises:
|
|
290
|
+
ParserError: if the serialized string length is too large.
|
|
291
|
+
"""
|
|
292
|
+
number_chars = data & 0x7FFFFFFF
|
|
293
|
+
latin1 = data & 0x80000000
|
|
294
|
+
if number_chars > definitions.MAX_LENGTH:
|
|
295
|
+
raise errors.ParserError('Bad serialized string length')
|
|
296
|
+
|
|
297
|
+
if latin1:
|
|
298
|
+
_, chars = self.decoder.ReadBytes(number_chars)
|
|
299
|
+
chars = chars.decode('latin-1')
|
|
300
|
+
else:
|
|
301
|
+
_, chars = self.decoder.ReadBytes(number_chars*2)
|
|
302
|
+
chars = chars.decode('utf-16-le')
|
|
303
|
+
|
|
304
|
+
return chars
|
|
305
|
+
|
|
306
|
+
def _DecodeBigInt(self, data: int) -> int:
|
|
307
|
+
"""Decodes a BigInteger.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
data: the data value
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
the decoded integer.
|
|
314
|
+
"""
|
|
315
|
+
length = data & 0x7FFFFFFF
|
|
316
|
+
is_negative = data & 0x80000000
|
|
317
|
+
if length == 0:
|
|
318
|
+
return 0
|
|
319
|
+
contents = []
|
|
320
|
+
for _ in range(length):
|
|
321
|
+
_, element = self.decoder.ReadBytes(8)
|
|
322
|
+
contents.extend(element)
|
|
323
|
+
return int.from_bytes(
|
|
324
|
+
contents, byteorder='little', signed=bool(is_negative))
|
|
325
|
+
|
|
326
|
+
def _DecodeTypedArray(
|
|
327
|
+
self, array_type: int, number_elements: int, is_version1: bool = False
|
|
328
|
+
) -> Any:
|
|
329
|
+
"""Decodes a typed array.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
array_type: the array type.
|
|
333
|
+
number_elements: the number of elements.
|
|
334
|
+
is_version1: True if the typed array is of version 1 type.
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
The typed array.
|
|
338
|
+
"""
|
|
339
|
+
self.all_objects.append(None)
|
|
340
|
+
dummy_index = len(self.all_objects) - 1
|
|
341
|
+
|
|
342
|
+
is_auto_length = number_elements == -1
|
|
343
|
+
if is_auto_length:
|
|
344
|
+
number_elements = 0
|
|
345
|
+
|
|
346
|
+
if is_version1:
|
|
347
|
+
value = self._DecodeV1ArrayBuffer(array_type, number_elements)
|
|
348
|
+
_byte_offset = 0
|
|
349
|
+
else:
|
|
350
|
+
value = self._StartRead()
|
|
351
|
+
_byte_offset = self.decoder.DecodeUint64()
|
|
352
|
+
|
|
353
|
+
self.all_objects[dummy_index] = value
|
|
354
|
+
return value
|
|
355
|
+
|
|
356
|
+
def _DecodeArrayBuffer(
|
|
357
|
+
self, buffer_type: int, data: int
|
|
358
|
+
) -> bytes:
|
|
359
|
+
"""Decodes an ArrayBuffer.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
buffer_type: the data type.
|
|
363
|
+
data: the data value.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
the array buffer.
|
|
367
|
+
"""
|
|
368
|
+
if buffer_type == definitions.StructuredDataType.ARRAY_BUFFER_OBJECT:
|
|
369
|
+
_, number_bytes = self.decoder.DecodeUint64()
|
|
370
|
+
elif (buffer_type ==
|
|
371
|
+
definitions.StructuredDataType.RESIZABLE_ARRAY_BUFFER_OBJECT):
|
|
372
|
+
_, number_bytes = self.decoder.DecodeUint64()
|
|
373
|
+
_, _max_bytes = self.decoder.DecodeUint64()
|
|
374
|
+
else:
|
|
375
|
+
number_bytes = data
|
|
376
|
+
|
|
377
|
+
_, buffer = self.decoder.ReadBytes(number_bytes)
|
|
378
|
+
return buffer
|
|
379
|
+
|
|
380
|
+
def _DecodeV1ArrayBuffer(self, array_type: int, number_elements: int):
|
|
381
|
+
"""Decodes a V1 ArrayBuffer.
|
|
382
|
+
|
|
383
|
+
Not currently implemented.
|
|
384
|
+
|
|
385
|
+
Raises:
|
|
386
|
+
NotImplementedError: because it's not yet supported.
|
|
387
|
+
"""
|
|
388
|
+
raise NotImplementedError('V1 Array Buffer not supported')
|
|
389
|
+
|
|
390
|
+
def DecodeValue(self) -> Any:
|
|
391
|
+
"""Decodes a Javascript value."""
|
|
392
|
+
if not self._DecodeHeader():
|
|
393
|
+
return None
|
|
394
|
+
|
|
395
|
+
tag, _ = self._PeekTagAndData()
|
|
396
|
+
if tag == definitions.StructuredDataType.TRANSFER_MAP_HEADER:
|
|
397
|
+
raise errors.ParserError('Transfer Map is not supported.')
|
|
398
|
+
|
|
399
|
+
value = self._StartRead()
|
|
400
|
+
|
|
401
|
+
while self._object_stack:
|
|
402
|
+
offset = self.decoder.stream.tell() % 8
|
|
403
|
+
if offset:
|
|
404
|
+
self.decoder.stream.seek(8 - offset, io.SEEK_CUR)
|
|
405
|
+
tag, _ = self._PeekTagAndData()
|
|
406
|
+
obj = self._object_stack[-1]
|
|
407
|
+
if tag == definitions.StructuredDataType.END_OF_KEYS:
|
|
408
|
+
self._object_stack.pop()
|
|
409
|
+
_, _ = self._DecodeTagAndData()
|
|
410
|
+
continue
|
|
411
|
+
|
|
412
|
+
expect_key_value_pairs = isinstance(obj, (dict, set))
|
|
413
|
+
key = self._StartRead()
|
|
414
|
+
if isinstance(key, types.Null) and expect_key_value_pairs:
|
|
415
|
+
self._object_stack.pop()
|
|
416
|
+
continue
|
|
417
|
+
|
|
418
|
+
if isinstance(obj, types.JSSet):
|
|
419
|
+
obj.values.add(key)
|
|
420
|
+
elif isinstance(obj, types.JSArray):
|
|
421
|
+
field = self._StartRead()
|
|
422
|
+
obj.values[key] = field
|
|
423
|
+
else:
|
|
424
|
+
field = self._StartRead()
|
|
425
|
+
obj[key] = field
|
|
426
|
+
|
|
427
|
+
return value
|
|
428
|
+
|
|
429
|
+
def _DecodeHeader(self) -> bool:
|
|
430
|
+
"""Decodes the header.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
True if the correct header tag is read.
|
|
434
|
+
|
|
435
|
+
Raises:
|
|
436
|
+
ParserError: when a header tag is not found.
|
|
437
|
+
"""
|
|
438
|
+
tag, _ = self._DecodeTagAndData()
|
|
439
|
+
if tag != definitions.StructuredDataType.HEADER:
|
|
440
|
+
raise errors.ParserError('Header tag not found.')
|
|
441
|
+
return True
|
|
442
|
+
|
|
443
|
+
def _DecodeRegexp(self, flags: int):
|
|
444
|
+
"""Decodes a regular expression.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
flags: the regular expression flags.
|
|
448
|
+
|
|
449
|
+
Raises:
|
|
450
|
+
ParserError: when the string tag is not found.
|
|
451
|
+
"""
|
|
452
|
+
tag, string_data = self._DecodeTagAndData()
|
|
453
|
+
if tag != definitions.StructuredDataType.STRING:
|
|
454
|
+
raise errors.ParserError('String tag not found')
|
|
455
|
+
|
|
456
|
+
pattern = self._DecodeString(string_data)
|
|
457
|
+
return types.RegExp(pattern=pattern, flags=flags)
|
|
458
|
+
|
|
459
|
+
def _StartRead(self) -> Any:
|
|
460
|
+
"""Reads the start of a serialized value.
|
|
461
|
+
|
|
462
|
+
Raises:
|
|
463
|
+
ParserError: when an unsupported StructuredDataType,
|
|
464
|
+
StructuredCloneTag or unknown tag is read.
|
|
465
|
+
"""
|
|
466
|
+
tag, data = self._DecodeTagAndData()
|
|
467
|
+
if tag == definitions.StructuredDataType.NULL:
|
|
468
|
+
value = types.Null()
|
|
469
|
+
elif tag == definitions.StructuredDataType.UNDEFINED:
|
|
470
|
+
value = types.Undefined()
|
|
471
|
+
elif tag == definitions.StructuredDataType.INT32:
|
|
472
|
+
value = data
|
|
473
|
+
elif tag == definitions.StructuredDataType.BOOLEAN:
|
|
474
|
+
value = bool(data)
|
|
475
|
+
elif tag == definitions.StructuredDataType.BOOLEAN_OBJECT:
|
|
476
|
+
value = bool(data)
|
|
477
|
+
self.all_objects.append(value)
|
|
478
|
+
elif tag == definitions.StructuredDataType.STRING:
|
|
479
|
+
value = self._DecodeString(data)
|
|
480
|
+
elif tag == definitions.StructuredDataType.STRING_OBJECT:
|
|
481
|
+
value = self._DecodeString(data)
|
|
482
|
+
self.all_objects.append(value)
|
|
483
|
+
elif tag == definitions.StructuredDataType.NUMBER_OBJECT:
|
|
484
|
+
_, value = self.decoder.DecodeDouble()
|
|
485
|
+
self.all_objects.append(value)
|
|
486
|
+
elif tag == definitions.StructuredDataType.BIGINT:
|
|
487
|
+
value = self._DecodeBigInt(data)
|
|
488
|
+
elif tag == definitions.StructuredDataType.BIGINT_OBJECT:
|
|
489
|
+
value = self._DecodeBigInt(data)
|
|
490
|
+
self.all_objects.append(value)
|
|
491
|
+
elif tag == definitions.StructuredDataType.DATE_OBJECT:
|
|
492
|
+
_, timestamp = self.decoder.DecodeDouble()
|
|
493
|
+
value = datetime.datetime.fromtimestamp(
|
|
494
|
+
timestamp/1000, tz=datetime.timezone.utc)
|
|
495
|
+
self.all_objects.append(value)
|
|
496
|
+
elif tag == definitions.StructuredDataType.REGEXP_OBJECT:
|
|
497
|
+
value = self._DecodeRegexp(data)
|
|
498
|
+
self.all_objects.append(value)
|
|
499
|
+
elif tag == definitions.StructuredDataType.ARRAY_OBJECT:
|
|
500
|
+
value = types.JSArray()
|
|
501
|
+
for _ in range(data):
|
|
502
|
+
value.values.append(None)
|
|
503
|
+
self._object_stack.append(value)
|
|
504
|
+
self.all_objects.append(value)
|
|
505
|
+
elif tag == definitions.StructuredDataType.OBJECT_OBJECT:
|
|
506
|
+
value = {}
|
|
507
|
+
self._object_stack.append(value)
|
|
508
|
+
self.all_objects.append(value)
|
|
509
|
+
elif tag == definitions.StructuredDataType.BACK_REFERENCE_OBJECT:
|
|
510
|
+
value = self.all_objects[data]
|
|
511
|
+
elif tag == definitions.StructuredDataType.ARRAY_BUFFER_OBJECT_V2:
|
|
512
|
+
value = self._DecodeArrayBuffer(tag, data)
|
|
513
|
+
self.all_objects.append(value)
|
|
514
|
+
elif tag == definitions.StructuredDataType.ARRAY_BUFFER_OBJECT:
|
|
515
|
+
value = self._DecodeArrayBuffer(tag, data)
|
|
516
|
+
self.all_objects.append(value)
|
|
517
|
+
elif tag == definitions.StructuredDataType.RESIZABLE_ARRAY_BUFFER_OBJECT:
|
|
518
|
+
value = self._DecodeArrayBuffer(tag, data)
|
|
519
|
+
self.all_objects.append(value)
|
|
520
|
+
elif tag == definitions.StructuredDataType.TYPED_ARRAY_OBJECT_V2:
|
|
521
|
+
_, array_type = self.decoder.DecodeUint64()
|
|
522
|
+
self._DecodeTypedArray(array_type, data)
|
|
523
|
+
elif tag == definitions.StructuredDataType.TYPED_ARRAY_OBJECT:
|
|
524
|
+
_, number_elements = self.decoder.DecodeUint64()
|
|
525
|
+
value = self._DecodeTypedArray(data, number_elements)
|
|
526
|
+
elif tag == definitions.StructuredDataType.MAP_OBJECT:
|
|
527
|
+
value = {}
|
|
528
|
+
self._object_stack.append(value)
|
|
529
|
+
self.all_objects.append(value)
|
|
530
|
+
elif tag == definitions.StructuredDataType.SET_OBJECT:
|
|
531
|
+
value = types.JSSet()
|
|
532
|
+
self._object_stack.append(value)
|
|
533
|
+
self.all_objects.append(value)
|
|
534
|
+
elif tag <= definitions.StructuredDataType.FLOAT_MAX:
|
|
535
|
+
value = struct.unpack('<d', struct.pack('<II', data, tag))[0]
|
|
536
|
+
elif (definitions.StructuredDataType.TYPED_ARRAY_V1_INT8 <= tag
|
|
537
|
+
<= definitions.StructuredDataType.TYPED_ARRAY_V1_UINT8_CLAMPED):
|
|
538
|
+
value = self._DecodeTypedArray(tag, data)
|
|
539
|
+
elif tag in iter(definitions.StructuredDataType):
|
|
540
|
+
raise errors.ParserError(
|
|
541
|
+
'Unsupported StructuredDataType',
|
|
542
|
+
definitions.StructuredDataType(tag))
|
|
543
|
+
elif tag in iter(definitions.StructuredCloneTags):
|
|
544
|
+
raise errors.ParserError(
|
|
545
|
+
'Unsupported StructuredCloneTag',
|
|
546
|
+
definitions.StructuredCloneTags(tag))
|
|
547
|
+
else:
|
|
548
|
+
raise errors.ParserError('Unknown tag', hex(tag))
|
|
549
|
+
|
|
550
|
+
# align the stream to an 8 byte boundary
|
|
551
|
+
offset = self.decoder.stream.tell() % 8
|
|
552
|
+
if offset:
|
|
553
|
+
_, _slack_bytes = self.decoder.ReadBytes(8 - offset)
|
|
554
|
+
|
|
555
|
+
return value
|
|
556
|
+
|
|
557
|
+
@classmethod
|
|
558
|
+
def FromDecoder(
|
|
559
|
+
cls, decoder: utils.StreamDecoder, base_offset: int = 0) -> Any:
|
|
560
|
+
"""Decodes the parsed JavaScript object from the StreamDecoder.
|
|
561
|
+
|
|
562
|
+
Args:
|
|
563
|
+
decoder: the StreamDecoder.
|
|
564
|
+
base_offset: the base offset.
|
|
565
|
+
|
|
566
|
+
Returns:
|
|
567
|
+
The class instance.
|
|
568
|
+
"""
|
|
569
|
+
return cls(decoder).DecodeValue()
|
|
570
|
+
|
|
571
|
+
@classmethod
|
|
572
|
+
def FromBytes(cls, raw_data: bytes, base_offset: int = 0) -> Any:
|
|
573
|
+
"""Returns the parsed JavaScript object from the raw bytes.
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
raw_data: the raw data to deserialize/parse.
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
A python representation of the parsed JavaScript object.
|
|
580
|
+
"""
|
|
581
|
+
if raw_data.startswith(definitions.FRAME_HEADER):
|
|
582
|
+
uncompressed_data = bytearray()
|
|
583
|
+
pos = len(definitions.FRAME_HEADER)
|
|
584
|
+
while pos < len(raw_data):
|
|
585
|
+
is_uncompressed = raw_data[pos]
|
|
586
|
+
block_size = int.from_bytes(
|
|
587
|
+
raw_data[pos + 1:pos + 4], byteorder="little", signed=False)
|
|
588
|
+
_masked_checksum = int.from_bytes(
|
|
589
|
+
raw_data[pos + 4: pos + 9], byteorder="little", signed=False)
|
|
590
|
+
if is_uncompressed:
|
|
591
|
+
uncompressed_data += raw_data[pos + 8: pos + 8 + block_size - 4]
|
|
592
|
+
else:
|
|
593
|
+
uncompressed_data += snappy.decompress(
|
|
594
|
+
raw_data[pos + 8: pos + 8 + block_size - 4])
|
|
595
|
+
pos += block_size + 4
|
|
596
|
+
else:
|
|
597
|
+
uncompressed_data = snappy.decompress(raw_data)
|
|
598
|
+
stream = io.BytesIO(uncompressed_data)
|
|
599
|
+
|
|
600
|
+
return cls.FromStream(stream=stream, base_offset=base_offset)
|