dfindexeddb 20240501__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/blink.py +5 -0
- dfindexeddb/indexeddb/chromium/record.py +11 -6
- dfindexeddb/indexeddb/chromium/v8.py +30 -10
- dfindexeddb/indexeddb/cli.py +52 -9
- 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 +52 -20
- dfindexeddb/indexeddb/types.py +71 -0
- dfindexeddb/leveldb/cli.py +69 -6
- dfindexeddb/leveldb/descriptor.py +2 -1
- 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 +33 -1
- dfindexeddb/utils.py +35 -1
- dfindexeddb/version.py +1 -1
- {dfindexeddb-20240501.dist-info → dfindexeddb-20241031.dist-info}/METADATA +42 -15
- dfindexeddb-20241031.dist-info/RECORD +41 -0
- {dfindexeddb-20240501.dist-info → dfindexeddb-20241031.dist-info}/WHEEL +1 -1
- dfindexeddb-20240501.dist-info/RECORD +0 -32
- {dfindexeddb-20240501.dist-info → dfindexeddb-20241031.dist-info}/AUTHORS +0 -0
- {dfindexeddb-20240501.dist-info → dfindexeddb-20241031.dist-info}/LICENSE +0 -0
- {dfindexeddb-20240501.dist-info → dfindexeddb-20241031.dist-info}/entry_points.txt +0 -0
- {dfindexeddb-20240501.dist-info → dfindexeddb-20241031.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)
|
|
@@ -82,15 +82,22 @@ class FileList:
|
|
|
82
82
|
files: List[FileData]
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
class JSArray
|
|
86
|
-
"""A parsed
|
|
85
|
+
class JSArray:
|
|
86
|
+
"""A parsed Javascript array.
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
properties
|
|
88
|
+
A Javascript array behaves like a Python list but allows assigning arbitrary
|
|
89
|
+
properties. The array is stored in the attribute __array__.
|
|
90
90
|
"""
|
|
91
|
+
def __init__(self):
|
|
92
|
+
self.__array__ = []
|
|
93
|
+
|
|
94
|
+
def Append(self, element: Any):
|
|
95
|
+
"""Appends a new element to the array."""
|
|
96
|
+
self.__array__.append(element)
|
|
91
97
|
|
|
92
98
|
def __repr__(self):
|
|
93
|
-
array_entries = ", ".join(
|
|
99
|
+
array_entries = ", ".join(
|
|
100
|
+
[str(entry) for entry in list(self.__array__)])
|
|
94
101
|
properties = ", ".join(
|
|
95
102
|
f'{key}: {value}' for key, value in self.properties.items())
|
|
96
103
|
return f'[{array_entries}, {properties}]'
|
|
@@ -100,6 +107,11 @@ class JSArray(list):
|
|
|
100
107
|
"""Returns the object properties."""
|
|
101
108
|
return self.__dict__
|
|
102
109
|
|
|
110
|
+
def __eq__(self, other: JSArray):
|
|
111
|
+
return (
|
|
112
|
+
self.__array__ == other.__array__
|
|
113
|
+
and self.properties == other.properties)
|
|
114
|
+
|
|
103
115
|
def __contains__(self, item):
|
|
104
116
|
return item in self.__dict__
|
|
105
117
|
|
|
@@ -107,24 +119,36 @@ class JSArray(list):
|
|
|
107
119
|
return self.__dict__[name]
|
|
108
120
|
|
|
109
121
|
|
|
110
|
-
class JSSet
|
|
111
|
-
"""A parsed
|
|
122
|
+
class JSSet:
|
|
123
|
+
"""A parsed Javascript set.
|
|
112
124
|
|
|
113
|
-
|
|
114
|
-
properties
|
|
125
|
+
A Javascript set behaves like a Python set but allows assigning arbitrary
|
|
126
|
+
properties. The array is stored in the attribute __set__.
|
|
115
127
|
"""
|
|
128
|
+
def __init__(self):
|
|
129
|
+
self.__set__ = set()
|
|
130
|
+
|
|
131
|
+
def Add(self, element: Any):
|
|
132
|
+
"""Adds a new element to the set."""
|
|
133
|
+
self.__set__.add(element)
|
|
116
134
|
|
|
117
135
|
def __repr__(self):
|
|
118
|
-
|
|
136
|
+
array_entries = ", ".join(
|
|
137
|
+
[str(entry) for entry in list(self.__set__)])
|
|
119
138
|
properties = ", ".join(
|
|
120
139
|
f'{key}: {value}' for key, value in self.properties.items())
|
|
121
|
-
return f'[{
|
|
140
|
+
return f'[{array_entries}, {properties}]'
|
|
122
141
|
|
|
123
142
|
@property
|
|
124
143
|
def properties(self) -> Dict[str, Any]:
|
|
125
144
|
"""Returns the object properties."""
|
|
126
145
|
return self.__dict__
|
|
127
146
|
|
|
147
|
+
def __eq__(self, other: JSSet):
|
|
148
|
+
return (
|
|
149
|
+
self.__set__ == other.__set__
|
|
150
|
+
and self.properties == other.properties)
|
|
151
|
+
|
|
128
152
|
def __contains__(self, item):
|
|
129
153
|
return item in self.__dict__
|
|
130
154
|
|
|
@@ -290,10 +314,11 @@ class SerializedScriptValueDecoder():
|
|
|
290
314
|
"""
|
|
291
315
|
_, length = self.decoder.DecodeUint32()
|
|
292
316
|
array = JSArray()
|
|
317
|
+
self.object_pool.append(array)
|
|
293
318
|
for _ in range(length):
|
|
294
319
|
_, _ = self.decoder.DecodeUint32()
|
|
295
320
|
_, value = self.DecodeValue()
|
|
296
|
-
array.
|
|
321
|
+
array.Append(value)
|
|
297
322
|
|
|
298
323
|
offset, terminator_tag = self.decoder.DecodeUint32()
|
|
299
324
|
if terminator_tag != definitions.TerminatorTag:
|
|
@@ -314,13 +339,13 @@ class SerializedScriptValueDecoder():
|
|
|
314
339
|
"""Decodes an Object value."""
|
|
315
340
|
tag = self.PeekTag()
|
|
316
341
|
js_object = {}
|
|
342
|
+
self.object_pool.append(js_object)
|
|
317
343
|
while tag != definitions.TerminatorTag:
|
|
318
344
|
name = self.DecodeStringData()
|
|
319
345
|
_, value = self.DecodeValue()
|
|
320
346
|
js_object[name] = value
|
|
321
347
|
tag = self.PeekTag()
|
|
322
348
|
_ = self.decoder.DecodeUint32()
|
|
323
|
-
self.object_pool.append(js_object)
|
|
324
349
|
return js_object
|
|
325
350
|
|
|
326
351
|
def DecodeStringData(self) -> str:
|
|
@@ -342,11 +367,11 @@ class SerializedScriptValueDecoder():
|
|
|
342
367
|
|
|
343
368
|
if peeked_tag == definitions.StringPoolTag:
|
|
344
369
|
_ = self.decoder.DecodeUint32()
|
|
345
|
-
if len(self.constant_pool)
|
|
370
|
+
if len(self.constant_pool) <= 0xff:
|
|
346
371
|
_, cp_index = self.decoder.DecodeUint8()
|
|
347
|
-
elif len(self.constant_pool)
|
|
372
|
+
elif len(self.constant_pool) <= 0xffff:
|
|
348
373
|
_, cp_index = self.decoder.DecodeUint16()
|
|
349
|
-
elif len(self.constant_pool)
|
|
374
|
+
elif len(self.constant_pool) <= 0xffffffff:
|
|
350
375
|
_, cp_index = self.decoder.DecodeUint32()
|
|
351
376
|
else:
|
|
352
377
|
raise errors.ParserError('Unexpected constant pool size value.')
|
|
@@ -450,6 +475,7 @@ class SerializedScriptValueDecoder():
|
|
|
450
475
|
"""Decodes a Map value."""
|
|
451
476
|
tag = self.PeekSerializationTag()
|
|
452
477
|
js_map = {} # TODO: make this into a JSMap (like JSArray/JSSet)
|
|
478
|
+
self.object_pool.append(js_map)
|
|
453
479
|
|
|
454
480
|
while tag != definitions.SerializationTag.NON_MAP_PROPERTIES:
|
|
455
481
|
_, key = self.DecodeValue()
|
|
@@ -468,17 +494,17 @@ class SerializedScriptValueDecoder():
|
|
|
468
494
|
pool_tag = self.PeekTag()
|
|
469
495
|
|
|
470
496
|
_, tag = self.decoder.DecodeUint32()
|
|
471
|
-
|
|
472
497
|
return js_map
|
|
473
498
|
|
|
474
499
|
def DecodeSetData(self) -> JSSet:
|
|
475
500
|
"""Decodes a SetData value."""
|
|
476
501
|
tag = self.PeekSerializationTag()
|
|
477
502
|
js_set = JSSet()
|
|
503
|
+
self.object_pool.append(js_set)
|
|
478
504
|
|
|
479
505
|
while tag != definitions.SerializationTag.NON_SET_PROPERTIES:
|
|
480
506
|
_, key = self.DecodeValue()
|
|
481
|
-
js_set.
|
|
507
|
+
js_set.Add(key)
|
|
482
508
|
tag = self.PeekSerializationTag()
|
|
483
509
|
|
|
484
510
|
# consume the NonSetPropertiesTag
|
|
@@ -540,8 +566,13 @@ class SerializedScriptValueDecoder():
|
|
|
540
566
|
|
|
541
567
|
def DecodeObjectReference(self) -> Any:
|
|
542
568
|
"""Decodes an ObjectReference value."""
|
|
543
|
-
|
|
544
|
-
|
|
569
|
+
if len(self.object_pool) < 0xFF:
|
|
570
|
+
_, object_ref = self.decoder.DecodeUint8()
|
|
571
|
+
elif len(self.object_pool) < 0xFFFF:
|
|
572
|
+
_, object_ref = self.decoder.DecodeUint16()
|
|
573
|
+
else: # if len(self.object_pool) < 0xFFFFFFFF:
|
|
574
|
+
_, object_ref = self.decoder.DecodeUint32()
|
|
575
|
+
return self.object_pool[object_ref]
|
|
545
576
|
|
|
546
577
|
def DecodeArrayBufferView(self) -> ArrayBufferView:
|
|
547
578
|
"""Decodes an ArrayBufferView value.
|
|
@@ -641,6 +672,7 @@ class SerializedScriptValueDecoder():
|
|
|
641
672
|
value = self.DecodeArrayBuffer()
|
|
642
673
|
elif tag == definitions.SerializationTag.ARRAY_BUFFER_VIEW:
|
|
643
674
|
value = self.DecodeArrayBufferView()
|
|
675
|
+
self.object_pool.append(value)
|
|
644
676
|
elif tag == definitions.SerializationTag.ARRAY_BUFFER_TRANSFER:
|
|
645
677
|
value = self.DecodeArrayBufferTransfer()
|
|
646
678
|
elif tag == definitions.SerializationTag.TRUE_OBJECT:
|
|
@@ -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: int
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclasses.dataclass
|
|
70
|
+
class Undefined:
|
|
71
|
+
"""A JavaScript undef."""
|
dfindexeddb/leveldb/cli.py
CHANGED
|
@@ -19,11 +19,13 @@ from datetime import datetime
|
|
|
19
19
|
import json
|
|
20
20
|
import pathlib
|
|
21
21
|
|
|
22
|
+
from dfindexeddb import utils
|
|
22
23
|
from dfindexeddb import version
|
|
23
24
|
from dfindexeddb.leveldb import descriptor
|
|
24
25
|
from dfindexeddb.leveldb import ldb
|
|
25
26
|
from dfindexeddb.leveldb import log
|
|
26
27
|
from dfindexeddb.leveldb import record
|
|
28
|
+
from dfindexeddb.leveldb.plugins import manager
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
_VALID_PRINTABLE_CHARACTERS = (
|
|
@@ -37,7 +39,7 @@ class Encoder(json.JSONEncoder):
|
|
|
37
39
|
def default(self, o):
|
|
38
40
|
"""Returns a serializable object for o."""
|
|
39
41
|
if dataclasses.is_dataclass(o):
|
|
40
|
-
o_dict =
|
|
42
|
+
o_dict = utils.asdict(o)
|
|
41
43
|
return o_dict
|
|
42
44
|
if isinstance(o, bytes):
|
|
43
45
|
out = []
|
|
@@ -66,13 +68,39 @@ def _Output(structure, output):
|
|
|
66
68
|
|
|
67
69
|
def DbCommand(args):
|
|
68
70
|
"""The CLI for processing leveldb folders."""
|
|
71
|
+
if args.plugin and args.plugin == 'list':
|
|
72
|
+
for plugin, _ in manager.LeveldbPluginManager.GetPlugins():
|
|
73
|
+
print(plugin)
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
if args.plugin:
|
|
77
|
+
plugin_class = manager.LeveldbPluginManager.GetPlugin(args.plugin)
|
|
78
|
+
else:
|
|
79
|
+
plugin_class = None
|
|
80
|
+
|
|
69
81
|
for leveldb_record in record.FolderReader(
|
|
70
|
-
args.source).GetRecords(
|
|
71
|
-
|
|
82
|
+
args.source).GetRecords(
|
|
83
|
+
use_manifest=args.use_manifest,
|
|
84
|
+
use_sequence_number=args.use_sequence_number):
|
|
85
|
+
if plugin_class:
|
|
86
|
+
plugin_record = plugin_class.FromLevelDBRecord(leveldb_record)
|
|
87
|
+
_Output(plugin_record, output=args.output)
|
|
88
|
+
else:
|
|
89
|
+
_Output(leveldb_record, output=args.output)
|
|
72
90
|
|
|
73
91
|
|
|
74
92
|
def LdbCommand(args):
|
|
75
93
|
"""The CLI for processing ldb files."""
|
|
94
|
+
if args.plugin and args.plugin == 'list':
|
|
95
|
+
for plugin, _ in manager.LeveldbPluginManager.GetPlugins():
|
|
96
|
+
print(plugin)
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
if args.plugin:
|
|
100
|
+
plugin_class = manager.LeveldbPluginManager.GetPlugin(args.plugin)
|
|
101
|
+
else:
|
|
102
|
+
plugin_class = None
|
|
103
|
+
|
|
76
104
|
ldb_file = ldb.FileReader(args.source)
|
|
77
105
|
|
|
78
106
|
if args.structure_type == 'blocks':
|
|
@@ -83,7 +111,11 @@ def LdbCommand(args):
|
|
|
83
111
|
elif args.structure_type == 'records' or not args.structure_type:
|
|
84
112
|
# Prints key value record information.
|
|
85
113
|
for key_value_record in ldb_file.GetKeyValueRecords():
|
|
86
|
-
|
|
114
|
+
if plugin_class:
|
|
115
|
+
plugin_record = plugin_class.FromKeyValueRecord(key_value_record)
|
|
116
|
+
_Output(plugin_record, output=args.output)
|
|
117
|
+
else:
|
|
118
|
+
_Output(key_value_record, output=args.output)
|
|
87
119
|
|
|
88
120
|
else:
|
|
89
121
|
print(f'{args.structure_type} is not supported for ldb files.')
|
|
@@ -91,6 +123,16 @@ def LdbCommand(args):
|
|
|
91
123
|
|
|
92
124
|
def LogCommand(args):
|
|
93
125
|
"""The CLI for processing log files."""
|
|
126
|
+
if args.plugin and args.plugin == 'list':
|
|
127
|
+
for plugin, _ in manager.LeveldbPluginManager.GetPlugins():
|
|
128
|
+
print(plugin)
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
if args.plugin:
|
|
132
|
+
plugin_class = manager.LeveldbPluginManager.GetPlugin(args.plugin)
|
|
133
|
+
else:
|
|
134
|
+
plugin_class = None
|
|
135
|
+
|
|
94
136
|
log_file = log.FileReader(args.source)
|
|
95
137
|
|
|
96
138
|
if args.structure_type == 'blocks':
|
|
@@ -112,7 +154,11 @@ def LogCommand(args):
|
|
|
112
154
|
or not args.structure_type):
|
|
113
155
|
# Prints key value record information.
|
|
114
156
|
for internal_key_record in log_file.GetParsedInternalKeys():
|
|
115
|
-
|
|
157
|
+
if plugin_class:
|
|
158
|
+
plugin_record = plugin_class.FromKeyValueRecord(internal_key_record)
|
|
159
|
+
_Output(plugin_record, output=args.output)
|
|
160
|
+
else:
|
|
161
|
+
_Output(internal_key_record, output=args.output)
|
|
116
162
|
|
|
117
163
|
else:
|
|
118
164
|
print(f'{args.structure_type} is not supported for log files.')
|
|
@@ -144,6 +190,7 @@ def DescriptorCommand(args):
|
|
|
144
190
|
else:
|
|
145
191
|
print(f'{args.structure_type} is not supported for descriptor files.')
|
|
146
192
|
|
|
193
|
+
|
|
147
194
|
def App():
|
|
148
195
|
"""The CLI app entrypoint for parsing leveldb files."""
|
|
149
196
|
parser = argparse.ArgumentParser(
|
|
@@ -160,10 +207,17 @@ def App():
|
|
|
160
207
|
required=True,
|
|
161
208
|
type=pathlib.Path,
|
|
162
209
|
help='The source leveldb directory')
|
|
163
|
-
parser_db.
|
|
210
|
+
recover_group = parser_db.add_mutually_exclusive_group()
|
|
211
|
+
recover_group.add_argument(
|
|
164
212
|
'--use_manifest',
|
|
165
213
|
action='store_true',
|
|
166
214
|
help='Use manifest file to determine active/deleted records.')
|
|
215
|
+
recover_group.add_argument(
|
|
216
|
+
'--use_sequence_number',
|
|
217
|
+
action='store_true',
|
|
218
|
+
help=(
|
|
219
|
+
'Use sequence number and file offset to determine active/deleted '
|
|
220
|
+
'records.'))
|
|
167
221
|
parser_db.add_argument(
|
|
168
222
|
'-o',
|
|
169
223
|
'--output',
|
|
@@ -173,6 +227,9 @@ def App():
|
|
|
173
227
|
'repr'],
|
|
174
228
|
default='json',
|
|
175
229
|
help='Output format. Default is json')
|
|
230
|
+
parser_db.add_argument(
|
|
231
|
+
'--plugin',
|
|
232
|
+
help='Use plugin to parse records.')
|
|
176
233
|
parser_db.set_defaults(func=DbCommand)
|
|
177
234
|
|
|
178
235
|
parser_log = subparsers.add_parser(
|
|
@@ -191,6 +248,9 @@ def App():
|
|
|
191
248
|
'repr'],
|
|
192
249
|
default='json',
|
|
193
250
|
help='Output format. Default is json')
|
|
251
|
+
parser_log.add_argument(
|
|
252
|
+
'--plugin',
|
|
253
|
+
help='Use plugin to parse records.')
|
|
194
254
|
parser_log.add_argument(
|
|
195
255
|
'-t',
|
|
196
256
|
'--structure_type',
|
|
@@ -218,6 +278,9 @@ def App():
|
|
|
218
278
|
'repr'],
|
|
219
279
|
default='json',
|
|
220
280
|
help='Output format. Default is json')
|
|
281
|
+
parser_ldb.add_argument(
|
|
282
|
+
'--plugin',
|
|
283
|
+
help='Use plugin to parse records.')
|
|
221
284
|
parser_ldb.add_argument(
|
|
222
285
|
'-t',
|
|
223
286
|
'--structure_type',
|
dfindexeddb/leveldb/log.py
CHANGED
|
@@ -152,7 +152,7 @@ class PhysicalRecord(utils.FromDecoderMixin):
|
|
|
152
152
|
@classmethod
|
|
153
153
|
def FromDecoder(
|
|
154
154
|
cls, decoder: utils.LevelDBDecoder, base_offset: int = 0
|
|
155
|
-
) -> PhysicalRecord:
|
|
155
|
+
) -> Optional[PhysicalRecord]:
|
|
156
156
|
"""Decodes a PhysicalRecord from the current position of a LevelDBDecoder.
|
|
157
157
|
|
|
158
158
|
Args:
|
|
@@ -161,11 +161,13 @@ class PhysicalRecord(utils.FromDecoderMixin):
|
|
|
161
161
|
read from.
|
|
162
162
|
|
|
163
163
|
Returns:
|
|
164
|
-
A PhysicalRecord.
|
|
164
|
+
A PhysicalRecord or None if the parsed header is 0.
|
|
165
165
|
"""
|
|
166
166
|
offset, checksum = decoder.DecodeUint32()
|
|
167
167
|
_, length = decoder.DecodeUint16()
|
|
168
168
|
_, record_type_byte = decoder.DecodeUint8()
|
|
169
|
+
if checksum == 0 or length == 0 or record_type_byte == 0:
|
|
170
|
+
return None
|
|
169
171
|
try:
|
|
170
172
|
record_type = definitions.LogFilePhysicalRecordType(record_type_byte)
|
|
171
173
|
except ValueError as error:
|
|
@@ -206,7 +208,11 @@ class Block:
|
|
|
206
208
|
buffer_length = len(self.data)
|
|
207
209
|
|
|
208
210
|
while buffer.tell() + PhysicalRecord.PHYSICAL_HEADER_LENGTH < buffer_length:
|
|
209
|
-
|
|
211
|
+
record = PhysicalRecord.FromStream(buffer, base_offset=self.offset)
|
|
212
|
+
if record:
|
|
213
|
+
yield record
|
|
214
|
+
else:
|
|
215
|
+
return
|
|
210
216
|
|
|
211
217
|
@classmethod
|
|
212
218
|
def FromStream(cls, stream: BinaryIO) -> Optional[Block]:
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
"""Leveldb Plugin module."""
|
|
16
|
+
|
|
17
|
+
from dfindexeddb.leveldb.plugins import chrome_notifications
|