Ticket #2220: sip-fixes.diff

File sip-fixes.diff, 26.4 KB (added by antoine, 8 years ago)

patch for sip parsing bugs

  • twisted/protocols/sip.py

    ==== Patch <sip-fixes> level 3
    Source: f7f26e75-0421-0410-8811-d6d8ddf80b0f:/local/twisted:305 [local]
    Target: bbbe8e31-12d6-0310-92fd-ac37d47ddeeb:/trunk:18594 [mirrored]
            (svn://svn.twistedmatrix.com/svn/Twisted/trunk)
    Log:
     r296@antoine-ubuntu:  antoine | 2006-10-30 11:33:09 +0100
     creating local branch
     
     r297@antoine-ubuntu:  antoine | 2006-10-30 12:16:54 +0100
     bug fixes + tests
     
     r303@antoine-ubuntu:  antoine | 2006-11-03 18:46:59 +0100
     redo multiline header fix
     
     r304@antoine-ubuntu:  antoine | 2006-11-03 18:53:30 +0100
     Via parsing as class method
     
     r305@antoine-ubuntu:  antoine | 2006-11-06 10:46:56 +0100
     test case for unknown params in Via header
     
    
    === twisted/protocols/sip.py
    ==================================================================
     
    9494    488: "Not Acceptable Here", 
    9595    491: "Request Pending", 
    9696    493: "Undecipherable", 
    97      
     97 
    9898    500: "Internal Server Error", 
    9999    501: "Not Implemented", 
    100100    502: "Bad Gateway", # no donut 
     
    102102    504: "Server Time-out", 
    103103    505: "SIP Version not supported", 
    104104    513: "Message Too Large", 
    105      
     105 
    106106    600: "Busy Everywhere", 
    107107    603: "Decline", 
    108108    604: "Does not exist anywhere", 
     
    167167        m.update(":") 
    168168        m.update(pszHEntity) 
    169169    HA2 = m.digest().encode('hex') 
    170      
     170 
    171171    m = md5.md5() 
    172172    m.update(HA1) 
    173173    m.update(":") 
     
    185185    return hash 
    186186 
    187187class Via: 
    188     """A SIP Via header.""" 
     188    """A SIP Via header. Unknown parameters are ignored.""" 
    189189 
    190190    def __init__(self, host, port=PORT, transport="UDP", ttl=None, hidden=False, 
    191                  received=None, rport=None, branch=None, maddr=None): 
     191                 received=None, rport=None, branch=None, maddr=None, **kargs): 
    192192        self.transport = transport 
    193193        self.host = host 
    194194        self.port = port 
     
    205205            s += ";hidden" 
    206206        for n in "ttl", "branch", "maddr", "received", "rport": 
    207207            value = getattr(self, n) 
    208             if value == True: 
     208            if value is True: 
    209209                s += ";" + n 
    210210            elif value != None: 
    211211                s += ";%s=%s" % (n, value) 
    212212        return s 
    213213 
    214  
    215 def parseViaHeader(value): 
    216     """Parse a Via header, returning Via class instance.""" 
    217     parts = value.split(";") 
    218     sent, params = parts[0], parts[1:] 
    219     protocolinfo, by = sent.split(" ", 1) 
    220     by = by.strip() 
    221     result = {} 
    222     pname, pversion, transport = protocolinfo.split("/") 
    223     if pname != "SIP" or pversion != "2.0": 
    224         raise ValueError, "wrong protocol or version: %r" % value 
    225     result["transport"] = transport 
    226     if ":" in by: 
    227         host, port = by.split(":") 
    228         result["port"] = int(port) 
    229         result["host"] = host 
    230     else: 
    231         result["host"] = by 
    232     for p in params: 
    233         # it's the comment-striping dance! 
    234         p = p.strip().split(" ", 1) 
    235         if len(p) == 1: 
    236             p, comment = p[0], "" 
     214    def fromString(cls, value): 
     215        """Parse a Via header, returning Via class instance.""" 
     216        parts = value.split(";") 
     217        sent, params = parts[0], parts[1:] 
     218        protocolinfo, by = sent.split(" ", 1) 
     219        by = by.strip() 
     220        result = {} 
     221        pname, pversion, transport = protocolinfo.split("/") 
     222        if pname != "SIP" or pversion != "2.0": 
     223            raise ValueError, "wrong protocol or version: %r" % value 
     224        result["transport"] = transport 
     225        if ":" in by: 
     226            host, port = by.split(":") 
     227            result["port"] = int(port) 
     228            result["host"] = host 
    237229        else: 
    238             p, comment = p 
    239         if p == "hidden": 
    240             result["hidden"] = True 
    241             continue 
    242         parts = p.split("=", 1) 
    243         if len(parts) == 1: 
    244             name, value = parts[0], True 
    245         else: 
    246             name, value = parts 
    247             if name in ("rport", "ttl"): 
    248                 value = int(value) 
    249         result[name] = value 
    250     return Via(**result) 
     230            result["host"] = by 
     231        for p in params: 
     232            # it's the comment-striping dance! 
     233            p = p.strip().split(" ", 1) 
     234            if len(p) == 1: 
     235                p, comment = p[0], "" 
     236            else: 
     237                p, comment = p 
     238            if p == "hidden": 
     239                result["hidden"] = True 
     240                continue 
     241            parts = p.split("=", 1) 
     242            if len(parts) == 1: 
     243                name, value = parts[0], True 
     244            else: 
     245                name, value = parts 
     246                if name in ("rport", "ttl"): 
     247                    value = int(value) 
     248            result[name] = value 
     249        return cls(**result) 
    251250 
     251    fromString = classmethod(fromString) 
    252252 
     253parseViaHeader = Via.fromString 
     254 
    253255class URL: 
    254256    """A SIP URL.""" 
    255257 
     
    301303 
    302304    def __str__(self): 
    303305        return self.toString() 
    304      
     306 
    305307    def __repr__(self): 
    306308        return '<URL %s:%s@%s:%r/%s>' % (self.username, self.password, self.host, self.port, self.transport) 
    307309 
     
    419421    """A SIP message.""" 
    420422 
    421423    length = None 
    422      
     424 
    423425    def __init__(self): 
    424426        self.headers = util.OrderedDict() # map name to list of values 
    425427        self.body = "" 
    426428        self.finished = 0 
    427      
     429 
    428430    def addHeader(self, name, value): 
    429431        name = name.lower() 
    430432        name = longHeaders.get(name, name) 
     
    434436 
    435437    def bodyDataReceived(self, data): 
    436438        self.body += data 
    437      
     439 
    438440    def creationFinished(self): 
    439441        if (self.length != None) and (self.length != len(self.body)): 
    440442            raise ValueError, "wrong body length" 
     
    465467        else: 
    466468            self.uri = parseURL(uri) 
    467469            cleanRequestURL(self.uri) 
    468      
     470 
    469471    def __repr__(self): 
    470472        return "<SIP Request %d:%s %s>" % (id(self), self.method, self.uri.toString()) 
    471473 
     
    501503    acceptResponses = 1 
    502504    acceptRequests = 1 
    503505    state = "firstline" # or "headers", "body" or "invalid" 
    504      
     506 
    505507    debug = 0 
    506      
     508 
    507509    def __init__(self, messageReceivedCallback): 
    508510        self.messageReceived = messageReceivedCallback 
    509511        self.reset() 
     
    513515        self.length = None # body length 
    514516        self.bodyReceived = 0 # how much of the body we received 
    515517        self.message = None 
     518        self.header = None 
    516519        self.setLineMode(remainingData) 
    517      
     520 
    518521    def invalidMessage(self): 
    519522        self.state = "invalid" 
    520523        self.setRawMode() 
    521      
     524 
    522525    def dataDone(self): 
    523526        # clear out any buffered data that may be hanging around 
    524527        self.clearLineBuffer() 
     
    530533        if self.length == None: 
    531534            # no content-length header, so end of data signals message done 
    532535            self.messageDone() 
    533         elif self.length < self.bodyReceived: 
     536        elif self.length > self.bodyReceived: 
    534537            # aborted in the middle 
    535538            self.reset() 
    536539        else: 
    537540            # we have enough data and message wasn't finished? something is wrong 
    538541            raise RuntimeError, "this should never happen" 
    539      
     542 
    540543    def dataReceived(self, data): 
    541544        try: 
    542545            basic.LineReceiver.dataReceived(self, data) 
    543546        except: 
    544547            log.err() 
    545548            self.invalidMessage() 
    546      
     549 
    547550    def handleFirstLine(self, line): 
    548551        """Expected to create self.message.""" 
    549552        raise NotImplementedError 
    550553 
    551554    def lineLengthExceeded(self, line): 
    552555        self.invalidMessage() 
    553      
     556 
    554557    def lineReceived(self, line): 
    555558        if self.state == "firstline": 
    556559            while line.startswith("\n") or line.startswith("\r"): 
     
    580583        else: 
    581584            assert self.state == "headers" 
    582585        if line: 
    583             # XXX support multi-line headers 
    584             try: 
    585                 name, value = line.split(":", 1) 
    586             except ValueError: 
    587                 self.invalidMessage() 
    588                 return 
    589             self.message.addHeader(name, value.lstrip()) 
    590             if name.lower() == "content-length": 
     586            # multi-line header 
     587            if line.startswith(" ") or line.startswith("\t"): 
     588                name, value = self.header 
     589                self.header = name, (value + line.lstrip()) 
     590            else: 
     591                # new header 
     592                if self.header: 
     593                    self.message.addHeader(*self.header) 
     594                    self.header = None 
    591595                try: 
    592                     self.length = int(value.lstrip()) 
     596                    name, value = line.split(":", 1) 
    593597                except ValueError: 
    594598                    self.invalidMessage() 
    595599                    return 
     600                self.header = name, value.lstrip() 
     601                # XXX we assume content-length won't be multiline 
     602                if name.lower() == "content-length": 
     603                    try: 
     604                        self.length = int(value.lstrip()) 
     605                    except ValueError: 
     606                        self.invalidMessage() 
     607                        return 
    596608        else: 
    597609            # CRLF, we now have message body until self.length bytes, 
    598610            # or if no length was given, until there is no more data 
    599611            # from the connection sending us data. 
    600612            self.state = "body" 
     613            if self.header: 
     614                self.message.addHeader(*self.header) 
     615                self.header = None 
    601616            if self.length == 0: 
    602617                self.messageDone() 
    603618                return 
     
    608623        self.message.creationFinished() 
    609624        self.messageReceived(self.message) 
    610625        self.reset(remainingData) 
    611      
     626 
    612627    def rawDataReceived(self, data): 
    613628        assert self.state in ("body", "invalid") 
    614629        if self.state == "invalid": 
     
    631646 
    632647class Base(protocol.DatagramProtocol): 
    633648    """Base class for SIP clients and servers.""" 
    634      
     649 
    635650    PORT = PORT 
    636651    debug = False 
    637      
     652 
    638653    def __init__(self): 
    639654        self.messages = [] 
    640655        self.parser = MessagesParser(self.addMessage) 
     
    658673    def _fixupNAT(self, message, (srcHost, srcPort)): 
    659674        # RFC 2543 6.40.2, 
    660675        senderVia = parseViaHeader(message.headers["via"][0]) 
    661         if senderVia.host != srcHost:             
     676        if senderVia.host != srcHost: 
    662677            senderVia.received = srcHost 
    663678            if senderVia.port != srcPort: 
    664679                senderVia.rport = srcPort 
     
    709724 
    710725    def handle_response(self, message, addr): 
    711726        """Override to define behavior for responses received. 
    712          
     727 
    713728        @type message: C{Message} 
    714729        @type addr: C{tuple} 
    715730        """ 
     
    760775 
    761776class Proxy(Base): 
    762777    """SIP proxy.""" 
    763      
     778 
    764779    PORT = PORT 
    765780 
    766781    locator = None # object implementing ILocator 
    767      
     782 
    768783    def __init__(self, host=None, port=PORT): 
    769784        """Create new instance. 
    770785 
     
    774789        self.host = host or socket.getfqdn() 
    775790        self.port = port 
    776791        Base.__init__(self) 
    777          
     792 
    778793    def getVia(self): 
    779794        """Return value of Via header for this proxy.""" 
    780795        return Via(host=self.host, port=self.port) 
     
    797812                d.addErrback(lambda e: 
    798813                    self.deliverResponse(self.responseFromRequest(e.code, message)) 
    799814                ) 
    800          
     815 
    801816    def handle_request_default(self, message, (srcHost, srcPort)): 
    802817        """Default request handler. 
    803          
     818 
    804819        Default behaviour for OPTIONS and unknown methods for proxies 
    805820        is to forward message on to the client. 
    806821 
     
    808823        everything. 
    809824        """ 
    810825        def _mungContactHeader(uri, message): 
    811             message.headers['contact'][0] = uri.toString()             
     826            message.headers['contact'][0] = uri.toString() 
    812827            return self.sendMessage(uri, message) 
    813          
     828 
    814829        viaHeader = self.getVia() 
    815830        if viaHeader.toString() in message.headers["via"]: 
    816831            # must be a loop, so drop message 
     
    824839        d = self.locator.getAddress(uri) 
    825840        d.addCallback(self.sendMessage, message) 
    826841        d.addErrback(self._cantForwardRequest, message) 
    827      
     842 
    828843    def _cantForwardRequest(self, error, message): 
    829844        error.trap(LookupError) 
    830845        del message.headers["via"][0] # this'll be us 
    831846        self.deliverResponse(self.responseFromRequest(404, message)) 
    832      
     847 
    833848    def deliverResponse(self, responseMessage): 
    834849        """Deliver response. 
    835850 
     
    838853        # XXX we don't do multicast yet 
    839854        host = destVia.received or destVia.host 
    840855        port = destVia.rport or destVia.port or self.PORT 
    841          
     856 
    842857        destAddr = URL(host=host, port=port) 
    843858        self.sendMessage(destAddr, responseMessage) 
    844859 
     
    848863        for name in ("via", "to", "from", "call-id", "cseq"): 
    849864            response.headers[name] = request.headers.get(name, [])[:] 
    850865        return response 
    851      
     866 
    852867    def handle_response(self, message, addr): 
    853868        """Default response handler.""" 
    854869        v = parseViaHeader(message.headers["via"][0]) 
     
    864879            self.gotResponse(message, addr) 
    865880            return 
    866881        self.deliverResponse(message) 
    867      
     882 
    868883    def gotResponse(self, message, addr): 
    869884        """Called with responses that are addressed at this server.""" 
    870885        pass 
     
    872887class IAuthorizer(Interface): 
    873888    def getChallenge(peer): 
    874889        """Generate a challenge the client may respond to. 
    875          
     890 
    876891        @type peer: C{tuple} 
    877892        @param peer: The client's address 
    878          
     893 
    879894        @rtype: C{str} 
    880895        @return: The challenge string 
    881896        """ 
    882      
     897 
    883898    def decode(response): 
    884899        """Create a credentials object from the given response. 
    885          
     900 
    886901        @type response: C{str} 
    887902        """ 
    888   
     903 
    889904class BasicAuthorizer: 
    890905    """Authorizer for insecure Basic (base64-encoded plaintext) authentication. 
    891      
     906 
    892907    This form of authentication is broken and insecure.  Do not use it. 
    893908    """ 
    894909 
    895910    implements(IAuthorizer) 
    896      
     911 
    897912    def getChallenge(self, peer): 
    898913        return None 
    899      
     914 
    900915    def decode(self, response): 
    901916        # At least one SIP client improperly pads its Base64 encoded messages 
    902917        for i in range(3): 
     
    917932 
    918933class DigestedCredentials(cred.credentials.UsernameHashedPassword): 
    919934    """Yet Another Simple Digest-MD5 authentication scheme""" 
    920      
     935 
    921936    def __init__(self, username, fields, challenges): 
    922937        self.username = username 
    923938        self.fields = fields 
    924939        self.challenges = challenges 
    925      
     940 
    926941    def checkPassword(self, password): 
    927942        method = 'REGISTER' 
    928943        response = self.fields.get('response') 
     
    937952        if opaque not in self.challenges: 
    938953            return False 
    939954        del self.challenges[opaque] 
    940          
     955 
    941956        user, domain = self.username.split('@', 1) 
    942957        if uri is None: 
    943958            uri = 'sip:' + domain 
     
    946961            DigestCalcHA1(algo, user, domain, password, nonce, cnonce), 
    947962            nonce, nc, cnonce, qop, method, uri, None, 
    948963        ) 
    949          
     964 
    950965        return expected == response 
    951966 
    952967class DigestAuthorizer: 
    953968    CHALLENGE_LIFETIME = 15 
    954      
     969 
    955970    implements(IAuthorizer) 
    956      
     971 
    957972    def __init__(self): 
    958973        self.outstanding = {} 
    959      
     974 
    960975    def generateNonce(self): 
    961976        c = tuple([random.randrange(sys.maxint) for _ in range(3)]) 
    962977        c = '%d%d%d' % c 
     
    975990            'qop-options="auth"', 
    976991            'algorithm="MD5"', 
    977992        )) 
    978          
     993 
    979994    def decode(self, response): 
    980995        response = ' '.join(response.splitlines()) 
    981996        parts = response.split(',') 
     
    10031018    authorizers = { 
    10041019        'digest': DigestAuthorizer(), 
    10051020    } 
    1006      
     1021 
    10071022    def __init__(self, *args, **kw): 
    10081023        Proxy.__init__(self, *args, **kw) 
    10091024        self.liveChallenges = {} 
    1010          
     1025 
    10111026    def handle_ACK_request(self, message, (host, port)): 
    10121027        # XXX 
    10131028        # ACKs are a client's way of indicating they got the last message 
     
    10421057            m.headers.setdefault('www-authenticate', []).append(value) 
    10431058        self.deliverResponse(m) 
    10441059 
    1045   
     1060 
    10461061    def login(self, message, host, port): 
    10471062        parts = message.headers['authorization'][0].split(None, 1) 
    10481063        a = self.authorizers.get(parts[0].lower()) 
     
    10671082    def _cbLogin(self, (i, a, l), message, host, port): 
    10681083        # It's stateless, matey.  What a joke. 
    10691084        self.register(message, host, port) 
    1070      
     1085 
    10711086    def _ebLogin(self, failure, message, host, port): 
    10721087        failure.trap(cred.error.UnauthorizedLogin) 
    10731088        self.unauthorized(message, host, port) 
     
    11371152    """A simplistic registry for a specific domain.""" 
    11381153 
    11391154    implements(IRegistry, ILocator) 
    1140      
     1155 
    11411156    def __init__(self, domain): 
    11421157        self.domain = domain # the domain we handle registration for 
    11431158        self.users = {} # map username to (IDelayedCall for expiry, address URI) 
     
    11501165            return defer.succeed(url) 
    11511166        else: 
    11521167            return defer.fail(LookupError("no such user")) 
    1153              
     1168 
    11541169    def getRegistrationInfo(self, userURI): 
    11551170        if userURI.host != self.domain: 
    11561171            return defer.fail(LookupError("unknown domain")) 
     
    11591174            return defer.succeed(Registration(int(dc.getTime() - time.time()), url)) 
    11601175        else: 
    11611176            return defer.fail(LookupError("no such user")) 
    1162          
     1177 
    11631178    def _expireRegistration(self, username): 
    11641179        try: 
    11651180            dc, url = self.users[username] 
     
    11691184            dc.cancel() 
    11701185            del self.users[username] 
    11711186        return defer.succeed(Registration(0, url)) 
    1172      
     1187 
    11731188    def registerAddress(self, domainURL, logicalURL, physicalURL): 
    11741189        if domainURL.host != self.domain: 
    11751190            log.msg("Registration for domain we don't handle.") 
  • twisted/test/test_sip.py

    === twisted/test/test_sip.py
    ==================================================================
     
    8585 
    8686""".replace("\n", "\r\n") 
    8787 
     88# multiline headers (example from RFC 3261) 
     89response_multiline = """\ 
     90SIP/2.0 200 OK 
     91Via: SIP/2.0/UDP server10.biloxi.com 
     92    ;branch=z9hG4bKnashds8;received=192.0.2.3 
     93Via: SIP/2.0/UDP bigbox3.site3.atlanta.com 
     94    ;branch=z9hG4bK77ef4c2312983.1;received=192.0.2.2 
     95Via: SIP/2.0/UDP pc33.atlanta.com 
     96    ;branch=z9hG4bK776asdhds ;received=192.0.2.1 
     97To: Bob <sip:bob@biloxi.com>;tag=a6c85cf 
     98From: Alice <sip:alice@atlanta.com>;tag=1928301774 
     99Call-ID: a84b4c76e66710@pc33.atlanta.com 
     100CSeq: 314159 INVITE 
     101Contact: <sip:bob@192.0.2.4> 
     102Content-Type: application/sdp 
     103Content-Length: 0 
     104\n""".replace("\n", "\r\n") 
     105 
     106 
    88107class TestRealm: 
    89108    def requestAvatar(self, avatarId, mind, *interfaces): 
    90109        return sip.IContact, None, lambda: None 
     
    155174        self.validateMessage(l[0], "INVITE", "sip:foo", 
    156175                             {"from": ["mo"], "to": ["joe"], "content-length": ["4"]}, 
    157176                             "abcd") 
    158          
     177 
    159178    def testSimpleResponse(self): 
    160179        l = self.l 
    161180        self.feedMessage(response1) 
     
    167186        self.assertEquals(m.body, "") 
    168187        self.assertEquals(m.finished, 1) 
    169188 
     189    def testIncomplete(self): 
     190        # test for "aborted" request (body shorter than content-length) 
     191        l = self.l 
     192        self.feedMessage(request4[:-1]) 
     193        self.assertEquals(len(l), 2) 
    170194 
     195    def testMultiLine(self): 
     196        l = self.l 
     197        self.feedMessage(response_multiline) 
     198        self.assertEquals(len(l), 1) 
     199        m = l[0] 
     200        self.assertEquals(m.headers['via'][0], 
     201            "SIP/2.0/UDP server10.biloxi.com;branch=z9hG4bKnashds8;received=192.0.2.3") 
     202        self.assertEquals(m.headers['via'][2], 
     203            "SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds ;received=192.0.2.1") 
     204 
     205 
    171206class MessageParsingTestCase2(MessageParsingTestCase): 
    172207    """Same as base class, but feed data char by char.""" 
    173208 
     
    208243        self.assertEquals(v1.transport, v2.transport) 
    209244        self.assertEquals(v1.host, v2.host) 
    210245        self.assertEquals(v1.port, v2.port) 
    211      
     246 
    212247    def testComplex(self): 
    213248        s = "SIP/2.0/UDP first.example.com:4000;ttl=16;maddr=224.2.0.1 ;branch=a7c6a8dlze (Example)" 
    214249        v = sip.parseViaHeader(s) 
     
    222257        self.assertEquals(v.toString(), 
    223258                          "SIP/2.0/UDP first.example.com:4000;ttl=16;branch=a7c6a8dlze;maddr=224.2.0.1") 
    224259        self.checkRoundtrip(v) 
    225      
     260 
    226261    def testSimple(self): 
    227262        s = "SIP/2.0/UDP example.com;hidden" 
    228263        v = sip.parseViaHeader(s) 
     
    236271        self.assertEquals(v.toString(), 
    237272                          "SIP/2.0/UDP example.com:5060;hidden") 
    238273        self.checkRoundtrip(v) 
    239      
     274 
     275    def testOneIsNotTrue(self): 
     276        s = "SIP/2.0/UDP example.com;ttl=1" 
     277        v = sip.parseViaHeader(s) 
     278        self.assertEquals(v.toString().rsplit(';')[1], "ttl=1") 
     279        self.checkRoundtrip(v) 
     280 
    240281    def testSimpler(self): 
    241282        v = sip.Via("example.com") 
    242283        self.checkRoundtrip(v) 
     
    253294        self.assertEquals(v.port, 5060) 
    254295        self.assertEquals(v.received, "22.13.1.5") 
    255296        self.assertEquals(v.rport, 12345) 
    256          
     297 
    257298        self.assertNotEquals(v.toString().find("rport=12345"), -1) 
    258299 
     300    def testUnknownParam(self): 
     301        s = "SIP/2.0/UDP example.com;alias;eggs;spam=1234" 
     302        v = sip.parseViaHeader(s) 
     303        self.checkRoundtrip(v) 
     304 
    259305class URLTestCase(unittest.TestCase): 
    260306 
    261307    def testRoundtrip(self): 
     
    305351    implements(sip.ILocator) 
    306352    def getAddress(self, logicalURL): 
    307353        return defer.fail(LookupError()) 
    308      
    309354 
     355 
    310356class ProxyTestCase(unittest.TestCase): 
    311357 
    312358    def setUp(self): 
     
    314360        self.proxy.locator = DummyLocator() 
    315361        self.sent = [] 
    316362        self.proxy.sendMessage = lambda dest, msg: self.sent.append((dest, msg)) 
    317      
     363 
    318364    def testRequestForward(self): 
    319365        r = sip.Request("INVITE", "sip:foo") 
    320366        r.addHeader("via", sip.Via("1.2.3.4").toString()) 
     
    334380                           "SIP/2.0/UDP 1.2.3.4:5060", 
    335381                           "SIP/2.0/UDP 1.2.3.5:5060"]) 
    336382 
    337      
     383 
    338384    def testReceivedRequestForward(self): 
    339385        r = sip.Request("INVITE", "sip:foo") 
    340386        r.addHeader("via", sip.Via("1.2.3.4").toString()) 
     
    346392        self.assertEquals(m.headers["via"], 
    347393                          ["SIP/2.0/UDP 127.0.0.1:5060", 
    348394                           "SIP/2.0/UDP 1.2.3.4:5060;received=1.1.1.1"]) 
    349          
    350395 
     396 
    351397    def testResponseWrongVia(self): 
    352398        # first via must match proxy's address 
    353399        r = sip.Response(200) 
    354400        r.addHeader("via", sip.Via("foo.com").toString()) 
    355401        self.proxy.datagramReceived(r.toString(), ("1.1.1.1", 5060)) 
    356402        self.assertEquals(len(self.sent), 0) 
    357      
     403 
    358404    def testResponseForward(self): 
    359405        r = sip.Response(200) 
    360406        r.addHeader("via", sip.Via("127.0.0.1").toString()) 
     
    365411        self.assertEquals((dest.host, dest.port), ("client.com", 1234)) 
    366412        self.assertEquals(m.code, 200) 
    367413        self.assertEquals(m.headers["via"], ["SIP/2.0/UDP client.com:1234"]) 
    368          
     414 
    369415    def testReceivedResponseForward(self): 
    370416        r = sip.Response(200) 
    371417        r.addHeader("via", sip.Via("127.0.0.1").toString()) 
     
    374420        self.assertEquals(len(self.sent), 1) 
    375421        dest, m = self.sent[0] 
    376422        self.assertEquals((dest.host, dest.port), ("client.com", 5060)) 
    377          
     423 
    378424    def testResponseToUs(self): 
    379425        r = sip.Response(200) 
    380426        r.addHeader("via", sip.Via("127.0.0.1").toString()) 
     
    385431        m, addr = l[0] 
    386432        self.assertEquals(len(m.headers.get("via", [])), 0) 
    387433        self.assertEquals(m.code, 200) 
    388      
     434 
    389435    def testLoop(self): 
    390436        r = sip.Request("INVITE", "sip:foo") 
    391         r.addHeader("via", sip.Via("1.2.3.4").toString())  
     437        r.addHeader("via", sip.Via("1.2.3.4").toString()) 
    392438        r.addHeader("via", sip.Via("127.0.0.1").toString()) 
    393439        self.proxy.datagramReceived(r.toString(), ("client.com", 5060)) 
    394440        self.assertEquals(self.sent, []) 
     
    431477        r.addHeader("contact", "sip:joe@client.com:1234") 
    432478        r.addHeader("via", sip.Via("client.com").toString()) 
    433479        self.proxy.datagramReceived(r.toString(), ("client.com", 5060)) 
    434      
     480 
    435481    def unregister(self): 
    436482        r = sip.Request("REGISTER", "sip:bell.example.com") 
    437483        r.addHeader("to", "sip:joe@bell.example.com") 
     
    439485        r.addHeader("via", sip.Via("client.com").toString()) 
    440486        r.addHeader("expires", "0") 
    441487        self.proxy.datagramReceived(r.toString(), ("client.com", 5060)) 
    442      
     488 
    443489    def testRegister(self): 
    444490        self.register() 
    445491        dest, m = self.sent[0] 
     
    482528    def testFailedAuthentication(self): 
    483529        self.addPortal() 
    484530        self.register() 
    485          
     531 
    486532        self.assertEquals(len(self.registry.users), 0) 
    487533        self.assertEquals(len(self.sent), 1) 
    488534        dest, m = self.sent[0] 
     
    500546        r.addHeader("via", sip.Via("client.com").toString()) 
    501547        r.addHeader("authorization", "Basic " + "userXname:passXword".encode('base64')) 
    502548        self.proxy.datagramReceived(r.toString(), ("client.com", 5060)) 
    503          
     549 
    504550        self.assertEquals(len(self.registry.users), 1) 
    505551        self.assertEquals(len(self.sent), 1) 
    506552        dest, m = self.sent[0] 
    507553        self.assertEquals(m.code, 200) 
    508554 
    509      
     555 
    510556    def testFailedBasicAuthentication(self): 
    511557        self.addPortal() 
    512558        self.proxy.authorizers = self.proxy.authorizers.copy() 
     
    518564        r.addHeader("via", sip.Via("client.com").toString()) 
    519565        r.addHeader("authorization", "Basic " + "userXname:password".encode('base64')) 
    520566        self.proxy.datagramReceived(r.toString(), ("client.com", 5060)) 
    521          
     567 
    522568        self.assertEquals(len(self.registry.users), 0) 
    523569        self.assertEquals(len(self.sent), 1) 
    524570        dest, m = self.sent[0] 
     
    546592        d = self.proxy.locator.getAddress(url) 
    547593        self.assertFailure(d, LookupError) 
    548594        return d 
    549      
     595 
    550596    def testNoContactLookup(self): 
    551597        self.register() 
    552598        url = sip.URL(username="jane", host="bell.example.com") 
     
    623669            self.assertEquals(r.code, 200) 
    624670        d.addCallback(check) 
    625671        return d 
    626          
    627672 
     673 
    628674registerRequest = """ 
    629675REGISTER sip:intarweb.us SIP/2.0\r 
    630676Via: SIP/2.0/UDP 192.168.1.100:50609\r 
     
    724770        for d, uri in self.registry.users.values(): 
    725771            d.cancel() 
    726772        del self.proxy 
    727      
     773 
    728774    def testChallenge(self): 
    729775        self.proxy.datagramReceived(registerRequest, ("127.0.0.1", 5632)) 
    730776        self.assertEquals(