Skip to content

Commit 95b3390

Browse files
committed
Initial commit of leveldb plugin
1 parent e52abb2 commit 95b3390

File tree

8 files changed

+379
-5
lines changed

8 files changed

+379
-5
lines changed

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: 55 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,37 @@ 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+
elif args.plugin:
76+
plugin_class = manager.LeveldbPluginManager.GetPlugin(args.plugin)
77+
else:
78+
plugin_class = None
79+
6980
for leveldb_record in record.FolderReader(
7081
args.source).GetRecords(
7182
use_manifest=args.use_manifest,
7283
use_sequence_number=args.use_sequence_number):
73-
_Output(leveldb_record, output=args.output)
84+
if plugin_class:
85+
plugin_record = plugin_class.FromKeyValueRecord(leveldb_record)
86+
_Output(plugin_record, output=args.output)
87+
else:
88+
_Output(leveldb_record, output=args.output)
7489

7590

7691
def LdbCommand(args):
7792
"""The CLI for processing ldb files."""
93+
if args.plugin and args.plugin == 'list':
94+
for plugin, _ in manager.LeveldbPluginManager.GetPlugins():
95+
print(plugin)
96+
return
97+
elif args.plugin:
98+
plugin_class = manager.LeveldbPluginManager.GetPlugin(args.plugin)
99+
else:
100+
plugin_class = None
101+
78102
ldb_file = ldb.FileReader(args.source)
79103

80104
if args.structure_type == 'blocks':
@@ -85,14 +109,27 @@ def LdbCommand(args):
85109
elif args.structure_type == 'records' or not args.structure_type:
86110
# Prints key value record information.
87111
for key_value_record in ldb_file.GetKeyValueRecords():
88-
_Output(key_value_record, output=args.output)
112+
if plugin_class:
113+
plugin_record = plugin_class.FromKeyValueRecord(key_value_record)
114+
_Output(plugin_record, output=args.output)
115+
else:
116+
_Output(key_value_record, output=args.output)
89117

90118
else:
91119
print(f'{args.structure_type} is not supported for ldb files.')
92120

93121

94122
def LogCommand(args):
95123
"""The CLI for processing log files."""
124+
if args.plugin and args.plugin == 'list':
125+
for plugin, _ in manager.LeveldbPluginManager.GetPlugins():
126+
print(plugin)
127+
return
128+
elif args.plugin:
129+
plugin_class = manager.LeveldbPluginManager.GetPlugin(args.plugin)
130+
else:
131+
plugin_class = None
132+
96133
log_file = log.FileReader(args.source)
97134

98135
if args.structure_type == 'blocks':
@@ -114,7 +151,11 @@ def LogCommand(args):
114151
or not args.structure_type):
115152
# Prints key value record information.
116153
for internal_key_record in log_file.GetParsedInternalKeys():
117-
_Output(internal_key_record, output=args.output)
154+
if plugin_class:
155+
plugin_record = plugin_class.FromKeyValueRecord(internal_key_record)
156+
_Output(plugin_record, output=args.output)
157+
else:
158+
_Output(internal_key_record, output=args.output)
118159

119160
else:
120161
print(f'{args.structure_type} is not supported for log files.')
@@ -146,6 +187,7 @@ def DescriptorCommand(args):
146187
else:
147188
print(f'{args.structure_type} is not supported for descriptor files.')
148189

190+
149191
def App():
150192
"""The CLI app entrypoint for parsing leveldb files."""
151193
parser = argparse.ArgumentParser(
@@ -182,6 +224,9 @@ def App():
182224
'repr'],
183225
default='json',
184226
help='Output format. Default is json')
227+
parser_db.add_argument(
228+
'--plugin',
229+
help='Use plugin to parse records.')
185230
parser_db.set_defaults(func=DbCommand)
186231

187232
parser_log = subparsers.add_parser(
@@ -200,6 +245,9 @@ def App():
200245
'repr'],
201246
default='json',
202247
help='Output format. Default is json')
248+
parser_log.add_argument(
249+
'--plugin',
250+
help='Use plugin to parse records.')
203251
parser_log.add_argument(
204252
'-t',
205253
'--structure_type',
@@ -227,6 +275,9 @@ def App():
227275
'repr'],
228276
default='json',
229277
help='Output format. Default is json')
278+
parser_ldb.add_argument(
279+
'--plugin',
280+
help='Use plugin to parse records.')
230281
parser_ldb.add_argument(
231282
'-t',
232283
'--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
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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 Any, Union
22+
23+
try:
24+
from dfdatetime import webkit_time
25+
from google.protobuf.json_format import MessageToJson
26+
from dfindexeddb.leveldb.plugins import notification_database_data_pb2 as notification_pb2
27+
_has_import_dependencies = True
28+
except ImportError as err:
29+
_has_import_dependencies = False
30+
logging.warning(f'Could not import dependencies for leveldb.plugins.chrome_notifications: %s', err)
31+
32+
from dfindexeddb.indexeddb.chromium import blink
33+
from dfindexeddb.leveldb.plugins import interface
34+
from dfindexeddb.leveldb.plugins import manager
35+
from dfindexeddb.leveldb import record
36+
37+
38+
@dataclasses.dataclass
39+
class ChromeNotificationRecord(interface.LeveldbPlugin):
40+
src_file: str = None
41+
offset: int = None
42+
key: str = None
43+
sequence_number: int = None
44+
type: int = None
45+
origin: str = None
46+
service_worker_registration_id: int = None
47+
notification_title: str = None
48+
notification_direction: str = None
49+
notification_lang: str = None
50+
notification_body: str = None
51+
notification_tag: str = None
52+
notification_icon: str = None
53+
notification_silent: bool = None
54+
notification_data: str = None
55+
notification_require_interaction: bool = None
56+
notification_time: str = None
57+
notification_renotify: bool = None
58+
notification_badge: str = None
59+
notification_image: str = None
60+
notification_id: str = None
61+
replaced_existing_notification: bool = None
62+
num_clicks: int = None
63+
num_action_button_clicks: int = None
64+
creation_time: str = None
65+
closed_reason: str = None
66+
has_triggered: bool = None
67+
68+
@classmethod
69+
def FromKeyValueRecord(
70+
cls,
71+
ldb_record
72+
) -> ChromeNotificationRecord:
73+
record = cls()
74+
record.offset = ldb_record.offset
75+
record.key = ldb_record.key.decode()
76+
record.sequence_number = ldb_record.sequence_number
77+
record.type = ldb_record.record_type
78+
79+
if not ldb_record.value:
80+
return record
81+
82+
notification_proto = notification_pb2.NotificationDatabaseDataProto()
83+
notification_proto.ParseFromString(ldb_record.value)
84+
85+
record.origin = notification_proto.origin
86+
record.service_worker_registration_id = (
87+
notification_proto.service_worker_registration_id)
88+
record.notification_title = notification_proto.notification_data.title
89+
record.notification_direction = (
90+
notification_proto.notification_data.direction)
91+
record.notification_lang = notification_proto.notification_data.lang
92+
record.notification_body = notification_proto.notification_data.body
93+
record.notification_tag = notification_proto.notification_data.tag
94+
record.notification_icon = notification_proto.notification_data.icon
95+
record.notification_silent = notification_proto.notification_data.silent
96+
record.notification_data = notification_proto.notification_data.data
97+
record.notification_require_interaction = (
98+
notification_proto.notification_data.require_interaction)
99+
record.notification_time = webkit_time.WebKitTime(
100+
timestamp=notification_proto.notification_data.timestamp
101+
).CopyToDateTimeString()
102+
record.notification_renotify = notification_proto.notification_data.renotify
103+
record.notification_badge = notification_proto.notification_data.badge
104+
record.notification_image = notification_proto.notification_data.image
105+
record.notification_id = notification_proto.notification_id
106+
record.replaced_existing_notification = (
107+
notification_proto.replaced_existing_notification)
108+
record.num_clicks = notification_proto.num_clicks
109+
record.num_action_button_clicks = (
110+
notification_proto.num_action_button_clicks)
111+
record.creation_time = webkit_time.WebKitTime(
112+
timestamp=notification_proto.creation_time_millis
113+
).CopyToDateTimeString()
114+
record.closed_reason = notification_proto.closed_reason
115+
record.has_triggered = notification_proto.has_triggered
116+
117+
if not notification_proto.notification_data.data:
118+
return record
119+
120+
notification_data = blink.V8ScriptValueDecoder(
121+
raw_data=notification_proto.notification_data.data).Deserialize()
122+
record.notification_data = notification_data
123+
124+
return record
125+
126+
127+
# check if dependencies are in existence..
128+
129+
if _has_import_dependencies:
130+
manager.PluginManager.RegisterPlugin(ChromeNotificationRecord)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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 Union
17+
18+
from dfindexeddb.leveldb import record
19+
20+
21+
class LeveldbPlugin:
22+
23+
@classmethod
24+
def FromLevelDBRecord(cls,
25+
ldb_record: record.LevelDBRecord):
26+
"""Parses a leveldb record."""
27+
parsed_record = cls.FromKeyValueRecord(ldb_record.record)
28+
ldb_record.record = parsed_record
29+
return ldb_record
30+
31+
@classmethod
32+
def FromKeyValueRecord(cls, ldb_record):
33+
"""Parses a leveldb key value record."""

0 commit comments

Comments
 (0)