view libervia/frontends/tools/jid.py @ 4240:79c8a70e1813

backend, frontend: prepare remote control: This is a series of changes necessary to prepare the implementation of remote control feature: - XEP-0166: add a `priority` attribute to `ApplicationData`: this is needed when several applications are working in a same session, to know which one must be handled first. Will be used to make Remote Control have precedence over Call content. - XEP-0166: `_call_plugins` is now async and is not used with `DeferredList` anymore: the benefit to have methods called in parallels is very low, and it cause a lot of trouble as we can't predict order. Methods are now called sequentially so workflow can be predicted. - XEP-0167: fix `senders` XMPP attribute <=> SDP mapping - XEP-0234: preflight acceptance key is now `pre-accepted` instead of `file-accepted`, so the same key can be used with other jingle applications. - XEP-0167, XEP-0343: move some method to XEP-0167 - XEP-0353: use new `priority` feature to call preflight methods of applications according to it. - frontend (webrtc): refactor the sources/sink handling with a more flexible mechanism based on Pydantic models. It is now possible to have has many Data Channel as necessary, to have them in addition to A/V streams, to specify manually GStreamer sources and sinks, etc. - frontend (webrtc): rework of the pipeline to reduce latency. - frontend: new `portal_desktop` method. Screenshare portal handling has been moved there, and RemoteDesktop portal has been added. - frontend (webrtc): fix `extract_ufrag_pwd` method. rel 436
author Goffi <goffi@goffi.org>
date Sat, 11 May 2024 13:52:41 +0200
parents 26b7ed2817da
children
line wrap: on
line source

#!/usr/bin/env python3

# Libervia XMPP
# Copyright (C) 2009-2023 Jérôme Poisson (goffi@goffi.org)

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from typing import Optional, Tuple


class JID(str):
    """This class helps manage JID (<local>@<domain>/<resource>)"""

    def __new__(cls, jid_str: str) -> "JID":
        return str.__new__(cls, cls._normalize(jid_str))

    def __init__(self, jid_str: str):
        self._node, self._domain, self._resource = self._parse()

    @staticmethod
    def _normalize(jid_str: str) -> str:
        """Naive normalization before instantiating and parsing the JID"""
        if not jid_str:
            return jid_str
        tokens = jid_str.split("/")
        tokens[0] = tokens[0].lower()  # force node and domain to lower-case
        return "/".join(tokens)

    def _parse(self) -> Tuple[Optional[str], str, Optional[str]]:
        """Find node, domain, and resource from JID"""
        node_end = self.find("@")
        if node_end < 0:
            node_end = 0
        domain_end = self.find("/")
        if domain_end == 0:
            raise ValueError("a jid can't start with '/'")
        if domain_end == -1:
            domain_end = len(self)
        node = self[:node_end] or None
        domain = self[(node_end + 1) if node_end else 0 : domain_end]
        resource = self[domain_end + 1 :] or None
        return node, domain, resource

    @property
    def node(self) -> Optional[str]:
        return self._node

    @property
    def local(self) -> Optional[str]:
        return self._node

    @property
    def domain(self) -> str:
        return self._domain

    @property
    def resource(self) -> Optional[str]:
        return self._resource

    @property
    def bare(self) -> "JID":
        if not self.node:
            return JID(self.domain)
        return JID(f"{self.node}@{self.domain}")

    def change_resource(self, resource: str) -> "JID":
        """Build a new JID with the same node and domain but a different resource.

        @param resource: The new resource for the JID.
        @return: A new JID instance with the updated resource.
        """
        return JID(f"{self.bare}/{resource}")

    def is_valid(self) -> bool:
        """
        @return: True if the JID is XMPP compliant
        """
        # Simple check for domain part
        if not self.domain or self.domain.startswith(".") or self.domain.endswith("."):
            return False
        if ".." in self.domain:
            return False
        return True


def new_resource(entity: JID, resource: str) -> JID:
    """Build a new JID from the given entity and resource.

    @param entity: original JID
    @param resource: new resource
    @return: a new JID instance
    """
    return entity.change_resource(resource)