From d4b682e937e5063dc8a45a5b9e20d22d7febb67c Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Fri, 5 Jan 2024 16:23:49 +0100 Subject: [PATCH 01/25] Add support for XML configuration files (DIS-2157) --- dissect/target/helpers/configutil.py | 59 ++++++++++++++++++++++++- tests/_data/helpers/configutil/test.xml | 17 +++++++ tests/filesystems/test_config.py | 58 ++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 tests/_data/helpers/configutil/test.xml diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 2d0ec4b34..217b139d6 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -20,6 +20,8 @@ Union, ) +from defusedxml import ElementTree + from dissect.target.exceptions import ConfigurationParsingError, FileNotFoundError from dissect.target.filesystem import FilesystemEntry from dissect.target.helpers.fsutil import TargetPath @@ -254,6 +256,59 @@ def parse_file(self, fh: TextIO) -> None: self.parsed_data = {"content": fh.read(), "size": str(fh.tell())} +class Xml(ConfigurationParser): + def _tree(self, tree: ElementTree) -> dict: + nodes = {} + counter = {} + for node in tree.findall("*"): + if node.tag in counter: + counter[node.tag] += 1 + nodes[f"{node.tag}-{counter[node.tag]}"] = self._tree(node) + else: + counter[node.tag] = 1 + nodes[f"{node.tag}"] = self._tree(node) + result = {"tag": tree.tag} + if tree.attrib: + result["attributes"] = tree.attrib + if nodes: + result["nodes"] = nodes + + text = str(tree.text).strip(" \n\r") + if text: + result["text"] = text + + return result + + def _fix(self, content: str, position: tuple(int, int)) -> str: + """Quick heuritsic fix. If there is an invalid token, just remove it""" + lineno, offset = position + lines = content.split("\n") + line = lines[lineno - 1] + line = line[: offset - 1] + "" + line[offset + 1 :] + lines[lineno - 1] = line + return "\n".join(lines) + + def parse_file(self, fh: TextIO) -> None: + content = fh.read() + document = content + errors = 0 + limit = 20 + tree = None + while not tree and errors < limit: + try: + tree = self._tree(ElementTree.fromstring(document)) + break + except ElementTree.ParseError as err: + errors += 1 + document = self._fix(document, err.position) + + if not tree: + # Document could not be parsed, we give up + self.parsed_data = {"nodes": tree, "errors": errors, "text": content} + else: + self.parsed_data = {"nodes": tree, "errors": errors} + + class ScopeManager: """A (context)manager for dictionary scoping. @@ -528,11 +583,12 @@ def create_parser(self, options: Optional[ParserOptions] = None) -> Configuratio "*/systemd/*": ParserConfig(SystemD), "*/sysconfig/network-scripts/ifcfg-*": ParserConfig(Default), "*/sysctl.d/*.conf": ParserConfig(Default), + "*/xml/*": ParserConfig(Xml), } CONFIG_MAP: dict[tuple[str, ...], ParserConfig] = { "ini": ParserConfig(Ini), - "xml": ParserConfig(Txt), + "xml": ParserConfig(Xml), "json": ParserConfig(Txt), "cnf": ParserConfig(Default), "conf": ParserConfig(Default, separator=(r"\s",)), @@ -549,6 +605,7 @@ def create_parser(self, options: Optional[ParserOptions] = None) -> Configuratio "hosts": ParserConfig(Default, separator=(r"\s",)), "nsswitch.conf": ParserConfig(Default, separator=(":",)), "lsb-release": ParserConfig(Default), + "catalog": ParserConfig(Xml), } diff --git a/tests/_data/helpers/configutil/test.xml b/tests/_data/helpers/configutil/test.xml new file mode 100644 index 000000000..00577232a --- /dev/null +++ b/tests/_data/helpers/configutil/test.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/tests/filesystems/test_config.py b/tests/filesystems/test_config.py index d201afefa..7ab647010 100644 --- a/tests/filesystems/test_config.py +++ b/tests/filesystems/test_config.py @@ -73,6 +73,64 @@ def mapped_file(test_file: str, fs_unix: VirtualFilesystem) -> VirtualFilesystem }, }, ), + ( + "_data/helpers/configutil/test.xml", + { + "nodes": { + "tag": "Server", + "attributes": {"port": "8005", "shutdown": "SHUTDOWN"}, + "nodes": { + "Listener": { + "tag": "Listener", + "attributes": {"className": "org.apache.catalina.core.JasperListener"}, + }, + "Service": { + "tag": "Service", + "attributes": {"name": "Catalina"}, + "nodes": { + "Connector": { + "tag": "Connector", + "attributes": { + "port": "8080", + "protocol": "HTTP/1.1", + "connectionTimeout": "20000", + "redirectPort": "8443", + }, + }, + "Engine": { + "tag": "Engine", + "attributes": {"name": "Catalina", "defaultHost": "localhost"}, + "nodes": { + "Host": { + "tag": "Host", + "attributes": { + "name": "localhost", + "appBase": "webapps", + "unpackWARs": "true", + "autoDeploy": "true", + }, + "nodes": { + "Valve": { + "tag": "Valve", + "attributes": { + "className": "org.apache.catalina.valves.AccessLogValve", + "directory": "logs", + "prefix": "localhost_access_log.", + "suffix": ".txt", + "pattern": "%h %l %u %t s", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "errors": 3, + }, + ), ], ) def test_parse_file_input(target_unix: Target, mapped_file: str, expected_output: dict) -> None: From b7f4402482bfd75bbb57e03fcc3d887c6f309ff9 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Fri, 5 Jan 2024 16:54:03 +0100 Subject: [PATCH 02/25] Update dissect/target/helpers/configutil.py Co-authored-by: Erik Schamper <1254028+Schamper@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 217b139d6..b094c10b1 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -280,7 +280,7 @@ def _tree(self, tree: ElementTree) -> dict: return result def _fix(self, content: str, position: tuple(int, int)) -> str: - """Quick heuritsic fix. If there is an invalid token, just remove it""" + """Quick heuristic fix. If there is an invalid token, just remove it.""" lineno, offset = position lines = content.split("\n") line = lines[lineno - 1] From 1ed21712cfd38b05c98166ba0a1e11b3eb9fe208 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:38:36 +0100 Subject: [PATCH 03/25] Update dissect/target/helpers/configutil.py Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 38 +++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index b094c10b1..7fa5cc80d 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -258,26 +258,30 @@ def parse_file(self, fh: TextIO) -> None: class Xml(ConfigurationParser): def _tree(self, tree: ElementTree) -> dict: - nodes = {} - counter = {} - for node in tree.findall("*"): - if node.tag in counter: - counter[node.tag] += 1 - nodes[f"{node.tag}-{counter[node.tag]}"] = self._tree(node) - else: - counter[node.tag] = 1 - nodes[f"{node.tag}"] = self._tree(node) - result = {"tag": tree.tag} + root = {tree.tag: {} if tree.attrib else None} + + children = list(tree) + + if children: + childs = defaultdict(list) + for child in map(self._tree, children): + for key, value in child.items(): + childs[key].append(value) + + root = {tree.tag: {key: value[0] if len(value) == 1 else value for key, value in childs.items()}} + if tree.attrib: - result["attributes"] = tree.attrib - if nodes: - result["nodes"] = nodes + root[tree.tag].update((key, value) for key, value in tree.attrib.items()) - text = str(tree.text).strip(" \n\r") - if text: - result["text"] = text + if tree.text: + text = tree.text.strip() + if children or tree.attrib: + if text: + root[tree.tag] = text + else: + root[tree.tag] = text - return result + return root def _fix(self, content: str, position: tuple(int, int)) -> str: """Quick heuristic fix. If there is an invalid token, just remove it.""" From 8d39839dd79fdc6fd8073d2f9de8d1203703d096 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:38:46 +0100 Subject: [PATCH 04/25] Update dissect/target/helpers/configutil.py Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 7fa5cc80d..9a8aa581f 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -297,7 +297,7 @@ def parse_file(self, fh: TextIO) -> None: document = content errors = 0 limit = 20 - tree = None + tree = {} while not tree and errors < limit: try: tree = self._tree(ElementTree.fromstring(document)) From 351995d3cc61babccb9776cba60123e1cebcdd99 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:39:16 +0100 Subject: [PATCH 05/25] Update dissect/target/helpers/configutil.py Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 9a8aa581f..8303c517a 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -308,9 +308,11 @@ def parse_file(self, fh: TextIO) -> None: if not tree: # Document could not be parsed, we give up - self.parsed_data = {"nodes": tree, "errors": errors, "text": content} - else: - self.parsed_data = {"nodes": tree, "errors": errors} + if not tree: + # Document could not be parsed, we give up + raise ConfigurationParsingError(f"Could not parse XML file: {fh.name} after {errors} attempts.") + + self.parsed_data = {**tree, "errors": errors} class ScopeManager: From 53f22d860ca9c03177ce06ed58b86e61518fb93d Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Fri, 5 Jan 2024 16:23:49 +0100 Subject: [PATCH 06/25] Add support for XML configuration files (DIS-2157) --- dissect/target/helpers/configutil.py | 59 ++++++++++++++++++++++++- tests/_data/helpers/configutil/test.xml | 17 +++++++ tests/filesystems/test_config.py | 58 ++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 tests/_data/helpers/configutil/test.xml diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 2d0ec4b34..217b139d6 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -20,6 +20,8 @@ Union, ) +from defusedxml import ElementTree + from dissect.target.exceptions import ConfigurationParsingError, FileNotFoundError from dissect.target.filesystem import FilesystemEntry from dissect.target.helpers.fsutil import TargetPath @@ -254,6 +256,59 @@ def parse_file(self, fh: TextIO) -> None: self.parsed_data = {"content": fh.read(), "size": str(fh.tell())} +class Xml(ConfigurationParser): + def _tree(self, tree: ElementTree) -> dict: + nodes = {} + counter = {} + for node in tree.findall("*"): + if node.tag in counter: + counter[node.tag] += 1 + nodes[f"{node.tag}-{counter[node.tag]}"] = self._tree(node) + else: + counter[node.tag] = 1 + nodes[f"{node.tag}"] = self._tree(node) + result = {"tag": tree.tag} + if tree.attrib: + result["attributes"] = tree.attrib + if nodes: + result["nodes"] = nodes + + text = str(tree.text).strip(" \n\r") + if text: + result["text"] = text + + return result + + def _fix(self, content: str, position: tuple(int, int)) -> str: + """Quick heuritsic fix. If there is an invalid token, just remove it""" + lineno, offset = position + lines = content.split("\n") + line = lines[lineno - 1] + line = line[: offset - 1] + "" + line[offset + 1 :] + lines[lineno - 1] = line + return "\n".join(lines) + + def parse_file(self, fh: TextIO) -> None: + content = fh.read() + document = content + errors = 0 + limit = 20 + tree = None + while not tree and errors < limit: + try: + tree = self._tree(ElementTree.fromstring(document)) + break + except ElementTree.ParseError as err: + errors += 1 + document = self._fix(document, err.position) + + if not tree: + # Document could not be parsed, we give up + self.parsed_data = {"nodes": tree, "errors": errors, "text": content} + else: + self.parsed_data = {"nodes": tree, "errors": errors} + + class ScopeManager: """A (context)manager for dictionary scoping. @@ -528,11 +583,12 @@ def create_parser(self, options: Optional[ParserOptions] = None) -> Configuratio "*/systemd/*": ParserConfig(SystemD), "*/sysconfig/network-scripts/ifcfg-*": ParserConfig(Default), "*/sysctl.d/*.conf": ParserConfig(Default), + "*/xml/*": ParserConfig(Xml), } CONFIG_MAP: dict[tuple[str, ...], ParserConfig] = { "ini": ParserConfig(Ini), - "xml": ParserConfig(Txt), + "xml": ParserConfig(Xml), "json": ParserConfig(Txt), "cnf": ParserConfig(Default), "conf": ParserConfig(Default, separator=(r"\s",)), @@ -549,6 +605,7 @@ def create_parser(self, options: Optional[ParserOptions] = None) -> Configuratio "hosts": ParserConfig(Default, separator=(r"\s",)), "nsswitch.conf": ParserConfig(Default, separator=(":",)), "lsb-release": ParserConfig(Default), + "catalog": ParserConfig(Xml), } diff --git a/tests/_data/helpers/configutil/test.xml b/tests/_data/helpers/configutil/test.xml new file mode 100644 index 000000000..00577232a --- /dev/null +++ b/tests/_data/helpers/configutil/test.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/tests/filesystems/test_config.py b/tests/filesystems/test_config.py index d201afefa..7ab647010 100644 --- a/tests/filesystems/test_config.py +++ b/tests/filesystems/test_config.py @@ -73,6 +73,64 @@ def mapped_file(test_file: str, fs_unix: VirtualFilesystem) -> VirtualFilesystem }, }, ), + ( + "_data/helpers/configutil/test.xml", + { + "nodes": { + "tag": "Server", + "attributes": {"port": "8005", "shutdown": "SHUTDOWN"}, + "nodes": { + "Listener": { + "tag": "Listener", + "attributes": {"className": "org.apache.catalina.core.JasperListener"}, + }, + "Service": { + "tag": "Service", + "attributes": {"name": "Catalina"}, + "nodes": { + "Connector": { + "tag": "Connector", + "attributes": { + "port": "8080", + "protocol": "HTTP/1.1", + "connectionTimeout": "20000", + "redirectPort": "8443", + }, + }, + "Engine": { + "tag": "Engine", + "attributes": {"name": "Catalina", "defaultHost": "localhost"}, + "nodes": { + "Host": { + "tag": "Host", + "attributes": { + "name": "localhost", + "appBase": "webapps", + "unpackWARs": "true", + "autoDeploy": "true", + }, + "nodes": { + "Valve": { + "tag": "Valve", + "attributes": { + "className": "org.apache.catalina.valves.AccessLogValve", + "directory": "logs", + "prefix": "localhost_access_log.", + "suffix": ".txt", + "pattern": "%h %l %u %t s", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "errors": 3, + }, + ), ], ) def test_parse_file_input(target_unix: Target, mapped_file: str, expected_output: dict) -> None: From 1aecf767e929e105301bf84f59e0ac75aa3a23ac Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Fri, 5 Jan 2024 16:54:03 +0100 Subject: [PATCH 07/25] Update dissect/target/helpers/configutil.py Co-authored-by: Erik Schamper <1254028+Schamper@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 217b139d6..b094c10b1 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -280,7 +280,7 @@ def _tree(self, tree: ElementTree) -> dict: return result def _fix(self, content: str, position: tuple(int, int)) -> str: - """Quick heuritsic fix. If there is an invalid token, just remove it""" + """Quick heuristic fix. If there is an invalid token, just remove it.""" lineno, offset = position lines = content.split("\n") line = lines[lineno - 1] From 7411820e53e157aca4335c9664ba4b9e9db6588e Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:38:36 +0100 Subject: [PATCH 08/25] Update dissect/target/helpers/configutil.py Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 38 +++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index b094c10b1..7fa5cc80d 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -258,26 +258,30 @@ def parse_file(self, fh: TextIO) -> None: class Xml(ConfigurationParser): def _tree(self, tree: ElementTree) -> dict: - nodes = {} - counter = {} - for node in tree.findall("*"): - if node.tag in counter: - counter[node.tag] += 1 - nodes[f"{node.tag}-{counter[node.tag]}"] = self._tree(node) - else: - counter[node.tag] = 1 - nodes[f"{node.tag}"] = self._tree(node) - result = {"tag": tree.tag} + root = {tree.tag: {} if tree.attrib else None} + + children = list(tree) + + if children: + childs = defaultdict(list) + for child in map(self._tree, children): + for key, value in child.items(): + childs[key].append(value) + + root = {tree.tag: {key: value[0] if len(value) == 1 else value for key, value in childs.items()}} + if tree.attrib: - result["attributes"] = tree.attrib - if nodes: - result["nodes"] = nodes + root[tree.tag].update((key, value) for key, value in tree.attrib.items()) - text = str(tree.text).strip(" \n\r") - if text: - result["text"] = text + if tree.text: + text = tree.text.strip() + if children or tree.attrib: + if text: + root[tree.tag] = text + else: + root[tree.tag] = text - return result + return root def _fix(self, content: str, position: tuple(int, int)) -> str: """Quick heuristic fix. If there is an invalid token, just remove it.""" From 8dbe3d69ce51c39bb40406aed7971e6888b7f150 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:38:46 +0100 Subject: [PATCH 09/25] Update dissect/target/helpers/configutil.py Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 7fa5cc80d..9a8aa581f 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -297,7 +297,7 @@ def parse_file(self, fh: TextIO) -> None: document = content errors = 0 limit = 20 - tree = None + tree = {} while not tree and errors < limit: try: tree = self._tree(ElementTree.fromstring(document)) From 2e7a990975c4f29163cb4f75abdc75703d9d8ca9 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:39:16 +0100 Subject: [PATCH 10/25] Update dissect/target/helpers/configutil.py Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 9a8aa581f..8303c517a 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -308,9 +308,11 @@ def parse_file(self, fh: TextIO) -> None: if not tree: # Document could not be parsed, we give up - self.parsed_data = {"nodes": tree, "errors": errors, "text": content} - else: - self.parsed_data = {"nodes": tree, "errors": errors} + if not tree: + # Document could not be parsed, we give up + raise ConfigurationParsingError(f"Could not parse XML file: {fh.name} after {errors} attempts.") + + self.parsed_data = {**tree, "errors": errors} class ScopeManager: From 601c79a10cb7c7cee3a44e577ce6dfe460ddf1a4 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Tue, 16 Jan 2024 12:33:49 +0100 Subject: [PATCH 11/25] Integrate feedback. --- dissect/target/helpers/configutil.py | 6 +- dissect/target/helpers/mount.py | 3 +- dissect/target/tools/mount.py | 3 +- tests/_data/helpers/configutil/test.xml | 3 +- tests/filesystems/test_config.py | 74 +++++++++---------------- 5 files changed, 33 insertions(+), 56 deletions(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 8303c517a..0d6592efc 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -2,7 +2,7 @@ import io import re -from collections import deque +from collections import defaultdict, deque from configparser import ConfigParser, MissingSectionHeaderError from dataclasses import dataclass from fnmatch import fnmatch @@ -257,6 +257,8 @@ def parse_file(self, fh: TextIO) -> None: class Xml(ConfigurationParser): + """Parses XML-files, ignores any constructor parameters.""" + def _tree(self, tree: ElementTree) -> dict: root = {tree.tag: {} if tree.attrib else None} @@ -306,8 +308,6 @@ def parse_file(self, fh: TextIO) -> None: errors += 1 document = self._fix(document, err.position) - if not tree: - # Document could not be parsed, we give up if not tree: # Document could not be parsed, we give up raise ConfigurationParsingError(f"Could not parse XML file: {fh.name} after {errors} attempts.") diff --git a/dissect/target/helpers/mount.py b/dissect/target/helpers/mount.py index 1f81b1627..060285b9f 100644 --- a/dissect/target/helpers/mount.py +++ b/dissect/target/helpers/mount.py @@ -3,9 +3,8 @@ from functools import lru_cache from typing import BinaryIO, Optional -from fuse import FuseOSError, Operations - from dissect.target.filesystem import Filesystem, FilesystemEntry +from fuse import FuseOSError, Operations log = logging.getLogger(__name__) diff --git a/dissect/target/tools/mount.py b/dissect/target/tools/mount.py index 7e4c31ac3..99efd29b0 100644 --- a/dissect/target/tools/mount.py +++ b/dissect/target/tools/mount.py @@ -12,9 +12,8 @@ ) try: - from fuse import FUSE - from dissect.target.helpers.mount import DissectMount + from fuse import FUSE HAS_FUSE = True except Exception: diff --git a/tests/_data/helpers/configutil/test.xml b/tests/_data/helpers/configutil/test.xml index 00577232a..15500dea4 100644 --- a/tests/_data/helpers/configutil/test.xml +++ b/tests/_data/helpers/configutil/test.xml @@ -1,6 +1,7 @@ - + a + b VirtualFilesystem ( "_data/helpers/configutil/test.xml", { - "nodes": { - "tag": "Server", - "attributes": {"port": "8005", "shutdown": "SHUTDOWN"}, - "nodes": { - "Listener": { - "tag": "Listener", - "attributes": {"className": "org.apache.catalina.core.JasperListener"}, + "Server": { + "Listener": ["a", "b"], # @todo add attributes on list items + "Service": { + "Connector": { + "port": "8080", + "protocol": "HTTP/1.1", + "connectionTimeout": "20000", + "redirectPort": "8443", }, - "Service": { - "tag": "Service", - "attributes": {"name": "Catalina"}, - "nodes": { - "Connector": { - "tag": "Connector", - "attributes": { - "port": "8080", - "protocol": "HTTP/1.1", - "connectionTimeout": "20000", - "redirectPort": "8443", - }, - }, - "Engine": { - "tag": "Engine", - "attributes": {"name": "Catalina", "defaultHost": "localhost"}, - "nodes": { - "Host": { - "tag": "Host", - "attributes": { - "name": "localhost", - "appBase": "webapps", - "unpackWARs": "true", - "autoDeploy": "true", - }, - "nodes": { - "Valve": { - "tag": "Valve", - "attributes": { - "className": "org.apache.catalina.valves.AccessLogValve", - "directory": "logs", - "prefix": "localhost_access_log.", - "suffix": ".txt", - "pattern": "%h %l %u %t s", - }, - }, - }, - }, - }, + "Engine": { + "Host": { + "Valve": { + "className": "org.apache.catalina.valves.AccessLogValve", + "directory": "logs", + "prefix": "localhost_access_log.", + "suffix": ".txt", + "pattern": "%h %l %u %t s", }, + "name": "localhost", + "appBase": "webapps", + "unpackWARs": "true", + "autoDeploy": "true", }, + "name": "Catalina", + "defaultHost": "localhost", }, + "name": "Catalina", }, - }, - "errors": 3, + "port": "8005", + "shutdown": "SHUTDOWN", + } }, ), ], From 20df2645bb082ea210ad89c43a2c8a2dcc8d7c9f Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:19:34 +0100 Subject: [PATCH 12/25] Integrate feedback. --- tests/filesystems/test_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/filesystems/test_config.py b/tests/filesystems/test_config.py index 1a20f1578..c4d5c0f10 100644 --- a/tests/filesystems/test_config.py +++ b/tests/filesystems/test_config.py @@ -77,7 +77,7 @@ def mapped_file(test_file: str, fs_unix: VirtualFilesystem) -> VirtualFilesystem "_data/helpers/configutil/test.xml", { "Server": { - "Listener": ["a", "b"], # @todo add attributes on list items + "Listener": ["a", "b"], # @todo add attributes on list items "Service": { "Connector": { "port": "8080", From c9aee01ba0c0c3216d84c4af0250f87a78274555 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:27:43 +0100 Subject: [PATCH 13/25] Integrate feedback. --- dissect/target/helpers/mount.py | 3 ++- dissect/target/tools/mount.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dissect/target/helpers/mount.py b/dissect/target/helpers/mount.py index 060285b9f..1f81b1627 100644 --- a/dissect/target/helpers/mount.py +++ b/dissect/target/helpers/mount.py @@ -3,9 +3,10 @@ from functools import lru_cache from typing import BinaryIO, Optional -from dissect.target.filesystem import Filesystem, FilesystemEntry from fuse import FuseOSError, Operations +from dissect.target.filesystem import Filesystem, FilesystemEntry + log = logging.getLogger(__name__) CACHE_SIZE = 1024 * 1024 diff --git a/dissect/target/tools/mount.py b/dissect/target/tools/mount.py index 99efd29b0..7e4c31ac3 100644 --- a/dissect/target/tools/mount.py +++ b/dissect/target/tools/mount.py @@ -12,9 +12,10 @@ ) try: - from dissect.target.helpers.mount import DissectMount from fuse import FUSE + from dissect.target.helpers.mount import DissectMount + HAS_FUSE = True except Exception: HAS_FUSE = False From fb830d66b0a2e33111a3e8695c259b7df5e44149 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:40:48 +0100 Subject: [PATCH 14/25] Integrate feedback. --- tests/plugins/general/test_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/plugins/general/test_config.py b/tests/plugins/general/test_config.py index 0954da08b..d5cf0d63c 100644 --- a/tests/plugins/general/test_config.py +++ b/tests/plugins/general/test_config.py @@ -68,7 +68,7 @@ def test_collapse_types( "hint, data_bytes", [ ("ini", b"[DEFAULT]\nkey=value"), - ("xml", b"currently_just_text"), + ("xml", b"currently_just_text"), ("json", b"currently_just_text"), ("cnf", b"key=value"), ("conf", b"key value"), From 2931f50da755f9b03b3b276edcc4981e654d09d4 Mon Sep 17 00:00:00 2001 From: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> Date: Thu, 18 Jan 2024 09:39:06 +0100 Subject: [PATCH 15/25] Minor parser & test changes --- dissect/target/helpers/configutil.py | 17 +++++++++++++---- tests/filesystems/test_config.py | 5 ++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 0d6592efc..87ffd9a68 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -257,7 +257,7 @@ def parse_file(self, fh: TextIO) -> None: class Xml(ConfigurationParser): - """Parses XML-files, ignores any constructor parameters.""" + """Parses an XML file. Ignores any constructor parameters passed from ``ConfigurationParser`.""" def _tree(self, tree: ElementTree) -> dict: root = {tree.tag: {} if tree.attrib else None} @@ -279,7 +279,9 @@ def _tree(self, tree: ElementTree) -> dict: text = tree.text.strip() if children or tree.attrib: if text: - root[tree.tag] = text + # Case where a xml tag has an attribute and text value. E.g. a + # We add a sentinel value, which we can use to unpack later. + root[tree.tag]["$element_text"] = text else: root[tree.tag] = text @@ -289,9 +291,12 @@ def _fix(self, content: str, position: tuple(int, int)) -> str: """Quick heuristic fix. If there is an invalid token, just remove it.""" lineno, offset = position lines = content.split("\n") + line = lines[lineno - 1] line = line[: offset - 1] + "" + line[offset + 1 :] + lines[lineno - 1] = line + return "\n".join(lines) def parse_file(self, fh: TextIO) -> None: @@ -300,19 +305,23 @@ def parse_file(self, fh: TextIO) -> None: errors = 0 limit = 20 tree = {} + while not tree and errors < limit: try: tree = self._tree(ElementTree.fromstring(document)) + import ipdb + + ipdb.set_trace() break except ElementTree.ParseError as err: errors += 1 document = self._fix(document, err.position) if not tree: - # Document could not be parsed, we give up + # Error limit reached. Thus we consider the document not parseable. raise ConfigurationParsingError(f"Could not parse XML file: {fh.name} after {errors} attempts.") - self.parsed_data = {**tree, "errors": errors} + self.parsed_data = tree class ScopeManager: diff --git a/tests/filesystems/test_config.py b/tests/filesystems/test_config.py index c4d5c0f10..693c3f086 100644 --- a/tests/filesystems/test_config.py +++ b/tests/filesystems/test_config.py @@ -77,7 +77,10 @@ def mapped_file(test_file: str, fs_unix: VirtualFilesystem) -> VirtualFilesystem "_data/helpers/configutil/test.xml", { "Server": { - "Listener": ["a", "b"], # @todo add attributes on list items + "Listener": [ + {"className": "org.apache.catalina.core.JasperListener1", "$element_text": "a"}, + {"className": "org.apache.catalina.core.JasperListener2", "$element_text": "b"}, + ], "Service": { "Connector": { "port": "8080", From d05e1cdf7c56fb344885db3411c3da9638e42993 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Thu, 18 Jan 2024 11:33:48 +0100 Subject: [PATCH 16/25] Remove debug code. --- dissect/target/helpers/configutil.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 87ffd9a68..90a2e2311 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -309,9 +309,6 @@ def parse_file(self, fh: TextIO) -> None: while not tree and errors < limit: try: tree = self._tree(ElementTree.fromstring(document)) - import ipdb - - ipdb.set_trace() break except ElementTree.ParseError as err: errors += 1 From fec51f4f6cc79d839fb6a4f327e73f6f2d8f93bd Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:41:03 +0100 Subject: [PATCH 17/25] Restore previous solution but with comments to explain rationale. --- dissect/target/helpers/configutil.py | 52 ++++++++++-------- tests/filesystems/test_config.py | 80 ++++++++++++++++++---------- 2 files changed, 82 insertions(+), 50 deletions(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 90a2e2311..a36549cff 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -2,7 +2,7 @@ import io import re -from collections import defaultdict, deque +from collections import deque from configparser import ConfigParser, MissingSectionHeaderError from dataclasses import dataclass from fnmatch import fnmatch @@ -260,32 +260,42 @@ class Xml(ConfigurationParser): """Parses an XML file. Ignores any constructor parameters passed from ``ConfigurationParser`.""" def _tree(self, tree: ElementTree) -> dict: - root = {tree.tag: {} if tree.attrib else None} - - children = list(tree) - - if children: - childs = defaultdict(list) - for child in map(self._tree, children): - for key, value in child.items(): - childs[key].append(value) + """Very simple but robust xml -> dict implementation, see comments.""" + nodes = {} + counter = {} + + # each node is a folder (so the structure is always the same! [1]) + for node in tree.findall("*"): + # if a node contains multiple nodes with the same name, number them + if node.tag in counter: + counter[node.tag] += 1 + nodes[f"{node.tag}-{counter[node.tag]}"] = self._tree(node) + else: + counter[node.tag] = 1 + nodes[f"{node.tag}"] = self._tree(node) - root = {tree.tag: {key: value[0] if len(value) == 1 else value for key, value in childs.items()}} + result = {"tag": tree.tag} + # all attribs go in the attribute folder + # (i.e. stable, does not change depending on xml structure! [2] + # Also, this way we "know" they have been attributes, i.e. we don't lose information! [3] if tree.attrib: - root[tree.tag].update((key, value) for key, value in tree.attrib.items()) + result["attributes"] = tree.attrib + # all subnodes go in the nodes folder + if nodes: + result["nodes"] = nodes + + # content goes into the text folder + # we don't use special prefixes ($) because XML docs may use them anyway (even though they are forbidden) if tree.text: - text = tree.text.strip() - if children or tree.attrib: - if text: - # Case where a xml tag has an attribute and text value. E.g. a - # We add a sentinel value, which we can use to unpack later. - root[tree.tag]["$element_text"] = text - else: - root[tree.tag] = text + text = str(tree.text).strip(" \n\r") + if text != "": + result["text"] = text + + # if you need to store meta-data, you can extend add more entries here... CDATA, Comments, errors - return root + return result def _fix(self, content: str, position: tuple(int, int)) -> str: """Quick heuristic fix. If there is an invalid token, just remove it.""" diff --git a/tests/filesystems/test_config.py b/tests/filesystems/test_config.py index 693c3f086..c9d7dd830 100644 --- a/tests/filesystems/test_config.py +++ b/tests/filesystems/test_config.py @@ -76,40 +76,62 @@ def mapped_file(test_file: str, fs_unix: VirtualFilesystem) -> VirtualFilesystem ( "_data/helpers/configutil/test.xml", { - "Server": { - "Listener": [ - {"className": "org.apache.catalina.core.JasperListener1", "$element_text": "a"}, - {"className": "org.apache.catalina.core.JasperListener2", "$element_text": "b"}, - ], + "tag": "Server", + "attributes": {"port": "8005", "shutdown": "SHUTDOWN"}, + "nodes": { + "Listener": { + "tag": "Listener", + "attributes": {"className": "org.apache.catalina.core.JasperListener1"}, + "text": "a", + }, + "Listener-2": { + "tag": "Listener", + "attributes": {"className": "org.apache.catalina.core.JasperListener2"}, + "text": "b", + }, "Service": { - "Connector": { - "port": "8080", - "protocol": "HTTP/1.1", - "connectionTimeout": "20000", - "redirectPort": "8443", - }, - "Engine": { - "Host": { - "Valve": { - "className": "org.apache.catalina.valves.AccessLogValve", - "directory": "logs", - "prefix": "localhost_access_log.", - "suffix": ".txt", - "pattern": "%h %l %u %t s", + "tag": "Service", + "attributes": {"name": "Catalina"}, + "nodes": { + "Connector": { + "tag": "Connector", + "attributes": { + "port": "8080", + "protocol": "HTTP/1.1", + "connectionTimeout": "20000", + "redirectPort": "8443", + }, + }, + "Engine": { + "tag": "Engine", + "attributes": {"name": "Catalina", "defaultHost": "localhost"}, + "nodes": { + "Host": { + "tag": "Host", + "attributes": { + "name": "localhost", + "appBase": "webapps", + "unpackWARs": "true", + "autoDeploy": "true", + }, + "nodes": { + "Valve": { + "tag": "Valve", + "attributes": { + "className": "org.apache.catalina.valves.AccessLogValve", + "directory": "logs", + "prefix": "localhost_access_log.", + "suffix": ".txt", + "pattern": "%h %l %u %t s", + }, + } + }, + } }, - "name": "localhost", - "appBase": "webapps", - "unpackWARs": "true", - "autoDeploy": "true", }, - "name": "Catalina", - "defaultHost": "localhost", }, - "name": "Catalina", }, - "port": "8005", - "shutdown": "SHUTDOWN", - } + }, }, ), ], From e4e1f4f5de70e39102326362c26222836b393444 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:10:47 +0100 Subject: [PATCH 18/25] Update dissect/target/helpers/configutil.py Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index a36549cff..4a7f3840e 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -289,8 +289,7 @@ def _tree(self, tree: ElementTree) -> dict: # content goes into the text folder # we don't use special prefixes ($) because XML docs may use them anyway (even though they are forbidden) if tree.text: - text = str(tree.text).strip(" \n\r") - if text != "": + if text := tree.text.strip(" \n\r"): result["text"] = text # if you need to store meta-data, you can extend add more entries here... CDATA, Comments, errors From 07004e30e01a8b8788782bc986d490ee0065a721 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:12:25 +0100 Subject: [PATCH 19/25] Update dissect/target/helpers/configutil.py Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 4a7f3840e..fe2f23b55 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -272,7 +272,7 @@ def _tree(self, tree: ElementTree) -> dict: nodes[f"{node.tag}-{counter[node.tag]}"] = self._tree(node) else: counter[node.tag] = 1 - nodes[f"{node.tag}"] = self._tree(node) + nodes[node.tag] = self._tree(node) result = {"tag": tree.tag} From 7a29b97a3daf303671c57d13f299dabdfe753fd3 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:14:19 +0100 Subject: [PATCH 20/25] Update dissect/target/helpers/configutil.py Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index fe2f23b55..42b7e638c 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -259,7 +259,7 @@ def parse_file(self, fh: TextIO) -> None: class Xml(ConfigurationParser): """Parses an XML file. Ignores any constructor parameters passed from ``ConfigurationParser`.""" - def _tree(self, tree: ElementTree) -> dict: + def _tree(self, tree: ElementTree, root: bool = False) -> dict: """Very simple but robust xml -> dict implementation, see comments.""" nodes = {} counter = {} From 81e583d173a0a0594dd62f233372e6f5f53a98d2 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:14:29 +0100 Subject: [PATCH 21/25] Update dissect/target/helpers/configutil.py Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 42b7e638c..d7313d447 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -317,7 +317,7 @@ def parse_file(self, fh: TextIO) -> None: while not tree and errors < limit: try: - tree = self._tree(ElementTree.fromstring(document)) + tree = self._tree(ElementTree.fromstring(document), root=True) break except ElementTree.ParseError as err: errors += 1 From 8cdfc19a98236c2c977be5a208bca7d23f18fc71 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:14:37 +0100 Subject: [PATCH 22/25] Update dissect/target/helpers/configutil.py Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index d7313d447..0369200ca 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -262,6 +262,7 @@ class Xml(ConfigurationParser): def _tree(self, tree: ElementTree, root: bool = False) -> dict: """Very simple but robust xml -> dict implementation, see comments.""" nodes = {} + result = {} counter = {} # each node is a folder (so the structure is always the same! [1]) From 416dfb7241074d3f1c7538ea10592853b0ca5314 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:14:45 +0100 Subject: [PATCH 23/25] Update dissect/target/helpers/configutil.py Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 0369200ca..1b594b2a2 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -275,7 +275,6 @@ def _tree(self, tree: ElementTree, root: bool = False) -> dict: counter[node.tag] = 1 nodes[node.tag] = self._tree(node) - result = {"tag": tree.tag} # all attribs go in the attribute folder # (i.e. stable, does not change depending on xml structure! [2] From 8066e7a072810a490cb817227a109518d7cde5f9 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:14:54 +0100 Subject: [PATCH 24/25] Update dissect/target/helpers/configutil.py Co-authored-by: Stefan de Reuver <9864602+Horofic@users.noreply.github.com> --- dissect/target/helpers/configutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 1b594b2a2..6cdf0a3f3 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -293,7 +293,7 @@ def _tree(self, tree: ElementTree, root: bool = False) -> dict: result["text"] = text # if you need to store meta-data, you can extend add more entries here... CDATA, Comments, errors - + result = {tree.tag: result} if root else result return result def _fix(self, content: str, position: tuple(int, int)) -> str: From 8a9992a87b2ea3cbbf9a098d9446083e937f4865 Mon Sep 17 00:00:00 2001 From: cecinestpasunepipe <110607403+cecinestpasunepipe@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:22:57 +0100 Subject: [PATCH 25/25] Implement feedback. --- dissect/target/helpers/configutil.py | 1 - tests/filesystems/test_config.py | 94 +++++++++++++--------------- 2 files changed, 44 insertions(+), 51 deletions(-) diff --git a/dissect/target/helpers/configutil.py b/dissect/target/helpers/configutil.py index 6cdf0a3f3..f0993cd03 100644 --- a/dissect/target/helpers/configutil.py +++ b/dissect/target/helpers/configutil.py @@ -275,7 +275,6 @@ def _tree(self, tree: ElementTree, root: bool = False) -> dict: counter[node.tag] = 1 nodes[node.tag] = self._tree(node) - # all attribs go in the attribute folder # (i.e. stable, does not change depending on xml structure! [2] # Also, this way we "know" they have been attributes, i.e. we don't lose information! [3] diff --git a/tests/filesystems/test_config.py b/tests/filesystems/test_config.py index c9d7dd830..2041ab2dc 100644 --- a/tests/filesystems/test_config.py +++ b/tests/filesystems/test_config.py @@ -76,57 +76,51 @@ def mapped_file(test_file: str, fs_unix: VirtualFilesystem) -> VirtualFilesystem ( "_data/helpers/configutil/test.xml", { - "tag": "Server", - "attributes": {"port": "8005", "shutdown": "SHUTDOWN"}, - "nodes": { - "Listener": { - "tag": "Listener", - "attributes": {"className": "org.apache.catalina.core.JasperListener1"}, - "text": "a", - }, - "Listener-2": { - "tag": "Listener", - "attributes": {"className": "org.apache.catalina.core.JasperListener2"}, - "text": "b", - }, - "Service": { - "tag": "Service", - "attributes": {"name": "Catalina"}, - "nodes": { - "Connector": { - "tag": "Connector", - "attributes": { - "port": "8080", - "protocol": "HTTP/1.1", - "connectionTimeout": "20000", - "redirectPort": "8443", + "Server": { + "attributes": {"port": "8005", "shutdown": "SHUTDOWN"}, + "nodes": { + "Listener": { + "attributes": {"className": "org.apache.catalina.core.JasperListener1"}, + "text": "a", + }, + "Listener-2": { + "attributes": {"className": "org.apache.catalina.core.JasperListener2"}, + "text": "b", + }, + "Service": { + "attributes": {"name": "Catalina"}, + "nodes": { + "Connector": { + "attributes": { + "port": "8080", + "protocol": "HTTP/1.1", + "connectionTimeout": "20000", + "redirectPort": "8443", + }, }, - }, - "Engine": { - "tag": "Engine", - "attributes": {"name": "Catalina", "defaultHost": "localhost"}, - "nodes": { - "Host": { - "tag": "Host", - "attributes": { - "name": "localhost", - "appBase": "webapps", - "unpackWARs": "true", - "autoDeploy": "true", - }, - "nodes": { - "Valve": { - "tag": "Valve", - "attributes": { - "className": "org.apache.catalina.valves.AccessLogValve", - "directory": "logs", - "prefix": "localhost_access_log.", - "suffix": ".txt", - "pattern": "%h %l %u %t s", - }, - } - }, - } + "Engine": { + "attributes": {"name": "Catalina", "defaultHost": "localhost"}, + "nodes": { + "Host": { + "attributes": { + "name": "localhost", + "appBase": "webapps", + "unpackWARs": "true", + "autoDeploy": "true", + }, + "nodes": { + "Valve": { + "attributes": { + "className": "org.apache.catalina.valves.AccessLogValve", + "directory": "logs", + "prefix": "localhost_access_log.", + "suffix": ".txt", + "pattern": "%h %l %u %t s", + }, + } + }, + } + }, }, }, },