]> code.delx.au - pymsnt/blobdiff - src/tlib/msn/msn.py
Sensible messages are now sent to both participants if a file is rejected for being...
[pymsnt] / src / tlib / msn / msn.py
index 0970c8da15eec1afc7738b53d07b103cb34e91ae..bcc9aaa04773475e77b0d9a94b65c23d6719f33e 100644 (file)
@@ -100,15 +100,18 @@ import msnp11chl
 from twisted.internet import reactor, task
 from twisted.internet.defer import Deferred
 from twisted.internet.protocol import ClientFactory
-from twisted.internet.ssl import ClientContextFactory
+try:
+    from twisted.internet.ssl import ClientContextFactory
+except ImportError:
+    print "You must install pycrypto and pyopenssl."
+    raise
 from twisted.python import failure, log
-from twisted.xish.domish import unescapeFromXml
 
 # Compat stuff
 from tlib import xmlw
 
 # System imports
-import types, operator, os, sys, base64, random, struct, random, sha, base64, StringIO, array, codecs
+import types, operator, os, sys, base64, random, struct, random, sha, base64, StringIO, array, codecs, binascii
 from urllib import quote, unquote
 
 
@@ -118,7 +121,6 @@ MSN_MAX_MESSAGE      = 1664               # max message length
 MSN_CVR_STR          = "0x040c winnt 5.1 i386 MSNMSGR 7.0.0777 msmsgs"
 MSN_AVATAR_GUID      = "{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}"
 MSN_MSNFTP_GUID      = "{5D3E02AB-6190-11D3-BBBB-00C04F795683}"
-P2PSEQ = [-3, -2, 0, -1, 1, 2, 3, 4, 5, 6, 7, 8]
 
 # auth constants
 LOGIN_SUCCESS  = 1
@@ -162,6 +164,15 @@ if DEBUGALL:
     MESSAGEDEBUG = True
     MSNP2PDEBUG = True
 
+
+P2PSEQ = [-3, -2, 0, -1, 1, 2, 3, 4, 5, 6, 7, 8]
+def p2pseq(n):
+    if n > 5:
+        return n - 3
+    else:
+        return P2PSEQ[n]
+
+
 def getVal(inp):
     return inp.split('=')[1]
 
@@ -422,7 +433,7 @@ class MSNMessage:
         self.screenName = screenName
         self.specialMessage = specialMessage
         self.message = message
-        self.headers = {'MIME-Version' : '1.0', 'Content-Type' : 'text/plain'}
+        self.headers = {'MIME-Version' : '1.0', 'Content-Type' : 'text/plain; charset=UTF-8'}
         self.length = length
         self.readPos = 0
 
@@ -433,6 +444,11 @@ class MSNMessage:
         """
         return reduce(operator.add, [len(x[0]) + len(x[1]) + 4  for x in self.headers.items()]) + len(self.message) + 2
 
+    def delHeader(self, header):
+        """ delete the desired header """
+        if self.headers.has_key(header):
+            del self.headers[header]
+
     def setHeader(self, header, value):
         """ set the desired header """
         self.headers[header] = value
@@ -826,9 +842,13 @@ class MSNEventBase(LineReceiver):
         m = self.currentMessage
         self.currentMessage = None
         if MESSAGEDEBUG: log.msg(m.message)
-        if not self.checkMessage(m):
+        try:
+            if not self.checkMessage(m):
+                self.setLineMode(extra)
+                return
+        except Exception, e:
             self.setLineMode(extra)
-            return
+            raise
         self.gotMessage(m)
         self.setLineMode(extra)
 
@@ -1032,16 +1052,17 @@ class NotificationClient(MSNEventBase):
         self.gotMSNAlert(bodytext, actionurl, subscrurl)
 
     def _gotUBX(self, message):
+        msnContact = self.factory.contacts.getContact(message.userHandle)
+        if not msnContact: return
         lm = message.message.lower()
         p1 = lm.find("<psm>") + 5
         p2 = lm.find("</psm>")
         if p1 >= 0 and p2 >= 0:
-            personal = unescapeFromXml(message.message[p1:p2])
-            msnContact = self.factory.contacts.getContact(message.userHandle)
-            if not msnContact: return
+            personal = xmlw.unescapeFromXml(message.message[p1:p2])
             msnContact.personal = personal
             self.contactPersonalChanged(message.userHandle, personal)
         else:
+            msnContact.personal = ''
             self.contactPersonalChanged(message.userHandle, '')
 
     def checkMessage(self, message):
@@ -1135,7 +1156,7 @@ class NotificationClient(MSNEventBase):
             if MSNP2PDEBUG: log.msg("Updated MSNObject received!" + msnobjStr)
             msnContact.msnobj = msnobj
             msnContact.msnobjGot = False
-            self.contactAvatarChanged(msnContact.userHandle, msnContact.msnobj.sha1d)
+            self.contactAvatarChanged(msnContact.userHandle, binascii.hexlify(b64dec(msnContact.msnobj.sha1d)))
 
     def handle_CHL(self, params):
         checkParamLen(len(params), 2, 'CHL')
@@ -1355,7 +1376,7 @@ class NotificationClient(MSNEventBase):
             self.currentMessage = MSNMessage(length=messageLen, userHandle=params[0], screenName="UBX", specialMessage=True)
             self.setRawMode()
         else:
-            self.contactPersonalChanged(params[0], '')
+            self._gotUBX(MSNMessage(userHandle=params[0]))
 
     def handle_UUX(self, params):
         checkParamLen(len(params), 2, 'UUX')
@@ -1437,7 +1458,7 @@ class NotificationClient(MSNEventBase):
         contact.
 
         @param userHandle: contact who's msnobj has been changed
-        @param hash: sha1 hash of their avatar
+        @param hash: sha1 hash of their avatar as hex string
         """
 
     def statusChanged(self, statusCode):
@@ -2082,7 +2103,7 @@ class SwitchboardClient(MSNEventBase):
             cookie = info['Invitation-Cookie']
             filename = info['Application-File']
             filesize = int(info['Application-FileSize'])
-            connectivity = (info.get('Connectivity').lower() == 'y')
+            connectivity = (info.get('Connectivity', 'n').lower() == 'y')
         except KeyError:
             log.msg('Received munged file transfer request ... ignoring.')
             return 0
@@ -2095,14 +2116,16 @@ class SwitchboardClient(MSNEventBase):
         if not message.getHeader("P2P-Dest") == self.userHandle: return
         packet = message.message
         binaryFields = BinaryFields(packet=packet)
-        if binaryFields[0] != 0:
+        if binaryFields[5] == BinaryFields.BYEGOT:
+            pass # Ignore the ACKs to SLP messages
+        elif binaryFields[0] != 0:
             slpLink = self.slpLinks.get(binaryFields[0])
             if not slpLink:
                 # Link has been killed. Ignore
                 return
             if slpLink.remoteUser == message.userHandle:
                 slpLink.handlePacket(packet)
-        if binaryFields[5] == BinaryFields.ACK or binaryFields[5] == BinaryFields.BYEGOT:
+        elif binaryFields[5] == BinaryFields.ACK:
             pass # Ignore the ACKs to SLP messages
         else:
             slpMessage = MSNSLPMessage(packet)
@@ -2314,11 +2337,13 @@ class SwitchboardClient(MSNEventBase):
         else: id, d = self._createIDMapping()
         if message.length == 0: message.length = message._calcMessageLen()
         self.sendLine("MSG %s %s %s" % (id, message.ack, message.length))
-        # apparently order matters with at least MIME-Version and Content-Type
-        self.sendLine('MIME-Version: %s' % message.getHeader('MIME-Version'))
-        self.sendLine('Content-Type: %s' % message.getHeader('Content-Type'))
+        # Apparently order matters with these
+        orderMatters = ("MIME-Version", "Content-Type", "Message-ID")
+        for header in orderMatters:
+            if message.hasHeader(header):
+                self.sendLine("%s: %s" % (header, message.getHeader(header)))
         # send the rest of the headers
-        for header in [h for h in message.headers.items() if h[0].lower() not in ('mime-version','content-type')]:
+        for header in [h for h in message.headers.items() if h[0] not in orderMatters]:
             self.sendLine("%s: %s" % (header[0], header[1]))
         self.transport.write("\r\n")
         self.transport.write(message.message)
@@ -2493,7 +2518,7 @@ class FileContext:
         data = data[:-1] # Uck, weird, but it works
         data += utf16net(self.filename)
         data = ljust(data, 570, '\0')
-        data += struct.pack("<L", 0xFFFFFFFF)
+        data += struct.pack("<L", 0xFFFFFFFFL)
         data = ljust(data, 638, '\0')
         return data
 
@@ -2662,7 +2687,7 @@ class SeqID:
         self.pos = -1
 
     def get(self):
-        return P2PSEQ[self.pos] + self.baseID
+        return p2pseq(self.pos) + self.baseID
     
     def next(self):
         self.pos += 1
@@ -2695,7 +2720,9 @@ class SLPLink:
         self.seqID = SeqID()
 
     def killLink(self):
+        if MSNP2PDEBUG: log.msg("killLink")
         def kill():
+            if MSNP2PDEBUG: log.msg("killLink - kill()")
             if not self.switchboard: return
             del self.switchboard.slpLinks[self.sessionID]
             self.switchboard = None
@@ -2704,8 +2731,7 @@ class SLPLink:
         reactor.callLater(0, kill)
 
     def warn(self, text):
-        if MSNP2PDEBUG:
-            log.msg("Warning in transfer: %s %s" % (self, text))
+        log.msg("Warning in transfer: %s %s" % (self, text))
 
     def sendP2PACK(self, ackHeaders):
         binaryFields = BinaryFields()
@@ -2771,21 +2797,19 @@ class SLPLink_Send(SLPLink):
     def write(self, data):
         if MSNP2PDEBUG: log.msg("write")
         i = 0
+        data = self.data + data
+        self.data = ""
         length = len(data)
         while i < length:
             if i + 1202 < length:
                 self._writeChunk(data[i:i+1202])
                 i += 1202
             else:
-                self.data += data[i:]
-                if len(self.data) >= 1202:
-                    data = self.data
-                    self.data = ""
-                    self.write(data)
+                self.data = data[i:]
                 return
 
     def _writeChunk(self, chunk):
-        log.msg("writing chunk")
+        if MSNP2PDEBUG: log.msg("writing chunk")
         binaryFields = BinaryFields()
         binaryFields[0] = self.sessionID
         if self.offset == 0:
@@ -2841,7 +2865,7 @@ class SLPLink_FileSend(SLPLink_Send):
         else:
             if slpMessage.status == "603":
                 self.acceptDeferred.callback((False,))
-            # SLPLink is over due to decline, error or BYE
+            if MSNP2PDEBUG: log.msg("SLPLink is over due to decline, error or BYE")
             self.killLink()
 
     def wait_data_ack(self, packet):
@@ -2870,7 +2894,8 @@ class SLPLink_AvatarSend(SLPLink_Send):
         self.handlePacket = lambda packet: None
 
     def handleSLPMessage(self, slpMessage):
-        self.killLink() # BYE or error
+        if MSNP2PDEBUG: log.msg("BYE or error")
+        self.killLink()
 
     def close(self):
         SLPLink_Send.close(self)
@@ -2894,8 +2919,9 @@ class SLPLink_Receive(SLPLink):
         if binaryFields[4] != 4:
             self.warn("field4," + str(binaryFields[4]))
             return
-        if binaryFields[9] != 1:
-            self.warn("field9," + str(binaryFields[9]))
+        # Just ignore the footer
+        #if binaryFields[9] != 1:
+        #    self.warn("field9," + str(binaryFields[9]))
         #   return
 
         self.sendP2PACK(binaryFields)
@@ -2909,8 +2935,9 @@ class SLPLink_Receive(SLPLink):
         if binaryFields[5] != self.dataFlag:
             self.warn("field5," + str(binaryFields[5]))
             return
-        if binaryFields[9] != 1:
-            self.warn("field9," + str(binaryFields[9]))
+        # Just ignore the footer
+        #if binaryFields[9] != 1:
+        #    self.warn("field9," + str(binaryFields[9]))
         #   return
         offset = binaryFields[2]
         total = binaryFields[3]
@@ -2928,7 +2955,7 @@ class SLPLink_Receive(SLPLink):
         
         self.pos += length
 
-        self.consumer.write(data)
+        self.consumer.write(str(data))
 
         if self.pos == total:
             self.sendP2PACK(binaryFields)
@@ -2949,12 +2976,15 @@ class SLPLink_FileReceive(SLPLink_Receive, FileReceive):
 
     def reject(self):
         # Send a 603 decline
+        if not self.switchboard: return
         self.sendSLPMessage("603", "application/x-msnmsgr-sessionreqbody", {"SessionID":self.sessionID}, branch=self.initialBranch)
         self.killLink()
 
     def accept(self, consumer):
         FileReceive.accept(self, consumer)
+        if not self.switchboard: return
         self.sendSLPMessage("200", "application/x-msnmsgr-sessionreqbody", {"SessionID":self.sessionID}, branch=self.initialBranch)
+        self.handlePacket = self.wait_data # Moved here because sometimes the second INVITE seems to be skipped
 
     def handleSLPMessage(self, slpMessage):
         if slpMessage.method == "INVITE": # The second invite
@@ -2962,9 +2992,10 @@ class SLPLink_FileReceive(SLPLink_Receive, FileReceive):
                     "Listening"   : "false",\
                     "Hashed-Nonce": "{00000000-0000-0000-0000-000000000000}"}
             self.sendSLPMessage("200", "application/x-msnmsgr-transrespbody", data, branch=slpMessage.branch)
-            self.handlePacket = self.wait_data
+#            self.handlePacket = self.wait_data # Moved up
         else:
-            self.killLink() # It's either a BYE or an error
+            if MSNP2PDEBUG: log.msg("It's either a BYE or an error")
+            self.killLink()
             # FIXME, do some error handling if it was an error
 
     def doFinished(self):
@@ -2987,7 +3018,7 @@ class SLPLink_AvatarReceive(SLPLink_Receive):
         if slpMessage.status == "200":
             self.handlePacket = self.wait_dataprep
         else:
-            # SLPLink is over due to error or BYE
+            if MSNP2PDEBUG: log.msg("SLPLink is over due to error or BYE")
             self.killLink()
 
     def doFinished(self):