# Time-stamp: <2002-03-03 13:04:16 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 xml.sax.handler import *
# from xml.sax import *
# There is a bug in expat that won't return white space between any of
# the encoded characters. For now people will just have to deal, it
# might be fixed in the new version of python

# from __future__ import nested_scopes
try:
   from   xml.parsers import expat
except:
   from   xml.parsers import pyexpat
   expat = pyexpat

from JabberTags import *
from IMCom import *
from threading import *
import VCardTags
import string
import socket
import os
import time
import sys
import traceback
# import codecs  no need for it..
import operator

def logDebug(s):
   print s.encode(sys.getdefaultencoding(), "replace")
#   stuff commented out so I don't depend on codecs.
#   myf = None
#   try:
#      myf = codecs.open(os.environ['HOME']+"/.imcom/debuglog.log","a+",
#                        'ISO-8859-1','replace')
#      myf.write(s) #.encode('ascii','replace'))
#      myf.write("\n")
#      myf.close()
#   except:
#      pass
      #print "An exception occuring logging to the debug file."
      #print "You may have missed a message or query response."
#   else:
#       myf.close()


class JabberContentHandler(Thread):
   """JCH is a parser for Jabber IM xml data. It requires a listener passed
   to the constructor to have the following attributes defined:
   mainSocket - the socket by which you are connected to the jabber server

   Also required are the following methods:
   closeSocket()              - Closes the sockets and finishes cleaning up
   handleStream(JabberStream) - Receives the initial stream start information
   handleIQ(JabberIQ)         - Receives IQ events
   handlePresence(JabberPresence) - Receives Presences Events
   handleMessage(JabberMessage) - Receives Message Events
   handleDisconnected()       - Called when the server disconnected us.
   """

   def __init__(self, imcom, ssl):
      "Constructor for JabberContentHandler. The imcom parameter is a\
      listener with many functions defined which are used as callbacks\
      as XML is parsed. A complete list of these is in the class docstring"
      Thread.__init__(self)
      self.elementStack=[]
      self.goalStack = []
      self.goalStack.append(0)
      self.imcom = imcom
      self.parser = expat.ParserCreate()
      self.parser.StartElementHandler = self.startElement
      self.parser.EndElementHandler = self.endElement
      self.parser.CharacterDataHandler = self.characters
      self.debug = 1
      self.ssl = ssl
      self.currentGoal  = 0
      self.BUILDROSTER  = 1
      self.GETPRESENCE  = 2
      self.GETMESSAGE   = 3
      self.GETVCARD     = 4
      self.REGISTER     = 5
      self.GETAGENTLIST = 6
      self.OOB          = 7
      self.ADMINQUERY   = 8
      self.ADMINWHO     = 9

   # Signals the start of an element
   #
   # We don't actually do anything with a start element unless it is
   # self-terminating. How we tell if its self terminating I don't
   # know
   def startElement(self, name, attrs):
      "startElement is a callback used by the expat parser. It is called\
      when a new element is started. This particular handler will parse\
      the attributes appropriately and push the correct element onto our\
      element stack."
      ds = "Started element " + name
      ds = ds + "\n" + self.getAttrs(attrs)
      self.debugStream(ds)

      # Switch on the element name.
      # Stream (start)
      if(string.lower(name) == "stream:stream"):
         to = self.getAttr(attrs, "to")
         ffrom = self.getAttr(attrs, "from")
         ffrom = string.lower(self.stripFrom(ffrom))
         id = self.getAttr(attrs, "id")
         self.goalStack.append(0)
         self.imcom.handleStream(JabberStream(to,ffrom,id))
         return

      # Error (Start) Subelement of EVERYTHING
      if(string.lower(name) == "error"):
         code = self.getAttr(attrs,"code")
         self.elementStack.append(ErrorTag(code))
         return

      # The dreaded X (start)
      if(string.lower(name) == "x"):
         # if(self.debug):
         #    logDebug("elementX start handler")
         ns = self.getAttr(attrs,"xmlns")
         #if(self.debug):
         #   logDebug("ns is " + ns)
         if(ns == "jabber:x:delay"):
            #   if(self.debug):
            #      logDebug("was a delay element")
            stamp = self.getAttr(attrs,"stamp")
            e = self.elementStack.pop()
            e.delay = stamp
            #if(self.debug):
            #   print("Appending a delay tag to", e)
            self.elementStack.append(e)
            return
         return

      # Presence (Start)
      if(string.lower(name) == "presence"):
         to = self.getAttr(attrs,"to")
         ffrom = self.getAttr(attrs,"from")
         jid = string.lower(self.stripFrom(ffrom))
         resource = self.getFromResource(ffrom)
         type = self.getAttr(attrs,"type")
         self.goalStack.append(self.GETPRESENCE)
         self.elementStack.append(JabberPresence(to,jid,type,resource))
         return

      # Message (Start)
      if(string.lower(name) == "message"):
         to = self.getAttr(attrs,"to")
         ffrom = self.getAttr(attrs,"from")
         resource = self.getFromResource(ffrom)
         ffrom = string.lower(self.stripFrom(ffrom))
         id = self.getAttr(attrs,"id")
         type = self.getAttr(attrs,"type")
         if(type == None):
            type = ""
         self.goalStack.append(self.GETMESSAGE)
         self.elementStack.append(JabberMessage(to,ffrom,resource,id,type))
         return

      # IQ (start)
      if(string.lower(name) == "iq"):
         to = self.getAttr(attrs, "to")
         ffrom = self.getAttr(attrs, "from")
         ffrom = self.stripFrom(ffrom)
         id = self.getAttr(attrs, "id")
         type = self.getAttr(attrs, "type")
         self.elementStack.append(JabberIQ(to,ffrom,id,type))
         return

      # Query (start)
      if(string.lower(name) == "query"):
         ns = self.getAttr(attrs,"xmlns")
         iq = self.elementStack.pop()
         iq.ns = ns
         self.elementStack.append(iq)
         if(ns == "jabber:iq:roster"):
            self.elementStack.append(JabberRoster())
            self.goalStack.append(self.BUILDROSTER)
            return
         if(ns == "jabber:iq:register"):
            self.elementStack.append(JabberRegister())
            self.goalStack.append(self.REGISTER)
            return
         if(ns == "jabber:iq:oob"):
            self.elementStack.append(JabberOOB())
            self.goalStack.append(self.OOB)
            return
         if(ns == "jabber:iq:agents"):
            self.elementStack.append(JabberAgentList())
            self.goalStack.append(self.GETAGENTLIST)
            return
         if(ns == "jabber:iq:admin"):
            self.elementStack.append(JabberAdmin())
            self.goalStack.append(self.ADMINQUERY)
            return
         return


      topgoal = self.goalStack[-1]

      # jabber:IQ:ADMIN WHO START
      if(topgoal == self.ADMINQUERY):
         sl = string.lower(name)
         if(sl == "who"):
            self.elementStack.append(JabberAdminWho())
            self.goalStack.append(self.ADMINWHO)
            return
         return

      if(topgoal == self.GETAGENTLIST):
         sl = string.lower(name)
         if(sl == "agent"):
            jid = self.getAttr(attrs,"jid")
            self.elementStack.append(JabberAgent(jid))
            return
         if(sl == "name"):
            self.elementStack.append(JabberDummyTag())
            return
         if(sl == "transport"):
            self.elementStack.append(JabberDummyTag())
            return
         return


      if(topgoal == self.REGISTER):
         # Key (start)
         if(string.lower(name) == "key"):
            self.elementStack.append(JabberRegisterKey())
            return
         # instructions, username, nick, password, name, first, last, email
         # address, city, state, zip, phone, url, date, misc, text
         sl = string.lower(name)
         if(sl == "instructions" or sl == "username" or sl == "nick" or
            sl == "password" or sl == "name" or sl == "first" or
            sl == "last" or sl ==  "email" or sl == "address" or
            sl == "city" or sl == "state" or sl == "zip" or
            sl == "phone" or sl == "url" or sl == "date" or
            sl == "misc" or sl == "text"):
            self.elementStack.append(JabberDummyTag())
            return
         return

      if(topgoal == self.BUILDROSTER):
         # Item (start)
         if(string.lower(name) == "item"):
            jid = string.lower(self.getAttr(attrs,"jid"))
            name = self.getAttr(attrs,"name")
            subscription = self.getAttr(attrs,"subscription")
            self.elementStack.append(JabberRosterItem(jid,name,subscription))
            return
         # Group support for roster items
         if(string.lower(name) == "group"):
            dt = JabberDummyTag()
            self.elementStack.append(dt)
            return
         return

      if(topgoal == self.OOB):
         sl = string.lower(name)
         if(sl == "url" or sl == "desc"):
            self.elementStack.append(JabberDummyTag())
            return
         return

      if(topgoal == self.GETPRESENCE):
         # Status (Start) Presence Subelement
         if(string.lower(name) == "status"):
            self.elementStack.append(JabberStatus())
            return
         # Show (Start) Presence Subelement
         if(string.lower(name) == "show"):
            self.elementStack.append(JabberShow())
            return
         # Priority (Start) Presence Subelement
         if(string.lower(name) == "priority"):
            self.elementStack.append(JabberDummyTag())
            return
         return

      if(topgoal == self.GETMESSAGE):
         # Body (Start) Message Subelement
         if(string.lower(name) == "body"):
            self.elementStack.append(JabberBody())
            return
         # Thread (Start) Message Subelement
         if(string.lower(name) == "thread"):
            self.elementStack.append(JabberBody())
            return
         return

      # VCard (Start)
      if(string.lower(name) == "vcard"):
         iq = self.elementStack.pop()
         iq.ns = "vcard"
         self.elementStack.append(iq)
         self.goalStack.append(self.GETVCARD)
         self.elementStack.append(VCardTags.VCard())
         return

      if(topgoal == self.GETVCARD):
         sl = string.lower(name)
         if(sl == "fn" or sl == "given" or sl == "family" or
            sl == "nickname" or sl == "email"):
            self.elementStack.append(VCardTags.VCardTemp())
            return
         return

      # Agent (Start) Subelement of IQ Query
      #if(string.lower(name) == "agent"):
      #   topgoal == self.GETAGENT
      #   jid = self.getAttr(attrs,"jid")
      #   self.elementStack.append(AgentTag(jid))
      #   return

      # everything else is a dummytag.
      # dt = JabberDummyTag()
      # self.elementStack.append(dt)

   # Signals the end of an element
   def endElement(self, name):
      "startElement is a callback used by the expat parser. It is called\
      when a new element is started. This particular handler will parse\
      the attributes appropriately and push the correct element onto our\
      element stack."
      self.debugStream("ended element " + name)

      # Stream (end)
      if(string.lower(name) == "stream:stream"):
         self.imcom.closeSocket()
         return

      # Error (end)
      if(string.lower(name) == "error"):
         e = self.elementStack.pop()
         t = self.elementStack.pop()
         t.error = e
         self.elementStack.append(t)
         return

      # Top level end
      sl = string.lower(name)

      # IQ (end)
      if(sl == "iq"):
         self.imcom.handleIQ(self.elementStack.pop())
         return

      topgoal = self.goalStack[-1]
      # Query (End) IQ Subelement
      if(string.lower(name) == "query" and
         (topgoal == self.OOB or
          topgoal == self.BUILDROSTER or
          topgoal == self.REGISTER or
          topgoal == self.GETAGENTLIST or
          topgoal == self.ADMINQUERY)):
         query = self.elementStack.pop()
         iq = self.elementStack.pop()
         iq.query = query
         self.elementStack.append(iq)
         self.goalStack.pop()
         return

      # Presence (end)
      if(string.lower(name) == "presence"):
         self.goalStack.pop()
         if(self.goalStack[-1] == self.ADMINWHO):
            p = self.elementStack.pop()
            w = self.elementStack.pop()
            w.presencelist.append(p)
            self.elementStack.append(w)
            return
         self.imcom.handlePresence(self.elementStack.pop())
         return

      # Message (end)
      if(string.lower(name) == "message"):
         msg = self.elementStack.pop()
         self.goalStack.pop()
         self.imcom.handleMessage(msg)
         return

      # jabber:iq:admin WHO end
      if(topgoal == self.ADMINWHO):
         if(sl == "who"):
            w = self.elementStack.pop()
            q = self.elementStack.pop()
            q.w = w
            self.elementStack.append(q)
            self.goalStack.pop()
            return
         return

      # (End) Goal is REGISTER
      # Switch on element name.
      if(topgoal == self.REGISTER):
         if(sl == "username" or sl == "nick" or
            sl == "password" or sl == "name" or sl == "first" or
            sl == "last" or sl ==  "email" or sl == "address" or
            sl == "city" or sl == "state" or sl == "zip" or
            sl == "phone" or sl == "url" or sl == "date" or
            sl == "misc" or sl == "text"):
            d = self.elementStack.pop()
            q = self.elementStack.pop()
            q.fields.append(sl)
            self.elementStack.append(q)
            return
         if(sl == "instructions"):
            k = self.elementStack.pop()
            q = self.elementStack.pop()
            if(hasattr(k,"text")):
               q.instructions = k.text
            self.elementStack.append(q)
            return
         if(sl == "key"):
            k = self.elementStack.pop()
            q = self.elementStack.pop()
            if(hasattr(k,"text")):
               q.key = k.text
            self.elementStack.append(q)
            return
         return

      # OOB (end)
      if(topgoal == self.OOB):
         if(sl == "url"):
            dt = self.elementStack.pop()
            q = self.elementStack.pop()
            q.url = dt.text
            self.elementStack.append(q)
            return
         if(sl == "desc"):
            dt = self.elementStack.pop()
            q = self.elementStack.pop()
            q.desc = dt.text
            self.elementStack.append(q)
            return
         return

      # BUILDROSTER (end)
      if(topgoal == self.BUILDROSTER):
         # Item (End) IQ Subelement
         if(sl == "item"):
            rosterItem = self.elementStack.pop()
            roster = self.elementStack.pop()
            roster.users.append(rosterItem)
            self.elementStack.append(roster)
            return
         # Group (End) Item Subelement
         if(sl == "group"):
            gt = self.elementStack.pop()
            ri = self.elementStack.pop()
            if(hasattr(gt,"text") and len(gt.text) > 0):
               ri.groups.append(gt.text)
               self.elementStack.append(ri)
               return
            else:
               self.elementStack.append(ri)
         return


      if(topgoal == self.GETPRESENCE):
         # Status (End) Presence Subelement
         if(sl == "status"):
            stat = self.elementStack.pop()
            pres = self.elementStack.pop()
            pres.status = stat
            self.elementStack.append(pres)
            return
         # Show (End) Presence Subelement
         if(sl == "show"):
            show = self.elementStack.pop()
            pres = self.elementStack.pop()
            pres.show = show
            self.elementStack.append(pres)
            return
         if(sl == "priority"):
            pri = self.elementStack.pop()
            pres = self.elementStack.pop()
            pres.priority = int(pri.text)
            self.elementStack.append(pres)
         return

      # GETMESSAGE (end)
      if(topgoal == self.GETMESSAGE):
         # Body (end) Message Subelement
         if(sl == "body"):
            body = self.elementStack.pop()
            msg = self.elementStack.pop()
            msg.body = body
            self.elementStack.append(msg)
            return
         # Thread (end) Message SubElement
         if(sl == "thread"):
            thread = self.elementStack.pop()
            msg = self.elementStack.pop()
            msg.thread = thread
            self.elementStack.append(msg)
            return
         return

      if(topgoal == self.GETVCARD):
         # VCard (end)
         if(sl == "vcard"):
            vc = self.elementStack.pop()
            iq = self.elementStack.pop()
            iq.query = vc
            self.elementStack.append(iq)
            self.goalStack.pop()
            return
         # FN (end) VCard Subelement
         if(sl == "fn"):
            te = self.elementStack.pop()
            vc = self.elementStack.pop()
            vc.fn = te.text
            self.elementStack.append(vc)
            return
         # Given (end) VCard Subelement
         if(sl == "given"):
            te = self.elementStack.pop()
            vc = self.elementStack.pop()
            vc.given = te.text
            self.elementStack.append(vc)
            return
         # Family (end) VCard Subelement
         if(sl == "family"):
            te = self.elementStack.pop()
            vc = self.elementStack.pop()
            vc.family = te.text
            self.elementStack.append(vc)
            return
         # Nickname (end) VCard Subelement
         if(sl == "nickname"):
            te = self.elementStack.pop()
            vc = self.elementStack.pop()
            vc.nickname = te.text
            self.elementStack.append(vc)
            return
         # Email (end) VCard Subelement
         if(sl == "email"):
            te = self.elementStack.pop()
            vc = self.elementStack.pop()
            vc.email = te.text
            self.elementStack.append(vc)
            return
         return

      if(topgoal == self.GETAGENTLIST):
         if(sl == "agent"):
            a = self.elementStack.pop()
            q = self.elementStack.pop()
            q.agentlist.append(a)
            self.elementStack.append(q)
            return
         if(sl == "name"):
            n = self.elementStack.pop()
            a = self.elementStack.pop()
            if(hasattr(n,"text")):
               a.name = n.text
            else:
               a.name = None
            self.elementStack.append(a)
            return
         if(sl == "transport"):
            t = self.elementStack.pop()
            a = self.elementStack.pop()
            if(hasattr(t,"text")):
               a.transport = t.text
            else:
               a.name = None
            self.elementStack.append(a)
            return
         return

      # Everything else is a dummy tag
      # self.elementStack.pop()

   # content is a string of the cdata
   def characters(self, content):
      "characters is another callback for the expat parser. It is called\
      when CDATA is read. Unfortunately it doesn't return the CDATA verbatim\
      , it returns chunks of CDATA divided by escaped charaters or newlines.\
      There are many hacks in this function to deal with that, but since\
      white space around all split points is lost I have to guess at what\
      to fill in."
      if(len(string.strip(content)) > 0):
         if(self.debug):
            self.debugStream("Content: >" + content)
         element = self.elementStack.pop()
         try:
            blah = element.text
            if(blah == None):
               element.text = content
               self.elementStack.append(element)
               return
         except:
            element.text = content
            self.elementStack.append(element)
            return
         if(element.text[-1] == "'" or element.text[-1] == ">" or
            element.text[-1] == '"' or element.text[-1] == "<" or
            element.text[-1] == "&" or content[0] == "'" or
            content[0] == '"' or content[0] == ">" or
            content[0] == "<" or content[0] == "&"):
            element.text = element.text + content
         else:
            element.text = element.text + "\n" + content
         self.elementStack.append(element)
         self.debugStream("\tcontent: " +  string.strip(content))

   # signals the start of a document
   def startDocument(self):
      pass

   # signals the end of the document
   def endDocument(self):
      pass

   def getAttr(self, attrs, name):
      try:
         return attrs[name]
      except:
         return None

   def getAttrs(self, attrs):
      "Prints out the key value pairs in an AttributesNS instance"
      retval = ""
      keys = attrs.keys()
      for temp in keys:
         retval = retval+"\t"+temp+" is "+attrs[temp]+"\n"
      return retval

   def debugStream(self, string):
      "If debug is turned on, print out the string"
      #if(self.debug):
      #   logDebug(string)
      pass


   def feed(self, data):
      "Feed data read from the socket to the parser"
      self.parser.Parse(data,0)

   def run(self):
      "The start function should be called, and not this one. Start will\
      create a new thread and then run this function. The purpose of this\
      function is to sit and read from the socket, and feed the xml parser."
      self.running = 1
      if(self.ssl and (sys.version_info[0] < 2 or
                       (sys.version_info[0] == 2 and sys.version_info[1] < 2))):
         self.imcom.tempsocket.setblocking(0)
      while(self.running):
         try:
            if(self.ssl):
               data = self.imcom.mainSocket.read(4096)
            else:
               data = self.imcom.mainSocket.recv(4096)
            if(self.debug and data):
               logDebug("Recv :> " + data)
            self.feed(data)
         except:
            a,b,c = sys.exc_info()
            if(self.ssl and operator.isSequenceType(b) and b[0] == 11):
               time.sleep(.1)
               data = None
               continue
            if(operator.isSequenceType(b) and hasattr(b,"__len__") and len(b) > 0 and b[0] == 4):
               continue
            if(operator.isSequenceType(b) and hasattr(b,"args") and len(b.args) > 0 and b.args[0] == 4):
                continue
            if(1):
               print "Receive Exception\n"
               traceback.print_exc()
               print "a is ", a
               print "b is ", b
               print
            if(self.running):
               self.running = 0
               self.imcom.handleDisconnected()
               if(self.imcom.mainSocket):
                  if(self.ssl != 0 and self.imcom.tempsocket):
                     self.imcom.tempsocket.close()
                  else:
                     self.imcom.mainSocket.close()

   def stripFrom(self, ffrom):
      "This function strips the resource information from a JID"
      result = ffrom
      if(ffrom != None and -1 != string.find(ffrom,"/")):
         result = ffrom[:string.find(ffrom,"/")]
      if(result != None):
         result = string.lower(result)
      return result

   def getFromResource(self, ffrom):
      "This function returns the resource information from a JID"
      result = ffrom
      if(ffrom != None and -1 != string.find(ffrom,"/")):
         result = ffrom[string.find(ffrom,'/')+1:]
      else:
         result = ""
      return result

if(__name__ == "__main__"):
   # run the test script
   #mf = open("test.xml","r+")
   imc = IMCom()
   imc.changeProfile("seifer.dyndns.org",5222,"airog","2gwen2")
   #imc.connect()
   #os.wait()
   #print "quitting"
   #jch = JabberContentHandler()
   #parse("test.xml",jch)
   #mf.close()

