dfindexeddb 20240501__tar.gz → 20241031__tar.gz

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 (47) hide show
  1. {dfindexeddb-20240501/dfindexeddb.egg-info → dfindexeddb-20241031}/PKG-INFO +42 -15
  2. {dfindexeddb-20240501 → dfindexeddb-20241031}/README.md +38 -14
  3. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/chromium/blink.py +5 -0
  4. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/chromium/record.py +11 -6
  5. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/chromium/v8.py +30 -10
  6. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/cli.py +52 -9
  7. dfindexeddb-20241031/dfindexeddb/indexeddb/firefox/definitions.py +143 -0
  8. dfindexeddb-20241031/dfindexeddb/indexeddb/firefox/gecko.py +600 -0
  9. dfindexeddb-20241031/dfindexeddb/indexeddb/firefox/record.py +180 -0
  10. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/safari/webkit.py +52 -20
  11. dfindexeddb-20241031/dfindexeddb/indexeddb/types.py +71 -0
  12. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/cli.py +69 -6
  13. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/descriptor.py +2 -1
  14. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/log.py +9 -3
  15. dfindexeddb-20241031/dfindexeddb/leveldb/plugins/__init__.py +17 -0
  16. dfindexeddb-20241031/dfindexeddb/leveldb/plugins/chrome_notifications.py +135 -0
  17. dfindexeddb-20241031/dfindexeddb/leveldb/plugins/interface.py +36 -0
  18. dfindexeddb-20241031/dfindexeddb/leveldb/plugins/manager.py +75 -0
  19. dfindexeddb-20241031/dfindexeddb/leveldb/plugins/notification_database_data_pb2.py +38 -0
  20. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/record.py +33 -1
  21. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/utils.py +35 -1
  22. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/version.py +1 -1
  23. {dfindexeddb-20240501 → dfindexeddb-20241031/dfindexeddb.egg-info}/PKG-INFO +42 -15
  24. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb.egg-info/SOURCES.txt +10 -1
  25. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb.egg-info/requires.txt +4 -0
  26. {dfindexeddb-20240501 → dfindexeddb-20241031}/pyproject.toml +8 -4
  27. {dfindexeddb-20240501 → dfindexeddb-20241031}/AUTHORS +0 -0
  28. {dfindexeddb-20240501 → dfindexeddb-20241031}/LICENSE +0 -0
  29. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/__init__.py +0 -0
  30. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/errors.py +0 -0
  31. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/__init__.py +0 -0
  32. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/chromium/__init__.py +0 -0
  33. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/chromium/definitions.py +0 -0
  34. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/firefox/__init__.py +0 -0
  35. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/safari/__init__.py +0 -0
  36. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/safari/definitions.py +0 -0
  37. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/safari/record.py +0 -0
  38. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/indexeddb/utils.py +0 -0
  39. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/__init__.py +0 -0
  40. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/definitions.py +0 -0
  41. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/ldb.py +0 -0
  42. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb/leveldb/utils.py +0 -0
  43. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb.egg-info/dependency_links.txt +0 -0
  44. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb.egg-info/entry_points.txt +0 -0
  45. {dfindexeddb-20240501 → dfindexeddb-20241031}/dfindexeddb.egg-info/top_level.txt +0 -0
  46. {dfindexeddb-20240501 → dfindexeddb-20241031}/setup.cfg +0 -0
  47. {dfindexeddb-20240501 → dfindexeddb-20241031}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dfindexeddb
3
- Version: 20240501
3
+ Version: 20241031
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>
@@ -219,6 +219,9 @@ License-File: LICENSE
219
219
  License-File: AUTHORS
220
220
  Requires-Dist: python-snappy==0.6.1
221
221
  Requires-Dist: zstd==1.5.5.1
222
+ Provides-Extra: plugins
223
+ Requires-Dist: protobuf; extra == "plugins"
224
+ Requires-Dist: dfdatetime; extra == "plugins"
222
225
 
223
226
  # dfIndexeddb
224
227
 
@@ -227,8 +230,7 @@ analysis of IndexedDB and LevelDB files.
227
230
 
228
231
  It parses LevelDB, IndexedDB and JavaScript structures from these files without
229
232
  requiring native libraries. (Note: only a subset of IndexedDB key types and
230
- JavaScript types for Safari and Chromium-based browsers are currently supported.
231
- Firefox is under development).
233
+ JavaScript types for Firefox, Safari and Chromium-based browsers are currently supported).
232
234
 
233
235
  The content of IndexedDB files is dependent on what a web application stores
234
236
  locally/offline using the web browser's
@@ -255,6 +257,12 @@ include:
255
257
  $ pip install dfindexeddb
256
258
  ```
257
259
 
260
+ To also install the dependencies for leveldb/indexeddb plugins, run
261
+ ```
262
+ $ pip install 'dfindexeddb[plugins]'
263
+ ```
264
+
265
+
258
266
  ## Installation from source
259
267
 
260
268
  1. [Linux] Install the snappy compression development package
@@ -273,6 +281,11 @@ include:
273
281
  $ pip install .
274
282
  ```
275
283
 
284
+ To also install the dependencies for leveldb/indexeddb plugins, run
285
+ ```
286
+ $ pip install '.[plugins]'
287
+ ```
288
+
276
289
  ## Usage
277
290
 
278
291
  Two CLI tools for parsing IndexedDB/LevelDB files are available after
@@ -299,6 +312,13 @@ options:
299
312
 
300
313
  #### Examples:
301
314
 
315
+ To parse IndexedDB records from an sqlite file for Firefox and output the
316
+ results as JSON, use the following command:
317
+
318
+ ```
319
+ dfindexeddb db -s SOURCE --format firefox -o json
320
+ ```
321
+
302
322
  To parse IndexedDB records from an sqlite file for Safari and output the
303
323
  results as JSON-L, use the following command:
304
324
 
@@ -359,7 +379,15 @@ options:
359
379
  To parse records from a LevelDB folder, use the following command:
360
380
 
361
381
  ```
362
- dfindexeddb db -s SOURCE
382
+ dfleveldb db -s SOURCE
383
+ ```
384
+
385
+ To parse records from a LevelDB folder, and use the sequence number to
386
+ determine recovered records and output as JSON, use the
387
+ following command:
388
+
389
+ ```
390
+ dfleveldb db -s SOURCE --use_sequence_number
363
391
  ```
364
392
 
365
393
  To parse blocks / physical records/ write batches / internal key records from a
@@ -383,15 +411,14 @@ following command:
383
411
 
384
412
  ```
385
413
  $ dfleveldb descriptor -s SOURCE [-o {json,jsonl,repr}] [-t {blocks,physical_records,versionedit} | -v]
386
-
387
- options:
388
- -h, --help show this help message and exit
389
- -s SOURCE, --source SOURCE
390
- The source leveldb file
391
- -o {json,jsonl,repr}, --output {json,jsonl,repr}
392
- Output format. Default is json
393
- -t {blocks,physical_records,versionedit}, --structure_type {blocks,physical_records,versionedit}
394
- Parses the specified structure. Default is versionedit.
395
- -v, --version_history
396
- Parses the leveldb version history.
397
414
  ```
415
+
416
+ #### Plugins
417
+
418
+ To apply a plugin parser for a leveldb file/folder, add the
419
+ `--plugin [Plugin Name]` argument. Currently, there is support for the
420
+ following artifacts:
421
+
422
+ | Plugin Name | Artifact Name |
423
+ | -------- | ------- |
424
+ | `ChromeNotificationRecord` | Chrome/Chromium Notifications |
@@ -5,8 +5,7 @@ analysis of IndexedDB and LevelDB files.
5
5
 
6
6
  It parses LevelDB, IndexedDB and JavaScript structures from these files without
7
7
  requiring native libraries. (Note: only a subset of IndexedDB key types and
8
- JavaScript types for Safari and Chromium-based browsers are currently supported.
9
- Firefox is under development).
8
+ JavaScript types for Firefox, Safari and Chromium-based browsers are currently supported).
10
9
 
11
10
  The content of IndexedDB files is dependent on what a web application stores
12
11
  locally/offline using the web browser's
@@ -33,6 +32,12 @@ include:
33
32
  $ pip install dfindexeddb
34
33
  ```
35
34
 
35
+ To also install the dependencies for leveldb/indexeddb plugins, run
36
+ ```
37
+ $ pip install 'dfindexeddb[plugins]'
38
+ ```
39
+
40
+
36
41
  ## Installation from source
37
42
 
38
43
  1. [Linux] Install the snappy compression development package
@@ -51,6 +56,11 @@ include:
51
56
  $ pip install .
52
57
  ```
53
58
 
59
+ To also install the dependencies for leveldb/indexeddb plugins, run
60
+ ```
61
+ $ pip install '.[plugins]'
62
+ ```
63
+
54
64
  ## Usage
55
65
 
56
66
  Two CLI tools for parsing IndexedDB/LevelDB files are available after
@@ -77,6 +87,13 @@ options:
77
87
 
78
88
  #### Examples:
79
89
 
90
+ To parse IndexedDB records from an sqlite file for Firefox and output the
91
+ results as JSON, use the following command:
92
+
93
+ ```
94
+ dfindexeddb db -s SOURCE --format firefox -o json
95
+ ```
96
+
80
97
  To parse IndexedDB records from an sqlite file for Safari and output the
81
98
  results as JSON-L, use the following command:
82
99
 
@@ -137,7 +154,15 @@ options:
137
154
  To parse records from a LevelDB folder, use the following command:
138
155
 
139
156
  ```
140
- dfindexeddb db -s SOURCE
157
+ dfleveldb db -s SOURCE
158
+ ```
159
+
160
+ To parse records from a LevelDB folder, and use the sequence number to
161
+ determine recovered records and output as JSON, use the
162
+ following command:
163
+
164
+ ```
165
+ dfleveldb db -s SOURCE --use_sequence_number
141
166
  ```
142
167
 
143
168
  To parse blocks / physical records/ write batches / internal key records from a
@@ -161,15 +186,14 @@ following command:
161
186
 
162
187
  ```
163
188
  $ dfleveldb descriptor -s SOURCE [-o {json,jsonl,repr}] [-t {blocks,physical_records,versionedit} | -v]
164
-
165
- options:
166
- -h, --help show this help message and exit
167
- -s SOURCE, --source SOURCE
168
- The source leveldb file
169
- -o {json,jsonl,repr}, --output {json,jsonl,repr}
170
- Output format. Default is json
171
- -t {blocks,physical_records,versionedit}, --structure_type {blocks,physical_records,versionedit}
172
- Parses the specified structure. Default is versionedit.
173
- -v, --version_history
174
- Parses the leveldb version history.
175
189
  ```
190
+
191
+ #### Plugins
192
+
193
+ To apply a plugin parser for a leveldb file/folder, add the
194
+ `--plugin [Plugin Name]` argument. Currently, there is support for the
195
+ following artifacts:
196
+
197
+ | Plugin Name | Artifact Name |
198
+ | -------- | ------- |
199
+ | `ChromeNotificationRecord` | Chrome/Chromium Notifications |
@@ -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)
@@ -1275,14 +1275,17 @@ class ExternalObjectEntry(utils.FromDecoderMixin):
1275
1275
  filename = None
1276
1276
  last_modified = None
1277
1277
  token = None
1278
- elif (object_type ==
1279
- 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
1280
1284
  blob_number = None
1281
1285
  mime_type = None
1282
1286
  size = None
1283
1287
  filename = None
1284
1288
  last_modified = None
1285
- _, token = decoder.DecodeBlobWithLength()
1286
1289
 
1287
1290
  return cls(offset=base_offset + offset, object_type=object_type,
1288
1291
  blob_number=blob_number, mime_type=mime_type, size=size,
@@ -1417,20 +1420,22 @@ class FolderReader:
1417
1420
 
1418
1421
  def GetRecords(
1419
1422
  self,
1420
- use_manifest: bool = False
1423
+ use_manifest: bool = False,
1424
+ use_sequence_number: bool = False
1421
1425
  ) -> Generator[IndexedDBRecord, None, None]:
1422
1426
  """Yield LevelDBRecords.
1423
1427
 
1424
1428
  Args:
1425
1429
  use_manifest: True to use the current manifest in the folder as a means to
1426
1430
  find the active file set.
1427
-
1431
+ use_sequence_number: True to use the sequence number to determine the
1428
1432
  Yields:
1429
1433
  IndexedDBRecord.
1430
1434
  """
1431
1435
  leveldb_folder_reader = record.FolderReader(self.foldername)
1432
1436
  for leveldb_record in leveldb_folder_reader.GetRecords(
1433
- use_manifest=use_manifest):
1437
+ use_manifest=use_manifest,
1438
+ use_sequence_number=use_sequence_number):
1434
1439
  try:
1435
1440
  yield IndexedDBRecord.FromLevelDBRecord(
1436
1441
  leveldb_record)
@@ -13,6 +13,8 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  """Parsers for v8 javascript serialized objects."""
16
+ from __future__ import annotations
17
+
16
18
  from dataclasses import dataclass
17
19
  from datetime import datetime
18
20
  import io
@@ -25,8 +27,8 @@ from dfindexeddb.indexeddb.chromium import definitions
25
27
 
26
28
 
27
29
  @dataclass
28
- class BufferArrayView:
29
- """A parsed Javascript BufferArrayView."""
30
+ class ArrayBufferView:
31
+ """A parsed Javascript ArrayBufferView."""
30
32
  buffer: bytes
31
33
  tag: definitions.V8ArrayBufferViewTag
32
34
  offset: int
@@ -34,15 +36,23 @@ class BufferArrayView:
34
36
  flags: int
35
37
 
36
38
 
37
- class JSArray(list):
39
+ @dataclass
40
+ class JSArray:
38
41
  """A parsed Javascript array.
39
42
 
40
- This is a wrapper around a standard Python list to allow assigning arbitrary
41
- properties as is possible in the Javascript equivalent.
43
+ A Javascript array behaves like a Python list but allows assigning arbitrary
44
+ properties. The array is stored in the attribute __array__.
42
45
  """
46
+ def __init__(self):
47
+ self.__array__ = []
48
+
49
+ def Append(self, element: Any):
50
+ """Appends a new element to the array."""
51
+ self.__array__.append(element)
43
52
 
44
53
  def __repr__(self):
45
- array_entries = ", ".join([str(entry) for entry in list(self)])
54
+ array_entries = ", ".join(
55
+ [str(entry) for entry in list(self.__array__)])
46
56
  properties = ", ".join(
47
57
  f'{key}: {value}' for key, value in self.properties.items())
48
58
  return f'[{array_entries}, {properties}]'
@@ -52,6 +62,11 @@ class JSArray(list):
52
62
  """Returns the object properties."""
53
63
  return self.__dict__
54
64
 
65
+ def __eq__(self, other: JSArray):
66
+ return (
67
+ self.__array__ == other.__array__
68
+ and self.properties == other.properties)
69
+
55
70
  def __contains__(self, item):
56
71
  return item in self.__dict__
57
72
 
@@ -194,6 +209,7 @@ class ValueDeserializer:
194
209
  if tag and tag == definitions.V8SerializationTag.ARRAY_BUFFER_VIEW:
195
210
  self._ConsumeTag(tag)
196
211
  result = self._ReadJSArrayBufferView(result)
212
+ self.objects[self._GetNextId()] = result
197
213
  return result
198
214
 
199
215
  def _ReadObjectInternal(self) -> Tuple[definitions.V8SerializationTag, Any]:
@@ -385,7 +401,10 @@ class ValueDeserializer:
385
401
  while self._PeekTag() != end_tag:
386
402
  key = self._ReadObject()
387
403
  value = self._ReadObject()
388
- js_object[key] = value
404
+ if isinstance(js_object, dict):
405
+ js_object[key] = value
406
+ else:
407
+ js_object.properties[key] = value
389
408
  num_properties += 1
390
409
  self._ConsumeTag(end_tag)
391
410
  return num_properties
@@ -407,7 +426,7 @@ class ValueDeserializer:
407
426
  js_array = JSArray()
408
427
  _, length = self.decoder.DecodeUint32Varint()
409
428
  for _ in range(length):
410
- js_array.append(Undefined())
429
+ js_array.Append(Undefined())
411
430
 
412
431
  num_properties = self._ReadJSObjectProperties(
413
432
  js_array.__dict__, definitions.V8SerializationTag.END_SPARSE_JS_ARRAY)
@@ -440,7 +459,7 @@ class ValueDeserializer:
440
459
 
441
460
  if self.version < 11 and isinstance(array_object, Undefined):
442
461
  continue
443
- js_array.append(array_object)
462
+ js_array.Append(array_object)
444
463
 
445
464
  num_properties = self._ReadJSObjectProperties(
446
465
  js_array.__dict__, definitions.V8SerializationTag.END_DENSE_JS_ARRAY)
@@ -571,6 +590,7 @@ class ValueDeserializer:
571
590
  if is_resizable:
572
591
  _, max_byte_length = self.decoder.DecodeUint32Varint()
573
592
  if byte_length > max_byte_length:
593
+ self.objects[next_id] = array_buffer
574
594
  return array_buffer
575
595
  if byte_length:
576
596
  _, array_buffer = self.decoder.ReadBytes(byte_length)
@@ -589,7 +609,7 @@ class ValueDeserializer:
589
609
  else:
590
610
  flags = 0
591
611
 
592
- return BufferArrayView(
612
+ return ArrayBufferView(
593
613
  buffer=buffer,
594
614
  tag=definitions.V8ArrayBufferViewTag(tag[0]),
595
615
  offset=byte_offset,
@@ -20,10 +20,13 @@ from datetime import datetime
20
20
  import json
21
21
  import pathlib
22
22
 
23
+ from dfindexeddb import utils
23
24
  from dfindexeddb import version
24
25
  from dfindexeddb.indexeddb.chromium import blink
25
26
  from dfindexeddb.indexeddb.chromium import record as chromium_record
26
27
  from dfindexeddb.indexeddb.chromium import v8
28
+ from dfindexeddb.indexeddb.firefox import gecko
29
+ from dfindexeddb.indexeddb.firefox import record as firefox_record
27
30
  from dfindexeddb.indexeddb.safari import record as safari_record
28
31
 
29
32
 
@@ -36,9 +39,9 @@ class Encoder(json.JSONEncoder):
36
39
  """A JSON encoder class for dfindexeddb fields."""
37
40
  def default(self, o):
38
41
  if dataclasses.is_dataclass(o):
39
- o_dict = dataclasses.asdict(o)
42
+ o_dict = utils.asdict(o)
40
43
  return o_dict
41
- if isinstance(o, bytes):
44
+ if isinstance(o, (bytes, bytearray)):
42
45
  out = []
43
46
  for x in o:
44
47
  if chr(x) not in _VALID_PRINTABLE_CHARACTERS:
@@ -50,6 +53,8 @@ class Encoder(json.JSONEncoder):
50
53
  return o.isoformat()
51
54
  if isinstance(o, v8.Undefined):
52
55
  return "<undefined>"
56
+ if isinstance(o, v8.JSArray):
57
+ return o.__dict__
53
58
  if isinstance(o, v8.Null):
54
59
  return "<null>"
55
60
  if isinstance(o, set):
@@ -72,18 +77,31 @@ def _Output(structure, output):
72
77
 
73
78
 
74
79
  def BlinkCommand(args):
75
- """The CLI for processing a file as a blink value."""
80
+ """The CLI for processing a file as a blink-encoded value."""
76
81
  with open(args.source, 'rb') as fd:
77
82
  buffer = fd.read()
78
83
  blink_value = blink.V8ScriptValueDecoder.FromBytes(buffer)
79
84
  _Output(blink_value, output=args.output)
80
85
 
81
86
 
87
+ def GeckoCommand(args):
88
+ """The CLI for processing a file as a gecko-encoded value."""
89
+ with open(args.source, 'rb') as fd:
90
+ buffer = fd.read()
91
+ blink_value = gecko.JSStructuredCloneDecoder.FromBytes(buffer)
92
+ _Output(blink_value, output=args.output)
93
+
94
+
82
95
  def DbCommand(args):
83
96
  """The CLI for processing a directory as IndexedDB."""
84
97
  if args.format in ('chrome', 'chromium'):
85
98
  for db_record in chromium_record.FolderReader(
86
- args.source).GetRecords(use_manifest=args.use_manifest):
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():
87
105
  _Output(db_record, output=args.output)
88
106
  elif args.format == 'safari':
89
107
  for db_record in safari_record.FileReader(args.source).Records():
@@ -130,6 +148,24 @@ def App():
130
148
  help='Output format. Default is json')
131
149
  parser_blink.set_defaults(func=BlinkCommand)
132
150
 
151
+ parser_gecko = subparsers.add_parser(
152
+ 'gecko', help='Parse a file as a gecko-encoded value.')
153
+ parser_gecko.add_argument(
154
+ '-s', '--source',
155
+ required=True,
156
+ type=pathlib.Path,
157
+ help='The source file.')
158
+ parser_gecko.add_argument(
159
+ '-o',
160
+ '--output',
161
+ choices=[
162
+ 'json',
163
+ 'jsonl',
164
+ 'repr'],
165
+ default='json',
166
+ help='Output format. Default is json')
167
+ parser_gecko.set_defaults(func=GeckoCommand)
168
+
133
169
  parser_db = subparsers.add_parser(
134
170
  'db', help='Parse a directory as IndexedDB.')
135
171
  parser_db.add_argument(
@@ -139,15 +175,22 @@ def App():
139
175
  help=(
140
176
  'The source IndexedDB folder (for chrome/chromium) '
141
177
  'or file (for safari).'))
178
+ recover_group = parser_db.add_mutually_exclusive_group()
179
+ recover_group.add_argument(
180
+ '--use_manifest',
181
+ action='store_true',
182
+ help='Use manifest file to determine active/deleted records.')
183
+ recover_group.add_argument(
184
+ '--use_sequence_number',
185
+ action='store_true',
186
+ help=(
187
+ 'Use sequence number and file offset to determine active/deleted '
188
+ 'records.'))
142
189
  parser_db.add_argument(
143
190
  '--format',
144
191
  required=True,
145
- choices=['chromium', 'chrome', 'safari'],
192
+ choices=['chromium', 'chrome', 'firefox', 'safari'],
146
193
  help='The type of IndexedDB to parse.')
147
- parser_db.add_argument(
148
- '--use_manifest',
149
- action='store_true',
150
- help='Use manifest file to determine active/deleted records.')
151
194
  parser_db.add_argument(
152
195
  '-o',
153
196
  '--output',
@@ -0,0 +1,143 @@
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 Firefox IndexedDB."""
16
+ from enum import IntEnum
17
+
18
+
19
+ class IndexedDBKeyType(IntEnum):
20
+ """IndexedDB Key Types."""
21
+ TERMINATOR = 0
22
+ FLOAT = 0x10
23
+ DATE = 0x20
24
+ STRING = 0x30
25
+ BINARY = 0x40
26
+ ARRAY = 0x50
27
+
28
+
29
+ MAX_ARRAY_COLLAPSE = 3
30
+ MAX_RECURSION_DEPTH = 64
31
+ MAX_LENGTH = (1 << 30) - 2
32
+ ONE_BYTE_LIMIT = 0x7E
33
+ TWO_BYTE_LIMIT = 0x3FFF + 0x7F
34
+ ONE_BYTE_ADJUST = 1
35
+ TWO_BYTE_ADJUST = -0x7F
36
+ THREE_BYTE_SHIFT = 6
37
+
38
+
39
+ class StructuredDataType(IntEnum):
40
+ """Structured Data Types."""
41
+ FLOAT_MAX = 0xFFF00000
42
+ HEADER = 0xFFF10000
43
+ NULL = 0xFFFF0000
44
+ UNDEFINED = 0xFFFF0001
45
+ BOOLEAN = 0xFFFF0002
46
+ INT32 = 0xFFFF0003
47
+ STRING = 0xFFFF0004
48
+ DATE_OBJECT = 0xFFFF0005
49
+ REGEXP_OBJECT = 0xFFFF0006
50
+ ARRAY_OBJECT = 0xFFFF0007
51
+ OBJECT_OBJECT = 0xFFFF0008
52
+ ARRAY_BUFFER_OBJECT_V2 = 0xFFFF0009
53
+ BOOLEAN_OBJECT = 0xFFFF000A
54
+ STRING_OBJECT = 0xFFFF000B
55
+ NUMBER_OBJECT = 0xFFFF000C
56
+ BACK_REFERENCE_OBJECT = 0xFFFF000D
57
+ DO_NOT_USE_1 = 0xFFFF000E
58
+ DO_NOT_USE_2 = 0xFFFF000F
59
+ TYPED_ARRAY_OBJECT_V2 = 0xFFFF0010
60
+ MAP_OBJECT = 0xFFFF0011
61
+ SET_OBJECT = 0xFFFF0012
62
+ END_OF_KEYS = 0xFFFF0013
63
+ DO_NOT_USE_3 = 0xFFFF0014
64
+ DATA_VIEW_OBJECT_V2 = 0xFFFF0015
65
+ SAVED_FRAME_OBJECT = 0xFFFF0016
66
+ JSPRINCIPALS = 0xFFFF0017
67
+ NULL_JSPRINCIPALS = 0xFFFF0018
68
+ RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_SYSTEM = 0xFFFF0019
69
+ RECONSTRUCTED_SAVED_FRAME_PRINCIPALS_IS_NOT_SYSTEM = 0xFFFF001A
70
+ SHARED_ARRAY_BUFFER_OBJECT = 0xFFFF001B
71
+ SHARED_WASM_MEMORY_OBJECT = 0xFFFF001C
72
+ BIGINT = 0xFFFF001D
73
+ BIGINT_OBJECT = 0xFFFF001E
74
+ ARRAY_BUFFER_OBJECT = 0xFFFF001F
75
+ TYPED_ARRAY_OBJECT = 0xFFFF0020
76
+ DATA_VIEW_OBJECT = 0xFFFF0021
77
+ ERROR_OBJECT = 0xFFFF0022
78
+ RESIZABLE_ARRAY_BUFFER_OBJECT = 0xFFFF0023
79
+ GROWABLE_SHARED_ARRAY_BUFFER_OBJECT = 0xFFFF0024
80
+ TYPED_ARRAY_V1_INT8 = 0xFFFF0100
81
+ TYPED_ARRAY_V1_UINT8 = 0xFFFF0101
82
+ TYPED_ARRAY_V1_INT16 = 0xFFFF0102
83
+ TYPED_ARRAY_V1_UINT16 = 0xFFFF0103
84
+ TYPED_ARRAY_V1_INT32 = 0xFFFF0104
85
+ TYPED_ARRAY_V1_UINT32 = 0xFFFF0105
86
+ TYPED_ARRAY_V1_FLOAT32 = 0xFFFF0106
87
+ TYPED_ARRAY_V1_FLOAT64 = 0xFFFF0107
88
+ TYPED_ARRAY_V1_UINT8_CLAMPED = 0xFFFF0108
89
+ TRANSFER_MAP_HEADER = 0xFFFF0200
90
+ TRANSFER_MAP_PENDING_ENTRY = 0xFFFF0201
91
+ TRANSFER_MAP_ARRAY_BUFFER = 0xFFFF0202
92
+ TRANSFER_MAP_STORED_ARRAY_BUFFER = 0xFFFF0203
93
+ TRANSFER_MAP_END_OF_BUILTIN_TYPES = 0xFFFF0204
94
+
95
+
96
+ class StructuredCloneTags(IntEnum):
97
+ """Structured Clone Tags."""
98
+ BLOB = 0xFFFF8001
99
+ FILE_WITHOUT_LASTMODIFIEDDATE = 0xFFFF8002
100
+ FILELIST = 0xFFFF8003
101
+ MUTABLEFILE = 0xFFFF8004
102
+ FILE = 0xFFFF8005
103
+ WASM_MODULE = 0xFFFF8006
104
+ IMAGEDATA = 0xFFFF8007
105
+ DOMPOINT = 0xFFFF8008
106
+ DOMPOINTREADONLY = 0xFFFF8009
107
+ CRYPTOKEY = 0xFFFF800A
108
+ NULL_PRINCIPAL = 0xFFFF800B
109
+ SYSTEM_PRINCIPAL = 0xFFFF800C
110
+ CONTENT_PRINCIPAL = 0xFFFF800D
111
+ DOMQUAD = 0xFFFF800E
112
+ RTCCERTIFICATE = 0xFFFF800F
113
+ DOMRECT = 0xFFFF8010
114
+ DOMRECTREADONLY = 0xFFFF8011
115
+ EXPANDED_PRINCIPAL = 0xFFFF8012
116
+ DOMMATRIX = 0xFFFF8013
117
+ URLSEARCHPARAMS = 0xFFFF8014
118
+ DOMMATRIXREADONLY = 0xFFFF8015
119
+ DOMEXCEPTION = 0xFFFF80016
120
+ EMPTY_SLOT_9 = 0xFFFF8017
121
+ STRUCTUREDCLONETESTER = 0xFFFF8018
122
+ FILESYSTEMHANDLE = 0xFFFF8019
123
+ FILESYSTEMFILEHANDLE = 0xFFFF801A
124
+ FILESYSTEMDIRECTORYHANDLE = 0xFFFF801B
125
+ IMAGEBITMAP = 0xFFFF801C
126
+ MAP_MESSAGEPORT = 0xFFFF801D
127
+ FORMDATA = 0xFFFF801E
128
+ CANVAS = 0xFFFF801F # This tag is for OffscreenCanvas.
129
+ DIRECTORY = 0xFFFF8020
130
+ INPUTSTREAM = 0xFFFF8021
131
+ STRUCTURED_CLONE_HOLDER = 0xFFFF8022
132
+ BROWSING_CONTEXT = 0xFFFF8023
133
+ CLONED_ERROR_OBJECT = 0xFFFF8024
134
+ READABLESTREAM = 0xFFFF8025
135
+ WRITABLESTREAM = 0xFFFF8026
136
+ TRANSFORMSTREAM = 0xFFFF8027
137
+ VIDEOFRAME = 0xFFFF8028
138
+ ENCODEDVIDEOCHUNK = 0xFFFF8029
139
+ AUDIODATA = 0xFFFF8030
140
+ ENCODEDAUDIOCHUNK = 0xFFFF8031
141
+
142
+
143
+ FRAME_HEADER = b'\xff\x06\x00\x00sNaPpY'