dfindexeddb 20240417__py3-none-any.whl → 20240519__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/blink.py +5 -0
- dfindexeddb/indexeddb/chromium/record.py +90 -7
- dfindexeddb/indexeddb/cli.py +82 -80
- dfindexeddb/indexeddb/safari/definitions.py +123 -0
- dfindexeddb/indexeddb/safari/record.py +238 -0
- dfindexeddb/indexeddb/safari/webkit.py +701 -0
- dfindexeddb/leveldb/cli.py +70 -11
- dfindexeddb/leveldb/log.py +9 -3
- dfindexeddb/leveldb/plugins/__init__.py +17 -0
- dfindexeddb/leveldb/plugins/chrome_notifications.py +135 -0
- dfindexeddb/leveldb/plugins/interface.py +36 -0
- dfindexeddb/leveldb/plugins/manager.py +75 -0
- dfindexeddb/leveldb/plugins/notification_database_data_pb2.py +38 -0
- dfindexeddb/leveldb/record.py +212 -53
- dfindexeddb/utils.py +34 -0
- dfindexeddb/version.py +1 -1
- {dfindexeddb-20240417.dist-info → dfindexeddb-20240519.dist-info}/METADATA +74 -80
- dfindexeddb-20240519.dist-info/RECORD +37 -0
- dfindexeddb-20240417.dist-info/RECORD +0 -29
- {dfindexeddb-20240417.dist-info → dfindexeddb-20240519.dist-info}/AUTHORS +0 -0
- {dfindexeddb-20240417.dist-info → dfindexeddb-20240519.dist-info}/LICENSE +0 -0
- {dfindexeddb-20240417.dist-info → dfindexeddb-20240519.dist-info}/WHEEL +0 -0
- {dfindexeddb-20240417.dist-info → dfindexeddb-20240519.dist-info}/entry_points.txt +0 -0
- {dfindexeddb-20240417.dist-info → dfindexeddb-20240519.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,701 @@
|
|
|
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 WebKit encoded JavaScript values."""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
import io
|
|
21
|
+
import plistlib
|
|
22
|
+
from typing import Any, Dict, List, Tuple, Union
|
|
23
|
+
|
|
24
|
+
from dfindexeddb import errors
|
|
25
|
+
from dfindexeddb import utils
|
|
26
|
+
from dfindexeddb.indexeddb.safari import definitions
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ArrayBufferView:
|
|
31
|
+
"""A parsed JavaScript ArrayBufferView.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
array_buffer_view_subtag: the sub tag.
|
|
35
|
+
buffer: the buffer.
|
|
36
|
+
offset: the offset of the view.
|
|
37
|
+
length: the length of the view.
|
|
38
|
+
"""
|
|
39
|
+
array_buffer_view_subtag: definitions.ArrayBufferViewSubtag
|
|
40
|
+
buffer: bytes
|
|
41
|
+
offset: int
|
|
42
|
+
length: int
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class ResizableArrayBuffer:
|
|
47
|
+
"""A parsed Resizable Array Buffer.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
buffer: the buffer.
|
|
51
|
+
max_length: the maximum length of the buffer (for resizing).
|
|
52
|
+
"""
|
|
53
|
+
buffer: bytes
|
|
54
|
+
max_length: int
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class FileData:
|
|
59
|
+
"""A parsed FileData.
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
path: the path.
|
|
63
|
+
url: the URL.
|
|
64
|
+
type: the type.
|
|
65
|
+
name: the file name.
|
|
66
|
+
last_modified: the last modified timestamp.
|
|
67
|
+
"""
|
|
68
|
+
path: str
|
|
69
|
+
url: str
|
|
70
|
+
type: str
|
|
71
|
+
name: str
|
|
72
|
+
last_modified: float
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class FileList:
|
|
77
|
+
"""A parsed FileList.
|
|
78
|
+
|
|
79
|
+
Attributes:
|
|
80
|
+
files: the list of files.
|
|
81
|
+
"""
|
|
82
|
+
files: List[FileData]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class JSArray(list):
|
|
86
|
+
"""A parsed JavaScript array.
|
|
87
|
+
|
|
88
|
+
This is a wrapper around a standard Python list to allow assigning arbitrary
|
|
89
|
+
properties as is possible in the JavaScript equivalent.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __repr__(self):
|
|
93
|
+
array_entries = ", ".join([str(entry) for entry in list(self)])
|
|
94
|
+
properties = ", ".join(
|
|
95
|
+
f'{key}: {value}' for key, value in self.properties.items())
|
|
96
|
+
return f'[{array_entries}, {properties}]'
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def properties(self) -> Dict[str, Any]:
|
|
100
|
+
"""Returns the object properties."""
|
|
101
|
+
return self.__dict__
|
|
102
|
+
|
|
103
|
+
def __contains__(self, item):
|
|
104
|
+
return item in self.__dict__
|
|
105
|
+
|
|
106
|
+
def __getitem__(self, name):
|
|
107
|
+
return self.__dict__[name]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class JSSet(set):
|
|
111
|
+
"""A parsed JavaScript set.
|
|
112
|
+
|
|
113
|
+
This is a wrapper around a standard Python set to allow assigning arbitrary
|
|
114
|
+
properties as is possible in the JavaScript equivalent.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __repr__(self):
|
|
118
|
+
set_entries = ", ".join([str(entry) for entry in list(self)])
|
|
119
|
+
properties = ", ".join(
|
|
120
|
+
f'{key}: {value}' for key, value in self.properties.items())
|
|
121
|
+
return f'[{set_entries}, {properties}]'
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def properties(self) -> Dict[str, Any]:
|
|
125
|
+
"""Returns the object properties."""
|
|
126
|
+
return self.__dict__
|
|
127
|
+
|
|
128
|
+
def __contains__(self, item):
|
|
129
|
+
return item in self.__dict__
|
|
130
|
+
|
|
131
|
+
def __getitem__(self, name):
|
|
132
|
+
return self.__dict__[name]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass
|
|
136
|
+
class Null:
|
|
137
|
+
"""A parsed JavaScript Null."""
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass
|
|
141
|
+
class RegExp:
|
|
142
|
+
"""A parsed JavaScript RegExp.
|
|
143
|
+
|
|
144
|
+
Attributes:
|
|
145
|
+
pattern: the pattern.
|
|
146
|
+
flags: the flags.
|
|
147
|
+
"""
|
|
148
|
+
pattern: str
|
|
149
|
+
flags: str
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass(frozen=True)
|
|
153
|
+
class Undefined:
|
|
154
|
+
"""A parsed JavaScript undef."""
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class IDBKeyData(utils.FromDecoderMixin):
|
|
159
|
+
"""An IDBKeyData.
|
|
160
|
+
|
|
161
|
+
Attributes:
|
|
162
|
+
offset: the offset at which the IDBKeyData was parsed.
|
|
163
|
+
key_type: the IDB Key Type.
|
|
164
|
+
data: the key data.
|
|
165
|
+
"""
|
|
166
|
+
offset: int
|
|
167
|
+
key_type: definitions.SIDBKeyType
|
|
168
|
+
data: Union[float, datetime, str, bytes, list]
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def FromDecoder(
|
|
172
|
+
cls, decoder: utils.StreamDecoder, base_offset: int = 0) -> IDBKeyData:
|
|
173
|
+
"""Decodes an IDBKeyData from the current position of decoder.
|
|
174
|
+
|
|
175
|
+
Refer to IDBSerialization.cpp for the encoding scheme.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
decoder: the decoder
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
the IDBKeyData.
|
|
182
|
+
|
|
183
|
+
Raises:
|
|
184
|
+
ParserError: when the key version is not found or an unknown key type is
|
|
185
|
+
encountered or an old-style PropertyList key type is found.
|
|
186
|
+
"""
|
|
187
|
+
def _DecodeKeyBuffer(key_type):
|
|
188
|
+
if key_type == definitions.SIDBKeyType.MIN:
|
|
189
|
+
data = None
|
|
190
|
+
if key_type == definitions.SIDBKeyType.NUMBER:
|
|
191
|
+
_, data = decoder.DecodeDouble()
|
|
192
|
+
elif key_type == definitions.SIDBKeyType.DATE:
|
|
193
|
+
_, timestamp = decoder.DecodeDouble()
|
|
194
|
+
data = datetime.utcfromtimestamp(timestamp/1000)
|
|
195
|
+
elif key_type == definitions.SIDBKeyType.STRING:
|
|
196
|
+
_, length = decoder.DecodeUint32()
|
|
197
|
+
_, raw_data = decoder.ReadBytes(length*2)
|
|
198
|
+
data = raw_data.decode('utf-16-le')
|
|
199
|
+
elif key_type == definitions.SIDBKeyType.BINARY:
|
|
200
|
+
_, length = decoder.DecodeUint32()
|
|
201
|
+
_, data = decoder.ReadBytes(length)
|
|
202
|
+
elif key_type == definitions.SIDBKeyType.ARRAY:
|
|
203
|
+
_, length = decoder.DecodeUint64()
|
|
204
|
+
data = []
|
|
205
|
+
for _ in range(length):
|
|
206
|
+
_, key_type = decoder.DecodeUint8()
|
|
207
|
+
element = _DecodeKeyBuffer(key_type)
|
|
208
|
+
data.append(element)
|
|
209
|
+
else:
|
|
210
|
+
raise errors.ParserError('Unknown definitions.SIDBKeyType found.')
|
|
211
|
+
return data
|
|
212
|
+
|
|
213
|
+
offset, version_header = decoder.DecodeUint8()
|
|
214
|
+
if version_header != definitions.SIDBKeyVersion:
|
|
215
|
+
raise errors.ParserError('SIDBKeyVersion not found.')
|
|
216
|
+
|
|
217
|
+
_, raw_key_type = decoder.DecodeUint8()
|
|
218
|
+
key_type = definitions.SIDBKeyType(raw_key_type)
|
|
219
|
+
|
|
220
|
+
# "Old-style key is characterized by this magic character that
|
|
221
|
+
# begins serialized PropertyLists
|
|
222
|
+
if key_type == b'b':
|
|
223
|
+
raise errors.ParserError('Old-style PropertyList key type found.')
|
|
224
|
+
data = _DecodeKeyBuffer(key_type)
|
|
225
|
+
|
|
226
|
+
return cls(
|
|
227
|
+
offset=offset+base_offset,
|
|
228
|
+
key_type=key_type,
|
|
229
|
+
data=data)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class SerializedScriptValueDecoder():
|
|
233
|
+
"""Decodes a Serialized Script Value from a stream of bytes.
|
|
234
|
+
|
|
235
|
+
Attributes:
|
|
236
|
+
decoder: the stream decoder for the given byte stream.
|
|
237
|
+
version: the parsed serialized script version.
|
|
238
|
+
constant_pool: the constant pool.
|
|
239
|
+
object_pool: the object pool.
|
|
240
|
+
"""
|
|
241
|
+
def __init__(self, stream: io.BytesIO):
|
|
242
|
+
self.decoder = utils.StreamDecoder(stream)
|
|
243
|
+
self.version = None
|
|
244
|
+
self.constant_pool = []
|
|
245
|
+
self.object_pool = []
|
|
246
|
+
|
|
247
|
+
def PeekTag(self) -> int:
|
|
248
|
+
"""Peeks a tag from the current position."""
|
|
249
|
+
_, peeked_bytes = self.decoder.PeekBytes(4)
|
|
250
|
+
return int.from_bytes(peeked_bytes, byteorder='little')
|
|
251
|
+
|
|
252
|
+
def PeekSerializationTag(self) -> definitions.SerializationTag:
|
|
253
|
+
"""Peeks a SerializationTag from the current position.
|
|
254
|
+
|
|
255
|
+
Raises:
|
|
256
|
+
ParserError if an invalid SerializationTag was parsed.
|
|
257
|
+
"""
|
|
258
|
+
offset, terminal_byte = self.decoder.PeekBytes(1)
|
|
259
|
+
try:
|
|
260
|
+
return definitions.SerializationTag(terminal_byte[0])
|
|
261
|
+
except ValueError as error:
|
|
262
|
+
raise errors.ParserError(
|
|
263
|
+
f'Invalid SerializationTag {terminal_byte} at offset {offset}'
|
|
264
|
+
) from error
|
|
265
|
+
|
|
266
|
+
def DecodeSerializationTag(self) -> Tuple[int, definitions.SerializationTag]:
|
|
267
|
+
"""Decodes a SerializationTag.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
a tuple of the offset and the serialization tag.
|
|
271
|
+
|
|
272
|
+
Raises:
|
|
273
|
+
ParserError if an invalid terminal value was encountered.
|
|
274
|
+
"""
|
|
275
|
+
offset, terminal_byte = self.decoder.DecodeUint8()
|
|
276
|
+
try:
|
|
277
|
+
return offset, definitions.SerializationTag(terminal_byte)
|
|
278
|
+
except ValueError as error:
|
|
279
|
+
raise errors.ParserError(
|
|
280
|
+
f'Invalid terminal {terminal_byte} at offset {offset}') from error
|
|
281
|
+
|
|
282
|
+
def DecodeArray(self) -> JSArray:
|
|
283
|
+
"""Decodes an Array value.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
the JavaScript array.
|
|
287
|
+
|
|
288
|
+
Raises:
|
|
289
|
+
ParserError if an invalid Terminator tag was found.
|
|
290
|
+
"""
|
|
291
|
+
_, length = self.decoder.DecodeUint32()
|
|
292
|
+
array = JSArray()
|
|
293
|
+
self.object_pool.append(array)
|
|
294
|
+
for _ in range(length):
|
|
295
|
+
_, _ = self.decoder.DecodeUint32()
|
|
296
|
+
_, value = self.DecodeValue()
|
|
297
|
+
array.append(value)
|
|
298
|
+
|
|
299
|
+
offset, terminator_tag = self.decoder.DecodeUint32()
|
|
300
|
+
if terminator_tag != definitions.TerminatorTag:
|
|
301
|
+
raise errors.ParserError(f'Terminator tag not found at offset {offset}.')
|
|
302
|
+
|
|
303
|
+
offset, tag = self.decoder.DecodeUint32()
|
|
304
|
+
if tag == definitions.NonIndexPropertiesTag:
|
|
305
|
+
while tag != definitions.TerminatorTag:
|
|
306
|
+
name = self.DecodeStringData()
|
|
307
|
+
_, value = self.DecodeValue()
|
|
308
|
+
_, tag = self.decoder.DecodeUint32()
|
|
309
|
+
array.properties[name] = value
|
|
310
|
+
elif tag != definitions.TerminatorTag:
|
|
311
|
+
raise errors.ParserError(f'Terminator tag not found at offset {offset}.')
|
|
312
|
+
return array
|
|
313
|
+
|
|
314
|
+
def DecodeObject(self) -> Dict[str, Any]:
|
|
315
|
+
"""Decodes an Object value."""
|
|
316
|
+
tag = self.PeekTag()
|
|
317
|
+
js_object = {}
|
|
318
|
+
self.object_pool.append(js_object)
|
|
319
|
+
while tag != definitions.TerminatorTag:
|
|
320
|
+
name = self.DecodeStringData()
|
|
321
|
+
_, value = self.DecodeValue()
|
|
322
|
+
js_object[name] = value
|
|
323
|
+
tag = self.PeekTag()
|
|
324
|
+
_ = self.decoder.DecodeUint32()
|
|
325
|
+
return js_object
|
|
326
|
+
|
|
327
|
+
def DecodeStringData(self) -> str:
|
|
328
|
+
"""Decodes a StringData value.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
A JavaScript array.
|
|
332
|
+
|
|
333
|
+
Raises:
|
|
334
|
+
ParserError if an:
|
|
335
|
+
* unexpected TerminatorTag is found
|
|
336
|
+
* unexpected constant pool size value is found
|
|
337
|
+
* disallowed string length is found.
|
|
338
|
+
* unable to to decode a buffer as utf-16-le.
|
|
339
|
+
"""
|
|
340
|
+
peeked_tag = self.PeekTag()
|
|
341
|
+
if peeked_tag == definitions.TerminatorTag:
|
|
342
|
+
raise errors.ParserError('Unexpected TerminatorTag found')
|
|
343
|
+
|
|
344
|
+
if peeked_tag == definitions.StringPoolTag:
|
|
345
|
+
_ = self.decoder.DecodeUint32()
|
|
346
|
+
if len(self.constant_pool) <= 0xff:
|
|
347
|
+
_, cp_index = self.decoder.DecodeUint8()
|
|
348
|
+
elif len(self.constant_pool) <= 0xffff:
|
|
349
|
+
_, cp_index = self.decoder.DecodeUint16()
|
|
350
|
+
elif len(self.constant_pool) <= 0xffffffff:
|
|
351
|
+
_, cp_index = self.decoder.DecodeUint32()
|
|
352
|
+
else:
|
|
353
|
+
raise errors.ParserError('Unexpected constant pool size value.')
|
|
354
|
+
return self.constant_pool[cp_index]
|
|
355
|
+
|
|
356
|
+
_, length_with_8bit_flag = self.decoder.DecodeUint32()
|
|
357
|
+
if length_with_8bit_flag == definitions.TerminatorTag:
|
|
358
|
+
raise errors.ParserError('Disallowed string length found.')
|
|
359
|
+
|
|
360
|
+
length = length_with_8bit_flag & 0x7FFFFFFF
|
|
361
|
+
is_8bit = length_with_8bit_flag & definitions.StringDataIs8BitFlag
|
|
362
|
+
|
|
363
|
+
if is_8bit:
|
|
364
|
+
_, characters = self.decoder.ReadBytes(length)
|
|
365
|
+
value = characters.decode('latin-1')
|
|
366
|
+
else:
|
|
367
|
+
_, characters = self.decoder.ReadBytes(2*length)
|
|
368
|
+
try:
|
|
369
|
+
value = characters.decode('utf-16-le')
|
|
370
|
+
except UnicodeDecodeError:
|
|
371
|
+
raise errors.ParserError(
|
|
372
|
+
f'Unable to decode {len(characters)} characters as utf-16-le')
|
|
373
|
+
self.constant_pool.append(value)
|
|
374
|
+
return value
|
|
375
|
+
|
|
376
|
+
def DecodeDate(self) -> datetime:
|
|
377
|
+
"""Decodes a Date value."""
|
|
378
|
+
_, timestamp = self.decoder.DecodeDouble()
|
|
379
|
+
value = datetime.utcfromtimestamp(timestamp/1000)
|
|
380
|
+
return value
|
|
381
|
+
|
|
382
|
+
def DecodeFileData(self) -> FileData:
|
|
383
|
+
"""Decodes a FileData value."""
|
|
384
|
+
path = self.DecodeStringData()
|
|
385
|
+
url = self.DecodeStringData()
|
|
386
|
+
file_type = self.DecodeStringData()
|
|
387
|
+
name = self.DecodeStringData()
|
|
388
|
+
_, last_modified = self.decoder.DecodeDouble()
|
|
389
|
+
|
|
390
|
+
return FileData(
|
|
391
|
+
path=path,
|
|
392
|
+
url=url,
|
|
393
|
+
type=file_type,
|
|
394
|
+
name=name,
|
|
395
|
+
last_modified=last_modified)
|
|
396
|
+
|
|
397
|
+
def DecodeFileList(self) -> FileList:
|
|
398
|
+
"""Decodes a FileList value."""
|
|
399
|
+
_, length = self.decoder.DecodeUint32()
|
|
400
|
+
file_list = []
|
|
401
|
+
for _ in range(length):
|
|
402
|
+
file_list.append(self.DecodeFileData())
|
|
403
|
+
return FileList(files=file_list)
|
|
404
|
+
|
|
405
|
+
def DecodeImageData(self) -> Dict[str, Any]:
|
|
406
|
+
"""Decodes an ImageData value."""
|
|
407
|
+
_, width = self.decoder.DecodeUint32()
|
|
408
|
+
_, height = self.decoder.DecodeUint32()
|
|
409
|
+
_, length = self.decoder.DecodeUint32()
|
|
410
|
+
data = self.decoder.ReadBytes(length)
|
|
411
|
+
|
|
412
|
+
if self.version > 7:
|
|
413
|
+
_, color_space = self.decoder.DecodeUint8()
|
|
414
|
+
else:
|
|
415
|
+
color_space = None
|
|
416
|
+
|
|
417
|
+
# TODO: make this a dataclass?
|
|
418
|
+
return {
|
|
419
|
+
'width': width,
|
|
420
|
+
'height': height,
|
|
421
|
+
'length': length,
|
|
422
|
+
'data': data,
|
|
423
|
+
'color_space': color_space
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
def DecodeBlob(self) -> Dict[str, Any]:
|
|
427
|
+
"""Decodes a Blob value."""
|
|
428
|
+
url = self.DecodeStringData()
|
|
429
|
+
blob_type = self.DecodeStringData()
|
|
430
|
+
size = self.decoder.DecodeUint64()
|
|
431
|
+
if self.version >= 11:
|
|
432
|
+
_, memory_cost = self.decoder.DecodeUint64()
|
|
433
|
+
else:
|
|
434
|
+
memory_cost = None
|
|
435
|
+
|
|
436
|
+
# TODO: make this a dataclass?
|
|
437
|
+
return {
|
|
438
|
+
'url': url,
|
|
439
|
+
'blob_type': blob_type,
|
|
440
|
+
'size': size,
|
|
441
|
+
'memory_cost': memory_cost
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
def DecodeRegExp(self) -> RegExp:
|
|
445
|
+
"""Decodes a RegExp value."""
|
|
446
|
+
pattern = self.DecodeStringData()
|
|
447
|
+
flags = self.DecodeStringData()
|
|
448
|
+
return RegExp(pattern=pattern, flags=flags)
|
|
449
|
+
|
|
450
|
+
def DecodeMapData(self) -> dict:
|
|
451
|
+
"""Decodes a Map value."""
|
|
452
|
+
tag = self.PeekSerializationTag()
|
|
453
|
+
js_map = {} # TODO: make this into a JSMap (like JSArray/JSSet)
|
|
454
|
+
self.object_pool.append(js_map)
|
|
455
|
+
|
|
456
|
+
while tag != definitions.SerializationTag.NON_MAP_PROPERTIES:
|
|
457
|
+
_, key = self.DecodeValue()
|
|
458
|
+
_, value = self.DecodeValue()
|
|
459
|
+
js_map[key] = value
|
|
460
|
+
tag = self.PeekSerializationTag()
|
|
461
|
+
|
|
462
|
+
# consume the NonMapPropertiesTag
|
|
463
|
+
_, tag = self.DecodeSerializationTag()
|
|
464
|
+
|
|
465
|
+
pool_tag = self.PeekTag()
|
|
466
|
+
while pool_tag != definitions.TerminatorTag:
|
|
467
|
+
name = self.DecodeStringData()
|
|
468
|
+
value = self.DecodeValue()
|
|
469
|
+
js_map[name] = value
|
|
470
|
+
pool_tag = self.PeekTag()
|
|
471
|
+
|
|
472
|
+
_, tag = self.decoder.DecodeUint32()
|
|
473
|
+
return js_map
|
|
474
|
+
|
|
475
|
+
def DecodeSetData(self) -> JSSet:
|
|
476
|
+
"""Decodes a SetData value."""
|
|
477
|
+
tag = self.PeekSerializationTag()
|
|
478
|
+
js_set = JSSet()
|
|
479
|
+
self.object_pool.append(js_set)
|
|
480
|
+
|
|
481
|
+
while tag != definitions.SerializationTag.NON_SET_PROPERTIES:
|
|
482
|
+
_, key = self.DecodeValue()
|
|
483
|
+
js_set.add(key)
|
|
484
|
+
tag = self.PeekSerializationTag()
|
|
485
|
+
|
|
486
|
+
# consume the NonSetPropertiesTag
|
|
487
|
+
_, tag = self.DecodeSerializationTag()
|
|
488
|
+
|
|
489
|
+
pool_tag = self.PeekTag()
|
|
490
|
+
while pool_tag != definitions.TerminatorTag:
|
|
491
|
+
name = self.DecodeStringData()
|
|
492
|
+
value = self.DecodeValue()
|
|
493
|
+
js_set.properties[name] = value
|
|
494
|
+
pool_tag = self.decoder.PeekBytes(4)
|
|
495
|
+
|
|
496
|
+
# consume the TerminatorTag
|
|
497
|
+
_, tag = self.decoder.DecodeUint32()
|
|
498
|
+
return js_set
|
|
499
|
+
|
|
500
|
+
def DecodeCryptoKey(self) -> bytes:
|
|
501
|
+
"""Decodes a CryptoKey value."""
|
|
502
|
+
_, wrapped_key_length = self.decoder.DecodeUint32()
|
|
503
|
+
_, wrapped_key = self.decoder.ReadBytes(wrapped_key_length)
|
|
504
|
+
key = plistlib.loads(wrapped_key) # TODO: unwrap the wrapped key.
|
|
505
|
+
return key
|
|
506
|
+
|
|
507
|
+
def DecodeBigIntData(self) -> int:
|
|
508
|
+
"""Decodes a BigIntData value."""
|
|
509
|
+
_, sign = self.decoder.DecodeUint8()
|
|
510
|
+
_, number_of_elements = self.decoder.DecodeUint32()
|
|
511
|
+
contents = []
|
|
512
|
+
for _ in range(number_of_elements):
|
|
513
|
+
_, element = self.decoder.ReadBytes(8)
|
|
514
|
+
contents.extend(element)
|
|
515
|
+
value = int.from_bytes(contents, byteorder='little', signed=bool(sign))
|
|
516
|
+
return value
|
|
517
|
+
|
|
518
|
+
def DecodeArrayBuffer(self) -> bytes:
|
|
519
|
+
"""Decodes an ArrayBuffer value."""
|
|
520
|
+
_, byte_length = self.decoder.DecodeUint64()
|
|
521
|
+
_, buffer = self.decoder.ReadBytes(byte_length)
|
|
522
|
+
self.object_pool.append(buffer)
|
|
523
|
+
return buffer
|
|
524
|
+
|
|
525
|
+
def DecodeResizableArrayBuffer(self) -> ResizableArrayBuffer:
|
|
526
|
+
"""Decodes an ArrayBuffer value."""
|
|
527
|
+
_, byte_length = self.decoder.DecodeUint64()
|
|
528
|
+
_, max_length = self.decoder.DecodeUint64()
|
|
529
|
+
_, buffer = self.decoder.ReadBytes(byte_length)
|
|
530
|
+
self.object_pool.append(buffer)
|
|
531
|
+
return ResizableArrayBuffer(buffer=buffer, max_length=max_length)
|
|
532
|
+
|
|
533
|
+
def DecodeArrayBufferTransfer(self) -> int:
|
|
534
|
+
"""Decodes an ArrayBufferTransfer value."""
|
|
535
|
+
_, value = self.decoder.DecodeUint32()
|
|
536
|
+
return value
|
|
537
|
+
|
|
538
|
+
def DecodeSharedArrayBuffer(self) -> int:
|
|
539
|
+
"""Decodes an SharedArrayBuffer value."""
|
|
540
|
+
_, value = self.decoder.DecodeUint32()
|
|
541
|
+
return value
|
|
542
|
+
|
|
543
|
+
def DecodeObjectReference(self) -> Any:
|
|
544
|
+
"""Decodes an ObjectReference value."""
|
|
545
|
+
if len(self.object_pool) < 0xFF:
|
|
546
|
+
_, object_ref = self.decoder.DecodeUint8()
|
|
547
|
+
elif len(self.object_pool) < 0xFFFF:
|
|
548
|
+
_, object_ref = self.decoder.DecodeUint16()
|
|
549
|
+
else: # if len(self.object_pool) < 0xFFFFFFFF:
|
|
550
|
+
_, object_ref = self.decoder.DecodeUint32()
|
|
551
|
+
return self.object_pool[object_ref]
|
|
552
|
+
|
|
553
|
+
def DecodeArrayBufferView(self) -> ArrayBufferView:
|
|
554
|
+
"""Decodes an ArrayBufferView value.
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
an ArrayBufferView.
|
|
558
|
+
|
|
559
|
+
Raises:
|
|
560
|
+
ParserError if an unexpected serialization tag is found.
|
|
561
|
+
"""
|
|
562
|
+
_, array_buffer_view_subtag = self.decoder.DecodeUint8()
|
|
563
|
+
array_buffer_view_subtag = definitions.ArrayBufferViewSubtag(
|
|
564
|
+
array_buffer_view_subtag)
|
|
565
|
+
_, byte_offset = self.decoder.DecodeUint64()
|
|
566
|
+
_, byte_length = self.decoder.DecodeUint64()
|
|
567
|
+
_, next_serialization_tag = self.DecodeSerializationTag()
|
|
568
|
+
|
|
569
|
+
if next_serialization_tag == definitions.SerializationTag.ARRAY_BUFFER:
|
|
570
|
+
value = self.DecodeArrayBuffer()
|
|
571
|
+
elif (next_serialization_tag ==
|
|
572
|
+
definitions.SerializationTag.OBJECT_REFERENCE):
|
|
573
|
+
value = self.DecodeObjectReference()
|
|
574
|
+
else:
|
|
575
|
+
raise errors.ParserError(
|
|
576
|
+
f'Unexpected serialization tag {next_serialization_tag}.')
|
|
577
|
+
return ArrayBufferView(
|
|
578
|
+
array_buffer_view_subtag=array_buffer_view_subtag,
|
|
579
|
+
buffer=value,
|
|
580
|
+
offset=byte_offset,
|
|
581
|
+
length=byte_length)
|
|
582
|
+
|
|
583
|
+
def DecodeSerializedValue(self) -> Any:
|
|
584
|
+
"""Decodes a serialized value.
|
|
585
|
+
|
|
586
|
+
Returns:
|
|
587
|
+
the serialized value.
|
|
588
|
+
|
|
589
|
+
Raises:
|
|
590
|
+
ParserError when CurrentVersion is not found.
|
|
591
|
+
"""
|
|
592
|
+
_, current_version = self.decoder.DecodeUint32()
|
|
593
|
+
if current_version != definitions.CurrentVersion:
|
|
594
|
+
raise errors.ParserError(
|
|
595
|
+
f'{current_version} is not the expected CurrentVersion')
|
|
596
|
+
_, value = self.DecodeValue()
|
|
597
|
+
return value
|
|
598
|
+
|
|
599
|
+
def DecodeValue(self) -> Tuple[int, Any]:
|
|
600
|
+
"""Decodes a value.
|
|
601
|
+
|
|
602
|
+
Returns:
|
|
603
|
+
the offset and parsed value.
|
|
604
|
+
|
|
605
|
+
Raises:
|
|
606
|
+
ParserError when an unhandled SerializationTag is found.
|
|
607
|
+
"""
|
|
608
|
+
offset, tag = self.DecodeSerializationTag()
|
|
609
|
+
if tag == definitions.SerializationTag.ARRAY:
|
|
610
|
+
value = self.DecodeArray()
|
|
611
|
+
elif tag == definitions.SerializationTag.OBJECT:
|
|
612
|
+
value = self.DecodeObject()
|
|
613
|
+
elif tag == definitions.SerializationTag.UNDEFINED:
|
|
614
|
+
value = Undefined()
|
|
615
|
+
elif tag == definitions.SerializationTag.NULL:
|
|
616
|
+
value = Null()
|
|
617
|
+
elif tag == definitions.SerializationTag.INT:
|
|
618
|
+
_, value = self.decoder.DecodeInt32()
|
|
619
|
+
elif tag == definitions.SerializationTag.ZERO:
|
|
620
|
+
value = 0
|
|
621
|
+
elif tag == definitions.SerializationTag.ONE:
|
|
622
|
+
value = 1
|
|
623
|
+
elif tag == definitions.SerializationTag.FALSE:
|
|
624
|
+
value = False
|
|
625
|
+
elif tag == definitions.SerializationTag.TRUE:
|
|
626
|
+
value = True
|
|
627
|
+
elif tag == definitions.SerializationTag.DOUBLE:
|
|
628
|
+
_, value = self.decoder.DecodeDouble()
|
|
629
|
+
elif tag == definitions.SerializationTag.DATE:
|
|
630
|
+
value = self.DecodeDate()
|
|
631
|
+
elif tag == definitions.SerializationTag.FILE:
|
|
632
|
+
value = self.DecodeFileData()
|
|
633
|
+
elif tag == definitions.SerializationTag.FILE_LIST:
|
|
634
|
+
value = self.DecodeFileList()
|
|
635
|
+
elif tag == definitions.SerializationTag.IMAGE_DATA:
|
|
636
|
+
value = self.DecodeImageData()
|
|
637
|
+
elif tag == definitions.SerializationTag.BLOB:
|
|
638
|
+
value = self.DecodeBlob()
|
|
639
|
+
elif tag == definitions.SerializationTag.STRING:
|
|
640
|
+
value = self.DecodeStringData()
|
|
641
|
+
elif tag == definitions.SerializationTag.EMPTY_STRING:
|
|
642
|
+
value = ''
|
|
643
|
+
elif tag == definitions.SerializationTag.REG_EXP:
|
|
644
|
+
value = self.DecodeRegExp()
|
|
645
|
+
elif tag == definitions.SerializationTag.OBJECT_REFERENCE:
|
|
646
|
+
value = self.DecodeObjectReference()
|
|
647
|
+
elif tag == definitions.SerializationTag.ARRAY_BUFFER:
|
|
648
|
+
value = self.DecodeArrayBuffer()
|
|
649
|
+
elif tag == definitions.SerializationTag.ARRAY_BUFFER_VIEW:
|
|
650
|
+
value = self.DecodeArrayBufferView()
|
|
651
|
+
self.object_pool.append(value)
|
|
652
|
+
elif tag == definitions.SerializationTag.ARRAY_BUFFER_TRANSFER:
|
|
653
|
+
value = self.DecodeArrayBufferTransfer()
|
|
654
|
+
elif tag == definitions.SerializationTag.TRUE_OBJECT:
|
|
655
|
+
self.object_pool.append(True)
|
|
656
|
+
value = True
|
|
657
|
+
elif tag == definitions.SerializationTag.FALSE_OBJECT:
|
|
658
|
+
self.object_pool.append(False)
|
|
659
|
+
value = False
|
|
660
|
+
elif tag == definitions.SerializationTag.STRING_OBJECT:
|
|
661
|
+
value = self.DecodeStringData()
|
|
662
|
+
self.object_pool.append(value)
|
|
663
|
+
elif tag == definitions.SerializationTag.EMPTY_STRING_OBJECT:
|
|
664
|
+
value = ''
|
|
665
|
+
self.object_pool.append(value)
|
|
666
|
+
elif tag == definitions.SerializationTag.NUMBER_OBJECT:
|
|
667
|
+
_, value = self.decoder.DecodeDouble()
|
|
668
|
+
self.object_pool.append(value)
|
|
669
|
+
elif tag == definitions.SerializationTag.SET_OBJECT:
|
|
670
|
+
value = self.DecodeSetData()
|
|
671
|
+
elif tag == definitions.SerializationTag.MAP_OBJECT:
|
|
672
|
+
value = self.DecodeMapData()
|
|
673
|
+
elif tag == definitions.SerializationTag.CRYPTO_KEY:
|
|
674
|
+
value = self.DecodeCryptoKey()
|
|
675
|
+
elif tag == definitions.SerializationTag.SHARED_ARRAY_BUFFER:
|
|
676
|
+
value = self.DecodeSharedArrayBuffer()
|
|
677
|
+
elif tag == definitions.SerializationTag.BIGINT:
|
|
678
|
+
value = self.DecodeBigIntData()
|
|
679
|
+
elif tag == definitions.SerializationTag.BIGINT_OBJECT:
|
|
680
|
+
value = self.DecodeBigIntData()
|
|
681
|
+
self.object_pool.append(value)
|
|
682
|
+
else:
|
|
683
|
+
raise errors.ParserError(f'Unhandled Serialization Tag {tag.name} found.')
|
|
684
|
+
return offset, value
|
|
685
|
+
|
|
686
|
+
@classmethod
|
|
687
|
+
def FromBytes(cls, data: bytes) -> Any:
|
|
688
|
+
"""Returns a deserialized JavaScript object from the data.
|
|
689
|
+
|
|
690
|
+
Args:
|
|
691
|
+
data: the data to deserialize/parse.
|
|
692
|
+
|
|
693
|
+
Returns:
|
|
694
|
+
A python representation of the parsed JavaScript object.
|
|
695
|
+
|
|
696
|
+
Raises:
|
|
697
|
+
errors.ParserError: if there is an invalid V8 JavaScript header.
|
|
698
|
+
"""
|
|
699
|
+
stream = io.BytesIO(data)
|
|
700
|
+
deserializer = cls(stream)
|
|
701
|
+
return deserializer.DecodeSerializedValue()
|