From 4afbb0322bb12023631e644671716fb413a32d13 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 12 Jun 2015 00:46:47 +0100 Subject: [PATCH] =?UTF-8?q?Rework=20slixmpp.jid=E2=80=99s=20JID=20classes?= =?UTF-8?q?=20to=20make=20them=20more=20efficient.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- slixmpp/jid.py | 356 +++++++++++++++++++------------------------------ 1 file changed, 134 insertions(+), 222 deletions(-) diff --git a/slixmpp/jid.py b/slixmpp/jid.py index c03c5df7..abb8067a 100644 --- a/slixmpp/jid.py +++ b/slixmpp/jid.py @@ -11,12 +11,11 @@ :license: MIT, see LICENSE for more details """ -from __future__ import unicode_literals - import re import socket from copy import deepcopy +from functools import lru_cache from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError @@ -30,22 +29,8 @@ JID_PATTERN = re.compile( ) #: The set of escape sequences for the characters not allowed by nodeprep. -JID_ESCAPE_SEQUENCES = set(['\\20', '\\22', '\\26', '\\27', '\\2f', - '\\3a', '\\3c', '\\3e', '\\40', '\\5c']) - -#: A mapping of unallowed characters to their escape sequences. An escape -#: sequence for '\' is also included since it must also be escaped in -#: certain situations. -JID_ESCAPE_TRANSFORMATIONS = {' ': '\\20', - '"': '\\22', - '&': '\\26', - "'": '\\27', - '/': '\\2f', - ':': '\\3a', - '<': '\\3c', - '>': '\\3e', - '@': '\\40', - '\\': '\\5c'} +JID_ESCAPE_SEQUENCES = {'\\20', '\\22', '\\26', '\\27', '\\2f', + '\\3a', '\\3c', '\\3e', '\\40', '\\5c'} #: The reverse mapping of escape sequences to their original forms. JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ', @@ -60,6 +45,8 @@ JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ', '\\5c': '\\'} +# TODO: Find the best cache size for a standard usage. +@lru_cache(maxsize=1024) def _parse_jid(data): """ Parse string data into the node, domain, and resource @@ -91,17 +78,19 @@ def _validate_node(node): :returns: The local portion of a JID, as validated by nodeprep. """ - if node is not None: - try: - node = nodeprep(node) - except StringprepError: - raise InvalidJID('Nodeprep failed') + if node is None: + return '' - 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 + 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): @@ -170,42 +159,19 @@ def _validate_resource(resource): :returns: The local portion of a JID, as validated by resourceprep. """ - if resource is not None: - try: - resource = resourceprep(resource) - except StringprepError: - raise InvalidJID('Resourceprep failed') + if resource is None: + return '' - 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 + try: + resource = resourceprep(resource) + except StringprepError: + raise InvalidJID('Resourceprep failed') - -def _escape_node(node): - """Escape the local portion of a JID.""" - result = [] - - for i, char in enumerate(node): - if char == '\\': - if ''.join((node[i:i+3])) in JID_ESCAPE_SEQUENCES: - result.append('\\5c') - continue - result.append(char) - - for i, char in enumerate(result): - if char != '\\': - result[i] = JID_ESCAPE_TRANSFORMATIONS.get(char, char) - - escaped = ''.join(result) - - if escaped.startswith('\\20') or escaped.endswith('\\20'): - raise InvalidJID('Escaped local part starts or ends with "\\20"') - - _validate_node(escaped) - - return escaped + 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 _unescape_node(node): @@ -230,9 +196,7 @@ def _unescape_node(node): seq = seq[1:] else: unescaped.append(char) - unescaped = ''.join(unescaped) - - return unescaped + return ''.join(unescaped) def _format_jid(local=None, domain=None, resource=None): @@ -266,47 +230,47 @@ class InvalidJID(ValueError): """ # pylint: disable=R0903 -class UnescapedJID(object): +class UnescapedJID: """ .. versionadded:: 1.1.10 """ - def __init__(self, local, domain, resource): - self._jid = (local, domain, resource) + __slots__ = ('_node', '_domain', '_resource') - # pylint: disable=R0911 - def __getattr__(self, name): + def __init__(self, node, domain, resource): + self._node = node + self._domain = domain + self._resource = resource + + def __getattribute__(self, name): """Retrieve the given JID component. :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 _format_jid(*self._jid) - elif name == 'bare': - return _format_jid(self._jid[0], self._jid[1]) - elif name == '_jid': - return getattr(super(JID, self), '_jid') - else: - return None + return self._resource + if name in ('user', 'username', 'local', 'node'): + return self._node + if name in ('server', 'domain', 'host'): + return self._domain + if name in ('full', 'jid'): + return _format_jid(self._node, self._domain, self._resource) + if name == 'bare': + return _format_jid(self._node, self._domain) + return object.__getattribute__(self, name) def __str__(self): """Use the full JID as the string value.""" - return _format_jid(*self._jid) + return _format_jid(self._node, self._domain, self._resource) def __repr__(self): """Use the full JID as the representation.""" - return self.__str__() + return _format_jid(self._node, self._domain, self._resource) -class JID(object): +class JID: """ A representation of a Jabber ID, or JID. @@ -318,13 +282,13 @@ class JID(object): The JID is a full JID otherwise. **JID Properties:** - :jid: Alias for ``full``. :full: The string value of the full JID. + :jid: Alias for ``full``. :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``. + :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``. @@ -332,49 +296,23 @@ class JID(object): :param string jid: A string of the form ``'[user@]domain[/resource]'``. - :param string local: - Optional. Specify the local, or username, portion - of the JID. If provided, it will override the local - value provided by the `jid` parameter. The given - local value will also be escaped if necessary. - :param string domain: - Optional. Specify the domain of the JID. If - provided, it will override the domain given by - the `jid` parameter. - :param string resource: - Optional. Specify the resource value of the JID. - If provided, it will override the domain given - by the `jid` parameter. :raises InvalidJID: """ - # pylint: disable=W0212 - def __init__(self, jid=None, **kwargs): - in_local = kwargs.get('local', None) - in_domain = kwargs.get('domain', None) - in_resource = kwargs.get('resource', None) - parts = None - if in_local or in_domain or in_resource: - parts = (in_local, in_domain, in_resource) + __slots__ = ('_node', '_domain', '_resource') + def __init__(self, jid=None): if not jid: - parsed_jid = (None, None, None) + self._node = '' + self._domain = '' + self._resource = '' elif not isinstance(jid, JID): - parsed_jid = _parse_jid(jid) + self._node, self._domain, self._resource = _parse_jid(jid) else: - parsed_jid = jid._jid - - local, domain, resource = parsed_jid - - if 'local' in kwargs: - local = _escape_node(in_local) - if 'domain' in kwargs: - domain = _validate_domain(in_domain) - if 'resource' in kwargs: - resource = _validate_resource(in_resource) - - self._jid = (local, domain, resource) + self._node = jid._node + self._domain = jid._domain + self._resource = jid._resource def unescape(self): """Return an unescaped JID object. @@ -387,151 +325,125 @@ class JID(object): .. versionadded:: 1.1.10 """ - return UnescapedJID(_unescape_node(self._jid[0]), - self._jid[1], - self._jid[2]) - - def regenerate(self): - """No-op - - .. deprecated:: 1.1.10 - """ - pass - - def reset(self, data): - """Start fresh from a new JID string. - - :param string data: A string of the form ``'[user@]domain[/resource]'``. - - .. deprecated:: 1.1.10 - """ - self._jid = JID(data)._jid - - @property - def resource(self): - return self._jid[2] or '' - - @property - def user(self): - return self._jid[0] or '' - - @property - def local(self): - return self._jid[0] or '' + return UnescapedJID(_unescape_node(self._node), + self._domain, + self._resource) @property def node(self): - return self._jid[0] or '' + return self._node + + @property + def user(self): + return self._node + + @property + def local(self): + return self._node @property def username(self): - return self._jid[0] or '' - - @property - def bare(self): - return _format_jid(self._jid[0], self._jid[1]) - - @property - def server(self): - return self._jid[1] or '' + return self._node @property def domain(self): - return self._jid[1] or '' + return self._domain + + @property + def server(self): + return self._domain @property def host(self): - return self._jid[1] or '' - - @property - def full(self): - return _format_jid(*self._jid) - - @property - def jid(self): - return _format_jid(*self._jid) + return self._domain @property def bare(self): - return _format_jid(self._jid[0], self._jid[1]) + return _format_jid(self._node, self._domain) + @property + def resource(self): + return self._resource - @resource.setter - def resource(self, value): - self._jid = JID(self, resource=value)._jid + @property + def full(self): + return _format_jid(self._node, self._domain, self._resource) - @user.setter - def user(self, value): - self._jid = JID(self, local=value)._jid - - @username.setter - def username(self, value): - self._jid = JID(self, local=value)._jid - - @local.setter - def local(self, value): - self._jid = JID(self, local=value)._jid + @property + def jid(self): + return _format_jid(self._node, self._domain, self._resource) @node.setter def node(self, value): - self._jid = JID(self, local=value)._jid + self._node = _validate_node(value) - @server.setter - def server(self, value): - self._jid = JID(self, domain=value)._jid + @user.setter + def user(self, value): + self._node = _validate_node(value) + + @local.setter + def local(self, value): + self._node = _validate_node(value) + + @username.setter + def username(self, value): + self._node = _validate_node(value) @domain.setter def domain(self, value): - self._jid = JID(self, domain=value)._jid + self._domain = _validate_domain(value) + + @server.setter + def server(self, value): + self._domain = _validate_domain(value) @host.setter def host(self, value): - self._jid = JID(self, domain=value)._jid - - @full.setter - def full(self, value): - self._jid = JID(value)._jid - - @jid.setter - def jid(self, value): - self._jid = JID(value)._jid + self._domain = _validate_domain(value) @bare.setter def bare(self, value): - parsed = JID(value)._jid - self._jid = (parsed[0], parsed[1], self._jid[2]) + node, domain, resource = _parse_jid(value) + assert not resource + self._node = node + self._domain = domain + @resource.setter + def resource(self, value): + self._resource = _validate_resource(value) + + @full.setter + def full(self, value): + self._node, self._domain, self._resource = _parse_jid(value) + + @jid.setter + def jid(self, value): + self._node, self._domain, self._resource = _parse_jid(value) def __str__(self): """Use the full JID as the string value.""" - return _format_jid(*self._jid) + return _format_jid(self._node, self._domain, self._resource) def __repr__(self): """Use the full JID as the representation.""" - return self.__str__() + return _format_jid(self._node, self._domain, self._resource) # pylint: disable=W0212 def __eq__(self, other): """Two JIDs are equal if they have the same full JID value.""" if isinstance(other, UnescapedJID): return False + if not isinstance(other, JID): + other = JID(other) - other = JID(other) - return self._jid == other._jid + return (self._node == other._node and + self._domain == other._domain and + self._resource == other._resource) - # pylint: disable=W0212 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.__str__()) - - def __copy__(self): - """Generate a duplicate JID.""" - return JID(self) - - def __deepcopy__(self, memo): - """Generate a duplicate JID.""" - return JID(deepcopy(str(self), memo)) + return hash(_format_jid(self._node, self._domain, self._resource))