summaryrefslogtreecommitdiff
path: root/sleekxmpp/jid.py
diff options
context:
space:
mode:
authorLance Stout <lancestout@gmail.com>2012-07-22 00:16:35 -0700
committerLance Stout <lancestout@gmail.com>2012-07-24 01:43:20 -0700
commite4e18a416f63cfe44c1db92e5e18c4dfe8e229c1 (patch)
treef5b7a6393f0acf07ae6197df3f43efbe60193330 /sleekxmpp/jid.py
parent01cc0e6defcb4d1999d9777a316ffeabef4b0c93 (diff)
downloadslixmpp-e4e18a416f63cfe44c1db92e5e18c4dfe8e229c1.tar.gz
slixmpp-e4e18a416f63cfe44c1db92e5e18c4dfe8e229c1.tar.bz2
slixmpp-e4e18a416f63cfe44c1db92e5e18c4dfe8e229c1.tar.xz
slixmpp-e4e18a416f63cfe44c1db92e5e18c4dfe8e229c1.zip
Add validation for JIDs.
Diffstat (limited to 'sleekxmpp/jid.py')
-rw-r--r--sleekxmpp/jid.py262
1 files changed, 262 insertions, 0 deletions
diff --git a/sleekxmpp/jid.py b/sleekxmpp/jid.py
new file mode 100644
index 00000000..e6da5746
--- /dev/null
+++ b/sleekxmpp/jid.py
@@ -0,0 +1,262 @@
+# -*- 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)