source:
ralphm-patches/stanza-module.patch
Last change on this file was 79:0752a1cca356, checked in by Ralph Meijer <ralphm@…>, 6 years ago | |
---|---|
File size: 17.8 KB |
-
wokkel/generic.py
# HG changeset patch # Parent b4c3d0419ab21df80fcab1fe7b90523a90c12533 diff --git a/wokkel/generic.py b/wokkel/generic.py
a b 7 7 Generic XMPP protocol helpers. 8 8 """ 9 9 10 __all__ = ['parseXml', 'FallbackHandler', 'VersionHandler', 'XmlPipe', 11 'DeferredXmlStreamFactory', 'prepareIDNName', 12 'Stanza', 'Request', 'ErrorStanza'] 13 10 14 from zope.interface import implements 11 15 12 16 from twisted.internet import defer, protocol 13 from twisted.python import reflect14 17 from twisted.python.deprecate import deprecated 15 18 from twisted.python.versions import Version 16 from twisted.words.protocols.jabber import error, jid,xmlstream19 from twisted.words.protocols.jabber import error, xmlstream 17 20 from twisted.words.protocols.jabber.xmlstream import toResponse 18 21 from twisted.words.xish import domish, utility 19 22 from twisted.words.xish.xmlstream import BootstrapMixin 20 23 21 24 from wokkel.iwokkel import IDisco 25 from wokkel.stanza import Stanza, ErrorStanza, Request 22 26 from wokkel.subprotocols import XMPPHandler 23 27 24 28 IQ_GET = '/iq[@type="get"]' … … 47 51 48 52 49 53 50 def stripNamespace(rootElement):51 namespace = rootElement.uri52 53 def strip(element):54 if element.uri == namespace:55 element.uri = None56 if element.defaultUri == namespace:57 element.defaultUri = None58 for child in element.elements():59 strip(child)60 61 if namespace is not None:62 strip(rootElement)63 64 return rootElement65 66 67 68 54 class FallbackHandler(XMPPHandler): 69 55 """ 70 56 XMPP subprotocol handler that catches unhandled iq requests. … … 162 148 163 149 164 150 165 class Stanza(object):166 """167 Abstract representation of a stanza.168 169 @ivar sender: The sending entity.170 @type sender: L{jid.JID}171 @ivar recipient: The receiving entity.172 @type recipient: L{jid.JID}173 """174 175 recipient = None176 sender = None177 stanzaKind = None178 stanzaID = None179 stanzaType = None180 181 def __init__(self, recipient=None, sender=None):182 self.recipient = recipient183 self.sender = sender184 185 186 @classmethod187 def fromElement(Class, element):188 """189 Create a stanza from a L{domish.Element}.190 """191 stanza = Class()192 stanza.parseElement(element)193 return stanza194 195 196 def parseElement(self, element):197 """198 Parse the stanza element.199 200 This is called with the stanza's element when a L{Stanza} is201 created using L{fromElement}. It parses the stanza's core attributes202 (addressing, type and id), strips the namespace from the stanza203 element for easier transport across streams and passes on204 child elements for further parsing.205 206 Child element parsers are defined by providing a C{childParsers}207 attribute on a subclass, as a mapping from (URI, name) to the name208 of the handler on C{self}. C{parseElement} will accumulate209 C{childParsers} from its class hierarchy, iterate over the child210 elements and pass it to matching handlers based on the child element's211 URI and name. The special key of C{None} can be used to pass all212 child elements to.213 """214 if element.hasAttribute('from'):215 self.sender = jid.internJID(element['from'])216 if element.hasAttribute('to'):217 self.recipient = jid.internJID(element['to'])218 self.stanzaType = element.getAttribute('type')219 self.stanzaID = element.getAttribute('id')220 221 # Save element222 stripNamespace(element)223 self.element = element224 225 # accumulate all childHandlers in the class hierarchy of Class226 handlers = {}227 reflect.accumulateClassDict(self.__class__, 'childParsers', handlers)228 229 for child in element.elements():230 try:231 handler = handlers[child.uri, child.name]232 except KeyError:233 try:234 handler = handlers[None]235 except KeyError:236 continue237 238 getattr(self, handler)(child)239 240 241 def toElement(self):242 element = domish.Element((None, self.stanzaKind))243 if self.sender is not None:244 element['from'] = self.sender.full()245 if self.recipient is not None:246 element['to'] = self.recipient.full()247 if self.stanzaType:248 element['type'] = self.stanzaType249 if self.stanzaID:250 element['id'] = self.stanzaID251 return element252 253 254 255 class ErrorStanza(Stanza):256 257 def parseElement(self, element):258 Stanza.parseElement(self, element)259 self.exception = error.exceptionFromStanza(element)260 261 262 263 class Request(Stanza):264 """265 IQ request stanza.266 267 This is a base class for IQ get or set stanzas, to be used with268 L{wokkel.subprotocols.StreamManager.request}.269 """270 271 stanzaKind = 'iq'272 stanzaType = 'get'273 timeout = None274 275 childParsers = {None: 'parseRequest'}276 277 def __init__(self, recipient=None, sender=None, stanzaType=None):278 Stanza.__init__(self, recipient=recipient, sender=sender)279 if stanzaType is not None:280 self.stanzaType = stanzaType281 282 283 def parseRequest(self, element):284 """285 Called with the request's child element for parsing.286 287 When a request instance is created using L{fromElement}, this method288 is called with the child element of the iq. Override this method for289 parsing the request's payload.290 """291 292 293 def toElement(self):294 element = Stanza.toElement(self)295 296 if not self.stanzaID:297 element.addUniqueId()298 self.stanzaID = element['id']299 300 return element301 302 303 304 151 class DeferredXmlStreamFactory(BootstrapMixin, protocol.ClientFactory): 305 152 protocol = xmlstream.XmlStream 306 153 -
new file wokkel/stanza.py
diff --git a/wokkel/stanza.py b/wokkel/stanza.py new file mode 100644
- + 1 # -*- test-case-name: wokkel.test.test_stanza -*- 2 # 3 # Copyright (c) Ralph Meijer. 4 # See LICENSE for details. 5 6 """ 7 XMPP Stanza helpers. 8 """ 9 10 from twisted.python import reflect 11 from twisted.words.protocols.jabber import error, jid 12 from twisted.words.xish import domish 13 14 def stripNamespace(rootElement): 15 namespace = rootElement.uri 16 17 def strip(element): 18 if element.uri == namespace: 19 element.uri = None 20 if element.defaultUri == namespace: 21 element.defaultUri = None 22 for child in element.elements(): 23 strip(child) 24 25 if namespace is not None: 26 strip(rootElement) 27 28 return rootElement 29 30 31 32 class Stanza(object): 33 """ 34 Abstract representation of a stanza. 35 36 @ivar recipient: The receiving entity. 37 @type recipient: L{jid.JID} 38 39 @ivar sender: The sending entity. 40 @type sender: L{jid.JID} 41 42 @ivar stanzaKind: One of C{'message'}, C{'presence'}, C{'iq'}. 43 @type stanzaKind: L{unicode}. 44 45 @ivar stanzaID: The optional stanza identifier. 46 @type stanzaID: L{unicode}. 47 48 @ivar stanzaType: The optional stanza type. 49 @type stanzaType: L{unicode}. 50 51 @ivar element: The serialized XML of this stanza. 52 @type element: L{domish.Element}. 53 """ 54 55 recipient = None 56 sender = None 57 stanzaKind = None 58 stanzaID = None 59 stanzaType = None 60 element = None 61 62 def __init__(self, recipient=None, sender=None): 63 self.recipient = recipient 64 self.sender = sender 65 66 67 @classmethod 68 def fromElement(Class, element): 69 """ 70 Create a stanza from a L{domish.Element}. 71 """ 72 stanza = Class() 73 stanza.parseElement(element) 74 return stanza 75 76 77 def parseElement(self, element): 78 """ 79 Parse the stanza element. 80 81 This is called with the stanza's element when a L{Stanza} is 82 created using L{fromElement}. It parses the stanza's core attributes 83 (addressing, type and id), strips the namespace from the stanza 84 element for easier transport across streams and passes on 85 child elements for further parsing. 86 87 Child element parsers are defined by providing a C{childParsers} 88 attribute on a subclass, as a mapping from (URI, name) to the name 89 of the handler on C{self}. C{parseElement} will accumulate 90 C{childParsers} from its class hierarchy, iterate over the child 91 elements and pass it to matching handlers based on the child element's 92 URI and name. The special key of C{None} can be used to pass all 93 child elements to. 94 """ 95 if element.hasAttribute('from'): 96 self.sender = jid.internJID(element['from']) 97 if element.hasAttribute('to'): 98 self.recipient = jid.internJID(element['to']) 99 self.stanzaKind = element.name 100 self.stanzaType = element.getAttribute('type') 101 self.stanzaID = element.getAttribute('id') 102 103 # Save element 104 stripNamespace(element) 105 self.element = element 106 107 # accumulate all childHandlers in the class hierarchy of Class 108 handlers = {} 109 reflect.accumulateClassDict(self.__class__, 'childParsers', handlers) 110 111 for child in element.elements(): 112 try: 113 handler = handlers[child.uri, child.name] 114 except KeyError: 115 try: 116 handler = handlers[None] 117 except KeyError: 118 continue 119 120 getattr(self, handler)(child) 121 122 123 def toElement(self): 124 element = domish.Element((None, self.stanzaKind)) 125 self.element = element 126 if self.sender is not None: 127 element['from'] = self.sender.full() 128 if self.recipient is not None: 129 element['to'] = self.recipient.full() 130 if self.stanzaType: 131 element['type'] = self.stanzaType 132 if self.stanzaID: 133 element['id'] = self.stanzaID 134 return element 135 136 137 138 class ErrorStanza(Stanza): 139 140 def parseElement(self, element): 141 Stanza.parseElement(self, element) 142 self.exception = error.exceptionFromStanza(element) 143 144 145 146 class Request(Stanza): 147 """ 148 IQ request stanza. 149 150 This is a base class for IQ get or set stanzas, to be used with 151 L{wokkel.subprotocols.StreamManager.request}. 152 """ 153 154 stanzaKind = 'iq' 155 stanzaType = 'get' 156 timeout = None 157 158 childParsers = {None: 'parseRequest'} 159 160 def __init__(self, recipient=None, sender=None, stanzaType=None): 161 Stanza.__init__(self, recipient=recipient, sender=sender) 162 if stanzaType is not None: 163 self.stanzaType = stanzaType 164 165 166 def parseRequest(self, element): 167 """ 168 Called with the request's child element for parsing. 169 170 When a request instance is created using L{fromElement}, this method 171 is called with the child element of the iq. Override this method for 172 parsing the request's payload. 173 """ 174 175 176 def toElement(self): 177 element = Stanza.toElement(self) 178 179 if not self.stanzaID: 180 element.addUniqueId() 181 self.stanzaID = element['id'] 182 183 return element 184 185 186 -
new file wokkel/test/test_stanza.py
diff --git a/wokkel/test/test_stanza.py b/wokkel/test/test_stanza.py new file mode 100644
- + 1 # Copyright (c) Ralph Meijer. 2 # See LICENSE for details. 3 4 from twisted.trial import unittest 5 from twisted.words.protocols.jabber.jid import JID 6 7 from wokkel.generic import parseXml 8 from wokkel.stanza import Request, Stanza 9 10 NS_VERSION = 'jabber:iq:version' 11 12 """ 13 Tests for L{wokkel.stanza}. 14 """ 15 16 17 18 class StanzaTest(unittest.TestCase): 19 """ 20 Tests for L{Stanza}. 21 """ 22 23 def test_fromElement(self): 24 xml = """ 25 <message type='chat' from='other@example.org' to='user@example.org'/> 26 """ 27 28 element = parseXml(xml) 29 stanza = Stanza.fromElement(element) 30 self.assertEqual('chat', stanza.stanzaType) 31 self.assertEqual(JID('other@example.org'), stanza.sender) 32 self.assertEqual(JID('user@example.org'), stanza.recipient) 33 self.assertIdentical(element, stanza.element) 34 35 36 def test_fromElementStanzaKind(self): 37 """ 38 The stanza kind is also recorded in the stanza. 39 """ 40 xml = """<presence/>""" 41 stanza = Stanza.fromElement(parseXml(xml)) 42 self.assertEqual(u'presence', stanza.stanzaKind) 43 44 45 def test_fromElementChildParser(self): 46 """ 47 Child elements for which no parser is defined are ignored. 48 """ 49 xml = """ 50 <message from='other@example.org' to='user@example.org'> 51 <x xmlns='http://example.org/'/> 52 </message> 53 """ 54 55 class Message(Stanza): 56 childParsers = {('http://example.org/', 'x'): '_childParser_x'} 57 elements = [] 58 59 def _childParser_x(self, element): 60 self.elements.append(element) 61 62 message = Message.fromElement(parseXml(xml)) 63 self.assertEqual(1, len(message.elements)) 64 65 66 def test_fromElementChildParserAll(self): 67 """ 68 Child elements for which no parser is defined are ignored. 69 """ 70 xml = """ 71 <message from='other@example.org' to='user@example.org'> 72 <x xmlns='http://example.org/'/> 73 </message> 74 """ 75 76 class Message(Stanza): 77 childParsers = {None: '_childParser'} 78 elements = [] 79 80 def _childParser(self, element): 81 self.elements.append(element) 82 83 message = Message.fromElement(parseXml(xml)) 84 self.assertEqual(1, len(message.elements)) 85 86 87 def test_fromElementChildParserUnknown(self): 88 """ 89 Child elements for which no parser is defined are ignored. 90 """ 91 xml = """ 92 <message from='other@example.org' to='user@example.org'> 93 <x xmlns='http://example.org/'/> 94 </message> 95 """ 96 Stanza.fromElement(parseXml(xml)) 97 98 99 100 101 class RequestTest(unittest.TestCase): 102 """ 103 Tests for L{Request}. 104 """ 105 106 def setUp(self): 107 self.request = Request() 108 109 110 def test_requestParser(self): 111 """ 112 The request's child element is passed to requestParser. 113 """ 114 xml = """ 115 <iq type='get'> 116 <query xmlns='jabber:iq:version'/> 117 </iq> 118 """ 119 120 class VersionRequest(Request): 121 elements = [] 122 123 def parseRequest(self, element): 124 self.elements.append((element.uri, element.name)) 125 126 request = VersionRequest.fromElement(parseXml(xml)) 127 self.assertEqual([(NS_VERSION, 'query')], request.elements) 128 129 130 def test_toElementStanzaKind(self): 131 """ 132 A request is an iq stanza. 133 """ 134 element = self.request.toElement() 135 self.assertIdentical(None, element.uri) 136 self.assertEquals('iq', element.name) 137 138 139 def test_toElementStanzaType(self): 140 """ 141 The request has type 'get'. 142 """ 143 self.assertEquals('get', self.request.stanzaType) 144 element = self.request.toElement() 145 self.assertEquals('get', element.getAttribute('type')) 146 147 148 def test_toElementStanzaTypeSet(self): 149 """ 150 The request has type 'set'. 151 """ 152 self.request.stanzaType = 'set' 153 element = self.request.toElement() 154 self.assertEquals('set', element.getAttribute('type')) 155 156 157 def test_toElementStanzaID(self): 158 """ 159 A request, when rendered, has an identifier. 160 """ 161 element = self.request.toElement() 162 self.assertNotIdentical(None, self.request.stanzaID) 163 self.assertEquals(self.request.stanzaID, element.getAttribute('id')) 164 165 166 def test_toElementRecipient(self): 167 """ 168 A request without recipient, has no 'to' attribute. 169 """ 170 self.request = Request(recipient=JID('other@example.org')) 171 self.assertEquals(JID('other@example.org'), self.request.recipient) 172 element = self.request.toElement() 173 self.assertEquals(u'other@example.org', element.getAttribute('to')) 174 175 176 def test_toElementRecipientNone(self): 177 """ 178 A request without recipient, has no 'to' attribute. 179 """ 180 element = self.request.toElement() 181 self.assertFalse(element.hasAttribute('to')) 182 183 184 def test_toElementSender(self): 185 """ 186 A request with sender, has a 'from' attribute. 187 """ 188 self.request = Request(sender=JID('user@example.org')) 189 self.assertEquals(JID('user@example.org'), self.request.sender) 190 element = self.request.toElement() 191 self.assertEquals(u'user@example.org', element.getAttribute('from')) 192 193 194 def test_toElementSenderNone(self): 195 """ 196 A request without sender, has no 'from' attribute. 197 """ 198 element = self.request.toElement() 199 self.assertFalse(element.hasAttribute('from')) 200 201 202 def test_timeoutDefault(self): 203 """ 204 The default is no timeout. 205 """ 206 self.assertIdentical(None, self.request.timeout) 207 208 209 def test_stanzaTypeInit(self): 210 """ 211 If stanzaType is passed in __init__, it overrides the class variable. 212 """ 213 214 class SetRequest(Request): 215 stanzaType = 'set' 216 217 request = SetRequest(stanzaType='get') 218 self.assertEqual('get', request.stanzaType) 219 220 221 def test_stanzaTypeClass(self): 222 """ 223 If stanzaType is not passed in __init__, the class variable is used. 224 """ 225 226 class SetRequest(Request): 227 stanzaType = 'set' 228 229 request = SetRequest() 230 self.assertEqual('set', request.stanzaType)
Note: See TracBrowser
for help on using the repository browser.