Initial Rust version.
This commit is contained in:
		
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -14,4 +14,9 @@ slixmpp.egg-info/
 | 
			
		||||
.DS_STORE
 | 
			
		||||
.idea/
 | 
			
		||||
.vscode/
 | 
			
		||||
venv/
 | 
			
		||||
venv/
 | 
			
		||||
 | 
			
		||||
# Added by cargo
 | 
			
		||||
 | 
			
		||||
/target
 | 
			
		||||
/Cargo.lock
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "slixmpp"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
jid = "0.10"
 | 
			
		||||
pyo3 = "0.21"
 | 
			
		||||
 | 
			
		||||
[lib]
 | 
			
		||||
crate-type = ["cdylib"]
 | 
			
		||||
							
								
								
									
										347
									
								
								slixmpp/jid.py
									
									
									
									
									
								
							
							
						
						
									
										347
									
								
								slixmpp/jid.py
									
									
									
									
									
								
							@@ -1,346 +1 @@
 | 
			
		||||
 | 
			
		||||
# slixmpp.jid
 | 
			
		||||
# ~~~~~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
# This module allows for working with Jabber IDs (JIDs).
 | 
			
		||||
# Part of Slixmpp: The Slick XMPP Library
 | 
			
		||||
# :copyright: (c) 2011 Nathanael C. Fritz
 | 
			
		||||
# :license: MIT, see LICENSE for more details
 | 
			
		||||
from __future__ import annotations
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
import socket
 | 
			
		||||
 | 
			
		||||
from functools import lru_cache
 | 
			
		||||
from typing import (
 | 
			
		||||
    Optional,
 | 
			
		||||
    Union,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError
 | 
			
		||||
 | 
			
		||||
HAVE_INET_PTON = hasattr(socket, 'inet_pton')
 | 
			
		||||
 | 
			
		||||
#: The basic regex pattern that a JID must match in order to determine
 | 
			
		||||
#: the local, domain, and resource parts. This regex does NOT do any
 | 
			
		||||
#: validation, which requires application of nodeprep, resourceprep, etc.
 | 
			
		||||
JID_PATTERN = re.compile(
 | 
			
		||||
    "^(?:([^\"&'/:<>@]{1,1023})@)?([^/@]{1,1023})(?:/(.{1,1023}))?$"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
#: The set of escape sequences for the characters not allowed by nodeprep.
 | 
			
		||||
JID_ESCAPE_SEQUENCES = {'\\20', '\\22', '\\26', '\\27', '\\2f',
 | 
			
		||||
                        '\\3a', '\\3c', '\\3e', '\\40', '\\5c'}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO: Find the best cache size for a standard usage.
 | 
			
		||||
@lru_cache(maxsize=1024)
 | 
			
		||||
def _parse_jid(data: str):
 | 
			
		||||
    """
 | 
			
		||||
    Parse string data into the node, domain, and resource
 | 
			
		||||
    components of a JID, if possible.
 | 
			
		||||
 | 
			
		||||
    :param string data: A string that is potentially a JID.
 | 
			
		||||
 | 
			
		||||
    :raises InvalidJID:
 | 
			
		||||
 | 
			
		||||
    :returns: tuple of the validated local, domain, and resource strings
 | 
			
		||||
    """
 | 
			
		||||
    match = JID_PATTERN.match(data)
 | 
			
		||||
    if not match:
 | 
			
		||||
        raise InvalidJID('JID could not be parsed')
 | 
			
		||||
 | 
			
		||||
    (node, domain, resource) = match.groups()
 | 
			
		||||
 | 
			
		||||
    node = _validate_node(node)
 | 
			
		||||
    domain = _validate_domain(domain)
 | 
			
		||||
    resource = _validate_resource(resource)
 | 
			
		||||
 | 
			
		||||
    return node, domain, resource
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _validate_node(node: Optional[str]):
 | 
			
		||||
    """Validate the local, or username, portion of a JID.
 | 
			
		||||
 | 
			
		||||
    :raises InvalidJID:
 | 
			
		||||
 | 
			
		||||
    :returns: The local portion of a JID, as validated by nodeprep.
 | 
			
		||||
    """
 | 
			
		||||
    if node is None:
 | 
			
		||||
        return ''
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        node = nodeprep(node)
 | 
			
		||||
    except StringprepError:
 | 
			
		||||
        raise InvalidJID('Nodeprep failed')
 | 
			
		||||
 | 
			
		||||
    if not node:
 | 
			
		||||
        raise InvalidJID('Localpart must not be 0 bytes')
 | 
			
		||||
    if len(node) > 1023:
 | 
			
		||||
        raise InvalidJID('Localpart must be less than 1024 bytes')
 | 
			
		||||
    return node
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _validate_domain(domain: str):
 | 
			
		||||
    """Validate the domain portion of a JID.
 | 
			
		||||
 | 
			
		||||
    IP literal addresses are left as-is, if valid. Domain names
 | 
			
		||||
    are stripped of any trailing label separators (`.`), and are
 | 
			
		||||
    checked with the nameprep profile of stringprep. If the given
 | 
			
		||||
    domain is actually a punyencoded version of a domain name, it
 | 
			
		||||
    is converted back into its original Unicode form. Domains must
 | 
			
		||||
    also not start or end with a dash (`-`).
 | 
			
		||||
 | 
			
		||||
    :raises InvalidJID:
 | 
			
		||||
 | 
			
		||||
    :returns: The validated domain name
 | 
			
		||||
    """
 | 
			
		||||
    ip_addr = False
 | 
			
		||||
 | 
			
		||||
    # First, check if this is an IPv4 address
 | 
			
		||||
    try:
 | 
			
		||||
        socket.inet_aton(domain)
 | 
			
		||||
        ip_addr = True
 | 
			
		||||
    except socket.error:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    # Check if this is an IPv6 address
 | 
			
		||||
    if not ip_addr and HAVE_INET_PTON and domain[0] == '[' and domain[-1] == ']':
 | 
			
		||||
        try:
 | 
			
		||||
            ip = domain[1:-1]
 | 
			
		||||
            socket.inet_pton(socket.AF_INET6, ip)
 | 
			
		||||
            ip_addr = True
 | 
			
		||||
        except (socket.error, ValueError):
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    if not ip_addr:
 | 
			
		||||
        # This is a domain name, which must be checked further
 | 
			
		||||
 | 
			
		||||
        if domain and domain[-1] == '.':
 | 
			
		||||
            domain = domain[:-1]
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            domain = idna(domain)
 | 
			
		||||
        except StringprepError:
 | 
			
		||||
            raise InvalidJID(f'idna validation failed: {domain}')
 | 
			
		||||
 | 
			
		||||
        if ':' in domain:
 | 
			
		||||
            raise InvalidJID(f'Domain containing a port: {domain}')
 | 
			
		||||
        for label in domain.split('.'):
 | 
			
		||||
            if not label:
 | 
			
		||||
                raise InvalidJID(f'Domain containing too many dots: {domain}')
 | 
			
		||||
            if '-' in (label[0], label[-1]):
 | 
			
		||||
                raise InvalidJID(f'Domain starting or ending with -: {domain}')
 | 
			
		||||
 | 
			
		||||
    if not domain:
 | 
			
		||||
        raise InvalidJID('Domain must not be 0 bytes')
 | 
			
		||||
    if len(domain) > 1023:
 | 
			
		||||
        raise InvalidJID('Domain must be less than 1024 bytes')
 | 
			
		||||
 | 
			
		||||
    return domain
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _validate_resource(resource: Optional[str]):
 | 
			
		||||
    """Validate the resource portion of a JID.
 | 
			
		||||
 | 
			
		||||
    :raises InvalidJID:
 | 
			
		||||
 | 
			
		||||
    :returns: The local portion of a JID, as validated by resourceprep.
 | 
			
		||||
    """
 | 
			
		||||
    if resource is None:
 | 
			
		||||
        return ''
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        resource = resourceprep(resource)
 | 
			
		||||
    except StringprepError:
 | 
			
		||||
        raise InvalidJID('Resourceprep failed')
 | 
			
		||||
 | 
			
		||||
    if not resource:
 | 
			
		||||
        raise InvalidJID('Resource must not be 0 bytes')
 | 
			
		||||
    if len(resource) > 1023:
 | 
			
		||||
        raise InvalidJID('Resource must be less than 1024 bytes')
 | 
			
		||||
    return resource
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _format_jid(
 | 
			
		||||
        local: Optional[str] = None,
 | 
			
		||||
        domain: Optional[str] = None,
 | 
			
		||||
        resource: Optional[str] = None,
 | 
			
		||||
    ):
 | 
			
		||||
    """Format the given JID components into a full or bare JID.
 | 
			
		||||
 | 
			
		||||
    :param string local: Optional. The local portion of the JID.
 | 
			
		||||
    :param string domain: Required. The domain name portion of the JID.
 | 
			
		||||
    :param strin resource: Optional. The resource portion of the JID.
 | 
			
		||||
 | 
			
		||||
    :return: A full or bare JID string.
 | 
			
		||||
    """
 | 
			
		||||
    if domain is None:
 | 
			
		||||
        return ''
 | 
			
		||||
    if local is not None:
 | 
			
		||||
        result = local + '@' + domain
 | 
			
		||||
    else:
 | 
			
		||||
        result = domain
 | 
			
		||||
    if resource is not None:
 | 
			
		||||
        result += '/' + resource
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InvalidJID(ValueError):
 | 
			
		||||
    """
 | 
			
		||||
    Raised when attempting to create a JID that does not pass validation.
 | 
			
		||||
 | 
			
		||||
    It can also be raised if modifying an existing JID in such a way as
 | 
			
		||||
    to make it invalid, such trying to remove the domain from an existing
 | 
			
		||||
    full JID while the local and resource portions still exist.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JID:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    A representation of a Jabber ID, or JID.
 | 
			
		||||
 | 
			
		||||
    Each JID may have three components: a user, a domain, and an optional
 | 
			
		||||
    resource. For example: user@domain/resource
 | 
			
		||||
 | 
			
		||||
    When a resource is not used, the JID is called a bare JID.
 | 
			
		||||
    The JID is a full JID otherwise.
 | 
			
		||||
 | 
			
		||||
    **JID Properties:**
 | 
			
		||||
        :full: The string value of the full JID.
 | 
			
		||||
        :jid: Alias for ``full``.
 | 
			
		||||
        :bare: The string value of the bare JID.
 | 
			
		||||
        :node: The node portion of the JID.
 | 
			
		||||
        :user: Alias for ``node``.
 | 
			
		||||
        :local: Alias for ``node``.
 | 
			
		||||
        :username: Alias for ``node``.
 | 
			
		||||
        :domain: The domain name portion of the JID.
 | 
			
		||||
        :server: Alias for ``domain``.
 | 
			
		||||
        :host: Alias for ``domain``.
 | 
			
		||||
        :resource: The resource portion of the JID.
 | 
			
		||||
 | 
			
		||||
    :param string jid:
 | 
			
		||||
        A string of the form ``'[user@]domain[/resource]'``.
 | 
			
		||||
    :param bool bare:
 | 
			
		||||
        If present, discard the provided resource.
 | 
			
		||||
 | 
			
		||||
    :raises InvalidJID:
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __slots__ = ('_node', '_domain', '_resource', '_bare', '_full')
 | 
			
		||||
 | 
			
		||||
    def __init__(self, jid: Optional[Union[str, 'JID']] = None, bare: bool = False):
 | 
			
		||||
        if not jid:
 | 
			
		||||
            self._node = ''
 | 
			
		||||
            self._domain = ''
 | 
			
		||||
            self._resource = ''
 | 
			
		||||
            self._bare = ''
 | 
			
		||||
            self._full = ''
 | 
			
		||||
            return
 | 
			
		||||
        elif not isinstance(jid, JID):
 | 
			
		||||
            node, domain, resource = _parse_jid(jid)
 | 
			
		||||
            self._node = node
 | 
			
		||||
            self._domain = domain
 | 
			
		||||
            self._resource = resource if not bare else ''
 | 
			
		||||
        else:
 | 
			
		||||
            self._node = jid._node
 | 
			
		||||
            self._domain = jid._domain
 | 
			
		||||
            self._resource = jid._resource if not bare else ''
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    def _update_bare_full(self):
 | 
			
		||||
        """Format the given JID into a bare and a full JID.
 | 
			
		||||
        """
 | 
			
		||||
        self._bare = (self._node + '@' + self._domain
 | 
			
		||||
                      if self._node
 | 
			
		||||
                      else self._domain)
 | 
			
		||||
        self._full = (self._bare + '/' + self._resource
 | 
			
		||||
                      if self._resource
 | 
			
		||||
                      else self._bare)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def bare(self) -> str:
 | 
			
		||||
        return self._bare
 | 
			
		||||
 | 
			
		||||
    @bare.setter
 | 
			
		||||
    def bare(self, value: str):
 | 
			
		||||
        node, domain, resource = _parse_jid(value)
 | 
			
		||||
        assert not resource
 | 
			
		||||
        self._node = node
 | 
			
		||||
        self._domain = domain
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def node(self) -> str:
 | 
			
		||||
        return self._node
 | 
			
		||||
 | 
			
		||||
    @node.setter
 | 
			
		||||
    def node(self, value: Optional[str]):
 | 
			
		||||
        self._node = _validate_node(value)
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def domain(self) -> str:
 | 
			
		||||
        return self._domain
 | 
			
		||||
 | 
			
		||||
    @domain.setter
 | 
			
		||||
    def domain(self, value: str):
 | 
			
		||||
        self._domain = _validate_domain(value)
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def resource(self) -> str:
 | 
			
		||||
        return self._resource
 | 
			
		||||
 | 
			
		||||
    @resource.setter
 | 
			
		||||
    def resource(self, value: Optional[str]):
 | 
			
		||||
        self._resource = _validate_resource(value)
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def full(self) -> str:
 | 
			
		||||
        return self._full
 | 
			
		||||
 | 
			
		||||
    @full.setter
 | 
			
		||||
    def full(self, value: str):
 | 
			
		||||
        self._node, self._domain, self._resource = _parse_jid(value)
 | 
			
		||||
        self._update_bare_full()
 | 
			
		||||
 | 
			
		||||
    user = node
 | 
			
		||||
    local = node
 | 
			
		||||
    username = node
 | 
			
		||||
 | 
			
		||||
    server = domain
 | 
			
		||||
    host = domain
 | 
			
		||||
 | 
			
		||||
    jid = full
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        """Use the full JID as the string value."""
 | 
			
		||||
        return self._full
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        """Use the full JID as the representation."""
 | 
			
		||||
        return self._full
 | 
			
		||||
 | 
			
		||||
    # pylint: disable=W0212
 | 
			
		||||
    def __eq__(self, other):
 | 
			
		||||
        """Two JIDs are equal if they have the same full JID value."""
 | 
			
		||||
        if not isinstance(other, JID):
 | 
			
		||||
            try:
 | 
			
		||||
                other = JID(other)
 | 
			
		||||
            except InvalidJID:
 | 
			
		||||
                return NotImplemented
 | 
			
		||||
 | 
			
		||||
        return (self._node == other._node and
 | 
			
		||||
                self._domain == other._domain and
 | 
			
		||||
                self._resource == other._resource)
 | 
			
		||||
 | 
			
		||||
    def __ne__(self, other):
 | 
			
		||||
        """Two JIDs are considered unequal if they are not equal."""
 | 
			
		||||
        return not self == other
 | 
			
		||||
 | 
			
		||||
    def __hash__(self):
 | 
			
		||||
        """Hash a JID based on the string version of its full JID."""
 | 
			
		||||
        return hash(self._full)
 | 
			
		||||
from libslixmpp import JID, InvalidJID
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										278
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,278 @@
 | 
			
		||||
use pyo3::exceptions::{PyNotImplementedError, PyValueError};
 | 
			
		||||
use pyo3::prelude::*;
 | 
			
		||||
 | 
			
		||||
pyo3::create_exception!(py_jid, InvalidJID, PyValueError, "Raised when attempting to create a JID that does not pass validation.\n\nIt can also be raised if modifying an existing JID in such a way as\nto make it invalid, such trying to remove the domain from an existing\nfull JID while the local and resource portions still exist.");
 | 
			
		||||
 | 
			
		||||
fn to_exc(err: jid::Error) -> PyErr {
 | 
			
		||||
    InvalidJID::new_err(err.to_string())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A representation of a Jabber ID, or JID.
 | 
			
		||||
///
 | 
			
		||||
/// Each JID may have three components: a user, a domain, and an optional resource. For example:
 | 
			
		||||
/// user@domain/resource
 | 
			
		||||
///
 | 
			
		||||
/// When a resource is not used, the JID is called a bare JID. The JID is a full JID otherwise.
 | 
			
		||||
///
 | 
			
		||||
/// Raises InvalidJID if the parser rejects it.
 | 
			
		||||
#[pyclass(name = "JID", module = "slixmpp.jid")]
 | 
			
		||||
struct PyJid {
 | 
			
		||||
    jid: Option<jid::Jid>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[pymethods]
 | 
			
		||||
impl PyJid {
 | 
			
		||||
    #[new]
 | 
			
		||||
    #[pyo3(signature = (jid=None, bare=false))]
 | 
			
		||||
    fn new(jid: Option<&Bound<'_, PyAny>>, bare: bool) -> PyResult<Self> {
 | 
			
		||||
        if let Some(jid) = jid {
 | 
			
		||||
            if let Ok(py_jid) = jid.extract::<PyRef<PyJid>>() {
 | 
			
		||||
                if bare {
 | 
			
		||||
                    if let Some(py_jid) = &(*py_jid).jid {
 | 
			
		||||
                        Ok(PyJid {
 | 
			
		||||
                            jid: Some(jid::Jid::Bare(py_jid.to_bare())),
 | 
			
		||||
                        })
 | 
			
		||||
                    } else {
 | 
			
		||||
                        Ok(PyJid { jid: None })
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    Ok(PyJid {
 | 
			
		||||
                        jid: (*py_jid).jid.clone(),
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                let jid: &str = jid.extract()?;
 | 
			
		||||
                if jid.is_empty() {
 | 
			
		||||
                    Ok(PyJid { jid: None })
 | 
			
		||||
                } else {
 | 
			
		||||
                    let mut jid = jid::Jid::new(jid).map_err(to_exc)?;
 | 
			
		||||
                    if bare {
 | 
			
		||||
                        jid = jid::Jid::Bare(jid.into_bare())
 | 
			
		||||
                    }
 | 
			
		||||
                    Ok(PyJid { jid: Some(jid) })
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(PyJid { jid: None })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    // TODO: implement or remove from the API
 | 
			
		||||
    fn unescape() {
 | 
			
		||||
    }
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    #[getter]
 | 
			
		||||
    fn get_bare(&self) -> String {
 | 
			
		||||
        match &self.jid {
 | 
			
		||||
            None => String::new(),
 | 
			
		||||
            Some(jid) => jid.to_bare().to_string(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[setter]
 | 
			
		||||
    fn set_bare(&mut self, bare: &str) -> PyResult<()> {
 | 
			
		||||
        let bare = jid::BareJid::new(bare).map_err(to_exc)?;
 | 
			
		||||
        self.jid = Some(match &self.jid {
 | 
			
		||||
            Some(jid::Jid::Bare(_)) | None => jid::Jid::Bare(bare),
 | 
			
		||||
            Some(jid::Jid::Full(jid)) => jid::Jid::Full(bare.with_resource(&jid.resource())),
 | 
			
		||||
        });
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[getter]
 | 
			
		||||
    fn get_full(&self) -> String {
 | 
			
		||||
        match &self.jid {
 | 
			
		||||
            None => String::new(),
 | 
			
		||||
            Some(jid) => jid.to_string(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[setter]
 | 
			
		||||
    fn set_full(&mut self, full: &str) -> PyResult<()> {
 | 
			
		||||
        // JID.full = 'domain' is acceptable in slixmpp.
 | 
			
		||||
        self.jid = Some(jid::Jid::new(full).map_err(to_exc)?);
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[getter]
 | 
			
		||||
    fn get_node(&self) -> String {
 | 
			
		||||
        match &self.jid {
 | 
			
		||||
            None => String::new(),
 | 
			
		||||
            Some(jid) => jid
 | 
			
		||||
                .node_str()
 | 
			
		||||
                .map(ToString::to_string)
 | 
			
		||||
                .unwrap_or_else(String::new),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[setter]
 | 
			
		||||
    fn set_node(&mut self, node: &str) -> PyResult<()> {
 | 
			
		||||
        let node = jid::NodePart::new(node).map_err(to_exc)?;
 | 
			
		||||
        self.jid = Some(match &self.jid {
 | 
			
		||||
            Some(jid::Jid::Bare(jid)) => {
 | 
			
		||||
                jid::Jid::Bare(jid::BareJid::from_parts(Some(&node), &jid.domain()))
 | 
			
		||||
            }
 | 
			
		||||
            Some(jid::Jid::Full(jid)) => jid::Jid::Full(jid::FullJid::from_parts(
 | 
			
		||||
                Some(&node),
 | 
			
		||||
                &jid.domain(),
 | 
			
		||||
                &jid.resource(),
 | 
			
		||||
            )),
 | 
			
		||||
            None => Err(InvalidJID::new_err("JID.node must apply to a proper JID"))?,
 | 
			
		||||
        });
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[getter]
 | 
			
		||||
    fn get_domain(&self) -> String {
 | 
			
		||||
        match &self.jid {
 | 
			
		||||
            None => String::new(),
 | 
			
		||||
            Some(jid) => jid.domain_str().to_string(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[setter]
 | 
			
		||||
    fn set_domain(&mut self, domain: &str) -> PyResult<()> {
 | 
			
		||||
        let domain = jid::DomainPart::new(domain).map_err(to_exc)?;
 | 
			
		||||
        self.jid = Some(match &self.jid {
 | 
			
		||||
            Some(jid::Jid::Bare(jid)) => {
 | 
			
		||||
                jid::Jid::Bare(jid::BareJid::from_parts(jid.node().as_ref(), &domain))
 | 
			
		||||
            }
 | 
			
		||||
            Some(jid::Jid::Full(jid)) => jid::Jid::Full(jid::FullJid::from_parts(
 | 
			
		||||
                jid.node().as_ref(),
 | 
			
		||||
                &domain,
 | 
			
		||||
                &jid.resource(),
 | 
			
		||||
            )),
 | 
			
		||||
            None => jid::Jid::Bare(jid::BareJid::from_parts(None, &domain)),
 | 
			
		||||
        });
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[getter]
 | 
			
		||||
    fn get_resource(&self) -> String {
 | 
			
		||||
        match &self.jid {
 | 
			
		||||
            None => String::new(),
 | 
			
		||||
            Some(jid) => jid
 | 
			
		||||
                .resource_str()
 | 
			
		||||
                .map(ToString::to_string)
 | 
			
		||||
                .unwrap_or_else(String::new),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[setter]
 | 
			
		||||
    fn set_resource(&mut self, resource: &str) -> PyResult<()> {
 | 
			
		||||
        let resource = jid::ResourcePart::new(resource).map_err(to_exc)?;
 | 
			
		||||
        self.jid = Some(match &self.jid {
 | 
			
		||||
            Some(jid::Jid::Bare(jid)) => jid::Jid::Full(jid.with_resource(&resource)),
 | 
			
		||||
            Some(jid::Jid::Full(jid)) => jid::Jid::Full(jid::FullJid::from_parts(
 | 
			
		||||
                jid.node().as_ref(),
 | 
			
		||||
                &jid.domain(),
 | 
			
		||||
                &resource,
 | 
			
		||||
            )),
 | 
			
		||||
            None => Err(InvalidJID::new_err(
 | 
			
		||||
                "JID.resource must apply to a proper JID",
 | 
			
		||||
            ))?,
 | 
			
		||||
        });
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Use the full JID as the string value.
 | 
			
		||||
    fn __str__(&self) -> String {
 | 
			
		||||
        match &self.jid {
 | 
			
		||||
            None => String::new(),
 | 
			
		||||
            Some(jid) => jid.to_string(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Use the full JID as the representation.
 | 
			
		||||
    fn __repr__(&self) -> String {
 | 
			
		||||
        match &self.jid {
 | 
			
		||||
            None => String::new(),
 | 
			
		||||
            Some(jid) => jid.to_string(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Two JIDs are equal if they have the same full JID value.
 | 
			
		||||
    fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: pyo3::basic::CompareOp) -> PyResult<bool> {
 | 
			
		||||
        let other = if let Ok(other) = other.extract::<PyRef<PyJid>>() {
 | 
			
		||||
            other
 | 
			
		||||
        } else if other.is_none() {
 | 
			
		||||
            Bound::new(other.py(), PyJid::new(None, false)?)?.borrow()
 | 
			
		||||
        } else {
 | 
			
		||||
            Bound::new(other.py(), PyJid::new(Some(other), false)?)?.borrow()
 | 
			
		||||
        };
 | 
			
		||||
        match (&self.jid, &other.jid) {
 | 
			
		||||
            (None, None) => Ok(true),
 | 
			
		||||
            (Some(jid), Some(other)) => match op {
 | 
			
		||||
                pyo3::basic::CompareOp::Eq => Ok(jid == other),
 | 
			
		||||
                pyo3::basic::CompareOp::Ne => Ok(jid != other),
 | 
			
		||||
                _ => Err(PyNotImplementedError::new_err(
 | 
			
		||||
                    "Only == and != are implemented",
 | 
			
		||||
                )),
 | 
			
		||||
            },
 | 
			
		||||
            _ => Ok(false),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Hash a JID based on the string version of its full JID.
 | 
			
		||||
    fn __hash__(&self) -> isize {
 | 
			
		||||
        if let Some(jid) = &self.jid {
 | 
			
		||||
            // Use the same algorithm as the Python JID.
 | 
			
		||||
            let string = jid.to_string();
 | 
			
		||||
            unsafe { pyo3::ffi::_Py_HashBytes(string.as_ptr() as *const _, string.len() as isize) }
 | 
			
		||||
        } else {
 | 
			
		||||
            0
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Aliases
 | 
			
		||||
 | 
			
		||||
    #[getter]
 | 
			
		||||
    fn get_user(&self) -> String {
 | 
			
		||||
        self.get_node()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[setter]
 | 
			
		||||
    fn set_user(&mut self, user: &str) -> PyResult<()> {
 | 
			
		||||
        self.set_node(user)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[getter]
 | 
			
		||||
    fn get_server(&self) -> String {
 | 
			
		||||
        self.get_domain()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[setter]
 | 
			
		||||
    fn set_server(&mut self, server: &str) -> PyResult<()> {
 | 
			
		||||
        self.set_domain(server)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[getter]
 | 
			
		||||
    fn get_host(&self) -> String {
 | 
			
		||||
        self.get_domain()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[setter]
 | 
			
		||||
    fn set_host(&mut self, host: &str) -> PyResult<()> {
 | 
			
		||||
        self.set_domain(host)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[getter]
 | 
			
		||||
    fn get_jid(&self) -> String {
 | 
			
		||||
        self.get_full()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[setter]
 | 
			
		||||
    fn set_jid(&mut self, jid: &str) -> PyResult<()> {
 | 
			
		||||
        self.set_full(jid)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[pymodule]
 | 
			
		||||
#[pyo3(name = "libslixmpp")]
 | 
			
		||||
fn py_jid(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
 | 
			
		||||
    m.add_class::<PyJid>()?;
 | 
			
		||||
    m.add("InvalidJID", py.get_type_bound::<InvalidJID>())?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
 | 
			
		||||
import unittest
 | 
			
		||||
from slixmpp.test import SlixTest
 | 
			
		||||
from slixmpp import JID, InvalidJID
 | 
			
		||||
from slixmpp.jid import nodeprep
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestJIDClass(SlixTest):
 | 
			
		||||
@@ -279,9 +278,5 @@ class TestJIDClass(SlixTest):
 | 
			
		||||
        #self.assertRaises(InvalidJID, JID, '%s@example.com' % '\\20foo2')
 | 
			
		||||
        #self.assertRaises(InvalidJID, JID, '%s@example.com' % 'bar2\\20')
 | 
			
		||||
 | 
			
		||||
    def testNodePrepIdemptotent(self):
 | 
			
		||||
        node = 'ᴹᴵᴷᴬᴱᴸ'
 | 
			
		||||
        self.assertEqual(nodeprep(node), nodeprep(nodeprep(node)))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestJIDClass)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user