Ticket #3582: twisted3582.patch

File twisted3582.patch, 72.4 KB (added by Rami C, 9 years ago)

Updated patch against trunk with several fixes

  • 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, util
    9 from twisted.protocols import sip
    10 from twisted.internet import defer, reactor, utils
    11 from twisted.python.versions import Version
    12 
    13 from twisted.test import proto_helpers
    14 
    15 from twisted import cred
    16 import twisted.cred.portal
    17 import twisted.cred.checkers
    18 
    19 from zope.interface import implements
    20 
    21 
    22 # request, prefixed by random CRLFs
    23 request1 = "\n\r\n\n\r" + """\
    24 INVITE sip:foo SIP/2.0
    25 From: mo
    26 To: joe
    27 Content-Length: 4
    28 
    29 abcd""".replace("\n", "\r\n")
    30 
    31 # request, no content-length
    32 request2 = """INVITE sip:foo SIP/2.0
    33 From: mo
    34 To: joe
    35 
    36 1234""".replace("\n", "\r\n")
    37 
    38 # request, with garbage after
    39 request3 = """INVITE sip:foo SIP/2.0
    40 From: mo
    41 To: joe
    42 Content-Length: 4
    43 
    44 1234
    45 
    46 lalalal""".replace("\n", "\r\n")
    47 
    48 # three requests
    49 request4 = """INVITE sip:foo SIP/2.0
    50 From: mo
    51 To: joe
    52 Content-Length: 0
    53 
    54 INVITE sip:loop SIP/2.0
    55 From: foo
    56 To: bar
    57 Content-Length: 4
    58 
    59 abcdINVITE sip:loop SIP/2.0
    60 From: foo
    61 To: bar
    62 Content-Length: 4
    63 
    64 1234""".replace("\n", "\r\n")
    65 
    66 # response, no content
    67 response1 = """SIP/2.0 200 OK
    68 From:  foo
    69 To:bar
    70 Content-Length: 0
    71 
    72 """.replace("\n", "\r\n")
    73 
    74 # short header version
    75 request_short = """\
    76 INVITE sip:foo SIP/2.0
    77 f: mo
    78 t: joe
    79 l: 4
    80 
    81 abcd""".replace("\n", "\r\n")
    82 
    83 request_natted = """\
    84 INVITE sip:foo SIP/2.0
    85 Via: SIP/2.0/UDP 10.0.0.1:5060;rport
    86 
    87 """.replace("\n", "\r\n")
    88 
    89 class TestRealm:
    90     def requestAvatar(self, avatarId, mind, *interfaces):
    91         return sip.IContact, None, lambda: None
    92 
    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.l
    112         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.l
    121         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.l
    134         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.l
    143         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.l
    160         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.l
    169         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 header
    276         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 number
    309         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 address
    436         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         pass
    495 
    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.registry
    505         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.proxy
    515 
    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 d
    549 
    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 = p
    570 
    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 an
    615         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 d
    666 
    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 d
    673 
    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.registry
    693         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().port
    713         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.deferred
    720         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 d
    726 
    727     def test_amoralRPort(self):
    728         """
    729         rport is allowed without a value, apparently because server
    730         implementors might be too stupid to check the received port
    731         against 5060 and see if they're equal, and because client
    732         implementors might be too stupid to bind to port 5060, or set a
    733         value on the rport parameter they send if they bind to another
    734         port.
    735         """
    736         p = self.clientPort.getHost().port
    737         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.deferred
    754         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 d
    760 
    761 
    762 
    763 registerRequest = """
    764 REGISTER sip:intarweb.us SIP/2.0\r
    765 Via: SIP/2.0/UDP 192.168.1.100:50609\r
    766 From: <sip:exarkun@intarweb.us:50609>\r
    767 To: <sip:exarkun@intarweb.us:50609>\r
    768 Contact: "exarkun" <sip:exarkun@192.168.1.100:50609>\r
    769 Call-ID: 94E7E5DAF39111D791C6000393764646@intarweb.us\r
    770 CSeq: 9898 REGISTER\r
    771 Expires: 500\r
    772 User-Agent: X-Lite build 1061\r
    773 Content-Length: 0\r
    774 \r
    775 """
    776 
    777 challengeResponse = """\
    778 SIP/2.0 401 Unauthorized\r
    779 Via: SIP/2.0/UDP 192.168.1.100:50609;received=127.0.0.1;rport=5632\r
    780 To: <sip:exarkun@intarweb.us:50609>\r
    781 From: <sip:exarkun@intarweb.us:50609>\r
    782 Call-ID: 94E7E5DAF39111D791C6000393764646@intarweb.us\r
    783 CSeq: 9898 REGISTER\r
    784 WWW-Authenticate: Digest nonce="92956076410767313901322208775",opaque="1674186428",qop-options="auth",algorithm="MD5",realm="intarweb.us"\r
    785 \r
    786 """
    787 
    788 authRequest = """\
    789 REGISTER sip:intarweb.us SIP/2.0\r
    790 Via: SIP/2.0/UDP 192.168.1.100:50609\r
    791 From: <sip:exarkun@intarweb.us:50609>\r
    792 To: <sip:exarkun@intarweb.us:50609>\r
    793 Contact: "exarkun" <sip:exarkun@192.168.1.100:50609>\r
    794 Call-ID: 94E7E5DAF39111D791C6000393764646@intarweb.us\r
    795 CSeq: 9899 REGISTER\r
    796 Expires: 500\r
    797 Authorization: Digest username="exarkun",realm="intarweb.us",nonce="92956076410767313901322208775",response="4a47980eea31694f997369214292374b",uri="sip:intarweb.us",algorithm=MD5,opaque="1674186428"\r
    798 User-Agent: X-Lite build 1061\r
    799 Content-Length: 0\r
    800 \r
    801 """
    802 
    803 okResponse = """\
    804 SIP/2.0 200 OK\r
    805 Via: SIP/2.0/UDP 192.168.1.100:50609;received=127.0.0.1;rport=5632\r
    806 To: <sip:exarkun@intarweb.us:50609>\r
    807 From: <sip:exarkun@intarweb.us:50609>\r
    808 Call-ID: 94E7E5DAF39111D791C6000393764646@intarweb.us\r
    809 CSeq: 9899 REGISTER\r
    810 Contact: sip:exarkun@127.0.0.1:5632\r
    811 Expires: 3600\r
    812 Content-Length: 0\r
    813 \r
    814 """
    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 = 3600
    834         return reg
    835 
    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.registry
    853         self.transport = proto_helpers.FakeDatagramTransport()
    854         self.proxy.transport = self.transport
    855 
    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 = p
    862     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.proxy
    870 
    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

     
    2828
    2929abcd""".replace("\n", "\r\n")
    3030
     31
    3132# request, no content-length
    3233request2 = """INVITE sip:foo SIP/2.0
    3334From: mo
     
    3536
    36371234""".replace("\n", "\r\n")
    3738
     39
    3840# request, with garbage after
    3941request3 = """INVITE sip:foo SIP/2.0
    4042From: mo
     
    4547
    4648lalalal""".replace("\n", "\r\n")
    4749
     50
    4851# three requests
    4952request4 = """INVITE sip:foo SIP/2.0
    5053From: mo
     
    6366
    64671234""".replace("\n", "\r\n")
    6568
     69
    6670# response, no content
    6771response1 = """SIP/2.0 200 OK
    6872From:  foo
     
    7175
    7276""".replace("\n", "\r\n")
    7377
     78
    7479# short header version
    7580request_short = """\
    7681INVITE sip:foo SIP/2.0
     
    8085
    8186abcd""".replace("\n", "\r\n")
    8287
     88
    8389request_natted = """\
    8490INVITE sip:foo SIP/2.0
    8591Via: SIP/2.0/UDP 10.0.0.1:5060;rport
    8692
    8793""".replace("\n", "\r\n")
    8894
     95
     96
    8997class TestRealm:
    9098    def requestAvatar(self, avatarId, mind, *interfaces):
    9199        return sip.IContact, None, lambda: None
    92100
     101
     102
     103class 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
    93131class MessageParsingTestCase(unittest.TestCase):
    94132    def setUp(self):
    95133        self.l = []
     
    337375
    338376
    339377
    340 class URLTestCase(unittest.TestCase):
     378class URITestCase(unittest.TestCase):
     379    """
     380    Tests for L{sip.URI} and {sip.parseURL}.
     381    """
    341382
    342383    def testRoundtrip(self):
    343384        for url in [
    344385            "sip:j.doe@big.com",
    345386            "sip:j.doe:secret@big.com;transport=tcp",
    346             "sip:j.doe@big.com?subject=project",
     387            "sip:j.doe@big.com?Subject=project",
    347388            "sip:example.com",
    348389            ]:
    349390            self.assertEqual(sip.parseURL(url).toString(), url)
    350391
    351     def testComplex(self):
     392
     393    def test_complex(self):
     394        """
     395        Test parsing and printing a URI with one of everything.
     396        """
    352397        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")
    354399        url = sip.parseURL(s)
    355400        for k, v in [("username", "user"), ("password", "pass"),
    356401                     ("host", "hosta"), ("port", 123),
    357402                     ("transport", "udp"), ("usertype", "phone"),
    358403                     ("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"})]:
    361407            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')
    362412
    363413
     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
    364546class ParseTestCase(unittest.TestCase):
    365547
    366548    def testParseAddress(self):
     549        """
     550        Confirm that various names and addresses are parsed correctly.
     551        """
    367552        for address, name, urls, params in [
    368553            ('"A. G. Bell" <sip:foo@example.com>',
    369554             "A. G. Bell", "sip:foo@example.com", {}),
    370555            ("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", {}),
    371572            ("sip:foo@example.com", "", "sip:foo@example.com", {}),
    372573            ("<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", {}),
    375582            ]:
    376583            gname, gurl, gparams = sip.parseAddress(address)
    377584            self.assertEqual(name, gname)
     
    379586            self.assertEqual(gparams, params)
    380587
    381588
     589
    382590class DummyLocator:
    383591    implements(sip.ILocator)
    384592    def getAddress(self, logicalURL):
    385593        return defer.succeed(sip.URL("server.com", port=5060))
    386594
     595
     596
    387597class FailingLocator:
    388598    implements(sip.ILocator)
    389599    def getAddress(self, logicalURL):
    390600        return defer.fail(LookupError())
    391601
    392602
     603
    393604class ProxyTestCase(unittest.TestCase):
    394605
    395606    def setUp(self):
  • twisted/protocols/sip.py

     
    99Documented in RFC 2543.
    1010[Superceded by 3261]
    1111
    12 
    1312This module contains a deprecated implementation of HTTP Digest authentication.
    1413See L{twisted.cred.credentials} and L{twisted.cred._digest} for its new home.
     14
     15Features required by RFC3261 missing from this module:
     16 * SIPS support
     17
    1518"""
    1619
    1720# system imports
    1821import socket, time, sys, random, warnings
     22import urllib
     23import re
    1924from zope.interface import implements, Interface
    2025
    2126# twisted imports
    2227from twisted.python import log, util
    23 from twisted.python.deprecate import deprecated
     28from twisted.python.deprecate import deprecated, getDeprecationWarningString
    2429from twisted.python.versions import Version
     30from twisted.python.compat import set
    2531from twisted.python.hashlib import md5
    2632from twisted.internet import protocol, defer, reactor
    2733
     
    113119    606: "Not Acceptable",
    114120}
    115121
     122
     123
    116124specialCases = {
    117125    'cseq': 'CSeq',
    118126    'call-id': 'Call-ID',
     
    120128}
    121129
    122130
     131
     132def 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))
    123149def dashCapitalize(s):
    124150    ''' Capitalize a string, making sure to treat - as a word seperator '''
    125151    return '-'.join([ x.capitalize() for x in s.split('-')])
    126152
     153
     154
    127155def unq(s):
    128156    if s[0] == s[-1] == '"':
    129157        return s[1:-1]
    130158    return s
    131159
     160
     161
    132162def DigestCalcHA1(
    133163    pszAlg,
    134164    pszUserName,
     
    205235    See RFC 3261, sections 8.1.1.7, 18.2.2, and 20.42.
    206236
    207237    @ivar transport: Network protocol used for this leg. (Probably either "TCP"
    208     or "UDP".)
     238        or "UDP".)
    209239    @type transport: C{str}
     240
    210241    @ivar branch: Unique identifier for this request.
    211242    @type branch: C{str}
     243
    212244    @ivar host: Hostname or IP for this leg.
    213245    @type host: C{str}
     246
    214247    @ivar port: Port used for this leg.
    215248    @type port C{int}, or None.
     249
    216250    @ivar rportRequested: Whether to request RFC 3581 client processing or not.
    217251    @type rportRequested: C{bool}
     252
    218253    @ivar rportValue: Servers wishing to honor requests for RFC 3581 processing
    219     should set this parameter to the source port the request was received
    220     from.
     254        should set this parameter to the source port the request was received
     255        from.
    221256    @type rportValue: C{int}, or None.
    222257
    223258    @ivar ttl: Time-to-live for requests on multicast paths.
    224259    @type ttl: C{int}, or None.
     260
    225261    @ivar maddr: The destination multicast address, if any.
    226262    @type maddr: C{str}, or None.
     263
    227264    @ivar hidden: Obsolete in SIP 2.0.
    228265    @type hidden: C{bool}
     266
    229267    @ivar otherParams: Any other parameters in the header.
    230268    @type otherParams: C{dict}
    231269    """
     
    237275        Set parameters of this Via header. All arguments correspond to
    238276        attributes of the same name.
    239277
    240         To maintain compatibility with old SIP
    241         code, the 'rport' argument is used to determine the values of
    242         C{rportRequested} and C{rportValue}. If None, C{rportRequested} is set
    243         to True. (The deprecated method for doing this is to pass True.) If an
    244         integer, C{rportValue} is set to the given 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.
    245283
    246284        Any arguments not explicitly named here are collected into the
    247285        C{otherParams} dict.
     
    369407    return Via(**result)
    370408
    371409
    372 class URL:
    373     """A SIP URL."""
     410class URI:
     411    """
     412    A SIP URI, as defined in RFC 3261, section 19.1.
    374413
     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
    375455    def __init__(self, host, username=None, password=None, port=None,
    376456                 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        """
    378461        self.username = username
    379462        self.host = host
    380463        self.password = password
     
    382465        self.transport = transport
    383466        self.usertype = usertype
    384467        self.method = method
    385         self.tag = tag
    386468        self.ttl = ttl
    387469        self.maddr = maddr
    388470        if other == None:
    389             self.other = []
     471            self.other = {}
    390472        else:
    391473            self.other = other
    392474        if headers == None:
     
    394476        else:
    395477            self.headers = headers
    396478
     479
    397480    def toString(self):
     481        """
     482        Format this object's contents as a SIP URI.
     483        """
    398484        l = []; w = l.append
    399485        w("sip:")
    400486        if self.username != None:
    401             w(self.username)
     487            w(urllib.quote(self.username))
    402488            if self.password != None:
    403                 w(":%s" % self.password)
     489                w(":%s" % (urllib.quote(self.password)))
    404490            w("@")
    405491        w(self.host)
    406492        if self.port != None:
    407493            w(":%d" % self.port)
    408494        if self.usertype != None:
    409495            w(";user=%s" % self.usertype)
    410         for n in ("transport", "ttl", "maddr", "method", "tag"):
     496        for n in ("transport", "ttl", "maddr", "method"):
    411497            v = getattr(self, n)
    412498            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)
    416505        if self.headers:
    417506            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()]))
    419509        return "".join(l)
    420510
     511
    421512    def __str__(self):
     513        """
     514        Format this object's contents as a SIP URI.
     515        """
    422516        return self.toString()
    423517
     518
    424519    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()
    426524
    427525
     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.
     603URL = URI
     604
     605
     606
    428607def 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'.
    430611
    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.
    432619    """
    433620    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])
    436630    parts = url[4:].split(";")
    437631    userdomain, params = parts[0], parts[1:]
    438632    udparts = userdomain.split("@", 1)
     
    440634        userpass, hostport = udparts
    441635        upparts = userpass.split(":", 1)
    442636        if len(upparts) == 1:
    443             d["username"] = upparts[0]
     637            d["username"] = urllib.unquote(upparts[0])
    444638        else:
    445             d["username"] = upparts[0]
    446             d["password"] = upparts[1]
     639            d["username"] = urllib.unquote(upparts[0])
     640            d["password"] = urllib.unquote(upparts[1])
    447641    else:
    448642        hostport = udparts[0]
    449643    hpparts = hostport.split(":", 1)
     644
    450645    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
    452652    else:
     653        if "?" in hpparts[1]:
     654            _port, headers = hpparts[1].split("?", 1)
     655            parseHeaders(headers)
     656        else:
     657            _port = hpparts[1]
    453658        d["host"] = hpparts[0]
    454         d["port"] = int(hpparts[1])
     659        d["port"] = int(_port)
    455660    if host != None:
    456661        d["host"] = host
    457662    if port != None:
    458663        d["port"] = port
    459664    for p in params:
    460665        if p == params[-1] and "?" in p:
    461             d["headers"] = h = {}
    462666            p, headers = p.split("?", 1)
    463             for header in headers.split("&"):
    464                 k, v = header.split("=")
    465                 h[k] = v
     667            parseHeaders(headers)
    466668        nv = p.split("=", 1)
    467669        if len(nv) == 1:
    468             d.setdefault("other", []).append(p)
     670            d.setdefault("other", {})[urllib.unquote(p.lower())] = ''
    469671            continue
    470         name, value = nv
     672        name, value = [urllib.unquote(x.lower()) for x in nv]
    471673        if name == "user":
    472674            d["usertype"] = value
    473         elif name in ("transport", "ttl", "maddr", "method", "tag"):
     675        elif name in ("transport", "ttl", "maddr", "method"):
    474676            if name == "ttl":
    475677                value = int(value)
    476678            d[name] = value
    477679        else:
    478             d.setdefault("other", []).append(p)
    479     return URL(**d)
     680            d.setdefault("other", {})[name] = value
     681    return URI(**d)
    480682
    481683
     684
    482685def cleanRequestURL(url):
    483686    """Clean a URL from a Request line."""
    484687    url.transport = None
     
    490693def parseAddress(address, host=None, port=None, clean=0):
    491694    """Return (name, uri, params) for From/To/Contact header.
    492695
     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
    493705    @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.
    494710    """
     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
    495724    address = address.strip()
    496725    # 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
    499734    params = {}
    500735    name, url = address.split("<", 1)
    501736    name = name.strip()
     
    503738        name = name[1:]
    504739    if name.endswith('"'):
    505740        name = name[:-1]
     741    name = re.sub(r'\\(.)', r'\1', name)
    506742    url, paramstring = url.split(">", 1)
    507743    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)
    515745    if clean:
    516746        # rfc 2543 6.21
    517747        url.ttl = None
    518748        url.headers = {}
    519749        url.transport = None
    520750        url.maddr = None
    521     return name, url, params
     751    return name.decode('utf8', 'replace'), url, params
    522752
    523753
     754
    524755class SIPError(Exception):
    525756    def __init__(self, code, phrase=None):
    526757        if phrase is None:
     
    530761        self.phrase = phrase
    531762
    532763
     764
    533765class RegistrationError(SIPError):
    534766    """Registration was not possible."""
    535767
    536768
     769
    537770class Message:
    538771    """A SIP message."""
    539772
     
    563796        s = "%s\r\n" % self._getHeaderLine()
    564797        for n, vs in self.headers.items():
    565798            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)
    567800        s += "\r\n"
    568801        s += self.body
    569802        return s
     
    612845class MessagesParser(basic.LineReceiver):
    613846    """A SIP messages parser.
    614847
    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.
    617850    """
    618851
    619852    version = "SIP/2.0"
     
    8351068        raise NotImplementedError
    8361069
    8371070
     1071
    8381072class IContact(Interface):
    8391073    """A user of a registrar or proxy"""
    8401074
    8411075
     1076
    8421077class Registration:
    8431078    def __init__(self, secondsToExpiry, contactURL):
    8441079        self.secondsToExpiry = secondsToExpiry
    8451080        self.contactURL = contactURL
    8461081
     1082
     1083
    8471084class IRegistry(Interface):
    8481085    """Allows registration of logical->physical URL mapping."""
    8491086
     
    8531090        @return: Deferred of C{Registration} or failure with RegistrationError.
    8541091        """
    8551092
     1093
    8561094    def unregisterAddress(domainURL, logicalURL, physicalURL):
    8571095        """Unregister the physical address of a logical URL.
    8581096
    8591097        @return: Deferred of C{Registration} or failure with RegistrationError.
    8601098        """
    8611099
     1100
    8621101    def getRegistrationInfo(logicalURL):
    8631102        """Get registration info for logical URL.
    8641103
     
    8661105        """
    8671106
    8681107
     1108
    8691109class ILocator(Interface):
    8701110    """Allow looking up physical address for logical URL."""
    8711111
     
    8771117        """
    8781118
    8791119
     1120
    8801121class Proxy(Base):
    8811122    """SIP proxy."""
    8821123
     
    8941135        self.port = port
    8951136        Base.__init__(self)
    8961137
     1138
    8971139    def getVia(self):
    8981140        """Return value of Via header for this proxy."""
    8991141        return Via(host=self.host, port=self.port)
    9001142
     1143
    9011144    def handle_request(self, message, addr):
    9021145        # send immediate 100/trying message before processing
    9031146        #self.deliverResponse(self.responseFromRequest(100, message))
     
    9171160                    self.deliverResponse(self.responseFromRequest(e.code, message))
    9181161                )
    9191162
     1163
    9201164    def handle_request_default(self, message, (srcHost, srcPort)):
    9211165        """Default request handler.
    9221166
     
    9441188        d.addCallback(self.sendMessage, message)
    9451189        d.addErrback(self._cantForwardRequest, message)
    9461190
     1191
    9471192    def _cantForwardRequest(self, error, message):
    9481193        error.trap(LookupError)
    9491194        del message.headers["via"][0] # this'll be us
    9501195        self.deliverResponse(self.responseFromRequest(404, message))
    9511196
     1197
    9521198    def deliverResponse(self, responseMessage):
    9531199        """Deliver response.
    9541200
     
    9611207        destAddr = URL(host=host, port=port)
    9621208        self.sendMessage(destAddr, responseMessage)
    9631209
     1210
    9641211    def responseFromRequest(self, code, request):
    9651212        """Create a response to a request message."""
    9661213        response = Response(code)
     
    9681215            response.headers[name] = request.headers.get(name, [])[:]
    9691216        return response
    9701217
     1218
    9711219    def handle_response(self, message, addr):
    9721220        """Default response handler."""
    9731221        v = parseViaHeader(message.headers["via"][0])
     
    9841232            return
    9851233        self.deliverResponse(message)
    9861234
     1235
    9871236    def gotResponse(self, message, addr):
    9881237        """Called with responses that are addressed at this server."""
    9891238        pass
    9901239
     1240
     1241
    9911242class IAuthorizer(Interface):
    9921243    def getChallenge(peer):
    9931244        """Generate a challenge the client may respond to.
     
    9991250        @return: The challenge string
    10001251        """
    10011252
     1253
    10021254    def decode(response):
    10031255        """Create a credentials object from the given response.
    10041256
    10051257        @type response: C{str}
    10061258        """
    10071259
     1260
     1261
    10081262class BasicAuthorizer:
    10091263    """Authorizer for insecure Basic (base64-encoded plaintext) authentication.
    10101264
     
    10271281    def getChallenge(self, peer):
    10281282        return None
    10291283
     1284
    10301285    def decode(self, response):
    10311286        # At least one SIP client improperly pads its Base64 encoded messages
    10321287        for i in range(3):
     
    10501305    """Yet Another Simple Digest-MD5 authentication scheme"""
    10511306
    10521307    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)
    10581311        self.username = username
    10591312        self.fields = fields
    10601313        self.challenges = challenges
    10611314
     1315
    10621316    def checkPassword(self, password):
    10631317        method = 'REGISTER'
    10641318        response = self.fields.get('response')
     
    10911345    implements(IAuthorizer)
    10921346
    10931347    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)
    11001351        self.outstanding = {}
    11011352
    11021353
    1103 
    11041354    def generateNonce(self):
    11051355        c = tuple([random.randrange(sys.maxint) for _ in range(3)])
    11061356        c = '%d%d%d' % c
    11071357        return c
    11081358
     1359
    11091360    def generateOpaque(self):
    11101361        return str(random.randrange(sys.maxint))
    11111362
     1363
    11121364    def getChallenge(self, peer):
    11131365        c = self.generateNonce()
    11141366        o = self.generateOpaque()
     
    11201372            'algorithm="MD5"',
    11211373        ))
    11221374
     1375
    11231376    def decode(self, response):
    11241377        response = ' '.join(response.splitlines())
    11251378        parts = response.split(',')
     
    11341387            raise SIPError(400)
    11351388
    11361389
     1390
    11371391class RegisterProxy(Proxy):
    11381392    """A proxy that allows registration for a specific domain.
    11391393
     
    11521406        if "digest" not in self.authorizers:
    11531407            self.authorizers["digest"] = DigestAuthorizer()
    11541408
     1409
    11551410    def handle_ACK_request(self, message, (host, port)):
    11561411        # XXX
    11571412        # ACKs are a client's way of indicating they got the last message
     
    11601415        # if no ACK is received.
    11611416        pass
    11621417
     1418
    11631419    def handle_REGISTER_request(self, message, (host, port)):
    11641420        """Handle a registration request.
    11651421
     
    11751431            else:
    11761432                return self.login(message, host, port)
    11771433
     1434
    11781435    def unauthorized(self, message, host, port):
    11791436        m = self.responseFromRequest(401, message)
    11801437        for (scheme, auth) in self.authorizers.iteritems():
     
    12081465        else:
    12091466            self.deliverResponse(self.responseFromRequest(501, message))
    12101467
     1468
    12111469    def _cbLogin(self, (i, a, l), message, host, port):
    12121470        # It's stateless, matey.  What a joke.
    12131471        self.register(message, host, port)
    12141472
     1473
    12151474    def _ebLogin(self, failure, message, host, port):
    12161475        failure.trap(cred.error.UnauthorizedLogin)
    12171476        self.unauthorized(message, host, port)
    12181477
     1478
    12191479    def register(self, message, host, port):
    12201480        """Allow all users to register"""
    12211481        name, toURL, params = parseAddress(message.headers["to"][0], clean=1)
     
    12381498                errbackArgs=(message,)
    12391499            )
    12401500
     1501
    12411502    def _cbRegister(self, registration, message):
    12421503        response = self.responseFromRequest(200, message)
    12431504        if registration.contactURL != None:
     
    12461507        response.addHeader("content-length", "0")
    12471508        self.deliverResponse(response)
    12481509
     1510
    12491511    def _ebRegister(self, error, message):
    12501512        error.trap(RegistrationError, LookupError)
    12511513        # XXX return error message, and alter tests to deal with
    12521514        # this, currently tests assume no message sent on failure
    12531515
     1516
    12541517    def unregister(self, message, toURL, contact):
    12551518        try:
    12561519            expires = int(message.headers["expires"][0])
     
    12671530                    ).addErrback(self._ebUnregister, message
    12681531                    )
    12691532
     1533
    12701534    def _cbUnregister(self, registration, message):
    12711535        msg = self.responseFromRequest(200, message)
    12721536        msg.headers.setdefault('contact', []).append(registration.contactURL.toString())
    12731537        msg.addHeader("expires", "0")
    12741538        self.deliverResponse(msg)
    12751539
     1540
    12761541    def _ebUnregister(self, registration, message):
    12771542        pass
    12781543
    12791544
     1545
    12801546class InMemoryRegistry:
    12811547    """A simplistic registry for a specific domain."""
    12821548
     
    12861552        self.domain = domain # the domain we handle registration for
    12871553        self.users = {} # map username to (IDelayedCall for expiry, address URI)
    12881554
     1555
    12891556    def getAddress(self, userURI):
    12901557        if userURI.host != self.domain:
    12911558            return defer.fail(LookupError("unknown domain"))
     
    12951562        else:
    12961563            return defer.fail(LookupError("no such user"))
    12971564
     1565
    12981566    def getRegistrationInfo(self, userURI):
    12991567        if userURI.host != self.domain:
    13001568            return defer.fail(LookupError("unknown domain"))
     
    13041572        else:
    13051573            return defer.fail(LookupError("no such user"))
    13061574
     1575
    13071576    def _expireRegistration(self, username):
    13081577        try:
    13091578            dc, url = self.users[username]
     
    13141583            del self.users[username]
    13151584        return defer.succeed(Registration(0, url))
    13161585
     1586
    13171587    def registerAddress(self, domainURL, logicalURL, physicalURL):
    13181588        if domainURL.host != self.domain:
    13191589            log.msg("Registration for domain we don't handle.")
     
    13301600        self.users[logicalURL.username] = (dc, physicalURL)
    13311601        return defer.succeed(Registration(int(dc.getTime() - time.time()), physicalURL))
    13321602
     1603
    13331604    def unregisterAddress(self, domainURL, logicalURL, physicalURL):
    13341605        return self._expireRegistration(logicalURL.username)