]> code.delx.au - pymsnt/commitdiff
Trunk is broken. Don't use!
authorjamesbunton <jamesbunton@55fbd22a-6204-0410-b2f0-b6c764c7e90a>
Sat, 5 Nov 2005 11:12:34 +0000 (11:12 +0000)
committerjamesbunton <jamesbunton@55fbd22a-6204-0410-b2f0-b6c764c7e90a>
Sat, 5 Nov 2005 11:12:34 +0000 (11:12 +0000)
git-svn-id: http://delx.cjb.net/svn/pymsnt/trunk@28 55fbd22a-6204-0410-b2f0-b6c764c7e90a

committer: jamesbunton <jamesbunton@55fbd22a-6204-0410-b2f0-b6c764c7e90a>

21 files changed:
src/avatar.py
src/contact.py
src/disco.py
src/ft.py
src/groupchat.py
src/housekeep.py
src/jabw.py
src/legacy/glue.py
src/legacy/legacylist.py
src/legacy/msnw.py
src/main.py
src/misciq.py
src/register.py
src/session.py
src/tlib/msn/msn.py [moved from src/tlib/msn.py with 100% similarity]
src/tlib/msn/msnft.py [moved from src/tlib/msnft.py with 100% similarity]
src/tlib/msn/msnp11chl.py [moved from src/tlib/msnp11chl.py with 100% similarity]
src/tlib/msn/msnp2p.py [moved from src/tlib/msnp2p.py with 100% similarity]
src/tlib/msn/test_msn.py [new file with mode: 0644]
src/tlib/xmlw.py [new file with mode: 0644]
src/utils.py

index 391245049088fba68397a08f22d1a56ae1a21a24..03edd7cdcfb6556ad21d7d6c9af1b0b73b0857ca 100644 (file)
@@ -4,10 +4,7 @@
 import utils
 import config
 from twisted.internet import reactor
-if(utils.checkTwisted()):
-       from twisted.xish.domish import Element
-else:
-       from tlib.domish import Element
+from tlib.xmlw import Element
 from debug import LogEvent, INFO, WARN, ERROR
 import jabw
 import config
index 48cb1842bca780b594e76794d0dda878c367de90..bab7bec1ad7ce6a80e0cdcf3e8525b9719db8586 100644 (file)
@@ -3,10 +3,7 @@
 
 import utils
 from twisted.internet import reactor
-if(utils.checkTwisted()):
-       from twisted.xish.domish import Element
-else:
-       from tlib.domish import Element
+from tlib.xmlw import Element
 from debug import LogEvent, INFO, WARN, ERROR
 import disco
 import legacy
index c2431e2369acea7ac37508566269a1a9e32f6c50..8dd00cdff7c518c316a532da1c609864f1eec023 100644 (file)
@@ -2,12 +2,7 @@
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
 import utils
-if(utils.checkTwisted()):
-       from twisted.xish.domish import Element
-       from twisted.words.protocols.jabber import jid
-else:
-       from tlib.domish import Element
-       from tlib.jabber import jid
+from tlib.xmlw import Element
 from twisted.internet.defer import Deferred
 from twisted.internet import reactor
 from debug import LogEvent, INFO, WARN, ERROR
@@ -114,8 +109,8 @@ class ServerDiscovery:
                iqType = el.getAttribute("type")
                ulang = utils.getLang(el)
                try: # Stringprep
-                       froj = jid.JID(fro)
-                       to = jid.JID(to).full()
+                       froj = utils.jid(fro)
+                       to = utils.jid(to).full()
                except Exception, e:
                        LogEvent(WARN, "", "Dropping IQ because of stringprep error")
 
index fbf200e55ba431a62f107b227e97b8dfba44e126..ec983595bcaea4b5d3c6df1fc4546f51170c5544 100644 (file)
--- a/src/ft.py
+++ b/src/ft.py
@@ -5,10 +5,7 @@ import disco
 from debug import LogEvent, INFO, WARN, ERROR
 import config
 import utils
-if utils.checkTwisted():
-       from twisted.xish.domish import Element
-else:
-       from tlib.domish import Element
+from tlib.xmlw import Element
 
 import random
 import sys
index 27b4d0ef1ce29afaaf09f2fdae2d2ea1567c6e0f..db430d8602b2e8f49e4911d681a1131377ccc57b 100644 (file)
@@ -3,10 +3,7 @@
 
 import utils
 from twisted.internet import reactor
-if(utils.checkTwisted()):
-       from twisted.xish.domish import Element
-else:
-       from tlib.domish import Element
+from tlib.xmlw import Element
 from debug import LogEvent, INFO, WARN, ERROR
 import disco
 import jabw
@@ -50,8 +47,6 @@ class BaseGroupchat:
 
                LogEvent(INFO, self.roomJID())
 
-               utils.mutilateMe(self)
-       
        def roomJID(self):
                """ Returns the room JID """
                return self.ID + "@" + config.jid
index c3ed1defd6420bbc95543f8901e159b68fc2f3de..1fbd4888460539e6bf43dcb4e2afea3a24591920 100644 (file)
@@ -4,10 +4,6 @@
 import utils
 import config
 import xdb
-if(utils.checkTwisted()):
-       from twisted.words.protocols.jabber import jid
-else:
-       from tlib.jabber import jid
 
 import shutil
 import os
@@ -68,7 +64,7 @@ def doSpoolPrepCheck():
        for file in os.listdir(pre):
                try:
                        file = xdb.unmangle(file).decode("utf-8")
-                       filej = jid.JID(file).full()
+                       filej = utils.jid(file).full()
                        if(file != filej):
                                file = xdb.mangle(file)
                                filej = xdb.mangle(filej)
index 0d0a1f51604e886ed0fee0b75bf1290b0b7aacea..08c4788fc9863e0fd754e4352213f68f42b66feb 100644 (file)
@@ -2,12 +2,7 @@
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
 import utils
-if(utils.checkTwisted()):
-       from twisted.xish.domish import Element
-       from twisted.words.protocols.jabber import jid
-else:
-       from tlib.domish import Element
-       from tlib.jabber import jid
+from tlib.xmlw import Element
 from debug import LogEvent, INFO, WARN, ERROR
 import disco
 
@@ -39,7 +34,7 @@ def sendPresence(pytrans, to, fro, show=None, status=None, priority=None, ptype=
        # Strip the resource off any presence subscribes (as per XMPP RFC 3921 Section 5.1.6)
        # Makes eJabberd behave :)
        if(ptype == "subscribe"):
-               (user,host,res) = jid.parse(to)
+               (user,host,res) = utils.jid(to)
                to = "%s@%s" % (user, host)
        
        el = Element((None, "presence"))
@@ -188,8 +183,8 @@ class JabberConnection:
                fro = el.getAttribute("from")
                to = el.getAttribute("to")
                try:
-                       froj = jid.JID(fro)
-                       toj = jid.JID(to)
+                       froj = utils.jid(fro)
+                       toj = utils.jid(to)
                except Exception, e:
                        LogEvent(WARN, self.jabberID)
                        return
@@ -250,9 +245,9 @@ class JabberConnection:
                """ Handles incoming presence packets """
                #LogEvent(INFO, self.jabberID)
                fro = el.getAttribute("from")
-               froj = jid.JID(fro)
+               froj = utils.jid(fro)
                to = el.getAttribute("to")
-               toj = jid.JID(to)
+               toj = utils.jid(to)
                
                # Grab the contents of the <presence/> packet
                ptype = el.getAttribute("type")
index 5319442ed15a23a429213432d1434b064ed0273b..7ff04672045f6b7d55209941506551cd0eaa2b5c 100644 (file)
@@ -3,10 +3,7 @@
 
 import utils
 from twisted.internet import task
-if utils.checkTwisted():
-       from twisted.xish.domish import Element
-else:
-       from tlib.domish import Element
+from tlib.xmlw import Element
 from tlib import msn, msnp2p
 from debug import LogEvent, INFO, WARN, ERROR
 import sha
@@ -157,7 +154,6 @@ class LegacyGroupchat(groupchat.BaseGroupchat):
                self.switchboardSession = None
                groupchat.BaseGroupchat.removeMe(self)
                LogEvent(INFO, self.roomJID())
-               utils.mutilateMe(self)
        
        def sendLegacyMessage(self, message, noerror):
                LogEvent(INFO, self.roomJID())
@@ -208,8 +204,6 @@ class LegacyConnection(msnw.MSNConnection):
                self.legacyList = None
                self.session = None
 
-               utils.mutilateMe(self)
-       
        def jidRes(self, resource):
                to = self.session.jabberID
                if resource:
index 3e8c8a0faf28092cc91c7fdd22f13f000f48626f..8ede7fe300cf6f48c70c2556fe86a5927e24c0b1 100644 (file)
@@ -2,10 +2,7 @@
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
 import utils
-if utils.checkTwisted():
-       from twisted.xish.domish import Element
-else:
-       from tlib.domish import Element
+from tlib.xmlw import Element
 from tlib import msn
 from legacy import glue
 from debug import LogEvent, INFO, WARN, ERROR
index 89471af38ea9e41ed97834bea065c7710c8fc032..9a23a9175bd93be6e3e225f842f4616edc691de9 100644 (file)
@@ -293,7 +293,7 @@ class SwitchboardSessionBase:
                        else:
                                chunks = int(math.ceil(len(message) / float(MAXMESSAGESIZE)))
                                chunk = 0
-                               guid = utils.random_guid()
+                               guid = msn.random_guid()
                                while chunk < chunks:
                                        offset = chunk * MAXMESSAGESIZE
                                        text = message[offset : offset + MAXMESSAGESIZE]
@@ -373,8 +373,6 @@ class GroupchatSwitchboardSession(SwitchboardSessionBase):
                self.groupchat = None
                self.ready = False
 
-               utils.mutilateMe(self)
-       
        def sbRequestAccepted(self, (host, port, key)):
                # Connect to the switchboard server
                LogEvent(INFO, self.ident)
@@ -465,8 +463,6 @@ class SwitchboardSession(SwitchboardSessionBase):
                        self.killTimer.cancel()
                self.killTimer = None
 
-               utils.mutilateMe(self)
-       
        def resetTimer(self):
                # Sets a count down timer to kill this switchboard session in 30 minutes
                self.killTimer.cancel()
@@ -565,8 +561,6 @@ class Notification(msn.NotificationClient):
                        self.factory.msncon = None
                self.factory = None
 
-               utils.mutilateMe(self)
-       
        def badConditions(self):
                if not (self.factory and self.factory.msncon and self.factory.msncon.session and self.factory.msncon.session.alive):
                        if not self.removed:
@@ -716,8 +710,6 @@ class Switchboard(msn.SwitchboardClient):
                        self.callid.cancel() # Cancel the invite fail message
                self.callid = None
 
-               utils.mutilateMe(self)
-       
        def badConditions(self):
                if not (self.switchboardSession and self.switchboardSession.msncon and self.switchboardSession.msncon.session and self.switchboardSession.msncon.session.alive):
                        if self.switchboardSession:
index 70d0733be5de52809e095975653a4e65ad7201c7..f23a19f61aa08c2389c83af657e02b7271c44334 100644 (file)
@@ -17,12 +17,7 @@ xmlconfig.reloadConfig()
 
 from twisted.internet import reactor, task
 from twisted.internet.defer import Deferred
-if utils.checkTwisted():
-       from twisted.words.protocols.jabber import component, jid
-       from twisted.xish.domish import Element
-else:
-       from tlib.jabber import component, jid
-       from tlib.domish import Element
+from tlib.xmlw import Element
 from debug import LogEvent, INFO, WARN, ERROR
 
 import xdb
@@ -160,7 +155,7 @@ class PyTransport(component.Service):
        def onMessage(self, el):
                fro = el.getAttribute("from")
                try:
-                       froj = jid.JID(fro)
+                       froj = utils.jid(fro)
                except Exception, e:
                        LogEvent(WARN, "", "Failed stringprep.")
                        return
@@ -181,8 +176,8 @@ class PyTransport(component.Service):
                fro = el.getAttribute("from")
                to = el.getAttribute("to")
                try:
-                       froj = jid.JID(fro)
-                       toj = jid.JID(to)
+                       froj = utils.jid(fro)
+                       toj = utils.jid(to)
                except Exception, e:
                        LogEvent(WARN, "", "Failed stringprep.")
                        return
index 56b5be6158f24b33477f65b97ffbdb723ea2dcb1..c2b180b5101de58ea6864ef1146b6b530ef7b68a 100644 (file)
@@ -2,12 +2,6 @@
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
 import utils
-if(utils.checkTwisted()):
-       from twisted.xish.domish import Element
-       from twisted.words.protocols.jabber import jid
-else:
-       from tlib.domish import Element
-       from tlib.jabber import jid
 from twisted.internet import reactor, task
 from debug import LogEvent, INFO, WARN, ERROR
 import jabw
@@ -33,7 +27,7 @@ class ConnectUsers:
                ID = el.getAttribute("id")
                ulang = utils.getLang(el)
 
-               if config.admins.count(jid.JID(to).userhost()) == 0:
+               if config.admins.count(utils.jid(to).userhost()) == 0:
                        self.pytrans.discovery.sendIqError(to=to, fro=config.jid, ID=ID, xmlns=disco.COMMANDS, etype="cancel", condition="not-authorized")
                        return
 
@@ -134,7 +128,7 @@ class AdHocCommands:
        def incomingIq(self, el):
                itype = el.getAttribute("type")
                fro = el.getAttribute("from")
-               froj = jid.JID(fro)
+               froj = utils.jid(fro)
                to = el.getAttribute("to")
                ID = el.getAttribute("id")
 
@@ -222,7 +216,7 @@ class VCardFactory:
        def incomingIq(self, el):
                itype = el.getAttribute("type")
                fro = el.getAttribute("from")
-               froj = jid.JID(fro)
+               froj = utils.jid(fro)
                to = el.getAttribute("to")
                ID = el.getAttribute("id")
                if(itype != "get" and itype != "error"):
@@ -282,7 +276,7 @@ class IqAvatarFactory:
        def incomingIq(self, el):
                itype = el.getAttribute("type")
                fro = el.getAttribute("from")
-               froj = jid.JID(fro)
+               froj = utils.jid(fro)
                to = el.getAttribute("to")
                ID = el.getAttribute("id")
 
index dffca0c7feb144836571ef4d4d8f00c5ae12fe08..9af2270e40d163e48c531962c59c655301fe4180 100644 (file)
@@ -2,12 +2,7 @@
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
 import utils
-if(utils.checkTwisted()):
-       from twisted.xish.domish import Element
-       from twisted.words.protocols.jabber import jid
-else:
-       from tlib.domish import Element
-       from tlib.jabber import jid
+from tlib.xmlw import Element
 from debug import LogEvent, INFO, WARN, ERROR
 import disco
 import session
@@ -43,11 +38,11 @@ class RegisterManager:
                        (blah1, password, blah3) = self.getRegInfo(jabberID)
                
                reginfo = legacy.formRegEntry(username, password)
-               self.pytrans.xdb.set(jid.JID(jabberID).userhost(), legacy.namespace, reginfo)
+               self.pytrans.xdb.set(utils.jid(jabberID).userhost(), legacy.namespace, reginfo)
        
        def getRegInfo(self, jabberID):
                LogEvent(INFO)
-               result = self.pytrans.xdb.request(jid.JID(jabberID).userhost(), legacy.namespace)
+               result = self.pytrans.xdb.request(utils.jid(jabberID).userhost(), legacy.namespace)
                if(result == None):
                        LogEvent(INFO, "", "Not registered!")
                        return None
@@ -103,7 +98,7 @@ class RegisterManager:
                ID = incoming.getAttribute("id")
                fro = incoming.getAttribute("from")
                LogEvent(INFO)
-               source = jid.JID(fro).userhost()
+               source = utils.jid(fro).userhost()
                ulang = utils.getLang(incoming)
                username = None
                password = None
index c1f2f370e5961baec0f329c049881ba071a09a3f..5b9615cfebf6bebb6ff2e38801e8a86e376fb993 100644 (file)
@@ -10,10 +10,6 @@ import config
 import lang
 import disco
 from debug import LogEvent, INFO, WARN, ERROR
-if utils.checkTwisted():
-       from twisted.words.protocols.jabber import jid
-else:
-       from tlib.jabber import jid
 
 
 
@@ -106,7 +102,6 @@ class Session(jabw.JabberConnection):
                        self.pytrans = None
                
                LogEvent(INFO, self.jabberID, "Removed!")
-               utils.mutilateMe(self)
        
        def doVCardUpdate(self):
                def vCardReceived(el):
@@ -147,7 +142,7 @@ class Session(jabw.JabberConnection):
        def updateNickname(self, nickname):
                self.nickname = nickname
                if not self.nickname:
-                       j = jid.JID(self.jabberID)
+                       j = utils.jid(self.jabberID)
                        self.nickname = j.user
                self.setStatus(self.show, self.status)
        
similarity index 100%
rename from src/tlib/msn.py
rename to src/tlib/msn/msn.py
similarity index 100%
rename from src/tlib/msnft.py
rename to src/tlib/msn/msnft.py
similarity index 100%
rename from src/tlib/msnp2p.py
rename to src/tlib/msn/msnp2p.py
diff --git a/src/tlib/msn/test_msn.py b/src/tlib/msn/test_msn.py
new file mode 100644 (file)
index 0000000..695b12b
--- /dev/null
@@ -0,0 +1,409 @@
+# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
+# Copyright (c) 2005      James Bunton
+# See LICENSE for details.
+
+"""
+Test cases for msn.
+"""
+
+# Twisted imports
+from twisted.protocols import loopback
+from twisted.internet.defer import Deferred
+from twisted.internet import reactor
+from twisted.trial import unittest
+
+# System imports
+import StringIO, sys
+
+import msn
+
+
+def printError(f):
+    print f
+
+class StringIOWithoutClosing(StringIO.StringIO):
+    disconnecting = 0
+    def close(self): pass
+    def loseConnection(self): pass
+
+class PassportTests(unittest.TestCase):
+
+    def setUp(self):
+        self.result = []
+        self.deferred = Deferred()
+        self.deferred.addCallback(lambda r: self.result.append(r))
+        self.deferred.addErrback(printError)
+
+    def testNexus(self):
+        protocol = msn.PassportNexus(self.deferred, 'https://foobar.com/somepage.quux')
+        headers = {
+            'Content-Length' : '0',
+            'Content-Type'   : 'text/html',
+            'PassportURLs'   : 'DARealm=Passport.Net,DALogin=login.myserver.com/,DAReg=reg.myserver.com'
+        }
+        transport = StringIOWithoutClosing()
+        protocol.makeConnection(transport)
+        protocol.dataReceived('HTTP/1.0 200 OK\r\n')
+        for (h,v) in headers.items(): protocol.dataReceived('%s: %s\r\n' % (h,v))
+        protocol.dataReceived('\r\n')
+        self.failUnless(self.result[0] == "https://login.myserver.com/")
+
+    def _doLoginTest(self, response, headers):
+        protocol = msn.PassportLogin(self.deferred,'foo@foo.com','testpass','https://foo.com/', 'a')
+        protocol.makeConnection(StringIOWithoutClosing())
+        protocol.dataReceived(response)
+        for (h,v) in headers.items(): protocol.dataReceived('%s: %s\r\n' % (h,v))
+        protocol.dataReceived('\r\n')
+
+    def testPassportLoginSuccess(self):
+        headers = {
+            'Content-Length'      : '0',
+            'Content-Type'        : 'text/html',
+            'Authentication-Info' : "Passport1.4 da-status=success,tname=MSPAuth," +
+                                    "tname=MSPProf,tname=MSPSec,from-PP='somekey'," +
+                                    "ru=http://messenger.msn.com"
+        }
+        self._doLoginTest('HTTP/1.1 200 OK\r\n', headers)
+        self.failUnless(self.result[0] == (msn.LOGIN_SUCCESS, 'somekey'))
+
+    def testPassportLoginFailure(self):
+        headers = {
+            'Content-Type'     : 'text/html',
+            'WWW-Authenticate' : 'Passport1.4 da-status=failed,' +
+                                 'srealm=Passport.NET,ts=-3,prompt,cburl=http://host.com,' +
+                                 'cbtxt=the%20error%20message'
+        }
+        self._doLoginTest('HTTP/1.1 401 Unauthorized\r\n', headers)
+        self.failUnless(self.result[0] == (msn.LOGIN_FAILURE, 'the error message'))
+
+    def testPassportLoginRedirect(self):
+        headers = {
+            'Content-Type'        : 'text/html',
+            'Authentication-Info' : 'Passport1.4 da-status=redir',
+            'Location'            : 'https://newlogin.host.com/'
+        }
+        self._doLoginTest('HTTP/1.1 302 Found\r\n', headers)
+        self.failUnless(self.result[0] == (msn.LOGIN_REDIRECT, 'https://newlogin.host.com/', 'a'))
+
+
+class DummySwitchboardClient(msn.SwitchboardClient):
+    def userTyping(self, message):
+        self.state = 'TYPING'
+
+    def gotSendRequest(self, fileName, fileSize, cookie, message):
+        if fileName == 'foobar.ext' and fileSize == 31337 and cookie == 1234: self.state = 'INVITATION'
+
+
+class DummyNotificationServer(msn.MSNEventBase):
+    def __init__(self):
+        pass
+
+class DummyNotificationClient(msn.NotificationClient):
+    def loggedIn(self, userHandle, verified):
+        if userHandle == 'foo@bar.com' and verified:
+            self.state = 'LOGIN'
+
+    def gotProfile(self, message):
+        self.state = 'PROFILE'
+
+    def gotContactStatus(self, code, userHandle, screenName):
+        if code == msn.STATUS_AWAY and userHandle == "foo@bar.com" and screenName == "Test Screen Name":
+            self.state = 'INITSTATUS'
+
+    def contactStatusChanged(self, code, userHandle, screenName):
+        if code == msn.STATUS_LUNCH and userHandle == "foo@bar.com" and screenName == "Test Name":
+            self.state = 'NEWSTATUS'
+
+    def contactOffline(self, userHandle):
+        if userHandle == "foo@bar.com": self.state = 'OFFLINE'
+
+    def statusChanged(self, code):
+        if code == msn.STATUS_HIDDEN: self.state = 'MYSTATUS'
+
+    def listSynchronized(self, *args):
+        self.state = 'GOTLIST'
+
+    def gotPhoneNumber(self, userHandle, phoneType, number):
+        msn.NotificationClient.gotPhoneNumber(self, userHandle, phoneType, number)
+        self.state = 'GOTPHONE'
+
+    def userRemovedMe(self, userHandle, listVersion):
+        msn.NotificationClient.userRemovedMe(self, userHandle, listVersion)
+        c = self.factory.contacts.getContact(userHandle)
+        if not c and self.factory.contacts.version == listVersion: self.state = 'USERREMOVEDME'
+
+    def userAddedMe(self, userGuid, userHandle, screenName):
+        msn.NotificationClient.userAddedMe(self, userGuid, userHandle, screenName)
+        c = self.factory.contacts.getContact(userHandle)
+        if c and (c.lists | msn.PENDING_LIST) and (self.factory.contacts.version == listVersion) and \
+           (screenName == 'Screen Name'):
+            self.state = 'USERADDEDME'
+
+    def gotSwitchboardInvitation(self, sessionID, host, port, key, userHandle, screenName):
+        if sessionID == 1234 and \
+           host == '192.168.1.1' and \
+           port == 1863 and \
+           key == '123.456' and \
+           userHandle == 'foo@foo.com' and \
+           screenName == 'Screen Name':
+            self.state = 'SBINVITED'
+
+    def msnAlertReceived(self, body, action, subscr):
+        print body, action, subscr
+        self.state == 'NOTIFICATION'
+
+
+class DummyTransport: pass
+class DummyPingNotificationClient(msn.NotificationClient):
+    def __init__(self):
+        msn.NotificationClient.__init__(self)
+        self.transport = DummyTransport()
+        self.transport.loseConnection = self.loseConnection
+
+    def sendLine(self, line):
+        if line.startswith("PNG") and self.good:
+            self.lineReceived("QNG 50")
+    
+    def loseConnection(self, *args):
+        self.state = 'DISCONNECTED'
+
+
+class NotificationTests(unittest.TestCase):
+    """ testing the various events in NotificationClient """
+    #skip = "Not ready"
+
+    def setUp(self):
+        self.client = DummyNotificationClient()
+        self.client.factory = msn.NotificationFactory()
+        self.client.state = 'START'
+
+    def tearDown(self):
+        self.client = None
+
+    def testLogin(self):
+        self.client.lineReceived('USR 1 OK foo@bar.com Test%20Screen%20Name 1 0')
+        self.failUnless((self.client.state == 'LOGIN'), 'Failed to detect successful login')
+
+    def testProfile(self):
+        m = 'MSG Hotmail Hotmail 353\r\nMIME-Version: 1.0\r\nContent-Type: text/x-msmsgsprofile; charset=UTF-8\r\n'
+        m += 'LoginTime: 1016941010\r\nEmailEnabled: 1\r\nMemberIdHigh: 40000\r\nMemberIdLow: -600000000\r\nlang_preference: 1033\r\n'
+        m += 'preferredEmail: foo@bar.com\r\ncountry: AU\r\nPostalCode: 90210\r\nGender: M\r\nKid: 0\r\nAge:\r\nsid: 400\r\n'
+        m += 'kv: 2\r\nMSPAuth: 2CACCBCCADMoV8ORoz64BVwmjtksIg!kmR!Rj5tBBqEaW9hc4YnPHSOQ$$\r\n\r\n'
+        map(self.client.lineReceived, m.split('\r\n')[:-1])
+        self.failUnless((self.client.state == 'PROFILE'), 'Failed to detect initial profile')
+
+    def testMSNAlert(self):
+        m = 'NOT %s\r\n'
+        m += '<NOTIFICATION ver="2" id="1342902633" siteid="199999999" siteurl="http://alerts.msn.com">\r\n'
+        m += '<TO pid="0x0006BFFD:0x8582C0FB" name="example@passport.com"/>\r\n'
+        m += '<MSG pri="1" id="1342902633">\r\n'
+        m += '<SUBSCR url="http://g.msn.com/3ALMSNTRACKING/199999999ToastChange?http://alerts.msn.com/Alerts/MyAlerts.aspx?strela=1"/>\r\n'
+        m += '<ACTION url="http://g.msn.com/3ALMSNTRACKING/199999999ToastAction?http://alerts.msn.com/Alerts/MyAlerts.aspx?strela=1"/>\r\n'
+        m += '<BODY lang="3076" icon="">\r\n'
+        m += '<TEXT>utf8-encoded text</TEXT></BODY></MSG>\r\n'
+        m += '</NOTIFICATION>\r\n'
+        m = m % len(m - len("NOT %s\r\n"))
+        map(self.client.lineReceived, m.split('\r\n')[:-1])
+        self.failUnless((self.client.state == 'NOTIFICATION'), 'Failed to detect MSN Alert message')
+
+    def testStatus(self):
+        t = [('ILN 1 AWY foo@bar.com Test%20Screen%20Name 0', 'INITSTATUS', 'Failed to detect initial status report'),
+             ('NLN LUN foo@bar.com Test%20Name 0', 'NEWSTATUS', 'Failed to detect contact status change'),
+             ('FLN foo@bar.com', 'OFFLINE', 'Failed to detect contact signing off'),
+             ('CHG 1 HDN 0', 'MYSTATUS', 'Failed to detect my status changing')]
+        for i in t:
+            self.client.lineReceived(i[0])
+            self.failUnless((self.client.state == i[1]), i[2])
+
+    def testListSync(self):
+        # currently this test does not take into account the fact
+        # that BPRs sent as part of the SYN reply may not be interpreted
+        # as such if they are for the last LST -- maybe I should
+        # factor this in later.
+        self.client.makeConnection(StringIOWithoutClosing())
+        msn.NotificationClient.loggedIn(self.client, 'foo@foo.com', 'foobar', 1)
+        lines = [
+            "SYN %s 100 1 1" % self.client.currentID,
+            "GTC A",
+            "BLP AL",
+            "LSG 0 Other%20Contacts 0",
+            "LST userHandle@email.com Some%20Name 11 0"
+        ]
+        map(self.client.lineReceived, lines)
+        contacts = self.client.factory.contacts
+        contact = contacts.getContact('userHandle@email.com')
+        self.failUnless(contacts.version == 100, "Invalid contact list version")
+        self.failUnless(contact.screenName == 'Some Name', "Invalid screen-name for user")
+        self.failUnless(contacts.groups == {0 : 'Other Contacts'}, "Did not get proper group list")
+        self.failUnless(contact.groups == [0] and contact.lists == 11, "Invalid contact list/group info")
+        self.failUnless(self.client.state == 'GOTLIST', "Failed to call list sync handler")
+
+    def testAsyncPhoneChange(self):
+        c = msn.MSNContact(userHandle='userHandle@email.com')
+        self.client.factory.contacts = msn.MSNContactList()
+        self.client.factory.contacts.addContact(c)
+        self.client.makeConnection(StringIOWithoutClosing())
+        self.client.lineReceived("BPR 101 userHandle@email.com PHH 123%20456")
+        c = self.client.factory.contacts.getContact('userHandle@email.com')
+        self.failUnless(self.client.state == 'GOTPHONE', "Did not fire phone change callback")
+        self.failUnless(c.homePhone == '123 456', "Did not update the contact's phone number")
+        self.failUnless(self.client.factory.contacts.version == 101, "Did not update list version")
+
+    def testLateBPR(self):
+        """
+        This test makes sure that if a BPR response that was meant
+        to be part of a SYN response (but came after the last LST)
+        is received, the correct contact is updated and all is well
+        """
+        self.client.makeConnection(StringIOWithoutClosing())
+        msn.NotificationClient.loggedIn(self.client, 'foo@foo.com', 'foo', 1)
+        lines = [
+            "SYN %s 100 1 1" % self.client.currentID,
+            "GTC A",
+            "BLP AL",
+            "LSG 0 Other%20Contacts 0",
+            "LST userHandle@email.com Some%20Name 11 0",
+            "BPR PHH 123%20456"
+        ]
+        map(self.client.lineReceived, lines)
+        contact = self.client.factory.contacts.getContact('userHandle@email.com')
+        self.failUnless(contact.homePhone == '123 456', "Did not update contact's phone number")
+
+    def testUserRemovedMe(self):
+        self.client.factory.contacts = msn.MSNContactList()
+        contact = msn.MSNContact(userHandle='foo@foo.com')
+        contact.addToList(msn.REVERSE_LIST)
+        self.client.factory.contacts.addContact(contact)
+        self.client.lineReceived("REM 0 RL 100 foo@foo.com")
+        self.failUnless(self.client.state == 'USERREMOVEDME', "Failed to remove user from reverse list")
+
+    def testUserAddedMe(self):
+        self.client.factory.contacts = msn.MSNContactList()
+        self.client.lineReceived("ADC 0 RL N=foo@foo.com F=Screen%20Name")
+        self.failUnless(self.client.state == 'USERADDEDME', "Failed to add user to reverse lise")
+
+    def testAsyncSwitchboardInvitation(self):
+        self.client.lineReceived("RNG 1234 192.168.1.1:1863 CKI 123.456 foo@foo.com Screen%20Name")
+        self.failUnless(self.client.state == "SBINVITED")
+
+class NotificationPingTests(unittest.TestCase):
+    """ tests pinging in the NotificationClient class """
+    def setUp(self):
+        msn.PINGSPEED = 0.1
+
+    def tearDown(self):
+        msn.PINGSPEED = 50.0
+
+    def testPingGood(self):
+        client = DummyPingNotificationClient()
+        client.good = True
+        client.state = 'CONNECTED'
+        client.pingCheckerStart()
+        for i in xrange(10):
+            reactor.iterate(msn.PINGSPEED)
+            self.failUnless((client.state == 'CONNECTED'), 'Disconnected with good pinging, %s sec.' % str(i*msn.PINGSPEED))
+        client.logOut()
+
+
+    def testPingBad(self):
+        client = DummyPingNotificationClient()
+        client.good = False
+        client.state = 'CONNECTED'
+        client.pingCheckerStart()
+        for i in xrange(7):
+            self.failUnless((client.state == 'CONNECTED'), 'Disconnected too early with bad pinging, %s sec.' % str(i*msn.PINGSPEED))
+            reactor.iterate(msn.PINGSPEED)
+        self.failUnless((client.state == 'DISCONNECTED'), 'Should have disconnected sooner with bad pinging.')
+        client.logOut()
+        
+
+class MessageHandlingTests(unittest.TestCase):
+    """ testing various message handling methods from SwichboardClient """
+    skip = "Not ready"
+
+    def setUp(self):
+        self.client = DummySwitchboardClient()
+        self.client.state = 'START'
+
+    def tearDown(self):
+        self.client = None
+
+    def testTypingCheck(self):
+        m = msn.MSNMessage()
+        m.setHeader('Content-Type', 'text/x-msmsgscontrol')
+        m.setHeader('TypingUser', 'foo@bar')
+        self.client.checkMessage(m)
+        self.failUnless((self.client.state == 'TYPING'), 'Failed to detect typing notification')
+
+    def testFileInvitation(self, lazyClient=False):
+        m = msn.MSNMessage()
+        m.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
+        m.message += 'Application-Name: File Transfer\r\n'
+        if not lazyClient:
+            m.message += 'Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n'
+        m.message += 'Invitation-Command: Invite\r\n'
+        m.message += 'Invitation-Cookie: 1234\r\n'
+        m.message += 'Application-File: foobar.ext\r\n'
+        m.message += 'Application-FileSize: 31337\r\n\r\n'
+        self.client.checkMessage(m)
+        self.failUnless((self.client.state == 'INVITATION'), 'Failed to detect file transfer invitation')
+
+    def testFileInvitationMissingGUID(self):
+        return self.testFileInvitation(True)
+
+    def testFileResponse(self):
+        d = Deferred()
+        d.addCallback(self.fileResponse)
+        self.client.cookies['iCookies'][1234] = (d, None)
+        m = msn.MSNMessage()
+        m.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
+        m.message += 'Invitation-Command: ACCEPT\r\n'
+        m.message += 'Invitation-Cookie: 1234\r\n\r\n'
+        self.client.checkMessage(m)
+        self.failUnless((self.client.state == 'RESPONSE'), 'Failed to detect file transfer response')
+
+    def testFileInfo(self):
+        d = Deferred()
+        d.addCallback(self.fileInfo)
+        self.client.cookies['external'][1234] = (d, None)
+        m = msn.MSNMessage()
+        m.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
+        m.message += 'Invitation-Command: ACCEPT\r\n'
+        m.message += 'Invitation-Cookie: 1234\r\n'
+        m.message += 'IP-Address: 192.168.0.1\r\n'
+        m.message += 'Port: 6891\r\n'
+        m.message += 'AuthCookie: 4321\r\n\r\n'
+        self.client.checkMessage(m)
+        self.failUnless((self.client.state == 'INFO'), 'Failed to detect file transfer info')
+
+    def fileResponse(self, (accept, cookie, info)):
+        if accept and cookie == 1234: self.client.state = 'RESPONSE'
+
+    def fileInfo(self, (accept, ip, port, aCookie, info)):
+        if accept and ip == '192.168.0.1' and port == 6891 and aCookie == 4321: self.client.state = 'INFO'
+
+
+class FileTransferTestCase(unittest.TestCase):
+    """ test FileSend against FileReceive """
+    skip = "Not ready"
+
+    def setUp(self):
+        self.input = StringIOWithoutClosing()
+        self.input.writelines(['a'] * 7000)
+        self.input.seek(0)
+        self.output = StringIOWithoutClosing()
+
+    def tearDown(self):
+        self.input = None
+        self.output = None
+
+    def testFileTransfer(self):
+        auth = 1234
+        sender = msn.FileSend(self.input)
+        sender.auth = auth
+        sender.fileSize = 7000
+        client = msn.FileReceive(auth, "foo@bar.com", self.output)
+        client.fileSize = 7000
+        loopback.loopback(sender, client)
+        self.failUnless((client.completed and sender.completed), "send failed to complete")
+        self.failUnless((self.input.getvalue() == self.output.getvalue()), "saved file does not match original")
diff --git a/src/tlib/xmlw.py b/src/tlib/xmlw.py
new file mode 100644 (file)
index 0000000..37deee9
--- /dev/null
@@ -0,0 +1,102 @@
+# Copyright 2004-2005 James Bunton <james@delx.cjb.net>
+# Licensed for distribution under the GPL version 2, check COPYING for details
+
+checkTwistedCached = None
+def checkTwisted():
+       """ Returns False if we're using an old version that needs tlib, otherwise returns True """
+       global checkTwistedCached
+       if checkTwistedCached == None:
+               import twisted.copyright
+               checkTwistedCached = (VersionNumber(twisted.copyright.version) >= VersionNumber("2.0.0"))
+       return checkTwistedCached
+
+class VersionNumber:
+       def __init__(self, vstring):
+               self.varray = [0]
+               index = 0 
+               flag = True
+               for c in vstring:
+                       if c == '.':
+                               self.varray.append(0)
+                               index += 1
+                               flag = True
+                       elif c.isdigit() and flag:
+                               self.varray[index] *= 10
+                               self.varray[index] += int(c)
+                       else:
+                               flag = False
+       
+       def __cmp__(self, other):
+               i = 0
+               while(True):
+                       if i == len(other.varray):
+                               if i < len(self.varray):
+                                       return 1
+                               else:
+                                       return 0
+                       if i == len(self.varray):
+                               if i < len(other.varray):
+                                       return -1
+                               else:
+                                       return 0
+
+                       if self.varray[i] > other.varray[i]:
+                               return 1
+                       elif self.varray[i] < other.varray[i]:
+                               return -1
+
+                       i += 1
+
+
+
+def parseText(text, beExtremelyLenient=False):
+       return TextParser(beExtremelyLenient).parseString(text)
+
+def parseFile(filename, beExtremelyLenient=False):
+       t = TextParser(beExtremelyLenient)
+       t.parseFile(filename)
+       return t.root
+
+
+try:
+       from twisted.words.xish.domish import SuxElementStream, Element
+except ImportError:
+       try:
+               if checkTwisted():
+                       from twisted.xish.domish import SuxElementStream, Element
+               else:
+                       from tlib.domish import SuxElementStream, Element
+       except ImportError:
+               print "Could not find the XML DOM. If you're using Twisted 2.x make sure you have twisted.words installed."
+               raise
+
+
+class TextParser:
+       """ Taken from http://xoomer.virgilio.it/dialtone/rsschannel.py """
+
+       def __init__(self, beExtremelyLenient=False):
+               self.root = None
+               self.beExtremelyLenient = beExtremelyLenient
+
+       def parseFile(self, filename):
+               return self.parseString(file(filename).read())
+
+       def parseString(self, data):
+               es = SuxElementStream()
+               es.beExtremelyLenient = self.beExtremelyLenient
+               es.DocumentStartEvent = self.docStart
+               es.DocumentEndEvent = self.docEnd
+               es.ElementEvent = self.element
+               es.parse(data)
+               return self.root
+
+       def docStart(self, e):
+               self.root = e
+
+       def docEnd(self):
+               pass
+
+       def element(self, e):
+               self.root.addChild(e)
+
+
index 4f768e38830ab6fd8b303d324ebbb734c219ea3d..d0184c900b3aa8e8906b310e70a54955ef20c7f4 100644 (file)
@@ -1,32 +1,17 @@
 # Copyright 2004-2005 James Bunton <james@delx.cjb.net>
 # Licensed for distribution under the GPL version 2, check COPYING for details
 
-def mutilateMe(me):
-       """ Mutilates a class :) """
-#      for key in dir(me):
-#              exec "me." + key + " = None"
 
-def getLang(el):
-       return el.getAttribute((u'http://www.w3.org/XML/1998/namespace', u'lang'))
 
-
-import random
-def random_guid():
-       format = "{%4X%4X-%4X-%4X-%4X-%4X%4X%4X}"
-       data = []
-       for x in xrange(8):
-               data.append(random.random() * 0xAAFF + 0x1111)
-       data = tuple(data)
-       
-       return format % data
+try:
+       from twisted.words.protocols.jabber import intern as jid
+except:
+       from tlib.jabber.jid import intern as jid
 
 
-import base64
-def b64enc(s):
-       return base64.encodestring(s).replace('\n', '')
+def getLang(el):
+       return el.getAttribute((u'http://www.w3.org/XML/1998/namespace', u'lang'))
 
-def b64dec(s):
-       return base64.decodestring(s)
 
 try:
        import Image
@@ -163,21 +148,3 @@ class TextParser:
                self.root.addChild(e)
 
 
-
-class RollingStack:
-       def __init__(self, size):
-               self.lst = []
-               self.size = size
-       
-       def push(self, data):
-               self.lst.append(str(data))
-               if len(self.lst) > self.size:
-                       self.lst.remove(self.lst[0])
-       
-       def grabAll(self):
-               return "".join(self.lst)
-       
-       def flush(self):
-               self.lst = []
-
-