# -*- coding: utf-8 -*- """ sleekxmpp.jid ~~~~~~~~~~~~~~~~~~~~~~~ This module allows for working with Jabber IDs (JIDs) by providing accessors for the various components of a JID. Part of SleekXMPP: The Sleek XMPP Library :copyright: (c) 2011 Nathanael C. Fritz :license: MIT, see LICENSE for more details """ from __future__ import unicode_literals import re import socket import stringprep import encodings.idna from sleekxmpp.util import stringprep_profiles ILLEGAL_CHARS = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r' + \ '\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19' + \ '\x1a\x1b\x1c\x1d\x1e\x1f' + \ ' !"#$%&\'()*+,./:;<=>?@[\\]^_`{|}~\x7f' JID_PATTERN = "^(?:([^\"&'/:<>@]{1,1023})@)?([^/@]{1,1023})(?:/(.{1,1023}))?$" nodeprep = stringprep_profiles.create( nfkc=True, bidi=True, mappings=[ stringprep_profiles.b1_mapping, stringprep_profiles.c12_mapping], prohibited=[ stringprep.in_table_c11, stringprep.in_table_c12, stringprep.in_table_c21, stringprep.in_table_c22, stringprep.in_table_c3, stringprep.in_table_c4, stringprep.in_table_c5, stringprep.in_table_c6, stringprep.in_table_c7, stringprep.in_table_c8, stringprep.in_table_c9, lambda c: c in '\'"&/:<>@'], unassigned=[stringprep.in_table_a1]) resourceprep = stringprep_profiles.create( nfkc=True, bidi=True, mappings=[stringprep_profiles.b1_mapping], prohibited=[ stringprep.in_table_c12, stringprep.in_table_c21, stringprep.in_table_c22, stringprep.in_table_c3, stringprep.in_table_c4, stringprep.in_table_c5, stringprep.in_table_c6, stringprep.in_table_c7, stringprep.in_table_c8, stringprep.in_table_c9], unassigned=[stringprep.in_table_a1]) class InvalidJID(ValueError): pass def parse_jid(data): """ Parse string data into the node, domain, and resource components of a JID. """ match = re.match(JID_PATTERN, data) if not match: raise InvalidJID (node, domain, resource) = match.groups() ip_addr = False try: socket.inet_aton(domain) ip_addr = True except socket.error: pass if not ip_addr and hasattr(socket, 'inet_pton'): try: socket.inet_pton(socket.AF_INET6, domain.strip('[]')) ip_addr = True except socket.error: pass if not ip_addr: domain_parts = [] for label in domain.split('.'): try: label = encodings.idna.nameprep(label) encodings.idna.ToASCII(label) except UnicodeError: raise InvalidJID for char in label: if char in ILLEGAL_CHARS: raise InvalidJID if '-' in (label[0], label[-1]): raise InvalidJID domain_parts.append(label) domain = '.'.join(domain_parts) try: if node is not None: node = nodeprep(node) if resource is not None: resource = resourceprep(resource) except stringprep_profiles.StringPrepError: raise InvalidJID return node, domain, resource class JID(object): """ 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:** :jid: Alias for ``full``. :full: The string value of the full JID. :bare: The string value of the bare JID. :user: The username portion of the JID. :username: Alias for ``user``. :local: Alias for ``user``. :node: Alias for ``user``. :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]'``. """ def __init__(self, jid=None, local=None, domain=None, resource=None): """Initialize a new JID""" self._jid = (None, None, None) if jid is None or jid == '': jid = (None, None, None) elif not isinstance(jid, JID): jid = parse_jid(jid) else: jid = jid._jid orig_local, orig_domain, orig_resource = jid self._jid = (local or orig_local or None, domain or orig_domain or None, resource or orig_resource or None) def regenerate(self): """Deprecated""" pass def reset(self, data): """Start fresh from a new JID string. :param string data: A string of the form ``'[user@]domain[/resource]'``. """ self._jid = JID(data)._jid def __getattr__(self, name): """handle getting the jid values, using cache if available. :param name: one of: user, server, domain, resource, full, or bare. """ if name == 'resource': return self._jid[2] or '' elif name in ('user', 'username', 'local', 'node'): return self._jid[0] or '' elif name in ('server', 'domain', 'host'): return self._jid[1] or '' elif name in ('full', 'jid'): return str(self) elif name == 'bare': return str(JID(local=self._jid[0], domain=self._jid[1])) else: object.__getattr__(self, name) def __setattr__(self, name, value): """handle getting the jid values, using cache if available. :param name: one of: ``user``, ``username``, ``local``, ``node``, ``server``, ``domain``, ``host``, ``resource``, ``full``, ``jid``, or ``bare``. :param value: The new string value of the JID component. """ if name == 'resource': self._jid = JID(self, resource=value)._jid elif name in ('user', 'username', 'local', 'node'): self._jid = JID(self, local=value)._jid elif name in ('server', 'domain', 'host'): self._jid = JID(self, domain=value)._jid elif name in ('full', 'jid'): self._jid = JID(value)._jid elif name == 'bare': parsed = JID(value)._jid self._jid = (parsed[0], parsed[1], self._jid[2]) else: object.__setattr__(self, name, value) def __str__(self): """Use the full JID as the string value.""" result = [] if self._jid[0]: result.append(self._jid[0]) result.append('@') if self._jid[1]: result.append(self._jid[1]) if self._jid[2]: result.append('/') result.append(self._jid[2]) return ''.join(result) def __repr__(self): return self.__str__() def __eq__(self, other): """ Two JIDs are considered equal if they have the same full JID value. """ other = JID(other) return self._jid == other._jid def __ne__(self, other): """Two JIDs are considered unequal if they are not equal.""" return not self._jid == other._jid def __hash__(self): """Hash a JID based on the string version of its full JID.""" return hash(self.__str__()) def __copy__(self): """Generate a duplicate JID.""" return JID(self)