dfindexeddb 20240305__py3-none-any.whl → 20240324__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/blink.py +2 -1
- dfindexeddb/indexeddb/chromium.py +4 -4
- dfindexeddb/indexeddb/cli.py +101 -0
- dfindexeddb/indexeddb/utils.py +0 -0
- dfindexeddb/leveldb/cli.py +217 -0
- dfindexeddb/leveldb/definitions.py +16 -0
- dfindexeddb/leveldb/descriptor.py +10 -11
- dfindexeddb/leveldb/ldb.py +20 -24
- dfindexeddb/leveldb/log.py +25 -18
- dfindexeddb/leveldb/record.py +102 -0
- dfindexeddb/leveldb/utils.py +116 -0
- dfindexeddb/utils.py +5 -46
- dfindexeddb/version.py +1 -1
- {dfindexeddb-20240305.dist-info → dfindexeddb-20240324.dist-info}/METADATA +46 -32
- dfindexeddb-20240324.dist-info/RECORD +26 -0
- {dfindexeddb-20240305.dist-info → dfindexeddb-20240324.dist-info}/WHEEL +1 -1
- dfindexeddb-20240324.dist-info/entry_points.txt +3 -0
- dfindexeddb/cli.py +0 -180
- dfindexeddb-20240305.dist-info/RECORD +0 -22
- dfindexeddb-20240305.dist-info/entry_points.txt +0 -2
- {dfindexeddb-20240305.dist-info → dfindexeddb-20240324.dist-info}/AUTHORS +0 -0
- {dfindexeddb-20240305.dist-info → dfindexeddb-20240324.dist-info}/LICENSE +0 -0
- {dfindexeddb-20240305.dist-info → dfindexeddb-20240324.dist-info}/top_level.txt +0 -0
dfindexeddb/leveldb/log.py
CHANGED
|
@@ -15,12 +15,13 @@
|
|
|
15
15
|
"""Parser for LevelDB Log (.log) files."""
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
-
from dataclasses import dataclass
|
|
18
|
+
from dataclasses import dataclass
|
|
19
19
|
import io
|
|
20
20
|
from typing import BinaryIO, Generator, Iterable, Optional
|
|
21
21
|
|
|
22
|
-
from dfindexeddb import
|
|
22
|
+
from dfindexeddb import errors
|
|
23
23
|
from dfindexeddb.leveldb import definitions
|
|
24
|
+
from dfindexeddb.leveldb import utils
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
@dataclass
|
|
@@ -29,18 +30,17 @@ class ParsedInternalKey:
|
|
|
29
30
|
|
|
30
31
|
Attributes:
|
|
31
32
|
offset: the offset of the record.
|
|
32
|
-
|
|
33
|
+
record_type: the record type.
|
|
33
34
|
sequence_number: the sequence number (inferred from the relative location
|
|
34
35
|
the ParsedInternalKey in a WriteBatch.)
|
|
35
36
|
key: the record key.
|
|
36
37
|
value: the record value.
|
|
37
38
|
"""
|
|
38
39
|
offset: int
|
|
39
|
-
|
|
40
|
+
record_type: definitions.InternalRecordType
|
|
40
41
|
sequence_number: int
|
|
41
42
|
key: bytes
|
|
42
43
|
value: bytes
|
|
43
|
-
__type__: str = 'ParsedInternalKey'
|
|
44
44
|
|
|
45
45
|
@classmethod
|
|
46
46
|
def FromDecoder(
|
|
@@ -65,15 +65,17 @@ class ParsedInternalKey:
|
|
|
65
65
|
"""
|
|
66
66
|
offset, record_type = decoder.DecodeUint8()
|
|
67
67
|
_, key = decoder.DecodeBlobWithLength()
|
|
68
|
-
|
|
68
|
+
record_type = definitions.InternalRecordType(record_type)
|
|
69
|
+
|
|
70
|
+
if record_type == definitions.InternalRecordType.VALUE:
|
|
69
71
|
_, value = decoder.DecodeBlobWithLength()
|
|
70
|
-
elif record_type ==
|
|
72
|
+
elif record_type == definitions.InternalRecordType.DELETED:
|
|
71
73
|
value = b''
|
|
72
74
|
else:
|
|
73
75
|
raise ValueError(f'Invalid record type {record_type}')
|
|
74
76
|
return cls(
|
|
75
77
|
offset=base_offset + offset,
|
|
76
|
-
|
|
78
|
+
record_type=record_type,
|
|
77
79
|
key=key,
|
|
78
80
|
value=value,
|
|
79
81
|
sequence_number=sequence_number)
|
|
@@ -92,7 +94,7 @@ class WriteBatch(utils.FromDecoderMixin):
|
|
|
92
94
|
offset: int
|
|
93
95
|
sequence_number: int
|
|
94
96
|
count: int
|
|
95
|
-
records: Iterable[ParsedInternalKey]
|
|
97
|
+
records: Iterable[ParsedInternalKey]
|
|
96
98
|
|
|
97
99
|
@classmethod
|
|
98
100
|
def FromDecoder(
|
|
@@ -142,7 +144,7 @@ class PhysicalRecord(utils.FromDecoderMixin):
|
|
|
142
144
|
checksum: int
|
|
143
145
|
length: int
|
|
144
146
|
record_type: definitions.LogFilePhysicalRecordType
|
|
145
|
-
contents: bytes
|
|
147
|
+
contents: bytes
|
|
146
148
|
contents_offset: int
|
|
147
149
|
|
|
148
150
|
PHYSICAL_HEADER_LENGTH = 7
|
|
@@ -163,8 +165,13 @@ class PhysicalRecord(utils.FromDecoderMixin):
|
|
|
163
165
|
"""
|
|
164
166
|
offset, checksum = decoder.DecodeUint32()
|
|
165
167
|
_, length = decoder.DecodeUint16()
|
|
166
|
-
|
|
167
|
-
|
|
168
|
+
_, record_type_byte = decoder.DecodeUint8()
|
|
169
|
+
try:
|
|
170
|
+
record_type = definitions.LogFilePhysicalRecordType(record_type_byte)
|
|
171
|
+
except ValueError as error:
|
|
172
|
+
raise errors.ParserError(
|
|
173
|
+
f'Error parsing record type of Physical Record at offset '
|
|
174
|
+
f'{offset + base_offset}') from error
|
|
168
175
|
contents_offset, contents = decoder.ReadBytes(length)
|
|
169
176
|
return cls(
|
|
170
177
|
base_offset=base_offset,
|
|
@@ -185,7 +192,7 @@ class Block:
|
|
|
185
192
|
data: the block data.
|
|
186
193
|
"""
|
|
187
194
|
offset: int
|
|
188
|
-
data: bytes
|
|
195
|
+
data: bytes
|
|
189
196
|
|
|
190
197
|
BLOCK_SIZE = 32768
|
|
191
198
|
|
|
@@ -276,7 +283,7 @@ class FileReader:
|
|
|
276
283
|
"""
|
|
277
284
|
buffer = bytearray()
|
|
278
285
|
for physical_record in self.GetPhysicalRecords():
|
|
279
|
-
if(physical_record.record_type ==
|
|
286
|
+
if (physical_record.record_type ==
|
|
280
287
|
definitions.LogFilePhysicalRecordType.FULL):
|
|
281
288
|
buffer = physical_record.contents
|
|
282
289
|
offset = physical_record.contents_offset + physical_record.base_offset
|
|
@@ -295,13 +302,13 @@ class FileReader:
|
|
|
295
302
|
yield WriteBatch.FromBytes(buffer, base_offset=offset)
|
|
296
303
|
buffer = bytearray()
|
|
297
304
|
|
|
298
|
-
def
|
|
299
|
-
"""Returns an iterator of
|
|
305
|
+
def GetParsedInternalKeys(self) -> Generator[ParsedInternalKey, None, None]:
|
|
306
|
+
"""Returns an iterator of ParsedInternalKey instances.
|
|
300
307
|
|
|
301
|
-
A batch can contain
|
|
308
|
+
A batch can contain one or more key value records.
|
|
302
309
|
|
|
303
310
|
Yields:
|
|
304
|
-
|
|
311
|
+
ParsedInternalKey
|
|
305
312
|
"""
|
|
306
313
|
for batch in self.GetWriteBatches():
|
|
307
314
|
yield from batch.records
|
|
@@ -0,0 +1,102 @@
|
|
|
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
|
+
"""A module for records from LevelDB files."""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
import dataclasses
|
|
18
|
+
import pathlib
|
|
19
|
+
import sys
|
|
20
|
+
from typing import Any, Generator, Union
|
|
21
|
+
|
|
22
|
+
from dfindexeddb.leveldb import descriptor
|
|
23
|
+
from dfindexeddb.leveldb import ldb
|
|
24
|
+
from dfindexeddb.leveldb import log
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclasses.dataclass
|
|
28
|
+
class LevelDBRecord:
|
|
29
|
+
"""A leveldb record.
|
|
30
|
+
|
|
31
|
+
A record can come from a log file, a table (ldb) file or a descriptor
|
|
32
|
+
(MANIFEST) file.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
path: the file path where the record was parsed from.
|
|
36
|
+
record: the leveldb record.
|
|
37
|
+
"""
|
|
38
|
+
path: str
|
|
39
|
+
record: Union[
|
|
40
|
+
ldb.KeyValueRecord,
|
|
41
|
+
log.ParsedInternalKey,
|
|
42
|
+
descriptor.VersionEdit]
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def FromFile(
|
|
46
|
+
cls,
|
|
47
|
+
file_path: pathlib.Path,
|
|
48
|
+
include_versionedit: bool = False
|
|
49
|
+
) -> Generator[LevelDBRecord, Any, Any]:
|
|
50
|
+
"""Yields leveldb records from the given path.
|
|
51
|
+
|
|
52
|
+
Yields:
|
|
53
|
+
LevelDBRecords
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
file_path: the file path.
|
|
57
|
+
include_versionedit: include VersionEdit records from descriptor files.
|
|
58
|
+
"""
|
|
59
|
+
if file_path.name.endswith('.log'):
|
|
60
|
+
for record in log.FileReader(
|
|
61
|
+
file_path.as_posix()).GetParsedInternalKeys():
|
|
62
|
+
yield cls(path=file_path.as_posix(), record=record)
|
|
63
|
+
elif file_path.name.endswith('.ldb'):
|
|
64
|
+
for record in ldb.FileReader(file_path.as_posix()).GetKeyValueRecords():
|
|
65
|
+
yield cls(path=file_path.as_posix(), record=record)
|
|
66
|
+
elif file_path.name.startswith('MANIFEST'):
|
|
67
|
+
if not include_versionedit:
|
|
68
|
+
print(f'Ignoring {file_path.as_posix()}', file=sys.stderr)
|
|
69
|
+
return
|
|
70
|
+
for record in descriptor.FileReader(
|
|
71
|
+
file_path.as_posix()).GetVersionEdits():
|
|
72
|
+
yield cls(path=file_path.as_posix(), record=record)
|
|
73
|
+
elif file_path.name in ('LOCK', 'CURRENT', 'LOG', 'LOG.old'):
|
|
74
|
+
print(f'Ignoring {file_path.as_posix()}', file=sys.stderr)
|
|
75
|
+
else:
|
|
76
|
+
print(f'Unsupported file type {file_path.as_posix()}', file=sys.stderr)
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def FromDir(
|
|
80
|
+
cls,
|
|
81
|
+
path: pathlib.Path,
|
|
82
|
+
include_versionedit: bool = False
|
|
83
|
+
) -> Generator[LevelDBRecord, Any, Any]:
|
|
84
|
+
"""Yields LevelDBRecords from the given directory.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
path: the file path.
|
|
88
|
+
include_versionedit: include VersionEdit records from descriptor files.
|
|
89
|
+
|
|
90
|
+
Yields:
|
|
91
|
+
LevelDBRecords
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
ValueError: if path is not a directory.
|
|
95
|
+
"""
|
|
96
|
+
if path.is_dir():
|
|
97
|
+
for file_path in path.iterdir():
|
|
98
|
+
yield from cls.FromFile(
|
|
99
|
+
file_path=file_path,
|
|
100
|
+
include_versionedit=include_versionedit)
|
|
101
|
+
else:
|
|
102
|
+
raise ValueError(f'{path} is not a directory')
|
|
@@ -0,0 +1,116 @@
|
|
|
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
|
+
"""Helper/utility classes for LevelDB."""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
import io
|
|
18
|
+
from typing import BinaryIO, Tuple, Type, TypeVar
|
|
19
|
+
|
|
20
|
+
from dfindexeddb import errors
|
|
21
|
+
from dfindexeddb import utils
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LevelDBDecoder(utils.StreamDecoder):
|
|
25
|
+
"""A helper class to decode data types from LevelDB files."""
|
|
26
|
+
|
|
27
|
+
def DecodeBool(self) -> Tuple[int, bool]:
|
|
28
|
+
"""Returns a Tuple of the offset of decoding and the bool value."""
|
|
29
|
+
offset, buffer = self.ReadBytes(1)
|
|
30
|
+
return offset, buffer[0] is not None
|
|
31
|
+
|
|
32
|
+
def DecodeString(self) -> Tuple[int, str]:
|
|
33
|
+
"""Returns a tuple of the offset of decoding and the string value.
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
errors.DecoderError: when the parsed string buffer is not even (i.e.
|
|
37
|
+
cannot be decoded as a UTF-16-BE string.
|
|
38
|
+
"""
|
|
39
|
+
offset = self.stream.tell()
|
|
40
|
+
buffer = self.stream.read()
|
|
41
|
+
if len(buffer) % 2:
|
|
42
|
+
raise errors.DecoderError(
|
|
43
|
+
f'Odd number of bytes encountered at offset {offset}')
|
|
44
|
+
return offset, buffer.decode('utf-16-be')
|
|
45
|
+
|
|
46
|
+
def DecodeLengthPrefixedSlice(self) -> Tuple[int, bytes]:
|
|
47
|
+
"""Returns a tuple of the offset of decoding and the byte 'slice'."""
|
|
48
|
+
offset, num_bytes = self.DecodeUint32Varint()
|
|
49
|
+
_, blob = self.ReadBytes(num_bytes)
|
|
50
|
+
return offset, blob
|
|
51
|
+
|
|
52
|
+
def DecodeBlobWithLength(self) -> Tuple[int, bytes]:
|
|
53
|
+
"""Returns a tuple of a the offset of decoding and the binary blob."""
|
|
54
|
+
offset, num_bytes = self.DecodeUint64Varint()
|
|
55
|
+
_, blob = self.ReadBytes(num_bytes)
|
|
56
|
+
return offset, blob
|
|
57
|
+
|
|
58
|
+
def DecodeStringWithLength(self, encoding='utf-16-be') -> Tuple[int, str]:
|
|
59
|
+
"""Returns a tuple of the offset of decoding and the string value."""
|
|
60
|
+
offset, length = self.DecodeUint64Varint()
|
|
61
|
+
_, buffer = self.ReadBytes(length*2)
|
|
62
|
+
return offset, buffer.decode(encoding=encoding)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
T = TypeVar('T')
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class FromDecoderMixin:
|
|
69
|
+
"""A mixin for parsing dataclass attributes using a LevelDBDecoder."""
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def FromDecoder(
|
|
73
|
+
cls: Type[T], decoder: LevelDBDecoder, base_offset: int = 0) -> T:
|
|
74
|
+
"""Decodes a class type from the current position of a LevelDBDecoder.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
decoder: the LevelDBDecoder.
|
|
78
|
+
base_offset: the base offset.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
The class instance.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
NotImplementedError if the child class does not implement this method.
|
|
85
|
+
"""
|
|
86
|
+
raise NotImplementedError
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def FromStream(
|
|
90
|
+
cls: Type[T], stream: BinaryIO, base_offset: int = 0) -> T:
|
|
91
|
+
"""Decodes a class type from the current position of a binary stream.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
stream: the binary stream.
|
|
95
|
+
base_offset: the base offset of the binary stream.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
The class instance.
|
|
99
|
+
"""
|
|
100
|
+
decoder = LevelDBDecoder(stream)
|
|
101
|
+
return cls.FromDecoder(decoder=decoder, base_offset=base_offset)
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def FromBytes(
|
|
105
|
+
cls: Type[T], raw_data: bytes, base_offset: int = 0) -> T:
|
|
106
|
+
"""Parses a class type from raw bytes.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
raw_data: the raw data.
|
|
110
|
+
base_offset: the base offset of the raw data.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
The class instance.
|
|
114
|
+
"""
|
|
115
|
+
stream = io.BytesIO(raw_data)
|
|
116
|
+
return cls.FromStream(stream=stream, base_offset=base_offset)
|
dfindexeddb/utils.py
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# See the License for the specific language governing permissions and
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
"""Utilities for dfindexeddb."""
|
|
16
|
+
from __future__ import annotations
|
|
16
17
|
import io
|
|
17
18
|
import os
|
|
18
19
|
import struct
|
|
@@ -206,48 +207,6 @@ class StreamDecoder:
|
|
|
206
207
|
return self.DecodeZigzagVarint(max_bytes=10)
|
|
207
208
|
|
|
208
209
|
|
|
209
|
-
|
|
210
|
-
class LevelDBDecoder(StreamDecoder):
|
|
211
|
-
"""A helper class to decode data types from LevelDB files."""
|
|
212
|
-
|
|
213
|
-
def DecodeBool(self) -> Tuple[int, bool]:
|
|
214
|
-
"""Returns a Tuple of the offset of decoding and the bool value."""
|
|
215
|
-
offset, buffer = self.ReadBytes(1)
|
|
216
|
-
return offset, buffer[0] is not None
|
|
217
|
-
|
|
218
|
-
def DecodeString(self) -> Tuple[int, str]:
|
|
219
|
-
"""Returns a tuple of the offset of decoding and the string value.
|
|
220
|
-
|
|
221
|
-
Raises:
|
|
222
|
-
errors.DecoderError: when the parsed string buffer is not even (i.e.
|
|
223
|
-
cannot be decoded as a UTF-16-BE string.
|
|
224
|
-
"""
|
|
225
|
-
offset = self.stream.tell()
|
|
226
|
-
buffer = self.stream.read()
|
|
227
|
-
if len(buffer) % 2:
|
|
228
|
-
raise errors.DecoderError(
|
|
229
|
-
f'Odd number of bytes encountered at offset {offset}')
|
|
230
|
-
return offset, buffer.decode('utf-16-be')
|
|
231
|
-
|
|
232
|
-
def DecodeLengthPrefixedSlice(self) -> Tuple[int, bytes]:
|
|
233
|
-
"""Returns a tuple of the offset of decoding and the byte 'slice'."""
|
|
234
|
-
offset, num_bytes = self.DecodeUint32Varint()
|
|
235
|
-
_, blob = self.ReadBytes(num_bytes)
|
|
236
|
-
return offset, blob
|
|
237
|
-
|
|
238
|
-
def DecodeBlobWithLength(self) -> Tuple[int, bytes]:
|
|
239
|
-
"""Returns a tuple of a the offset of decoding and the binary blob."""
|
|
240
|
-
offset, num_bytes = self.DecodeUint64Varint()
|
|
241
|
-
_, blob = self.ReadBytes(num_bytes)
|
|
242
|
-
return offset, blob
|
|
243
|
-
|
|
244
|
-
def DecodeStringWithLength(self, encoding='utf-16-be') -> Tuple[int, str]:
|
|
245
|
-
"""Returns a tuple of the offset of decoding and the string value."""
|
|
246
|
-
offset, length = self.DecodeUint64Varint()
|
|
247
|
-
_, buffer = self.ReadBytes(length*2)
|
|
248
|
-
return offset, buffer.decode(encoding=encoding)
|
|
249
|
-
|
|
250
|
-
|
|
251
210
|
T = TypeVar('T')
|
|
252
211
|
|
|
253
212
|
|
|
@@ -256,11 +215,11 @@ class FromDecoderMixin:
|
|
|
256
215
|
|
|
257
216
|
@classmethod
|
|
258
217
|
def FromDecoder(
|
|
259
|
-
cls: Type[T], decoder:
|
|
260
|
-
"""Decodes a class type from the current position of a
|
|
218
|
+
cls: Type[T], decoder: StreamDecoder, base_offset: int = 0) -> T:
|
|
219
|
+
"""Decodes a class type from the current position of a StreamDecoder.
|
|
261
220
|
|
|
262
221
|
Args:
|
|
263
|
-
decoder: the
|
|
222
|
+
decoder: the StreamDecoder.
|
|
264
223
|
base_offset: the base offset.
|
|
265
224
|
|
|
266
225
|
Returns:
|
|
@@ -283,7 +242,7 @@ class FromDecoderMixin:
|
|
|
283
242
|
Returns:
|
|
284
243
|
The class instance.
|
|
285
244
|
"""
|
|
286
|
-
decoder =
|
|
245
|
+
decoder = StreamDecoder(stream)
|
|
287
246
|
return cls.FromDecoder(decoder=decoder, base_offset=base_offset)
|
|
288
247
|
|
|
289
248
|
@classmethod
|
dfindexeddb/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dfindexeddb
|
|
3
|
-
Version:
|
|
3
|
+
Version: 20240324
|
|
4
4
|
Summary: dfindexeddb is an experimental Python tool for performing digital forensic analysis of IndexedDB and leveldb files.
|
|
5
5
|
Author-email: Syd Pleno <sydp@google.com>
|
|
6
6
|
Maintainer-email: dfIndexeddb Developers <dfindexeddb-dev@googlegroups.com>
|
|
@@ -226,7 +226,9 @@ dfindexeddb is an experimental Python tool for performing digital forensic
|
|
|
226
226
|
analysis of IndexedDB and leveldb files.
|
|
227
227
|
|
|
228
228
|
It parses leveldb, IndexedDB and javascript structures from these files without
|
|
229
|
-
requiring native libraries.
|
|
229
|
+
requiring native libraries. (Note: only a subset of IndexedDB key types and
|
|
230
|
+
Javascript types for Chromium-based browsers are currently supported. Safari
|
|
231
|
+
and Firefox are under development).
|
|
230
232
|
|
|
231
233
|
The content of IndexedDB files is dependent on what a web application stores
|
|
232
234
|
locally/offline using the web browser's
|
|
@@ -236,25 +238,34 @@ include:
|
|
|
236
238
|
* emails and contact information from an e-mail application,
|
|
237
239
|
* images and metadata from a photo gallery application
|
|
238
240
|
|
|
241
|
+
|
|
239
242
|
## Installation
|
|
240
243
|
|
|
244
|
+
1. [Linux] Install the snappy compression development package
|
|
245
|
+
|
|
241
246
|
```
|
|
242
|
-
$
|
|
247
|
+
$ sudo apt install libsnappy-dev
|
|
243
248
|
```
|
|
244
249
|
|
|
245
|
-
|
|
250
|
+
2. Create a virtual environment and install the package
|
|
246
251
|
|
|
247
|
-
|
|
252
|
+
```
|
|
253
|
+
$ python3 -m venv .venv
|
|
254
|
+
$ source .venv/bin/activate
|
|
255
|
+
$ pip install dfindexeddb
|
|
256
|
+
```
|
|
248
257
|
|
|
249
|
-
|
|
258
|
+
## Installation from source
|
|
259
|
+
|
|
260
|
+
1. [Linux] Install the snappy compression development package
|
|
250
261
|
|
|
251
262
|
```
|
|
252
263
|
$ sudo apt install libsnappy-dev
|
|
253
264
|
```
|
|
254
265
|
|
|
255
|
-
2. Clone or download the repository to your local machine.
|
|
266
|
+
2. Clone or download/unzip the repository to your local machine.
|
|
256
267
|
|
|
257
|
-
3. Create a
|
|
268
|
+
3. Create a virtual environment and install the package
|
|
258
269
|
|
|
259
270
|
```
|
|
260
271
|
$ python3 -m venv .venv
|
|
@@ -264,55 +275,58 @@ $ pip install dfindexeddb
|
|
|
264
275
|
|
|
265
276
|
## Usage
|
|
266
277
|
|
|
267
|
-
|
|
278
|
+
Two CLI tools for parsing IndexedDB/leveldb files are available after
|
|
279
|
+
installation:
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
### IndexedDB
|
|
268
283
|
|
|
269
284
|
```
|
|
270
285
|
$ dfindexeddb -h
|
|
271
|
-
usage: dfindexeddb [-h] -s SOURCE [--json]
|
|
272
|
-
|
|
273
|
-
A cli tool for the dfindexeddb package
|
|
286
|
+
usage: dfindexeddb [-h] -s SOURCE [--json]
|
|
274
287
|
|
|
275
|
-
|
|
276
|
-
{log,ldb,indexeddb}
|
|
288
|
+
A cli tool for parsing indexeddb files
|
|
277
289
|
|
|
278
290
|
options:
|
|
291
|
+
-h, --help show this help message and exit
|
|
279
292
|
-s SOURCE, --source SOURCE
|
|
280
|
-
The source leveldb
|
|
293
|
+
The source leveldb folder
|
|
281
294
|
--json Output as JSON
|
|
282
295
|
```
|
|
283
296
|
|
|
284
|
-
|
|
297
|
+
### LevelDB
|
|
285
298
|
|
|
286
299
|
```
|
|
287
|
-
$
|
|
288
|
-
usage:
|
|
300
|
+
$ dfleveldb -h
|
|
301
|
+
usage: dfleveldb [-h] {db,log,ldb,descriptor} ...
|
|
302
|
+
|
|
303
|
+
A cli tool for parsing leveldb files
|
|
289
304
|
|
|
290
305
|
positional arguments:
|
|
291
|
-
{
|
|
306
|
+
{db,log,ldb,descriptor}
|
|
307
|
+
db Parse a directory as leveldb.
|
|
308
|
+
log Parse a leveldb log file.
|
|
309
|
+
ldb Parse a leveldb table (.ldb) file.
|
|
310
|
+
descriptor Parse a leveldb descriptor (MANIFEST) file.
|
|
292
311
|
|
|
293
312
|
options:
|
|
294
313
|
-h, --help show this help message and exit
|
|
295
314
|
```
|
|
296
315
|
|
|
297
|
-
To parse a LevelDB .
|
|
316
|
+
To parse records from a LevelDB log (.log) file, use the following command:
|
|
298
317
|
|
|
299
318
|
```
|
|
300
|
-
$
|
|
301
|
-
|
|
319
|
+
$ dfleveldb log -s <SOURCE> [--json]
|
|
320
|
+
```
|
|
302
321
|
|
|
303
|
-
|
|
304
|
-
{blocks,records}
|
|
322
|
+
To parse records from a LevelDB table (.ldb) file, use the following command:
|
|
305
323
|
|
|
306
|
-
|
|
307
|
-
|
|
324
|
+
```
|
|
325
|
+
$ dfleveldb ldb -s <SOURCE> [--json]
|
|
308
326
|
```
|
|
309
327
|
|
|
310
|
-
To parse
|
|
328
|
+
To parse version edit records from a Descriptor (MANIFEST) file:
|
|
311
329
|
|
|
312
330
|
```
|
|
313
|
-
$
|
|
314
|
-
usage: dfindexeddb indexeddb [-h]
|
|
315
|
-
|
|
316
|
-
options:
|
|
317
|
-
-h, --help show this help message and exit
|
|
331
|
+
$ dfleveldb descriptor -s <SOURCE> [--json]
|
|
318
332
|
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
dfindexeddb/__init__.py,sha256=KPYL9__l8od6_OyDfGRTgaJ6iy_fqIgZ-dS2S-e3Rac,599
|
|
2
|
+
dfindexeddb/errors.py,sha256=PNpwyf_lrPc4TE77oAakX3mu5D_YcP3f80wq8Y1LkvY,749
|
|
3
|
+
dfindexeddb/utils.py,sha256=pV2blFnMxDwk3kBRK6UVji66ctkYpm6wfH9p0jCC7Nk,8797
|
|
4
|
+
dfindexeddb/version.py,sha256=EVJT5s12vbz8xf-Q1XokuIHYzjSFiD4qJS1qBmpWjdc,750
|
|
5
|
+
dfindexeddb/indexeddb/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
|
|
6
|
+
dfindexeddb/indexeddb/blink.py,sha256=voC5P33MkyF9gMYJL6XVu2jDk68auTj3qxsCZw_x9rs,3548
|
|
7
|
+
dfindexeddb/indexeddb/chromium.py,sha256=wczfAYSVVWwfzrSf3O_DxZmjVy4b6fzBom6mr9nKZHA,44713
|
|
8
|
+
dfindexeddb/indexeddb/cli.py,sha256=drTSV2Eu-DKQev8xRqgifX68vTAjW0Nt_kUqT79-ZWo,3235
|
|
9
|
+
dfindexeddb/indexeddb/definitions.py,sha256=yline3y3gmZx6s-dwjpPDNs5HO4zT6KZqPWQfEsHDoM,7413
|
|
10
|
+
dfindexeddb/indexeddb/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
dfindexeddb/indexeddb/v8.py,sha256=ldqpc9T1kG7BOdjnHjQ5hNO9OCXZ3_Zd6vRSpC-NrEA,21893
|
|
12
|
+
dfindexeddb/leveldb/__init__.py,sha256=KPYL9__l8od6_OyDfGRTgaJ6iy_fqIgZ-dS2S-e3Rac,599
|
|
13
|
+
dfindexeddb/leveldb/cli.py,sha256=tqu8Vpw45d38zVGLvTNDiNtppHoakj_4IrzURzQsEv8,6702
|
|
14
|
+
dfindexeddb/leveldb/definitions.py,sha256=wwm0uySOeI0-2fG9KvrY-StE0NP3iW5mCEuKYQL4ahg,1436
|
|
15
|
+
dfindexeddb/leveldb/descriptor.py,sha256=cw7A_KDiKf3ZyYThPbvC-LtnPM7Jvn3zr2jPswRBTq4,10426
|
|
16
|
+
dfindexeddb/leveldb/ldb.py,sha256=mN-M7PLtE_VLZCbCbzRgjkSezbMUhgDjgWgPgIxJ1jM,8087
|
|
17
|
+
dfindexeddb/leveldb/log.py,sha256=QeH8oESOPEZUjANGiDRSmXZa2SuoKlPFBJY7SxTV1lg,9209
|
|
18
|
+
dfindexeddb/leveldb/record.py,sha256=AnM4kQb81igmJla5q3rQUYvlzPwZaEHTvauxOC_dtM8,3217
|
|
19
|
+
dfindexeddb/leveldb/utils.py,sha256=RgEEZ7Z35m3CcOUypAiViQSzKjBgSXZ3aeJhQjY3H9w,3748
|
|
20
|
+
dfindexeddb-20240324.dist-info/AUTHORS,sha256=QbvjbAom57fpEkekkCVFUj0B9KUMGraR510aUMBC-PE,286
|
|
21
|
+
dfindexeddb-20240324.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
22
|
+
dfindexeddb-20240324.dist-info/METADATA,sha256=YT7g3UbA5SXyZZLKSYwwL8Yra8Vi40xh4eJ_G7rD1M4,16473
|
|
23
|
+
dfindexeddb-20240324.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
24
|
+
dfindexeddb-20240324.dist-info/entry_points.txt,sha256=WG9YNLZ9lBx4Q9QF6wS4dZdZfADT3Zs4_-MV5TcA0ls,102
|
|
25
|
+
dfindexeddb-20240324.dist-info/top_level.txt,sha256=X9OTaub1c8S_JJ7g-f8JdkhhdiZ4x1j4eni1hdUCwE4,12
|
|
26
|
+
dfindexeddb-20240324.dist-info/RECORD,,
|