Ticket #3582: twisted3582.patch
File twisted3582.patch, 72.4 KB (added by , 9 years ago) |
---|
-
twisted/test/test_sip.py
1 # -*- test-case-name: twisted.test.test_sip -*-2 # Copyright (c) Twisted Matrix Laboratories.3 # See LICENSE for details.4 5 6 """Session Initialization Protocol tests."""7 8 from twisted.trial import unittest, util9 from twisted.protocols import sip10 from twisted.internet import defer, reactor, utils11 from twisted.python.versions import Version12 13 from twisted.test import proto_helpers14 15 from twisted import cred16 import twisted.cred.portal17 import twisted.cred.checkers18 19 from zope.interface import implements20 21 22 # request, prefixed by random CRLFs23 request1 = "\n\r\n\n\r" + """\24 INVITE sip:foo SIP/2.025 From: mo26 To: joe27 Content-Length: 428 29 abcd""".replace("\n", "\r\n")30 31 # request, no content-length32 request2 = """INVITE sip:foo SIP/2.033 From: mo34 To: joe35 36 1234""".replace("\n", "\r\n")37 38 # request, with garbage after39 request3 = """INVITE sip:foo SIP/2.040 From: mo41 To: joe42 Content-Length: 443 44 123445 46 lalalal""".replace("\n", "\r\n")47 48 # three requests49 request4 = """INVITE sip:foo SIP/2.050 From: mo51 To: joe52 Content-Length: 053 54 INVITE sip:loop SIP/2.055 From: foo56 To: bar57 Content-Length: 458 59 abcdINVITE sip:loop SIP/2.060 From: foo61 To: bar62 Content-Length: 463 64 1234""".replace("\n", "\r\n")65 66 # response, no content67 response1 = """SIP/2.0 200 OK68 From: foo69 To:bar70 Content-Length: 071 72 """.replace("\n", "\r\n")73 74 # short header version75 request_short = """\76 INVITE sip:foo SIP/2.077 f: mo78 t: joe79 l: 480 81 abcd""".replace("\n", "\r\n")82 83 request_natted = """\84 INVITE sip:foo SIP/2.085 Via: SIP/2.0/UDP 10.0.0.1:5060;rport86 87 """.replace("\n", "\r\n")88 89 class TestRealm:90 def requestAvatar(self, avatarId, mind, *interfaces):91 return sip.IContact, None, lambda: None92 93 class MessageParsingTestCase(unittest.TestCase):94 def setUp(self):95 self.l = []96 self.parser = sip.MessagesParser(self.l.append)97 98 def feedMessage(self, message):99 self.parser.dataReceived(message)100 self.parser.dataDone()101 102 def validateMessage(self, m, method, uri, headers, body):103 """Validate Requests."""104 self.assertEqual(m.method, method)105 self.assertEqual(m.uri.toString(), uri)106 self.assertEqual(m.headers, headers)107 self.assertEqual(m.body, body)108 self.assertEqual(m.finished, 1)109 110 def testSimple(self):111 l = self.l112 self.feedMessage(request1)113 self.assertEqual(len(l), 1)114 self.validateMessage(115 l[0], "INVITE", "sip:foo",116 {"from": ["mo"], "to": ["joe"], "content-length": ["4"]},117 "abcd")118 119 def testTwoMessages(self):120 l = self.l121 self.feedMessage(request1)122 self.feedMessage(request2)123 self.assertEqual(len(l), 2)124 self.validateMessage(125 l[0], "INVITE", "sip:foo",126 {"from": ["mo"], "to": ["joe"], "content-length": ["4"]},127 "abcd")128 self.validateMessage(l[1], "INVITE", "sip:foo",129 {"from": ["mo"], "to": ["joe"]},130 "1234")131 132 def testGarbage(self):133 l = self.l134 self.feedMessage(request3)135 self.assertEqual(len(l), 1)136 self.validateMessage(137 l[0], "INVITE", "sip:foo",138 {"from": ["mo"], "to": ["joe"], "content-length": ["4"]},139 "1234")140 141 def testThreeInOne(self):142 l = self.l143 self.feedMessage(request4)144 self.assertEqual(len(l), 3)145 self.validateMessage(146 l[0], "INVITE", "sip:foo",147 {"from": ["mo"], "to": ["joe"], "content-length": ["0"]},148 "")149 self.validateMessage(150 l[1], "INVITE", "sip:loop",151 {"from": ["foo"], "to": ["bar"], "content-length": ["4"]},152 "abcd")153 self.validateMessage(154 l[2], "INVITE", "sip:loop",155 {"from": ["foo"], "to": ["bar"], "content-length": ["4"]},156 "1234")157 158 def testShort(self):159 l = self.l160 self.feedMessage(request_short)161 self.assertEqual(len(l), 1)162 self.validateMessage(163 l[0], "INVITE", "sip:foo",164 {"from": ["mo"], "to": ["joe"], "content-length": ["4"]},165 "abcd")166 167 def testSimpleResponse(self):168 l = self.l169 self.feedMessage(response1)170 self.assertEqual(len(l), 1)171 m = l[0]172 self.assertEqual(m.code, 200)173 self.assertEqual(m.phrase, "OK")174 self.assertEqual(175 m.headers,176 {"from": ["foo"], "to": ["bar"], "content-length": ["0"]})177 self.assertEqual(m.body, "")178 self.assertEqual(m.finished, 1)179 180 181 class MessageParsingTestCase2(MessageParsingTestCase):182 """Same as base class, but feed data char by char."""183 184 def feedMessage(self, message):185 for c in message:186 self.parser.dataReceived(c)187 self.parser.dataDone()188 189 190 class MakeMessageTestCase(unittest.TestCase):191 192 def testRequest(self):193 r = sip.Request("INVITE", "sip:foo")194 r.addHeader("foo", "bar")195 self.assertEqual(196 r.toString(),197 "INVITE sip:foo SIP/2.0\r\nFoo: bar\r\n\r\n")198 199 def testResponse(self):200 r = sip.Response(200, "OK")201 r.addHeader("foo", "bar")202 r.addHeader("Content-Length", "4")203 r.bodyDataReceived("1234")204 self.assertEqual(205 r.toString(),206 "SIP/2.0 200 OK\r\nFoo: bar\r\nContent-Length: 4\r\n\r\n1234")207 208 def testStatusCode(self):209 r = sip.Response(200)210 self.assertEqual(r.toString(), "SIP/2.0 200 OK\r\n\r\n")211 212 213 class ViaTestCase(unittest.TestCase):214 215 def checkRoundtrip(self, v):216 s = v.toString()217 self.assertEqual(s, sip.parseViaHeader(s).toString())218 219 def testExtraWhitespace(self):220 v1 = sip.parseViaHeader('SIP/2.0/UDP 192.168.1.1:5060')221 v2 = sip.parseViaHeader('SIP/2.0/UDP 192.168.1.1:5060')222 self.assertEqual(v1.transport, v2.transport)223 self.assertEqual(v1.host, v2.host)224 self.assertEqual(v1.port, v2.port)225 226 def test_complex(self):227 """228 Test parsing a Via header with one of everything.229 """230 s = ("SIP/2.0/UDP first.example.com:4000;ttl=16;maddr=224.2.0.1"231 " ;branch=a7c6a8dlze (Example)")232 v = sip.parseViaHeader(s)233 self.assertEqual(v.transport, "UDP")234 self.assertEqual(v.host, "first.example.com")235 self.assertEqual(v.port, 4000)236 self.assertEqual(v.rport, None)237 self.assertEqual(v.rportValue, None)238 self.assertEqual(v.rportRequested, False)239 self.assertEqual(v.ttl, 16)240 self.assertEqual(v.maddr, "224.2.0.1")241 self.assertEqual(v.branch, "a7c6a8dlze")242 self.assertEqual(v.hidden, 0)243 self.assertEqual(v.toString(),244 "SIP/2.0/UDP first.example.com:4000"245 ";ttl=16;branch=a7c6a8dlze;maddr=224.2.0.1")246 self.checkRoundtrip(v)247 248 def test_simple(self):249 """250 Test parsing a simple Via header.251 """252 s = "SIP/2.0/UDP example.com;hidden"253 v = sip.parseViaHeader(s)254 self.assertEqual(v.transport, "UDP")255 self.assertEqual(v.host, "example.com")256 self.assertEqual(v.port, 5060)257 self.assertEqual(v.rport, None)258 self.assertEqual(v.rportValue, None)259 self.assertEqual(v.rportRequested, False)260 self.assertEqual(v.ttl, None)261 self.assertEqual(v.maddr, None)262 self.assertEqual(v.branch, None)263 self.assertEqual(v.hidden, True)264 self.assertEqual(v.toString(),265 "SIP/2.0/UDP example.com:5060;hidden")266 self.checkRoundtrip(v)267 268 def testSimpler(self):269 v = sip.Via("example.com")270 self.checkRoundtrip(v)271 272 273 def test_deprecatedRPort(self):274 """275 Setting rport to True is deprecated, but still produces a Via header276 with the expected properties.277 """278 v = sip.Via("foo.bar", rport=True)279 280 warnings = self.flushWarnings(281 offendingFunctions=[self.test_deprecatedRPort])282 self.assertEqual(len(warnings), 1)283 self.assertEqual(284 warnings[0]['message'],285 'rport=True is deprecated since Twisted 9.0.')286 self.assertEqual(287 warnings[0]['category'],288 DeprecationWarning)289 290 self.assertEqual(v.toString(), "SIP/2.0/UDP foo.bar:5060;rport")291 self.assertEqual(v.rport, True)292 self.assertEqual(v.rportRequested, True)293 self.assertEqual(v.rportValue, None)294 295 296 def test_rport(self):297 """298 An rport setting of None should insert the parameter with no value.299 """300 v = sip.Via("foo.bar", rport=None)301 self.assertEqual(v.toString(), "SIP/2.0/UDP foo.bar:5060;rport")302 self.assertEqual(v.rportRequested, True)303 self.assertEqual(v.rportValue, None)304 305 306 def test_rportValue(self):307 """308 An rport numeric setting should insert the parameter with the number309 value given.310 """311 v = sip.Via("foo.bar", rport=1)312 self.assertEqual(v.toString(), "SIP/2.0/UDP foo.bar:5060;rport=1")313 self.assertEqual(v.rportRequested, False)314 self.assertEqual(v.rportValue, 1)315 self.assertEqual(v.rport, 1)316 317 318 def testNAT(self):319 s = "SIP/2.0/UDP 10.0.0.1:5060;received=22.13.1.5;rport=12345"320 v = sip.parseViaHeader(s)321 self.assertEqual(v.transport, "UDP")322 self.assertEqual(v.host, "10.0.0.1")323 self.assertEqual(v.port, 5060)324 self.assertEqual(v.received, "22.13.1.5")325 self.assertEqual(v.rport, 12345)326 327 self.assertNotEquals(v.toString().find("rport=12345"), -1)328 329 330 def test_unknownParams(self):331 """332 Parsing and serializing Via headers with unknown parameters should work.333 """334 s = "SIP/2.0/UDP example.com:5060;branch=a12345b;bogus;pie=delicious"335 v = sip.parseViaHeader(s)336 self.assertEqual(v.toString(), s)337 338 339 340 class URLTestCase(unittest.TestCase):341 342 def testRoundtrip(self):343 for url in [344 "sip:j.doe@big.com",345 "sip:j.doe:secret@big.com;transport=tcp",346 "sip:j.doe@big.com?subject=project",347 "sip:example.com",348 ]:349 self.assertEqual(sip.parseURL(url).toString(), url)350 351 def testComplex(self):352 s = ("sip:user:pass@hosta:123;transport=udp;user=phone;method=foo;"353 "ttl=12;maddr=1.2.3.4;blah;goo=bar?a=b&c=d")354 url = sip.parseURL(s)355 for k, v in [("username", "user"), ("password", "pass"),356 ("host", "hosta"), ("port", 123),357 ("transport", "udp"), ("usertype", "phone"),358 ("method", "foo"), ("ttl", 12),359 ("maddr", "1.2.3.4"), ("other", ["blah", "goo=bar"]),360 ("headers", {"a": "b", "c": "d"})]:361 self.assertEqual(getattr(url, k), v)362 363 364 class ParseTestCase(unittest.TestCase):365 366 def testParseAddress(self):367 for address, name, urls, params in [368 ('"A. G. Bell" <sip:foo@example.com>',369 "A. G. Bell", "sip:foo@example.com", {}),370 ("Anon <sip:foo@example.com>", "Anon", "sip:foo@example.com", {}),371 ("sip:foo@example.com", "", "sip:foo@example.com", {}),372 ("<sip:foo@example.com>", "", "sip:foo@example.com", {}),373 ("foo <sip:foo@example.com>;tag=bar;foo=baz", "foo",374 "sip:foo@example.com", {"tag": "bar", "foo": "baz"}),375 ]:376 gname, gurl, gparams = sip.parseAddress(address)377 self.assertEqual(name, gname)378 self.assertEqual(gurl.toString(), urls)379 self.assertEqual(gparams, params)380 381 382 class DummyLocator:383 implements(sip.ILocator)384 def getAddress(self, logicalURL):385 return defer.succeed(sip.URL("server.com", port=5060))386 387 class FailingLocator:388 implements(sip.ILocator)389 def getAddress(self, logicalURL):390 return defer.fail(LookupError())391 392 393 class ProxyTestCase(unittest.TestCase):394 395 def setUp(self):396 self.proxy = sip.Proxy("127.0.0.1")397 self.proxy.locator = DummyLocator()398 self.sent = []399 self.proxy.sendMessage = lambda dest, msg: self.sent.append((dest, msg))400 401 def testRequestForward(self):402 r = sip.Request("INVITE", "sip:foo")403 r.addHeader("via", sip.Via("1.2.3.4").toString())404 r.addHeader("via", sip.Via("1.2.3.5").toString())405 r.addHeader("foo", "bar")406 r.addHeader("to", "<sip:joe@server.com>")407 r.addHeader("contact", "<sip:joe@1.2.3.5>")408 self.proxy.datagramReceived(r.toString(), ("1.2.3.4", 5060))409 self.assertEqual(len(self.sent), 1)410 dest, m = self.sent[0]411 self.assertEqual(dest.port, 5060)412 self.assertEqual(dest.host, "server.com")413 self.assertEqual(m.uri.toString(), "sip:foo")414 self.assertEqual(m.method, "INVITE")415 self.assertEqual(m.headers["via"],416 ["SIP/2.0/UDP 127.0.0.1:5060",417 "SIP/2.0/UDP 1.2.3.4:5060",418 "SIP/2.0/UDP 1.2.3.5:5060"])419 420 421 def testReceivedRequestForward(self):422 r = sip.Request("INVITE", "sip:foo")423 r.addHeader("via", sip.Via("1.2.3.4").toString())424 r.addHeader("foo", "bar")425 r.addHeader("to", "<sip:joe@server.com>")426 r.addHeader("contact", "<sip:joe@1.2.3.4>")427 self.proxy.datagramReceived(r.toString(), ("1.1.1.1", 5060))428 dest, m = self.sent[0]429 self.assertEqual(m.headers["via"],430 ["SIP/2.0/UDP 127.0.0.1:5060",431 "SIP/2.0/UDP 1.2.3.4:5060;received=1.1.1.1"])432 433 434 def testResponseWrongVia(self):435 # first via must match proxy's address436 r = sip.Response(200)437 r.addHeader("via", sip.Via("foo.com").toString())438 self.proxy.datagramReceived(r.toString(), ("1.1.1.1", 5060))439 self.assertEqual(len(self.sent), 0)440 441 def testResponseForward(self):442 r = sip.Response(200)443 r.addHeader("via", sip.Via("127.0.0.1").toString())444 r.addHeader("via", sip.Via("client.com", port=1234).toString())445 self.proxy.datagramReceived(r.toString(), ("1.1.1.1", 5060))446 self.assertEqual(len(self.sent), 1)447 dest, m = self.sent[0]448 self.assertEqual((dest.host, dest.port), ("client.com", 1234))449 self.assertEqual(m.code, 200)450 self.assertEqual(m.headers["via"], ["SIP/2.0/UDP client.com:1234"])451 452 def testReceivedResponseForward(self):453 r = sip.Response(200)454 r.addHeader("via", sip.Via("127.0.0.1").toString())455 r.addHeader(456 "via",457 sip.Via("10.0.0.1", received="client.com").toString())458 self.proxy.datagramReceived(r.toString(), ("1.1.1.1", 5060))459 self.assertEqual(len(self.sent), 1)460 dest, m = self.sent[0]461 self.assertEqual((dest.host, dest.port), ("client.com", 5060))462 463 def testResponseToUs(self):464 r = sip.Response(200)465 r.addHeader("via", sip.Via("127.0.0.1").toString())466 l = []467 self.proxy.gotResponse = lambda *a: l.append(a)468 self.proxy.datagramReceived(r.toString(), ("1.1.1.1", 5060))469 self.assertEqual(len(l), 1)470 m, addr = l[0]471 self.assertEqual(len(m.headers.get("via", [])), 0)472 self.assertEqual(m.code, 200)473 474 def testLoop(self):475 r = sip.Request("INVITE", "sip:foo")476 r.addHeader("via", sip.Via("1.2.3.4").toString())477 r.addHeader("via", sip.Via("127.0.0.1").toString())478 self.proxy.datagramReceived(r.toString(), ("client.com", 5060))479 self.assertEqual(self.sent, [])480 481 def testCantForwardRequest(self):482 r = sip.Request("INVITE", "sip:foo")483 r.addHeader("via", sip.Via("1.2.3.4").toString())484 r.addHeader("to", "<sip:joe@server.com>")485 self.proxy.locator = FailingLocator()486 self.proxy.datagramReceived(r.toString(), ("1.2.3.4", 5060))487 self.assertEqual(len(self.sent), 1)488 dest, m = self.sent[0]489 self.assertEqual((dest.host, dest.port), ("1.2.3.4", 5060))490 self.assertEqual(m.code, 404)491 self.assertEqual(m.headers["via"], ["SIP/2.0/UDP 1.2.3.4:5060"])492 493 def testCantForwardResponse(self):494 pass495 496 #testCantForwardResponse.skip = "not implemented yet"497 498 499 class RegistrationTestCase(unittest.TestCase):500 501 def setUp(self):502 self.proxy = sip.RegisterProxy(host="127.0.0.1")503 self.registry = sip.InMemoryRegistry("bell.example.com")504 self.proxy.registry = self.proxy.locator = self.registry505 self.sent = []506 self.proxy.sendMessage = lambda dest, msg: self.sent.append((dest, msg))507 setUp = utils.suppressWarnings(setUp,508 util.suppress(category=DeprecationWarning,509 message=r'twisted.protocols.sip.DigestAuthorizer was deprecated'))510 511 def tearDown(self):512 for d, uri in self.registry.users.values():513 d.cancel()514 del self.proxy515 516 def register(self):517 r = sip.Request("REGISTER", "sip:bell.example.com")518 r.addHeader("to", "sip:joe@bell.example.com")519 r.addHeader("contact", "sip:joe@client.com:1234")520 r.addHeader("via", sip.Via("client.com").toString())521 self.proxy.datagramReceived(r.toString(), ("client.com", 5060))522 523 def unregister(self):524 r = sip.Request("REGISTER", "sip:bell.example.com")525 r.addHeader("to", "sip:joe@bell.example.com")526 r.addHeader("contact", "*")527 r.addHeader("via", sip.Via("client.com").toString())528 r.addHeader("expires", "0")529 self.proxy.datagramReceived(r.toString(), ("client.com", 5060))530 531 def testRegister(self):532 self.register()533 dest, m = self.sent[0]534 self.assertEqual((dest.host, dest.port), ("client.com", 5060))535 self.assertEqual(m.code, 200)536 self.assertEqual(m.headers["via"], ["SIP/2.0/UDP client.com:5060"])537 self.assertEqual(m.headers["to"], ["sip:joe@bell.example.com"])538 self.assertEqual(m.headers["contact"], ["sip:joe@client.com:5060"])539 self.failUnless(540 int(m.headers["expires"][0]) in (3600, 3601, 3599, 3598))541 self.assertEqual(len(self.registry.users), 1)542 dc, uri = self.registry.users["joe"]543 self.assertEqual(uri.toString(), "sip:joe@client.com:5060")544 d = self.proxy.locator.getAddress(sip.URL(username="joe",545 host="bell.example.com"))546 d.addCallback(lambda desturl : (desturl.host, desturl.port))547 d.addCallback(self.assertEqual, ('client.com', 5060))548 return d549 550 def testUnregister(self):551 self.register()552 self.unregister()553 dest, m = self.sent[1]554 self.assertEqual((dest.host, dest.port), ("client.com", 5060))555 self.assertEqual(m.code, 200)556 self.assertEqual(m.headers["via"], ["SIP/2.0/UDP client.com:5060"])557 self.assertEqual(m.headers["to"], ["sip:joe@bell.example.com"])558 self.assertEqual(m.headers["contact"], ["sip:joe@client.com:5060"])559 self.assertEqual(m.headers["expires"], ["0"])560 self.assertEqual(self.registry.users, {})561 562 563 def addPortal(self):564 r = TestRealm()565 p = cred.portal.Portal(r)566 c = cred.checkers.InMemoryUsernamePasswordDatabaseDontUse()567 c.addUser('userXname@127.0.0.1', 'passXword')568 p.registerChecker(c)569 self.proxy.portal = p570 571 def testFailedAuthentication(self):572 self.addPortal()573 self.register()574 575 self.assertEqual(len(self.registry.users), 0)576 self.assertEqual(len(self.sent), 1)577 dest, m = self.sent[0]578 self.assertEqual(m.code, 401)579 580 581 def test_basicAuthentication(self):582 """583 Test that registration with basic authentication suceeds.584 """585 self.addPortal()586 self.proxy.authorizers = self.proxy.authorizers.copy()587 self.proxy.authorizers['basic'] = sip.BasicAuthorizer()588 warnings = self.flushWarnings(589 offendingFunctions=[self.test_basicAuthentication])590 self.assertEqual(len(warnings), 1)591 self.assertEqual(592 warnings[0]['message'],593 "twisted.protocols.sip.BasicAuthorizer was deprecated in "594 "Twisted 9.0.0")595 self.assertEqual(596 warnings[0]['category'],597 DeprecationWarning)598 r = sip.Request("REGISTER", "sip:bell.example.com")599 r.addHeader("to", "sip:joe@bell.example.com")600 r.addHeader("contact", "sip:joe@client.com:1234")601 r.addHeader("via", sip.Via("client.com").toString())602 r.addHeader("authorization",603 "Basic " + "userXname:passXword".encode('base64'))604 self.proxy.datagramReceived(r.toString(), ("client.com", 5060))605 606 self.assertEqual(len(self.registry.users), 1)607 self.assertEqual(len(self.sent), 1)608 dest, m = self.sent[0]609 self.assertEqual(m.code, 200)610 611 612 def test_failedBasicAuthentication(self):613 """614 Failed registration with basic authentication results in an615 unauthorized error response.616 """617 self.addPortal()618 self.proxy.authorizers = self.proxy.authorizers.copy()619 self.proxy.authorizers['basic'] = sip.BasicAuthorizer()620 warnings = self.flushWarnings(621 offendingFunctions=[self.test_failedBasicAuthentication])622 self.assertEqual(len(warnings), 1)623 self.assertEqual(624 warnings[0]['message'],625 "twisted.protocols.sip.BasicAuthorizer was deprecated in "626 "Twisted 9.0.0")627 self.assertEqual(628 warnings[0]['category'],629 DeprecationWarning)630 r = sip.Request("REGISTER", "sip:bell.example.com")631 r.addHeader("to", "sip:joe@bell.example.com")632 r.addHeader("contact", "sip:joe@client.com:1234")633 r.addHeader("via", sip.Via("client.com").toString())634 r.addHeader(635 "authorization", "Basic " + "userXname:password".encode('base64'))636 self.proxy.datagramReceived(r.toString(), ("client.com", 5060))637 638 self.assertEqual(len(self.registry.users), 0)639 self.assertEqual(len(self.sent), 1)640 dest, m = self.sent[0]641 self.assertEqual(m.code, 401)642 643 644 def testWrongDomainRegister(self):645 r = sip.Request("REGISTER", "sip:wrong.com")646 r.addHeader("to", "sip:joe@bell.example.com")647 r.addHeader("contact", "sip:joe@client.com:1234")648 r.addHeader("via", sip.Via("client.com").toString())649 self.proxy.datagramReceived(r.toString(), ("client.com", 5060))650 self.assertEqual(len(self.sent), 0)651 652 def testWrongToDomainRegister(self):653 r = sip.Request("REGISTER", "sip:bell.example.com")654 r.addHeader("to", "sip:joe@foo.com")655 r.addHeader("contact", "sip:joe@client.com:1234")656 r.addHeader("via", sip.Via("client.com").toString())657 self.proxy.datagramReceived(r.toString(), ("client.com", 5060))658 self.assertEqual(len(self.sent), 0)659 660 def testWrongDomainLookup(self):661 self.register()662 url = sip.URL(username="joe", host="foo.com")663 d = self.proxy.locator.getAddress(url)664 self.assertFailure(d, LookupError)665 return d666 667 def testNoContactLookup(self):668 self.register()669 url = sip.URL(username="jane", host="bell.example.com")670 d = self.proxy.locator.getAddress(url)671 self.assertFailure(d, LookupError)672 return d673 674 675 class Client(sip.Base):676 677 def __init__(self):678 sip.Base.__init__(self)679 self.received = []680 self.deferred = defer.Deferred()681 682 def handle_response(self, response, addr):683 self.received.append(response)684 self.deferred.callback(self.received)685 686 687 class LiveTest(unittest.TestCase):688 689 def setUp(self):690 self.proxy = sip.RegisterProxy(host="127.0.0.1")691 self.registry = sip.InMemoryRegistry("bell.example.com")692 self.proxy.registry = self.proxy.locator = self.registry693 self.serverPort = reactor.listenUDP(694 0, self.proxy, interface="127.0.0.1")695 self.client = Client()696 self.clientPort = reactor.listenUDP(697 0, self.client, interface="127.0.0.1")698 self.serverAddress = (self.serverPort.getHost().host,699 self.serverPort.getHost().port)700 setUp = utils.suppressWarnings(setUp,701 util.suppress(category=DeprecationWarning,702 message=r'twisted.protocols.sip.DigestAuthorizer was deprecated'))703 704 def tearDown(self):705 for d, uri in self.registry.users.values():706 d.cancel()707 d1 = defer.maybeDeferred(self.clientPort.stopListening)708 d2 = defer.maybeDeferred(self.serverPort.stopListening)709 return defer.gatherResults([d1, d2])710 711 def testRegister(self):712 p = self.clientPort.getHost().port713 r = sip.Request("REGISTER", "sip:bell.example.com")714 r.addHeader("to", "sip:joe@bell.example.com")715 r.addHeader("contact", "sip:joe@127.0.0.1:%d" % p)716 r.addHeader("via", sip.Via("127.0.0.1", port=p).toString())717 self.client.sendMessage(718 sip.URL(host="127.0.0.1", port=self.serverAddress[1]), r)719 d = self.client.deferred720 def check(received):721 self.assertEqual(len(received), 1)722 r = received[0]723 self.assertEqual(r.code, 200)724 d.addCallback(check)725 return d726 727 def test_amoralRPort(self):728 """729 rport is allowed without a value, apparently because server730 implementors might be too stupid to check the received port731 against 5060 and see if they're equal, and because client732 implementors might be too stupid to bind to port 5060, or set a733 value on the rport parameter they send if they bind to another734 port.735 """736 p = self.clientPort.getHost().port737 r = sip.Request("REGISTER", "sip:bell.example.com")738 r.addHeader("to", "sip:joe@bell.example.com")739 r.addHeader("contact", "sip:joe@127.0.0.1:%d" % p)740 r.addHeader("via", sip.Via("127.0.0.1", port=p, rport=True).toString())741 warnings = self.flushWarnings(742 offendingFunctions=[self.test_amoralRPort])743 self.assertEqual(len(warnings), 1)744 self.assertEqual(745 warnings[0]['message'],746 'rport=True is deprecated since Twisted 9.0.')747 self.assertEqual(748 warnings[0]['category'],749 DeprecationWarning)750 self.client.sendMessage(sip.URL(host="127.0.0.1",751 port=self.serverAddress[1]),752 r)753 d = self.client.deferred754 def check(received):755 self.assertEqual(len(received), 1)756 r = received[0]757 self.assertEqual(r.code, 200)758 d.addCallback(check)759 return d760 761 762 763 registerRequest = """764 REGISTER sip:intarweb.us SIP/2.0\r765 Via: SIP/2.0/UDP 192.168.1.100:50609\r766 From: <sip:exarkun@intarweb.us:50609>\r767 To: <sip:exarkun@intarweb.us:50609>\r768 Contact: "exarkun" <sip:exarkun@192.168.1.100:50609>\r769 Call-ID: 94E7E5DAF39111D791C6000393764646@intarweb.us\r770 CSeq: 9898 REGISTER\r771 Expires: 500\r772 User-Agent: X-Lite build 1061\r773 Content-Length: 0\r774 \r775 """776 777 challengeResponse = """\778 SIP/2.0 401 Unauthorized\r779 Via: SIP/2.0/UDP 192.168.1.100:50609;received=127.0.0.1;rport=5632\r780 To: <sip:exarkun@intarweb.us:50609>\r781 From: <sip:exarkun@intarweb.us:50609>\r782 Call-ID: 94E7E5DAF39111D791C6000393764646@intarweb.us\r783 CSeq: 9898 REGISTER\r784 WWW-Authenticate: Digest nonce="92956076410767313901322208775",opaque="1674186428",qop-options="auth",algorithm="MD5",realm="intarweb.us"\r785 \r786 """787 788 authRequest = """\789 REGISTER sip:intarweb.us SIP/2.0\r790 Via: SIP/2.0/UDP 192.168.1.100:50609\r791 From: <sip:exarkun@intarweb.us:50609>\r792 To: <sip:exarkun@intarweb.us:50609>\r793 Contact: "exarkun" <sip:exarkun@192.168.1.100:50609>\r794 Call-ID: 94E7E5DAF39111D791C6000393764646@intarweb.us\r795 CSeq: 9899 REGISTER\r796 Expires: 500\r797 Authorization: Digest username="exarkun",realm="intarweb.us",nonce="92956076410767313901322208775",response="4a47980eea31694f997369214292374b",uri="sip:intarweb.us",algorithm=MD5,opaque="1674186428"\r798 User-Agent: X-Lite build 1061\r799 Content-Length: 0\r800 \r801 """802 803 okResponse = """\804 SIP/2.0 200 OK\r805 Via: SIP/2.0/UDP 192.168.1.100:50609;received=127.0.0.1;rport=5632\r806 To: <sip:exarkun@intarweb.us:50609>\r807 From: <sip:exarkun@intarweb.us:50609>\r808 Call-ID: 94E7E5DAF39111D791C6000393764646@intarweb.us\r809 CSeq: 9899 REGISTER\r810 Contact: sip:exarkun@127.0.0.1:5632\r811 Expires: 3600\r812 Content-Length: 0\r813 \r814 """815 816 class FakeDigestAuthorizer(sip.DigestAuthorizer):817 def generateNonce(self):818 return '92956076410767313901322208775'819 def generateOpaque(self):820 return '1674186428'821 822 823 class FakeRegistry(sip.InMemoryRegistry):824 """Make sure expiration is always seen to be 3600.825 826 Otherwise slow reactors fail tests incorrectly.827 """828 829 def _cbReg(self, reg):830 if 3600 < reg.secondsToExpiry or reg.secondsToExpiry < 3598:831 raise RuntimeError(832 "bad seconds to expire: %s" % reg.secondsToExpiry)833 reg.secondsToExpiry = 3600834 return reg835 836 def getRegistrationInfo(self, uri):837 d = sip.InMemoryRegistry.getRegistrationInfo(self, uri)838 return d.addCallback(self._cbReg)839 840 def registerAddress(self, domainURL, logicalURL, physicalURL):841 d = sip.InMemoryRegistry.registerAddress(842 self, domainURL, logicalURL, physicalURL)843 return d.addCallback(self._cbReg)844 845 class AuthorizationTestCase(unittest.TestCase):846 def setUp(self):847 self.proxy = sip.RegisterProxy(host="intarweb.us")848 self.proxy.authorizers = self.proxy.authorizers.copy()849 self.proxy.authorizers['digest'] = FakeDigestAuthorizer()850 851 self.registry = FakeRegistry("intarweb.us")852 self.proxy.registry = self.proxy.locator = self.registry853 self.transport = proto_helpers.FakeDatagramTransport()854 self.proxy.transport = self.transport855 856 r = TestRealm()857 p = cred.portal.Portal(r)858 c = cred.checkers.InMemoryUsernamePasswordDatabaseDontUse()859 c.addUser('exarkun@intarweb.us', 'password')860 p.registerChecker(c)861 self.proxy.portal = p862 setUp = utils.suppressWarnings(setUp,863 util.suppress(category=DeprecationWarning,864 message=r'twisted.protocols.sip.DigestAuthorizer was deprecated'))865 866 def tearDown(self):867 for d, uri in self.registry.users.values():868 d.cancel()869 del self.proxy870 871 def testChallenge(self):872 self.proxy.datagramReceived(registerRequest, ("127.0.0.1", 5632))873 874 self.assertEqual(875 self.transport.written[-1],876 ((challengeResponse, ("127.0.0.1", 5632)))877 )878 self.transport.written = []879 880 self.proxy.datagramReceived(authRequest, ("127.0.0.1", 5632))881 882 self.assertEqual(883 self.transport.written[-1],884 ((okResponse, ("127.0.0.1", 5632)))885 )886 testChallenge.suppress = [887 util.suppress(888 category=DeprecationWarning,889 message=r'twisted.protocols.sip.DigestAuthorizer was deprecated'),890 util.suppress(891 category=DeprecationWarning,892 message=r'twisted.protocols.sip.DigestedCredentials was deprecated'),893 util.suppress(894 category=DeprecationWarning,895 message=r'twisted.protocols.sip.DigestCalcHA1 was deprecated'),896 util.suppress(897 category=DeprecationWarning,898 message=r'twisted.protocols.sip.DigestCalcResponse was deprecated')]899 900 901 902 class DeprecationTests(unittest.TestCase):903 """904 Tests for deprecation of obsolete components of L{twisted.protocols.sip}.905 """906 907 def test_deprecatedDigestCalcHA1(self):908 """909 L{sip.DigestCalcHA1} is deprecated.910 """911 self.callDeprecated(Version("Twisted", 9, 0, 0),912 sip.DigestCalcHA1, '', '', '', '', '', '')913 914 915 def test_deprecatedDigestCalcResponse(self):916 """917 L{sip.DigestCalcResponse} is deprecated.918 """919 self.callDeprecated(Version("Twisted", 9, 0, 0),920 sip.DigestCalcResponse, '', '', '', '', '', '', '',921 '')922 923 def test_deprecatedBasicAuthorizer(self):924 """925 L{sip.BasicAuthorizer} is deprecated.926 """927 self.callDeprecated(Version("Twisted", 9, 0, 0), sip.BasicAuthorizer)928 929 930 def test_deprecatedDigestAuthorizer(self):931 """932 L{sip.DigestAuthorizer} is deprecated.933 """934 self.callDeprecated(Version("Twisted", 9, 0, 0), sip.DigestAuthorizer)935 936 937 def test_deprecatedDigestedCredentials(self):938 """939 L{sip.DigestedCredentials} is deprecated.940 """941 self.callDeprecated(Version("Twisted", 9, 0, 0),942 sip.DigestedCredentials, '', {}, {}) -
twisted/protocols/test/test_sip.py
28 28 29 29 abcd""".replace("\n", "\r\n") 30 30 31 31 32 # request, no content-length 32 33 request2 = """INVITE sip:foo SIP/2.0 33 34 From: mo … … 35 36 36 37 1234""".replace("\n", "\r\n") 37 38 39 38 40 # request, with garbage after 39 41 request3 = """INVITE sip:foo SIP/2.0 40 42 From: mo … … 45 47 46 48 lalalal""".replace("\n", "\r\n") 47 49 50 48 51 # three requests 49 52 request4 = """INVITE sip:foo SIP/2.0 50 53 From: mo … … 63 66 64 67 1234""".replace("\n", "\r\n") 65 68 69 66 70 # response, no content 67 71 response1 = """SIP/2.0 200 OK 68 72 From: foo … … 71 75 72 76 """.replace("\n", "\r\n") 73 77 78 74 79 # short header version 75 80 request_short = """\ 76 81 INVITE sip:foo SIP/2.0 … … 80 85 81 86 abcd""".replace("\n", "\r\n") 82 87 88 83 89 request_natted = """\ 84 90 INVITE sip:foo SIP/2.0 85 91 Via: SIP/2.0/UDP 10.0.0.1:5060;rport 86 92 87 93 """.replace("\n", "\r\n") 88 94 95 96 89 97 class TestRealm: 90 98 def requestAvatar(self, avatarId, mind, *interfaces): 91 99 return sip.IContact, None, lambda: None 92 100 101 102 103 class TestHeaderCapitalize(unittest.TestCase): 104 105 def test_simpleHeaderCapitalized(self): 106 r = sip.Request("INVITE", "sip:foo") 107 r.addHeader("foo", "bar") 108 self.assertEqual( 109 r.toString(), 110 "INVITE sip:foo SIP/2.0\r\nFoo: bar\r\n\r\n") 111 112 113 def test_complexHeaderCapitalized(self): 114 r = sip.Request("INVITE", "sip:foo") 115 r.addHeader("foo-bar-baz", "quux") 116 self.assertEqual( 117 r.toString(), 118 "INVITE sip:foo SIP/2.0\r\nFoo-Bar-Baz: quux\r\n\r\n") 119 120 121 def test_specialCaseHeaderCapitalized(self): 122 r = sip.Request("INVITE", "sip:foo") 123 r.addHeader("www-authenticate", "foo") 124 self.assertEqual( 125 r.toString(), 126 "INVITE sip:foo SIP/2.0\r\nWWW-Authenticate: foo\r\n\r\n") 127 128 129 130 93 131 class MessageParsingTestCase(unittest.TestCase): 94 132 def setUp(self): 95 133 self.l = [] … … 337 375 338 376 339 377 340 class URLTestCase(unittest.TestCase): 378 class URITestCase(unittest.TestCase): 379 """ 380 Tests for L{sip.URI} and {sip.parseURL}. 381 """ 341 382 342 383 def testRoundtrip(self): 343 384 for url in [ 344 385 "sip:j.doe@big.com", 345 386 "sip:j.doe:secret@big.com;transport=tcp", 346 "sip:j.doe@big.com? subject=project",387 "sip:j.doe@big.com?Subject=project", 347 388 "sip:example.com", 348 389 ]: 349 390 self.assertEqual(sip.parseURL(url).toString(), url) 350 391 351 def testComplex(self): 392 393 def test_complex(self): 394 """ 395 Test parsing and printing a URI with one of everything. 396 """ 352 397 s = ("sip:user:pass@hosta:123;transport=udp;user=phone;method=foo;" 353 "ttl=12;maddr=1.2.3.4;blah;goo=bar? a=b&c=d")398 "ttl=12;maddr=1.2.3.4;blah;goo=bar?foo-baz=b&c=d") 354 399 url = sip.parseURL(s) 355 400 for k, v in [("username", "user"), ("password", "pass"), 356 401 ("host", "hosta"), ("port", 123), 357 402 ("transport", "udp"), ("usertype", "phone"), 358 403 ("method", "foo"), ("ttl", 12), 359 ("maddr", "1.2.3.4"), ("other", ["blah", "goo=bar"]), 360 ("headers", {"a": "b", "c": "d"})]: 404 ("maddr", "1.2.3.4"), ("other", {"blah": "", 405 "goo": "bar"}), 406 ("headers", {"foo-baz": "b", "c": "d"})]: 361 407 self.assertEqual(getattr(url, k), v) 408 self.assertEquals( 409 str(url), 410 'sip:user:pass@hosta:123;user=phone;transport=udp;' 411 'ttl=12;maddr=1.2.3.4;method=foo;blah;goo=bar?C=d&Foo-Baz=b') 362 412 363 413 414 def test_headers(self): 415 """ 416 SIP headers included in the URI are parsed correctly. 417 """ 418 419 uris = ["sip:foo@bar.com?header=value", 420 "sip:foo@bar.com:5060?header=value", 421 "sip:foo@bar.com;method=invite?header=value"] 422 for uri in uris: 423 self.assertEquals(sip.parseURL(uri).headers, 424 {"header": "value"}) 425 426 427 def test_invalidScheme(self): 428 """ 429 Attempts to parse unsupported URI schemes are rejected. 430 """ 431 self.assertRaises(sip.SIPError, sip.parseURL, "http://example.com/") 432 self.assertRaises(sip.SIPError, sip.parseURL, "sips:bob@example.com") 433 434 435 def test_hash(self): 436 """ 437 URIs are hashable. 438 """ 439 s1 = ("sip:user:pass@hosta:123;transport=udp;user=phone;method=foo;" 440 "ttl=12;maddr=1.2.3.4;blah;goo=bar?foo-baz=b&c=d") 441 s2 = ("sip:user:pass@hostb:123;transport=udp;user=phone;method=foo;" 442 "ttl=12;maddr=1.2.3.4;blah;goo=bar?foo-baz=b&c=d") 443 s3 = ("sip:user:pass@hosta:123;transport=udp;user=voip;method=foo;" 444 "ttl=12;maddr=1.2.3.4;blah;goo=bar?foo-baz=b&c=d") 445 s4 = ("sip:user:pass@hosta:123;transport=udp;user=phone;method=foo;" 446 "ttl=16;maddr=1.2.3.4;blah;goo=bar?foo-baz=b&c=d") 447 s5 = ("sip:user:pass@hosta:123;transport=udp;user=phone;method=foo;" 448 "ttl=12;maddr=1.2.3.5;blah;goo=bar?foo-baz=b&c=d") 449 s6 = ("sip:user:pass@hosta:123;transport=udp;user=phone;method=foo;" 450 "ttl=12;maddr=1.2.3.4;blah;foo=bar?foo-baz=b&c=d") 451 s7 = ("sip:user:pass@hosta:123;transport=udp;user=phone;method=foo;" 452 "ttl=12;maddr=1.2.3.4;blah;goo=bar?foo-baz=b&c=e") 453 d = { 454 sip.URI("example.com"): -2, 455 sip.URI("example.com", "bob"): -1, 456 } 457 for i, s in enumerate([s1, s2, s3, s4, s5, s6, s7]): 458 d[sip.parseURL(s)] = i 459 self.assertEqual(d[sip.URI("example.com")], -2) 460 self.assertEqual(d[sip.URI("example.com", "bob")], -1) 461 for i, s in enumerate([s1, s2, s3, s4, s5, s6, s7]): 462 self.assertEqual(d[sip.parseURL(s)], i) 463 464 465 def test_escaping(self): 466 """ 467 Percent-encoded characters are decoded and encoded correctly. 468 """ 469 uriString = ("sip:sips%3Auser%40example.com:x%20x@example.net" 470 ";m%65thod=foo%00baz?a%62c-foo=de%66") 471 uri = sip.parseURL(uriString) 472 self.assertEqual(uri.username, "sips:user@example.com") 473 self.assertEqual(uri.password, "x x") 474 self.assertEqual(uri.method, "foo\x00baz") 475 self.assertEqual(uri.headers, {"abc-foo": "def"}) 476 self.assertEqual(uri.toString(), 477 ("sip:sips%3Auser%40example.com:x%20x@example.net" 478 ";method=foo%00baz?Abc-Foo=def")) 479 480 481 def test_equivalence(self): 482 """ 483 All the URIs the RFC says are equivalent should compare equal. 484 """ 485 def assertEquivalentURIs(l, r): 486 self.assertEqual(sip.parseURL(l), sip.parseURL(r)) 487 488 assertEquivalentURIs("sip:%61lice@atlanta.com;transport=TCP", 489 "sip:alice@AtLanTa.CoM;Transport=tcp") 490 assertEquivalentURIs("sip:carol@chicago.com", 491 "sip:carol@chicago.com;newparam=5") 492 assertEquivalentURIs("sip:carol@chicago.com", 493 "sip:carol@chicago.com;security=on") 494 assertEquivalentURIs("sip:carol@chicago.com;security=on", 495 "sip:carol@chicago.com;newparam=5") 496 assertEquivalentURIs("sip:biloxi.com;transport=tcp;method=REGISTER?" 497 "to=sip:bob%40biloxi.com", 498 "sip:biloxi.com;method=REGISTER;transport=tcp?" 499 "to=sip:bob%40biloxi.com") 500 assertEquivalentURIs("sip:alice@atlanta.com?subject=project%20x" 501 "&priority=urgent", 502 "sip:alice@atlanta.com?priority=urgent&" 503 "subject=project%20x") 504 505 506 def test_nonequivalence(self): 507 """ 508 Ensure that certain difference between similar URIs prevent them from 509 comparing equal. 510 """ 511 def assertNonequivalent(l, r): 512 self.assertNotEqual(sip.parseURL(l), sip.parseURL(r)) 513 514 assertNonequivalent("sip:carol@chicago.com;security=off", 515 "sip:carol@chicago.com;security=on") 516 assertNonequivalent("SIP:ALICE@AtLanTa.CoM;Transport=udp", 517 "sip:alice@AtLanTa.CoM;Transport=UDP") 518 assertNonequivalent("sip:bob@biloxi.com", "sip:bob@biloxi.com:5060") 519 assertNonequivalent("sip:bob@biloxi.com", 520 "sip:bob@biloxi.com;transport=udp") 521 assertNonequivalent("sip:bob@biloxi.com", 522 "sip:bob@biloxi.com:5060;transport=udp") 523 assertNonequivalent("sip:bob@biloxi.com", 524 "sip:bob@biloxi.com:5060;transport=tcp") 525 assertNonequivalent("sip:carol@chicago.com", 526 "sip:carol@chicago.com?Subject=next%20meeting") 527 assertNonequivalent("sip:bob@localhost", "sip:bob@127.0.0.1") 528 529 530 def test_capitalization(self): 531 """ 532 Ensure that parameters and headers are correctly treated as case- 533 insensitive (i.e. lowercase) 534 """ 535 s1 = ("sip:user:pass@hosta:123;transport=udp;user=phone;method=foo;" 536 "ttl=12;maddr=1.2.3.4;blah;goo=bar?foo-baz=b&c=e") 537 s2 = ("sip:user:pass@hosta:123;transport=udp;user=phone;method=foo;" 538 "ttl=12;maddr=1.2.3.4;blah;Goo=bar?foo-baz=b&c=e") 539 s3 = ("SIP:user:pass@hosta:123;transport=udp;user=phone;method=foo;" 540 "ttl=12;maddr=1.2.3.4;blah;goo=bar?fOo-baz=b&c=e") 541 self.assertEqual(sip.parseURL(s1), sip.parseURL(s2)) 542 self.assertEqual(sip.parseURL(s2), sip.parseURL(s3)) 543 544 545 364 546 class ParseTestCase(unittest.TestCase): 365 547 366 548 def testParseAddress(self): 549 """ 550 Confirm that various names and addresses are parsed correctly. 551 """ 367 552 for address, name, urls, params in [ 368 553 ('"A. G. Bell" <sip:foo@example.com>', 369 554 "A. G. Bell", "sip:foo@example.com", {}), 370 555 ("Anon <sip:foo@example.com>", "Anon", "sip:foo@example.com", {}), 556 ('"A. G. Bell" <sip:foo@example.com>', 557 "A. G. Bell", "sip:foo@example.com", {}), 558 (' "A. G. Bell" <sip:foo@example.com>', 559 "A. G. Bell", "sip:foo@example.com", {}), 560 ('"Bell, A. G." <sip:bell@example.com>', 561 "Bell, A. G.", "sip:bell@example.com", {}), 562 ('" \\\\A. G. \\"Bell" <sip:foo@example.com>', 563 " \\A. G. \"Bell", "sip:foo@example.com", {}), 564 ('"\\x21A. G. Bell" <sip:foo@example.com>', 565 "x21A. G. Bell", "sip:foo@example.com", {}), 566 ("abcd1234-.!%*_+`'~ <sip:foo@example.com>", 567 "abcd1234-.!%*_+`'~", "sip:foo@example.com", {}), 568 ('"C\xc3\xa9sar" <sip:C%C3%A9sar@example.com>', 569 u'C\xe9sar', 'sip:C%C3%A9sar@example.com', {}), 570 ("Anon <sip:foo@example.com>", 571 "Anon", "sip:foo@example.com", {}), 371 572 ("sip:foo@example.com", "", "sip:foo@example.com", {}), 372 573 ("<sip:foo@example.com>", "", "sip:foo@example.com", {}), 373 ("foo <sip:foo@example.com>;tag=bar;foo=baz", "foo", 374 "sip:foo@example.com", {"tag": "bar", "foo": "baz"}), 574 ("foo <sip:foo@example.com>;tag=bar;foo=baz;boz", 575 "foo", "sip:foo@example.com", {"tag": "bar", "foo": "baz", 576 "boz": ""}), 577 ("sip:foo@example.com;tag=bar;foo=baz", 578 "", "sip:foo@example.com", {"tag": "bar", "foo": "baz"}), 579 # test the use of name.decode('utf8', 'replace') 580 ('"Invalid \xc3\x28" <sip:foo@example.com>', 581 u"Invalid \ufffd(", "sip:foo@example.com", {}), 375 582 ]: 376 583 gname, gurl, gparams = sip.parseAddress(address) 377 584 self.assertEqual(name, gname) … … 379 586 self.assertEqual(gparams, params) 380 587 381 588 589 382 590 class DummyLocator: 383 591 implements(sip.ILocator) 384 592 def getAddress(self, logicalURL): 385 593 return defer.succeed(sip.URL("server.com", port=5060)) 386 594 595 596 387 597 class FailingLocator: 388 598 implements(sip.ILocator) 389 599 def getAddress(self, logicalURL): 390 600 return defer.fail(LookupError()) 391 601 392 602 603 393 604 class ProxyTestCase(unittest.TestCase): 394 605 395 606 def setUp(self): -
twisted/protocols/sip.py
9 9 Documented in RFC 2543. 10 10 [Superceded by 3261] 11 11 12 13 12 This module contains a deprecated implementation of HTTP Digest authentication. 14 13 See L{twisted.cred.credentials} and L{twisted.cred._digest} for its new home. 14 15 Features required by RFC3261 missing from this module: 16 * SIPS support 17 15 18 """ 16 19 17 20 # system imports 18 21 import socket, time, sys, random, warnings 22 import urllib 23 import re 19 24 from zope.interface import implements, Interface 20 25 21 26 # twisted imports 22 27 from twisted.python import log, util 23 from twisted.python.deprecate import deprecated 28 from twisted.python.deprecate import deprecated, getDeprecationWarningString 24 29 from twisted.python.versions import Version 30 from twisted.python.compat import set 25 31 from twisted.python.hashlib import md5 26 32 from twisted.internet import protocol, defer, reactor 27 33 … … 113 119 606: "Not Acceptable", 114 120 } 115 121 122 123 116 124 specialCases = { 117 125 'cseq': 'CSeq', 118 126 'call-id': 'Call-ID', … … 120 128 } 121 129 122 130 131 132 def headerCapitalize(h): 133 """ 134 Return a version of the given header name that's capitalized in the 135 traditional fashion. 136 137 @param h: A SIP header name. 138 """ 139 h = h.lower() 140 if h in specialCases: 141 return specialCases[h] 142 if '-' in h: 143 return '-'.join([bit.capitalize() for bit in h.split('-')]) 144 return h.capitalize() 145 146 147 148 @deprecated(Version("Twisted", 8, 2, 0)) 123 149 def dashCapitalize(s): 124 150 ''' Capitalize a string, making sure to treat - as a word seperator ''' 125 151 return '-'.join([ x.capitalize() for x in s.split('-')]) 126 152 153 154 127 155 def unq(s): 128 156 if s[0] == s[-1] == '"': 129 157 return s[1:-1] 130 158 return s 131 159 160 161 132 162 def DigestCalcHA1( 133 163 pszAlg, 134 164 pszUserName, … … 205 235 See RFC 3261, sections 8.1.1.7, 18.2.2, and 20.42. 206 236 207 237 @ivar transport: Network protocol used for this leg. (Probably either "TCP" 208 or "UDP".)238 or "UDP".) 209 239 @type transport: C{str} 240 210 241 @ivar branch: Unique identifier for this request. 211 242 @type branch: C{str} 243 212 244 @ivar host: Hostname or IP for this leg. 213 245 @type host: C{str} 246 214 247 @ivar port: Port used for this leg. 215 248 @type port C{int}, or None. 249 216 250 @ivar rportRequested: Whether to request RFC 3581 client processing or not. 217 251 @type rportRequested: C{bool} 252 218 253 @ivar rportValue: Servers wishing to honor requests for RFC 3581 processing 219 should set this parameter to the source port the request was received220 from.254 should set this parameter to the source port the request was received 255 from. 221 256 @type rportValue: C{int}, or None. 222 257 223 258 @ivar ttl: Time-to-live for requests on multicast paths. 224 259 @type ttl: C{int}, or None. 260 225 261 @ivar maddr: The destination multicast address, if any. 226 262 @type maddr: C{str}, or None. 263 227 264 @ivar hidden: Obsolete in SIP 2.0. 228 265 @type hidden: C{bool} 266 229 267 @ivar otherParams: Any other parameters in the header. 230 268 @type otherParams: C{dict} 231 269 """ … … 237 275 Set parameters of this Via header. All arguments correspond to 238 276 attributes of the same name. 239 277 240 To maintain compatibility with old SIP 241 code, the 'rport' argument is used to determine the values of242 C{rportRequested} and C{rportValue}. If None, C{rportRequested} is set243 to True. (The deprecated method for doing this is to pass True.) If an244 integer, C{rportValue} is set to thegiven value.278 To maintain compatibility with old SIP code, the 'rport' argument is 279 used to determine the values of C{rportRequested} and C{rportValue}. If 280 None, C{rportRequested} is set to True. (The deprecated method for 281 doing this is to pass True.) If an integer, C{rportValue} is set to the 282 given value. 245 283 246 284 Any arguments not explicitly named here are collected into the 247 285 C{otherParams} dict. … … 369 407 return Via(**result) 370 408 371 409 372 class URL: 373 """A SIP URL.""" 410 class URI: 411 """ 412 A SIP URI, as defined in RFC 3261, section 19.1. 374 413 414 @ivar host: A hostname or IP address. 415 @type host: C{str} 416 417 @ivar username: The identifier of a particular resource at the host being 418 addressed. 419 @type username: C{str}, or None. 420 421 @ivar password: A password associated with the username. 422 @type password: C{str}, or None. 423 424 @ivar port: The port number where the request is to be sent. 425 @type port: C{int}, or None. 426 427 @ivar transport: The transport mechanism to be used for sending SIP 428 messages. 429 @type transport: C{str}, or None. 430 431 @ivar usertype: The 'user' URI parameter. May be 'phone' or 'dialstring' - 432 see RFC 3261, 19.1.6., and RFC 4967. 433 @type usertype: C{str}, or None. 434 435 @ivar method: The SIP method to use when forming a request from this 436 URI. (INVITE, REGISTER, etc.) 437 @type method: C{str}, or None. 438 439 @ivar ttl: Time-to-live for multicast requests. Used with C{maddr}. 440 @type ttl: C{int}, or None. 441 442 @ivar maddr: Server address to be contacted for this user. For use with 443 multicast requests. 444 @type maddr: C{str}, or None. 445 446 @ivar other: Any other URI parameters not specifically mentioned 447 here. 448 @type other: C{dict}, or None. 449 450 @ivar headers: Key-value pairs to be used as headers when forming 451 a request from this URI. 452 @type headers: C{dict}, or None. 453 """ 454 375 455 def __init__(self, host, username=None, password=None, port=None, 376 456 transport=None, usertype=None, method=None, 377 ttl=None, maddr=None, tag=None, other=None, headers=None): 457 ttl=None, maddr=None, other=None, headers=None): 458 """ 459 Set parameters of this URI. 460 """ 378 461 self.username = username 379 462 self.host = host 380 463 self.password = password … … 382 465 self.transport = transport 383 466 self.usertype = usertype 384 467 self.method = method 385 self.tag = tag386 468 self.ttl = ttl 387 469 self.maddr = maddr 388 470 if other == None: 389 self.other = []471 self.other = {} 390 472 else: 391 473 self.other = other 392 474 if headers == None: … … 394 476 else: 395 477 self.headers = headers 396 478 479 397 480 def toString(self): 481 """ 482 Format this object's contents as a SIP URI. 483 """ 398 484 l = []; w = l.append 399 485 w("sip:") 400 486 if self.username != None: 401 w( self.username)487 w(urllib.quote(self.username)) 402 488 if self.password != None: 403 w(":%s" % self.password)489 w(":%s" % (urllib.quote(self.password))) 404 490 w("@") 405 491 w(self.host) 406 492 if self.port != None: 407 493 w(":%d" % self.port) 408 494 if self.usertype != None: 409 495 w(";user=%s" % self.usertype) 410 for n in ("transport", "ttl", "maddr", "method" , "tag"):496 for n in ("transport", "ttl", "maddr", "method"): 411 497 v = getattr(self, n) 412 498 if v != None: 413 w(";%s=%s" % (n, v)) 414 for v in self.other: 415 w(";%s" % v) 499 w(";%s=%s" % (urllib.quote(n), urllib.quote(str(v)))) 500 for k, v in self.other.iteritems(): 501 if v: 502 w(";%s=%s" % (urllib.quote(k), urllib.quote(v))) 503 else: 504 w(";%s" % k) 416 505 if self.headers: 417 506 w("?") 418 w("&".join([("%s=%s" % (specialCases.get(h) or dashCapitalize(h), v)) for (h, v) in self.headers.items()])) 507 w("&".join([("%s=%s" % (headerCapitalize(h), urllib.quote(v))) 508 for (h, v) in self.headers.items()])) 419 509 return "".join(l) 420 510 511 421 512 def __str__(self): 513 """ 514 Format this object's contents as a SIP URI. 515 """ 422 516 return self.toString() 423 517 518 424 519 def __repr__(self): 425 return '<URL %s:%s@%s:%r/%s>' % (self.username, self.password, self.host, self.port, self.transport) 520 """ 521 Provide a debugging representation of this object. 522 """ 523 return '<sip.URI %s>' % self.toString() 426 524 427 525 526 def __eq__(self, other): 527 """ 528 Comparison for URI equivalence, as described in RFC 3261, section 529 19.1.4. 530 """ 531 if not isinstance(other, URI): 532 return False 533 534 if self.username != other.username or self.password != other.password: 535 return False 536 537 if self.host.lower() != other.host.lower() or self.port != other.port: 538 return False 539 540 selfParams = set([p.lower() for p in self.other]) 541 otherParams = set([p.lower() for p in other.other]) 542 comparisonAttributes = ['usertype', 'method', 'ttl', 'maddr', 543 'transport'] 544 comparisonParams = [(name, self.other[name], other.other[name]) 545 for name in selfParams.intersection(otherParams)] 546 comparisonParams.extend([(name, getattr(self, name), 547 getattr(other, name)) 548 for name in comparisonAttributes]) 549 for param, left, right in comparisonParams: 550 if left == right: 551 continue 552 # Check for case-insensitively equal strings 553 elif (hasattr(left, 'lower') and hasattr(right, 'lower') and 554 left.lower() == right.lower()): 555 continue 556 else: 557 return False 558 559 if self.headers != other.headers: 560 return False 561 562 return True 563 564 565 def __ne__(self, other): 566 """ 567 Deal with Python's confusing equality model. 568 """ 569 return not self.__eq__(other) 570 571 572 def __hash__(self): 573 """ 574 Provide a hash value for this URI based on the most common contents. 575 """ 576 # __eq__ compares on: 577 # username 578 # password 579 # host (case-insensitive) 580 # port 581 # other params (case-insensitive) 582 # usertype 583 # method 584 # ttl 585 # maddr 586 # transport 587 # headers (case-insensitive) 588 cmp_params = [(x[0].lower(), x[1].lower()) for x in self.other.items()] 589 for attr in ['usertype', 'method', 'ttl', 'maddr', 'transport']: 590 value = getattr(self, attr, None) 591 if hasattr(value, 'lower'): 592 value = value.lower() 593 cmp_params.append((attr, value)) 594 # headers are already lowercased 595 cmp_params.extend(self.headers.items()) 596 fields = (self.username, self.password, self.host.lower(), self.port, 597 tuple(cmp_params)) 598 return hash(fields) 599 600 601 602 #"URL" is the deprecated spelling of this class. 603 URL = URI 604 605 606 428 607 def parseURL(url, host=None, port=None): 429 """Return string into URL object. 608 """ 609 Parse string into URI object. 610 URIs are of of form 'sip:user@example.com'. 430 611 431 URIs are of of form 'sip:user@example.com'. 612 @param url: A string representation of a SIP URI. 613 614 @param host: The host to use for the URI object returned, overriding the 615 value specified in the input. 616 617 @param port: The port to use for the URI object returned, overriding the 618 value specified in the input. 432 619 """ 433 620 d = {} 434 if not url.startswith("sip:"): 435 raise ValueError("unsupported scheme: " + url[:4]) 621 622 def parseHeaders(headers): 623 d["headers"] = h = {} 624 for header in headers.split("&"): 625 k, v = header.split("=") 626 h[urllib.unquote(k.lower())] = urllib.unquote(v) 627 628 if not url[:4].lower() == "sip:": 629 raise SIPError(416, "Unsupported URI scheme: " + url[:4]) 436 630 parts = url[4:].split(";") 437 631 userdomain, params = parts[0], parts[1:] 438 632 udparts = userdomain.split("@", 1) … … 440 634 userpass, hostport = udparts 441 635 upparts = userpass.split(":", 1) 442 636 if len(upparts) == 1: 443 d["username"] = u pparts[0]637 d["username"] = urllib.unquote(upparts[0]) 444 638 else: 445 d["username"] = u pparts[0]446 d["password"] = u pparts[1]639 d["username"] = urllib.unquote(upparts[0]) 640 d["password"] = urllib.unquote(upparts[1]) 447 641 else: 448 642 hostport = udparts[0] 449 643 hpparts = hostport.split(":", 1) 644 450 645 if len(hpparts) == 1: 451 d["host"] = hpparts[0] 646 if "?" in hpparts[0]: 647 _host, headers = hpparts[0].split("?", 1) 648 parseHeaders(headers) 649 else: 650 _host = hpparts[0] 651 d["host"] = _host 452 652 else: 653 if "?" in hpparts[1]: 654 _port, headers = hpparts[1].split("?", 1) 655 parseHeaders(headers) 656 else: 657 _port = hpparts[1] 453 658 d["host"] = hpparts[0] 454 d["port"] = int( hpparts[1])659 d["port"] = int(_port) 455 660 if host != None: 456 661 d["host"] = host 457 662 if port != None: 458 663 d["port"] = port 459 664 for p in params: 460 665 if p == params[-1] and "?" in p: 461 d["headers"] = h = {}462 666 p, headers = p.split("?", 1) 463 for header in headers.split("&"): 464 k, v = header.split("=") 465 h[k] = v 667 parseHeaders(headers) 466 668 nv = p.split("=", 1) 467 669 if len(nv) == 1: 468 d.setdefault("other", []).append(p)670 d.setdefault("other", {})[urllib.unquote(p.lower())] = '' 469 671 continue 470 name, value = nv672 name, value = [urllib.unquote(x.lower()) for x in nv] 471 673 if name == "user": 472 674 d["usertype"] = value 473 elif name in ("transport", "ttl", "maddr", "method" , "tag"):675 elif name in ("transport", "ttl", "maddr", "method"): 474 676 if name == "ttl": 475 677 value = int(value) 476 678 d[name] = value 477 679 else: 478 d.setdefault("other", []).append(p)479 return UR L(**d)680 d.setdefault("other", {})[name] = value 681 return URI(**d) 480 682 481 683 684 482 685 def cleanRequestURL(url): 483 686 """Clean a URL from a Request line.""" 484 687 url.transport = None … … 490 693 def parseAddress(address, host=None, port=None, clean=0): 491 694 """Return (name, uri, params) for From/To/Contact header. 492 695 696 @param address: A string representation of a SIP address (A 'name-addr', as 697 defined in RFC 3261, section 25.1, plus parameters.) 698 699 @param host: The host to use for the URI object returned, overriding the 700 value specified in the input. 701 702 @param port: The port to use for the URI object returned, overriding the 703 value specified in the input. 704 493 705 @param clean: remove unnecessary info, usually for From and To headers. 706 707 Although many headers such as From can contain any valid URI, even those 708 with schemes other than 'sip', this function raises SIPError if the scheme 709 is not 'sip' because the upper layers do not support it. 494 710 """ 711 def splitParams(paramstring): 712 params = {} 713 paramstring = paramstring.strip() 714 if paramstring: 715 for l in paramstring.split(";"): 716 if not l: 717 continue 718 x = l.split("=") 719 if len(x) > 1: 720 params[x[0]] = x[1] 721 else: 722 params [x[0]] = '' 723 return params 495 724 address = address.strip() 496 725 # simple 'sip:foo' case 497 if address.startswith("sip:"): 498 return "", parseURL(address, host=host, port=port), {} 726 if not '<' in address: 727 i = address.rfind(";tag=") 728 if i > -1: 729 params = splitParams(address[i:]) 730 address = address[:i] 731 else: 732 params = {} 733 return u"", parseURL(address, host=host, port=port), params 499 734 params = {} 500 735 name, url = address.split("<", 1) 501 736 name = name.strip() … … 503 738 name = name[1:] 504 739 if name.endswith('"'): 505 740 name = name[:-1] 741 name = re.sub(r'\\(.)', r'\1', name) 506 742 url, paramstring = url.split(">", 1) 507 743 url = parseURL(url, host=host, port=port) 508 paramstring = paramstring.strip() 509 if paramstring: 510 for l in paramstring.split(";"): 511 if not l: 512 continue 513 k, v = l.split("=") 514 params[k] = v 744 params = splitParams(paramstring) 515 745 if clean: 516 746 # rfc 2543 6.21 517 747 url.ttl = None 518 748 url.headers = {} 519 749 url.transport = None 520 750 url.maddr = None 521 return name , url, params751 return name.decode('utf8', 'replace'), url, params 522 752 523 753 754 524 755 class SIPError(Exception): 525 756 def __init__(self, code, phrase=None): 526 757 if phrase is None: … … 530 761 self.phrase = phrase 531 762 532 763 764 533 765 class RegistrationError(SIPError): 534 766 """Registration was not possible.""" 535 767 536 768 769 537 770 class Message: 538 771 """A SIP message.""" 539 772 … … 563 796 s = "%s\r\n" % self._getHeaderLine() 564 797 for n, vs in self.headers.items(): 565 798 for v in vs: 566 s += "%s: %s\r\n" % ( specialCases.get(n) or dashCapitalize(n), v)799 s += "%s: %s\r\n" % (headerCapitalize(n), v) 567 800 s += "\r\n" 568 801 s += self.body 569 802 return s … … 612 845 class MessagesParser(basic.LineReceiver): 613 846 """A SIP messages parser. 614 847 615 Expects dataReceived, dataDone repeatedly, 616 in that order.Shouldn't be connected to actual transport.848 Expects dataReceived, dataDone repeatedly, in that order. 849 Shouldn't be connected to actual transport. 617 850 """ 618 851 619 852 version = "SIP/2.0" … … 835 1068 raise NotImplementedError 836 1069 837 1070 1071 838 1072 class IContact(Interface): 839 1073 """A user of a registrar or proxy""" 840 1074 841 1075 1076 842 1077 class Registration: 843 1078 def __init__(self, secondsToExpiry, contactURL): 844 1079 self.secondsToExpiry = secondsToExpiry 845 1080 self.contactURL = contactURL 846 1081 1082 1083 847 1084 class IRegistry(Interface): 848 1085 """Allows registration of logical->physical URL mapping.""" 849 1086 … … 853 1090 @return: Deferred of C{Registration} or failure with RegistrationError. 854 1091 """ 855 1092 1093 856 1094 def unregisterAddress(domainURL, logicalURL, physicalURL): 857 1095 """Unregister the physical address of a logical URL. 858 1096 859 1097 @return: Deferred of C{Registration} or failure with RegistrationError. 860 1098 """ 861 1099 1100 862 1101 def getRegistrationInfo(logicalURL): 863 1102 """Get registration info for logical URL. 864 1103 … … 866 1105 """ 867 1106 868 1107 1108 869 1109 class ILocator(Interface): 870 1110 """Allow looking up physical address for logical URL.""" 871 1111 … … 877 1117 """ 878 1118 879 1119 1120 880 1121 class Proxy(Base): 881 1122 """SIP proxy.""" 882 1123 … … 894 1135 self.port = port 895 1136 Base.__init__(self) 896 1137 1138 897 1139 def getVia(self): 898 1140 """Return value of Via header for this proxy.""" 899 1141 return Via(host=self.host, port=self.port) 900 1142 1143 901 1144 def handle_request(self, message, addr): 902 1145 # send immediate 100/trying message before processing 903 1146 #self.deliverResponse(self.responseFromRequest(100, message)) … … 917 1160 self.deliverResponse(self.responseFromRequest(e.code, message)) 918 1161 ) 919 1162 1163 920 1164 def handle_request_default(self, message, (srcHost, srcPort)): 921 1165 """Default request handler. 922 1166 … … 944 1188 d.addCallback(self.sendMessage, message) 945 1189 d.addErrback(self._cantForwardRequest, message) 946 1190 1191 947 1192 def _cantForwardRequest(self, error, message): 948 1193 error.trap(LookupError) 949 1194 del message.headers["via"][0] # this'll be us 950 1195 self.deliverResponse(self.responseFromRequest(404, message)) 951 1196 1197 952 1198 def deliverResponse(self, responseMessage): 953 1199 """Deliver response. 954 1200 … … 961 1207 destAddr = URL(host=host, port=port) 962 1208 self.sendMessage(destAddr, responseMessage) 963 1209 1210 964 1211 def responseFromRequest(self, code, request): 965 1212 """Create a response to a request message.""" 966 1213 response = Response(code) … … 968 1215 response.headers[name] = request.headers.get(name, [])[:] 969 1216 return response 970 1217 1218 971 1219 def handle_response(self, message, addr): 972 1220 """Default response handler.""" 973 1221 v = parseViaHeader(message.headers["via"][0]) … … 984 1232 return 985 1233 self.deliverResponse(message) 986 1234 1235 987 1236 def gotResponse(self, message, addr): 988 1237 """Called with responses that are addressed at this server.""" 989 1238 pass 990 1239 1240 1241 991 1242 class IAuthorizer(Interface): 992 1243 def getChallenge(peer): 993 1244 """Generate a challenge the client may respond to. … … 999 1250 @return: The challenge string 1000 1251 """ 1001 1252 1253 1002 1254 def decode(response): 1003 1255 """Create a credentials object from the given response. 1004 1256 1005 1257 @type response: C{str} 1006 1258 """ 1007 1259 1260 1261 1008 1262 class BasicAuthorizer: 1009 1263 """Authorizer for insecure Basic (base64-encoded plaintext) authentication. 1010 1264 … … 1027 1281 def getChallenge(self, peer): 1028 1282 return None 1029 1283 1284 1030 1285 def decode(self, response): 1031 1286 # At least one SIP client improperly pads its Base64 encoded messages 1032 1287 for i in range(3): … … 1050 1305 """Yet Another Simple Digest-MD5 authentication scheme""" 1051 1306 1052 1307 def __init__(self, username, fields, challenges): 1053 warnings.warn( 1054 "twisted.protocols.sip.DigestedCredentials was deprecated " 1055 "in Twisted 9.0.0", 1056 category=DeprecationWarning, 1057 stacklevel=2) 1308 msg = getDeprecationWarningString(DigestedCredentials, 1309 Version("Twisted", 9, 0, 0)) 1310 warnings.warn(msg, category=DeprecationWarning, stacklevel=2) 1058 1311 self.username = username 1059 1312 self.fields = fields 1060 1313 self.challenges = challenges 1061 1314 1315 1062 1316 def checkPassword(self, password): 1063 1317 method = 'REGISTER' 1064 1318 response = self.fields.get('response') … … 1091 1345 implements(IAuthorizer) 1092 1346 1093 1347 def __init__(self): 1094 warnings.warn( 1095 "twisted.protocols.sip.DigestAuthorizer was deprecated " 1096 "in Twisted 9.0.0", 1097 category=DeprecationWarning, 1098 stacklevel=2) 1099 1348 msg = getDeprecationWarningString(DigestAuthorizer, 1349 Version("Twisted", 9, 0, 0)) 1350 warnings.warn(msg, category=DeprecationWarning, stacklevel=2) 1100 1351 self.outstanding = {} 1101 1352 1102 1353 1103 1104 1354 def generateNonce(self): 1105 1355 c = tuple([random.randrange(sys.maxint) for _ in range(3)]) 1106 1356 c = '%d%d%d' % c 1107 1357 return c 1108 1358 1359 1109 1360 def generateOpaque(self): 1110 1361 return str(random.randrange(sys.maxint)) 1111 1362 1363 1112 1364 def getChallenge(self, peer): 1113 1365 c = self.generateNonce() 1114 1366 o = self.generateOpaque() … … 1120 1372 'algorithm="MD5"', 1121 1373 )) 1122 1374 1375 1123 1376 def decode(self, response): 1124 1377 response = ' '.join(response.splitlines()) 1125 1378 parts = response.split(',') … … 1134 1387 raise SIPError(400) 1135 1388 1136 1389 1390 1137 1391 class RegisterProxy(Proxy): 1138 1392 """A proxy that allows registration for a specific domain. 1139 1393 … … 1152 1406 if "digest" not in self.authorizers: 1153 1407 self.authorizers["digest"] = DigestAuthorizer() 1154 1408 1409 1155 1410 def handle_ACK_request(self, message, (host, port)): 1156 1411 # XXX 1157 1412 # ACKs are a client's way of indicating they got the last message … … 1160 1415 # if no ACK is received. 1161 1416 pass 1162 1417 1418 1163 1419 def handle_REGISTER_request(self, message, (host, port)): 1164 1420 """Handle a registration request. 1165 1421 … … 1175 1431 else: 1176 1432 return self.login(message, host, port) 1177 1433 1434 1178 1435 def unauthorized(self, message, host, port): 1179 1436 m = self.responseFromRequest(401, message) 1180 1437 for (scheme, auth) in self.authorizers.iteritems(): … … 1208 1465 else: 1209 1466 self.deliverResponse(self.responseFromRequest(501, message)) 1210 1467 1468 1211 1469 def _cbLogin(self, (i, a, l), message, host, port): 1212 1470 # It's stateless, matey. What a joke. 1213 1471 self.register(message, host, port) 1214 1472 1473 1215 1474 def _ebLogin(self, failure, message, host, port): 1216 1475 failure.trap(cred.error.UnauthorizedLogin) 1217 1476 self.unauthorized(message, host, port) 1218 1477 1478 1219 1479 def register(self, message, host, port): 1220 1480 """Allow all users to register""" 1221 1481 name, toURL, params = parseAddress(message.headers["to"][0], clean=1) … … 1238 1498 errbackArgs=(message,) 1239 1499 ) 1240 1500 1501 1241 1502 def _cbRegister(self, registration, message): 1242 1503 response = self.responseFromRequest(200, message) 1243 1504 if registration.contactURL != None: … … 1246 1507 response.addHeader("content-length", "0") 1247 1508 self.deliverResponse(response) 1248 1509 1510 1249 1511 def _ebRegister(self, error, message): 1250 1512 error.trap(RegistrationError, LookupError) 1251 1513 # XXX return error message, and alter tests to deal with 1252 1514 # this, currently tests assume no message sent on failure 1253 1515 1516 1254 1517 def unregister(self, message, toURL, contact): 1255 1518 try: 1256 1519 expires = int(message.headers["expires"][0]) … … 1267 1530 ).addErrback(self._ebUnregister, message 1268 1531 ) 1269 1532 1533 1270 1534 def _cbUnregister(self, registration, message): 1271 1535 msg = self.responseFromRequest(200, message) 1272 1536 msg.headers.setdefault('contact', []).append(registration.contactURL.toString()) 1273 1537 msg.addHeader("expires", "0") 1274 1538 self.deliverResponse(msg) 1275 1539 1540 1276 1541 def _ebUnregister(self, registration, message): 1277 1542 pass 1278 1543 1279 1544 1545 1280 1546 class InMemoryRegistry: 1281 1547 """A simplistic registry for a specific domain.""" 1282 1548 … … 1286 1552 self.domain = domain # the domain we handle registration for 1287 1553 self.users = {} # map username to (IDelayedCall for expiry, address URI) 1288 1554 1555 1289 1556 def getAddress(self, userURI): 1290 1557 if userURI.host != self.domain: 1291 1558 return defer.fail(LookupError("unknown domain")) … … 1295 1562 else: 1296 1563 return defer.fail(LookupError("no such user")) 1297 1564 1565 1298 1566 def getRegistrationInfo(self, userURI): 1299 1567 if userURI.host != self.domain: 1300 1568 return defer.fail(LookupError("unknown domain")) … … 1304 1572 else: 1305 1573 return defer.fail(LookupError("no such user")) 1306 1574 1575 1307 1576 def _expireRegistration(self, username): 1308 1577 try: 1309 1578 dc, url = self.users[username] … … 1314 1583 del self.users[username] 1315 1584 return defer.succeed(Registration(0, url)) 1316 1585 1586 1317 1587 def registerAddress(self, domainURL, logicalURL, physicalURL): 1318 1588 if domainURL.host != self.domain: 1319 1589 log.msg("Registration for domain we don't handle.") … … 1330 1600 self.users[logicalURL.username] = (dc, physicalURL) 1331 1601 return defer.succeed(Registration(int(dc.getTime() - time.time()), physicalURL)) 1332 1602 1603 1333 1604 def unregisterAddress(self, domainURL, logicalURL, physicalURL): 1334 1605 return self._expireRegistration(logicalURL.username)