dfindexeddb 20240519__py3-none-any.whl → 20241105__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/definitions.py +5 -0
- dfindexeddb/indexeddb/chromium/record.py +181 -95
- dfindexeddb/indexeddb/chromium/v8.py +30 -61
- dfindexeddb/indexeddb/cli.py +62 -22
- dfindexeddb/indexeddb/firefox/definitions.py +143 -0
- dfindexeddb/indexeddb/firefox/gecko.py +600 -0
- dfindexeddb/indexeddb/firefox/record.py +180 -0
- dfindexeddb/indexeddb/safari/definitions.py +7 -7
- dfindexeddb/indexeddb/safari/webkit.py +31 -98
- dfindexeddb/indexeddb/types.py +71 -0
- dfindexeddb/leveldb/cli.py +18 -11
- dfindexeddb/leveldb/descriptor.py +24 -7
- dfindexeddb/leveldb/ldb.py +5 -2
- dfindexeddb/leveldb/log.py +11 -5
- dfindexeddb/leveldb/plugins/manager.py +2 -2
- dfindexeddb/utils.py +2 -2
- dfindexeddb/version.py +1 -1
- {dfindexeddb-20240519.dist-info → dfindexeddb-20241105.dist-info}/METADATA +12 -6
- dfindexeddb-20241105.dist-info/RECORD +41 -0
- {dfindexeddb-20240519.dist-info → dfindexeddb-20241105.dist-info}/WHEEL +1 -1
- dfindexeddb-20240519.dist-info/RECORD +0 -37
- {dfindexeddb-20240519.dist-info → dfindexeddb-20241105.dist-info}/AUTHORS +0 -0
- {dfindexeddb-20240519.dist-info → dfindexeddb-20241105.dist-info}/LICENSE +0 -0
- {dfindexeddb-20240519.dist-info → dfindexeddb-20241105.dist-info}/entry_points.txt +0 -0
- {dfindexeddb-20240519.dist-info → dfindexeddb-20241105.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
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
|
+
"""Firefox IndexedDB records."""
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
import sqlite3
|
|
18
|
+
import sys
|
|
19
|
+
import traceback
|
|
20
|
+
from typing import Any, Generator, Optional
|
|
21
|
+
|
|
22
|
+
from dfindexeddb import errors
|
|
23
|
+
from dfindexeddb.indexeddb.firefox import gecko
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class FirefoxObjectStoreInfo:
|
|
28
|
+
"""A FireFox ObjectStoreInfo.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
id: the object store ID.
|
|
32
|
+
name: the object store name.
|
|
33
|
+
key_path: the object store key path.
|
|
34
|
+
auto_inc: the current auto-increment value.
|
|
35
|
+
database_name: the database name from the database table.
|
|
36
|
+
"""
|
|
37
|
+
id: int
|
|
38
|
+
name: str
|
|
39
|
+
key_path: str
|
|
40
|
+
auto_inc: int
|
|
41
|
+
database_name: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class FirefoxIndexedDBRecord:
|
|
46
|
+
"""A Firefox IndexedDBRecord.
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
key: the parsed key.
|
|
50
|
+
value: the parsed value.
|
|
51
|
+
file_ids: the file identifiers.
|
|
52
|
+
object_store_id: the object store id.
|
|
53
|
+
object_store_name: the object store name from the object_store table.
|
|
54
|
+
database_name: the IndexedDB database name from the database table.
|
|
55
|
+
"""
|
|
56
|
+
key: Any
|
|
57
|
+
value: Any
|
|
58
|
+
file_ids: Optional[str]
|
|
59
|
+
object_store_id: int
|
|
60
|
+
object_store_name: str
|
|
61
|
+
database_name: str
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class FileReader:
|
|
65
|
+
"""A reader for Firefox IndexedDB sqlite3 files.
|
|
66
|
+
|
|
67
|
+
Attributes:
|
|
68
|
+
database_name: the database name.
|
|
69
|
+
origin: the database origin.
|
|
70
|
+
metadata_version: the metadata version.
|
|
71
|
+
last_vacuum_time: the last vacuum time.
|
|
72
|
+
last_analyze_time: the last analyze time.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, filename: str):
|
|
76
|
+
"""Initializes the FileReader.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
filename: the IndexedDB filename.
|
|
80
|
+
"""
|
|
81
|
+
self.filename = filename
|
|
82
|
+
|
|
83
|
+
with sqlite3.connect(f'file:{self.filename}?mode=ro', uri=True) as conn:
|
|
84
|
+
cursor = conn.execute(
|
|
85
|
+
'SELECT name, origin, version, last_vacuum_time, last_analyze_time '
|
|
86
|
+
'FROM database')
|
|
87
|
+
result = cursor.fetchone()
|
|
88
|
+
self.database_name = result[0]
|
|
89
|
+
self.origin = result[1]
|
|
90
|
+
self.metadata_version = result[2]
|
|
91
|
+
self.last_vacuum_time = result[3]
|
|
92
|
+
self.last_analyze_time = result[4]
|
|
93
|
+
|
|
94
|
+
def _ParseKey(self, key: bytes) -> Any:
|
|
95
|
+
"""Parses a key."""
|
|
96
|
+
try:
|
|
97
|
+
return gecko.IDBKey.FromBytes(key)
|
|
98
|
+
except errors.ParserError as e:
|
|
99
|
+
print('failed to parse', key, file=sys.stderr)
|
|
100
|
+
traceback.print_exception(type(e), e, e.__traceback__)
|
|
101
|
+
return key
|
|
102
|
+
|
|
103
|
+
def _ParseValue(self, value: bytes) -> Any:
|
|
104
|
+
"""Parses a value."""
|
|
105
|
+
try:
|
|
106
|
+
return gecko.JSStructuredCloneDecoder.FromBytes(value)
|
|
107
|
+
except errors.ParserError as err:
|
|
108
|
+
print('failed to parse', value, file=sys.stderr)
|
|
109
|
+
traceback.print_exception(type(err), err, err.__traceback__)
|
|
110
|
+
return value
|
|
111
|
+
|
|
112
|
+
def ObjectStores(self) -> Generator[FirefoxObjectStoreInfo, None, None]:
|
|
113
|
+
"""Returns the Object Store information from the IndexedDB database.
|
|
114
|
+
|
|
115
|
+
Yields:
|
|
116
|
+
FirefoxObjectStoreInfo instances.
|
|
117
|
+
"""
|
|
118
|
+
with sqlite3.connect(f'file:{self.filename}?mode=ro', uri=True) as conn:
|
|
119
|
+
cursor = conn.execute(
|
|
120
|
+
'SELECT id, auto_increment, name, key_path FROM object_store')
|
|
121
|
+
results = cursor.fetchall()
|
|
122
|
+
for result in results:
|
|
123
|
+
yield FirefoxObjectStoreInfo(
|
|
124
|
+
id=result[0],
|
|
125
|
+
name=result[2],
|
|
126
|
+
key_path=result[3],
|
|
127
|
+
auto_inc=result[1],
|
|
128
|
+
database_name=self.database_name)
|
|
129
|
+
|
|
130
|
+
def RecordsByObjectStoreId(
|
|
131
|
+
self,
|
|
132
|
+
object_store_id: int
|
|
133
|
+
) -> Generator[FirefoxIndexedDBRecord, None, None]:
|
|
134
|
+
"""Returns FirefoxIndexedDBRecords by a given object store id.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
object_store_id: the object store id.
|
|
138
|
+
"""
|
|
139
|
+
with sqlite3.connect(f'file:{self.filename}?mode=ro', uri=True) as conn:
|
|
140
|
+
conn.text_factory = bytes
|
|
141
|
+
cursor = conn.execute(
|
|
142
|
+
'SELECT od.key, od.data, od.object_store_id, od.file_ids, os.name '
|
|
143
|
+
'FROM object_data od '
|
|
144
|
+
'JOIN object_store os ON od.object_store_id == os.id '
|
|
145
|
+
'WHERE os.id = ? ORDER BY od.key', (object_store_id, ))
|
|
146
|
+
for row in cursor:
|
|
147
|
+
key = self._ParseKey(row[0])
|
|
148
|
+
if row[3]:
|
|
149
|
+
value = row[1]
|
|
150
|
+
else:
|
|
151
|
+
value = self._ParseValue(row[1])
|
|
152
|
+
yield FirefoxIndexedDBRecord(
|
|
153
|
+
key=key,
|
|
154
|
+
value=value,
|
|
155
|
+
object_store_id=row[2],
|
|
156
|
+
file_ids=row[3],
|
|
157
|
+
object_store_name=row[4].decode('utf-8'),
|
|
158
|
+
database_name=self.database_name)
|
|
159
|
+
|
|
160
|
+
def Records(self) -> Generator[FirefoxIndexedDBRecord, None, None]:
|
|
161
|
+
"""Returns FirefoxIndexedDBRecords from the database."""
|
|
162
|
+
with sqlite3.connect(f'file:{self.filename}?mode=ro', uri=True) as conn:
|
|
163
|
+
conn.text_factory = bytes
|
|
164
|
+
cursor = conn.execute(
|
|
165
|
+
'SELECT od.key, od.data, od.object_store_id, od.file_ids, os.name '
|
|
166
|
+
'FROM object_data od '
|
|
167
|
+
'JOIN object_store os ON od.object_store_id == os.id')
|
|
168
|
+
for row in cursor:
|
|
169
|
+
key = self._ParseKey(row[0])
|
|
170
|
+
if row[3]:
|
|
171
|
+
value = row[1]
|
|
172
|
+
else:
|
|
173
|
+
value = self._ParseValue(row[1])
|
|
174
|
+
yield FirefoxIndexedDBRecord(
|
|
175
|
+
key=key,
|
|
176
|
+
value=value,
|
|
177
|
+
object_store_id=row[2],
|
|
178
|
+
file_ids=row[3],
|
|
179
|
+
object_store_name=row[4].decode('utf-8'),
|
|
180
|
+
database_name=self.database_name)
|
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
from enum import IntEnum
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
CURRENT_VERSION = 0x0000000F # 15
|
|
20
|
+
TERMINATOR_TAG = 0xFFFFFFFF
|
|
21
|
+
STRING_POOL_TAG = 0xFFFFFFFE
|
|
22
|
+
NON_INDEX_PROPERTIES_TAG = 0xFFFFFFFD
|
|
23
|
+
IMAGE_DATA_POOL_TAG = 0xFFFFFFFE
|
|
24
|
+
STRING_DATA_IS_8BIT_FLAG = 0x80000000
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
SIDB_KEY_VERSION = 0x00
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class SIDBKeyType(IntEnum):
|
|
@@ -23,6 +23,7 @@ from typing import Any, Dict, List, Tuple, Union
|
|
|
23
23
|
|
|
24
24
|
from dfindexeddb import errors
|
|
25
25
|
from dfindexeddb import utils
|
|
26
|
+
from dfindexeddb.indexeddb import types
|
|
26
27
|
from dfindexeddb.indexeddb.safari import definitions
|
|
27
28
|
|
|
28
29
|
|
|
@@ -82,78 +83,6 @@ class FileList:
|
|
|
82
83
|
files: List[FileData]
|
|
83
84
|
|
|
84
85
|
|
|
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
86
|
@dataclass
|
|
158
87
|
class IDBKeyData(utils.FromDecoderMixin):
|
|
159
88
|
"""An IDBKeyData.
|
|
@@ -169,7 +98,10 @@ class IDBKeyData(utils.FromDecoderMixin):
|
|
|
169
98
|
|
|
170
99
|
@classmethod
|
|
171
100
|
def FromDecoder(
|
|
172
|
-
cls,
|
|
101
|
+
cls,
|
|
102
|
+
decoder: utils.StreamDecoder,
|
|
103
|
+
base_offset: int = 0
|
|
104
|
+
) -> IDBKeyData:
|
|
173
105
|
"""Decodes an IDBKeyData from the current position of decoder.
|
|
174
106
|
|
|
175
107
|
Refer to IDBSerialization.cpp for the encoding scheme.
|
|
@@ -211,7 +143,7 @@ class IDBKeyData(utils.FromDecoderMixin):
|
|
|
211
143
|
return data
|
|
212
144
|
|
|
213
145
|
offset, version_header = decoder.DecodeUint8()
|
|
214
|
-
if version_header != definitions.
|
|
146
|
+
if version_header != definitions.SIDB_KEY_VERSION:
|
|
215
147
|
raise errors.ParserError('SIDBKeyVersion not found.')
|
|
216
148
|
|
|
217
149
|
_, raw_key_type = decoder.DecodeUint8()
|
|
@@ -279,7 +211,7 @@ class SerializedScriptValueDecoder():
|
|
|
279
211
|
raise errors.ParserError(
|
|
280
212
|
f'Invalid terminal {terminal_byte} at offset {offset}') from error
|
|
281
213
|
|
|
282
|
-
def DecodeArray(self) -> JSArray:
|
|
214
|
+
def DecodeArray(self) -> types.JSArray:
|
|
283
215
|
"""Decodes an Array value.
|
|
284
216
|
|
|
285
217
|
Returns:
|
|
@@ -289,25 +221,25 @@ class SerializedScriptValueDecoder():
|
|
|
289
221
|
ParserError if an invalid Terminator tag was found.
|
|
290
222
|
"""
|
|
291
223
|
_, length = self.decoder.DecodeUint32()
|
|
292
|
-
array = JSArray()
|
|
224
|
+
array = types.JSArray()
|
|
293
225
|
self.object_pool.append(array)
|
|
294
226
|
for _ in range(length):
|
|
295
227
|
_, _ = self.decoder.DecodeUint32()
|
|
296
228
|
_, value = self.DecodeValue()
|
|
297
|
-
array.append(value)
|
|
229
|
+
array.values.append(value)
|
|
298
230
|
|
|
299
231
|
offset, terminator_tag = self.decoder.DecodeUint32()
|
|
300
|
-
if terminator_tag != definitions.
|
|
232
|
+
if terminator_tag != definitions.TERMINATOR_TAG:
|
|
301
233
|
raise errors.ParserError(f'Terminator tag not found at offset {offset}.')
|
|
302
234
|
|
|
303
235
|
offset, tag = self.decoder.DecodeUint32()
|
|
304
|
-
if tag == definitions.
|
|
305
|
-
while tag != definitions.
|
|
236
|
+
if tag == definitions.NON_INDEX_PROPERTIES_TAG:
|
|
237
|
+
while tag != definitions.TERMINATOR_TAG:
|
|
306
238
|
name = self.DecodeStringData()
|
|
307
239
|
_, value = self.DecodeValue()
|
|
308
240
|
_, tag = self.decoder.DecodeUint32()
|
|
309
241
|
array.properties[name] = value
|
|
310
|
-
elif tag != definitions.
|
|
242
|
+
elif tag != definitions.TERMINATOR_TAG:
|
|
311
243
|
raise errors.ParserError(f'Terminator tag not found at offset {offset}.')
|
|
312
244
|
return array
|
|
313
245
|
|
|
@@ -316,7 +248,7 @@ class SerializedScriptValueDecoder():
|
|
|
316
248
|
tag = self.PeekTag()
|
|
317
249
|
js_object = {}
|
|
318
250
|
self.object_pool.append(js_object)
|
|
319
|
-
while tag != definitions.
|
|
251
|
+
while tag != definitions.TERMINATOR_TAG:
|
|
320
252
|
name = self.DecodeStringData()
|
|
321
253
|
_, value = self.DecodeValue()
|
|
322
254
|
js_object[name] = value
|
|
@@ -338,10 +270,10 @@ class SerializedScriptValueDecoder():
|
|
|
338
270
|
* unable to to decode a buffer as utf-16-le.
|
|
339
271
|
"""
|
|
340
272
|
peeked_tag = self.PeekTag()
|
|
341
|
-
if peeked_tag == definitions.
|
|
273
|
+
if peeked_tag == definitions.TERMINATOR_TAG:
|
|
342
274
|
raise errors.ParserError('Unexpected TerminatorTag found')
|
|
343
275
|
|
|
344
|
-
if peeked_tag == definitions.
|
|
276
|
+
if peeked_tag == definitions.STRING_POOL_TAG:
|
|
345
277
|
_ = self.decoder.DecodeUint32()
|
|
346
278
|
if len(self.constant_pool) <= 0xff:
|
|
347
279
|
_, cp_index = self.decoder.DecodeUint8()
|
|
@@ -354,11 +286,11 @@ class SerializedScriptValueDecoder():
|
|
|
354
286
|
return self.constant_pool[cp_index]
|
|
355
287
|
|
|
356
288
|
_, length_with_8bit_flag = self.decoder.DecodeUint32()
|
|
357
|
-
if length_with_8bit_flag == definitions.
|
|
289
|
+
if length_with_8bit_flag == definitions.TERMINATOR_TAG:
|
|
358
290
|
raise errors.ParserError('Disallowed string length found.')
|
|
359
291
|
|
|
360
292
|
length = length_with_8bit_flag & 0x7FFFFFFF
|
|
361
|
-
is_8bit = length_with_8bit_flag & definitions.
|
|
293
|
+
is_8bit = length_with_8bit_flag & definitions.STRING_DATA_IS_8BIT_FLAG
|
|
362
294
|
|
|
363
295
|
if is_8bit:
|
|
364
296
|
_, characters = self.decoder.ReadBytes(length)
|
|
@@ -367,9 +299,10 @@ class SerializedScriptValueDecoder():
|
|
|
367
299
|
_, characters = self.decoder.ReadBytes(2*length)
|
|
368
300
|
try:
|
|
369
301
|
value = characters.decode('utf-16-le')
|
|
370
|
-
except UnicodeDecodeError:
|
|
302
|
+
except UnicodeDecodeError as exc:
|
|
371
303
|
raise errors.ParserError(
|
|
372
|
-
f'Unable to decode {len(characters)} characters as utf-16-le'
|
|
304
|
+
f'Unable to decode {len(characters)} characters as utf-16-le'
|
|
305
|
+
) from exc
|
|
373
306
|
self.constant_pool.append(value)
|
|
374
307
|
return value
|
|
375
308
|
|
|
@@ -441,11 +374,11 @@ class SerializedScriptValueDecoder():
|
|
|
441
374
|
'memory_cost': memory_cost
|
|
442
375
|
}
|
|
443
376
|
|
|
444
|
-
def DecodeRegExp(self) -> RegExp:
|
|
377
|
+
def DecodeRegExp(self) -> types.RegExp:
|
|
445
378
|
"""Decodes a RegExp value."""
|
|
446
379
|
pattern = self.DecodeStringData()
|
|
447
380
|
flags = self.DecodeStringData()
|
|
448
|
-
return RegExp(pattern=pattern, flags=flags)
|
|
381
|
+
return types.RegExp(pattern=pattern, flags=flags)
|
|
449
382
|
|
|
450
383
|
def DecodeMapData(self) -> dict:
|
|
451
384
|
"""Decodes a Map value."""
|
|
@@ -463,7 +396,7 @@ class SerializedScriptValueDecoder():
|
|
|
463
396
|
_, tag = self.DecodeSerializationTag()
|
|
464
397
|
|
|
465
398
|
pool_tag = self.PeekTag()
|
|
466
|
-
while pool_tag != definitions.
|
|
399
|
+
while pool_tag != definitions.TERMINATOR_TAG:
|
|
467
400
|
name = self.DecodeStringData()
|
|
468
401
|
value = self.DecodeValue()
|
|
469
402
|
js_map[name] = value
|
|
@@ -472,22 +405,22 @@ class SerializedScriptValueDecoder():
|
|
|
472
405
|
_, tag = self.decoder.DecodeUint32()
|
|
473
406
|
return js_map
|
|
474
407
|
|
|
475
|
-
def DecodeSetData(self) -> JSSet:
|
|
408
|
+
def DecodeSetData(self) -> types.JSSet:
|
|
476
409
|
"""Decodes a SetData value."""
|
|
477
410
|
tag = self.PeekSerializationTag()
|
|
478
|
-
js_set = JSSet()
|
|
411
|
+
js_set = types.JSSet()
|
|
479
412
|
self.object_pool.append(js_set)
|
|
480
413
|
|
|
481
414
|
while tag != definitions.SerializationTag.NON_SET_PROPERTIES:
|
|
482
415
|
_, key = self.DecodeValue()
|
|
483
|
-
js_set.add(key)
|
|
416
|
+
js_set.values.add(key)
|
|
484
417
|
tag = self.PeekSerializationTag()
|
|
485
418
|
|
|
486
419
|
# consume the NonSetPropertiesTag
|
|
487
420
|
_, tag = self.DecodeSerializationTag()
|
|
488
421
|
|
|
489
422
|
pool_tag = self.PeekTag()
|
|
490
|
-
while pool_tag != definitions.
|
|
423
|
+
while pool_tag != definitions.TERMINATOR_TAG:
|
|
491
424
|
name = self.DecodeStringData()
|
|
492
425
|
value = self.DecodeValue()
|
|
493
426
|
js_set.properties[name] = value
|
|
@@ -590,7 +523,7 @@ class SerializedScriptValueDecoder():
|
|
|
590
523
|
ParserError when CurrentVersion is not found.
|
|
591
524
|
"""
|
|
592
525
|
_, current_version = self.decoder.DecodeUint32()
|
|
593
|
-
if current_version != definitions.
|
|
526
|
+
if current_version != definitions.CURRENT_VERSION:
|
|
594
527
|
raise errors.ParserError(
|
|
595
528
|
f'{current_version} is not the expected CurrentVersion')
|
|
596
529
|
_, value = self.DecodeValue()
|
|
@@ -611,9 +544,9 @@ class SerializedScriptValueDecoder():
|
|
|
611
544
|
elif tag == definitions.SerializationTag.OBJECT:
|
|
612
545
|
value = self.DecodeObject()
|
|
613
546
|
elif tag == definitions.SerializationTag.UNDEFINED:
|
|
614
|
-
value = Undefined()
|
|
547
|
+
value = types.Undefined()
|
|
615
548
|
elif tag == definitions.SerializationTag.NULL:
|
|
616
|
-
value = Null()
|
|
549
|
+
value = types.Null()
|
|
617
550
|
elif tag == definitions.SerializationTag.INT:
|
|
618
551
|
_, value = self.decoder.DecodeInt32()
|
|
619
552
|
elif tag == definitions.SerializationTag.ZERO:
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
"""Types for indexeddb."""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import dataclasses
|
|
19
|
+
from typing import Any, Dict, List, Set
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclasses.dataclass
|
|
23
|
+
class JSArray:
|
|
24
|
+
"""A parsed Javascript array.
|
|
25
|
+
|
|
26
|
+
A JavaScript array behaves like a Python list but allows assigning arbitrary
|
|
27
|
+
properties. The array is stored in the attribute __array__.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
values: the array values.
|
|
31
|
+
properties: the array properties.
|
|
32
|
+
"""
|
|
33
|
+
values: List[Any] = dataclasses.field(default_factory=list)
|
|
34
|
+
properties: Dict[Any, Any] = dataclasses.field(default_factory=dict)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclasses.dataclass
|
|
38
|
+
class JSSet:
|
|
39
|
+
"""A parsed JavaScript set.
|
|
40
|
+
|
|
41
|
+
A JavaScript set behaves like a Python set but allows assigning arbitrary
|
|
42
|
+
properties. The array is stored in the attribute __set__.
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
values: the set values.
|
|
46
|
+
properties: the set properties.
|
|
47
|
+
"""
|
|
48
|
+
values: Set[Any] = dataclasses.field(default_factory=set)
|
|
49
|
+
properties: Dict[Any, Any] = dataclasses.field(default_factory=dict)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclasses.dataclass
|
|
53
|
+
class Null:
|
|
54
|
+
"""A parsed JavaScript Null."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclasses.dataclass
|
|
58
|
+
class RegExp:
|
|
59
|
+
"""A parsed JavaScript RegExp.
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
pattern: the pattern.
|
|
63
|
+
flags: the flags.
|
|
64
|
+
"""
|
|
65
|
+
pattern: str
|
|
66
|
+
flags: str
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclasses.dataclass
|
|
70
|
+
class Undefined:
|
|
71
|
+
"""A JavaScript undef."""
|
dfindexeddb/leveldb/cli.py
CHANGED
|
@@ -203,10 +203,11 @@ def App():
|
|
|
203
203
|
parser_db = subparsers.add_parser(
|
|
204
204
|
'db', help='Parse a directory as leveldb.')
|
|
205
205
|
parser_db.add_argument(
|
|
206
|
-
'-s',
|
|
206
|
+
'-s',
|
|
207
|
+
'--source',
|
|
207
208
|
required=True,
|
|
208
209
|
type=pathlib.Path,
|
|
209
|
-
help='The source leveldb directory')
|
|
210
|
+
help='The source leveldb directory.')
|
|
210
211
|
recover_group = parser_db.add_mutually_exclusive_group()
|
|
211
212
|
recover_group.add_argument(
|
|
212
213
|
'--use_manifest',
|
|
@@ -226,7 +227,7 @@ def App():
|
|
|
226
227
|
'jsonl',
|
|
227
228
|
'repr'],
|
|
228
229
|
default='json',
|
|
229
|
-
help='Output format. Default is json')
|
|
230
|
+
help='Output format. Default is json.')
|
|
230
231
|
parser_db.add_argument(
|
|
231
232
|
'--plugin',
|
|
232
233
|
help='Use plugin to parse records.')
|
|
@@ -235,10 +236,11 @@ def App():
|
|
|
235
236
|
parser_log = subparsers.add_parser(
|
|
236
237
|
'log', help='Parse a leveldb log file.')
|
|
237
238
|
parser_log.add_argument(
|
|
238
|
-
'-s',
|
|
239
|
+
'-s',
|
|
240
|
+
'--source',
|
|
239
241
|
required=True,
|
|
240
242
|
type=pathlib.Path,
|
|
241
|
-
help='The source leveldb file')
|
|
243
|
+
help='The source leveldb file.')
|
|
242
244
|
parser_log.add_argument(
|
|
243
245
|
'-o',
|
|
244
246
|
'--output',
|
|
@@ -247,7 +249,7 @@ def App():
|
|
|
247
249
|
'jsonl',
|
|
248
250
|
'repr'],
|
|
249
251
|
default='json',
|
|
250
|
-
help='Output format. Default is json')
|
|
252
|
+
help='Output format. Default is json.')
|
|
251
253
|
parser_log.add_argument(
|
|
252
254
|
'--plugin',
|
|
253
255
|
help='Use plugin to parse records.')
|
|
@@ -265,7 +267,8 @@ def App():
|
|
|
265
267
|
parser_ldb = subparsers.add_parser(
|
|
266
268
|
'ldb', help='Parse a leveldb table (.ldb) file.')
|
|
267
269
|
parser_ldb.add_argument(
|
|
268
|
-
'-s',
|
|
270
|
+
'-s',
|
|
271
|
+
'--source',
|
|
269
272
|
required=True,
|
|
270
273
|
type=pathlib.Path,
|
|
271
274
|
help='The source leveldb file')
|
|
@@ -277,7 +280,7 @@ def App():
|
|
|
277
280
|
'jsonl',
|
|
278
281
|
'repr'],
|
|
279
282
|
default='json',
|
|
280
|
-
help='Output format. Default is json')
|
|
283
|
+
help='Output format. Default is json.')
|
|
281
284
|
parser_ldb.add_argument(
|
|
282
285
|
'--plugin',
|
|
283
286
|
help='Use plugin to parse records.')
|
|
@@ -293,7 +296,8 @@ def App():
|
|
|
293
296
|
parser_descriptor = subparsers.add_parser(
|
|
294
297
|
'descriptor', help='Parse a leveldb descriptor (MANIFEST) file.')
|
|
295
298
|
parser_descriptor.add_argument(
|
|
296
|
-
'-s',
|
|
299
|
+
'-s',
|
|
300
|
+
'--source',
|
|
297
301
|
required=True,
|
|
298
302
|
type=pathlib.Path,
|
|
299
303
|
help='The source leveldb file')
|
|
@@ -305,13 +309,16 @@ def App():
|
|
|
305
309
|
'jsonl',
|
|
306
310
|
'repr'],
|
|
307
311
|
default='json',
|
|
308
|
-
help='Output format. Default is json')
|
|
312
|
+
help='Output format. Default is json.')
|
|
309
313
|
db_group = parser_descriptor.add_mutually_exclusive_group()
|
|
310
314
|
db_group.add_argument(
|
|
311
315
|
'-t',
|
|
312
316
|
'--structure_type',
|
|
313
317
|
choices=[
|
|
314
|
-
'blocks',
|
|
318
|
+
'blocks',
|
|
319
|
+
'physical_records',
|
|
320
|
+
'versionedit'
|
|
321
|
+
],
|
|
315
322
|
help='Parses the specified structure. Default is versionedit.')
|
|
316
323
|
db_group.add_argument(
|
|
317
324
|
'-v',
|