:py:mod:`dissect.target.helpers.configutil` =========================================== .. py:module:: dissect.target.helpers.configutil Module Contents --------------- Classes ~~~~~~~ .. autoapisummary:: dissect.target.helpers.configutil.PeekableIterator dissect.target.helpers.configutil.ConfigurationParser dissect.target.helpers.configutil.Default dissect.target.helpers.configutil.CSVish dissect.target.helpers.configutil.Ini dissect.target.helpers.configutil.Txt dissect.target.helpers.configutil.Bin dissect.target.helpers.configutil.Xml dissect.target.helpers.configutil.ListUnwrapper dissect.target.helpers.configutil.Json dissect.target.helpers.configutil.Yaml dissect.target.helpers.configutil.Toml dissect.target.helpers.configutil.Env dissect.target.helpers.configutil.ScopeManager dissect.target.helpers.configutil.Indentation dissect.target.helpers.configutil.SystemD dissect.target.helpers.configutil.Leases dissect.target.helpers.configutil.ParserOptions dissect.target.helpers.configutil.ParserConfig Functions ~~~~~~~~~ .. autoapisummary:: :nosignatures: dissect.target.helpers.configutil.parse dissect.target.helpers.configutil.parse_config Attributes ~~~~~~~~~~ .. autoapisummary:: dissect.target.helpers.configutil.HAS_YAML dissect.target.helpers.configutil.HAS_TOML dissect.target.helpers.configutil.log dissect.target.helpers.configutil.MATCH_MAP dissect.target.helpers.configutil.CONFIG_MAP dissect.target.helpers.configutil.KNOWN_FILES .. py:data:: HAS_YAML :value: True .. py:data:: HAS_TOML :value: True .. py:data:: log .. py:class:: PeekableIterator(iterable: collections.abc.Iterable[str]) .. py:method:: __iter__() -> PeekableIterator .. py:method:: __next__() -> str .. py:method:: peek() -> str | None .. py:class:: ConfigurationParser(collapse: bool | collections.abc.Iterable[str] = False, collapse_inverse: bool = False, separator: tuple[str] = ('=', ), comment_prefixes: tuple[str] = (';', '#')) A configuration parser where you can configure certain aspects of the parsing mechanism. :param collapse: A ``bool`` or an ``Iterator``: If ``True``: it will collapse all the resulting dictionary values. If an ``Iterable`` it will collapse on the keys defined in ``collapse``. :param collapse_inverse: Inverses the collapsing mechanism. Collapse on everything that is not inside ``collapse``. :param separator: Contains what values it should look for as a separator. :param comment_prefixes: Contains what constitutes as a comment. .. py:attribute:: collapse_all .. py:attribute:: collapse .. py:attribute:: separator :value: ('=',) .. py:attribute:: comment_prefixes :value: (';', '#') .. py:attribute:: parsed_data .. py:method:: __getitem__(item: Any) -> dict | str .. py:method:: __contains__(item: str) -> bool .. py:method:: parse_file(fh: TextIO) -> None :abstractmethod: Parse the contents of ``fh`` into key/value pairs. This function should **set** :attr:`parsed_data` as a side_effect. :param fh: The text to parse. .. py:method:: get(item: str, default: Any | None = None) -> Any .. py:method:: read_file(fh: TextIO | io.BytesIO) -> None Parse a configuration file. :raises ConfigurationParsingError: If any exception occurs during during the parsing process. .. py:method:: merge(other: ConfigurationParser) -> ConfigurationParser Merge the contents of another parser into this one. On conflict, the values of the other parser will be used. :param other: The other parser to merge. :returns: The merged parser. .. py:method:: keys() -> collections.abc.KeysView .. py:method:: items() -> collections.abc.ItemsView .. py:method:: as_dict() -> dict .. py:class:: Default(*args, **kwargs) Bases: :py:obj:`ConfigurationParser` Parse a configuration file specified by ``separator`` and ``comment_prefixes``. This parser splits only on the first ``separator`` it finds: .. code-block:: keyvalue -> {"key": "value"} keyvalue continuation -> {"key": "value continuation"} # Unless we collapse values, we add them to a list to not overwrite any values. keyvalue1 keyvalue2 -> {key: [value1, value2]} -> skip .. py:attribute:: SEPARATOR .. py:attribute:: COMMENTS .. py:attribute:: skip_lines .. py:method:: line_reader(fh: TextIO, strip_comments: bool = True) -> collections.abc.Iterator[str] .. py:method:: parse_file(fh: TextIO) -> None Parse the contents of ``fh`` into key/value pairs. This function should **set** :attr:`parsed_data` as a side_effect. :param fh: The text to parse. .. py:class:: CSVish(*args, fields: tuple[str, Ellipsis], **kwargs) Bases: :py:obj:`Default` Parses CSV-ish config files (does not confirm to CSV standard!). .. py:attribute:: fields .. py:attribute:: num_fields .. py:attribute:: maxsplit .. py:method:: parse_file(fh: TextIO) -> None Parse the contents of ``fh`` into key/value pairs. This function should **set** :attr:`parsed_data` as a side_effect. :param fh: The text to parse. .. py:class:: Ini(*args, **kwargs) Bases: :py:obj:`ConfigurationParser` Parses an ini file according using the built-in Python ``ConfigParser``. .. py:attribute:: parsed_data .. py:method:: parse_file(fh: TextIO) -> None Parse the contents of ``fh`` into key/value pairs. This function should **set** :attr:`parsed_data` as a side_effect. :param fh: The text to parse. .. py:class:: Txt(collapse: bool | collections.abc.Iterable[str] = False, collapse_inverse: bool = False, separator: tuple[str] = ('=', ), comment_prefixes: tuple[str] = (';', '#')) Bases: :py:obj:`ConfigurationParser` Read the file into ``content``, and show the bumber of bytes read. .. py:method:: parse_file(fh: TextIO) -> None Parse the contents of ``fh`` into key/value pairs. This function should **set** :attr:`parsed_data` as a side_effect. :param fh: The text to parse. .. py:class:: Bin(collapse: bool | collections.abc.Iterable[str] = False, collapse_inverse: bool = False, separator: tuple[str] = ('=', ), comment_prefixes: tuple[str] = (';', '#')) Bases: :py:obj:`ConfigurationParser` Read the file into ``binary`` and show the number of bytes read. .. py:method:: parse_file(fh: io.BytesIO) -> None Parse the contents of ``fh`` into key/value pairs. This function should **set** :attr:`parsed_data` as a side_effect. :param fh: The text to parse. .. py:class:: Xml(collapse: bool | collections.abc.Iterable[str] = False, collapse_inverse: bool = False, separator: tuple[str] = ('=', ), comment_prefixes: tuple[str] = (';', '#')) Bases: :py:obj:`ConfigurationParser` Parses an XML file. Ignores any constructor parameters passed from ``ConfigurationParser``. .. py:method:: parse_file(fh: TextIO) -> None Parse the contents of ``fh`` into key/value pairs. This function should **set** :attr:`parsed_data` as a side_effect. :param fh: The text to parse. .. py:class:: ListUnwrapper Provides utility functions to unwrap dictionary objects out of lists. .. py:method:: unwrap(data: dict | list) -> dict | list :staticmethod: Transforms a list with dictionaries to a dictionary. The order of the list is preserved. If no dictionary is found, the list remains untouched: .. code-block:: ["value1", "value2"] -> ["value1", "value2"] {"data": "value"} -> {"data": "value"} [{"data": "value"}] -> { "list_item0": { "data": "value" } } .. py:class:: Json(collapse: bool | collections.abc.Iterable[str] = False, collapse_inverse: bool = False, separator: tuple[str] = ('=', ), comment_prefixes: tuple[str] = (';', '#')) Bases: :py:obj:`ConfigurationParser` Parses a JSON file. .. py:method:: parse_file(fh: TextIO) -> None Parse the contents of ``fh`` into key/value pairs. This function should **set** :attr:`parsed_data` as a side_effect. :param fh: The text to parse. .. py:class:: Yaml(collapse: bool | collections.abc.Iterable[str] = False, collapse_inverse: bool = False, separator: tuple[str] = ('=', ), comment_prefixes: tuple[str] = (';', '#')) Bases: :py:obj:`ConfigurationParser` Parses a Yaml file. .. py:method:: parse_file(fh: TextIO) -> None Parse the contents of ``fh`` into key/value pairs. This function should **set** :attr:`parsed_data` as a side_effect. :param fh: The text to parse. .. py:class:: Toml(collapse: bool | collections.abc.Iterable[str] = False, collapse_inverse: bool = False, separator: tuple[str] = ('=', ), comment_prefixes: tuple[str] = (';', '#')) Bases: :py:obj:`ConfigurationParser` Parses a Toml file. .. py:method:: parse_file(fh: TextIO) -> None Parse the contents of ``fh`` into key/value pairs. This function should **set** :attr:`parsed_data` as a side_effect. :param fh: The text to parse. .. py:class:: Env(comments: bool = False, *args, **kwargs) Bases: :py:obj:`ConfigurationParser` Parses ``.env`` file contents according to Docker and bash specification. Does not apply interpolation of substituted values, e.g. ``foo=${bar}`` and does not attempt to parse list or dict strings. Does not support dynamic env files, e.g. ``foo=`bar```. Also does not support multi-line key/value assignments (yet). .. rubric:: References - https://docs.docker.com/compose/environment-variables/variable-interpolation/#env-file-syntax - https://github.com/theskumar/python-dotenv/blob/main/src/dotenv/parser.py .. py:attribute:: RE_KV .. py:attribute:: comments :value: False .. py:attribute:: parsed_data :type: dict | tuple[dict, str | None] .. py:method:: parse_file(fh: TextIO) -> None Parse the contents of ``fh`` into key/value pairs. This function should **set** :attr:`parsed_data` as a side_effect. :param fh: The text to parse. .. py:class:: ScopeManager A (context)manager for dictionary scoping. This class provides utility functions to keep track of scopes inside a dictionary. .. attribute:: _parents A dictionary accounting what child belongs to which parent dictionary. .. attribute:: _root The initial dictionary. .. attribute:: _current The current dictionary. .. attribute:: _previous The node before the current (changed) node. .. py:method:: __enter__() -> Self .. py:method:: __exit__(type: ScopeManager.__exit__.type[BaseException] | None, value: BaseException | None, traceback: types.TracebackType | None) -> None .. py:method:: push(name: str, keep_prev: bool = False) -> Literal[True] Push a new key to the :attr:`_current` dictionary and return that we did. .. py:method:: pop(keep_prev: bool = False) -> bool Pop :attr:`_current` and return whether we changed the :attr:`_parents` dictionary. .. py:method:: update(key: str, value: str) -> None Update the :attr:`_current` dictionary with ``key`` and ``value``. .. py:method:: update_prev(key: str, value: str) -> None Update the :attr:`_previous` dictionary with ``key`` and ``value``. .. py:method:: is_root() -> bool Utility function to check whether the current dictionary is a root dictionary. .. py:method:: clean() -> None Clean up the internal state. This is called automatically when :class:`ScopeManager` is used as a contextmanager. .. py:class:: Indentation(*args, **kwargs) Bases: :py:obj:`Default` This parser is used for files that use a single level of indentation to specify a different scope. Examples of these files are the ``sshd_config`` file. Where "Match" statements use a single layer of indentation to specify a scope for the key value pairs. The parser parses this as the following: .. code-block:: key value key2 value2 -> {"key value": {"key2": "value2"}} .. py:method:: parse_file(fh: TextIO) -> None Parse the contents of ``fh`` into key/value pairs. This function should **set** :attr:`parsed_data` as a side_effect. :param fh: The text to parse. .. py:class:: SystemD(*args, **kwargs) Bases: :py:obj:`Indentation` A :class:`ConfigurationParser` that specifically parses systemd configuration files. .. rubric:: Examples .. code-block:: >>> systemd_data = textwrap.dedent( ''' [Section1] Key=Value [Section2] Key2=Value 2\ Value 2 continued ''' ) >>> parser = SystemD(io.StringIO(systemd_data)) >>> parser.parser_items { "Section1": { "Key": "Value }, "Section2": { "Key2": "Value2 Value 2 continued } } .. py:method:: parse_file(fh: TextIO) -> None Parse the contents of ``fh`` into key/value pairs. This function should **set** :attr:`parsed_data` as a side_effect. :param fh: The text to parse. .. py:class:: Leases(*args, **kwargs) Bases: :py:obj:`Default` A :class:`ConfigurationParser` that specifically parses dhclient ``.leases`` files. .. rubric:: Examples .. code-block:: >>> Leases = textwrap.dedent( ''' lease { interface "eth0"; # A comment that gets ignored fixed-address 1.2.3.4; option dhcp-lease-time 13337; option routers 0.0.0.0; option host-name "hostname"; renew 1 2023/12/31 13:37:00; rebind 2 2023/01/01 01:00:00; expire 3 2024/01/01 13:37:00; # Another comment that gets ignored } ''' ) >>> parser = Leases(io.StringIO(lease)) >>> parser.parsed_data { "lease-0": { "interface": "eth0", "fixed-address": "1.2.3.4" "option": { "dhcp-lease-time": "13337", "routers": "0.0.0.0", "host-name": "hostname" }, "renew": "1 2023/12/31 13:37:00", "rebind": "2 2023/01/01 01:00:00", "expire": "3 2024/01/01 13:37:00", } .. py:method:: parse_file(fh: TextIO) -> None Parse the contents of ``fh`` into key/value pairs. This function should **set** :attr:`parsed_data` as a side_effect. :param fh: The text to parse. .. py:class:: ParserOptions .. py:attribute:: collapse :type: bool | set[str] | None :value: None .. py:attribute:: collapse_inverse :type: bool | None :value: None .. py:attribute:: separator :type: tuple[str] | None :value: None .. py:attribute:: comment_prefixes :type: tuple[str] | None :value: None .. py:class:: ParserConfig .. py:attribute:: parser :type: type[ConfigurationParser] .. py:attribute:: collapse :type: bool | set[str] | None :value: None .. py:attribute:: collapse_inverse :type: bool | None :value: None .. py:attribute:: separator :type: tuple[str] | None :value: None .. py:attribute:: comment_prefixes :type: tuple[str] | None :value: None .. py:attribute:: fields :type: tuple[str, Ellipsis] | None :value: None .. py:method:: create_parser(options: ParserOptions | None = None) -> ConfigurationParser .. py:data:: MATCH_MAP :type: dict[str, ParserConfig] .. py:data:: CONFIG_MAP :type: dict[str, ParserConfig] .. py:data:: KNOWN_FILES :type: dict[str, ParserConfig] .. py:function:: parse(path: pathlib.Path, hint: str | None = None, *args, **kwargs) -> ConfigurationParser Parses the content of an ``path`` or ``entry`` to a dictionary. :param path: The path to either a directory or file. :param hint: What kind of parser should be used. :param collapse: Whether it should collapse everything or just a certain set of keys. :param collapse_inverse: Invert the collapse function to collapse everything but the keys inside ``collapse``. :param separator: The separator that should be used for parsing. :param comment_prefixes: What is specified as a comment. :raises FileNotFoundError: If the ``path`` is not a file. .. py:function:: parse_config(entry: pathlib.Path, hint: str | None = None, options: ParserOptions | None = None) -> ConfigurationParser