dfindexeddb 20241105__py3-none-any.whl → 20260205__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.
Files changed (34) hide show
  1. dfindexeddb/indexeddb/chromium/blink.py +116 -74
  2. dfindexeddb/indexeddb/chromium/definitions.py +240 -125
  3. dfindexeddb/indexeddb/chromium/record.py +651 -346
  4. dfindexeddb/indexeddb/chromium/sqlite.py +362 -0
  5. dfindexeddb/indexeddb/chromium/v8.py +100 -78
  6. dfindexeddb/indexeddb/cli.py +282 -121
  7. dfindexeddb/indexeddb/firefox/definitions.py +7 -4
  8. dfindexeddb/indexeddb/firefox/gecko.py +98 -74
  9. dfindexeddb/indexeddb/firefox/record.py +78 -26
  10. dfindexeddb/indexeddb/safari/definitions.py +5 -3
  11. dfindexeddb/indexeddb/safari/record.py +86 -53
  12. dfindexeddb/indexeddb/safari/webkit.py +85 -71
  13. dfindexeddb/indexeddb/types.py +4 -1
  14. dfindexeddb/leveldb/cli.py +146 -138
  15. dfindexeddb/leveldb/definitions.py +6 -2
  16. dfindexeddb/leveldb/descriptor.py +70 -56
  17. dfindexeddb/leveldb/ldb.py +39 -33
  18. dfindexeddb/leveldb/log.py +41 -30
  19. dfindexeddb/leveldb/plugins/chrome_notifications.py +30 -18
  20. dfindexeddb/leveldb/plugins/interface.py +5 -6
  21. dfindexeddb/leveldb/plugins/manager.py +10 -9
  22. dfindexeddb/leveldb/record.py +71 -62
  23. dfindexeddb/leveldb/utils.py +105 -13
  24. dfindexeddb/utils.py +36 -31
  25. dfindexeddb/version.py +2 -2
  26. dfindexeddb-20260205.dist-info/METADATA +171 -0
  27. dfindexeddb-20260205.dist-info/RECORD +41 -0
  28. {dfindexeddb-20241105.dist-info → dfindexeddb-20260205.dist-info}/WHEEL +1 -1
  29. dfindexeddb-20241105.dist-info/AUTHORS +0 -12
  30. dfindexeddb-20241105.dist-info/METADATA +0 -424
  31. dfindexeddb-20241105.dist-info/RECORD +0 -41
  32. {dfindexeddb-20241105.dist-info → dfindexeddb-20260205.dist-info}/entry_points.txt +0 -0
  33. {dfindexeddb-20241105.dist-info → dfindexeddb-20260205.dist-info/licenses}/LICENSE +0 -0
  34. {dfindexeddb-20241105.dist-info → dfindexeddb-20260205.dist-info}/top_level.txt +0 -0
@@ -16,28 +16,30 @@
16
16
  import argparse
17
17
  import dataclasses
18
18
  import enum
19
- from datetime import datetime
20
19
  import json
21
20
  import pathlib
21
+ from datetime import datetime
22
+ from typing import Any
22
23
 
23
- from dfindexeddb import utils
24
- from dfindexeddb import version
24
+ from dfindexeddb import utils, version
25
25
  from dfindexeddb.indexeddb import types
26
26
  from dfindexeddb.indexeddb.chromium import blink
27
+ from dfindexeddb.indexeddb.chromium import sqlite
27
28
  from dfindexeddb.indexeddb.chromium import record as chromium_record
28
29
  from dfindexeddb.indexeddb.firefox import gecko
29
30
  from dfindexeddb.indexeddb.firefox import record as firefox_record
30
31
  from dfindexeddb.indexeddb.safari import record as safari_record
31
32
 
32
-
33
33
  _VALID_PRINTABLE_CHARACTERS = (
34
- ' abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' +
35
- '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~.')
34
+ " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
35
+ + "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~."
36
+ )
36
37
 
37
38
 
38
39
  class Encoder(json.JSONEncoder):
39
40
  """A JSON encoder class for dfindexeddb fields."""
40
- def default(self, o):
41
+
42
+ def default(self, o): # type: ignore[no-untyped-def]
41
43
  if dataclasses.is_dataclass(o):
42
44
  o_dict = utils.asdict(o)
43
45
  return o_dict
@@ -45,18 +47,18 @@ class Encoder(json.JSONEncoder):
45
47
  out = []
46
48
  for x in o:
47
49
  if chr(x) not in _VALID_PRINTABLE_CHARACTERS:
48
- out.append(f'\\x{x:02X}')
50
+ out.append(f"\\x{x:02X}")
49
51
  else:
50
52
  out.append(chr(x))
51
- return ''.join(out)
53
+ return "".join(out)
52
54
  if isinstance(o, datetime):
53
55
  return o.isoformat()
54
56
  if isinstance(o, types.Undefined):
55
- return '<undefined>'
57
+ return "<undefined>"
56
58
  if isinstance(o, types.JSArray):
57
59
  return o.__dict__
58
60
  if isinstance(o, types.Null):
59
- return '<null>'
61
+ return "<null>"
60
62
  if isinstance(o, set):
61
63
  return list(o)
62
64
  if isinstance(o, types.RegExp):
@@ -66,189 +68,348 @@ class Encoder(json.JSONEncoder):
66
68
  return json.JSONEncoder.default(self, o)
67
69
 
68
70
 
69
- def _Output(structure, output):
70
- """Helper method to output parsed structure to stdout."""
71
- if output == 'json':
71
+ def _Output(structure: Any, output: str) -> None:
72
+ """Helper method to output parsed structure to stdout.
73
+
74
+ Args:
75
+ structure: The structure to output.
76
+ output: The output format.
77
+ """
78
+ if output == "json":
72
79
  print(json.dumps(structure, indent=2, cls=Encoder))
73
- elif output == 'jsonl':
80
+ elif output == "jsonl":
74
81
  print(json.dumps(structure, cls=Encoder))
75
- elif output == 'repr':
82
+ elif output == "repr":
76
83
  print(structure)
77
84
 
78
85
 
79
- def BlinkCommand(args):
86
+ def BlinkCommand(args: argparse.Namespace) -> None:
80
87
  """The CLI for processing a file as a blink-encoded value."""
81
- with open(args.source, 'rb') as fd:
88
+ with open(args.source, "rb") as fd:
82
89
  buffer = fd.read()
83
90
  blink_value = blink.V8ScriptValueDecoder.FromBytes(buffer)
84
91
  _Output(blink_value, output=args.output)
85
92
 
86
93
 
87
- def GeckoCommand(args):
94
+ def GeckoCommand(args: argparse.Namespace) -> None:
88
95
  """The CLI for processing a file as a gecko-encoded value."""
89
- with open(args.source, 'rb') as fd:
96
+ with open(args.source, "rb") as fd:
90
97
  buffer = fd.read()
91
98
  blink_value = gecko.JSStructuredCloneDecoder.FromBytes(buffer)
92
99
  _Output(blink_value, output=args.output)
93
100
 
94
101
 
95
- def DbCommand(args):
102
+ def DbCommand(args: argparse.Namespace) -> None:
96
103
  """The CLI for processing a directory as IndexedDB."""
97
- if args.format in ('chrome', 'chromium'):
98
- for db_record in chromium_record.FolderReader(
99
- args.source).GetRecords(
100
- use_manifest=args.use_manifest,
101
- use_sequence_number=args.use_sequence_number):
102
- _Output(db_record, output=args.output)
103
- elif args.format == 'firefox':
104
- for db_record in firefox_record.FileReader(args.source).Records():
105
- _Output(db_record, output=args.output)
106
- elif args.format == 'safari':
107
- for db_record in safari_record.FileReader(args.source).Records():
108
- _Output(db_record, output=args.output)
109
-
110
-
111
- def LdbCommand(args):
104
+ if args.format in ("chrome", "chromium"):
105
+ if args.source.is_file():
106
+ if args.object_store_id is not None:
107
+ records = sqlite.DatabaseReader(
108
+ str(args.source)
109
+ ).RecordsByObjectStoreId(
110
+ args.object_store_id, include_raw_data=args.include_raw_data
111
+ )
112
+ else:
113
+ records = sqlite.DatabaseReader(str(args.source)).Records(
114
+ include_raw_data=args.include_raw_data
115
+ )
116
+ for chromium_db_record in records:
117
+ if args.filter_value is not None and args.filter_value not in str(
118
+ chromium_db_record.value
119
+ ):
120
+ continue
121
+ if args.filter_key is not None and args.filter_key not in str(
122
+ chromium_db_record.key.value
123
+ ):
124
+ continue
125
+ _Output(chromium_db_record, output=args.output)
126
+ else:
127
+ for chromium_leveldb_record in chromium_record.FolderReader(
128
+ args.source
129
+ ).GetRecords(
130
+ use_manifest=args.use_manifest,
131
+ use_sequence_number=args.use_sequence_number,
132
+ ):
133
+ if (
134
+ args.object_store_id is not None
135
+ and chromium_leveldb_record.object_store_id != args.object_store_id
136
+ ):
137
+ continue
138
+ if args.filter_value is not None and args.filter_value not in str(
139
+ chromium_leveldb_record.value
140
+ ):
141
+ continue
142
+ if args.filter_key is not None and args.filter_key not in str(
143
+ chromium_leveldb_record.key.value
144
+ ):
145
+ continue
146
+ _Output(chromium_leveldb_record, output=args.output)
147
+ elif args.format == "firefox":
148
+ if args.object_store_id is not None:
149
+ firefox_db_records = firefox_record.FileReader(
150
+ str(args.source)
151
+ ).RecordsByObjectStoreId(
152
+ args.object_store_id, include_raw_data=args.include_raw_data
153
+ )
154
+ else:
155
+ firefox_db_records = firefox_record.FileReader(str(args.source)).Records(
156
+ include_raw_data=args.include_raw_data
157
+ )
158
+
159
+ for firefox_db_record in firefox_db_records:
160
+ if args.filter_value is not None and args.filter_value not in str(
161
+ firefox_db_record.value
162
+ ):
163
+ continue
164
+ if args.filter_key is not None and args.filter_key not in str(
165
+ firefox_db_record.key.value
166
+ ):
167
+ continue
168
+ _Output(firefox_db_record, output=args.output)
169
+ elif args.format == "safari":
170
+ if args.object_store_id is not None:
171
+ safari_db_records = safari_record.FileReader(
172
+ str(args.source)
173
+ ).RecordsByObjectStoreId(
174
+ args.object_store_id, include_raw_data=args.include_raw_data
175
+ )
176
+ else:
177
+ safari_db_records = safari_record.FileReader(str(args.source)).Records(
178
+ include_raw_data=args.include_raw_data
179
+ )
180
+
181
+ for safari_db_record in safari_db_records:
182
+ if args.filter_value is not None and args.filter_value not in str(
183
+ safari_db_record.value
184
+ ):
185
+ continue
186
+ if args.filter_key is not None and args.filter_key not in str(
187
+ safari_db_record.key
188
+ ):
189
+ continue
190
+ _Output(safari_db_record, output=args.output)
191
+
192
+
193
+ def LdbCommand(args: argparse.Namespace) -> None:
112
194
  """The CLI for processing a LevelDB table (.ldb) file as IndexedDB."""
113
- for db_record in chromium_record.IndexedDBRecord.FromFile(args.source):
195
+ for db_record in chromium_record.ChromiumIndexedDBRecord.FromFile(
196
+ args.source
197
+ ):
198
+ if args.filter_value is not None and args.filter_value not in str(
199
+ db_record.value
200
+ ):
201
+ continue
202
+ if args.filter_key is not None and args.filter_key not in str(
203
+ db_record.key
204
+ ):
205
+ continue
114
206
  _Output(db_record, output=args.output)
115
207
 
116
208
 
117
- def LogCommand(args):
209
+ def LogCommand(args: argparse.Namespace) -> None:
118
210
  """The CLI for processing a LevelDB log file as IndexedDB."""
119
- for db_record in chromium_record.IndexedDBRecord.FromFile(args.source):
211
+ for db_record in chromium_record.ChromiumIndexedDBRecord.FromFile(
212
+ args.source
213
+ ):
214
+ if args.filter_value is not None and args.filter_value not in str(
215
+ db_record.value
216
+ ):
217
+ continue
218
+ if args.filter_key is not None and args.filter_key not in str(
219
+ db_record.key
220
+ ):
221
+ continue
120
222
  _Output(db_record, output=args.output)
121
223
 
122
224
 
123
- def App():
225
+ def App() -> None:
124
226
  """The CLI app entrypoint for dfindexeddb."""
125
227
  parser = argparse.ArgumentParser(
126
- prog='dfindexeddb',
127
- description='A cli tool for parsing IndexedDB files',
128
- epilog=f'Version {version.GetVersion()}')
228
+ prog="dfindexeddb",
229
+ description="A cli tool for parsing IndexedDB files",
230
+ epilog=f"Version {version.GetVersion()}",
231
+ )
129
232
 
130
233
  subparsers = parser.add_subparsers()
131
234
 
132
235
  parser_blink = subparsers.add_parser(
133
- 'blink', help='Parse a file as a blink-encoded value.')
236
+ "blink", help="Parse a file as a blink-encoded value."
237
+ )
134
238
  parser_blink.add_argument(
135
- '-s',
136
- '--source',
239
+ "-s",
240
+ "--source",
137
241
  required=True,
138
242
  type=pathlib.Path,
139
- help='The source file.')
243
+ help="The source file.",
244
+ )
140
245
  parser_blink.add_argument(
141
- '-o',
142
- '--output',
143
- choices=[
144
- 'json',
145
- 'jsonl',
146
- 'repr'],
147
- default='json',
148
- help='Output format. Default is json.')
246
+ "-o",
247
+ "--output",
248
+ choices=["json", "jsonl", "repr"],
249
+ default="json",
250
+ help="Output format. Default is json.",
251
+ )
149
252
  parser_blink.set_defaults(func=BlinkCommand)
150
253
 
151
254
  parser_gecko = subparsers.add_parser(
152
- 'gecko', help='Parse a file as a gecko-encoded value.')
255
+ "gecko", help="Parse a file as a gecko-encoded value."
256
+ )
153
257
  parser_gecko.add_argument(
154
- '-s',
155
- '--source',
258
+ "-s",
259
+ "--source",
156
260
  required=True,
157
261
  type=pathlib.Path,
158
- help='The source file.')
262
+ help="The source file.",
263
+ )
159
264
  parser_gecko.add_argument(
160
- '-o',
161
- '--output',
162
- choices=[
163
- 'json',
164
- 'jsonl',
165
- 'repr'],
166
- default='json',
167
- help='Output format. Default is json.')
265
+ "-o",
266
+ "--output",
267
+ choices=["json", "jsonl", "repr"],
268
+ default="json",
269
+ help="Output format. Default is json.",
270
+ )
168
271
  parser_gecko.set_defaults(func=GeckoCommand)
169
272
 
170
273
  parser_db = subparsers.add_parser(
171
- 'db', help='Parse a directory/file as IndexedDB.')
274
+ "db", help="Parse a directory/file as IndexedDB."
275
+ )
172
276
  parser_db.add_argument(
173
- '-s',
174
- '--source',
277
+ "-s",
278
+ "--source",
175
279
  required=True,
176
280
  type=pathlib.Path,
177
281
  help=(
178
- 'The source IndexedDB folder (for chrome/chromium) '
179
- 'or sqlite3 file (for firefox/safari).'))
282
+ "The source IndexedDB folder (for chrome/chromium) "
283
+ "or sqlite3 file (for firefox/safari)."
284
+ ),
285
+ )
180
286
  recover_group = parser_db.add_mutually_exclusive_group()
181
287
  recover_group.add_argument(
182
- '--use_manifest',
183
- action='store_true',
184
- help='Use manifest file to determine active/deleted records.')
288
+ "--use_manifest",
289
+ action="store_true",
290
+ help="Use manifest file to determine active/deleted records.",
291
+ )
185
292
  recover_group.add_argument(
186
- '--use_sequence_number',
187
- action='store_true',
293
+ "--use_sequence_number",
294
+ action="store_true",
188
295
  help=(
189
- 'Use sequence number and file offset to determine active/deleted '
190
- 'records.'))
296
+ "Use sequence number and file offset to determine active/deleted "
297
+ "records."
298
+ ),
299
+ )
191
300
  parser_db.add_argument(
192
- '--format',
301
+ "--format",
193
302
  required=True,
194
- choices=[
195
- 'chromium',
196
- 'chrome',
197
- 'firefox',
198
- 'safari'],
199
- help='The type of IndexedDB to parse.')
303
+ choices=["chromium", "chrome", "firefox", "safari"],
304
+ help="The type of IndexedDB to parse.",
305
+ )
306
+ parser_db.add_argument(
307
+ "--object_store_id",
308
+ type=int,
309
+ help="The object store ID to filter by.",
310
+ )
311
+ parser_db.add_argument(
312
+ "--include_raw_data",
313
+ action="store_true",
314
+ help="Include raw key and value in the output.",
315
+ )
316
+ parser_db.add_argument(
317
+ "-o",
318
+ "--output",
319
+ choices=["json", "jsonl", "repr"],
320
+ default="json",
321
+ help="Output format. Default is json.",
322
+ )
200
323
  parser_db.add_argument(
201
- '-o',
202
- '--output',
203
- choices=[
204
- 'json',
205
- 'jsonl',
206
- 'repr'],
207
- default='json',
208
- help='Output format. Default is json.')
324
+ "--filter_value",
325
+ type=str,
326
+ help=(
327
+ "Only output records where the value contains this string. "
328
+ "Values are normalized to strings before comparison."
329
+ ),
330
+ )
331
+ parser_db.add_argument(
332
+ "--filter_key",
333
+ type=str,
334
+ help=(
335
+ "Only output records where the key contains this string. "
336
+ "Keys are normalized to strings before comparison."
337
+ ),
338
+ )
209
339
  parser_db.set_defaults(func=DbCommand)
210
340
 
211
341
  parser_ldb = subparsers.add_parser(
212
- 'ldb',
213
- help='Parse a ldb file as IndexedDB.')
342
+ "ldb", help="Parse a ldb file as IndexedDB."
343
+ )
214
344
  parser_ldb.add_argument(
215
- '-s',
216
- '--source',
345
+ "-s",
346
+ "--source",
217
347
  required=True,
218
348
  type=pathlib.Path,
219
- help='The source .ldb file.')
349
+ help="The source .ldb file.",
350
+ )
351
+ parser_ldb.add_argument(
352
+ "-o",
353
+ "--output",
354
+ choices=["json", "jsonl", "repr"],
355
+ default="json",
356
+ help="Output format. Default is json.",
357
+ )
358
+ parser_ldb.add_argument(
359
+ "--filter_value",
360
+ type=str,
361
+ help=(
362
+ "Only output records where the value contains this string. "
363
+ "Values are normalized to strings before comparison."
364
+ ),
365
+ )
220
366
  parser_ldb.add_argument(
221
- '-o',
222
- '--output',
223
- choices=[
224
- 'json',
225
- 'jsonl',
226
- 'repr'],
227
- default='json',
228
- help='Output format. Default is json.')
367
+ "--filter_key",
368
+ type=str,
369
+ help=(
370
+ "Only output records where the key contains this string. "
371
+ "Keys are normalized to strings before comparison."
372
+ ),
373
+ )
229
374
  parser_ldb.set_defaults(func=LdbCommand)
230
375
 
231
376
  parser_log = subparsers.add_parser(
232
- 'log',
233
- help='Parse a log file as IndexedDB.')
377
+ "log", help="Parse a log file as IndexedDB."
378
+ )
234
379
  parser_log.add_argument(
235
- '-s', '--source',
380
+ "-s",
381
+ "--source",
236
382
  required=True,
237
383
  type=pathlib.Path,
238
- help='The source .log file.')
384
+ help="The source .log file.",
385
+ )
386
+ parser_log.add_argument(
387
+ "-o",
388
+ "--output",
389
+ choices=["json", "jsonl", "repr"],
390
+ default="json",
391
+ help="Output format. Default is json.",
392
+ )
239
393
  parser_log.add_argument(
240
- '-o',
241
- '--output',
242
- choices=[
243
- 'json',
244
- 'jsonl',
245
- 'repr'],
246
- default='json',
247
- help='Output format. Default is json.')
394
+ "--filter_value",
395
+ type=str,
396
+ help=(
397
+ "Only output records where the value contains this string. "
398
+ "Values are normalized to strings before comparison."
399
+ ),
400
+ )
401
+ parser_log.add_argument(
402
+ "--filter_key",
403
+ type=str,
404
+ help=(
405
+ "Only output records where the key contains this string. "
406
+ "Keys are normalized to strings before comparison."
407
+ ),
408
+ )
248
409
  parser_log.set_defaults(func=LogCommand)
249
410
 
250
- args = parser.parse_args()
251
- if hasattr(args, 'func'):
411
+ args: argparse.Namespace = parser.parse_args()
412
+ if hasattr(args, "func"):
252
413
  args.func(args)
253
414
  else:
254
415
  parser.print_help()
@@ -18,6 +18,7 @@ from enum import IntEnum
18
18
 
19
19
  class IndexedDBKeyType(IntEnum):
20
20
  """IndexedDB Key Types."""
21
+
21
22
  TERMINATOR = 0
22
23
  FLOAT = 0x10
23
24
  DATE = 0x20
@@ -38,6 +39,7 @@ THREE_BYTE_SHIFT = 6
38
39
 
39
40
  class StructuredDataType(IntEnum):
40
41
  """Structured Data Types."""
42
+
41
43
  FLOAT_MAX = 0xFFF00000
42
44
  HEADER = 0xFFF10000
43
45
  NULL = 0xFFFF0000
@@ -45,7 +47,7 @@ class StructuredDataType(IntEnum):
45
47
  BOOLEAN = 0xFFFF0002
46
48
  INT32 = 0xFFFF0003
47
49
  STRING = 0xFFFF0004
48
- DATE_OBJECT = 0xFFFF0005
50
+ DATE_OBJECT = 0xFFFF0005
49
51
  REGEXP_OBJECT = 0xFFFF0006
50
52
  ARRAY_OBJECT = 0xFFFF0007
51
53
  OBJECT_OBJECT = 0xFFFF0008
@@ -53,8 +55,8 @@ class StructuredDataType(IntEnum):
53
55
  BOOLEAN_OBJECT = 0xFFFF000A
54
56
  STRING_OBJECT = 0xFFFF000B
55
57
  NUMBER_OBJECT = 0xFFFF000C
56
- BACK_REFERENCE_OBJECT = 0xFFFF000D
57
- DO_NOT_USE_1 = 0xFFFF000E
58
+ BACK_REFERENCE_OBJECT = 0xFFFF000D
59
+ DO_NOT_USE_1 = 0xFFFF000E
58
60
  DO_NOT_USE_2 = 0xFFFF000F
59
61
  TYPED_ARRAY_OBJECT_V2 = 0xFFFF0010
60
62
  MAP_OBJECT = 0xFFFF0011
@@ -95,6 +97,7 @@ class StructuredDataType(IntEnum):
95
97
 
96
98
  class StructuredCloneTags(IntEnum):
97
99
  """Structured Clone Tags."""
100
+
98
101
  BLOB = 0xFFFF8001
99
102
  FILE_WITHOUT_LASTMODIFIEDDATE = 0xFFFF8002
100
103
  FILELIST = 0xFFFF8003
@@ -140,4 +143,4 @@ class StructuredCloneTags(IntEnum):
140
143
  ENCODEDAUDIOCHUNK = 0xFFFF8031
141
144
 
142
145
 
143
- FRAME_HEADER = b'\xff\x06\x00\x00sNaPpY'
146
+ FRAME_HEADER = b"\xff\x06\x00\x00sNaPpY"