Skip to content

Commit

Permalink
Add binary configuration parser (#893)
Browse files Browse the repository at this point in the history
(DIS-2162)
  • Loading branch information
cecinestpasunepipe authored Oct 15, 2024
1 parent bea348a commit e86f92e
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 4 deletions.
4 changes: 4 additions & 0 deletions dissect/target/filesystems/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,14 @@ def open(self) -> BinaryIO:
Returns:
A file-like object holding a byte representation of :attr:`parser_items`.
"""

if isinstance(self.parser_items, ConfigurationParser):
# Currently trying to open the underlying entry
return self.entry.open()

if isinstance(self.parser_items, bytes):
return io.BytesIO(self.parser_items)

output_data = self._write_value_mapping(self.parser_items)
return io.BytesIO(bytes(output_data, "utf-8"))

Expand Down
26 changes: 23 additions & 3 deletions dissect/target/helpers/configutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def parse_file(self, fh: TextIO) -> None:
def get(self, item: str, default: Optional[Any] = None) -> Any:
return self.parsed_data.get(item, default)

def read_file(self, fh: TextIO) -> None:
def read_file(self, fh: TextIO | io.BytesIO) -> None:
"""Parse a configuration file.
Raises:
Expand Down Expand Up @@ -303,6 +303,14 @@ def parse_file(self, fh: TextIO) -> None:
self.parsed_data = {"content": fh.read(), "size": str(fh.tell())}


class Bin(ConfigurationParser):

"""Read the file into ``binary`` and show the number of bytes read"""

def parse_file(self, fh: io.BytesIO) -> None:
self.parsed_data = {"binary": fh.read(), "size": str(fh.tell())}


class Xml(ConfigurationParser):
"""Parses an XML file. Ignores any constructor parameters passed from ``ConfigurationParser`."""

Expand Down Expand Up @@ -733,6 +741,8 @@ def create_parser(self, options: Optional[ParserOptions] = None) -> Configuratio
"*/sysconfig/network-scripts/ifcfg-*": ParserConfig(Default),
"*/sysctl.d/*.conf": ParserConfig(Default),
"*/xml/*": ParserConfig(Xml),
"*.bashrc": ParserConfig(Txt),
"*/vim/vimrc*": ParserConfig(Txt),
}

CONFIG_MAP: dict[tuple[str, ...], ParserConfig] = {
Expand All @@ -744,6 +754,13 @@ def create_parser(self, options: Optional[ParserOptions] = None) -> Configuratio
"cnf": ParserConfig(Default),
"conf": ParserConfig(Default, separator=(r"\s",)),
"sample": ParserConfig(Txt),
"sh": ParserConfig(Txt),
"key": ParserConfig(Txt),
"crt": ParserConfig(Txt),
"pem": ParserConfig(Txt),
"pl": ParserConfig(Txt), # various admin panels
"lua": ParserConfig(Txt), # wireshark etc.
"txt": ParserConfig(Txt),
"systemd": ParserConfig(SystemD),
"template": ParserConfig(Txt),
"toml": ParserConfig(Toml),
Expand All @@ -759,6 +776,7 @@ def create_parser(self, options: Optional[ParserOptions] = None) -> Configuratio
"nsswitch.conf": ParserConfig(Default, separator=(":",)),
"lsb-release": ParserConfig(Default),
"catalog": ParserConfig(Xml),
"ld.so.cache": ParserConfig(Bin),
"fstab": ParserConfig(
CSVish,
separator=(r"\s",),
Expand Down Expand Up @@ -832,9 +850,11 @@ def parse_config(
parser_type = _select_parser(entry, hint)

parser = parser_type.create_parser(options)

with entry.open() as fh:
open_file = io.TextIOWrapper(fh, encoding="utf-8")
if not isinstance(parser, Bin):
open_file = io.TextIOWrapper(fh, encoding="utf-8")
else:
open_file = io.BytesIO(fh.read())
parser.read_file(open_file)

return parser
Expand Down
15 changes: 14 additions & 1 deletion tests/helpers/test_configutil.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from __future__ import annotations

import textwrap
from io import StringIO
from io import BytesIO, StringIO
from pathlib import Path
from typing import TYPE_CHECKING, Union

import pytest

from dissect.target.exceptions import FileNotFoundError
from dissect.target.helpers.configutil import (
Bin,
ConfigurationParser,
CSVish,
Default,
Expand Down Expand Up @@ -269,6 +270,18 @@ def test_json_syntax(data_string: str, expected_data: Union[dict, list]) -> None
assert parser.parsed_data == expected_data


@pytest.mark.parametrize(
"data, expected_data",
[
(b"\x00\x01\x02", {"binary": b"\x00\x01\x02", "size": "3"}),
],
)
def test_bin_parser(data: bytes, expected_data: dict) -> None:
parser = Bin()
parser.parse_file(BytesIO(data))
assert parser.parsed_data == expected_data


@pytest.mark.parametrize(
"fields, separator, comment, data_string, expected_data",
[
Expand Down

0 comments on commit e86f92e

Please sign in to comment.