From 836a9c962e20932ef15e5594da94f22e73cc10f2 Mon Sep 17 00:00:00 2001 From: Goffi Date: Sat, 6 Jun 2026 12:16:07 +0200 Subject: [PATCH] tools (common/uri): Modernization + more flexible URIs: - Remove Python < 3.9 workaround - Type hints + docstring update - Accept `kwargs` when `type_` is `None`. rel 477 --- libervia/backend/tools/common/uri.py | 83 ++++++++++++++-------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/libervia/backend/tools/common/uri.py b/libervia/backend/tools/common/uri.py index 3b01b0b8..dd1ef4f2 100644 --- a/libervia/backend/tools/common/uri.py +++ b/libervia/backend/tools/common/uri.py @@ -17,30 +17,28 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -"""XMPP uri parsing tools""" +"""XMPP URI parsing and building tools""" -from typing import Optional -import sys import urllib.parse -import urllib.request, urllib.parse, urllib.error + +from typing import Literal # FIXME: basic implementation, need to follow RFC 5122 +XmppUriType = Literal["pubsub"] | None -def parse_xmpp_uri(uri): - """Parse an XMPP uri and return a dict with various information - @param uri(unicode): uri to parse - @return dict(unicode, unicode): data depending of the URI where key can be: - type: one of ("pubsub", TODO) - type is always present - sub_type: can be: - - microblog - only used for pubsub for now - path: XMPP path (jid of the service or entity) - node: node used - id: id of the element (item for pubsub) - @raise ValueError: the scheme is not xmpp +def parse_xmpp_uri(uri: str) -> dict[str, str]: + """Parse an XMPP URI and return a dict with various information. + + @param uri: URI to parse. + @return: data depending on the URI where keys can be: + - "type": one of ("pubsub", ""). Always present. + - "sub_type": can be "microblog", only used for pubsub for now. + - "path": XMPP path (JID of the service or entity). + - "node": node used. + - "id": ID of the element (item for pubsub). + @raise ValueError: the scheme is not "xmpp". """ uri_split = urllib.parse.urlsplit(uri) if uri_split.scheme != "xmpp": @@ -48,24 +46,17 @@ def parse_xmpp_uri(uri): # XXX: we don't use jid.JID for path as it can be used both in backend and frontend # which may use different JID classes - data = {"path": urllib.parse.unquote(uri_split.path)} + data: dict[str, str] = {"path": urllib.parse.unquote(uri_split.path)} query_end = uri_split.query.find(";") if query_end == -1: - # we just have a JID - query_type = None + query_type: str | None = None else: query_type = uri_split.query[:query_end] if "=" in query_type: raise ValueError("no query type, invalid XMPP URI") - if sys.version_info >= (3, 9): - # parse_qs behaviour has been modified in Python 3.9, ";" is not understood as a - # parameter separator anymore but the "separator" argument has been added to - # change it. - pairs = urllib.parse.parse_qs(uri_split.geturl(), separator=";") - else: - pairs = urllib.parse.parse_qs(uri_split.geturl()) + pairs = urllib.parse.parse_qs(uri_split.geturl(), separator=";") for k, v in list(pairs.items()): if len(v) != 1: raise NotImplementedError("multiple values not managed") @@ -87,35 +78,47 @@ def parse_xmpp_uri(uri): return data -def add_pairs(uri, pairs): +def _add_pairs(uri_parts: list[str], pairs: dict[str, str]) -> None: + """Append ;key=value pairs to a URI parts list. + + Underscores in keys are converted to hyphens. + """ for k, v in pairs.items(): - uri.append( + uri_parts.append( ";" - + urllib.parse.quote_plus(k.encode("utf-8")) + + urllib.parse.quote_plus(k.replace("_", "-").encode("utf-8")) + "=" + urllib.parse.quote_plus(v.encode("utf-8")) ) -def build_xmpp_uri(type_: Optional[str] = None, **kwargs: str) -> str: - uri = ["xmpp:"] - subtype = kwargs.pop("subtype", None) - path = kwargs.pop("path") +def build_xmpp_uri( + type_: XmppUriType = None, + subtype: str | None = None, + path: str = "", + **kwargs: str, +) -> str: + """Build an xmpp: URI. + + @param type_: URI query type (e.g.; "pubsub"). + @param subtype: sub-type hint (e.g. "microblog"). + @param path: XMPP path (JID). + @param kwargs: additional query parameters + """ + uri: list[str] = ["xmpp:"] uri.append(urllib.parse.quote_plus(path.encode("utf-8")).replace("%40", "@")) if type_ is None: - # we have an URI to a JID if kwargs: - raise NotImplementedError( - "keyword arguments are not supported for URI without type" - ) + uri.append("?") + _add_pairs(uri, kwargs) elif type_ == "pubsub": if subtype == "microblog" and not kwargs.get("node"): kwargs["node"] = "urn:xmpp:microblog:0" if kwargs: uri.append("?") - add_pairs(uri, kwargs) + _add_pairs(uri, kwargs) else: - raise NotImplementedError(f"{type_} URI are not handled yet".format(type_=type_)) + raise NotImplementedError(f"{type_!r} URI type is not handled yet") return "".join(uri)