Skip to content

Commit

Permalink
fix: allow skip property name validation
Browse files Browse the repository at this point in the history
D-Bus specification allow property names not following the same naming
restrictions as member names:

> Strictly speaking, D-Bus property names are not required to follow
> the same naming restrictions as member names, but D-Bus property
> names that would not be valid member names (in particular,
> GObject-style dash-separated property names) can cause
> interoperability problems and should be avoided.

https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces

For example:

``` xml
  <interface name=“org.freedesktop.portal.PowerProfileMonitor”>
    <property type=“b” name=“power-saver-enabled” access=“read” />
    <property type=“u” name=“version” access=“read” />
  </interface
```

This commit add argument `validate=True` to:

- dbus_fast.introspection.Property.__init__
- dbus_fast.introspection.Property.from_xml

As well as add argument `validate_property_names=True` to:

- dbus_fast.aio.message_bus.MessageBus.introspect
- dbus_fast.introspection.Interface.from_xml
- dbus_fast.introspection.Node.parse
- dbus_fast.message_bus.BaseMessageBus.introspect
  • Loading branch information
black-desk committed Jan 14, 2025
1 parent 0817235 commit c8cf670
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 12 deletions.
9 changes: 8 additions & 1 deletion src/dbus_fast/aio/message_bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,11 @@ def on_hello(reply, err):
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.
Expand All @@ -274,6 +278,8 @@ async def introspect(
: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 <dbus_fast.introspection.Node>`
Expand All @@ -295,6 +301,7 @@ async def introspect(
path,
partial(self._reply_handler, future),
check_callback_type=False,
validate_property_names=validate_property_names,
)

timer_handle = self._loop.call_later(
Expand Down
42 changes: 32 additions & 10 deletions src/dbus_fast/introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,10 @@ def __init__(
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:
Expand All @@ -256,7 +258,7 @@ def __init__(
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``.
Expand All @@ -276,7 +278,7 @@ def from_xml(element):
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`."""
Expand Down Expand Up @@ -321,7 +323,9 @@ def __init__(
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`.
Expand All @@ -345,7 +349,9 @@ def from_xml(element: ET.Element) -> "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

Expand Down Expand Up @@ -406,7 +412,9 @@ def __init__(
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``.
Expand All @@ -415,6 +423,8 @@ def from_xml(element: ET.Element, is_root: bool = False):
: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 <dbus_fast.InvalidIntrospectionError>` - If the XML tree is not valid introspection data.
Expand All @@ -423,20 +433,30 @@ def from_xml(element: ET.Element, is_root: bool = False):

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 <dbus_fast.InvalidIntrospectionError>` - If the string is not valid introspection data.
Expand All @@ -447,7 +467,9 @@ def parse(data: str) -> "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`."""
Expand Down
9 changes: 8 additions & 1 deletion src/dbus_fast/message_bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ def introspect(
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.
Expand All @@ -276,6 +277,10 @@ def introspect(
:param callback: A callback that will be called with the introspection
data as a :class:`Node <dbus_fast.introspection.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 <dbus_fast.InvalidObjectPathError>` - If the given object path is not valid.
Expand All @@ -287,7 +292,9 @@ def introspect(
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
Expand Down

0 comments on commit c8cf670

Please sign in to comment.