From f58f1a6466d7ffb3a600774f8c36b5c93279437b Mon Sep 17 00:00:00 2001 From: black_desk Date: Thu, 16 Jan 2025 04:18:37 +0800 Subject: [PATCH] fix: void validate arguments/properties name (#358) --- src/dbus_fast/aio/message_bus.py | 9 +++- src/dbus_fast/introspection.py | 45 +++++++++++++------ src/dbus_fast/message_bus.py | 9 +++- tests/data/sloppy-introspection.xml | 10 +++++ ...ospection.xml => strict-introspection.xml} | 6 +-- tests/test_introspection.py | 37 +++++++++++---- 6 files changed, 89 insertions(+), 27 deletions(-) create mode 100644 tests/data/sloppy-introspection.xml rename tests/data/{introspection.xml => strict-introspection.xml} (87%) diff --git a/src/dbus_fast/aio/message_bus.py b/src/dbus_fast/aio/message_bus.py index a3af716..cae8276 100644 --- a/src/dbus_fast/aio/message_bus.py +++ b/src/dbus_fast/aio/message_bus.py @@ -260,7 +260,11 @@ class MessageBus(BaseMessageBus): return await future async def introspect( - self, bus_name: str, path: str, timeout: float = 30.0 + self, + bus_name: str, + path: str, + timeout: float = 30.0, + validate_property_names: bool = True, ) -> intr.Node: """Get introspection data for the node at the given path from the given bus name. @@ -274,6 +278,8 @@ class MessageBus(BaseMessageBus): :type path: str :param timeout: The timeout to introspect. :type timeout: float + :param validate_property_names: Whether to validate property names or not. + :type validate_property_names: bool :returns: The introspection data for the name at the path. :rtype: :class:`Node ` @@ -295,6 +301,7 @@ class MessageBus(BaseMessageBus): path, partial(self._reply_handler, future), check_callback_type=False, + validate_property_names=validate_property_names, ) timer_handle = self._loop.call_later( diff --git a/src/dbus_fast/introspection.py b/src/dbus_fast/introspection.py index 2c97db5..423009d 100644 --- a/src/dbus_fast/introspection.py +++ b/src/dbus_fast/introspection.py @@ -34,9 +34,6 @@ class Arg: direction: Optional[list[ArgDirection]] = None, name: Optional[str] = None, ): - if name is not None: - assert_member_name_valid(name) - type_ = None if type(signature) is SignatureType: type_ = signature @@ -245,8 +242,10 @@ class Property: name: str, signature: str, access: PropertyAccess = PropertyAccess.READWRITE, + validate: bool = True, ): - assert_member_name_valid(name) + if validate: + assert_member_name_valid(name) tree = get_signature_tree(signature) if len(tree.types) != 1: @@ -259,7 +258,7 @@ class Property: self.access = access self.type = tree.types[0] - def from_xml(element): + def from_xml(element, validate: bool = True): """Convert an :class:`xml.etree.ElementTree.Element` to a :class:`Property`. The element must be valid DBus introspection XML for a ``property``. @@ -279,7 +278,7 @@ class Property: if not signature: raise InvalidIntrospectionError('properties must have a "type" attribute') - return Property(name, signature, access) + return Property(name, signature, access, validate=validate) def to_xml(self) -> ET.Element: """Convert this :class:`Property` into an :class:`xml.etree.ElementTree.Element`.""" @@ -324,7 +323,9 @@ class Interface: self.properties = properties if properties is not None else [] @staticmethod - def from_xml(element: ET.Element) -> "Interface": + def from_xml( + element: ET.Element, validate_property_names: bool = True + ) -> "Interface": """Convert a :class:`xml.etree.ElementTree.Element` into a :class:`Interface`. @@ -348,7 +349,9 @@ class Interface: elif child.tag == "signal": interface.signals.append(Signal.from_xml(child)) elif child.tag == "property": - interface.properties.append(Property.from_xml(child)) + interface.properties.append( + Property.from_xml(child, validate=validate_property_names) + ) return interface @@ -409,7 +412,9 @@ class Node: self.is_root = is_root @staticmethod - def from_xml(element: ET.Element, is_root: bool = False): + def from_xml( + element: ET.Element, is_root: bool = False, validate_property_names: bool = True + ) -> "Node": """Convert an :class:`xml.etree.ElementTree.Element` to a :class:`Node`. The element must be valid DBus introspection XML for a ``node``. @@ -418,6 +423,8 @@ class Node: :type element: :class:`xml.etree.ElementTree.Element` :param is_root: Whether this is the root node :type is_root: bool + :param validate_property_names: Whether to validate property names or not + :type validate_property_names: bool :raises: - :class:`InvalidIntrospectionError ` - If the XML tree is not valid introspection data. @@ -426,20 +433,30 @@ class Node: for child in element: if child.tag == "interface": - node.interfaces.append(Interface.from_xml(child)) + node.interfaces.append( + Interface.from_xml( + child, validate_property_names=validate_property_names + ) + ) elif child.tag == "node": - node.nodes.append(Node.from_xml(child)) + node.nodes.append( + Node.from_xml( + child, validate_property_names=validate_property_names + ) + ) return node @staticmethod - def parse(data: str) -> "Node": + def parse(data: str, validate_property_names: bool = True) -> "Node": """Parse XML data as a string into a :class:`Node`. The string must be valid DBus introspection XML. :param data: The XMl string. :type data: str + :param validate_property_names: Whether to validate property names or not + :type validate_property_names: bool :raises: - :class:`InvalidIntrospectionError ` - If the string is not valid introspection data. @@ -450,7 +467,9 @@ class Node: 'introspection data must have a "node" for the root element' ) - return Node.from_xml(element, is_root=True) + return Node.from_xml( + element, is_root=True, validate_property_names=validate_property_names + ) def to_xml(self) -> ET.Element: """Convert this :class:`Node` into an :class:`xml.etree.ElementTree.Element`.""" diff --git a/src/dbus_fast/message_bus.py b/src/dbus_fast/message_bus.py index 2534127..d0aa795 100644 --- a/src/dbus_fast/message_bus.py +++ b/src/dbus_fast/message_bus.py @@ -262,6 +262,7 @@ class BaseMessageBus: path: str, callback: Callable[[Optional[intr.Node], Optional[Exception]], None], check_callback_type: bool = True, + validate_property_names: bool = True, ) -> None: """Get introspection data for the node at the given path from the given bus name. @@ -276,6 +277,10 @@ class BaseMessageBus: :param callback: A callback that will be called with the introspection data as a :class:`Node `. :type callback: :class:`Callable` + :param check_callback_type: Whether to check callback type or not. + :type check_callback_type: bool + :param validate_property_names: Whether to validate property names or not. + :type validate_property_names: bool :raises: - :class:`InvalidObjectPathError ` - If the given object path is not valid. @@ -287,7 +292,9 @@ class BaseMessageBus: def reply_notify(reply: Optional[Message], err: Optional[Exception]) -> None: try: BaseMessageBus._check_method_return(reply, err, "s") - result = intr.Node.parse(reply.body[0]) # type: ignore[union-attr] + result = intr.Node.parse( + reply.body[0], validate_property_names=validate_property_names + ) # type: ignore[union-attr] except Exception as e: callback(None, e) return diff --git a/tests/data/sloppy-introspection.xml b/tests/data/sloppy-introspection.xml new file mode 100644 index 0000000..fa7d73a --- /dev/null +++ b/tests/data/sloppy-introspection.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/tests/data/introspection.xml b/tests/data/strict-introspection.xml similarity index 87% rename from tests/data/introspection.xml rename to tests/data/strict-introspection.xml index 56fa0f4..869663a 100644 --- a/tests/data/introspection.xml +++ b/tests/data/strict-introspection.xml @@ -5,7 +5,7 @@ - + @@ -16,11 +16,11 @@ - + - + diff --git a/tests/test_introspection.py b/tests/test_introspection.py index b110cc8..b64c828 100644 --- a/tests/test_introspection.py +++ b/tests/test_introspection.py @@ -1,14 +1,33 @@ import os -from dbus_fast import ArgDirection, PropertyAccess, SignatureType +from dbus_fast import ( + ArgDirection, + PropertyAccess, + SignatureType, + InvalidMemberNameError, +) from dbus_fast import introspection as intr -with open(f"{os.path.dirname(__file__)}/data/introspection.xml") as f: - example_data = f.read() +with open(f"{os.path.dirname(__file__)}/data/strict-introspection.xml") as f: + strict_data = f.read() + +with open(f"{os.path.dirname(__file__)}/data/sloppy-introspection.xml") as f: + sloppy_data = f.read() -def test_example_introspection_from_xml(): - node = intr.Node.parse(example_data) +def test_introspection_from_xml_sloppy(): + intr.Node.parse(sloppy_data, validate_property_names=False) + + +def test_introspection_from_xml_strict(): + try: + node = intr.Node.parse(sloppy_data) + except InvalidMemberNameError: + pass + else: + assert False, "Expected an AssertionError" + + node = intr.Node.parse(strict_data) assert len(node.interfaces) == 1 interface = node.interfaces[0] @@ -61,12 +80,12 @@ def test_example_introspection_from_xml(): assert len(changed.args) == 1 new_value = changed.args[0] assert type(new_value) is intr.Arg - assert new_value.name == "new_value" + assert new_value.name == "0-new_value" assert new_value.signature == "b" def test_example_introspection_to_xml(): - node = intr.Node.parse(example_data) + node = intr.Node.parse(strict_data) tree = node.to_xml() assert tree.tag == "node" assert tree.attrib.get("name") == "/com/example/sample_object0" @@ -94,7 +113,7 @@ def test_example_introspection_to_xml(): arg = signal[0] assert arg.tag == "arg" - assert arg.attrib.get("name") == "new_value" + assert arg.attrib.get("name") == "0-new_value" assert arg.attrib.get("type") == "b" signal = interface[4] @@ -109,7 +128,7 @@ def test_example_introspection_to_xml(): arg = signal[1] assert arg.tag == "arg" - assert arg.attrib.get("name") == "new_value2" + assert arg.attrib.get("name") == "0-new_value2" assert arg.attrib.get("type") == "y" prop = interface[5]