# Time-stamp: <2002-03-20 19:31:45 crabbkw>
# Code and design by Casey Crabb (crabbkw@rose-hulman.edu)
# This code is licensed under the BSD license.
# See the LICENSE file for details
#
# Copyright Casey Crabb (crabbkw@rose-hulman.edu) July 2001
#

# from __future__ import nested_scopes
from JabberTags import *
from threading import *
from JabberHandler import *
from IHTTPServer import IMComHTTPServer, FileGet
import string
import socket
import os
import time
import sha
import sys
import operator
import traceback

errorCodes = {"0":"Unknown or unspecified error",
              "400":"Bad Request",
              "401":"Unauthorized",
              "402":"Payment Required",
              "403":"Forbidden",
              "404":"Not Found",
              "405":"Not Allowed",
              "406":"Not Acceptable",
              "407":"Registration Required",
              "408":"Request Timeout",
              "409":"Username Not Available",
              "500":"Internal Server Error",
              "501":"Not Implemented",
              "502":"Remote Server Error",
              "503":"Service Unavailable",
              "504":"Remove Server Timeout"}

def logDebug(s):
    print s.encode(sys.getdefaultencoding(),'replace')

class IMCom(Thread):
    def __init__(self, listener):
        Thread.__init__(self)
        self.jch = None
        self.idhash = {}
        self.nickhash = {}
        self.jidhash =  {}
        self.agenthash = {}
        self.grouphash = {} #group names are the keys, lists of JIDs the data
        self.gjidhash = {} #jids are keys, list of groups are the data
        self.reshash = {} #jids are keys, (last_resource, high_priority, {res1:(stat,show,pri),res2:(stat,show,pri)}) is data,
        self.filehash = {}
        self.preshash = {} # jids are keys, (available, show, status) are values
        self.conferences = {} #jids are keys, (conference-nick-name, {nick:(stat,show),nick2:(stat,show)})
        self.confnick = {} #nicks are keys, values are jids of conference
        self.id = 0
        self.mainSocket = None
        self.listener = listener
        self.debug = 0
        self.setDaemon(1)
        self.running = 1
        self.keepalive = "\n"
        self.status = "online"
        self.sendport = 8000
        self.ssl = 0
        self.currentStatus = None
        self.currentStatusReason = None
        self.priority = None
        # Callbacks
        self.cbHandleDisconnected       = self.dummyWrapper
        self.cbHandleAdminWho           = self.dummyWrapper
        self.cbHandlePresenceUpdate     = self.dummyWrapper
        self.cbHandleConferenceMessage  = self.dummyWrapper
        self.cbHandleConferencePresence = self.dummyWrapper
        self.cbHandleMessageReceive     = self.dummyWrapper
        self.cbHandleMessageError       = self.dummyWrapper
        self.cbHandleIQError            = self.dummyWrapper
        self.cbHandleInfoError          = self.dummyWrapper
        self.cbHandleFileReceive        = self.dummyWrapper
        self.cbHandleFileReceived       = self.dummyWrapper
        self.cbHandleFileErrorReceived  = self.dummyWrapper
        self.cbHandleAgentList          = self.dummyWrapper
        self.cbHandleAgentRegister      = self.dummyWrapper
        self.cbHandleAgentRegistered    = self.dummyWrapper
        self.cbHandleSubscribe          = self.dummyWrapper
        self.cbHandleSubscribed         = self.dummyWrapper
        self.cbHandleUnsubscribed       = self.dummyWrapper
        self.cbHandleUnsubscribe        = self.dummyWrapper
        self.cbHandleRosterUpdateCheck  = self.dummyWrapper
        self.cbHandleRosterUpdate       = self.dummyWrapper
        self.cbHandleVCardSubmit        = self.dummyWrapper
        self.cbHandleVCard              = self.dummyWrapper
        self.cbHandleNoVCard            = self.dummyWrapper
        self.cbHandleLogin              = self.dummyWrapper
        self.cbHandleGrabRoster         = self.dummyWrapper

    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # -------------------------------------------------------------------------
    #  Utility Functions
    # -------------------------------------------------------------------------
    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    def dummyWrapper(self, *args):
        if(self.debug):
            text = "Call to dummyWrapper with args: [ "
            for a in args:
                text = text + str(a) + ", "
            print text + " ] "
        pass

    # Fix text so it is proper XML
    def normalize(self, str):
        str = string.replace( str, "&", "&amp;" );
        str = string.replace( str, '"', "&quot;");
        str = string.replace( str, "'", "&apos;");
        str = string.replace( str, "<", "&lt;");
        str = string.replace( str, ">", "&gt;");
        return str;


    # Send keepalive packets every 60 seconds
    def run(self):
        while(self.running):
            time.sleep(60)
            #self.listener.handleIdleTick()
            #self.sendPacket(self.keepalive)
            if(self.running):
                self.sendPacket("\n")

    def setDebug(self, debug):
        "Sets the value of the debug variable for both the XML parser \
        and the IMCom handler."
        self.debug = debug
        self.jch.debug = debug

    def updateHash(self, hash, oldkey, newkey, value):
        keys = hash.keys()
        for item in keys:
            if(item == oldkey):
                del hash[item]
        hash[newkey] = value










    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # -------------------------------------------------------------------------
    #  API Functions for the profile,login procedure
    # -------------------------------------------------------------------------
    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    def changeProfile(self, host, port, user, password, resource, ssl, priority, encoding):
        "Sets up the current profile to use. This function MUST be called \
        before connect() is called."
        self.host = host
        self.port = port
        self.user = user
        self.password = password
        self.resource = resource
        self.priority = priority
        self.encoding = encoding
        if(ssl):
            self.ssl = 1
        else:
            self.ssl = 0
        try:
            if(self.mainSocket != None):
                # print "Disconnecting"
                self.disconnect()
                self.jidhash = {}
                self.nickhash = {}
                self.idhash = {}
                self.filehash = {}
                self.preshash = {}
                self.mainSocket = None
            # print "Connecting..."
            self.connect()
        except:
            # print "IMCom.changeProfile failed"
            traceback.print_exc()
            a,b,c = sys.exc_info()
            print a
            print b

    def connect(self):
        "Actually connects to the jabber server and initiates communications. \
        It is important to call changeProfile() before calling this \
        function. This function will automatically start the parser thread \
        and the keep-alive thread."

        tempsocket = socket.socket(socket.AF_INET,
                                   socket.SOCK_STREAM)
        tempsocket.connect((self.host,self.port))
        if(self.ssl!=0):
            self.mainSocket = socket.ssl(tempsocket, None, None)
            self.tempsocket = tempsocket
        else:
            self.mainSocket = tempsocket
        self.jch = JabberContentHandler(self, self.ssl)
        self.jch.debug = self.debug
        self.keepalive = "\n"
        self.status = "unknown"
        self.sendPacket('<?xml version="1.0" encoding="UTF-8" ?>')
        self.sendPacket("<stream:stream to='"+self.host+
                        "' xmlns='jabber:client' "+
                        "xmlns:stream='http://etherx.jabber.org/streams'>")
        self.jch.setDaemon(1)
        self.jch.start()
        self.currentStatus = "online"
        self.currentStatusReason = "online"
        self.running = 1
        self.start()

    def disconnect(self, mangleFunctions=1):
        "Disconnect sends the server a \"We're going offline\" message, \
        followed by the stream termination sequence, then closes the socket."
        self.sendOffline()
        self.sendPacket("</stream:stream>")
        self.closeSocket(mangleFunctions)

    def closeSocket(self, mangleFunctions=1):
        "This function should not be called by the user, it forcefully \
        terminates the connection which may cause the jabber server to \
        become confused about the user's presence. It is here to handle \
        errors in the socket connection. It is called by the xml parser \
        thread."
        if(mangleFunctions == 1):
            self.mangleFunctions()
        self.running = 0
        self.jch.running = 0
        if(self.ssl != 0):
            self.tempsocket.close()
        else:
            self.mainSocket.close()
        self.mainSocket = None

    def nullFunction(self, *rest):
        pass

    def mangleFunctions(self):
        "A user should never call this function. The purpose of this \
        function is to mangle function which may cause problems when \
        the connection has already been closed"
        if(self.debug):
            logDebug("Mangling functions")
        self.handleDisconnected = self.nullFunction
        self.sendPacket = self.nullFunction
        self.closeSocket = self.nullFunction
        self.disconnect = self.nullFunction

    def getRoster(self):
        self.id = self.id + 1
        id = "getroster%d"%self.id
        tosend = "<iq type='get' id='"+id+"'><query"\
                 " xmlns='jabber:iq:roster'/></iq>"
        self.sendPacket(tosend)
        self.idhash[id] = "getroster"



    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # -------------------------------------------------------------------------
    #  API Functions for sending requests to the server
    # -------------------------------------------------------------------------
    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    def sendPacket(self, text):
        "Sends a packet of text to the server"
        if(self.mainSocket == None or self.running == 0):
            self.running=0
            self.jch.running = 0
            self.mangleFunctions()
            return
        try:
            text = text.encode("utf-8","replace")
            if(self.debug and text != "\n"):
                logDebug("Sending: " + text)
            if(self.ssl != 0):
                self.mainSocket.write(text)
            else:
                self.mainSocket.send(text)
        except:
            logDebug("Error sending: " + text)
            i = 0
            traceback.print_exc()
            a,b,c = sys.exc_info()
            print a
            print b
            self.running=0
            self.jch.running = 0
            self.handleDisconnected()


    def sendMultiMessage(self, tolist, body, thread):
        "Send a message to each jid in tolist with body and thread \n\
        id. If thread is None it will not use a thread id."
        for to in tolist:
            self.sendMessage(to, body, thread)

    def sendMessage(self, to, body, thread, resource = None):
        "Send a message to someone with body and thread id. \n\
        if thread is None it will not use a thread id."
        body = self.normalize(body)
        packet = None
        if resource == None and self.reshash.has_key(to) and string.find(to,'/') == -1:
            to = to + '/' + self.reshash[to][0]
        if resource != None and string.find(to,'/') == -1:
            to = to + '/' + resource
        if(self.conferences.has_key(to)):
            packet = "<message to='" + to + "' type='groupchat'>"\
                     "<body>"+body+"</body></message>"
            self.sendPacket(packet)
            return
        if(self.confnick.has_key(to)):
            packet = "<message to='" + self.confnick[to] + "' type='groupchat'>"\
                     "<body>"+body+"</body></message>"
            self.sendPacket(packet)
            return
        if(thread):
            packet = "<message to='" + to + "' type='chat'>"\
                     "<body>"+body+"</body><thread>" +\
                     thread + "</thread></message>"
        else:
            packet = "<message to='" + to + "'><body>"+body+"</body></message>"
        self.sendPacket(packet)

    def sendFile(self, to, fname):
        "Send a file to a particular jid. The jid MUST include the \
        resource string."
        self.sendport = self.sendport + 1
        ihs = IMComHTTPServer(os.path.dirname(fname),self.sendport)
        ihs.setDaemon(1)
        ihs.start()
        hostname = socket.gethostbyname_ex(socket.gethostname())[2][0]
        self.id = self.id + 1
        id = "file_id%d"%self.id
        packet = "<iq type='set' id='"+id+"' to='"+to+"'>" + \
                 "<query xmlns='jabber:iq:oob'>" + \
                 "<url>http://"+hostname+":%d"%self.sendport
        packet = packet + "/"+os.path.basename(fname)+"</url>"+"</query></iq>"
        self.sendPacket(packet)

    def getFile(self, jid):
        "Will accept a transfer initiated by a user. JID must be the \
        fully qualified JID of the user."
        if(self.filehash.has_key(jid)):
            file, id = self.filehash[jid].pop(0)
            fg = FileGet(jid,file,id,self)
            fg.start()
            # del self.filehash[jid]
        else:
            self.cbHandleFileErrorReceived(jid, "none", "Input Error",
                                           "Not expecting a file " \
                                           "from user specified.")
        return

    def sendSubscribed(self, to):
        "Send a subscription authorization to someone"
        packet = "<presence to='" +to+"' type='subscribed'/>"
        self.sendPacket(packet)

    def sendSubscribe(self, to):
        "Send a subscription request to someone"
        packet = "<presence to='" + to + "' type='subscribe'/>"
        self.sendPacket(packet)

    def sendUnsubscribed(self, to):
        "Send an unsubscribed authorization to someone"
        packet = "<presence to='" + to + "' type='unsubscribed'/>"
        self.sendPacket(packet)

    def sendUnsubscribe(self, to):
        "Send an unsubscription request to someone"
        packet = "<presence to='" + to + "' type='unsubscribe'/>"
        self.sendPacket(packet)

    def sendPriority(self, priority):
        self.priority = priority
        tosend = "<presence type='available'>"\
                 "<show>"+self.currentStatus+"</show>"\
                 "<priority>"+str(priority)+"</priority>"
        if(self.currentStatusReason != None or self.currentStatusReason != ""):
            tosend = tosend + "<status>"+self.currentStatusReason+"</status>"
        tosend = tosend + "</presence>"
        self.sendPacket(tosend)

    def sendOnline(self, reason="online"):
        "Send an online presence event"
        priority = "%d"%(self.priority)
        self.currentStatus = "online"
        self.currentStatusReason = reason
        if(reason == None):
            tosend = "<presence type='available'>"\
                     "<show>online</show>"\
                     "<priority>"+priority+"</priority>"\
                     "</presence>"
        else:
            tosend = "<presence type='available'>"\
                     "<show>online</show><status>"+reason+"</status>"\
                     "<priority>"+priority+"</priority>"\
                     "</presence>"
        self.keepalive = tosend
        self.status = "online"
        self.sendPacket(tosend)

    def sendOffline(self):
        "Send and offline presence event"
        tosend = "<presence type='unavailable'/>"
        self.keepalive = tosend
        self.status = "disconnected"
        self.sendPacket(tosend)

    def sendChat(self):
        "Send a chat presence event"
        priority = "%d"%(self.priority)
        self.currentStatus = "chat"
        self.currentStatusReason = None
        tosend = "<presence>"\
                 "<show>chat</show><status>online</status>"\
                 "<priority>"+priority+"</priority>"\
                 "</presence>"
        self.keepalive = tosend
        self.status = "chat"
        self.sendPacket(tosend)

    def sendAway(self, reason):
        "Send an away presence event with the given reason. Reason CANNOT be \
        None"
        priority = "%d"%(self.priority-2)
        self.currentStatus = "away"
        self.currentStatusReason = reason
        tosend = "<presence>"\
                 "<show>away</show><status>"+self.normalize(reason)+\
                 "</status>"\
                 "<priority>"+priority+"</priority>"\
                 "</presence>"
        self.keepalive = tosend
        self.status = "away"
        self.sendPacket(tosend)

    def sendXA(self, reason):
        "Send an xa presence event with the given reason. Reason CANNOT be \
        None"
        priority = "%d"%(self.priority-4)
        self.currentStatus = "xa"
        self.currentStatusReason = reason
        tosend = "<presence>"\
                 "<show>xa</show><status>"+self.normalize(reason)+"</status>"\
                 "<priority>"+priority+"</priority>"\
                 "</presence>"
        self.keepalive = tosend
        self.status = "xa"
        self.sendPacket(tosend)

    def sendDND(self, reason):
        "Send an dnd presence event with the given reason. Reason CANNOT be \
        None"
        priority = "%d"%(self.priority-6)
        self.currentStatus = "dnd"
        self.currentStatusReason = reason
        tosend = "<presence>"\
                 "<show>dnd</show><status>"+self.normalize(reason)+"</status>"\
                 "<priority>"+priority+"</priority>"\
                 "</presence>"
        self.keepalive = tosend
        self.status = "dnd"
        self.sendPacket(tosend)

    def sendGetInfo(self, to):
        "Send an information request on a person."
        self.id = self.id + 1
        id = "vcard%d"%self.id
        tosend = "<iq type='get' id='"+id+"' to='"+to+"'>"\
                 "<query xmlns='vcard-temp'></query></iq>"
        self.idhash[id] = "vcard"
        self.sendPacket(tosend)

    def sendSetInfo(self, displayname, family, given, nickname, email):
        "Submit your vCard to the Server."
        self.id = self.id + 1
        id = "svcard%d"%self.id
        tosend = "<iq type='set' id='"+id+"'>"\
                 "<VCARD xmlns='vcard-temp' version='3.0'><FN>"+displayname+"</FN>" + \
                 "<N><FAMILY>"+family+"</FAMILY><GIVEN>"+given+"</GIVEN></N>" + \
                 "<NICKNAME>"+nickname+"</NICKNAME><EMAIL><INTERNET/><PREF/>" + \
                 email + "</EMAIL></VCARD></iq>"
        self.idhash[id] = "vcard-submit"
        self.sendPacket(tosend)

    def sendAgentListRequest(self):
        "Send a request for the transport list."
        self.id = self.id + 1
        id = "agentlist%d"%self.id
        packet = "<iq type='get' id='"+id+"'>"\
                 "<query xmlns='jabber:iq:agents'/>"\
                 "</iq>"
        self.idhash[id] = "agentlist"
        self.sendPacket(packet)

    def sendAgentRegHelp(self, to):
        "Begin the registration process with a transport."
        self.id = self.id + 1
        id = "agenthelp%d"%self.id
        packet = "<iq type='get' id='"+id+"' to='"+to+"'>"\
                 "<query xmlns='jabber:iq:register'/></iq>"
        self.idhash[id] = "agenthelp"
        self.sendPacket(packet)

    def sendAgentRegistration(self, to, fields):
        "Complete registration process with a transport."
        names = self.regkey[2]
        self.idhash[self.regkey[1]] = "registration"
        if(len(names) != len(fields)):
            return
        packet = "<iq type='set' id='"+self.regkey[1]+"' to='"+to+"'>"\
                 "<query xmlns='jabber:iq:register'>"
        if(self.regkey[0]):
            packet = packet + "<key>"+self.regkey[0]+"</key>"
        i = 0
        while(i < len(fields)):
            if(fields[i] == '""'):
                fields[i] = ""
            i = i + 1
        i = 0
        while(i < len(fields)):
            packet = packet + "<"+names[i]+">"+fields[i]+"</"+names[i]+">"
            i = i + 1
        packet = packet +"</query></iq>"
        self.sendPacket(packet)
        return

    # Sets a roster item's nickname
    def setNick(self, jid, nick):
        "Set someones nickname"
        self.id = self.id + 1
        id = "rosterupdate%d"%self.id
        packet = "<iq type='set' id='"+id+"'>"\
                 "<query xmlns='jabber:iq:roster'>"\
                 "<item jid='"+jid+"' name='"+nick+"'>"
        if(self.gjidhash.has_key(jid)):
            for item in self.gjidhash[jid]:
                packet = packet + "<group>"+item+"</group>"
        packet = packet + "</item></query></iq>"
        self.idhash[id] = "rosterupdate"
        self.sendPacket(packet)

    # Set a users group to just the group specified
    def setGroup(self, jid, group):
        "Set someones group to just the group specified"
        if(not self.jidhash.has_key(jid)):
            return
        self.id = self.id + 1
        id = "rosterupdate%d"%self.id
        packet = "<iq type='set' id='"+id+"'>"\
                 "<query xmlns='jabber:iq:roster'>"\
                 "<item jid='"+jid+"' name='"+self.jidhash[jid]+"'>"
        packet = packet + "<group>"+group+"</group>"
        packet = packet + "</item></query></iq>"
        self.idhash[id] = "rosterupdate"
        self.gjidhash[jid] = [group]
        keys = self.grouphash.keys()
        for key in keys:
            if(operator.contains(self.grouphash[key],jid)):
                self.grouphash[key].remove(jid)
        if(not self.grouphash.has_key(group)):
            self.grouphash[group] = [jid]
        else:
            t = self.grouphash[group]
            if(not operator.contains(t,jid)):
                t.append(jid)
            self.grouphash[group] = t
        self.sendPacket(packet)

    # Add a user to a group
    def addGroup(self, jid, group):
        "Add someone to a group"
        if(not self.jidhash.has_key(jid)):
            return

        self.id = self.id + 1
        id = "rosterupdate%d"%self.id
        packet = "<iq type='set' id='"+id+"'>"\
                 "<query xmlns='jabber:iq:roster'>"\
                 "<item jid='"+jid+"' name='"+self.jidhash[jid]+"'>"
        if self.gjidhash.has_key(jid):
            for item in self.gjidhash[jid]:
                packet = packet + "<group>"+item+"</group>"
        packet = packet + "<group>"+group+"</group>"
        packet = packet + "</item></query></iq>"
        self.idhash[id] = "rosterupdate"
        if self.gjidhash.has_key(jid):
            t = self.gjidhash[jid]
            if(not operator.contains(t,group)):
                t.append(group)
                self.gjidhash[jid] = t
        else:
            self.gjidhash[jid] = (group)
        if(not self.grouphash.has_key(group)):
            self.grouphash[group] = [jid]
        else:
            t = self.grouphash[group]
            if(not operator.contains(t,jid)):
                t.append(jid)
            self.grouphash[group] = t
        self.sendPacket(packet)

    def helperRemoveUserFromGroup(self, jid, group):
        "This is a helper function for the remove group function. \
        Users should NOT call this function directly."
        if(self.debug):
            logDebug("Attempting to remove " + jid + " from " + group)
        if(not self.grouphash.has_key(group)):
            return
        g = self.grouphash[group]
        if(not operator.contains(g,jid)):
            return
        if(self.debug):
            logDebug("Found " + jid + " in group " + group)
        g.remove(jid)
        self.grouphash[group] = g
        if(self.debug):
            logDebug("Successfully removed " + jid + " from " + group)

    # Remove a user from a group
    def removeGroup(self, jid, group):
        "Remove someone from a group"
        if(not self.jidhash.has_key(jid) or not self.grouphash.has_key(group)):
            if(self.debug):
                logDebug(jid + "not found in jidhash or grouphash")
            return
        self.id = self.id + 1
        id = "rosterupdate%d"%self.id
        packet = "<iq type='set' id='"+id+"'>"\
                 "<query xmlns='jabber:iq:roster'>"\
                 "<item jid='"+jid+"' name='"+self.jidhash[jid]+"'>"
        for item in self.gjidhash[jid]:
            if(item == group):
                continue
            packet = packet + "<group>"+item+"</group>"
        packet = packet + "</item></query></iq>"
        self.idhash[id] = "rosterupdate"
        t = self.gjidhash[jid]
        if(operator.contains(t,group)):
            t.remove(group)
        if(operator.contains(self.grouphash[group],jid)):
            self.grouphash[group].remove(jid)
        self.gjidhash[jid] = t
        self.sendPacket(packet)

    # Permanently removes an item from a roster, killing ALL subscriptions:
    # both to and from.
    def removeUser(self, jid):
        "Deletes an item from a roster, killing ALL subscriptions: both \
        to and from."
        self.id = self.id + 1
        id = "removeuser%d"%self.id
        packet = "<iq type='set' id='"+id+"'>"\
                 "<query xmlns='jabber:iq:roster'>"\
                 "<item jid='"+jid+"' subscription='remove'/></query></iq>"
        self.idhash[id] = "removeuser"
        self.sendPacket(packet)


    # Join a conference
    def joinConference(self, confjid, confnick):
        "Joins a conference specified by confjid, and creates an alias nickname confnick for it"
        self.conferences[confjid] = (confnick, {})
        self.confnick[confnick] = confjid
        packet = "<presence to='"+confjid+"/"+self.user+"'><status>Online</status></presence>"
        self.sendPacket(packet)

    def leaveConference(self, conf):
        "Leaves a conferences specified by conf which can be either the jid or the nickname"
        confjid = None
        try:
            if(self.conferences.has_key(conf)):
                confjid = conf
                nick = self.conferences[conf][0]
                del self.conferences[conf]
                del self.confnick[nick]
            if(self.confnick.has_key(conf)):
                confjid = self.confnick[conf]
                del self.confnick[conf]
                del self.conferences[confjid]
            if(confjid != None):
                packet = "<presence to='"+confjid+"/"+self.user+"' type='unavailable'/>"
                self.sendPacket(packet)
        except:
            if(self.debug):
                traceback.print_exc()
                a,b,c = sys.exc_info()
                print "An exception occured"
                print(a)
                print(b)





















    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # -------------------------------------------------------------------------
    #  Callback functions from the XML parsing layer
    # -------------------------------------------------------------------------
    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    def handleDisconnected(self):
        "This is another function the user should never call. It is \
        called by the xml parser when something has gone awry"
        self.running = 0
        self.mangleFunctions()
        self.cbHandleDisconnected()
        self.mainSocket = None


    def handleStream(self, stream):
        # This function handles the initial startup by sending in the
        # login information
        id = stream.id
        hd = sha.new(id+self.password).hexdigest()
        toSend = "<iq id='" + id + "' type='set'>"\
                 "<query xmlns='jabber:iq:auth'>"\
                 "<username>"+self.user+"</username>"\
                 "<digest>"+hd+"</digest>"\
                 "<resource>"+self.resource+"</resource></query></iq>"
        self.sendPacket(toSend)
        self.idhash[id] = "login"

    def handleIQ(self, iq):
        #This will be gory.
        type = iq.type
        if(type == "set"):
            if(iq.ns == "jabber:iq:roster"):
                query = iq.query
                for item in query.users:
                    if(item.subscription == "remove"):
                        #remove the user from our lists
                        try:
                            nick = self.jidhash[item.jid]
                            del self.jidhash[item.jid]
                            del self.nickhash[nick]
                            del self.preshash[item.jid]
                            self.cbHandleRosterUpdate(item.jid, item.name,
                                                      item.subscription, 1)
                        except:
                            traceback.print_exc()
                            a,b,c = sys.exc_info()
                            print "An exception occured"
                            print(a)
                            print(b)
                            self.cbHandleRosterUpdate(item.jid, item.name,
                                                      item.subscription, 0)
                    elif(item.subscription == "to" or
                         item.subscription == "both" or
                         item.subscription == "from"):
                        oldkey = None
                        try:
                            oldkey = self.jidhash[item.jid]
                        except:
                            pass
                        if( item.name == None ):
                            self.jidhash[item.jid]=item.jid
                        else:
                            self.jidhash[item.jid]=item.name
                        if(oldkey):
                            self.updateHash(self.nickhash,oldkey,item.name,item.jid)
                        else:
                            self.nickhash[item.name]=item.jid
                        self.cbHandleRosterUpdate(item.jid, item.name,
                                                  item.subscription,1)
                return

            if(iq.ns == "jabber:iq:oob"):
                query = iq.query
                urls = string.split(query.url,"\n")
                url = urls[-1]
                if(self.filehash.has_key(iq.ffrom)):
                    self.filehash[iq.ffrom].append((url,iq.id))
                else:
                    self.filehash[iq.ffrom] = [(url,iq.id)]
                self.cbHandleFileReceive(iq.ffrom, url)
                return
        if(type == "result"):
            if(iq.ns == "vcard"):
                del self.idhash[iq.id]
                vc = iq.query
                self.cbHandleVCard(iq.ffrom, vc.fn, vc.given,
                                   vc.family, vc.nickname, vc.email)
            elif(self.idhash.has_key(iq.id) and self.idhash[iq.id] == "vcard"):
                del self.idhash[iq.id]
                self.cbHandleNoVCard(iq.ffrom)
            else:
                # Throw success to the IQ ID manager.
                self.handleIQHelper(iq,1)
            return
        if(type == "error"):
            # Throw error to the IQ ID manager.
            self.handleIQHelper(iq,0)


    def setHighestPriorityProfile(self, ffrom):
        pkeys = self.reshash[ffrom][2].keys()
        highestn = pkeys[0]
        highestp = self.reshash[ffrom][2][highestn][2]
        for key in pkeys:
            if(self.reshash[ffrom][2][key][2] > highestp):
                highestn = key
                highestp = self.reshash[ffrom][2][key][2]
        self.reshash[ffrom][0] = highestn
        self.reshash[ffrom][1] = highestp

    def handlePresence(self, pres):
        # um, ick.
        duplicate = 0

        # handle conference stuff first.
        if(self.conferences.has_key(pres.ffrom)):
            if(pres.type == None or string.lower(pres.type) == "available"):
                # update our list of people in the conference
                # update the listener..
                nick = pres.resource
                try:
                    show = pres.show.text
                except:
                    show = "online"
                try:
                    reason = pres.status.text
                except:
                    reason = "unspecified"
                self.conferences[pres.ffrom][1][nick] = (reason,show)
                self.cbHandleConferencePresence(pres.ffrom, nick, show, reason)
                return
            if(string.lower(pres.type) == "unavailable"):
                # update our list of people in the conference
                # update the listener..
                nick = pres.resource
                try:
                    del self.conferences[pres.ffrom][1][nick]
                except:
                    pass
                self.cbHandleConferencePresence(pres.ffrom, nick, "offline", "leaving")
                return
            return

        if(pres.type == None or string.lower(pres.type) == "available"):
            if(pres.show == None):
                show="online"
            else:
                try:
                    show = pres.show.text
                except:
                    show = "online"
            if(pres.status == None or pres.status.text == None):
                status="online"
            else:
                status=pres.status.text
            priority = pres.priority
            if self.preshash.has_key( pres.ffrom ):
                a,b,c = self.preshash[pres.ffrom]
                if( a == 1 and b == show and c == status ):
                    duplicate = 1
            self.preshash[pres.ffrom]=(1, show, status)
            if(not self.reshash.has_key(pres.ffrom)):
                self.reshash[pres.ffrom] = [pres.resource,priority,{}]
            #else:
            #    self.reshash[pres.ffrom][0] = pres.resource
            self.reshash[pres.ffrom][2][pres.resource] = (status,show,priority)
            if(priority >= self.reshash[pres.ffrom][1]):
                self.reshash[pres.ffrom][1] = priority
                self.reshash[pres.ffrom][0] = pres.resource
            elif(pres.resource == self.reshash[pres.ffrom][0] and
                 priority < self.reshash[pres.ffrom][1]):
                self.setHighestPriorityProfile(pres.ffrom)
            self.cbHandlePresenceUpdate(pres.ffrom,show,status,1,
                                        pres.resource, duplicate)
            return

        if(string.lower(pres.type) == "unavailable"):
            if(pres.show == None or pres.show.text == None):
                show = "offline"
            else:
                show = pres.show.text
            if(pres.status == None):
                status = "Disconnected"
            else:
                status = pres.status.text

            if(self.reshash.has_key(pres.ffrom)):
                if(len(self.reshash[pres.ffrom][2]) == 1):
                    del self.reshash[pres.ffrom]
                    if self.preshash.has_key( pres.ffrom ):
                        a,b,c = self.preshash[pres.ffrom]
                        if( a == 1 and b == show and c == status ):
                            duplicate = 1
                    self.preshash[pres.ffrom]=(0, show, status)
                else:
                    del self.reshash[pres.ffrom][2][pres.resource]
                    if(self.reshash[pres.ffrom][0] == pres.resource):
                        self.setHighestPriorityProfile(pres.ffrom)
                    status2, reason, priority = self.reshash[pres.ffrom][2][self.reshash[pres.ffrom][0]]
                    if self.preshash.has_key( pres.ffrom ):
                        a,b,c = self.preshash[pres.ffrom]
                        if( a == 1 and b == show and c == status2 ):
                            duplicate = 1
                    self.preshash[pres.ffrom] = (1, reason, status2)
            self.cbHandlePresenceUpdate(pres.ffrom,show,status,0,
                                        pres.resource, duplicate)
            return

        if(string.lower(pres.type) == "subscribe"):
            self.cbHandleSubscribe(pres.ffrom)
            return

        if(string.lower(pres.type) == "unsubscribe"):
            self.cbHandleUnsubscribe(pres.ffrom)
            return

        if(string.lower(pres.type) == "subscribed"):
            self.preshash[pres.ffrom] = (0, "offline", "Disconnected")
            self.gjidhash[pres.ffrom] = []
            self.cbHandleSubscribed(pres.ffrom)
            return

        if(string.lower(pres.type) == "unsubscribed"):
            try:
                if(not self.jidhash.has_key(pres.ffrom)):
                    return
                nick = self.jidhash[pres.ffrom]
                del self.jidhash[pres.ffrom]
                del self.nickhash[nick]
                del self.preshash[pres.ffrom]
                del self.gjidhash[pres.ffrom]
                # update grouphash (ugh)
                keys = self.grouphash.keys()
                for k in keys:
                    self.helperRemoveUserFromGroup(pres.ffrom, k)
                    if(len(k) == 0):
                        self.grouphash.remove(k)
                self.cbHandleUnsubscribed(pres.ffrom,1)
            except:
                traceback.print_exc()
                a,b,c = sys.exc_info()
                print "An exception occured"
                print(a)
                print(b)
                self.cbHandleUnsubscribed(pres.ffrom,0)
            return

    def handleMessage(self, msg):
        # um, hrm
        if(msg.type == "error"):
            if(hasattr(msg,"body") and hasattr(msg,"ffrom") and
               hasattr(msg,"error") and hasattr(msg.error, "code")
                and msg.error.code != None ):
                self.cbHandleMessageError(msg.ffrom,msg.body.text,
                                                 msg.error.code)
            elif(hasattr(msg,"error") and hasattr(msg.error, "code")
                and msg.error.code != None ):
                self.cbHandleMessageError("unknown","",msg.error.code)
            elif(hasattr(msg,"error") and hasattr(msg.error,"text")):
                self.cbHandleMessageError("",msg.error.text,"0")
            elif(hasattr(msg,"body") and hasattr(msg.body,"text")):
                self.cbHandleMessageError("",msg.body.text,"0")
            else:
                self.cbHandleMessageError("", "Unknown message error","0")
            return
        if(msg.body != None and
           msg.body.text != None and
           msg.ffrom != None and
           msg.type != "error"):
            thread = None
            delay = None
            try:
                thread = msg.thread.text
            except:
                pass
            try:
                delay = msg.delay
                if(self.debug):
                    logDebug("Found a delayed message")
            except:
                if(self.debug):
                    logDebug("Message was not delayed")
                pass
            if(self.reshash.has_key(msg.ffrom)):
                self.reshash[msg.ffrom][0] = msg.resource
            if(self.conferences.has_key(msg.ffrom)):
                self.cbHandleConferenceMessage(self.conferences[msg.ffrom][0],
                                               msg.resource,
                                               msg.body.text,
                                               delay)
            else:
                self.cbHandleMessageReceive(msg.ffrom,msg.body.text,
                                            thread,delay,msg.resource)

    def handleIQHelper(self, iq, result):
        id = iq.id
        ns = iq.ns
        if(ns == "jabber:iq:admin"):
            pl = iq.query.w.presencelist
            for pres in pl:
                if(pres.show == None):
                    show = "online"
                else:
                    try:
                        show = pres.show.text
                    except:
                        show = "online"
                if(pres.status == None or pres.status.text == None):
                    status = "online"
                else:
                    status = pres.status.text
                self.cbHandleAdminWho(pres.ffrom,show,status,1,pres.resource)
            return
        if(not self.idhash.has_key(id)):
            if(self.debug):
                logDebug("I dunno what this IQ query id is: " + id)
            return
        if(self.idhash[id] == "login"):
            del self.idhash[id]
            if(result):
                self.cbHandleLogin(1)
                self.sendAgentListRequest()
                self.sendOnline()
                self.getRoster()
                return
            else:
                self.cbHandleLogin(0)
                self.disconnect()
                return
        if(self.idhash[id] == "vcard-submit"):
            del self.idhash[id]
            if(result):
                self.cbHandleVCardSubmit(1)
            else:
                self.cbHandleVCardSubmit(0)
            return
        if(self.idhash[id] == "getroster"):
            del self.idhash[id]
            if(result):
                #we got a roster
                for blah in iq.query.users:
                    if(blah.subscription == "to" or blah.subscription == "both"):
                        if(blah.name):
                            self.nickhash[blah.name] = blah.jid
                            self.jidhash[blah.jid] = blah.name
                        else:
                            self.nickhash[blah.jid] = blah.jid
                            self.jidhash[blah.jid] = blah.jid
                        if(not self.preshash.has_key(blah.jid)):
                            self.preshash[blah.jid] = (0, "offline", "Disconnected")
                        # set the groups for this user
                        self.gjidhash[blah.jid] = blah.groups
                        # add the user to the group hash.
                        for g in blah.groups:
                            if self.grouphash.has_key(g):
                                t = self.grouphash[g]
                                t.append(blah.jid)
                                self.grouphash[g]=t
                            else:
                                self.grouphash[g] = [blah.jid]
                self.cbHandleGrabRoster()
                return
        if(self.idhash[id] == "rosterupdate"):
            del self.idhash[id]
            if(result):
                #successfully set the nick, updated groups, whatever
                self.cbHandleRosterUpdateCheck(1,None)
            else:
                self.cbHandleRosterUpdateCheck(0,iq.error.text)
            return
        if(self.idhash[id] == "vcard"):
            del self.idhash[id]
            if(result):
                return
            else:
                self.cbHandleIQError(iq.ffrom,iq.error.code,
                                     iq.error.text)
                return
        if(self.idhash[id] == "agentlist"):
            del self.idhash[id]
            if ( len(self.agenthash) == 0 ):
                for x in iq.query.agentlist:
                    if ( x.name ):
                        self.agenthash[x.jid] = x.name
                return
            if(result and hasattr(iq,"query") and
               hasattr(iq.query,"agentlist")):
                self.cbHandleAgentList(iq.query.agentlist)
            return
        if(self.idhash[id] == "agenthelp"):
            del self.idhash[id]
            if(result and hasattr(iq,"query") and
               hasattr(iq.query,"fields") and
               hasattr(iq.query,"instructions")):
                key = None
                if(hasattr(iq.query,"key")):
                    key = iq.query.key
                self.regkey = [key,id,iq.query.fields]
                self.cbHandleAgentRegister(id, iq.ffrom,
                                                  iq.query.instructions,
                                                  iq.query.fields)
            elif(hasattr(iq,"type") and iq.type == "error"):
                self.cbHandleIQError(iq.ffrom,iq.error.code,iq.error.text)
            return
        if(self.idhash[id] == "registration"):
            del self.idhash[id]
            if(result):
                self.cbHandleAgentRegistered(iq.ffrom)
            return
        if(not result):
            self.cbHandleIQError(iq.ffrom,iq.error.code,iq.error.text)
            return


    def handleFileReceived(self, jid, URL, id):
        tosend = "<iq type='result' id='"+id+"' to='"+jid+"/" + \
                 self.reshash[jid][0]+"'/>"
        self.sendPacket(tosend)
        self.cbHandleFileReceived(jid, os.path.basename(URL))

    def handleFileErrorReceived(self, jid, url, id, type, text):
        tosend = "<iq type='error' id='"+id+"' to='"+jid+"'/>"
        self.sendPacket(tosend)
        self.cbHandleFileErrorReceived(jid,url,type,text)
