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.
Files changed (28) hide show
  1. dfindexeddb/indexeddb/chromium/blink.py +5 -0
  2. dfindexeddb/indexeddb/chromium/record.py +11 -6
  3. dfindexeddb/indexeddb/chromium/v8.py +30 -10
  4. dfindexeddb/indexeddb/cli.py +52 -9
  5. dfindexeddb/indexeddb/firefox/definitions.py +143 -0
  6. dfindexeddb/indexeddb/firefox/gecko.py +600 -0
  7. dfindexeddb/indexeddb/firefox/record.py +180 -0
  8. dfindexeddb/indexeddb/safari/webkit.py +52 -20
  9. dfindexeddb/indexeddb/types.py +71 -0
  10. dfindexeddb/leveldb/cli.py +69 -6
  11. dfindexeddb/leveldb/descriptor.py +2 -1
  12. dfindexeddb/leveldb/log.py +9 -3
  13. dfindexeddb/leveldb/plugins/__init__.py +17 -0
  14. dfindexeddb/leveldb/plugins/chrome_notifications.py +135 -0
  15. dfindexeddb/leveldb/plugins/interface.py +36 -0
  16. dfindexeddb/leveldb/plugins/manager.py +75 -0
  17. dfindexeddb/leveldb/plugins/notification_database_data_pb2.py +38 -0
  18. dfindexeddb/leveldb/record.py +33 -1
  19. dfindexeddb/utils.py +35 -1
  20. dfindexeddb/version.py +1 -1
  21. {dfindexeddb-20240501.dist-info → dfindexeddb-20241031.dist-info}/METADATA +42 -15
  22. dfindexeddb-20241031.dist-info/RECORD +41 -0
  23. {dfindexeddb-20240501.dist-info → dfindexeddb-20241031.dist-info}/WHEEL +1 -1
  24. dfindexeddb-20240501.dist-info/RECORD +0 -32
  25. {dfindexeddb-20240501.dist-info → dfindexeddb-20241031.dist-info}/AUTHORS +0 -0
  26. {dfindexeddb-20240501.dist-info → dfindexeddb-20241031.dist-info}/LICENSE +0 -0
  27. {dfindexeddb-20240501.dist-info → dfindexeddb-20241031.dist-info}/entry_points.txt +0 -0
  28. {dfindexeddb-20240501.dist-info → dfindexeddb-20241031.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,135 @@
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
+ """Parser plugin for Chrome Notifications."""
16
+ from __future__ import annotations
17
+
18
+ import dataclasses
19
+ import logging
20
+
21
+ from typing import Optional
22
+
23
+ try:
24
+ # pytype: disable=import-error
25
+ from dfdatetime import webkit_time
26
+ from dfindexeddb.leveldb.plugins import notification_database_data_pb2 as \
27
+ notification_pb2
28
+ # pytype: enable=import-error
29
+ _has_import_dependencies = True
30
+ except ImportError as err:
31
+ _has_import_dependencies = False
32
+ logging.warning((
33
+ 'Could not import dependencies for '
34
+ 'leveldb.plugins.chrome_notifications: %s'), err)
35
+
36
+ from dfindexeddb.indexeddb.chromium import blink
37
+ from dfindexeddb.leveldb.plugins import interface
38
+ from dfindexeddb.leveldb.plugins import manager
39
+
40
+
41
+ @dataclasses.dataclass
42
+ class ChromeNotificationRecord(interface.LeveldbPlugin):
43
+ """Chrome notification record."""
44
+ src_file: Optional[str] = None
45
+ offset: Optional[int] = None
46
+ key: Optional[str] = None
47
+ sequence_number: Optional[int] = None
48
+ type: Optional[int] = None
49
+ origin: Optional[str] = None
50
+ service_worker_registration_id: Optional[int] = None
51
+ notification_title: Optional[str] = None
52
+ notification_direction: Optional[str] = None
53
+ notification_lang: Optional[str] = None
54
+ notification_body: Optional[str] = None
55
+ notification_tag: Optional[str] = None
56
+ notification_icon: Optional[str] = None
57
+ notification_silent: Optional[bool] = None
58
+ notification_data: Optional[str] = None
59
+ notification_require_interaction: Optional[bool] = None
60
+ notification_time: Optional[str] = None
61
+ notification_renotify: Optional[bool] = None
62
+ notification_badge: Optional[str] = None
63
+ notification_image: Optional[str] = None
64
+ notification_id: Optional[str] = None
65
+ replaced_existing_notification: Optional[bool] = None
66
+ num_clicks: Optional[int] = None
67
+ num_action_button_clicks: Optional[int] = None
68
+ creation_time: Optional[str] = None
69
+ closed_reason: Optional[str] = None
70
+ has_triggered: Optional[bool] = None
71
+
72
+ @classmethod
73
+ def FromKeyValueRecord(
74
+ cls,
75
+ ldb_record
76
+ ) -> ChromeNotificationRecord:
77
+ record = cls()
78
+ record.offset = ldb_record.offset
79
+ record.key = ldb_record.key.decode()
80
+ record.sequence_number = ldb_record.sequence_number
81
+ record.type = ldb_record.record_type
82
+
83
+ if not ldb_record.value:
84
+ return record
85
+
86
+ # pylint: disable-next=no-member,line-too-long
87
+ notification_proto = notification_pb2.NotificationDatabaseDataProto() # pytype: disable=module-attr
88
+ notification_proto.ParseFromString(ldb_record.value)
89
+
90
+ record.origin = notification_proto.origin
91
+ record.service_worker_registration_id = (
92
+ notification_proto.service_worker_registration_id)
93
+ record.notification_title = notification_proto.notification_data.title
94
+ record.notification_direction = (
95
+ notification_proto.notification_data.direction)
96
+ record.notification_lang = notification_proto.notification_data.lang
97
+ record.notification_body = notification_proto.notification_data.body
98
+ record.notification_tag = notification_proto.notification_data.tag
99
+ record.notification_icon = notification_proto.notification_data.icon
100
+ record.notification_silent = notification_proto.notification_data.silent
101
+ record.notification_data = notification_proto.notification_data.data
102
+ record.notification_require_interaction = (
103
+ notification_proto.notification_data.require_interaction)
104
+ record.notification_time = webkit_time.WebKitTime(
105
+ timestamp=notification_proto.notification_data.timestamp
106
+ ).CopyToDateTimeString()
107
+ record.notification_renotify = notification_proto.notification_data.renotify
108
+ record.notification_badge = notification_proto.notification_data.badge
109
+ record.notification_image = notification_proto.notification_data.image
110
+ record.notification_id = notification_proto.notification_id
111
+ record.replaced_existing_notification = (
112
+ notification_proto.replaced_existing_notification)
113
+ record.num_clicks = notification_proto.num_clicks
114
+ record.num_action_button_clicks = (
115
+ notification_proto.num_action_button_clicks)
116
+ record.creation_time = webkit_time.WebKitTime(
117
+ timestamp=notification_proto.creation_time_millis
118
+ ).CopyToDateTimeString()
119
+ record.closed_reason = notification_proto.closed_reason
120
+ record.has_triggered = notification_proto.has_triggered
121
+
122
+ if not notification_proto.notification_data.data:
123
+ return record
124
+
125
+ notification_data = blink.V8ScriptValueDecoder(
126
+ raw_data=notification_proto.notification_data.data).Deserialize()
127
+ record.notification_data = notification_data
128
+
129
+ return record
130
+
131
+
132
+ # check if dependencies are in existence..
133
+
134
+ if _has_import_dependencies:
135
+ manager.PluginManager.RegisterPlugin(ChromeNotificationRecord)
@@ -0,0 +1,36 @@
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
+ """Interface for leveldb plugins."""
16
+ from typing import Any, Union
17
+
18
+ from dfindexeddb.leveldb import record
19
+ from dfindexeddb.leveldb import ldb
20
+ from dfindexeddb.leveldb import log
21
+
22
+ class LeveldbPlugin:
23
+ """The base leveldb plugin class."""
24
+
25
+ @classmethod
26
+ def FromLevelDBRecord(cls,
27
+ ldb_record: record.LevelDBRecord) -> Any:
28
+ """Parses a leveldb record."""
29
+ parsed_record = cls.FromKeyValueRecord(ldb_record.record)
30
+ ldb_record.record = parsed_record
31
+ return ldb_record
32
+
33
+ @classmethod
34
+ def FromKeyValueRecord(
35
+ cls, ldb_record: Union[ldb.KeyValueRecord, log.ParsedInternalKey]) -> Any:
36
+ """Parses a leveldb key value record."""
@@ -0,0 +1,75 @@
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 manager."""
16
+ from typing import Type
17
+
18
+ from dfindexeddb.leveldb.plugins import interface
19
+
20
+
21
+ class LeveldbPluginManager:
22
+ """The leveldb plugin manager."""
23
+
24
+ _class_registry = {}
25
+
26
+ @classmethod
27
+ def GetPlugins(cls):
28
+ """Retrieves the registered leveldb plugins.
29
+
30
+ Yields:
31
+ tuple: containing:
32
+ str: the name of the leveldb plugin.
33
+ class: the plugin class.
34
+ """
35
+ yield from cls._class_registry.items()
36
+
37
+ @classmethod
38
+ def GetPlugin(cls, plugin_name: str) -> interface.LeveldbPlugin:
39
+ """Retrieves a class object of a specific plugin.
40
+
41
+ Args:
42
+ plugin_name: name of the plugin.
43
+
44
+ Returns:
45
+ the LeveldbPlugin class.
46
+
47
+ Raises:
48
+ KeyError: if the plugin is not found/registered in the manager.
49
+ """
50
+ try:
51
+ return cls._class_registry[plugin_name]
52
+ except KeyError:
53
+ raise KeyError(f'Plugin not found: {plugin_name}')
54
+
55
+ @classmethod
56
+ def RegisterPlugin(cls, plugin_class: Type[interface.LeveldbPlugin]):
57
+ """Registers a leveldb plugin.
58
+
59
+ Args:
60
+ plugin_class (class): the plugin class to register.
61
+
62
+ Raises:
63
+ KeyError: if class is already set for the corresponding name.
64
+ """
65
+ plugin_name = plugin_class.__name__
66
+ if plugin_name in cls._class_registry:
67
+ raise KeyError(f'Plugin already registered {plugin_name}')
68
+ cls._class_registry[plugin_name] = plugin_class
69
+
70
+ @classmethod
71
+ def ClearPlugins(cls):
72
+ """Clears all plugin registrations."""
73
+ cls._class_registry = {}
74
+
75
+ PluginManager = LeveldbPluginManager()
@@ -0,0 +1,38 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: notification_database_data.proto
4
+ # Protobuf Python Version: 4.25.1
5
+ """Generated protocol buffer code."""
6
+ from google.protobuf import descriptor as _descriptor
7
+ from google.protobuf import descriptor_pool as _descriptor_pool
8
+ from google.protobuf import symbol_database as _symbol_database
9
+ from google.protobuf.internal import builder as _builder
10
+ # @@protoc_insertion_point(imports)
11
+
12
+ _sym_db = _symbol_database.Default()
13
+
14
+
15
+
16
+ # pylint: skip-file
17
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n notification_database_data.proto\"\xff\t\n\x1dNotificationDatabaseDataProto\x12\"\n\x1apersistent_notification_id\x18\x01 \x01(\x03\x12\x17\n\x0fnotification_id\x18\x05 \x01(\t\x12\x0e\n\x06origin\x18\x02 \x01(\t\x12&\n\x1eservice_worker_registration_id\x18\x03 \x01(\x03\x12&\n\x1ereplaced_existing_notification\x18\x06 \x01(\x08\x12\x12\n\nnum_clicks\x18\x07 \x01(\x05\x12 \n\x18num_action_button_clicks\x18\x08 \x01(\x05\x12\x1c\n\x14\x63reation_time_millis\x18\t \x01(\x03\x12%\n\x1dtime_until_first_click_millis\x18\n \x01(\x03\x12$\n\x1ctime_until_last_click_millis\x18\x0b \x01(\x03\x12\x1f\n\x17time_until_close_millis\x18\x0c \x01(\x03\x12\x42\n\rclosed_reason\x18\r \x01(\x0e\x32+.NotificationDatabaseDataProto.ClosedReason\x12J\n\x11notification_data\x18\x04 \x01(\x0b\x32/.NotificationDatabaseDataProto.NotificationData\x12\x15\n\rhas_triggered\x18\x0e \x01(\x08\x1a\xba\x01\n\x12NotificationAction\x12\x0e\n\x06\x61\x63tion\x18\x01 \x01(\t\x12\r\n\x05title\x18\x02 \x01(\t\x12\x0c\n\x04icon\x18\x03 \x01(\t\x12\x44\n\x04type\x18\x04 \x01(\x0e\x32\x36.NotificationDatabaseDataProto.NotificationAction.Type\x12\x13\n\x0bplaceholder\x18\x05 \x01(\t\"\x1c\n\x04Type\x12\n\n\x06\x42UTTON\x10\x00\x12\x08\n\x04TEXT\x10\x01\x1a\xe4\x03\n\x10NotificationData\x12\r\n\x05title\x18\x01 \x01(\t\x12L\n\tdirection\x18\x02 \x01(\x0e\x32\x39.NotificationDatabaseDataProto.NotificationData.Direction\x12\x0c\n\x04lang\x18\x03 \x01(\t\x12\x0c\n\x04\x62ody\x18\x04 \x01(\t\x12\x0b\n\x03tag\x18\x05 \x01(\t\x12\r\n\x05image\x18\x0f \x01(\t\x12\x0c\n\x04icon\x18\x06 \x01(\t\x12\r\n\x05\x62\x61\x64ge\x18\x0e \x01(\t\x12\x1d\n\x11vibration_pattern\x18\t \x03(\x05\x42\x02\x10\x01\x12\x11\n\ttimestamp\x18\x0c \x01(\x03\x12\x10\n\x08renotify\x18\r \x01(\x08\x12\x0e\n\x06silent\x18\x07 \x01(\x08\x12\x1b\n\x13require_interaction\x18\x0b \x01(\x08\x12\x0c\n\x04\x64\x61ta\x18\x08 \x01(\x0c\x12\x42\n\x07\x61\x63tions\x18\n \x03(\x0b\x32\x31.NotificationDatabaseDataProto.NotificationAction\x12\x1e\n\x16show_trigger_timestamp\x18\x10 \x01(\x03\";\n\tDirection\x12\x11\n\rLEFT_TO_RIGHT\x10\x00\x12\x11\n\rRIGHT_TO_LEFT\x10\x01\x12\x08\n\x04\x41UTO\x10\x02\"4\n\x0c\x43losedReason\x12\x08\n\x04USER\x10\x00\x12\r\n\tDEVELOPER\x10\x01\x12\x0b\n\x07UNKNOWN\x10\x02')
18
+
19
+ _globals = globals()
20
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
21
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'notification_database_data_pb2', _globals)
22
+ if _descriptor._USE_C_DESCRIPTORS == False:
23
+ DESCRIPTOR._options = None
24
+ _globals['_NOTIFICATIONDATABASEDATAPROTO_NOTIFICATIONDATA'].fields_by_name['vibration_pattern']._options = None
25
+ _globals['_NOTIFICATIONDATABASEDATAPROTO_NOTIFICATIONDATA'].fields_by_name['vibration_pattern']._serialized_options = b'\020\001'
26
+ _globals['_NOTIFICATIONDATABASEDATAPROTO']._serialized_start=37
27
+ _globals['_NOTIFICATIONDATABASEDATAPROTO']._serialized_end=1316
28
+ _globals['_NOTIFICATIONDATABASEDATAPROTO_NOTIFICATIONACTION']._serialized_start=589
29
+ _globals['_NOTIFICATIONDATABASEDATAPROTO_NOTIFICATIONACTION']._serialized_end=775
30
+ _globals['_NOTIFICATIONDATABASEDATAPROTO_NOTIFICATIONACTION_TYPE']._serialized_start=747
31
+ _globals['_NOTIFICATIONDATABASEDATAPROTO_NOTIFICATIONACTION_TYPE']._serialized_end=775
32
+ _globals['_NOTIFICATIONDATABASEDATAPROTO_NOTIFICATIONDATA']._serialized_start=778
33
+ _globals['_NOTIFICATIONDATABASEDATAPROTO_NOTIFICATIONDATA']._serialized_end=1262
34
+ _globals['_NOTIFICATIONDATABASEDATAPROTO_NOTIFICATIONDATA_DIRECTION']._serialized_start=1203
35
+ _globals['_NOTIFICATIONDATABASEDATAPROTO_NOTIFICATIONDATA_DIRECTION']._serialized_end=1262
36
+ _globals['_NOTIFICATIONDATABASEDATAPROTO_CLOSEDREASON']._serialized_start=1264
37
+ _globals['_NOTIFICATIONDATABASEDATAPROTO_CLOSEDREASON']._serialized_end=1316
38
+ # @@protoc_insertion_point(module_scope)
@@ -14,6 +14,7 @@
14
14
  # limitations under the License.
15
15
  """A module for records from LevelDB files."""
16
16
  from __future__ import annotations
17
+ from collections import defaultdict
17
18
  import dataclasses
18
19
  import pathlib
19
20
  import re
@@ -297,9 +298,38 @@ class FolderReader:
297
298
  record.recovered = True
298
299
  yield record
299
300
 
301
+ def _RecordsBySequenceNumber(self) -> Generator[LevelDBRecord, None, None]:
302
+ """Yields LevelDBRecords using the sequence number and file offset.
303
+
304
+ Yields:
305
+ LevelDBRecords.
306
+ """
307
+ unsorted_records = defaultdict(list)
308
+
309
+ for filename in self.foldername.iterdir():
310
+ for leveldb_record in LevelDBRecord.FromFile(filename):
311
+ if leveldb_record:
312
+ unsorted_records[leveldb_record.record.key].append(leveldb_record)
313
+ for _, unsorted_records in unsorted_records.items():
314
+ num_unsorted_records = len(unsorted_records)
315
+ if num_unsorted_records == 1:
316
+ unsorted_records[0].recovered = False
317
+ yield unsorted_records[0]
318
+ else:
319
+ for i, record in enumerate(sorted(
320
+ unsorted_records, key=lambda x: (
321
+ x.record.sequence_number, x.record.offset)),
322
+ start=1):
323
+ if i == num_unsorted_records:
324
+ record.recovered = False
325
+ else:
326
+ record.recovered = True
327
+ yield record
328
+
300
329
  def GetRecords(
301
330
  self,
302
- use_manifest: bool = False
331
+ use_manifest: bool = False,
332
+ use_sequence_number: bool = False
303
333
  ) -> Generator[LevelDBRecord, None, None]:
304
334
  """Yield LevelDBRecords.
305
335
 
@@ -312,6 +342,8 @@ class FolderReader:
312
342
  """
313
343
  if use_manifest:
314
344
  yield from self._RecordsByManifest()
345
+ elif use_sequence_number:
346
+ yield from self._RecordsBySequenceNumber()
315
347
  else:
316
348
  for filename in self.foldername.iterdir():
317
349
  yield from LevelDBRecord.FromFile(filename)
dfindexeddb/utils.py CHANGED
@@ -14,6 +14,8 @@
14
14
  # limitations under the License.
15
15
  """Utilities for dfindexeddb."""
16
16
  from __future__ import annotations
17
+ import copy
18
+ import dataclasses
17
19
  import io
18
20
  import os
19
21
  import struct
@@ -211,7 +213,7 @@ T = TypeVar('T')
211
213
 
212
214
 
213
215
  class FromDecoderMixin:
214
- """A mixin for parsing dataclass attributes using a LevelDBDecoder."""
216
+ """A mixin for parsing dataclass attributes using a StreamDecoder."""
215
217
 
216
218
  @classmethod
217
219
  def FromDecoder(
@@ -259,3 +261,35 @@ class FromDecoderMixin:
259
261
  """
260
262
  stream = io.BytesIO(raw_data)
261
263
  return cls.FromStream(stream=stream, base_offset=base_offset)
264
+
265
+
266
+ def asdict(obj, *, dict_factory=dict): # pylint: disable=invalid-name
267
+ """Custom implementation of the asdict dataclasses method to include the
268
+ class name under the __type__ attribute name.
269
+ """
270
+ if not dataclasses.is_dataclass(obj):
271
+ raise TypeError("asdict() should be called on dataclass instances")
272
+ return _asdict_inner(obj, dict_factory)
273
+
274
+
275
+ def _asdict_inner(obj, dict_factory):
276
+ """Custom implementation of the _asdict_inner dataclasses method."""
277
+ if dataclasses.is_dataclass(obj):
278
+ result = [('__type__', obj.__class__.__name__)]
279
+ for f in dataclasses.fields(obj):
280
+ value = _asdict_inner(getattr(obj, f.name), dict_factory)
281
+ result.append((f.name, value))
282
+ return dict_factory(result)
283
+
284
+ if isinstance(obj, tuple) and hasattr(obj, '_fields'):
285
+ return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj])
286
+
287
+ if isinstance(obj, (list, tuple)):
288
+ return type(obj)(_asdict_inner(v, dict_factory) for v in obj)
289
+
290
+ if isinstance(obj, dict):
291
+ return type(obj)((_asdict_inner(k, dict_factory),
292
+ _asdict_inner(v, dict_factory))
293
+ for k, v in obj.items())
294
+
295
+ return copy.deepcopy(obj)
dfindexeddb/version.py CHANGED
@@ -15,7 +15,7 @@
15
15
  """Version information for dfIndexeddb."""
16
16
 
17
17
 
18
- __version__ = "20240501"
18
+ __version__ = "20241031"
19
19
 
20
20
 
21
21
  def GetVersion():
@@ -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 |
@@ -0,0 +1,41 @@
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=g-uqQzT_iKM7PPEIuSCNkQG2ltwpnLpRA_dPtrgVzzc,9997
4
+ dfindexeddb/version.py,sha256=UAMCa51yvzp9xG8y5eagDkYAfzVLED94Kp7hTfVBSSI,751
5
+ dfindexeddb/indexeddb/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
6
+ dfindexeddb/indexeddb/cli.py,sha256=O07_DpVeGtYx2V-jUJF5Oc3OftM2FPLSwNdS45MLCdo,7435
7
+ dfindexeddb/indexeddb/types.py,sha256=cIXmShUbbXJMSbXkmthxGGrpIF9fWr3Ypfl6ckGoSBU,1892
8
+ dfindexeddb/indexeddb/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ dfindexeddb/indexeddb/chromium/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
10
+ dfindexeddb/indexeddb/chromium/blink.py,sha256=kwhPfzcWOOxYyXUWfV6f4grQwXzS2ABFaNVMIVhol3c,32268
11
+ dfindexeddb/indexeddb/chromium/definitions.py,sha256=1a-AmHVZ95uDB6se_fdarwJR8q0tFMQNh2xrZ2-VxN8,8739
12
+ dfindexeddb/indexeddb/chromium/record.py,sha256=LIuTwwQeQbn6CBXdo0AZZHounOWcnXRg6W082yxmNBo,47578
13
+ dfindexeddb/indexeddb/chromium/v8.py,sha256=tzkKhx0S53HLCNMCls4GCFXrRCjjrgkFSwL3mbNQsjg,22656
14
+ dfindexeddb/indexeddb/firefox/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
15
+ dfindexeddb/indexeddb/firefox/definitions.py,sha256=xkvlYaaFR2IDQBGJGnrhVIOUce6VuMq-kWXe2CLX3Aw,4306
16
+ dfindexeddb/indexeddb/firefox/gecko.py,sha256=m6-tGHOFnND-XG4C-9o1Atxo4BkHTMhFWCGqW2vFVPk,19423
17
+ dfindexeddb/indexeddb/firefox/record.py,sha256=yB7dYiwzCx1c67Sf6ViMSX51SCrcgU8OBUaVYczqTik,5766
18
+ dfindexeddb/indexeddb/safari/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
19
+ dfindexeddb/indexeddb/safari/definitions.py,sha256=nW8MmYx9Ob86W4pxm4QD4Xvr5QjoV34-U7wDhm2GIr0,2779
20
+ dfindexeddb/indexeddb/safari/record.py,sha256=bzoMSgpXs2SsEOKHjVh9tkJDZtzGkQByq3G5dK_Yd7Q,8010
21
+ dfindexeddb/indexeddb/safari/webkit.py,sha256=LHaSLOGr74dzGblrqC_RVYD6GCftzGP-p0oWujZ3l1c,22592
22
+ dfindexeddb/leveldb/__init__.py,sha256=KPYL9__l8od6_OyDfGRTgaJ6iy_fqIgZ-dS2S-e3Rac,599
23
+ dfindexeddb/leveldb/cli.py,sha256=e2C94FSP28dh83FWQXD5N44ymUDwkfFeX0Tfk9YLCTo,9913
24
+ dfindexeddb/leveldb/definitions.py,sha256=lPW_kjc47vyoGOoEWfgWvKcpGbN-0h7XXwCeMoFmYKk,1486
25
+ dfindexeddb/leveldb/descriptor.py,sha256=BgWO-sEqT2zhPu9oEplTa8O_szpgU2N4QfDZeroTcx0,12237
26
+ dfindexeddb/leveldb/ldb.py,sha256=mN-M7PLtE_VLZCbCbzRgjkSezbMUhgDjgWgPgIxJ1jM,8087
27
+ dfindexeddb/leveldb/log.py,sha256=ofw0r2f_3Ll5oHzssvp61nmjhIPdt3tmb9UeNiGLHXk,9401
28
+ dfindexeddb/leveldb/record.py,sha256=j7ZnU6VDVcYVpJRGFRb5Sr2edhC3aGp3U0kPNcoZgng,11912
29
+ dfindexeddb/leveldb/utils.py,sha256=RgEEZ7Z35m3CcOUypAiViQSzKjBgSXZ3aeJhQjY3H9w,3748
30
+ dfindexeddb/leveldb/plugins/__init__.py,sha256=RoC6tRkq8FhqIaFs6jwu1fao_qaSvlSfIFxQVjWregI,690
31
+ dfindexeddb/leveldb/plugins/chrome_notifications.py,sha256=-dyb_AJbUPE2wPJg_Y1Ns5CMtg4udi9Fqo5WKh6f3Z4,5354
32
+ dfindexeddb/leveldb/plugins/interface.py,sha256=QlNEvVvU8K9ChE2kblM97cOvXwvmCh9NuSf2b6WwezQ,1257
33
+ dfindexeddb/leveldb/plugins/manager.py,sha256=jisYyks3OQQQUVACoGcWN81UCGQEa537YvYL7v3CiFs,2139
34
+ dfindexeddb/leveldb/plugins/notification_database_data_pb2.py,sha256=DCPZHbyq2szLgrBprOKrJKycKJma8Z_SnAQM6Jx9bZg,4389
35
+ dfindexeddb-20241031.dist-info/AUTHORS,sha256=QbvjbAom57fpEkekkCVFUj0B9KUMGraR510aUMBC-PE,286
36
+ dfindexeddb-20241031.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
37
+ dfindexeddb-20241031.dist-info/METADATA,sha256=0Wi-Bd_2GyLC3ssYcDQuPKiFK6s98gOgir-1QuYfTPo,18972
38
+ dfindexeddb-20241031.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
39
+ dfindexeddb-20241031.dist-info/entry_points.txt,sha256=WG9YNLZ9lBx4Q9QF6wS4dZdZfADT3Zs4_-MV5TcA0ls,102
40
+ dfindexeddb-20241031.dist-info/top_level.txt,sha256=X9OTaub1c8S_JJ7g-f8JdkhhdiZ4x1j4eni1hdUCwE4,12
41
+ dfindexeddb-20241031.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,32 +0,0 @@
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=w3jTfQPKTCKVn_YxB-eYjw2WM5lN7n0625noN-CZErc,751
5
- dfindexeddb/indexeddb/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
6
- dfindexeddb/indexeddb/cli.py,sha256=MsVqxIhZWXoiBEDbysO9gWztsUkZ8cwFMCYmgSZViLw,5980
7
- dfindexeddb/indexeddb/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- dfindexeddb/indexeddb/chromium/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
9
- dfindexeddb/indexeddb/chromium/blink.py,sha256=Sa5BvDkuwXg2IPc4iUDkNA9cbQKpMnXd3bPpNWSPG0I,32122
10
- dfindexeddb/indexeddb/chromium/definitions.py,sha256=1a-AmHVZ95uDB6se_fdarwJR8q0tFMQNh2xrZ2-VxN8,8739
11
- dfindexeddb/indexeddb/chromium/record.py,sha256=kdItbJC5Mo6xWW71QylWvvKxtS4yYJ4BGc_ABHaHIiM,47366
12
- dfindexeddb/indexeddb/chromium/v8.py,sha256=NsbMgA6nRcAfdLg6CFwWadwsDS6TJ95-4MrgphaTuLw,22102
13
- dfindexeddb/indexeddb/firefox/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
14
- dfindexeddb/indexeddb/safari/__init__.py,sha256=kExXSVBCTKCD5BZJkdMfUMqGksH-DMJxP2_lI0gq-BE,575
15
- dfindexeddb/indexeddb/safari/definitions.py,sha256=nW8MmYx9Ob86W4pxm4QD4Xvr5QjoV34-U7wDhm2GIr0,2779
16
- dfindexeddb/indexeddb/safari/record.py,sha256=bzoMSgpXs2SsEOKHjVh9tkJDZtzGkQByq3G5dK_Yd7Q,8010
17
- dfindexeddb/indexeddb/safari/webkit.py,sha256=eQeKXOcVdCaZKM4xekw1d3iLTynXc_e-GCT_x9iSJNs,21595
18
- dfindexeddb/leveldb/__init__.py,sha256=KPYL9__l8od6_OyDfGRTgaJ6iy_fqIgZ-dS2S-e3Rac,599
19
- dfindexeddb/leveldb/cli.py,sha256=H1QmbZ2jQ75LqsxPaiOjflljDtYGCSxtsDI3mEf7BNU,7982
20
- dfindexeddb/leveldb/definitions.py,sha256=lPW_kjc47vyoGOoEWfgWvKcpGbN-0h7XXwCeMoFmYKk,1486
21
- dfindexeddb/leveldb/descriptor.py,sha256=WR3irG16oIE6VbaP9UPnzOD3KlHR8GYFnoeG6ySJUzU,12211
22
- dfindexeddb/leveldb/ldb.py,sha256=mN-M7PLtE_VLZCbCbzRgjkSezbMUhgDjgWgPgIxJ1jM,8087
23
- dfindexeddb/leveldb/log.py,sha256=QeH8oESOPEZUjANGiDRSmXZa2SuoKlPFBJY7SxTV1lg,9209
24
- dfindexeddb/leveldb/record.py,sha256=_xmeEBXz5nimqteOMAf0zYoi8wDLgG27F4BhLk6N21Q,10747
25
- dfindexeddb/leveldb/utils.py,sha256=RgEEZ7Z35m3CcOUypAiViQSzKjBgSXZ3aeJhQjY3H9w,3748
26
- dfindexeddb-20240501.dist-info/AUTHORS,sha256=QbvjbAom57fpEkekkCVFUj0B9KUMGraR510aUMBC-PE,286
27
- dfindexeddb-20240501.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
28
- dfindexeddb-20240501.dist-info/METADATA,sha256=Ejikzq8iS6BHrSroiBS4A_xR7O4GJnwVsmyoMF25jUM,18498
29
- dfindexeddb-20240501.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
30
- dfindexeddb-20240501.dist-info/entry_points.txt,sha256=WG9YNLZ9lBx4Q9QF6wS4dZdZfADT3Zs4_-MV5TcA0ls,102
31
- dfindexeddb-20240501.dist-info/top_level.txt,sha256=X9OTaub1c8S_JJ7g-f8JdkhhdiZ4x1j4eni1hdUCwE4,12
32
- dfindexeddb-20240501.dist-info/RECORD,,