source: wokkel/test/test_generic.py @ 178:37f36ea93838

Last change on this file since 178:37f36ea93838 was 178:37f36ea93838, checked in by Ralph Meijer <ralphm@…>, 8 years ago

Properly encode IDN domain names for SRV lookups.

Before Twisted 12.3.0, the SRV lookup done for outgoing
connections succeeded if passed a unicode string with all-ASCII
code points. A recent change made the DNS code more strict, and
only byte string are accepted as domain name. See
http://twistedmatrix.com/trac/ticket/6245 for details.

This change makes sure domain names are encoded to their ASCII
Compatible Equivalent (ACE) version before passing the resulting
byte string to twisted.names.srvconnect.SRVConnector, as per
RFC 3490.

Note that while connecting to servers with an IDN domain name
now works properly, authentication using MD5-Digest SASL mechanism
will fail until http://twistedmatrix.com/trac/ticket/5066 has been
resolved.

Fixes: #77.

  • Property exe set to *
File size: 9.4 KB
Line 
1# Copyright (c) Ralph Meijer.
2# See LICENSE for details.
3
4"""
5Tests for L{wokkel.generic}.
6"""
7
8from twisted.trial import unittest
9from twisted.words.xish import domish
10from twisted.words.protocols.jabber.jid import JID
11
12from wokkel import generic
13from wokkel.test.helpers import XmlStreamStub
14
15NS_VERSION = 'jabber:iq:version'
16
17class VersionHandlerTest(unittest.TestCase):
18    """
19    Tests for L{wokkel.generic.VersionHandler}.
20    """
21
22    def test_onVersion(self):
23        """
24        Test response to incoming version request.
25        """
26        self.stub = XmlStreamStub()
27        self.protocol = generic.VersionHandler('Test', '0.1.0')
28        self.protocol.xmlstream = self.stub.xmlstream
29        self.protocol.send = self.stub.xmlstream.send
30        self.protocol.connectionInitialized()
31
32        iq = domish.Element((None, 'iq'))
33        iq['from'] = 'user@example.org/Home'
34        iq['to'] = 'example.org'
35        iq['type'] = 'get'
36        iq.addElement((NS_VERSION, 'query'))
37        self.stub.send(iq)
38
39        response = self.stub.output[-1]
40        self.assertEquals('user@example.org/Home', response['to'])
41        self.assertEquals('example.org', response['from'])
42        self.assertEquals('result', response['type'])
43        self.assertEquals(NS_VERSION, response.query.uri)
44        elements = list(domish.generateElementsQNamed(response.query.children,
45                                                      'name', NS_VERSION))
46        self.assertEquals(1, len(elements))
47        self.assertEquals('Test', unicode(elements[0]))
48        elements = list(domish.generateElementsQNamed(response.query.children,
49                                                      'version', NS_VERSION))
50        self.assertEquals(1, len(elements))
51        self.assertEquals('0.1.0', unicode(elements[0]))
52
53
54
55class XmlPipeTest(unittest.TestCase):
56    """
57    Tests for L{wokkel.generic.XmlPipe}.
58    """
59
60    def setUp(self):
61        self.pipe = generic.XmlPipe()
62
63
64    def test_sendFromSource(self):
65        """
66        Send an element from the source and observe it from the sink.
67        """
68        def cb(obj):
69            called.append(obj)
70
71        called = []
72        self.pipe.sink.addObserver('/test[@xmlns="testns"]', cb)
73        element = domish.Element(('testns', 'test'))
74        self.pipe.source.send(element)
75        self.assertEquals([element], called)
76
77
78    def test_sendFromSink(self):
79        """
80        Send an element from the sink and observe it from the source.
81        """
82        def cb(obj):
83            called.append(obj)
84
85        called = []
86        self.pipe.source.addObserver('/test[@xmlns="testns"]', cb)
87        element = domish.Element(('testns', 'test'))
88        self.pipe.sink.send(element)
89        self.assertEquals([element], called)
90
91
92
93class StanzaTest(unittest.TestCase):
94    """
95    Tests for L{generic.Stanza}.
96    """
97
98    def test_fromElement(self):
99        xml = """
100        <message type='chat' from='other@example.org' to='user@example.org'/>
101        """
102
103        stanza = generic.Stanza.fromElement(generic.parseXml(xml))
104        self.assertEqual('chat', stanza.stanzaType)
105        self.assertEqual(JID('other@example.org'), stanza.sender)
106        self.assertEqual(JID('user@example.org'), stanza.recipient)
107
108
109    def test_fromElementChildParser(self):
110        """
111        Child elements for which no parser is defined are ignored.
112        """
113        xml = """
114        <message from='other@example.org' to='user@example.org'>
115          <x xmlns='http://example.org/'/>
116        </message>
117        """
118
119        class Message(generic.Stanza):
120            childParsers = {('http://example.org/', 'x'): '_childParser_x'}
121            elements = []
122
123            def _childParser_x(self, element):
124                self.elements.append(element)
125
126        message = Message.fromElement(generic.parseXml(xml))
127        self.assertEqual(1, len(message.elements))
128
129
130    def test_fromElementChildParserAll(self):
131        """
132        Child elements for which no parser is defined are ignored.
133        """
134        xml = """
135        <message from='other@example.org' to='user@example.org'>
136          <x xmlns='http://example.org/'/>
137        </message>
138        """
139
140        class Message(generic.Stanza):
141            childParsers = {None: '_childParser'}
142            elements = []
143
144            def _childParser(self, element):
145                self.elements.append(element)
146
147        message = Message.fromElement(generic.parseXml(xml))
148        self.assertEqual(1, len(message.elements))
149
150
151    def test_fromElementChildParserUnknown(self):
152        """
153        Child elements for which no parser is defined are ignored.
154        """
155        xml = """
156        <message from='other@example.org' to='user@example.org'>
157          <x xmlns='http://example.org/'/>
158        </message>
159        """
160        generic.Stanza.fromElement(generic.parseXml(xml))
161
162
163
164
165class RequestTest(unittest.TestCase):
166    """
167    Tests for L{generic.Request}.
168    """
169
170    def setUp(self):
171        self.request = generic.Request()
172
173
174    def test_requestParser(self):
175        """
176        The request's child element is passed to requestParser.
177        """
178        xml = """
179        <iq type='get'>
180          <query xmlns='jabber:iq:version'/>
181        </iq>
182        """
183
184        class VersionRequest(generic.Request):
185            elements = []
186
187            def parseRequest(self, element):
188                self.elements.append((element.uri, element.name))
189
190        request = VersionRequest.fromElement(generic.parseXml(xml))
191        self.assertEqual([(NS_VERSION, 'query')], request.elements)
192
193
194    def test_toElementStanzaKind(self):
195        """
196        A request is an iq stanza.
197        """
198        element = self.request.toElement()
199        self.assertIdentical(None, element.uri)
200        self.assertEquals('iq', element.name)
201
202
203    def test_toElementStanzaType(self):
204        """
205        The request has type 'get'.
206        """
207        self.assertEquals('get', self.request.stanzaType)
208        element = self.request.toElement()
209        self.assertEquals('get', element.getAttribute('type'))
210
211
212    def test_toElementStanzaTypeSet(self):
213        """
214        The request has type 'set'.
215        """
216        self.request.stanzaType = 'set'
217        element = self.request.toElement()
218        self.assertEquals('set', element.getAttribute('type'))
219
220
221    def test_toElementStanzaID(self):
222        """
223        A request, when rendered, has an identifier.
224        """
225        element = self.request.toElement()
226        self.assertNotIdentical(None, self.request.stanzaID)
227        self.assertEquals(self.request.stanzaID, element.getAttribute('id'))
228
229
230    def test_toElementRecipient(self):
231        """
232        A request without recipient, has no 'to' attribute.
233        """
234        self.request = generic.Request(recipient=JID('other@example.org'))
235        self.assertEquals(JID('other@example.org'), self.request.recipient)
236        element = self.request.toElement()
237        self.assertEquals(u'other@example.org', element.getAttribute('to'))
238
239
240    def test_toElementRecipientNone(self):
241        """
242        A request without recipient, has no 'to' attribute.
243        """
244        element = self.request.toElement()
245        self.assertFalse(element.hasAttribute('to'))
246
247
248    def test_toElementSender(self):
249        """
250        A request with sender, has a 'from' attribute.
251        """
252        self.request = generic.Request(sender=JID('user@example.org'))
253        self.assertEquals(JID('user@example.org'), self.request.sender)
254        element = self.request.toElement()
255        self.assertEquals(u'user@example.org', element.getAttribute('from'))
256
257
258    def test_toElementSenderNone(self):
259        """
260        A request without sender, has no 'from' attribute.
261        """
262        element = self.request.toElement()
263        self.assertFalse(element.hasAttribute('from'))
264
265
266    def test_timeoutDefault(self):
267        """
268        The default is no timeout.
269        """
270        self.assertIdentical(None, self.request.timeout)
271
272
273
274class PrepareIDNNameTests(unittest.TestCase):
275    """
276    Tests for L{wokkel.generic.prepareIDNName}.
277    """
278
279    def test_bytestring(self):
280        """
281        An ASCII-encoded byte string is left as-is.
282        """
283        name = b"example.com"
284        result = generic.prepareIDNName(name)
285        self.assertEqual(b"example.com", result)
286
287
288    def test_unicode(self):
289        """
290        A unicode all-ASCII name is converted to an ASCII byte string.
291        """
292        name = u"example.com"
293        result = generic.prepareIDNName(name)
294        self.assertEqual(b"example.com", result)
295
296
297    def test_unicodeNonASCII(self):
298        """
299        A unicode with non-ASCII is converted to its ACE equivalent.
300        """
301        name = u"\u00e9chec.example.com"
302        result = generic.prepareIDNName(name)
303        self.assertEqual(b"xn--chec-9oa.example.com", result)
304
305
306    def test_unicodeHalfwidthIdeographicFullStop(self):
307        """
308        Exotic dots in unicode names are converted to Full Stop.
309        """
310        name = u"\u00e9chec.example\uff61com"
311        result = generic.prepareIDNName(name)
312        self.assertEqual(b"xn--chec-9oa.example.com", result)
313
314
315    def test_unicodeTrailingDot(self):
316        """
317        Unicode names with trailing dots retain the trailing dot.
318
319        L{encodings.idna.ToASCII} doesn't allow the empty string as the input,
320        hence the implementation needs to strip a trailing dot, and re-add it
321        after encoding the labels.
322        """
323        name = u"example.com."
324        result = generic.prepareIDNName(name)
325        self.assertEqual(b"example.com.", result)
Note: See TracBrowser for help on using the repository browser.