Skip to content

Commit d6cf6ff

Browse files
authored
Add leveldb plugin system (#45)
1 parent 0f9f677 commit d6cf6ff

File tree

10 files changed

+312
-88
lines changed

10 files changed

+312
-88
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ include:
3333
$ pip install dfindexeddb
3434
```
3535

36+
To also install the dependencies for leveldb/indexeddb plugins, run
37+
```
38+
$ pip install 'dfindexeddb[plugins]'
39+
```
40+
41+
3642
## Installation from source
3743

3844
1. [Linux] Install the snappy compression development package
@@ -51,6 +57,11 @@ include:
5157
$ pip install .
5258
```
5359

60+
To also install the dependencies for leveldb/indexeddb plugins, run
61+
```
62+
$ pip install '.[plugins]'
63+
```
64+
5465
## Usage
5566

5667
Two CLI tools for parsing IndexedDB/LevelDB files are available after
@@ -170,3 +181,13 @@ following command:
170181
```
171182
$ dfleveldb descriptor -s SOURCE [-o {json,jsonl,repr}] [-t {blocks,physical_records,versionedit} | -v]
172183
```
184+
185+
#### Plugins
186+
187+
To apply a plugin parser for a leveldb file/folder, add the
188+
`--plugin [Plugin Name]` argument. Currently, there is support for the
189+
following artifacts:
190+
191+
| Plugin Name | Artifact Name |
192+
| -------- | ------- |
193+
| `ChromeNotificationRecord` | Chrome/Chromium Notifications |

dfindexeddb/indexeddb/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import json
2121
import pathlib
2222

23+
from dfindexeddb import utils
2324
from dfindexeddb import version
2425
from dfindexeddb.indexeddb.chromium import blink
2526
from dfindexeddb.indexeddb.chromium import record as chromium_record
@@ -36,7 +37,7 @@ class Encoder(json.JSONEncoder):
3637
"""A JSON encoder class for dfindexeddb fields."""
3738
def default(self, o):
3839
if dataclasses.is_dataclass(o):
39-
o_dict = dataclasses.asdict(o)
40+
o_dict = utils.asdict(o)
4041
return o_dict
4142
if isinstance(o, bytes):
4243
out = []

dfindexeddb/leveldb/cli.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
import json
2020
import pathlib
2121

22+
from dfindexeddb import utils
2223
from dfindexeddb import version
2324
from dfindexeddb.leveldb import descriptor
2425
from dfindexeddb.leveldb import ldb
2526
from dfindexeddb.leveldb import log
2627
from dfindexeddb.leveldb import record
28+
from dfindexeddb.leveldb.plugins import manager
2729

2830

2931
_VALID_PRINTABLE_CHARACTERS = (
@@ -37,7 +39,7 @@ class Encoder(json.JSONEncoder):
3739
def default(self, o):
3840
"""Returns a serializable object for o."""
3941
if dataclasses.is_dataclass(o):
40-
o_dict = dataclasses.asdict(o)
42+
o_dict = utils.asdict(o)
4143
return o_dict
4244
if isinstance(o, bytes):
4345
out = []
@@ -66,15 +68,39 @@ def _Output(structure, output):
6668

6769
def DbCommand(args):
6870
"""The CLI for processing leveldb folders."""
71+
if args.plugin and args.plugin == 'list':
72+
for plugin, _ in manager.LeveldbPluginManager.GetPlugins():
73+
print(plugin)
74+
return
75+
76+
if args.plugin:
77+
plugin_class = manager.LeveldbPluginManager.GetPlugin(args.plugin)
78+
else:
79+
plugin_class = None
80+
6981
for leveldb_record in record.FolderReader(
7082
args.source).GetRecords(
7183
use_manifest=args.use_manifest,
7284
use_sequence_number=args.use_sequence_number):
73-
_Output(leveldb_record, output=args.output)
85+
if plugin_class:
86+
plugin_record = plugin_class.FromLevelDBRecord(leveldb_record)
87+
_Output(plugin_record, output=args.output)
88+
else:
89+
_Output(leveldb_record, output=args.output)
7490

7591

7692
def LdbCommand(args):
7793
"""The CLI for processing ldb files."""
94+
if args.plugin and args.plugin == 'list':
95+
for plugin, _ in manager.LeveldbPluginManager.GetPlugins():
96+
print(plugin)
97+
return
98+
99+
if args.plugin:
100+
plugin_class = manager.LeveldbPluginManager.GetPlugin(args.plugin)
101+
else:
102+
plugin_class = None
103+
78104
ldb_file = ldb.FileReader(args.source)
79105

80106
if args.structure_type == 'blocks':
@@ -85,14 +111,28 @@ def LdbCommand(args):
85111
elif args.structure_type == 'records' or not args.structure_type:
86112
# Prints key value record information.
87113
for key_value_record in ldb_file.GetKeyValueRecords():
88-
_Output(key_value_record, output=args.output)
114+
if plugin_class:
115+
plugin_record = plugin_class.FromKeyValueRecord(key_value_record)
116+
_Output(plugin_record, output=args.output)
117+
else:
118+
_Output(key_value_record, output=args.output)
89119

90120
else:
91121
print(f'{args.structure_type} is not supported for ldb files.')
92122

93123

94124
def LogCommand(args):
95125
"""The CLI for processing log files."""
126+
if args.plugin and args.plugin == 'list':
127+
for plugin, _ in manager.LeveldbPluginManager.GetPlugins():
128+
print(plugin)
129+
return
130+
131+
if args.plugin:
132+
plugin_class = manager.LeveldbPluginManager.GetPlugin(args.plugin)
133+
else:
134+
plugin_class = None
135+
96136
log_file = log.FileReader(args.source)
97137

98138
if args.structure_type == 'blocks':
@@ -114,7 +154,11 @@ def LogCommand(args):
114154
or not args.structure_type):
115155
# Prints key value record information.
116156
for internal_key_record in log_file.GetParsedInternalKeys():
117-
_Output(internal_key_record, output=args.output)
157+
if plugin_class:
158+
plugin_record = plugin_class.FromKeyValueRecord(internal_key_record)
159+
_Output(plugin_record, output=args.output)
160+
else:
161+
_Output(internal_key_record, output=args.output)
118162

119163
else:
120164
print(f'{args.structure_type} is not supported for log files.')
@@ -146,6 +190,7 @@ def DescriptorCommand(args):
146190
else:
147191
print(f'{args.structure_type} is not supported for descriptor files.')
148192

193+
149194
def App():
150195
"""The CLI app entrypoint for parsing leveldb files."""
151196
parser = argparse.ArgumentParser(
@@ -182,6 +227,9 @@ def App():
182227
'repr'],
183228
default='json',
184229
help='Output format. Default is json')
230+
parser_db.add_argument(
231+
'--plugin',
232+
help='Use plugin to parse records.')
185233
parser_db.set_defaults(func=DbCommand)
186234

187235
parser_log = subparsers.add_parser(
@@ -200,6 +248,9 @@ def App():
200248
'repr'],
201249
default='json',
202250
help='Output format. Default is json')
251+
parser_log.add_argument(
252+
'--plugin',
253+
help='Use plugin to parse records.')
203254
parser_log.add_argument(
204255
'-t',
205256
'--structure_type',
@@ -227,6 +278,9 @@ def App():
227278
'repr'],
228279
default='json',
229280
help='Output format. Default is json')
281+
parser_ldb.add_argument(
282+
'--plugin',
283+
help='Use plugin to parse records.')
230284
parser_ldb.add_argument(
231285
'-t',
232286
'--structure_type',
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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 module."""
16+
17+
from dfindexeddb.leveldb.plugins import chrome_notifications

0 commit comments

Comments
 (0)