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.
@@ -780,6 +780,9 @@ class V8ScriptValueDecoder:
780
780
 
781
781
  Returns:
782
782
  A parsed CryptoKey.
783
+
784
+ Raises:
785
+ ParserError: if there is an unexpected CryptoKeySubTag.
783
786
  """
784
787
  _, raw_key_byte = self.deserializer.decoder.DecodeUint8()
785
788
  key_byte = definitions.CryptoKeySubTag(raw_key_byte)
@@ -795,6 +798,8 @@ class V8ScriptValueDecoder:
795
798
  key_type, algorithm_parameters = self._ReadED25519Key()
796
799
  elif key_byte == definitions.CryptoKeySubTag.NO_PARAMS_KEY:
797
800
  key_type, algorithm_parameters = self.ReadNoParamsKey()
801
+ else:
802
+ raise errors.ParserError('Unexpected CryptoKeySubTag')
798
803
 
799
804
  _, raw_usages = self.deserializer.decoder.DecodeUint32Varint()
800
805
  usages = definitions.CryptoKeyUsage(raw_usages)
@@ -17,7 +17,11 @@ from __future__ import annotations
17
17
  from dataclasses import dataclass, field
18
18
  from datetime import datetime
19
19
  import io
20
- from typing import Any, BinaryIO, Optional, Tuple, Type, TypeVar, Union
20
+ import pathlib
21
+ import sys
22
+ import traceback
23
+ from typing import Any, BinaryIO, Generator, Optional, Tuple, Type, TypeVar, \
24
+ Union
21
25
 
22
26
  from dfindexeddb import errors
23
27
  from dfindexeddb.indexeddb.chromium import blink
@@ -456,7 +460,7 @@ class MaxDatabaseIdKey(BaseIndexedDBKey):
456
460
  cls, decoder: utils.LevelDBDecoder, key_prefix: KeyPrefix,
457
461
  base_offset: int = 0
458
462
  ) -> MaxDatabaseIdKey:
459
- """Decodes the maximum databse key."""
463
+ """Decodes the maximum database key."""
460
464
  offset, key_type = decoder.DecodeUint8()
461
465
  if key_type != definitions.GlobalMetadataKeyType.MAX_DATABASE_ID:
462
466
  raise errors.ParserError('Not a MaxDatabaseIdKey')
@@ -1271,14 +1275,17 @@ class ExternalObjectEntry(utils.FromDecoderMixin):
1271
1275
  filename = None
1272
1276
  last_modified = None
1273
1277
  token = None
1274
- elif (object_type ==
1275
- definitions.ExternalObjectType.FILE_SYSTEM_ACCESS_HANDLE):
1278
+ else:
1279
+ if (object_type ==
1280
+ definitions.ExternalObjectType.FILE_SYSTEM_ACCESS_HANDLE):
1281
+ _, token = decoder.DecodeBlobWithLength()
1282
+ else:
1283
+ token = None
1276
1284
  blob_number = None
1277
1285
  mime_type = None
1278
1286
  size = None
1279
1287
  filename = None
1280
1288
  last_modified = None
1281
- _, token = decoder.DecodeBlobWithLength()
1282
1289
 
1283
1290
  return cls(offset=base_offset + offset, object_type=object_type,
1284
1291
  blob_number=blob_number, mime_type=mime_type, size=size,
@@ -1331,12 +1338,14 @@ class IndexedDBRecord:
1331
1338
  """An IndexedDB Record.
1332
1339
 
1333
1340
  Attributes:
1341
+ path: the source file path
1334
1342
  offset: the offset of the record.
1335
1343
  key: the key of the record.
1336
1344
  value: the value of the record.
1337
1345
  sequence_number: if available, the sequence number of the record.
1338
1346
  type: the type of the record.
1339
- level: the leveldb level, None indicates the record came from a log file.
1347
+ level: the leveldb level, if applicable, None can indicate the record
1348
+ originated from a log file or the level could not be determined.
1340
1349
  recovered: True if the record is a recovered record.
1341
1350
  """
1342
1351
  path: str
@@ -1350,7 +1359,8 @@ class IndexedDBRecord:
1350
1359
 
1351
1360
  @classmethod
1352
1361
  def FromLevelDBRecord(
1353
- cls, db_record: record.LevelDBRecord
1362
+ cls,
1363
+ db_record: record.LevelDBRecord
1354
1364
  ) -> IndexedDBRecord:
1355
1365
  """Returns an IndexedDBRecord from a ParsedInternalKey."""
1356
1366
  idb_key = IndexedDbKey.FromBytes(
@@ -1366,3 +1376,76 @@ class IndexedDBRecord:
1366
1376
  type=db_record.record.record_type,
1367
1377
  level=db_record.level,
1368
1378
  recovered=db_record.recovered)
1379
+
1380
+ @classmethod
1381
+ def FromFile(
1382
+ cls,
1383
+ file_path: pathlib.Path
1384
+ ) -> Generator[IndexedDBRecord, None, None]:
1385
+ """Yields IndexedDBRecords from a file."""
1386
+ for db_record in record.LevelDBRecord.FromFile(file_path):
1387
+ try:
1388
+ yield cls.FromLevelDBRecord(db_record)
1389
+ except(
1390
+ errors.ParserError,
1391
+ errors.DecoderError,
1392
+ NotImplementedError) as err:
1393
+ print((
1394
+ 'Error parsing Indexeddb record: '
1395
+ f'{err} at offset {db_record.record.offset} in '
1396
+ f'{db_record.path}'),
1397
+ file=sys.stderr)
1398
+ print(f'Traceback: {traceback.format_exc()}', file=sys.stderr)
1399
+
1400
+
1401
+ class FolderReader:
1402
+ """A IndexedDB folder reader for Chrome/Chromium.
1403
+
1404
+ Attributes:
1405
+ foldername (str): the source LevelDB folder.
1406
+ """
1407
+
1408
+ def __init__(self, foldername: pathlib.Path):
1409
+ """Initializes the FileReader.
1410
+
1411
+ Args:
1412
+ foldername: the source IndexedDB folder.
1413
+
1414
+ Raises:
1415
+ ValueError: if foldername is None or not a directory.
1416
+ """
1417
+ if not foldername or not foldername.is_dir():
1418
+ raise ValueError(f'{foldername} is None or not a directory')
1419
+ self.foldername = foldername
1420
+
1421
+ def GetRecords(
1422
+ self,
1423
+ use_manifest: bool = False,
1424
+ use_sequence_number: bool = False
1425
+ ) -> Generator[IndexedDBRecord, None, None]:
1426
+ """Yield LevelDBRecords.
1427
+
1428
+ Args:
1429
+ use_manifest: True to use the current manifest in the folder as a means to
1430
+ find the active file set.
1431
+ use_sequence_number: True to use the sequence number to determine the
1432
+ Yields:
1433
+ IndexedDBRecord.
1434
+ """
1435
+ leveldb_folder_reader = record.FolderReader(self.foldername)
1436
+ for leveldb_record in leveldb_folder_reader.GetRecords(
1437
+ use_manifest=use_manifest,
1438
+ use_sequence_number=use_sequence_number):
1439
+ try:
1440
+ yield IndexedDBRecord.FromLevelDBRecord(
1441
+ leveldb_record)
1442
+ except(
1443
+ errors.ParserError,
1444
+ errors.DecoderError,
1445
+ NotImplementedError) as err:
1446
+ print((
1447
+ 'Error parsing Indexeddb record: '
1448
+ f'{err} at offset {leveldb_record.record.offset} in '
1449
+ f'{leveldb_record.path}'),
1450
+ file=sys.stderr)
1451
+ print(f'Traceback: {traceback.format_exc()}', file=sys.stderr)
@@ -19,14 +19,13 @@ import enum
19
19
  from datetime import datetime
20
20
  import json
21
21
  import pathlib
22
- import sys
23
- import traceback
24
22
 
25
- from dfindexeddb import errors
23
+ from dfindexeddb import utils
26
24
  from dfindexeddb import version
27
- from dfindexeddb.leveldb import record as leveldb_record
25
+ from dfindexeddb.indexeddb.chromium import blink
28
26
  from dfindexeddb.indexeddb.chromium import record as chromium_record
29
27
  from dfindexeddb.indexeddb.chromium import v8
28
+ from dfindexeddb.indexeddb.safari import record as safari_record
30
29
 
31
30
 
32
31
  _VALID_PRINTABLE_CHARACTERS = (
@@ -38,7 +37,7 @@ class Encoder(json.JSONEncoder):
38
37
  """A JSON encoder class for dfindexeddb fields."""
39
38
  def default(self, o):
40
39
  if dataclasses.is_dataclass(o):
41
- o_dict = dataclasses.asdict(o)
40
+ o_dict = utils.asdict(o)
42
41
  return o_dict
43
42
  if isinstance(o, bytes):
44
43
  out = []
@@ -73,98 +72,92 @@ def _Output(structure, output):
73
72
  print(structure)
74
73
 
75
74
 
75
+ def BlinkCommand(args):
76
+ """The CLI for processing a file as a blink value."""
77
+ with open(args.source, 'rb') as fd:
78
+ buffer = fd.read()
79
+ blink_value = blink.V8ScriptValueDecoder.FromBytes(buffer)
80
+ _Output(blink_value, output=args.output)
81
+
82
+
76
83
  def DbCommand(args):
77
- """The CLI for processing a directory as indexeddb."""
78
- if args.use_manifest:
79
- for db_record in leveldb_record.LevelDBRecord.FromManifest(args.source):
80
- record = db_record.record
81
- try:
82
- idb_record = chromium_record.IndexedDBRecord.FromLevelDBRecord(
83
- db_record)
84
- except(
85
- errors.ParserError,
86
- errors.DecoderError,
87
- NotImplementedError) as err:
88
- print((
89
- f'Error parsing Indexeddb record {record.__class__.__name__}: {err}'
90
- f' at offset {record.offset} in {db_record.path}'), file=sys.stderr)
91
- print(f'Traceback: {traceback.format_exc()}', file=sys.stderr)
92
- continue
93
- _Output(idb_record, output=args.output)
94
- else:
95
- for db_record in leveldb_record.LevelDBRecord.FromDir(args.source):
96
- record = db_record.record
97
- try:
98
- idb_record = chromium_record.IndexedDBRecord.FromLevelDBRecord(
99
- db_record)
100
- except(
101
- errors.ParserError,
102
- errors.DecoderError,
103
- NotImplementedError) as err:
104
- print((
105
- f'Error parsing Indexeddb record {record.__class__.__name__}: {err}'
106
- f' at offset {record.offset} in {db_record.path}'), file=sys.stderr)
107
- print(f'Traceback: {traceback.format_exc()}', file=sys.stderr)
108
- continue
109
- _Output(idb_record, output=args.output)
84
+ """The CLI for processing a directory as IndexedDB."""
85
+ if args.format in ('chrome', 'chromium'):
86
+ for db_record in chromium_record.FolderReader(
87
+ args.source).GetRecords(
88
+ use_manifest=args.use_manifest,
89
+ use_sequence_number=args.use_sequence_number):
90
+ _Output(db_record, output=args.output)
91
+ elif args.format == 'safari':
92
+ for db_record in safari_record.FileReader(args.source).Records():
93
+ _Output(db_record, output=args.output)
110
94
 
111
95
 
112
96
  def LdbCommand(args):
113
- """The CLI for processing a leveldb table (.ldb) file as indexeddb."""
114
- for db_record in leveldb_record.LevelDBRecord.FromFile(args.source):
115
- record = db_record.record
116
- try:
117
- idb_record = chromium_record.IndexedDBRecord.FromLevelDBRecord(
118
- db_record)
119
- except(
120
- errors.ParserError,
121
- errors.DecoderError,
122
- NotImplementedError) as err:
123
- print(
124
- (f'Error parsing Indexeddb record {record.__class__.__name__}: {err} '
125
- f'at offset {record.offset} in {db_record.path}'), file=sys.stderr)
126
- print(f'Traceback: {traceback.format_exc()}', file=sys.stderr)
127
- continue
128
- _Output(idb_record, output=args.output)
97
+ """The CLI for processing a LevelDB table (.ldb) file as IndexedDB."""
98
+ for db_record in chromium_record.IndexedDBRecord.FromFile(args.source):
99
+ _Output(db_record, output=args.output)
129
100
 
130
101
 
131
102
  def LogCommand(args):
132
- """The CLI for processing a leveldb log file as indexeddb."""
133
- for db_record in leveldb_record.LevelDBRecord.FromFile(args.source):
134
- record = db_record.record
135
- try:
136
- idb_record = chromium_record.IndexedDBRecord.FromLevelDBRecord(
137
- db_record)
138
- except(
139
- errors.ParserError,
140
- errors.DecoderError,
141
- NotImplementedError) as err:
142
- print(
143
- (f'Error parsing Indexeddb record {record.__class__.__name__}: {err} '
144
- f'at offset {record.offset} in {db_record.path}'), file=sys.stderr)
145
- print(f'Traceback: {traceback.format_exc()}', file=sys.stderr)
146
- continue
147
- _Output(idb_record, output=args.output)
103
+ """The CLI for processing a LevelDB log file as IndexedDB."""
104
+ for db_record in chromium_record.IndexedDBRecord.FromFile(args.source):
105
+ _Output(db_record, output=args.output)
148
106
 
149
107
 
150
108
  def App():
151
109
  """The CLI app entrypoint for dfindexeddb."""
152
110
  parser = argparse.ArgumentParser(
153
111
  prog='dfindexeddb',
154
- description='A cli tool for parsing indexeddb files',
112
+ description='A cli tool for parsing IndexedDB files',
155
113
  epilog=f'Version {version.GetVersion()}')
156
114
 
157
115
  subparsers = parser.add_subparsers()
158
116
 
117
+ parser_blink = subparsers.add_parser(
118
+ 'blink', help='Parse a file as a blink value.')
119
+ parser_blink.add_argument(
120
+ '-s', '--source',
121
+ required=True,
122
+ type=pathlib.Path,
123
+ help=(
124
+ 'The source file.'))
125
+ parser_blink.add_argument(
126
+ '-o',
127
+ '--output',
128
+ choices=[
129
+ 'json',
130
+ 'jsonl',
131
+ 'repr'],
132
+ default='json',
133
+ help='Output format. Default is json')
134
+ parser_blink.set_defaults(func=BlinkCommand)
135
+
159
136
  parser_db = subparsers.add_parser(
160
- 'db', help='Parse a directory as indexeddb.')
161
- parser_db.add_argument(
162
- '-s', '--source', required=True, type=pathlib.Path,
163
- help='The source leveldb folder')
137
+ 'db', help='Parse a directory as IndexedDB.')
164
138
  parser_db.add_argument(
139
+ '-s', '--source',
140
+ required=True,
141
+ type=pathlib.Path,
142
+ help=(
143
+ 'The source IndexedDB folder (for chrome/chromium) '
144
+ 'or file (for safari).'))
145
+ recover_group = parser_db.add_mutually_exclusive_group()
146
+ recover_group.add_argument(
165
147
  '--use_manifest',
166
148
  action='store_true',
167
149
  help='Use manifest file to determine active/deleted records.')
150
+ recover_group.add_argument(
151
+ '--use_sequence_number',
152
+ action='store_true',
153
+ help=(
154
+ 'Use sequence number and file offset to determine active/deleted '
155
+ 'records.'))
156
+ parser_db.add_argument(
157
+ '--format',
158
+ required=True,
159
+ choices=['chromium', 'chrome', 'safari'],
160
+ help='The type of IndexedDB to parse.')
168
161
  parser_db.add_argument(
169
162
  '-o',
170
163
  '--output',
@@ -177,9 +170,12 @@ def App():
177
170
  parser_db.set_defaults(func=DbCommand)
178
171
 
179
172
  parser_ldb = subparsers.add_parser(
180
- 'ldb', help='Parse a ldb file as indexeddb.')
173
+ 'ldb',
174
+ help='Parse a ldb file as IndexedDB.')
181
175
  parser_ldb.add_argument(
182
- '-s', '--source', required=True, type=pathlib.Path,
176
+ '-s', '--source',
177
+ required=True,
178
+ type=pathlib.Path,
183
179
  help='The source .ldb file.')
184
180
  parser_ldb.add_argument(
185
181
  '-o',
@@ -193,9 +189,12 @@ def App():
193
189
  parser_ldb.set_defaults(func=LdbCommand)
194
190
 
195
191
  parser_log = subparsers.add_parser(
196
- 'log', help='Parse a log file as indexeddb.')
192
+ 'log',
193
+ help='Parse a log file as IndexedDB.')
197
194
  parser_log.add_argument(
198
- '-s', '--source', required=True, type=pathlib.Path,
195
+ '-s', '--source',
196
+ required=True,
197
+ type=pathlib.Path,
199
198
  help='The source .log file.')
200
199
  parser_log.add_argument(
201
200
  '-o',
@@ -209,4 +208,7 @@ def App():
209
208
  parser_log.set_defaults(func=LogCommand)
210
209
 
211
210
  args = parser.parse_args()
212
- args.func(args)
211
+ if hasattr(args, 'func'):
212
+ args.func(args)
213
+ else:
214
+ parser.print_help()
@@ -0,0 +1,123 @@
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
+ """Definitions for Webkit/Safari."""
16
+ from enum import IntEnum
17
+
18
+
19
+ CurrentVersion = 0x0000000F # 15
20
+ TerminatorTag = 0xFFFFFFFF
21
+ StringPoolTag = 0xFFFFFFFE
22
+ NonIndexPropertiesTag = 0xFFFFFFFD
23
+ ImageDataPoolTag = 0xFFFFFFFE
24
+ StringDataIs8BitFlag = 0x80000000
25
+
26
+
27
+ SIDBKeyVersion = 0x00
28
+
29
+
30
+ class SIDBKeyType(IntEnum):
31
+ """SIDBKeyType."""
32
+ MIN = 0x00
33
+ NUMBER = 0x20
34
+ DATE = 0x40
35
+ STRING = 0x60
36
+ BINARY = 0x80
37
+ ARRAY = 0xA0
38
+ MAX = 0xFF
39
+
40
+
41
+ class SerializationTag(IntEnum):
42
+ """Database Metadata key types.
43
+
44
+ All tags are recorded as a single uint8_t.
45
+ """
46
+ ARRAY = 1
47
+ OBJECT = 2
48
+ UNDEFINED = 3
49
+ NULL = 4
50
+ INT = 5
51
+ ZERO = 6
52
+ ONE = 7
53
+ FALSE = 8
54
+ TRUE = 9
55
+ DOUBLE = 10
56
+ DATE = 11
57
+ FILE = 12
58
+ FILE_LIST = 13
59
+ IMAGE_DATA = 14
60
+ BLOB = 15
61
+ STRING = 16
62
+ EMPTY_STRING = 17
63
+ REG_EXP = 18
64
+ OBJECT_REFERENCE = 19
65
+ MESSAGE_PORT_REFERENCE = 20
66
+ ARRAY_BUFFER = 21
67
+ ARRAY_BUFFER_VIEW = 22
68
+ ARRAY_BUFFER_TRANSFER = 23
69
+ TRUE_OBJECT = 24
70
+ FALSE_OBJECT = 25
71
+ STRING_OBJECT = 26
72
+ EMPTY_STRING_OBJECT = 27
73
+ NUMBER_OBJECT = 28
74
+ SET_OBJECT = 29
75
+ MAP_OBJECT = 30
76
+ NON_MAP_PROPERTIES = 31
77
+ NON_SET_PROPERTIES = 32
78
+ CRYPTO_KEY = 33
79
+ SHARED_ARRAY_BUFFER = 34
80
+ WASM_MODULE = 35
81
+ DOM_POINT_READONLY = 36
82
+ DOM_POINT = 37
83
+ DOM_RECT_READONLY = 38
84
+ DOM_RECT = 39
85
+ DOM_MATRIX_READONLY = 40
86
+ DOM_MATRIX = 41
87
+ DOM_QUAD = 42
88
+ IMAGE_BITMAP_TRANSFER = 43
89
+ RTC_CERTIFICATE = 44
90
+ IMAGE_BITMAP = 45
91
+ OFF_SCREEN_CANVAS_TRANSFER = 46
92
+ BIGINT = 47
93
+ BIGINT_OBJECT = 48
94
+ WASM_MEMORY = 49
95
+ RTC_DATA_CHANNEL_TRANSFER = 50
96
+ DOM_EXCEPTION = 51
97
+ WEB_CODECS_ENCODED_VIDEO_CHUNK = 52
98
+ WEB_CODECS_VIDEO_FRAME = 53
99
+ RESIZABLE_ARRAY_BUFFER = 54
100
+ ERROR_INSTANCE = 55
101
+ IN_MEMORY_OFFSCREEN_CANVAS = 56
102
+ IN_MEMORY_MESSAGE_PORT = 57
103
+ WEB_CODECS_ENCODED_AUDIO_CHUNK = 58
104
+ WEB_CODECS_AUDIO_DATA = 59
105
+ MEDIA_STREAM_TRACK = 60
106
+ MEDIA_SOURCE_HANDLE_TRANSFER = 61
107
+ ERROR = 255
108
+
109
+
110
+ class ArrayBufferViewSubtag(IntEnum):
111
+ """ArrayBufferView sub tags."""
112
+ DATA_VIEW = 0
113
+ INT8_ARRAY = 1
114
+ UINT8_ARRAY = 2
115
+ UINT8_CLAMPED_ARRAY = 3
116
+ INT16_ARRAY = 4
117
+ UINT16_ARRAY = 5
118
+ INT32_ARRAY = 6
119
+ UINT32_ARRAY = 7
120
+ FLOAT32_ARRAY = 8
121
+ FLOAT64_ARRAY = 9
122
+ BIG_INT64_ARRAY = 10
123
+ BIG_UINT64_ARRAY = 11