view libervia/frontends/tools/jid.py @ 4231:e11b13418ba6

plugin XEP-0353, XEP-0234, jingle: WebRTC data channel signaling implementation: Implement XEP-0343: Signaling WebRTC Data Channels in Jingle. The current version of the XEP (0.3.1) has no implementation and contains some flaws. After discussing this on xsf@, Daniel (from Conversations) mentioned that they had a sprint with Larma (from Dino) to work on another version and provided me with this link: https://gist.github.com/iNPUTmice/6c56f3e948cca517c5fb129016d99e74 . I have used it for my implementation. This implementation reuses work done on Jingle A/V call (notably XEP-0176 and XEP-0167 plugins), with adaptations. When used, XEP-0234 will not handle the file itself as it normally does. This is because WebRTC has several implementations (browser for web interface, GStreamer for others), and file/data must be handled directly by the frontend. This is particularly important for web frontends, as the file is not sent from the backend but from the end-user's browser device. Among the changes, there are: - XEP-0343 implementation. - `file_send` bridge method now use serialised dict as output. - New `BaseTransportHandler.is_usable` method which get content data and returns a boolean (default to `True`) to tell if this transport can actually be used in this context (when we are initiator). Used in webRTC case to see if call data are available. - Support of `application` media type, and everything necessary to handle data channels. - Better confirmation message, with file name, size and description when available. - When file is accepted in preflight, it is specified in following `action_new` signal for actual file transfer. This way, frontend can avoid the display or 2 confirmation messages. - XEP-0166: when not specified, default `content` name is now its index number instead of a UUID. This follows the behaviour of browsers. - XEP-0353: better handling of events such as call taken by another device. - various other updates. rel 441
author Goffi <goffi@goffi.org>
date Sat, 06 Apr 2024 12:57:23 +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)