changeset 2958:6eebb112108b

GHOP: 1st part of XEP-115 (Entity Capabilities) implementation by starGaming
author Karol 'grzywacz' Nowak <grzywacz@sul.uni.lodz.pl>
date Sun, 09 Dec 2007 22:36:14 +0100
parents bc85e5200dd3
children fcfa07f447f5
files MoinMoin/support/python_compatibility.py jabberbot/_tests/test_capat.py jabberbot/capat.py
diffstat 3 files changed, 149 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/support/python_compatibility.py	Tue Dec 04 22:24:53 2007 +0100
+++ b/MoinMoin/support/python_compatibility.py	Sun Dec 09 22:36:14 2007 +0100
@@ -74,3 +74,18 @@
             else:
                 d = kw or self.kw
             return self.fn(*(self.args + args), **d)
+"""
+This is a feature from python 2.5, needed for compatibility with python 2.3 and 2.4,
+although it may not be 100% compatible.
+"""
+try:
+    from hashlib import new as hash_new
+except (NameError,  ImportError):
+    def hash_new(name, string=''):
+        if name in ('SHA1', 'sha1'):
+            import sha
+            return sha.new(string)
+        elif name in ('MD5', 'md5'):
+            import md5
+            return md5.new(string)
+        raise ValueError("unsupported hash type")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jabberbot/_tests/test_capat.py	Sun Dec 09 22:36:14 2007 +0100
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import py.test
+import capat
+
+def test_ver_simple():
+    # example values supplied by the XEP 
+    ident = (("client", "pc"),)
+    feat = ("http://jabber.org/protocol/disco#info",
+            "http://jabber.org/protocol/disco#items",
+            "http://jabber.org/protocol/muc",
+           )
+    
+    assert capat.generate_ver(ident, feat) == "8RovUdtOmiAjzj+xI7SK5BCw3A8="
+
+def test_ver_complex():
+    # this test should verify that ordering works properly
+    ident = (("client", "animal"),
+             ("client", "bear"), # type ordering after category ordering
+             ("apples", "bar"),
+             ("apple", "foo"), # "apples" starts with "apple"
+                               # thus it's greater
+            )
+    feat = ()
+    
+    expected = capat.hash_new('sha1')
+    expected.update("apple/foo<apples/bar<client/animal<client/bear<")
+    expected = capat.base64.b64encode(expected.digest())
+    assert capat.generate_ver(ident, feat) == expected
+
+def test_xml():
+    try:
+        import pyxmpp.iq
+    except ImportError:
+        py.test.skip("pyxmpp needs to be installed for this test")
+    x = pyxmpp.iq.Iq(stanza_type='result', stanza_id='disco1',
+                     from_jid='romeo@montague.lit/orchard',
+                     to_jid='juliet@capulet.lit/chamber')
+    y = x.new_query(ns_uri='http://jabber.org/protocol/disco#info')
+    z = y.newChild(None, 'identity', None)
+    z.setProp('category', 'client')
+    z.setProp('type', 'pc')
+    y.newChild(None, 'feature', None).setProp(
+        'var','http://jabber.org/protocol/disco#info')
+    y.newChild(None, 'feature', None).setProp(
+        'var', 'http://jabber.org/protocol/disco#items')
+    y.newChild(None, 'feature', None).setProp(
+        'var', 'http://jabber.org/protocol/muc')
+    
+    assert capat.hash_iq(x) == "8RovUdtOmiAjzj+xI7SK5BCw3A8="
+    # hash value taken from `test_ver_simple`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jabberbot/capat.py	Sun Dec 09 22:36:14 2007 +0100
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+
+"""
+    MoinMoin - Entity Capabilities (XEP-0115) implementation
+
+    Enables Jabber/XMPP clients to save bandwidth by caching 
+    information about extensions supported by various client 
+    implementations.
+
+    @copyright: 2007 by Robert Lehmann <lehmannro@gmail.com>
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import base64
+import itertools
+from MoinMoin.support.python_compatibility import hash_new
+
+HASHALIASES = { # IANA Hash Function Textual Names Registry
+                # to `hashlib.new` mapping
+    'sha-1': 'sha1',
+    'sha-224': 'sha224',
+    'sha-256': 'sha256',
+    'sha-384': 'sha384',
+    'sha-512': 'sha512',
+    'md5': 'md5',
+    'md2': 'md2',
+}
+
+
+def generate_ver(identities, features, algo='sha-1'):
+    """Generate the 'ver' attribute according to XEP-0115.
+
+    See http://www.xmpp.org/extensions/xep-0115.html#ver
+    
+    @param identities: a number of (category, type) identity pairs
+    @param algo: optional algo attribute with IANA aliasing
+
+    @type identities: iterable of 2-tuples of strings
+    @type features: iterable of strings
+    @type algo: string (IANA Hash Function Textual Name)
+    """
+
+    # only IANA aliases are supported
+    if algo not in HASHALIASES:
+        raise ValueError("undefined hash algorithm")
+    algo = hash_new(HASHALIASES[algo])
+
+    ident = list(identities)
+    # default sorting already considers both, category and type
+    ident.sort()
+    ident = ('%s/%s' % (idcat, idtype) for idcat, idtype in ident)
+    
+    feat = list(features)
+    # strings (byte arrays) are ordered by i;octet by default
+    feat.sort()
+
+    s = '<'.join(itertools.chain(ident, feat, ('',)))
+    # the trailing empty string adds a trailing '<' to the result
+    algo.update(s)
+    s = base64.b64encode(algo.digest())
+
+    return s
+
+def hash_iq(stanza, algo='sha-1'):
+    """Search an <Iq/> entity for features/identities and generate a
+    'ver' attribute hash.
+
+    @type stanza: pyxmpp.iq.Iq
+    """
+    stanza = iter(stanza.get_query())
+    stanza.next() # drop first item: whole query
+    
+    feat = []
+    ident = []
+    
+    # traverse all child nodes
+    for item in stanza:
+        if item.name == 'identity':
+            ident.append((item.prop('category'), item.prop('type')))
+        elif item.name == 'feature':
+            feat.append(item.prop('var'))
+
+    return generate_ver(ident, feat, algo)