Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mrDoctorWho/xmpppy.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Smith <mrdoctorwho@helldev.net>2020-03-19 11:55:19 +0300
committerJohn Smith <mrdoctorwho@helldev.net>2020-03-19 11:55:19 +0300
commit54c228bdc584b0a9749a3b9ff2709c2584afb37e (patch)
tree3aee9a925e1ad6b21bfbecc3789082b2c2666242
parent7a7e98d507d50342afc33b18f45c8f2b24b8811c (diff)
Fix CRLFHEADmaster
-rw-r--r--xmpp/__init__.py84
-rw-r--r--xmpp/auth.py828
-rw-r--r--xmpp/browser.py522
-rw-r--r--xmpp/client.py750
-rw-r--r--xmpp/commands.py896
-rw-r--r--xmpp/debug.py628
-rw-r--r--xmpp/dispatcher.py976
-rw-r--r--xmpp/features.py460
-rw-r--r--xmpp/filetransfer.py452
-rw-r--r--xmpp/plugin.py140
-rw-r--r--xmpp/protocol.py2862
-rw-r--r--xmpp/roster.py564
-rw-r--r--xmpp/transports.py958
13 files changed, 5060 insertions, 5060 deletions
diff --git a/xmpp/__init__.py b/xmpp/__init__.py
index e56092b..e0802b5 100644
--- a/xmpp/__init__.py
+++ b/xmpp/__init__.py
@@ -1,42 +1,42 @@
-# $Id: __init__.py, v1.10 2013/10/21 alkorgun Exp $
-
-"""
-All features of xmpppy library contained within separate modules.
-At present there are modules:
-simplexml - XML handling routines
-protocol - jabber-objects (I.e. JID and different stanzas and sub-stanzas) handling routines.
-debug - Jacob Lundquist's debugging module. Very handy if you like colored debug.
-auth - Non-SASL and SASL stuff. You will need it to auth as a client or transport.
-transports - low level connection handling. TCP and TLS currently. HTTP support planned.
-roster - simple roster for use in clients.
-dispatcher - decision-making logic. Handles all hooks. The first who takes control over fresh stanzas.
-features - different stuff that didn't worths separating into modules
-browser - DISCO server framework. Allows to build dynamic disco tree.
-filetransfer - Currently contains only IBB stuff. Can be used for bot-to-bot transfers.
-
-Most of the classes that is defined in all these modules is an ancestors of
-class PlugIn so they share a single set of methods allowing you to compile
-a featured XMPP client. For every instance of PlugIn class the 'owner' is the class
-in what the plug was plugged. While plugging in such instance usually sets some
-methods of owner to it's own ones for easy access. All session specific info stored
-either in instance of PlugIn or in owner's instance. This is considered unhandy
-and there are plans to port 'Session' class from xmppd.py project for storing all
-session-related info. Though if you are not accessing instances variables directly
-and use only methods for access all values you should not have any problems.
-"""
-
-from . import auth
-from . import browser
-from . import commands
-from . import debug
-from . import dispatcher
-from . import features
-from . import filetransfer
-from . import plugin
-from . import protocol
-from . import roster
-from . import simplexml
-from . import transports
-
-from .client import *
-from .protocol import *
+# $Id: __init__.py, v1.10 2013/10/21 alkorgun Exp $
+
+"""
+All features of xmpppy library contained within separate modules.
+At present there are modules:
+simplexml - XML handling routines
+protocol - jabber-objects (I.e. JID and different stanzas and sub-stanzas) handling routines.
+debug - Jacob Lundquist's debugging module. Very handy if you like colored debug.
+auth - Non-SASL and SASL stuff. You will need it to auth as a client or transport.
+transports - low level connection handling. TCP and TLS currently. HTTP support planned.
+roster - simple roster for use in clients.
+dispatcher - decision-making logic. Handles all hooks. The first who takes control over fresh stanzas.
+features - different stuff that didn't worths separating into modules
+browser - DISCO server framework. Allows to build dynamic disco tree.
+filetransfer - Currently contains only IBB stuff. Can be used for bot-to-bot transfers.
+
+Most of the classes that is defined in all these modules is an ancestors of
+class PlugIn so they share a single set of methods allowing you to compile
+a featured XMPP client. For every instance of PlugIn class the 'owner' is the class
+in what the plug was plugged. While plugging in such instance usually sets some
+methods of owner to it's own ones for easy access. All session specific info stored
+either in instance of PlugIn or in owner's instance. This is considered unhandy
+and there are plans to port 'Session' class from xmppd.py project for storing all
+session-related info. Though if you are not accessing instances variables directly
+and use only methods for access all values you should not have any problems.
+"""
+
+from . import auth
+from . import browser
+from . import commands
+from . import debug
+from . import dispatcher
+from . import features
+from . import filetransfer
+from . import plugin
+from . import protocol
+from . import roster
+from . import simplexml
+from . import transports
+
+from .client import *
+from .protocol import *
diff --git a/xmpp/auth.py b/xmpp/auth.py
index 869ead4..7149dff 100644
--- a/xmpp/auth.py
+++ b/xmpp/auth.py
@@ -1,414 +1,414 @@
-## auth.py
-##
-## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2, or (at your option)
-## any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-
-# $Id: auth.py, v1.42 2013/10/21 alkorgun Exp $
-
-"""
-Provides library with all Non-SASL and SASL authentication mechanisms.
-Can be used both for client and transport authentication.
-"""
-
-import hashlib
-from . import dispatcher
-
-from base64 import encodestring, decodestring
-from .plugin import PlugIn
-from .protocol import *
-from random import random as _random
-from re import findall as re_findall
-
-def HH(some):
- return hashlib.md5(some).hexdigest()
-
-def H(some):
- return hashlib.md5(some).digest()
-
-def C(some):
- return ":".join(some)
-
-class NonSASL(PlugIn):
- """
- Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and transport authentication.
- """
- def __init__(self, user, password, resource):
- """
- Caches username, password and resource for auth.
- """
- PlugIn.__init__(self)
- self.DBG_LINE = "gen_auth"
- self.user = user
- self.password = password
- self.resource = resource
-
- def plugin(self, owner):
- """
- Determine the best auth method (digest/0k/plain) and use it for auth.
- Returns used method name on success. Used internally.
- """
- if not self.resource:
- return self.authComponent(owner)
- self.DEBUG("Querying server about possible auth methods", "start")
- resp = owner.Dispatcher.SendAndWaitForResponse(Iq("get", NS_AUTH, payload=[Node("username", payload=[self.user])]))
- if not isResultNode(resp):
- self.DEBUG("No result node arrived! Aborting...", "error")
- return None
- iq = Iq(typ="set", node=resp)
- query = iq.getTag("query")
- query.setTagData("username", self.user)
- query.setTagData("resource", self.resource)
- if query.getTag("digest"):
- self.DEBUG("Performing digest authentication", "ok")
- hash = hashlib.sha1(owner.Dispatcher.Stream._document_attrs["id"] + self.password).hexdigest()
- query.setTagData("digest", hash)
- if query.getTag("password"):
- query.delChild("password")
- method = "digest"
- elif query.getTag("token"):
- token = query.getTagData("token")
- seq = query.getTagData("sequence")
- self.DEBUG("Performing zero-k authentication", "ok")
- hash = hashlib.sha1(hashlib.sha1(self.password).hexdigest() + token).hexdigest()
- for i in xrange(int(seq)):
- hash = hashlib.sha1(hash).hexdigest()
- query.setTagData("hash", hash)
- method = "0k"
- else:
- self.DEBUG("Sequre methods unsupported, performing plain text authentication", "warn")
- query.setTagData("password", self.password)
- method = "plain"
- resp = owner.Dispatcher.SendAndWaitForResponse(iq)
- if isResultNode(resp):
- self.DEBUG("Sucessfully authenticated with remove host.", "ok")
- owner.User = self.user
- owner.Resource = self.resource
- owner._registered_name = owner.User + "@" + owner.Server + "/" + owner.Resource
- return method
- self.DEBUG("Authentication failed!", "error")
-
- def authComponent(self, owner):
- """
- Authenticate component. Send handshake stanza and wait for result. Returns "ok" on success.
- """
- self.handshake = 0
- hash = hashlib.sha1(owner.Dispatcher.Stream._document_attrs["id"] + self.password).hexdigest()
- owner.send(Node(NS_COMPONENT_ACCEPT + " handshake", payload=[hash]))
- owner.RegisterHandler("handshake", self.handshakeHandler, xmlns=NS_COMPONENT_ACCEPT)
- while not self.handshake:
- self.DEBUG("waiting on handshake", "notify")
- owner.Process(1)
- owner._registered_name = self.user
- if self.handshake + 1:
- return "ok"
-
- def handshakeHandler(self, disp, stanza):
- """
- Handler for registering in dispatcher for accepting transport authentication.
- """
- if stanza.getName() == "handshake":
- self.handshake = 1
- else:
- self.handshake = -1
-
-class SASL(PlugIn):
- """
- Implements SASL authentication.
- """
- def __init__(self, username, password):
- PlugIn.__init__(self)
- self.username = username
- self.password = password
-
- def plugin(self, owner):
- if "version" not in self._owner.Dispatcher.Stream._document_attrs:
- self.startsasl = "not-supported"
- elif self._owner.Dispatcher.Stream.features:
- try:
- self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
- except NodeProcessed:
- pass
- else:
- self.startsasl = None
-
- def auth(self):
- """
- Start authentication. Result can be obtained via "SASL.startsasl" attribute
- and will beeither "success" or "failure". Note that successfull
- auth will take at least two Dispatcher.Process() calls.
- """
- if self.startsasl:
- pass
- elif self._owner.Dispatcher.Stream.features:
- try:
- self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
- except NodeProcessed:
- pass
- else:
- self._owner.RegisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS)
-
- def plugout(self):
- """
- Remove SASL handlers from owner's dispatcher. Used internally.
- """
- if hasattr(self._owner, "features"):
- self._owner.UnregisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS)
- if hasattr(self._owner, "challenge"):
- self._owner.UnregisterHandler("challenge", self.SASLHandler, xmlns=NS_SASL)
- if hasattr(self._owner, "failure"):
- self._owner.UnregisterHandler("failure", self.SASLHandler, xmlns=NS_SASL)
- if hasattr(self._owner, "success"):
- self._owner.UnregisterHandler("success", self.SASLHandler, xmlns=NS_SASL)
-
- def FeaturesHandler(self, conn, feats):
- """
- Used to determine if server supports SASL auth. Used internally.
- """
- if not feats.getTag("mechanisms", namespace=NS_SASL):
- self.startsasl = "not-supported"
- self.DEBUG("SASL not supported by server", "error")
- return None
- mecs = []
- for mec in feats.getTag("mechanisms", namespace=NS_SASL).getTags("mechanism"):
- mecs.append(mec.getData())
- self._owner.RegisterHandler("challenge", self.SASLHandler, xmlns=NS_SASL)
- self._owner.RegisterHandler("failure", self.SASLHandler, xmlns=NS_SASL)
- self._owner.RegisterHandler("success", self.SASLHandler, xmlns=NS_SASL)
- if "ANONYMOUS" in mecs and self.username == None:
- node = Node("auth", attrs={"xmlns": NS_SASL, "mechanism": "ANONYMOUS"})
- elif "DIGEST-MD5" in mecs:
- node = Node("auth", attrs={"xmlns": NS_SASL, "mechanism": "DIGEST-MD5"})
- elif "PLAIN" in mecs:
- sasl_data = "%s\x00%s\x00%s" % ("@".join((self.username, self._owner.Server)), self.username, self.password)
- node = Node("auth", attrs={"xmlns": NS_SASL, "mechanism": "PLAIN"}, payload=[encodestring(sasl_data).replace("\r", "").replace("\n", "")])
- else:
- self.startsasl = "failure"
- self.DEBUG("I can only use DIGEST-MD5 and PLAIN mecanisms.", "error")
- return
- self.startsasl = "in-process"
- self._owner.send(node.__str__())
- raise NodeProcessed()
-
- def SASLHandler(self, conn, challenge):
- """
- Perform next SASL auth step. Used internally.
- """
- if challenge.getNamespace() != NS_SASL:
- return None
- if challenge.getName() == "failure":
- self.startsasl = "failure"
- try:
- reason = challenge.getChildren()[0]
- except Exception:
- reason = challenge
- self.DEBUG("Failed SASL authentification: %s" % reason, "error")
- raise NodeProcessed()
- elif challenge.getName() == "success":
- self.startsasl = "success"
- self.DEBUG("Successfully authenticated with remote server.", "ok")
- handlers = self._owner.Dispatcher.dumpHandlers()
- self._owner.Dispatcher.PlugOut()
- dispatcher.Dispatcher().PlugIn(self._owner)
- self._owner.Dispatcher.restoreHandlers(handlers)
- self._owner.User = self.username
- raise NodeProcessed()
- incoming_data = challenge.getData()
- chal = {}
- data = decodestring(incoming_data)
- self.DEBUG("Got challenge:" + data, "ok")
- for pair in re_findall('(\w+\s*=\s*(?:(?:"[^"]+")|(?:[^,]+)))', data):
- key, value = [x.strip() for x in pair.split("=", 1)]
- if value[:1] == '"' and value[-1:] == '"':
- value = value[1:-1]
- chal[key] = value
- if "qop" in chal and "auth" in [x.strip() for x in chal["qop"].split(",")]:
- resp = {}
- resp["username"] = self.username
- resp["realm"] = self._owner.Server
- resp["nonce"] = chal["nonce"]
- cnonce = ""
- for i in xrange(7):
- cnonce += hex(int(_random() * 65536 * 4096))[2:]
- resp["cnonce"] = cnonce
- resp["nc"] = ("00000001")
- resp["qop"] = "auth"
- resp["digest-uri"] = "xmpp/" + self._owner.Server
- A1 = C([H(C([resp["username"], resp["realm"], self.password])), resp["nonce"], resp["cnonce"]])
- A2 = C(["AUTHENTICATE", resp["digest-uri"]])
- response = HH(C([HH(A1), resp["nonce"], resp["nc"], resp["cnonce"], resp["qop"], HH(A2)]))
- resp["response"] = response
- resp["charset"] = "utf-8"
- sasl_data = ""
- for key in ("charset", "username", "realm", "nonce", "nc", "cnonce", "digest-uri", "response", "qop"):
- if key in ("nc", "qop", "response", "charset"):
- sasl_data += "%s=%s," % (key, resp[key])
- else:
- sasl_data += "%s=\"%s\"," % (key, resp[key])
- node = Node("response", attrs={"xmlns": NS_SASL}, payload=[encodestring(sasl_data[:-1]).replace("\r", "").replace("\n", "")])
- self._owner.send(node.__str__())
- elif "rspauth" in chal:
- self._owner.send(Node("response", attrs={"xmlns": NS_SASL}).__str__())
- else:
- self.startsasl = "failure"
- self.DEBUG("Failed SASL authentification: unknown challenge", "error")
- raise NodeProcessed()
-
-class Bind(PlugIn):
- """
- Bind some JID to the current connection to allow router know of our location.
- """
- def __init__(self):
- PlugIn.__init__(self)
- self.DBG_LINE = "bind"
- self.bound = None
-
- def plugin(self, owner):
- """
- Start resource binding, if allowed at this time. Used internally.
- """
- if self._owner.Dispatcher.Stream.features:
- try:
- self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
- except NodeProcessed:
- pass
- else:
- self._owner.RegisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS)
-
- def plugout(self):
- """
- Remove Bind handler from owner's dispatcher. Used internally.
- """
- self._owner.UnregisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS)
-
- def FeaturesHandler(self, conn, feats):
- """
- Determine if server supports resource binding and set some internal attributes accordingly.
- """
- if not feats.getTag("bind", namespace=NS_BIND):
- self.bound = "failure"
- self.DEBUG("Server does not requested binding.", "error")
- return None
- if feats.getTag("session", namespace=NS_SESSION):
- self.session = 1
- else:
- self.session = -1
- self.bound = []
-
- def Bind(self, resource=None):
- """
- Perform binding. Use provided resource name or random (if not provided).
- """
- while self.bound is None and self._owner.Process(1):
- pass
- if resource:
- resource = [Node("resource", payload=[resource])]
- else:
- resource = []
- resp = self._owner.SendAndWaitForResponse(Protocol("iq", typ="set", payload=[Node("bind", attrs={"xmlns": NS_BIND}, payload=resource)]))
- if isResultNode(resp):
- self.bound.append(resp.getTag("bind").getTagData("jid"))
- self.DEBUG("Successfully bound %s." % self.bound[-1], "ok")
- jid = JID(resp.getTag("bind").getTagData("jid"))
- self._owner.User = jid.getNode()
- self._owner.Resource = jid.getResource()
- resp = self._owner.SendAndWaitForResponse(Protocol("iq", typ="set", payload=[Node("session", attrs={"xmlns": NS_SESSION})]))
- if isResultNode(resp):
- self.DEBUG("Successfully opened session.", "ok")
- self.session = 1
- return "ok"
- else:
- self.DEBUG("Session open failed.", "error")
- self.session = 0
- elif resp:
- self.DEBUG("Binding failed: %s." % resp.getTag("error"), "error")
- else:
- self.DEBUG("Binding failed: timeout expired.", "error")
- return ""
-
-class ComponentBind(PlugIn):
- """
- ComponentBind some JID to the current connection to allow router know of our location.
- """
- def __init__(self, sasl):
- PlugIn.__init__(self)
- self.DBG_LINE = "bind"
- self.bound = None
- self.needsUnregister = None
- self.sasl = sasl
-
- def plugin(self, owner):
- """
- Start resource binding, if allowed at this time. Used internally.
- """
- if not self.sasl:
- self.bound = []
- return None
- if self._owner.Dispatcher.Stream.features:
- try:
- self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
- except NodeProcessed:
- pass
- else:
- self._owner.RegisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS)
- self.needsUnregister = 1
-
- def plugout(self):
- """
- Remove ComponentBind handler from owner's dispatcher. Used internally.
- """
- if self.needsUnregister:
- self._owner.UnregisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS)
-
- def FeaturesHandler(self, conn, feats):
- """
- Determine if server supports resource binding and set some internal attributes accordingly.
- """
- if not feats.getTag("bind", namespace=NS_BIND):
- self.bound = "failure"
- self.DEBUG("Server does not requested binding.", "error")
- return None
- if feats.getTag("session", namespace=NS_SESSION):
- self.session = 1
- else:
- self.session = -1
- self.bound = []
-
- def Bind(self, domain=None):
- """
- Perform binding. Use provided domain name (if not provided).
- """
- while self.bound is None and self._owner.Process(1):
- pass
- if self.sasl:
- xmlns = NS_COMPONENT_1
- else:
- xmlns = None
- self.bindresponse = None
- ttl = dispatcher.DefaultTimeout
- self._owner.RegisterHandler("bind", self.BindHandler, xmlns=xmlns)
- self._owner.send(Protocol("bind", attrs={"name": domain}, xmlns=NS_COMPONENT_1))
- while self.bindresponse is None and self._owner.Process(1) and ttl > 0:
- ttl -= 1
- self._owner.UnregisterHandler("bind", self.BindHandler, xmlns=xmlns)
- resp = self.bindresponse
- if resp and resp.getAttr("error"):
- self.DEBUG("Binding failed: %s." % resp.getAttr("error"), "error")
- elif resp:
- self.DEBUG("Successfully bound.", "ok")
- return "ok"
- else:
- self.DEBUG("Binding failed: timeout expired.", "error")
- return ""
-
- def BindHandler(self, conn, bind):
- self.bindresponse = bind
- pass
+## auth.py
+##
+## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2, or (at your option)
+## any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+# $Id: auth.py, v1.42 2013/10/21 alkorgun Exp $
+
+"""
+Provides library with all Non-SASL and SASL authentication mechanisms.
+Can be used both for client and transport authentication.
+"""
+
+import hashlib
+from . import dispatcher
+
+from base64 import encodestring, decodestring
+from .plugin import PlugIn
+from .protocol import *
+from random import random as _random
+from re import findall as re_findall
+
+def HH(some):
+ return hashlib.md5(some).hexdigest()
+
+def H(some):
+ return hashlib.md5(some).digest()
+
+def C(some):
+ return ":".join(some)
+
+class NonSASL(PlugIn):
+ """
+ Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and transport authentication.
+ """
+ def __init__(self, user, password, resource):
+ """
+ Caches username, password and resource for auth.
+ """
+ PlugIn.__init__(self)
+ self.DBG_LINE = "gen_auth"
+ self.user = user
+ self.password = password
+ self.resource = resource
+
+ def plugin(self, owner):
+ """
+ Determine the best auth method (digest/0k/plain) and use it for auth.
+ Returns used method name on success. Used internally.
+ """
+ if not self.resource:
+ return self.authComponent(owner)
+ self.DEBUG("Querying server about possible auth methods", "start")
+ resp = owner.Dispatcher.SendAndWaitForResponse(Iq("get", NS_AUTH, payload=[Node("username", payload=[self.user])]))
+ if not isResultNode(resp):
+ self.DEBUG("No result node arrived! Aborting...", "error")
+ return None
+ iq = Iq(typ="set", node=resp)
+ query = iq.getTag("query")
+ query.setTagData("username", self.user)
+ query.setTagData("resource", self.resource)
+ if query.getTag("digest"):
+ self.DEBUG("Performing digest authentication", "ok")
+ hash = hashlib.sha1(owner.Dispatcher.Stream._document_attrs["id"] + self.password).hexdigest()
+ query.setTagData("digest", hash)
+ if query.getTag("password"):
+ query.delChild("password")
+ method = "digest"
+ elif query.getTag("token"):
+ token = query.getTagData("token")
+ seq = query.getTagData("sequence")
+ self.DEBUG("Performing zero-k authentication", "ok")
+ hash = hashlib.sha1(hashlib.sha1(self.password).hexdigest() + token).hexdigest()
+ for i in xrange(int(seq)):
+ hash = hashlib.sha1(hash).hexdigest()
+ query.setTagData("hash", hash)
+ method = "0k"
+ else:
+ self.DEBUG("Sequre methods unsupported, performing plain text authentication", "warn")
+ query.setTagData("password", self.password)
+ method = "plain"
+ resp = owner.Dispatcher.SendAndWaitForResponse(iq)
+ if isResultNode(resp):
+ self.DEBUG("Sucessfully authenticated with remove host.", "ok")
+ owner.User = self.user
+ owner.Resource = self.resource
+ owner._registered_name = owner.User + "@" + owner.Server + "/" + owner.Resource
+ return method
+ self.DEBUG("Authentication failed!", "error")
+
+ def authComponent(self, owner):
+ """
+ Authenticate component. Send handshake stanza and wait for result. Returns "ok" on success.
+ """
+ self.handshake = 0
+ hash = hashlib.sha1(owner.Dispatcher.Stream._document_attrs["id"] + self.password).hexdigest()
+ owner.send(Node(NS_COMPONENT_ACCEPT + " handshake", payload=[hash]))
+ owner.RegisterHandler("handshake", self.handshakeHandler, xmlns=NS_COMPONENT_ACCEPT)
+ while not self.handshake:
+ self.DEBUG("waiting on handshake", "notify")
+ owner.Process(1)
+ owner._registered_name = self.user
+ if self.handshake + 1:
+ return "ok"
+
+ def handshakeHandler(self, disp, stanza):
+ """
+ Handler for registering in dispatcher for accepting transport authentication.
+ """
+ if stanza.getName() == "handshake":
+ self.handshake = 1
+ else:
+ self.handshake = -1
+
+class SASL(PlugIn):
+ """
+ Implements SASL authentication.
+ """
+ def __init__(self, username, password):
+ PlugIn.__init__(self)
+ self.username = username
+ self.password = password
+
+ def plugin(self, owner):
+ if "version" not in self._owner.Dispatcher.Stream._document_attrs:
+ self.startsasl = "not-supported"
+ elif self._owner.Dispatcher.Stream.features:
+ try:
+ self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
+ except NodeProcessed:
+ pass
+ else:
+ self.startsasl = None
+
+ def auth(self):
+ """
+ Start authentication. Result can be obtained via "SASL.startsasl" attribute
+ and will beeither "success" or "failure". Note that successfull
+ auth will take at least two Dispatcher.Process() calls.
+ """
+ if self.startsasl:
+ pass
+ elif self._owner.Dispatcher.Stream.features:
+ try:
+ self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
+ except NodeProcessed:
+ pass
+ else:
+ self._owner.RegisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS)
+
+ def plugout(self):
+ """
+ Remove SASL handlers from owner's dispatcher. Used internally.
+ """
+ if hasattr(self._owner, "features"):
+ self._owner.UnregisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS)
+ if hasattr(self._owner, "challenge"):
+ self._owner.UnregisterHandler("challenge", self.SASLHandler, xmlns=NS_SASL)
+ if hasattr(self._owner, "failure"):
+ self._owner.UnregisterHandler("failure", self.SASLHandler, xmlns=NS_SASL)
+ if hasattr(self._owner, "success"):
+ self._owner.UnregisterHandler("success", self.SASLHandler, xmlns=NS_SASL)
+
+ def FeaturesHandler(self, conn, feats):
+ """
+ Used to determine if server supports SASL auth. Used internally.
+ """
+ if not feats.getTag("mechanisms", namespace=NS_SASL):
+ self.startsasl = "not-supported"
+ self.DEBUG("SASL not supported by server", "error")
+ return None
+ mecs = []
+ for mec in feats.getTag("mechanisms", namespace=NS_SASL).getTags("mechanism"):
+ mecs.append(mec.getData())
+ self._owner.RegisterHandler("challenge", self.SASLHandler, xmlns=NS_SASL)
+ self._owner.RegisterHandler("failure", self.SASLHandler, xmlns=NS_SASL)
+ self._owner.RegisterHandler("success", self.SASLHandler, xmlns=NS_SASL)
+ if "ANONYMOUS" in mecs and self.username == None:
+ node = Node("auth", attrs={"xmlns": NS_SASL, "mechanism": "ANONYMOUS"})
+ elif "DIGEST-MD5" in mecs:
+ node = Node("auth", attrs={"xmlns": NS_SASL, "mechanism": "DIGEST-MD5"})
+ elif "PLAIN" in mecs:
+ sasl_data = "%s\x00%s\x00%s" % ("@".join((self.username, self._owner.Server)), self.username, self.password)
+ node = Node("auth", attrs={"xmlns": NS_SASL, "mechanism": "PLAIN"}, payload=[encodestring(sasl_data).replace("\r", "").replace("\n", "")])
+ else:
+ self.startsasl = "failure"
+ self.DEBUG("I can only use DIGEST-MD5 and PLAIN mecanisms.", "error")
+ return
+ self.startsasl = "in-process"
+ self._owner.send(node.__str__())
+ raise NodeProcessed()
+
+ def SASLHandler(self, conn, challenge):
+ """
+ Perform next SASL auth step. Used internally.
+ """
+ if challenge.getNamespace() != NS_SASL:
+ return None
+ if challenge.getName() == "failure":
+ self.startsasl = "failure"
+ try:
+ reason = challenge.getChildren()[0]
+ except Exception:
+ reason = challenge
+ self.DEBUG("Failed SASL authentification: %s" % reason, "error")
+ raise NodeProcessed()
+ elif challenge.getName() == "success":
+ self.startsasl = "success"
+ self.DEBUG("Successfully authenticated with remote server.", "ok")
+ handlers = self._owner.Dispatcher.dumpHandlers()
+ self._owner.Dispatcher.PlugOut()
+ dispatcher.Dispatcher().PlugIn(self._owner)
+ self._owner.Dispatcher.restoreHandlers(handlers)
+ self._owner.User = self.username
+ raise NodeProcessed()
+ incoming_data = challenge.getData()
+ chal = {}
+ data = decodestring(incoming_data)
+ self.DEBUG("Got challenge:" + data, "ok")
+ for pair in re_findall('(\w+\s*=\s*(?:(?:"[^"]+")|(?:[^,]+)))', data):
+ key, value = [x.strip() for x in pair.split("=", 1)]
+ if value[:1] == '"' and value[-1:] == '"':
+ value = value[1:-1]
+ chal[key] = value
+ if "qop" in chal and "auth" in [x.strip() for x in chal["qop"].split(",")]:
+ resp = {}
+ resp["username"] = self.username
+ resp["realm"] = self._owner.Server
+ resp["nonce"] = chal["nonce"]
+ cnonce = ""
+ for i in xrange(7):
+ cnonce += hex(int(_random() * 65536 * 4096))[2:]
+ resp["cnonce"] = cnonce
+ resp["nc"] = ("00000001")
+ resp["qop"] = "auth"
+ resp["digest-uri"] = "xmpp/" + self._owner.Server
+ A1 = C([H(C([resp["username"], resp["realm"], self.password])), resp["nonce"], resp["cnonce"]])
+ A2 = C(["AUTHENTICATE", resp["digest-uri"]])
+ response = HH(C([HH(A1), resp["nonce"], resp["nc"], resp["cnonce"], resp["qop"], HH(A2)]))
+ resp["response"] = response
+ resp["charset"] = "utf-8"
+ sasl_data = ""
+ for key in ("charset", "username", "realm", "nonce", "nc", "cnonce", "digest-uri", "response", "qop"):
+ if key in ("nc", "qop", "response", "charset"):
+ sasl_data += "%s=%s," % (key, resp[key])
+ else:
+ sasl_data += "%s=\"%s\"," % (key, resp[key])
+ node = Node("response", attrs={"xmlns": NS_SASL}, payload=[encodestring(sasl_data[:-1]).replace("\r", "").replace("\n", "")])
+ self._owner.send(node.__str__())
+ elif "rspauth" in chal:
+ self._owner.send(Node("response", attrs={"xmlns": NS_SASL}).__str__())
+ else:
+ self.startsasl = "failure"
+ self.DEBUG("Failed SASL authentification: unknown challenge", "error")
+ raise NodeProcessed()
+
+class Bind(PlugIn):
+ """
+ Bind some JID to the current connection to allow router know of our location.
+ """
+ def __init__(self):
+ PlugIn.__init__(self)
+ self.DBG_LINE = "bind"
+ self.bound = None
+
+ def plugin(self, owner):
+ """
+ Start resource binding, if allowed at this time. Used internally.
+ """
+ if self._owner.Dispatcher.Stream.features:
+ try:
+ self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
+ except NodeProcessed:
+ pass
+ else:
+ self._owner.RegisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS)
+
+ def plugout(self):
+ """
+ Remove Bind handler from owner's dispatcher. Used internally.
+ """
+ self._owner.UnregisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS)
+
+ def FeaturesHandler(self, conn, feats):
+ """
+ Determine if server supports resource binding and set some internal attributes accordingly.
+ """
+ if not feats.getTag("bind", namespace=NS_BIND):
+ self.bound = "failure"
+ self.DEBUG("Server does not requested binding.", "error")
+ return None
+ if feats.getTag("session", namespace=NS_SESSION):
+ self.session = 1
+ else:
+ self.session = -1
+ self.bound = []
+
+ def Bind(self, resource=None):
+ """
+ Perform binding. Use provided resource name or random (if not provided).
+ """
+ while self.bound is None and self._owner.Process(1):
+ pass
+ if resource:
+ resource = [Node("resource", payload=[resource])]
+ else:
+ resource = []
+ resp = self._owner.SendAndWaitForResponse(Protocol("iq", typ="set", payload=[Node("bind", attrs={"xmlns": NS_BIND}, payload=resource)]))
+ if isResultNode(resp):
+ self.bound.append(resp.getTag("bind").getTagData("jid"))
+ self.DEBUG("Successfully bound %s." % self.bound[-1], "ok")
+ jid = JID(resp.getTag("bind").getTagData("jid"))
+ self._owner.User = jid.getNode()
+ self._owner.Resource = jid.getResource()
+ resp = self._owner.SendAndWaitForResponse(Protocol("iq", typ="set", payload=[Node("session", attrs={"xmlns": NS_SESSION})]))
+ if isResultNode(resp):
+ self.DEBUG("Successfully opened session.", "ok")
+ self.session = 1
+ return "ok"
+ else:
+ self.DEBUG("Session open failed.", "error")
+ self.session = 0
+ elif resp:
+ self.DEBUG("Binding failed: %s." % resp.getTag("error"), "error")
+ else:
+ self.DEBUG("Binding failed: timeout expired.", "error")
+ return ""
+
+class ComponentBind(PlugIn):
+ """
+ ComponentBind some JID to the current connection to allow router know of our location.
+ """
+ def __init__(self, sasl):
+ PlugIn.__init__(self)
+ self.DBG_LINE = "bind"
+ self.bound = None
+ self.needsUnregister = None
+ self.sasl = sasl
+
+ def plugin(self, owner):
+ """
+ Start resource binding, if allowed at this time. Used internally.
+ """
+ if not self.sasl:
+ self.bound = []
+ return None
+ if self._owner.Dispatcher.Stream.features:
+ try:
+ self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
+ except NodeProcessed:
+ pass
+ else:
+ self._owner.RegisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS)
+ self.needsUnregister = 1
+
+ def plugout(self):
+ """
+ Remove ComponentBind handler from owner's dispatcher. Used internally.
+ """
+ if self.needsUnregister:
+ self._owner.UnregisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS)
+
+ def FeaturesHandler(self, conn, feats):
+ """
+ Determine if server supports resource binding and set some internal attributes accordingly.
+ """
+ if not feats.getTag("bind", namespace=NS_BIND):
+ self.bound = "failure"
+ self.DEBUG("Server does not requested binding.", "error")
+ return None
+ if feats.getTag("session", namespace=NS_SESSION):
+ self.session = 1
+ else:
+ self.session = -1
+ self.bound = []
+
+ def Bind(self, domain=None):
+ """
+ Perform binding. Use provided domain name (if not provided).
+ """
+ while self.bound is None and self._owner.Process(1):
+ pass
+ if self.sasl:
+ xmlns = NS_COMPONENT_1
+ else:
+ xmlns = None
+ self.bindresponse = None
+ ttl = dispatcher.DefaultTimeout
+ self._owner.RegisterHandler("bind", self.BindHandler, xmlns=xmlns)
+ self._owner.send(Protocol("bind", attrs={"name": domain}, xmlns=NS_COMPONENT_1))
+ while self.bindresponse is None and self._owner.Process(1) and ttl > 0:
+ ttl -= 1
+ self._owner.UnregisterHandler("bind", self.BindHandler, xmlns=xmlns)
+ resp = self.bindresponse
+ if resp and resp.getAttr("error"):
+ self.DEBUG("Binding failed: %s." % resp.getAttr("error"), "error")
+ elif resp:
+ self.DEBUG("Successfully bound.", "ok")
+ return "ok"
+ else:
+ self.DEBUG("Binding failed: timeout expired.", "error")
+ return ""
+
+ def BindHandler(self, conn, bind):
+ self.bindresponse = bind
+ pass
diff --git a/xmpp/browser.py b/xmpp/browser.py
index 5ecae1e..84d4049 100644
--- a/xmpp/browser.py
+++ b/xmpp/browser.py
@@ -1,261 +1,261 @@
-## browser.py
-##
-## Copyright (C) 2004 Alexey "Snake" Nezhdanov
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2, or (at your option)
-## any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-
-# $Id: browser.py, v1.13 2013/11/03 alkorgun Exp $
-
-"""
-Browser module provides DISCO server framework for your application.
-This functionality can be used for very different purposes - from publishing
-software version and supported features to building of "jabber site" that users
-can navigate with their disco browsers and interact with active content.
-
-Such functionality is achieved via registering "DISCO handlers" that are
-automatically called when user requests some node of your disco tree.
-"""
-
-from .dispatcher import *
-from .plugin import PlugIn
-
-class Browser(PlugIn):
- """
- WARNING! This class is for components only. It will not work in client mode!
-
- Standart xmpppy class that is ancestor of PlugIn and can be attached
- to your application.
- All processing will be performed in the handlers registered in the browser
- instance. You can register any number of handlers ensuring that for each
- node/jid combination only one (or none) handler registered.
- You can register static information or the fully-blown function that will
- calculate the answer dynamically.
- Example of static info (see JEP-0030, examples 13-14):
- # cl - your xmpppy connection instance.
- b = xmpp.browser.Browser()
- b.PlugIn(cl)
- items = []
- item = {}
- item["jid"] = "catalog.shakespeare.lit"
- item["node"] = "books"
- item["name"] = "Books by and about Shakespeare"
- items.append(item)
- item = {}
- item["jid"] = "catalog.shakespeare.lit"
- item["node"] = "clothing"
- item["name"] = "Wear your literary taste with pride"
- items.append(item)
- item = {}
- item["jid"] = "catalog.shakespeare.lit"
- item["node"] = "music"
- item["name"] = "Music from the time of Shakespeare"
- items.append(item)
- info = {"ids": [], "features": []}
- b.setDiscoHandler({"items": items, "info": info})
-
- items should be a list of item elements.
- every item element can have any of these four keys: "jid", "node", "name", "action"
- info should be a dicionary and must have keys "ids" and "features".
- Both of them should be lists:
- ids is a list of dictionaries and features is a list of text strings.
- Example (see JEP-0030, examples 1-2)
- # cl - your xmpppy connection instance.
- b = xmpp.browser.Browser()
- b.PlugIn(cl)
- items = []
- ids = []
- ids.append({"category": "conference", "type": "text", "name": "Play-Specific Chatrooms"})
- ids.append({"category": "directory", "type": "chatroom", "name": "Play-Specific Chatrooms"})
- features = [
- NS_DISCO_INFO,
- NS_DISCO_ITEMS,
- NS_MUC,
- NS_REGISTER,
- NS_SEARCH,
- NS_TIME,
- NS_VERSION
- ]
- info = {"ids": ids, "features": features}
- # info["xdata"] = xmpp.protocol.DataForm() # JEP-0128
- b.setDiscoHandler({"items": [], "info": info})
- """
- def __init__(self):
- """
- Initialises internal variables. Used internally.
- """
- PlugIn.__init__(self)
- DBG_LINE = "browser"
- self._exported_methods = []
- self._handlers = {"": {}}
-
- def plugin(self, owner):
- """
- Registers it's own iq handlers in your application dispatcher instance.
- Used internally.
- """
- owner.RegisterHandler("iq", self._DiscoveryHandler, typ="get", ns=NS_DISCO_INFO)
- owner.RegisterHandler("iq", self._DiscoveryHandler, typ="get", ns=NS_DISCO_ITEMS)
-
- def plugout(self):
- """
- Unregisters browser's iq handlers from your application dispatcher instance.
- Used internally.
- """
- self._owner.UnregisterHandler("iq", self._DiscoveryHandler, typ="get", ns=NS_DISCO_INFO)
- self._owner.UnregisterHandler("iq", self._DiscoveryHandler, typ="get", ns=NS_DISCO_ITEMS)
-
- def _traversePath(self, node, jid, set=0):
- """
- Returns dictionary and key or None,None
- None - root node (w/o "node" attribute)
- /a/b/c - node
- /a/b/ - branch
- Set returns "" or None as the key
- get returns "" or None as the key or None as the dict.
- Used internally.
- """
- if jid in self._handlers:
- cur = self._handlers[jid]
- elif set:
- self._handlers[jid] = {}
- cur = self._handlers[jid]
- else:
- cur = self._handlers[""]
- if node is None:
- node = [None]
- else:
- node = node.replace("/", " /").split("/")
- for i in node:
- if i != "" and i in cur:
- cur = cur[i]
- elif set and i != "":
- cur[i] = {dict: cur, str: i}
- cur = cur[i]
- elif set or "" in cur:
- return cur, ""
- else:
- return None, None
- if 1 in cur or set:
- return cur, 1
- raise Exception("Corrupted data")
-
- def setDiscoHandler(self, handler, node="", jid=""):
- """
- This is the main method that you will use in this class.
- It is used to register supplied DISCO handler (or dictionary with static info)
- as handler of some disco tree branch.
- If you do not specify the node this handler will be used for all queried nodes.
- If you do not specify the jid this handler will be used for all queried JIDs.
-
- Usage:
- cl.Browser.setDiscoHandler(someDict, node, jid)
- or
- cl.Browser.setDiscoHandler(someDISCOHandler, node, jid)
- where
-
- someDict = {
- "items":[
- {"jid": "jid2", "action": "action2", "node":"node2", "name": "name2"},
- {"jid": "jid4", "node": "node4"}
- ],
- "info" :{
- "ids":[
- {"category":" category1", "type": "type1", "name": "name1"},
- {"category":" category3", "type": "type3", "name": "name3"},
- ],
- "features": ["feature1", "feature2", "feature3", "feature4"],
- "xdata": DataForm
- }
- }
-
- and/or
-
- def someDISCOHandler(session,request,TYR):
- # if TYR == "items": # returns items list of the same format as shown above
- # elif TYR == "info": # returns info dictionary of the same format as shown above
- # else: # this case is impossible for now.
- """
- self.DEBUG("Registering handler %s for \"%s\" node->%s" % (handler, jid, node), "info")
- node, key = self._traversePath(node, jid, 1)
- node[key] = handler
-
- def getDiscoHandler(self, node="", jid=""):
- """
- Returns the previously registered DISCO handler
- that is resonsible for this node/jid combination.
- Used internally.
- """
- node, key = self._traversePath(node, jid)
- if node:
- return node[key]
-
- def delDiscoHandler(self, node="", jid=""):
- """
- Unregisters DISCO handler that is resonsible for this
- node/jid combination. When handler is unregistered the branch
- is handled in the same way that it's parent branch from this moment.
- """
- node, key = self._traversePath(node, jid)
- if node:
- handler = node[key]
- del node[dict][node[str]]
- return handler
-
- def _DiscoveryHandler(self, conn, request):
- """
- Servers DISCO iq request from the remote client.
- Automatically determines the best handler to use and calls it
- (to handle the request. Used internally.
- """
- node = request.getQuerynode()
- if node:
- nodestr = node
- else:
- nodestr = "None"
- handler = self.getDiscoHandler(node, request.getTo())
- if not handler:
- self.DEBUG("No Handler for request with jid->%s node->%s ns->%s" % (request.getTo().__str__().encode("utf8"), nodestr.encode("utf8"), request.getQueryNS().encode("utf8")), "error")
- conn.send(Error(request, ERR_ITEM_NOT_FOUND))
- raise NodeProcessed()
- self.DEBUG("Handling request with jid->%s node->%s ns->%s" % (request.getTo().__str__().encode("utf8"), nodestr.encode("utf8"), request.getQueryNS().encode("utf8")), "ok")
- rep = request.buildReply("result")
- if node:
- rep.setQuerynode(node)
- q = rep.getTag("query")
- if request.getQueryNS() == NS_DISCO_ITEMS:
- # handler must return list: [{jid, action, node, name}]
- if isinstance(handler, dict):
- lst = handler["items"]
- else:
- lst = handler(conn, request, "items")
- if lst == None:
- conn.send(Error(request, ERR_ITEM_NOT_FOUND))
- raise NodeProcessed()
- for item in lst:
- q.addChild("item", item)
- elif request.getQueryNS() == NS_DISCO_INFO:
- if isinstance(handler, dict):
- dt = handler["info"]
- else:
- dt = handler(conn, request, "info")
- if dt == None:
- conn.send(Error(request, ERR_ITEM_NOT_FOUND))
- raise NodeProcessed()
- # handler must return dictionary:
- # {"ids": [{}, {}, {}, {}], "features": [fe, at, ur, es], "xdata": DataForm}
- for id in dt["ids"]:
- q.addChild("identity", id)
- for feature in dt["features"]:
- q.addChild("feature", {"var": feature})
- if "xdata" in dt:
- q.addChild(node=dt["xdata"])
- conn.send(rep)
- raise NodeProcessed()
+## browser.py
+##
+## Copyright (C) 2004 Alexey "Snake" Nezhdanov
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2, or (at your option)
+## any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+# $Id: browser.py, v1.13 2013/11/03 alkorgun Exp $
+
+"""
+Browser module provides DISCO server framework for your application.
+This functionality can be used for very different purposes - from publishing
+software version and supported features to building of "jabber site" that users
+can navigate with their disco browsers and interact with active content.
+
+Such functionality is achieved via registering "DISCO handlers" that are
+automatically called when user requests some node of your disco tree.
+"""
+
+from .dispatcher import *
+from .plugin import PlugIn
+
+class Browser(PlugIn):
+ """
+ WARNING! This class is for components only. It will not work in client mode!
+
+ Standart xmpppy class that is ancestor of PlugIn and can be attached
+ to your application.
+ All processing will be performed in the handlers registered in the browser
+ instance. You can register any number of handlers ensuring that for each
+ node/jid combination only one (or none) handler registered.
+ You can register static information or the fully-blown function that will
+ calculate the answer dynamically.
+ Example of static info (see JEP-0030, examples 13-14):
+ # cl - your xmpppy connection instance.
+ b = xmpp.browser.Browser()
+ b.PlugIn(cl)
+ items = []
+ item = {}
+ item["jid"] = "catalog.shakespeare.lit"
+ item["node"] = "books"
+ item["name"] = "Books by and about Shakespeare"
+ items.append(item)
+ item = {}
+ item["jid"] = "catalog.shakespeare.lit"
+ item["node"] = "clothing"
+ item["name"] = "Wear your literary taste with pride"
+ items.append(item)
+ item = {}
+ item["jid"] = "catalog.shakespeare.lit"
+ item["node"] = "music"
+ item["name"] = "Music from the time of Shakespeare"
+ items.append(item)
+ info = {"ids": [], "features": []}
+ b.setDiscoHandler({"items": items, "info": info})
+
+ items should be a list of item elements.
+ every item element can have any of these four keys: "jid", "node", "name", "action"
+ info should be a dicionary and must have keys "ids" and "features".
+ Both of them should be lists:
+ ids is a list of dictionaries and features is a list of text strings.
+ Example (see JEP-0030, examples 1-2)
+ # cl - your xmpppy connection instance.
+ b = xmpp.browser.Browser()
+ b.PlugIn(cl)
+ items = []
+ ids = []
+ ids.append({"category": "conference", "type": "text", "name": "Play-Specific Chatrooms"})
+ ids.append({"category": "directory", "type": "chatroom", "name": "Play-Specific Chatrooms"})
+ features = [
+ NS_DISCO_INFO,
+ NS_DISCO_ITEMS,
+ NS_MUC,
+ NS_REGISTER,
+ NS_SEARCH,
+ NS_TIME,
+ NS_VERSION
+ ]
+ info = {"ids": ids, "features": features}
+ # info["xdata"] = xmpp.protocol.DataForm() # JEP-0128
+ b.setDiscoHandler({"items": [], "info": info})
+ """
+ def __init__(self):
+ """
+ Initialises internal variables. Used internally.
+ """
+ PlugIn.__init__(self)
+ DBG_LINE = "browser"
+ self._exported_methods = []
+ self._handlers = {"": {}}
+
+ def plugin(self, owner):
+ """
+ Registers it's own iq handlers in your application dispatcher instance.
+ Used internally.
+ """
+ owner.RegisterHandler("iq", self._DiscoveryHandler, typ="get", ns=NS_DISCO_INFO)
+ owner.RegisterHandler("iq", self._DiscoveryHandler, typ="get", ns=NS_DISCO_ITEMS)
+
+ def plugout(self):
+ """
+ Unregisters browser's iq handlers from your application dispatcher instance.
+ Used internally.
+ """
+ self._owner.UnregisterHandler("iq", self._DiscoveryHandler, typ="get", ns=NS_DISCO_INFO)
+ self._owner.UnregisterHandler("iq", self._DiscoveryHandler, typ="get", ns=NS_DISCO_ITEMS)
+
+ def _traversePath(self, node, jid, set=0):
+ """
+ Returns dictionary and key or None,None
+ None - root node (w/o "node" attribute)
+ /a/b/c - node
+ /a/b/ - branch
+ Set returns "" or None as the key
+ get returns "" or None as the key or None as the dict.
+ Used internally.
+ """
+ if jid in self._handlers:
+ cur = self._handlers[jid]
+ elif set:
+ self._handlers[jid] = {}
+ cur = self._handlers[jid]
+ else:
+ cur = self._handlers[""]
+ if node is None:
+ node = [None]
+ else:
+ node = node.replace("/", " /").split("/")
+ for i in node:
+ if i != "" and i in cur:
+ cur = cur[i]
+ elif set and i != "":
+ cur[i] = {dict: cur, str: i}
+ cur = cur[i]
+ elif set or "" in cur:
+ return cur, ""
+ else:
+ return None, None
+ if 1 in cur or set:
+ return cur, 1
+ raise Exception("Corrupted data")
+
+ def setDiscoHandler(self, handler, node="", jid=""):
+ """
+ This is the main method that you will use in this class.
+ It is used to register supplied DISCO handler (or dictionary with static info)
+ as handler of some disco tree branch.
+ If you do not specify the node this handler will be used for all queried nodes.
+ If you do not specify the jid this handler will be used for all queried JIDs.
+
+ Usage:
+ cl.Browser.setDiscoHandler(someDict, node, jid)
+ or
+ cl.Browser.setDiscoHandler(someDISCOHandler, node, jid)
+ where
+
+ someDict = {
+ "items":[
+ {"jid": "jid2", "action": "action2", "node":"node2", "name": "name2"},
+ {"jid": "jid4", "node": "node4"}
+ ],
+ "info" :{
+ "ids":[
+ {"category":" category1", "type": "type1", "name": "name1"},
+ {"category":" category3", "type": "type3", "name": "name3"},
+ ],
+ "features": ["feature1", "feature2", "feature3", "feature4"],
+ "xdata": DataForm
+ }
+ }
+
+ and/or
+
+ def someDISCOHandler(session,request,TYR):
+ # if TYR == "items": # returns items list of the same format as shown above
+ # elif TYR == "info": # returns info dictionary of the same format as shown above
+ # else: # this case is impossible for now.
+ """
+ self.DEBUG("Registering handler %s for \"%s\" node->%s" % (handler, jid, node), "info")
+ node, key = self._traversePath(node, jid, 1)
+ node[key] = handler
+
+ def getDiscoHandler(self, node="", jid=""):
+ """
+ Returns the previously registered DISCO handler
+ that is resonsible for this node/jid combination.
+ Used internally.
+ """
+ node, key = self._traversePath(node, jid)
+ if node:
+ return node[key]
+
+ def delDiscoHandler(self, node="", jid=""):
+ """
+ Unregisters DISCO handler that is resonsible for this
+ node/jid combination. When handler is unregistered the branch
+ is handled in the same way that it's parent branch from this moment.
+ """
+ node, key = self._traversePath(node, jid)
+ if node:
+ handler = node[key]
+ del node[dict][node[str]]
+ return handler
+
+ def _DiscoveryHandler(self, conn, request):
+ """
+ Servers DISCO iq request from the remote client.
+ Automatically determines the best handler to use and calls it
+ (to handle the request. Used internally.
+ """
+ node = request.getQuerynode()
+ if node:
+ nodestr = node
+ else:
+ nodestr = "None"
+ handler = self.getDiscoHandler(node, request.getTo())
+ if not handler:
+ self.DEBUG("No Handler for request with jid->%s node->%s ns->%s" % (request.getTo().__str__().encode("utf8"), nodestr.encode("utf8"), request.getQueryNS().encode("utf8")), "error")
+ conn.send(Error(request, ERR_ITEM_NOT_FOUND))
+ raise NodeProcessed()
+ self.DEBUG("Handling request with jid->%s node->%s ns->%s" % (request.getTo().__str__().encode("utf8"), nodestr.encode("utf8"), request.getQueryNS().encode("utf8")), "ok")
+ rep = request.buildReply("result")
+ if node:
+ rep.setQuerynode(node)
+ q = rep.getTag("query")
+ if request.getQueryNS() == NS_DISCO_ITEMS:
+ # handler must return list: [{jid, action, node, name}]
+ if isinstance(handler, dict):
+ lst = handler["items"]
+ else:
+ lst = handler(conn, request, "items")
+ if lst == None:
+ conn.send(Error(request, ERR_ITEM_NOT_FOUND))
+ raise NodeProcessed()
+ for item in lst:
+ q.addChild("item", item)
+ elif request.getQueryNS() == NS_DISCO_INFO:
+ if isinstance(handler, dict):
+ dt = handler["info"]
+ else:
+ dt = handler(conn, request, "info")
+ if dt == None:
+ conn.send(Error(request, ERR_ITEM_NOT_FOUND))
+ raise NodeProcessed()
+ # handler must return dictionary:
+ # {"ids": [{}, {}, {}, {}], "features": [fe, at, ur, es], "xdata": DataForm}
+ for id in dt["ids"]:
+ q.addChild("identity", id)
+ for feature in dt["features"]:
+ q.addChild("feature", {"var": feature})
+ if "xdata" in dt:
+ q.addChild(node=dt["xdata"])
+ conn.send(rep)
+ raise NodeProcessed()
diff --git a/xmpp/client.py b/xmpp/client.py
index 783ffd6..13f5d99 100644
--- a/xmpp/client.py
+++ b/xmpp/client.py
@@ -1,375 +1,375 @@
-## client.py
-##
-## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2, or (at your option)
-## any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-
-# $Id: client.py, v1.62 2013/10/21 alkorgun Exp $
-
-"""
-Provides PlugIn class functionality to develop extentions for xmpppy.
-Also provides Client and Component classes implementations as the
-examples of xmpppy structures usage.
-These classes can be used for simple applications "AS IS" though.
-"""
-
-from . import debug
-from . import transports
-from . import dispatcher
-from . import auth
-from . import roster
-
-from .plugin import PlugIn
-
-Debug = debug
-Debug.DEBUGGING_IS_ON = 1
-
-Debug.Debug.colors["socket"] = debug.color_dark_gray
-Debug.Debug.colors["CONNECTproxy"] = debug.color_dark_gray
-Debug.Debug.colors["nodebuilder"] = debug.color_brown
-Debug.Debug.colors["client"] = debug.color_cyan
-Debug.Debug.colors["component"] = debug.color_cyan
-Debug.Debug.colors["dispatcher"] = debug.color_green
-Debug.Debug.colors["browser"] = debug.color_blue
-Debug.Debug.colors["auth"] = debug.color_yellow
-Debug.Debug.colors["roster"] = debug.color_magenta
-Debug.Debug.colors["ibb"] = debug.color_yellow
-Debug.Debug.colors["down"] = debug.color_brown
-Debug.Debug.colors["up"] = debug.color_brown
-Debug.Debug.colors["data"] = debug.color_brown
-Debug.Debug.colors["ok"] = debug.color_green
-Debug.Debug.colors["warn"] = debug.color_yellow
-Debug.Debug.colors["error"] = debug.color_red
-Debug.Debug.colors["start"] = debug.color_dark_gray
-Debug.Debug.colors["stop"] = debug.color_dark_gray
-Debug.Debug.colors["sent"] = debug.color_yellow
-Debug.Debug.colors["got"] = debug.color_bright_cyan
-
-DBG_CLIENT = "client"
-DBG_COMPONENT = "component"
-
-
-class CommonClient:
- """
- Base for Client and Component classes.
- """
- def __init__(self, server, port=5222, debug=["always", "nodebuilder"]):
- """
- Caches server name and (optionally) port to connect to. "debug" parameter specifies
- the debug IDs that will go into debug output. You can either specifiy an "include"
- or "exclude" list. The latter is done via adding "always" pseudo-ID to the list.
- Full list: ["nodebuilder", "dispatcher", "gen_auth", "SASL_auth", "bind", "socket",
- "CONNECTproxy", "TLS", "roster", "browser", "ibb"].
- """
- if isinstance(self, Client):
- self.Namespace, self.DBG = "jabber:client", DBG_CLIENT
- elif isinstance(self, Component):
- self.Namespace, self.DBG = dispatcher.NS_COMPONENT_ACCEPT, DBG_COMPONENT
- self.defaultNamespace = self.Namespace
- self.disconnect_handlers = []
- self.Server = server
- self.Port = port
- if debug and not isinstance(debug, list):
- debug = ["always", "nodebuilder"]
- self._DEBUG = Debug.Debug(debug)
- self.DEBUG = self._DEBUG.Show
- self.debug_flags = self._DEBUG.debug_flags
- self.debug_flags.append(self.DBG)
- self._owner = self
- self._registered_name = None
- self.RegisterDisconnectHandler(self.DisconnectHandler)
- self.connected = ""
- self._route = 0
-
- def RegisterDisconnectHandler(self, handler):
- """
- Register handler that will be called on disconnect.
- """
- self.disconnect_handlers.append(handler)
-
- def UnregisterDisconnectHandler(self, handler):
- """
- Unregister handler that is called on disconnect.
- """
- self.disconnect_handlers.remove(handler)
-
- def disconnected(self):
- """
- Called on disconnection. Calls disconnect handlers and cleans things up.
- """
- self.connected = ""
- self.DEBUG(self.DBG, "Disconnect detected", "stop")
- self.disconnect_handlers.reverse()
- for dhnd in self.disconnect_handlers:
- dhnd()
- self.disconnect_handlers.reverse()
- if hasattr(self, "TLS"):
- self.TLS.PlugOut()
-
- def DisconnectHandler(self):
- """
- Default disconnect handler. Just raises an IOError.
- If you choosed to use this class in your production client,
- override this method or at least unregister it.
- """
- raise IOError("Disconnected!")
-
- def event(self, eventName, args={}):
- """
- Default event handler. To be overriden.
- """
- print("Event: %s-%s" % (eventName, args))
-
- def isConnected(self):
- """
- Returns connection state. F.e.: None / "tls" / "tcp+non_sasl" .
- """
- return self.connected
-
- def reconnectAndReauth(self, handlerssave=None):
- """
- Example of reconnection method. In fact, it can be used to batch connection and auth as well.
- """
- Dispatcher_ = False
- if not handlerssave:
- Dispatcher_, handlerssave = True, self.Dispatcher.dumpHandlers()
- if hasattr(self, "ComponentBind"):
- self.ComponentBind.PlugOut()
- if hasattr(self, "Bind"):
- self.Bind.PlugOut()
- self._route = 0
- if hasattr(self, "NonSASL"):
- self.NonSASL.PlugOut()
- if hasattr(self, "SASL"):
- self.SASL.PlugOut()
- if hasattr(self, "TLS"):
- self.TLS.PlugOut()
- if Dispatcher_:
- self.Dispatcher.PlugOut()
- if hasattr(self, "HTTPPROXYsocket"):
- self.HTTPPROXYsocket.PlugOut()
- if hasattr(self, "TCPsocket"):
- self.TCPsocket.PlugOut()
- if not self.connect(server=self._Server, proxy=self._Proxy):
- return None
- if not self.auth(self._User, self._Password, self._Resource):
- return None
- self.Dispatcher.restoreHandlers(handlerssave)
- return self.connected
-
- def connect(self, server=None, proxy=None, ssl=None, use_srv=False):
- """
- Make a tcp/ip connection, protect it with tls/ssl if possible and start XMPP stream.
- Returns None or "tcp" or "tls", depending on the result.
- """
- if not server:
- server = (self.Server, self.Port)
- if proxy:
- sock = transports.HTTPPROXYsocket(proxy, server, use_srv)
- else:
- sock = transports.TCPsocket(server, use_srv)
- connected = sock.PlugIn(self)
- if not connected:
- sock.PlugOut()
- return None
- self._Server, self._Proxy = server, proxy
- self.connected = "tcp"
- if (ssl is None and self.Connection.getPort() in (5223, 443)) or ssl:
- try: # FIXME. This should be done in transports.py
- transports.TLS().PlugIn(self, now=1)
- self.connected = "ssl"
- except transports.socket.sslerror:
- return None
- dispatcher.Dispatcher().PlugIn(self)
- while self.Dispatcher.Stream._document_attrs is None:
- if not self.Process(1):
- return None
- if "version" in self.Dispatcher.Stream._document_attrs and self.Dispatcher.Stream._document_attrs["version"] == "1.0":
- while not self.Dispatcher.Stream.features and self.Process(1):
- pass # If we get version 1.0 stream the features tag MUST BE presented
- return self.connected
-
-class Client(CommonClient):
- """
- Example client class, based on CommonClient.
- """
- def connect(self, server=None, proxy=None, secure=None, use_srv=True):
- """
- Connect to jabber server. If you want to specify different ip/port to connect to you can
- pass it as tuple as first parameter. If there is HTTP proxy between you and server
- specify it's address and credentials (if needed) in the second argument.
- If you want ssl/tls support to be discovered and enable automatically - leave third argument as None. (ssl will be autodetected only if port is 5223 or 443)
- If you want to force SSL start (i.e. if port 5223 or 443 is remapped to some non-standard port) then set it to 1.
- If you want to disable tls/ssl support completely, set it to 0.
- Example: connect(("192.168.5.5", 5222), {"host": "proxy.my.net", "port": 8080, "user": "me", "password": "secret"})
- Returns "" or "tcp" or "tls", depending on the result.
- """
- if not CommonClient.connect(self, server, proxy, secure, use_srv) or secure != None and not secure:
- return self.connected
- transports.TLS().PlugIn(self)
- if not hasattr(self, "Dispatcher"):
- return None
- if "version" not in self.Dispatcher.Stream._document_attrs or not self.Dispatcher.Stream._document_attrs["version"] == "1.0":
- return self.connected
- while not self.Dispatcher.Stream.features and self.Process(1):
- pass # If we get version 1.0 stream the features tag MUST BE presented
- if not self.Dispatcher.Stream.features.getTag("starttls"):
- return self.connected # TLS not supported by server
- while not self.TLS.starttls and self.Process(1):
- pass
- if not hasattr(self, "TLS") or self.TLS.starttls != "success":
- self.event("tls_failed")
- return self.connected
- self.connected = "tls"
- return self.connected
-
- def auth(self, user, password, resource="", sasl=1):
- """
- Authenticate connnection and bind resource. If resource is not provided
- random one or library name used.
- """
- self._User, self._Password, self._Resource = user, password, resource
- while not self.Dispatcher.Stream._document_attrs and self.Process(1):
- pass
- if "version" in self.Dispatcher.Stream._document_attrs and self.Dispatcher.Stream._document_attrs["version"] == "1.0":
- while not self.Dispatcher.Stream.features and self.Process(1):
- pass # If we get version 1.0 stream the features tag MUST BE presented
- if sasl:
- auth.SASL(user, password).PlugIn(self)
- if not sasl or self.SASL.startsasl == "not-supported":
- if not resource:
- resource = "xmpppy"
- if auth.NonSASL(user, password, resource).PlugIn(self):
- self.connected += "+old_auth"
- return "old_auth"
- return None
- self.SASL.auth()
- while self.SASL.startsasl == "in-process" and self.Process(1):
- pass
- if self.SASL.startsasl == "success":
- auth.Bind().PlugIn(self)
- while self.Bind.bound is None and self.Process(1):
- pass
- if self.Bind.Bind(resource):
- self.connected += "+sasl"
- return "sasl"
- elif hasattr(self, "SASL"):
- self.SASL.PlugOut()
-
- def getRoster(self):
- """
- Return the Roster instance, previously plugging it in and
- requesting roster from server if needed.
- """
- if not hasattr(self, "Roster"):
- roster.Roster().PlugIn(self)
- return self.Roster.getRoster()
-
- def sendInitPresence(self, requestRoster=1):
- """
- Send roster request and initial <presence/>.
- You can disable the first by setting requestRoster argument to 0.
- """
- self.sendPresence(requestRoster=requestRoster)
-
- def sendPresence(self, jid=None, typ=None, requestRoster=0):
- """
- Send some specific presence state.
- Can also request roster from server if according agrument is set.
- """
- if requestRoster:
- roster.Roster().PlugIn(self)
- self.send(dispatcher.Presence(to=jid, typ=typ))
-
-class Component(CommonClient):
- """
- Component class. The only difference from CommonClient is ability to perform component authentication.
- """
- def __init__(self, transport, port=5347, typ=None, debug=["always", "nodebuilder"], domains=None, sasl=0, bind=0, route=0, xcp=0):
- """
- Init function for Components.
- As components use a different auth mechanism which includes the namespace of the component.
- Jabberd1.4 and Ejabberd use the default namespace then for all client messages.
- Jabberd2 uses jabber:client.
- "transport" argument is a transport name that you are going to serve (f.e. "irc.localhost").
- "port" can be specified if "transport" resolves to correct IP. If it is not then you'll have to specify IP
- and port while calling "connect()".
- If you are going to serve several different domains with single Component instance - you must list them ALL
- in the "domains" argument.
- For jabberd2 servers you should set typ="jabberd2" argument.
- """
- CommonClient.__init__(self, transport, port=port, debug=debug)
- self.typ = typ
- self.sasl = sasl
- self.bind = bind
- self.route = route
- self.xcp = xcp
- if domains:
- self.domains = domains
- else:
- self.domains = [transport]
-
- def connect(self, server=None, proxy=None):
- """
- This will connect to the server, and if the features tag is found then set
- the namespace to be jabber:client as that is required for jabberd2.
- "server" and "proxy" arguments have the same meaning as in xmpp.Client.connect().
- """
- if self.sasl:
- self.Namespace = auth.NS_COMPONENT_1
- self.Server = server[0]
- CommonClient.connect(self, server=server, proxy=proxy)
- if self.connected and (self.typ == "jabberd2" or not self.typ and self.Dispatcher.Stream.features != None) and (not self.xcp):
- self.defaultNamespace = auth.NS_CLIENT
- self.Dispatcher.RegisterNamespace(self.defaultNamespace)
- self.Dispatcher.RegisterProtocol("iq", dispatcher.Iq)
- self.Dispatcher.RegisterProtocol("message", dispatcher.Message)
- self.Dispatcher.RegisterProtocol("presence", dispatcher.Presence)
- return self.connected
-
- def dobind(self, sasl):
- # This has to be done before binding, because we can receive a route stanza before binding finishes
- self._route = self.route
- if self.bind:
- for domain in self.domains:
- auth.ComponentBind(sasl).PlugIn(self)
- while self.ComponentBind.bound is None:
- self.Process(1)
- if (not self.ComponentBind.Bind(domain)):
- self.ComponentBind.PlugOut()
- return None
- self.ComponentBind.PlugOut()
-
- def auth(self, name, password, dup=None):
- """
- Authenticate component "name" with password "password".
- """
- self._User, self._Password, self._Resource = name, password, ""
- try:
- if self.sasl:
- auth.SASL(name, password).PlugIn(self)
- if not self.sasl or self.SASL.startsasl == "not-supported":
- if auth.NonSASL(name, password, "").PlugIn(self):
- self.dobind(sasl=False)
- self.connected += "+old_auth"
- return "old_auth"
- return None
- self.SASL.auth()
- while self.SASL.startsasl == "in-process" and self.Process(1):
- pass
- if self.SASL.startsasl == "success":
- self.dobind(sasl=True)
- self.connected += "+sasl"
- return "sasl"
- else:
- raise auth.NotAuthorized(self.SASL.startsasl)
- except Exception:
- self.DEBUG(self.DBG, "Failed to authenticate %s" % name, "error")
+## client.py
+##
+## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2, or (at your option)
+## any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+# $Id: client.py, v1.62 2013/10/21 alkorgun Exp $
+
+"""
+Provides PlugIn class functionality to develop extentions for xmpppy.
+Also provides Client and Component classes implementations as the
+examples of xmpppy structures usage.
+These classes can be used for simple applications "AS IS" though.
+"""
+
+from . import debug
+from . import transports
+from . import dispatcher
+from . import auth
+from . import roster
+
+from .plugin import PlugIn
+
+Debug = debug
+Debug.DEBUGGING_IS_ON = 1
+
+Debug.Debug.colors["socket"] = debug.color_dark_gray
+Debug.Debug.colors["CONNECTproxy"] = debug.color_dark_gray
+Debug.Debug.colors["nodebuilder"] = debug.color_brown
+Debug.Debug.colors["client"] = debug.color_cyan
+Debug.Debug.colors["component"] = debug.color_cyan
+Debug.Debug.colors["dispatcher"] = debug.color_green
+Debug.Debug.colors["browser"] = debug.color_blue
+Debug.Debug.colors["auth"] = debug.color_yellow
+Debug.Debug.colors["roster"] = debug.color_magenta
+Debug.Debug.colors["ibb"] = debug.color_yellow
+Debug.Debug.colors["down"] = debug.color_brown
+Debug.Debug.colors["up"] = debug.color_brown
+Debug.Debug.colors["data"] = debug.color_brown
+Debug.Debug.colors["ok"] = debug.color_green
+Debug.Debug.colors["warn"] = debug.color_yellow
+Debug.Debug.colors["error"] = debug.color_red
+Debug.Debug.colors["start"] = debug.color_dark_gray
+Debug.Debug.colors["stop"] = debug.color_dark_gray
+Debug.Debug.colors["sent"] = debug.color_yellow
+Debug.Debug.colors["got"] = debug.color_bright_cyan
+
+DBG_CLIENT = "client"
+DBG_COMPONENT = "component"
+
+
+class CommonClient:
+ """
+ Base for Client and Component classes.
+ """
+ def __init__(self, server, port=5222, debug=["always", "nodebuilder"]):
+ """
+ Caches server name and (optionally) port to connect to. "debug" parameter specifies
+ the debug IDs that will go into debug output. You can either specifiy an "include"
+ or "exclude" list. The latter is done via adding "always" pseudo-ID to the list.
+ Full list: ["nodebuilder", "dispatcher", "gen_auth", "SASL_auth", "bind", "socket",
+ "CONNECTproxy", "TLS", "roster", "browser", "ibb"].
+ """
+ if isinstance(self, Client):
+ self.Namespace, self.DBG = "jabber:client", DBG_CLIENT
+ elif isinstance(self, Component):
+ self.Namespace, self.DBG = dispatcher.NS_COMPONENT_ACCEPT, DBG_COMPONENT
+ self.defaultNamespace = self.Namespace
+ self.disconnect_handlers = []
+ self.Server = server
+ self.Port = port
+ if debug and not isinstance(debug, list):
+ debug = ["always", "nodebuilder"]
+ self._DEBUG = Debug.Debug(debug)
+ self.DEBUG = self._DEBUG.Show
+ self.debug_flags = self._DEBUG.debug_flags
+ self.debug_flags.append(self.DBG)
+ self._owner = self
+ self._registered_name = None
+ self.RegisterDisconnectHandler(self.DisconnectHandler)
+ self.connected = ""
+ self._route = 0
+
+ def RegisterDisconnectHandler(self, handler):
+ """
+ Register handler that will be called on disconnect.
+ """
+ self.disconnect_handlers.append(handler)
+
+ def UnregisterDisconnectHandler(self, handler):
+ """
+ Unregister handler that is called on disconnect.
+ """
+ self.disconnect_handlers.remove(handler)
+
+ def disconnected(self):
+ """
+ Called on disconnection. Calls disconnect handlers and cleans things up.
+ """
+ self.connected = ""
+ self.DEBUG(self.DBG, "Disconnect detected", "stop")
+ self.disconnect_handlers.reverse()
+ for dhnd in self.disconnect_handlers:
+ dhnd()
+ self.disconnect_handlers.reverse()
+ if hasattr(self, "TLS"):
+ self.TLS.PlugOut()
+
+ def DisconnectHandler(self):
+ """
+ Default disconnect handler. Just raises an IOError.
+ If you choosed to use this class in your production client,
+ override this method or at least unregister it.
+ """
+ raise IOError("Disconnected!")
+
+ def event(self, eventName, args={}):
+ """
+ Default event handler. To be overriden.
+ """
+ print("Event: %s-%s" % (eventName, args))
+
+ def isConnected(self):
+ """
+ Returns connection state. F.e.: None / "tls" / "tcp+non_sasl" .
+ """
+ return self.connected
+
+ def reconnectAndReauth(self, handlerssave=None):
+ """
+ Example of reconnection method. In fact, it can be used to batch connection and auth as well.
+ """
+ Dispatcher_ = False
+ if not handlerssave:
+ Dispatcher_, handlerssave = True, self.Dispatcher.dumpHandlers()
+ if hasattr(self, "ComponentBind"):
+ self.ComponentBind.PlugOut()
+ if hasattr(self, "Bind"):
+ self.Bind.PlugOut()
+ self._route = 0
+ if hasattr(self, "NonSASL"):
+ self.NonSASL.PlugOut()
+ if hasattr(self, "SASL"):
+ self.SASL.PlugOut()
+ if hasattr(self, "TLS"):
+ self.TLS.PlugOut()
+ if Dispatcher_:
+ self.Dispatcher.PlugOut()
+ if hasattr(self, "HTTPPROXYsocket"):
+ self.HTTPPROXYsocket.PlugOut()
+ if hasattr(self, "TCPsocket"):
+ self.TCPsocket.PlugOut()
+ if not self.connect(server=self._Server, proxy=self._Proxy):
+ return None
+ if not self.auth(self._User, self._Password, self._Resource):
+ return None
+ self.Dispatcher.restoreHandlers(handlerssave)
+ return self.connected
+
+ def connect(self, server=None, proxy=None, ssl=None, use_srv=False):
+ """
+ Make a tcp/ip connection, protect it with tls/ssl if possible and start XMPP stream.
+ Returns None or "tcp" or "tls", depending on the result.
+ """
+ if not server:
+ server = (self.Server, self.Port)
+ if proxy:
+ sock = transports.HTTPPROXYsocket(proxy, server, use_srv)
+ else:
+ sock = transports.TCPsocket(server, use_srv)
+ connected = sock.PlugIn(self)
+ if not connected:
+ sock.PlugOut()
+ return None
+ self._Server, self._Proxy = server, proxy
+ self.connected = "tcp"
+ if (ssl is None and self.Connection.getPort() in (5223, 443)) or ssl:
+ try: # FIXME. This should be done in transports.py
+ transports.TLS().PlugIn(self, now=1)
+ self.connected = "ssl"
+ except transports.socket.sslerror:
+ return None
+ dispatcher.Dispatcher().PlugIn(self)
+ while self.Dispatcher.Stream._document_attrs is None:
+ if not self.Process(1):
+ return None
+ if "version" in self.Dispatcher.Stream._document_attrs and self.Dispatcher.Stream._document_attrs["version"] == "1.0":
+ while not self.Dispatcher.Stream.features and self.Process(1):
+ pass # If we get version 1.0 stream the features tag MUST BE presented
+ return self.connected
+
+class Client(CommonClient):
+ """
+ Example client class, based on CommonClient.
+ """
+ def connect(self, server=None, proxy=None, secure=None, use_srv=True):
+ """
+ Connect to jabber server. If you want to specify different ip/port to connect to you can
+ pass it as tuple as first parameter. If there is HTTP proxy between you and server
+ specify it's address and credentials (if needed) in the second argument.
+ If you want ssl/tls support to be discovered and enable automatically - leave third argument as None. (ssl will be autodetected only if port is 5223 or 443)
+ If you want to force SSL start (i.e. if port 5223 or 443 is remapped to some non-standard port) then set it to 1.
+ If you want to disable tls/ssl support completely, set it to 0.
+ Example: connect(("192.168.5.5", 5222), {"host": "proxy.my.net", "port": 8080, "user": "me", "password": "secret"})
+ Returns "" or "tcp" or "tls", depending on the result.
+ """
+ if not CommonClient.connect(self, server, proxy, secure, use_srv) or secure != None and not secure:
+ return self.connected
+ transports.TLS().PlugIn(self)
+ if not hasattr(self, "Dispatcher"):
+ return None
+ if "version" not in self.Dispatcher.Stream._document_attrs or not self.Dispatcher.Stream._document_attrs["version"] == "1.0":
+ return self.connected
+ while not self.Dispatcher.Stream.features and self.Process(1):
+ pass # If we get version 1.0 stream the features tag MUST BE presented
+ if not self.Dispatcher.Stream.features.getTag("starttls"):
+ return self.connected # TLS not supported by server
+ while not self.TLS.starttls and self.Process(1):
+ pass
+ if not hasattr(self, "TLS") or self.TLS.starttls != "success":
+ self.event("tls_failed")
+ return self.connected
+ self.connected = "tls"
+ return self.connected
+
+ def auth(self, user, password, resource="", sasl=1):
+ """
+ Authenticate connnection and bind resource. If resource is not provided
+ random one or library name used.
+ """
+ self._User, self._Password, self._Resource = user, password, resource
+ while not self.Dispatcher.Stream._document_attrs and self.Process(1):
+ pass
+ if "version" in self.Dispatcher.Stream._document_attrs and self.Dispatcher.Stream._document_attrs["version"] == "1.0":
+ while not self.Dispatcher.Stream.features and self.Process(1):
+ pass # If we get version 1.0 stream the features tag MUST BE presented
+ if sasl:
+ auth.SASL(user, password).PlugIn(self)
+ if not sasl or self.SASL.startsasl == "not-supported":
+ if not resource:
+ resource = "xmpppy"
+ if auth.NonSASL(user, password, resource).PlugIn(self):
+ self.connected += "+old_auth"
+ return "old_auth"
+ return None
+ self.SASL.auth()
+ while self.SASL.startsasl == "in-process" and self.Process(1):
+ pass
+ if self.SASL.startsasl == "success":
+ auth.Bind().PlugIn(self)
+ while self.Bind.bound is None and self.Process(1):
+ pass
+ if self.Bind.Bind(resource):
+ self.connected += "+sasl"
+ return "sasl"
+ elif hasattr(self, "SASL"):
+ self.SASL.PlugOut()
+
+ def getRoster(self):
+ """
+ Return the Roster instance, previously plugging it in and
+ requesting roster from server if needed.
+ """
+ if not hasattr(self, "Roster"):
+ roster.Roster().PlugIn(self)
+ return self.Roster.getRoster()
+
+ def sendInitPresence(self, requestRoster=1):
+ """
+ Send roster request and initial <presence/>.
+ You can disable the first by setting requestRoster argument to 0.
+ """
+ self.sendPresence(requestRoster=requestRoster)
+
+ def sendPresence(self, jid=None, typ=None, requestRoster=0):
+ """
+ Send some specific presence state.
+ Can also request roster from server if according agrument is set.
+ """
+ if requestRoster:
+ roster.Roster().PlugIn(self)
+ self.send(dispatcher.Presence(to=jid, typ=typ))
+
+class Component(CommonClient):
+ """
+ Component class. The only difference from CommonClient is ability to perform component authentication.
+ """
+ def __init__(self, transport, port=5347, typ=None, debug=["always", "nodebuilder"], domains=None, sasl=0, bind=0, route=0, xcp=0):
+ """
+ Init function for Components.
+ As components use a different auth mechanism which includes the namespace of the component.
+ Jabberd1.4 and Ejabberd use the default namespace then for all client messages.
+ Jabberd2 uses jabber:client.
+ "transport" argument is a transport name that you are going to serve (f.e. "irc.localhost").
+ "port" can be specified if "transport" resolves to correct IP. If it is not then you'll have to specify IP
+ and port while calling "connect()".
+ If you are going to serve several different domains with single Component instance - you must list them ALL
+ in the "domains" argument.
+ For jabberd2 servers you should set typ="jabberd2" argument.
+ """
+ CommonClient.__init__(self, transport, port=port, debug=debug)
+ self.typ = typ
+ self.sasl = sasl
+ self.bind = bind
+ self.route = route
+ self.xcp = xcp
+ if domains:
+ self.domains = domains
+ else:
+ self.domains = [transport]
+
+ def connect(self, server=None, proxy=None):
+ """
+ This will connect to the server, and if the features tag is found then set
+ the namespace to be jabber:client as that is required for jabberd2.
+ "server" and "proxy" arguments have the same meaning as in xmpp.Client.connect().
+ """
+ if self.sasl:
+ self.Namespace = auth.NS_COMPONENT_1
+ self.Server = server[0]
+ CommonClient.connect(self, server=server, proxy=proxy)
+ if self.connected and (self.typ == "jabberd2" or not self.typ and self.Dispatcher.Stream.features != None) and (not self.xcp):
+ self.defaultNamespace = auth.NS_CLIENT
+ self.Dispatcher.RegisterNamespace(self.defaultNamespace)
+ self.Dispatcher.RegisterProtocol("iq", dispatcher.Iq)
+ self.Dispatcher.RegisterProtocol("message", dispatcher.Message)
+ self.Dispatcher.RegisterProtocol("presence", dispatcher.Presence)
+ return self.connected
+
+ def dobind(self, sasl):
+ # This has to be done before binding, because we can receive a route stanza before binding finishes
+ self._route = self.route
+ if self.bind:
+ for domain in self.domains:
+ auth.ComponentBind(sasl).PlugIn(self)
+ while self.ComponentBind.bound is None:
+ self.Process(1)
+ if (not self.ComponentBind.Bind(domain)):
+ self.ComponentBind.PlugOut()
+ return None
+ self.ComponentBind.PlugOut()
+
+ def auth(self, name, password, dup=None):
+ """
+ Authenticate component "name" with password "password".
+ """
+ self._User, self._Password, self._Resource = name, password, ""
+ try:
+ if self.sasl:
+ auth.SASL(name, password).PlugIn(self)
+ if not self.sasl or self.SASL.startsasl == "not-supported":
+ if auth.NonSASL(name, password, "").PlugIn(self):
+ self.dobind(sasl=False)
+ self.connected += "+old_auth"
+ return "old_auth"
+ return None
+ self.SASL.auth()
+ while self.SASL.startsasl == "in-process" and self.Process(1):
+ pass
+ if self.SASL.startsasl == "success":
+ self.dobind(sasl=True)
+ self.connected += "+sasl"
+ return "sasl"
+ else:
+ raise auth.NotAuthorized(self.SASL.startsasl)
+ except Exception:
+ self.DEBUG(self.DBG, "Failed to authenticate %s" % name, "error")
diff --git a/xmpp/commands.py b/xmpp/commands.py
index 42489ab..5fbe36a 100644
--- a/xmpp/commands.py
+++ b/xmpp/commands.py
@@ -1,448 +1,448 @@
-## Ad-Hoc Command manager
-
-## Mike Albon (c) 5th January 2005
-
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2, or (at your option)
-## any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-
-# $Id: commands.py, v1.18 2013/11/05 alkorgun Exp $
-
-"""
-This module is a ad-hoc command processor for xmpppy. It uses the plug-in mechanism like most of the core library.
-It depends on a DISCO browser manager.
-
-There are 3 classes here, a command processor Commands like the Browser,
-and a command template plugin Command, and an example command.
-
-To use this module:
-
- Instansiate the module with the parent transport and disco browser manager as parameters.
- "Plug in" commands using the command template.
- The command feature must be added to existing disco replies where neccessary.
-
-What it supplies:
-
- Automatic command registration with the disco browser manager.
- Automatic listing of commands in the public command list.
- A means of handling requests, by redirection though the command manager.
-"""
-
-from .plugin import PlugIn
-from .protocol import *
-
-class Commands(PlugIn):
- """
- Commands is an ancestor of PlugIn and can be attached to any session.
-
- The commands class provides a lookup and browse mechnism.
- It follows the same priciple of the Browser class, for Service Discovery to provide the list of commands,
- it adds the "list" disco type to your existing disco handler function.
-
- How it works:
- The commands are added into the existing Browser on the correct nodes.
- When the command list is built the supplied discovery handler function needs to have a "list" option in type.
- This then gets enumerated, all results returned as None are ignored.
- The command executed is then called using it's Execute method.
- All session management is handled by the command itself.
- """
- def __init__(self, browser):
- """
- Initialises class and sets up local variables.
- """
- PlugIn.__init__(self)
- DBG_LINE = "commands"
- self._exported_methods = []
- self._handlers = {"": {}}
- self._browser = browser
-
- def plugin(self, owner):
- """
- Makes handlers within the session.
- """
- # Plug into the session and the disco manager
- # We only need get and set, results are not needed by a service provider, only a service user.
- owner.RegisterHandler("iq", self._CommandHandler, typ="set", ns=NS_COMMANDS)
- owner.RegisterHandler("iq", self._CommandHandler, typ="get", ns=NS_COMMANDS)
- self._browser.setDiscoHandler(self._DiscoHandler, node=NS_COMMANDS, jid="")
-
- def plugout(self):
- """
- Removes handlers from the session.
- """
- # unPlug from the session and the disco manager
- self._owner.UnregisterHandler("iq", self._CommandHandler, ns=NS_COMMANDS)
- for jid in self._handlers:
- self._browser.delDiscoHandler(self._DiscoHandler, node=NS_COMMANDS)
-
- def _CommandHandler(self, conn, request):
- """
- The internal method to process the routing of command execution requests.
- """
- # This is the command handler itself.
- # We must:
- # Pass on command execution to command handler
- # (Do we need to keep session details here, or can that be done in the command?)
- jid = str(request.getTo())
- try:
- node = request.getTagAttr("command", "node")
- except Exception:
- conn.send(Error(request, ERR_BAD_REQUEST))
- raise NodeProcessed()
- if jid in self._handlers:
- if node in self._handlers[jid]:
- self._handlers[jid][node]["execute"](conn, request)
- else:
- conn.send(Error(request, ERR_ITEM_NOT_FOUND))
- raise NodeProcessed()
- elif node in self._handlers[""]:
- self._handlers[""][node]["execute"](conn, request)
- else:
- conn.send(Error(request, ERR_ITEM_NOT_FOUND))
- raise NodeProcessed()
-
- def _DiscoHandler(self, conn, request, typ):
- """
- The internal method to process service discovery requests.
- """
- # This is the disco manager handler.
- if typ == "items":
- # We must:
- # Generate a list of commands and return the list
- # * This handler does not handle individual commands disco requests.
- # Pseudo:
- # Enumerate the "item" disco of each command for the specified jid
- # Build responce and send
- # To make this code easy to write we add an "list" disco type, it returns a tuple or "none" if not advertised
- list = []
- items = []
- jid = str(request.getTo())
- # Get specific jid based results
- if jid in self._handlers:
- for each in self._handlers[jid].keys():
- items.append((jid, each))
- else:
- # Get generic results
- for each in self._handlers[""].keys():
- items.append(("", each))
- if items:
- for each in items:
- i = self._handlers[each[0]][each[1]]["disco"](conn, request, "list")
- if i != None:
- list.append(Node(tag="item", attrs={"jid": i[0], "node": i[1], "name": i[2]}))
- iq = request.buildReply("result")
- if request.getQuerynode():
- iq.setQuerynode(request.getQuerynode())
- iq.setQueryPayload(list)
- conn.send(iq)
- else:
- conn.send(Error(request, ERR_ITEM_NOT_FOUND))
- raise NodeProcessed()
- if typ == "info":
- return {
- "ids": [{"category": "automation", "type": "command-list"}],
- "features": []
- }
-
- def addCommand(self, name, cmddisco, cmdexecute, jid=""):
- """
- The method to call if adding a new command to the session,
- the requred parameters of cmddisco and cmdexecute
- are the methods to enable that command to be executed.
- """
- # This command takes a command object and the name of the command for registration
- # We must:
- # Add item into disco
- # Add item into command list
- if jid not in self._handlers:
- self._handlers[jid] = {}
- self._browser.setDiscoHandler(self._DiscoHandler, node=NS_COMMANDS, jid=jid)
- if name in self._handlers[jid]:
- raise NameError("Command Exists")
- self._handlers[jid][name] = {"disco": cmddisco, "execute": cmdexecute}
- # Need to add disco stuff here
- self._browser.setDiscoHandler(cmddisco, node=name, jid=jid)
-
- def delCommand(self, name, jid=""):
- """
- Removed command from the session.
- """
- # This command takes a command object and the name used for registration
- # We must:
- # Remove item from disco
- # Remove item from command list
- if jid not in self._handlers:
- raise NameError("Jid not found")
- if name not in self._handlers[jid]:
- raise NameError("Command not found")
- # Do disco removal here
- command = self.getCommand(name, jid)["disco"]
- del self._handlers[jid][name]
- self._browser.delDiscoHandler(command, node=name, jid=jid)
-
- def getCommand(self, name, jid=""):
- """
- Returns the command tuple.
- """
- # This gets the command object with name
- # We must:
- # Return item that matches this name
- if jid not in self._handlers:
- raise NameError("Jid not found")
- if name not in self._handlers[jid]:
- raise NameError("Command not found")
- return self._handlers[jid][name]
-
-class Command_Handler_Prototype(PlugIn):
- """
- This is a prototype command handler, as each command uses a disco method
- and execute method you can implement it any way you like, however this is
- my first attempt at making a generic handler that you can hang process
- stages on too. There is an example command below.
-
- The parameters are as follows:
- name: the name of the command within the jabber environment
- description: the natural language description
- discofeatures: the features supported by the command
- initial: the initial command in the from of {"execute": commandname}
-
- All stages set the "actions" dictionary for each session to represent the possible options available.
- """
- name = "examplecommand"
- count = 0
- description = "an example command"
- discofeatures = [NS_COMMANDS, NS_DATA]
-
- # This is the command template
- def __init__(self, jid=""):
- """
- Set up the class.
- """
- PlugIn.__init__(self)
- DBG_LINE = "command"
- self.sessioncount = 0
- self.sessions = {}
- # Disco information for command list pre-formatted as a tuple
- self.discoinfo = {
- "ids": [{
- "category": "automation",
- "type": "command-node",
- "name": self.description
- }],
- "features": self.discofeatures
- }
- self._jid = jid
-
- def plugin(self, owner):
- """
- Plug command into the commands class.
- """
- # The owner in this instance is the Command Processor
- self._commands = owner
- self._owner = owner._owner
- self._commands.addCommand(self.name, self._DiscoHandler, self.Execute, jid=self._jid)
-
- def plugout(self):
- """
- Remove command from the commands class.
- """
- self._commands.delCommand(self.name, self._jid)
-
- def getSessionID(self):
- """
- Returns an id for the command session.
- """
- self.count += 1
- return "cmd-%s-%d" % (self.name, self.count)
-
- def Execute(self, conn, request):
- """
- The method that handles all the commands, and routes them to the correct method for that stage.
- """
- # New request or old?
- try:
- session = request.getTagAttr("command", "sessionid")
- except Exception:
- session = None
- try:
- action = request.getTagAttr("command", "action")
- except Exception:
- action = None
- if action == None:
- action = "execute"
- # Check session is in session list
- if session in self.sessions:
- if self.sessions[session]["jid"] == request.getFrom():
- # Check action is vaild
- if action in self.sessions[session]["actions"]:
- # Execute next action
- self.sessions[session]["actions"][action](conn, request)
- else:
- # Stage not presented as an option
- self._owner.send(Error(request, ERR_BAD_REQUEST))
- raise NodeProcessed()
- else:
- # Jid and session don't match. Go away imposter
- self._owner.send(Error(request, ERR_BAD_REQUEST))
- raise NodeProcessed()
- elif session != None:
- # Not on this sessionid you won't.
- self._owner.send(Error(request, ERR_BAD_REQUEST))
- raise NodeProcessed()
- else:
- # New session
- self.initial[action](conn, request)
-
- def _DiscoHandler(self, conn, request, typ):
- """
- The handler for discovery events.
- """
- if typ == "list":
- result = (request.getTo(), self.name, self.description)
- elif typ == "items":
- result = []
- elif typ == "info":
- result = self.discoinfo
- return result
-
-class TestCommand(Command_Handler_Prototype):
- """
- Example class. You should read source if you wish to understate how it works.
- Generally, it presents a "master" that giudes user through to calculate something.
- """
- name = "testcommand"
- description = "a noddy example command"
-
- def __init__(self, jid=""):
- """ Init internal constants. """
- Command_Handler_Prototype.__init__(self, jid)
- self.initial = {"execute": self.cmdFirstStage}
-
- def cmdFirstStage(self, conn, request):
- """
- Determine.
- """
- # This is the only place this should be repeated as all other stages should have SessionIDs
- try:
- session = request.getTagAttr("command", "sessionid")
- except Exception:
- session = None
- if session == None:
- session = self.getSessionID()
- self.sessions[session] = {
- "jid": request.getFrom(),
- "actions": {
- "cancel": self.cmdCancel,
- "next": self.cmdSecondStage,
- "execute": self.cmdSecondStage
- },
- "data": {"type": None}
- }
- # As this is the first stage we only send a form
- reply = request.buildReply("result")
- form = DataForm(title="Select type of operation",
- data=[
- "Use the combobox to select the type of calculation you would like to do, then click Next.",
- DataField(name="calctype", desc="Calculation Type",
- value=self.sessions[session]["data"]["type"],
- options=[
- ["circlediameter", "Calculate the Diameter of a circle"],
- ["circlearea", "Calculate the area of a circle"]
- ],
- typ="list-single",
- required=1
- )])
- replypayload = [Node("actions", attrs={"execute": "next"}, payload=[Node("next")]), form]
- reply.addChild(name="command",
- namespace=NS_COMMANDS,
- attrs={
- "node": request.getTagAttr("command", "node"),
- "sessionid": session,
- "status": "executing"
- },
- payload=replypayload
- )
- self._owner.send(reply)
- raise NodeProcessed()
-
- def cmdSecondStage(self, conn, request):
- form = DataForm(node=request.getTag(name="command").getTag(name="x", namespace=NS_DATA))
- self.sessions[request.getTagAttr("command", "sessionid")]["data"]["type"] = form.getField("calctype").getValue()
- self.sessions[request.getTagAttr("command", "sessionid")]["actions"] = {
- "cancel": self.cmdCancel,
- None: self.cmdThirdStage,
- "previous": self.cmdFirstStage,
- "execute": self.cmdThirdStage,
- "next": self.cmdThirdStage
- }
- # The form generation is split out to another method as it may be called by cmdThirdStage
- self.cmdSecondStageReply(conn, request)
-
- def cmdSecondStageReply(self, conn, request):
- reply = request.buildReply("result")
- form = DataForm(title="Enter the radius",
- data=[
- "Enter the radius of the circle (numbers only)",
- DataField(desc="Radius", name="radius", typ="text-single")
- ])
- replypayload = [
- Node("actions",
- attrs={"execute": "complete"},
- payload=[Node("complete"),
- Node("prev")]),
- form
- ]
- reply.addChild(name="command",
- namespace=NS_COMMANDS,
- attrs={
- "node": request.getTagAttr("command", "node"),
- "sessionid": request.getTagAttr("command", "sessionid"),
- "status": "executing"
- },
- payload=replypayload
- )
- self._owner.send(reply)
- raise NodeProcessed()
-
- def cmdThirdStage(self, conn, request):
- form = DataForm(node=request.getTag(name="command").getTag(name="x", namespace=NS_DATA))
- try:
- numb = float(form.getField("radius").getValue())
- except Exception:
- self.cmdSecondStageReply(conn, request)
- from math import pi
- if self.sessions[request.getTagAttr("command", "sessionid")]["data"]["type"] == "circlearea":
- result = (numb ** 2) * pi
- else:
- result = numb * 2 * pi
- reply = request.buildReply("result")
- form = DataForm(typ="result", data=[DataField(desc="result", name="result", value=result)])
- reply.addChild(name="command",
- namespace=NS_COMMANDS,
- attrs={
- "node": request.getTagAttr("command", "node"),
- "sessionid": request.getTagAttr("command", "sessionid"),
- "status": "completed"
- },
- payload=[form]
- )
- self._owner.send(reply)
- raise NodeProcessed()
-
- def cmdCancel(self, conn, request):
- reply = request.buildReply("result")
- reply.addChild(name="command",
- namespace=NS_COMMANDS,
- attrs={
- "node": request.getTagAttr("command", "node"),
- "sessionid": request.getTagAttr("command", "sessionid"),
- "status": "cancelled"
- })
- self._owner.send(reply)
- del self.sessions[request.getTagAttr("command", "sessionid")]
+## Ad-Hoc Command manager
+
+## Mike Albon (c) 5th January 2005
+
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2, or (at your option)
+## any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+# $Id: commands.py, v1.18 2013/11/05 alkorgun Exp $
+
+"""
+This module is a ad-hoc command processor for xmpppy. It uses the plug-in mechanism like most of the core library.
+It depends on a DISCO browser manager.
+
+There are 3 classes here, a command processor Commands like the Browser,
+and a command template plugin Command, and an example command.
+
+To use this module:
+
+ Instansiate the module with the parent transport and disco browser manager as parameters.
+ "Plug in" commands using the command template.
+ The command feature must be added to existing disco replies where neccessary.
+
+What it supplies:
+
+ Automatic command registration with the disco browser manager.
+ Automatic listing of commands in the public command list.
+ A means of handling requests, by redirection though the command manager.
+"""
+
+from .plugin import PlugIn
+from .protocol import *
+
+class Commands(PlugIn):
+ """
+ Commands is an ancestor of PlugIn and can be attached to any session.
+
+ The commands class provides a lookup and browse mechnism.
+ It follows the same priciple of the Browser class, for Service Discovery to provide the list of commands,
+ it adds the "list" disco type to your existing disco handler function.
+
+ How it works:
+ The commands are added into the existing Browser on the correct nodes.
+ When the command list is built the supplied discovery handler function needs to have a "list" option in type.
+ This then gets enumerated, all results returned as None are ignored.
+ The command executed is then called using it's Execute method.
+ All session management is handled by the command itself.
+ """
+ def __init__(self, browser):
+ """
+ Initialises class and sets up local variables.
+ """
+ PlugIn.__init__(self)
+ DBG_LINE = "commands"
+ self._exported_methods = []
+ self._handlers = {"": {}}
+ self._browser = browser
+
+ def plugin(self, owner):
+ """
+ Makes handlers within the session.
+ """
+ # Plug into the session and the disco manager
+ # We only need get and set, results are not needed by a service provider, only a service user.
+ owner.RegisterHandler("iq", self._CommandHandler, typ="set", ns=NS_COMMANDS)
+ owner.RegisterHandler("iq", self._CommandHandler, typ="get", ns=NS_COMMANDS)
+ self._browser.setDiscoHandler(self._DiscoHandler, node=NS_COMMANDS, jid="")
+
+ def plugout(self):
+ """
+ Removes handlers from the session.
+ """
+ # unPlug from the session and the disco manager
+ self._owner.UnregisterHandler("iq", self._CommandHandler, ns=NS_COMMANDS)
+ for jid in self._handlers:
+ self._browser.delDiscoHandler(self._DiscoHandler, node=NS_COMMANDS)
+
+ def _CommandHandler(self, conn, request):
+ """
+ The internal method to process the routing of command execution requests.
+ """
+ # This is the command handler itself.
+ # We must:
+ # Pass on command execution to command handler
+ # (Do we need to keep session details here, or can that be done in the command?)
+ jid = str(request.getTo())
+ try:
+ node = request.getTagAttr("command", "node")
+ except Exception:
+ conn.send(Error(request, ERR_BAD_REQUEST))
+ raise NodeProcessed()
+ if jid in self._handlers:
+ if node in self._handlers[jid]:
+ self._handlers[jid][node]["execute"](conn, request)
+ else:
+ conn.send(Error(request, ERR_ITEM_NOT_FOUND))
+ raise NodeProcessed()
+ elif node in self._handlers[""]:
+ self._handlers[""][node]["execute"](conn, request)
+ else:
+ conn.send(Error(request, ERR_ITEM_NOT_FOUND))
+ raise NodeProcessed()
+
+ def _DiscoHandler(self, conn, request, typ):
+ """
+ The internal method to process service discovery requests.
+ """
+ # This is the disco manager handler.
+ if typ == "items":
+ # We must:
+ # Generate a list of commands and return the list
+ # * This handler does not handle individual commands disco requests.
+ # Pseudo:
+ # Enumerate the "item" disco of each command for the specified jid
+ # Build responce and send
+ # To make this code easy to write we add an "list" disco type, it returns a tuple or "none" if not advertised
+ list = []
+ items = []
+ jid = str(request.getTo())
+ # Get specific jid based results
+ if jid in self._handlers:
+ for each in self._handlers[jid].keys():
+ items.append((jid, each))
+ else:
+ # Get generic results
+ for each in self._handlers[""].keys():
+ items.append(("", each))
+ if items:
+ for each in items:
+ i = self._handlers[each[0]][each[1]]["disco"](conn, request, "list")
+ if i != None:
+ list.append(Node(tag="item", attrs={"jid": i[0], "node": i[1], "name": i[2]}))
+ iq = request.buildReply("result")
+ if request.getQuerynode():
+ iq.setQuerynode(request.getQuerynode())
+ iq.setQueryPayload(list)
+ conn.send(iq)
+ else:
+ conn.send(Error(request, ERR_ITEM_NOT_FOUND))
+ raise NodeProcessed()
+ if typ == "info":
+ return {
+ "ids": [{"category": "automation", "type": "command-list"}],
+ "features": []
+ }
+
+ def addCommand(self, name, cmddisco, cmdexecute, jid=""):
+ """
+ The method to call if adding a new command to the session,
+ the requred parameters of cmddisco and cmdexecute
+ are the methods to enable that command to be executed.
+ """
+ # This command takes a command object and the name of the command for registration
+ # We must:
+ # Add item into disco
+ # Add item into command list
+ if jid not in self._handlers:
+ self._handlers[jid] = {}
+ self._browser.setDiscoHandler(self._DiscoHandler, node=NS_COMMANDS, jid=jid)
+ if name in self._handlers[jid]:
+ raise NameError("Command Exists")
+ self._handlers[jid][name] = {"disco": cmddisco, "execute": cmdexecute}
+ # Need to add disco stuff here
+ self._browser.setDiscoHandler(cmddisco, node=name, jid=jid)
+
+ def delCommand(self, name, jid=""):
+ """
+ Removed command from the session.
+ """
+ # This command takes a command object and the name used for registration
+ # We must:
+ # Remove item from disco
+ # Remove item from command list
+ if jid not in self._handlers:
+ raise NameError("Jid not found")
+ if name not in self._handlers[jid]:
+ raise NameError("Command not found")
+ # Do disco removal here
+ command = self.getCommand(name, jid)["disco"]
+ del self._handlers[jid][name]
+ self._browser.delDiscoHandler(command, node=name, jid=jid)
+
+ def getCommand(self, name, jid=""):
+ """
+ Returns the command tuple.
+ """
+ # This gets the command object with name
+ # We must:
+ # Return item that matches this name
+ if jid not in self._handlers:
+ raise NameError("Jid not found")
+ if name not in self._handlers[jid]:
+ raise NameError("Command not found")
+ return self._handlers[jid][name]
+
+class Command_Handler_Prototype(PlugIn):
+ """
+ This is a prototype command handler, as each command uses a disco method
+ and execute method you can implement it any way you like, however this is
+ my first attempt at making a generic handler that you can hang process
+ stages on too. There is an example command below.
+
+ The parameters are as follows:
+ name: the name of the command within the jabber environment
+ description: the natural language description
+ discofeatures: the features supported by the command
+ initial: the initial command in the from of {"execute": commandname}
+
+ All stages set the "actions" dictionary for each session to represent the possible options available.
+ """
+ name = "examplecommand"
+ count = 0
+ description = "an example command"
+ discofeatures = [NS_COMMANDS, NS_DATA]
+
+ # This is the command template
+ def __init__(self, jid=""):
+ """
+ Set up the class.
+ """
+ PlugIn.__init__(self)
+ DBG_LINE = "command"
+ self.sessioncount = 0
+ self.sessions = {}
+ # Disco information for command list pre-formatted as a tuple
+ self.discoinfo = {
+ "ids": [{
+ "category": "automation",
+ "type": "command-node",
+ "name": self.description
+ }],
+ "features": self.discofeatures
+ }
+ self._jid = jid
+
+ def plugin(self, owner):
+ """
+ Plug command into the commands class.
+ """
+ # The owner in this instance is the Command Processor
+ self._commands = owner
+ self._owner = owner._owner
+ self._commands.addCommand(self.name, self._DiscoHandler, self.Execute, jid=self._jid)
+
+ def plugout(self):
+ """
+ Remove command from the commands class.
+ """
+ self._commands.delCommand(self.name, self._jid)
+
+ def getSessionID(self):
+ """
+ Returns an id for the command session.
+ """
+ self.count += 1
+ return "cmd-%s-%d" % (self.name, self.count)
+
+ def Execute(self, conn, request):
+ """
+ The method that handles all the commands, and routes them to the correct method for that stage.
+ """
+ # New request or old?
+ try:
+ session = request.getTagAttr("command", "sessionid")
+ except Exception:
+ session = None
+ try:
+ action = request.getTagAttr("command", "action")
+ except Exception:
+ action = None
+ if action == None:
+ action = "execute"
+ # Check session is in session list
+ if session in self.sessions:
+ if self.sessions[session]["jid"] == request.getFrom():
+ # Check action is vaild
+ if action in self.sessions[session]["actions"]:
+ # Execute next action
+ self.sessions[session]["actions"][action](conn, request)
+ else:
+ # Stage not presented as an option
+ self._owner.send(Error(request, ERR_BAD_REQUEST))
+ raise NodeProcessed()
+ else:
+ # Jid and session don't match. Go away imposter
+ self._owner.send(Error(request, ERR_BAD_REQUEST))
+ raise NodeProcessed()
+ elif session != None:
+ # Not on this sessionid you won't.
+ self._owner.send(Error(request, ERR_BAD_REQUEST))
+ raise NodeProcessed()
+ else:
+ # New session
+ self.initial[action](conn, request)
+
+ def _DiscoHandler(self, conn, request, typ):
+ """
+ The handler for discovery events.
+ """
+ if typ == "list":
+ result = (request.getTo(), self.name, self.description)
+ elif typ == "items":
+ result = []
+ elif typ == "info":
+ result = self.discoinfo
+ return result
+
+class TestCommand(Command_Handler_Prototype):
+ """
+ Example class. You should read source if you wish to understate how it works.
+ Generally, it presents a "master" that giudes user through to calculate something.
+ """
+ name = "testcommand"
+ description = "a noddy example command"
+
+ def __init__(self, jid=""):
+ """ Init internal constants. """
+ Command_Handler_Prototype.__init__(self, jid)
+ self.initial = {"execute": self.cmdFirstStage}
+
+ def cmdFirstStage(self, conn, request):
+ """
+ Determine.
+ """
+ # This is the only place this should be repeated as all other stages should have SessionIDs
+ try:
+ session = request.getTagAttr("command", "sessionid")
+ except Exception:
+ session = None
+ if session == None:
+ session = self.getSessionID()
+ self.sessions[session] = {
+ "jid": request.getFrom(),
+ "actions": {
+ "cancel": self.cmdCancel,
+ "next": self.cmdSecondStage,
+ "execute": self.cmdSecondStage
+ },
+ "data": {"type": None}
+ }
+ # As this is the first stage we only send a form
+ reply = request.buildReply("result")
+ form = DataForm(title="Select type of operation",
+ data=[
+ "Use the combobox to select the type of calculation you would like to do, then click Next.",
+ DataField(name="calctype", desc="Calculation Type",
+ value=self.sessions[session]["data"]["type"],
+ options=[
+ ["circlediameter", "Calculate the Diameter of a circle"],
+ ["circlearea", "Calculate the area of a circle"]
+ ],
+ typ="list-single",
+ required=1
+ )])
+ replypayload = [Node("actions", attrs={"execute": "next"}, payload=[Node("next")]), form]
+ reply.addChild(name="command",
+ namespace=NS_COMMANDS,
+ attrs={
+ "node": request.getTagAttr("command", "node"),
+ "sessionid": session,
+ "status": "executing"
+ },
+ payload=replypayload
+ )
+ self._owner.send(reply)
+ raise NodeProcessed()
+
+ def cmdSecondStage(self, conn, request):
+ form = DataForm(node=request.getTag(name="command").getTag(name="x", namespace=NS_DATA))
+ self.sessions[request.getTagAttr("command", "sessionid")]["data"]["type"] = form.getField("calctype").getValue()
+ self.sessions[request.getTagAttr("command", "sessionid")]["actions"] = {
+ "cancel": self.cmdCancel,
+ None: self.cmdThirdStage,
+ "previous": self.cmdFirstStage,
+ "execute": self.cmdThirdStage,
+ "next": self.cmdThirdStage
+ }
+ # The form generation is split out to another method as it may be called by cmdThirdStage
+ self.cmdSecondStageReply(conn, request)
+
+ def cmdSecondStageReply(self, conn, request):
+ reply = request.buildReply("result")
+ form = DataForm(title="Enter the radius",
+ data=[
+ "Enter the radius of the circle (numbers only)",
+ DataField(desc="Radius", name="radius", typ="text-single")
+ ])
+ replypayload = [
+ Node("actions",
+ attrs={"execute": "complete"},
+ payload=[Node("complete"),
+ Node("prev")]),
+ form
+ ]
+ reply.addChild(name="command",
+ namespace=NS_COMMANDS,
+ attrs={
+ "node": request.getTagAttr("command", "node"),
+ "sessionid": request.getTagAttr("command", "sessionid"),
+ "status": "executing"
+ },
+ payload=replypayload
+ )
+ self._owner.send(reply)
+ raise NodeProcessed()
+
+ def cmdThirdStage(self, conn, request):
+ form = DataForm(node=request.getTag(name="command").getTag(name="x", namespace=NS_DATA))
+ try:
+ numb = float(form.getField("radius").getValue())
+ except Exception:
+ self.cmdSecondStageReply(conn, request)
+ from math import pi
+ if self.sessions[request.getTagAttr("command", "sessionid")]["data"]["type"] == "circlearea":
+ result = (numb ** 2) * pi
+ else:
+ result = numb * 2 * pi
+ reply = request.buildReply("result")
+ form = DataForm(typ="result", data=[DataField(desc="result", name="result", value=result)])
+ reply.addChild(name="command",
+ namespace=NS_COMMANDS,
+ attrs={
+ "node": request.getTagAttr("command", "node"),
+ "sessionid": request.getTagAttr("command", "sessionid"),
+ "status": "completed"
+ },
+ payload=[form]
+ )
+ self._owner.send(reply)
+ raise NodeProcessed()
+
+ def cmdCancel(self, conn, request):
+ reply = request.buildReply("result")
+ reply.addChild(name="command",
+ namespace=NS_COMMANDS,
+ attrs={
+ "node": request.getTagAttr("command", "node"),
+ "sessionid": request.getTagAttr("command", "sessionid"),
+ "status": "cancelled"
+ })
+ self._owner.send(reply)
+ del self.sessions[request.getTagAttr("command", "sessionid")]
diff --git a/xmpp/debug.py b/xmpp/debug.py
index 6d8c34a..fe422bf 100644
--- a/xmpp/debug.py
+++ b/xmpp/debug.py
@@ -1,314 +1,314 @@
-## debug.py
-##
-## Copyright (C) 2003 Jacob Lundqvist
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU Lesser General Public License as published
-## by the Free Software Foundation; either version 2, or (at your option)
-## any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU Lesser General Public License for more details.
-
-# $Id: debug.py, v1.41 2013/10/21 alkorgun Exp $
-
-_version_ = "1.4.1"
-
-import os
-import sys
-import time
-
-from traceback import format_exception as traceback_format_exception
-
-colors_enabled = "TERM" in os.environ
-
-color_none = chr(27) + "[0m"
-color_black = chr(27) + "[30m"
-color_red = chr(27) + "[31m"
-color_green = chr(27) + "[32m"
-color_brown = chr(27) + "[33m"
-color_blue = chr(27) + "[34m"
-color_magenta = chr(27) + "[35m"
-color_cyan = chr(27) + "[36m"
-color_light_gray = chr(27) + "[37m"
-color_dark_gray = chr(27) + "[30;1m"
-color_bright_red = chr(27) + "[31;1m"
-color_bright_green = chr(27) + "[32;1m"
-color_yellow = chr(27) + "[33;1m"
-color_bright_blue = chr(27) + "[34;1m"
-color_purple = chr(27) + "[35;1m"
-color_bright_cyan = chr(27) + "[36;1m"
-color_white = chr(27) + "[37;1m"
-
-class NoDebug:
-
- def __init__(self, *args, **kwargs):
- self.debug_flags = []
-
- def show(self, *args, **kwargs):
- pass
-
- def Show(self, *args, **kwargs):
- pass
-
- def is_active(self, flag):
- pass
-
- colors = {}
-
- def active_set(self, active_flags=None):
- return 0
-
-LINE_FEED = "\n"
-
-class Debug:
-
- def __init__(self, active_flags=None, log_file=sys.stderr, prefix="DEBUG: ", sufix="\n", time_stamp=0, flag_show=None, validate_flags=False, welcome=-1):
- self.debug_flags = []
- if welcome == -1:
- if active_flags and len(active_flags):
- welcome = 1
- else:
- welcome = 0
- self._remove_dupe_flags()
- if log_file:
- if isinstance(log_file, str):
- try:
- self._fh = open(log_file, "w")
- except Exception:
- print("ERROR: can open %s for writing." % log_file)
- sys.exit(0)
- else: # assume its a stream type object
- self._fh = log_file
- else:
- self._fh = sys.stdout
- if time_stamp not in (0, 1, 2):
- raise Exception("Invalid time_stamp param", str(time_stamp))
- self.prefix = prefix
- self.sufix = sufix
- self.time_stamp = time_stamp
- self.flag_show = None # must be initialised after possible welcome
- self.validate_flags = validate_flags
- self.active_set(active_flags)
- if welcome:
- self.show("")
- caller = sys._getframe(1) # used to get name of caller
- try:
- mod_name = ":%s" % caller.f_locals["__name__"]
- except Exception:
- mod_name = ""
- self.show("Debug created for %s%s" % (caller.f_code.co_filename, mod_name))
- self.show(" flags defined: %s" % ",".join(self.active))
- if isinstance(flag_show, (str, type(None))):
- self.flag_show = flag_show
- else:
- raise Exception("Invalid type for flag_show!", str(flag_show))
-
- def show(self, msg, flag=None, prefix=None, sufix=None, lf=0):
- """
- flag can be of folowing types:
- None - this msg will always be shown if any debugging is on
- flag - will be shown if flag is active
- (flag1,flag2,,,) - will be shown if any of the given flags are active
-
- if prefix / sufix are not given, default ones from init will be used
-
- lf = -1 means strip linefeed if pressent
- lf = 1 means add linefeed if not pressent
- """
- if self.validate_flags:
- self._validate_flag(flag)
- if not self.is_active(flag):
- return None
- if prefix:
- pre = prefix
- else:
- pre = self.prefix
- if sufix:
- suf = sufix
- else:
- suf = self.sufix
- if self.time_stamp == 2:
- output = "%s%s " % (
- pre,
- time.strftime("%b %d %H:%M:%S",
- time.localtime(time.time()))
- )
- elif self.time_stamp == 1:
- output = "%s %s" % (
- time.strftime("%b %d %H:%M:%S",
- time.localtime(time.time())),
- pre
- )
- else:
- output = pre
- if self.flag_show:
- if flag:
- output = "%s%s%s" % (output, flag, self.flag_show)
- else:
- # this call uses the global default, dont print "None", just show the separator
- output = "%s %s" % (output, self.flag_show)
- output = "%s%s%s" % (output, msg, suf)
- if lf:
- # strip/add lf if needed
- last_char = output[-1]
- if lf == 1 and last_char != LINE_FEED:
- output = output + LINE_FEED
- elif lf == -1 and last_char == LINE_FEED:
- output = output[:-1]
- try:
- self._fh.write(output)
- except Exception:
- # unicode strikes again ;)
- s = unicode()
- for i in xrange(len(output)):
- if ord(output[i]) < 128:
- c = output[i]
- else:
- c = "?"
- s += c
- self._fh.write("%s%s%s" % (pre, s, suf))
- self._fh.flush()
-
- def is_active(self, flag):
- """
- If given flag(s) should generate output.
- """
- # try to abort early to quicken code
- if not self.active:
- return 0
- if not flag or flag in self.active:
- return 1
- else:
- # check for multi flag type:
- if isinstance(flag, (list, tuple)):
- for s in flag:
- if s in self.active:
- return 1
- return 0
-
- def active_set(self, active_flags=None):
- """
- Returns 1 if any flags where actually set, otherwise 0.
- """
- r = 0
- ok_flags = []
- if not active_flags:
- # no debuging at all
- self.active = []
- elif isinstance(active_flags, (tuple, list)):
- flags = self._as_one_list(active_flags)
- for t in flags:
- if t not in self.debug_flags:
- sys.stderr.write("Invalid debugflag given: %s\n" % t)
- ok_flags.append(t)
-
- self.active = ok_flags
- r = 1
- else:
- # assume comma string
- try:
- flags = active_flags.split(",")
- except Exception:
- self.show("***")
- self.show("*** Invalid debug param given: %s" % active_flags)
- self.show("*** please correct your param!")
- self.show("*** due to this, full debuging is enabled")
- self.active = self.debug_flags
- for f in flags:
- s = f.strip()
- ok_flags.append(s)
- self.active = ok_flags
- self._remove_dupe_flags()
- return r
-
- def active_get(self):
- """
- Returns currently active flags.
- """
- return self.active
-
- def _as_one_list(self, items):
- """
- Init param might contain nested lists, typically from group flags.
- This code organises lst and remves dupes.
- """
- if not isinstance(items, (list, tuple)):
- return [items]
- r = []
- for l in items:
- if isinstance(l, list):
- lst2 = self._as_one_list(l)
- for l2 in lst2:
- self._append_unique_str(r, l2)
- elif l == None:
- continue
- else:
- self._append_unique_str(r, l)
- return r
-
- def _append_unique_str(self, lst, item):
- """
- Filter out any dupes.
- """
- if not isinstance(item, str):
- raise Exception("Invalid item type (should be string)", str(item))
- if item not in lst:
- lst.append(item)
- return lst
-
- def _validate_flag(self, flags):
- """
- Verify that flag is defined.
- """
- if flags:
- for flag in self._as_one_list(flags):
- if not flag in self.debug_flags:
- raise Exception("Invalid debugflag given", str(flag))
-
- def _remove_dupe_flags(self):
- """
- If multiple instances of Debug is used in same app,
- some flags might be created multiple time, filter out dupes.
- """
- unique_flags = []
- for f in self.debug_flags:
- if f not in unique_flags:
- unique_flags.append(f)
- self.debug_flags = unique_flags
-
- colors = {}
-
- def Show(self, flag, msg, prefix=""):
- msg = msg.replace("\r", "\\r").replace("\n", "\\n").replace("><", ">\n <")
- if not colors_enabled:
- pass
- elif prefix in self.colors:
- msg = self.colors[prefix] + msg + color_none
- else:
- msg = color_none + msg
- if not colors_enabled:
- prefixcolor = ""
- elif flag in self.colors:
- prefixcolor = self.colors[flag]
- else:
- prefixcolor = color_none
- if prefix == "error":
- e = sys.exc_info()
- if e[0]:
- msg = msg + "\n" + "".join(traceback_format_exception(e[0], e[1], e[2])).rstrip()
- prefix = self.prefix + prefixcolor + (flag + " " * 12)[:12] + " " + (prefix + " " * 6)[:6]
- self.show(msg, flag, prefix)
-
- def is_active(self, flag):
- if not self.active:
- return 0
- if not flag or flag in self.active and DBG_ALWAYS not in self.active or flag not in self.active and DBG_ALWAYS in self.active:
- return 1
- return 0
-
-DBG_ALWAYS = "always"
-
-# Debug=NoDebug # Uncomment this to effectively disable all debugging and all debugging overhead.
+## debug.py
+##
+## Copyright (C) 2003 Jacob Lundqvist
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU Lesser General Public License as published
+## by the Free Software Foundation; either version 2, or (at your option)
+## any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU Lesser General Public License for more details.
+
+# $Id: debug.py, v1.41 2013/10/21 alkorgun Exp $
+
+_version_ = "1.4.1"
+
+import os
+import sys
+import time
+
+from traceback import format_exception as traceback_format_exception
+
+colors_enabled = "TERM" in os.environ
+
+color_none = chr(27) + "[0m"
+color_black = chr(27) + "[30m"
+color_red = chr(27) + "[31m"
+color_green = chr(27) + "[32m"
+color_brown = chr(27) + "[33m"
+color_blue = chr(27) + "[34m"
+color_magenta = chr(27) + "[35m"
+color_cyan = chr(27) + "[36m"
+color_light_gray = chr(27) + "[37m"
+color_dark_gray = chr(27) + "[30;1m"
+color_bright_red = chr(27) + "[31;1m"
+color_bright_green = chr(27) + "[32;1m"
+color_yellow = chr(27) + "[33;1m"
+color_bright_blue = chr(27) + "[34;1m"
+color_purple = chr(27) + "[35;1m"
+color_bright_cyan = chr(27) + "[36;1m"
+color_white = chr(27) + "[37;1m"
+
+class NoDebug:
+
+ def __init__(self, *args, **kwargs):
+ self.debug_flags = []
+
+ def show(self, *args, **kwargs):
+ pass
+
+ def Show(self, *args, **kwargs):
+ pass
+
+ def is_active(self, flag):
+ pass
+
+ colors = {}
+
+ def active_set(self, active_flags=None):
+ return 0
+
+LINE_FEED = "\n"
+
+class Debug:
+
+ def __init__(self, active_flags=None, log_file=sys.stderr, prefix="DEBUG: ", sufix="\n", time_stamp=0, flag_show=None, validate_flags=False, welcome=-1):
+ self.debug_flags = []
+ if welcome == -1:
+ if active_flags and len(active_flags):
+ welcome = 1
+ else:
+ welcome = 0
+ self._remove_dupe_flags()
+ if log_file:
+ if isinstance(log_file, str):
+ try:
+ self._fh = open(log_file, "w")
+ except Exception:
+ print("ERROR: can open %s for writing." % log_file)
+ sys.exit(0)
+ else: # assume its a stream type object
+ self._fh = log_file
+ else:
+ self._fh = sys.stdout
+ if time_stamp not in (0, 1, 2):
+ raise Exception("Invalid time_stamp param", str(time_stamp))
+ self.prefix = prefix
+ self.sufix = sufix
+ self.time_stamp = time_stamp
+ self.flag_show = None # must be initialised after possible welcome
+ self.validate_flags = validate_flags
+ self.active_set(active_flags)
+ if welcome:
+ self.show("")
+ caller = sys._getframe(1) # used to get name of caller
+ try:
+ mod_name = ":%s" % caller.f_locals["__name__"]
+ except Exception:
+ mod_name = ""
+ self.show("Debug created for %s%s" % (caller.f_code.co_filename, mod_name))
+ self.show(" flags defined: %s" % ",".join(self.active))
+ if isinstance(flag_show, (str, type(None))):
+ self.flag_show = flag_show
+ else:
+ raise Exception("Invalid type for flag_show!", str(flag_show))
+
+ def show(self, msg, flag=None, prefix=None, sufix=None, lf=0):
+ """
+ flag can be of folowing types:
+ None - this msg will always be shown if any debugging is on
+ flag - will be shown if flag is active
+ (flag1,flag2,,,) - will be shown if any of the given flags are active
+
+ if prefix / sufix are not given, default ones from init will be used
+
+ lf = -1 means strip linefeed if pressent
+ lf = 1 means add linefeed if not pressent
+ """
+ if self.validate_flags:
+ self._validate_flag(flag)
+ if not self.is_active(flag):
+ return None
+ if prefix:
+ pre = prefix
+ else:
+ pre = self.prefix
+ if sufix:
+ suf = sufix
+ else:
+ suf = self.sufix
+ if self.time_stamp == 2:
+ output = "%s%s " % (
+ pre,
+ time.strftime("%b %d %H:%M:%S",
+ time.localtime(time.time()))
+ )
+ elif self.time_stamp == 1:
+ output = "%s %s" % (
+ time.strftime("%b %d %H:%M:%S",
+ time.localtime(time.time())),
+ pre
+ )
+ else:
+ output = pre
+ if self.flag_show:
+ if flag:
+ output = "%s%s%s" % (output, flag, self.flag_show)
+ else:
+ # this call uses the global default, dont print "None", just show the separator
+ output = "%s %s" % (output, self.flag_show)
+ output = "%s%s%s" % (output, msg, suf)
+ if lf:
+ # strip/add lf if needed
+ last_char = output[-1]
+ if lf == 1 and last_char != LINE_FEED:
+ output = output + LINE_FEED
+ elif lf == -1 and last_char == LINE_FEED:
+ output = output[:-1]
+ try:
+ self._fh.write(output)
+ except Exception:
+ # unicode strikes again ;)
+ s = unicode()
+ for i in xrange(len(output)):
+ if ord(output[i]) < 128:
+ c = output[i]
+ else:
+ c = "?"
+ s += c
+ self._fh.write("%s%s%s" % (pre, s, suf))
+ self._fh.flush()
+
+ def is_active(self, flag):
+ """
+ If given flag(s) should generate output.
+ """
+ # try to abort early to quicken code
+ if not self.active:
+ return 0
+ if not flag or flag in self.active:
+ return 1
+ else:
+ # check for multi flag type:
+ if isinstance(flag, (list, tuple)):
+ for s in flag:
+ if s in self.active:
+ return 1
+ return 0
+
+ def active_set(self, active_flags=None):
+ """
+ Returns 1 if any flags where actually set, otherwise 0.
+ """
+ r = 0
+ ok_flags = []
+ if not active_flags:
+ # no debuging at all
+ self.active = []
+ elif isinstance(active_flags, (tuple, list)):
+ flags = self._as_one_list(active_flags)
+ for t in flags:
+ if t not in self.debug_flags:
+ sys.stderr.write("Invalid debugflag given: %s\n" % t)
+ ok_flags.append(t)
+
+ self.active = ok_flags
+ r = 1
+ else:
+ # assume comma string
+ try:
+ flags = active_flags.split(",")
+ except Exception:
+ self.show("***")
+ self.show("*** Invalid debug param given: %s" % active_flags)
+ self.show("*** please correct your param!")
+ self.show("*** due to this, full debuging is enabled")
+ self.active = self.debug_flags
+ for f in flags:
+ s = f.strip()
+ ok_flags.append(s)
+ self.active = ok_flags
+ self._remove_dupe_flags()
+ return r
+
+ def active_get(self):
+ """
+ Returns currently active flags.
+ """
+ return self.active
+
+ def _as_one_list(self, items):
+ """
+ Init param might contain nested lists, typically from group flags.
+ This code organises lst and remves dupes.
+ """
+ if not isinstance(items, (list, tuple)):
+ return [items]
+ r = []
+ for l in items:
+ if isinstance(l, list):
+ lst2 = self._as_one_list(l)
+ for l2 in lst2:
+ self._append_unique_str(r, l2)
+ elif l == None:
+ continue
+ else:
+ self._append_unique_str(r, l)
+ return r
+
+ def _append_unique_str(self, lst, item):
+ """
+ Filter out any dupes.
+ """
+ if not isinstance(item, str):
+ raise Exception("Invalid item type (should be string)", str(item))
+ if item not in lst:
+ lst.append(item)
+ return lst
+
+ def _validate_flag(self, flags):
+ """
+ Verify that flag is defined.
+ """
+ if flags:
+ for flag in self._as_one_list(flags):
+ if not flag in self.debug_flags:
+ raise Exception("Invalid debugflag given", str(flag))
+
+ def _remove_dupe_flags(self):
+ """
+ If multiple instances of Debug is used in same app,
+ some flags might be created multiple time, filter out dupes.
+ """
+ unique_flags = []
+ for f in self.debug_flags:
+ if f not in unique_flags:
+ unique_flags.append(f)
+ self.debug_flags = unique_flags
+
+ colors = {}
+
+ def Show(self, flag, msg, prefix=""):
+ msg = msg.replace("\r", "\\r").replace("\n", "\\n").replace("><", ">\n <")
+ if not colors_enabled:
+ pass
+ elif prefix in self.colors:
+ msg = self.colors[prefix] + msg + color_none
+ else:
+ msg = color_none + msg
+ if not colors_enabled:
+ prefixcolor = ""
+ elif flag in self.colors:
+ prefixcolor = self.colors[flag]
+ else:
+ prefixcolor = color_none
+ if prefix == "error":
+ e = sys.exc_info()
+ if e[0]:
+ msg = msg + "\n" + "".join(traceback_format_exception(e[0], e[1], e[2])).rstrip()
+ prefix = self.prefix + prefixcolor + (flag + " " * 12)[:12] + " " + (prefix + " " * 6)[:6]
+ self.show(msg, flag, prefix)
+
+ def is_active(self, flag):
+ if not self.active:
+ return 0
+ if not flag or flag in self.active and DBG_ALWAYS not in self.active or flag not in self.active and DBG_ALWAYS in self.active:
+ return 1
+ return 0
+
+DBG_ALWAYS = "always"
+
+# Debug=NoDebug # Uncomment this to effectively disable all debugging and all debugging overhead.
diff --git a/xmpp/dispatcher.py b/xmpp/dispatcher.py
index f873ffd..4674969 100644
--- a/xmpp/dispatcher.py
+++ b/xmpp/dispatcher.py
@@ -1,488 +1,488 @@
-## transports.py
-##
-## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2, or (at your option)
-## any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-
-# $Id: dispatcher.py, v1.45 2014/02/16 alkorgun Exp $
-
-"""
-Main xmpppy mechanism. Provides library with methods to assign different handlers
-to different XMPP stanzas.
-Contains one tunable attribute: DefaultTimeout (25 seconds by default). It defines time that
-Dispatcher.SendAndWaitForResponce method will wait for reply stanza before giving up.
-"""
-
-import sys
-import time
-from . import simplexml
-
-from .plugin import PlugIn
-from .protocol import *
-from select import select
-from xml.parsers.expat import ExpatError
-
-DefaultTimeout = 25
-ID = 0
-
-DBG_LINE = "dispatcher"
-
-if sys.hexversion >= 0x30000F0:
-
- def deferredRaise(e):
- raise e[0](e[1]).with_traceback(e[2])
-
-else:
-
- def deferredRaise(e):
- raise e[0], e[1], e[2]
-
-class Dispatcher(PlugIn):
- """
- Ancestor of PlugIn class. Handles XMPP stream, i.e. aware of stream headers.
- Can be plugged out/in to restart these headers (used for SASL f.e.).
- """
- def __init__(self):
- PlugIn.__init__(self)
- self.handlers = {}
- self._expected = {}
- self._defaultHandler = None
- self._pendingExceptions = []
- self._eventHandler = None
- self._cycleHandlers = []
- self._exported_methods = [
- self.Process,
- self.RegisterHandler,
-# self.RegisterDefaultHandler,
- self.RegisterEventHandler,
- self.UnregisterCycleHandler,
- self.RegisterCycleHandler,
- self.RegisterHandlerOnce,
- self.UnregisterHandler,
- self.RegisterProtocol,
- self.WaitForResponse,
- self.SendAndWaitForResponse,
- self.send,
- self.SendAndCallForResponse,
- self.disconnect,
- self.iter
- ]
-
- def dumpHandlers(self):
- """
- Return set of user-registered callbacks in it's internal format.
- Used within the library to carry user handlers set over Dispatcher replugins.
- """
- return self.handlers
-
- def restoreHandlers(self, handlers):
- """
- Restores user-registered callbacks structure from dump previously obtained via dumpHandlers.
- Used within the library to carry user handlers set over Dispatcher replugins.
- """
- self.handlers = handlers
-
- def _init(self):
- """
- Registers default namespaces/protocols/handlers. Used internally.
- """
- self.RegisterNamespace("unknown")
- self.RegisterNamespace(NS_STREAMS)
- self.RegisterNamespace(self._owner.defaultNamespace)
- self.RegisterProtocol("iq", Iq)
- self.RegisterProtocol("presence", Presence)
- self.RegisterProtocol("message", Message)
-# self.RegisterDefaultHandler(self.returnStanzaHandler)
- self.RegisterHandler("error", self.streamErrorHandler, xmlns=NS_STREAMS)
-
- def plugin(self, owner):
- """
- Plug the Dispatcher instance into Client class instance and send initial stream header. Used internally.
- """
- self._init()
- for method in self._old_owners_methods:
- if method.__name__ == "send":
- self._owner_send = method
- break
- self._owner.lastErrNode = None
- self._owner.lastErr = None
- self._owner.lastErrCode = None
- self.StreamInit()
-
- def plugout(self):
- """
- Prepares instance to be destructed.
- """
- self.Stream.dispatch = None
- self.Stream.DEBUG = None
- self.Stream.features = None
- self.Stream.destroy()
-
- def StreamInit(self):
- """
- Send an initial stream header.
- """
- self.Stream = simplexml.NodeBuilder()
- self.Stream._dispatch_depth = 2
- self.Stream.dispatch = self.dispatch
- self.Stream.stream_header_received = self._check_stream_start
- self._owner.debug_flags.append(simplexml.DBG_NODEBUILDER)
- self.Stream.DEBUG = self._owner.DEBUG
- self.Stream.features = None
- self._metastream = Node("stream:stream")
- self._metastream.setNamespace(self._owner.Namespace)
- self._metastream.setAttr("version", "1.0")
- self._metastream.setAttr("xmlns:stream", NS_STREAMS)
- self._metastream.setAttr("to", self._owner.Server)
- self._owner.send("<?xml version=\"1.0\"?>%s>" % str(self._metastream)[:-2])
-
- def _check_stream_start(self, ns, tag, attrs):
- if ns != NS_STREAMS or tag != "stream":
- raise ValueError("Incorrect stream start: (%s,%s). Terminating." % (tag, ns))
-
- def Process(self, timeout=8):
- """
- Check incoming stream for data waiting. If "timeout" is positive - block for as max. this time.
- Returns:
- 1) length of processed data if some data were processed;
- 2) "0" string if no data were processed but link is alive;
- 3) 0 (zero) if underlying connection is closed.
- Take note that in case of disconnection detect during Process() call
- disconnect handlers are called automatically.
- """
- for handler in self._cycleHandlers:
- handler(self)
- if self._pendingExceptions:
- deferredRaise(self._pendingExceptions.pop())
- if self._owner.Connection.pending_data(timeout):
- try:
- data = self._owner.Connection.receive()
- except IOError:
- return None
- try:
- self.Stream.Parse(data)
- except ExpatError:
- pass
- if self._pendingExceptions:
- deferredRaise(self._pendingExceptions.pop())
- if data:
- return len(data)
- return "0"
-
- def RegisterNamespace(self, xmlns, order="info"):
- """
- Creates internal structures for newly registered namespace.
- You can register handlers for this namespace afterwards. By default one namespace
- already registered (jabber:client or jabber:component:accept depending on context.
- """
- self.DEBUG("Registering namespace \"%s\"" % xmlns, order)
- self.handlers[xmlns] = {}
- self.RegisterProtocol("unknown", Protocol, xmlns=xmlns)
- self.RegisterProtocol("default", Protocol, xmlns=xmlns)
-
- def RegisterProtocol(self, tag_name, Proto, xmlns=None, order="info"):
- """
- Used to declare some top-level stanza name to dispatcher.
- Needed to start registering handlers for such stanzas.
- Iq, message and presence protocols are registered by default.
- """
- if not xmlns:
- xmlns = self._owner.defaultNamespace
- self.DEBUG("Registering protocol \"%s\" as %s(%s)" % (tag_name, Proto, xmlns), order)
- self.handlers[xmlns][tag_name] = {"type": Proto, "default": []}
-
- def RegisterNamespaceHandler(self, xmlns, handler, typ="", ns="", makefirst=0, system=0):
- """
- Register handler for processing all stanzas for specified namespace.
- """
- self.RegisterHandler("default", handler, typ, ns, xmlns, makefirst, system)
-
- def RegisterHandler(self, name, handler, typ="", ns="", xmlns=None, makefirst=0, system=0):
- """Register user callback as stanzas handler of declared type. Callback must take
- (if chained, see later) arguments: dispatcher instance (for replying), incomed
- return of previous handlers.
- The callback must raise xmpp.NodeProcessed just before return if it want preven
- callbacks to be called with the same stanza as argument _and_, more importantly
- library from returning stanza to sender with error set (to be enabled in 0.2 ve
- Arguments:
- "name" - name of stanza. F.e. "iq".
- "handler" - user callback.
- "typ" - value of stanza's "type" attribute. If not specified any value match
- "ns" - namespace of child that stanza must contain.
- "chained" - chain together output of several handlers.
- "makefirst" - insert handler in the beginning of handlers list instead of
- adding it to the end. Note that more common handlers (i.e. w/o "typ" and
- will be called first nevertheless).
- "system" - call handler even if NodeProcessed Exception were raised already.
- """
- if not xmlns:
- xmlns = self._owner.defaultNamespace
- self.DEBUG("Registering handler %s for \"%s\" type->%s ns->%s(%s)" % (handler, name, typ, ns, xmlns), "info")
- if not typ and not ns:
- typ = "default"
- if xmlns not in self.handlers:
- self.RegisterNamespace(xmlns, "warn")
- if name not in self.handlers[xmlns]:
- self.RegisterProtocol(name, Protocol, xmlns, "warn")
- if typ + ns not in self.handlers[xmlns][name]:
- self.handlers[xmlns][name][typ + ns] = []
- if makefirst:
- self.handlers[xmlns][name][typ + ns].insert(0, {"func": handler, "system": system})
- else:
- self.handlers[xmlns][name][typ + ns].append({"func": handler, "system": system})
-
- def RegisterHandlerOnce(self, name, handler, typ="", ns="", xmlns=None, makefirst=0, system=0):
- """
- Unregister handler after first call (not implemented yet).
- """
- if not xmlns:
- xmlns = self._owner.defaultNamespace
- self.RegisterHandler(name, handler, typ, ns, xmlns, makefirst, system)
-
- def UnregisterHandler(self, name, handler, typ="", ns="", xmlns=None):
- """
- Unregister handler. "typ" and "ns" must be specified exactly the same as with registering.
- """
- if not xmlns:
- xmlns = self._owner.defaultNamespace
- if xmlns not in self.handlers:
- return None
- if not typ and not ns:
- typ = "default"
- for pack in self.handlers[xmlns][name][typ + ns]:
- if handler == pack["func"]:
- break
- else:
- pack = None
- try:
- self.handlers[xmlns][name][typ + ns].remove(pack)
- except ValueError:
- pass
-
- def RegisterDefaultHandler(self, handler):
- """
- Specify the handler that will be used if no NodeProcessed exception were raised.
- This is returnStanzaHandler by default.
- """
- self._defaultHandler = handler
-
- def RegisterEventHandler(self, handler):
- """
- Register handler that will process events. F.e. "FILERECEIVED" event.
- """
- self._eventHandler = handler
-
- def returnStanzaHandler(self, conn, stanza):
- """
- Return stanza back to the sender with <feature-not-implemennted/> error set.
- """
- if stanza.getType() in ("get", "set"):
- conn.send(Error(stanza, ERR_FEATURE_NOT_IMPLEMENTED))
-
- def streamErrorHandler(self, conn, error):
- name, text = "error", error.getData()
- for tag in error.getChildren():
- if tag.getNamespace() == NS_XMPP_STREAMS:
- if tag.getName() == "text":
- text = tag.getData()
- else:
- name = tag.getName()
- if name in stream_exceptions.keys():
- exc = stream_exceptions[name]
- else:
- exc = StreamError
- raise exc((name, text))
-
- def RegisterCycleHandler(self, handler):
- """
- Register handler that will be called on every Dispatcher.Process() call.
- """
- if handler not in self._cycleHandlers:
- self._cycleHandlers.append(handler)
-
- def UnregisterCycleHandler(self, handler):
- """
- Unregister handler that will is called on every Dispatcher.Process() call.
- """
- if handler in self._cycleHandlers:
- self._cycleHandlers.remove(handler)
-
- def Event(self, realm, event, data):
- """
- Raise some event. Takes three arguments:
- 1) "realm" - scope of event. Usually a namespace.
- 2) "event" - the event itself. F.e. "SUCESSFULL SEND".
- 3) data that comes along with event. Depends on event.
- """
- if self._eventHandler:
- self._eventHandler(realm, event, data)
-
- def dispatch(self, stanza, session=None, direct=0):
- """
- Main procedure that performs XMPP stanza recognition and calling apppropriate handlers for it.
- Called internally.
- """
- if not session:
- session = self
- session.Stream._mini_dom = None
- name = stanza.getName()
- if not direct and self._owner._route:
- if name == "route":
- if stanza.getAttr("error") == None:
- if len(stanza.getChildren()) == 1:
- stanza = stanza.getChildren()[0]
- name = stanza.getName()
- else:
- for each in stanza.getChildren():
- self.dispatch(each, session, direct=1)
- return None
- elif name == "presence":
- return None
- elif name in ("features", "bind"):
- pass
- else:
- raise UnsupportedStanzaType(name)
- if name == "features":
- session.Stream.features = stanza
- xmlns = stanza.getNamespace()
- if xmlns not in self.handlers:
- self.DEBUG("Unknown namespace: " + xmlns, "warn")
- xmlns = "unknown"
- if name not in self.handlers[xmlns]:
- self.DEBUG("Unknown stanza: " + name, "warn")
- name = "unknown"
- else:
- self.DEBUG("Got %s/%s stanza" % (xmlns, name), "ok")
- if isinstance(stanza, Node):
- stanza = self.handlers[xmlns][name]["type"](node=stanza)
- typ = stanza.getType()
- if not typ:
- typ = ""
- stanza.props = stanza.getProperties()
- ID = stanza.getID()
- session.DEBUG("Dispatching %s stanza with type->%s props->%s id->%s" % (name, typ, stanza.props, ID), "ok")
- ls = ["default"] # we will use all handlers:
- if typ in self.handlers[xmlns][name]:
- ls.append(typ) # from very common...
- for prop in stanza.props:
- if prop in self.handlers[xmlns][name]:
- ls.append(prop)
- if typ and (typ + prop) in self.handlers[xmlns][name]:
- ls.append(typ + prop) # ...to very particular
- chain = self.handlers[xmlns]["default"]["default"]
- for key in ls:
- if key:
- chain = chain + self.handlers[xmlns][name][key]
- output = ""
- if ID in session._expected:
- user = 0
- if isinstance(session._expected[ID], tuple):
- cb, args = session._expected.pop(ID)
- session.DEBUG("Expected stanza arrived. Callback %s(%s) found!" % (cb, args), "ok")
- try:
- cb(session, stanza, **args)
- except NodeProcessed:
- pass
- else:
- session.DEBUG("Expected stanza arrived!", "ok")
- session._expected[ID] = stanza
- else:
- user = 1
- for handler in chain:
- if user or handler["system"]:
- try:
- handler["func"](session, stanza)
- except NodeProcessed:
- user = 0
- except Exception:
- self._pendingExceptions.insert(0, sys.exc_info())
- if user and self._defaultHandler:
- self._defaultHandler(session, stanza)
-
- def WaitForResponse(self, ID, timeout=DefaultTimeout):
- """
- Block and wait until stanza with specific "id" attribute will come.
- If no such stanza is arrived within timeout, return None.
- If operation failed for some reason then owner's attributes
- lastErrNode, lastErr and lastErrCode are set accordingly.
- """
- self._expected[ID] = None
- abort_time = time.time() + timeout
- self.DEBUG("Waiting for ID:%s with timeout %s..." % (ID, timeout), "wait")
- while not self._expected[ID]:
- if not self.Process(0.04):
- self._owner.lastErr = "Disconnect"
- return None
- if time.time() > abort_time:
- self._owner.lastErr = "Timeout"
- return None
- resp = self._expected.pop(ID)
- if resp.getErrorCode():
- self._owner.lastErrNode = resp
- self._owner.lastErr = resp.getError()
- self._owner.lastErrCode = resp.getErrorCode()
- return resp
-
- def SendAndWaitForResponse(self, stanza, timeout=DefaultTimeout):
- """
- Put stanza on the wire and wait for recipient's response to it.
- """
- return self.WaitForResponse(self.send(stanza), timeout)
-
- def SendAndCallForResponse(self, stanza, func, args={}):
- """
- Put stanza on the wire and call back when recipient replies.
- Additional callback arguments can be specified in args.
- """
- self._expected[self.send(stanza)] = (func, args)
-
- def send(self, stanza):
- """
- Serialize stanza and put it on the wire. Assign an unique ID to it before send.
- Returns assigned ID.
- """
- if isinstance(stanza, basestring):
- return self._owner_send(stanza)
- if not isinstance(stanza, Protocol):
- id = None
- elif not stanza.getID():
- global ID
- ID += 1
- id = repr(ID)
- stanza.setID(id)
- else:
- id = stanza.getID()
- if self._owner._registered_name and not stanza.getAttr("from"):
- stanza.setAttr("from", self._owner._registered_name)
- if self._owner._route and stanza.getName() != "bind":
- to = self._owner.Server
- if stanza.getTo() and stanza.getTo().getDomain():
- to = stanza.getTo().getDomain()
- frm = stanza.getFrom()
- if frm.getDomain():
- frm = frm.getDomain()
- route = Protocol("route", to=to, frm=frm, payload=[stanza])
- stanza = route
- stanza.setNamespace(self._owner.Namespace)
- stanza.setParent(self._metastream)
- self._owner_send(stanza)
- return id
-
- def disconnect(self):
- """
- Send a stream terminator and and handle all incoming stanzas before stream closure.
- """
- if self._owner.connected:
- self._owner_send("</stream:stream>")
- while self.Process(1):
- pass
-
- iter = type(send)(Process.__code__, Process.__globals__, name = "iter", argdefs = Process.__defaults__, closure = Process.__closure__)
+## transports.py
+##
+## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2, or (at your option)
+## any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+# $Id: dispatcher.py, v1.45 2014/02/16 alkorgun Exp $
+
+"""
+Main xmpppy mechanism. Provides library with methods to assign different handlers
+to different XMPP stanzas.
+Contains one tunable attribute: DefaultTimeout (25 seconds by default). It defines time that
+Dispatcher.SendAndWaitForResponce method will wait for reply stanza before giving up.
+"""
+
+import sys
+import time
+from . import simplexml
+
+from .plugin import PlugIn
+from .protocol import *
+from select import select
+from xml.parsers.expat import ExpatError
+
+DefaultTimeout = 25
+ID = 0
+
+DBG_LINE = "dispatcher"
+
+if sys.hexversion >= 0x30000F0:
+
+ def deferredRaise(e):
+ raise e[0](e[1]).with_traceback(e[2])
+
+else:
+
+ def deferredRaise(e):
+ raise e[0], e[1], e[2]
+
+class Dispatcher(PlugIn):
+ """
+ Ancestor of PlugIn class. Handles XMPP stream, i.e. aware of stream headers.
+ Can be plugged out/in to restart these headers (used for SASL f.e.).
+ """
+ def __init__(self):
+ PlugIn.__init__(self)
+ self.handlers = {}
+ self._expected = {}
+ self._defaultHandler = None
+ self._pendingExceptions = []
+ self._eventHandler = None
+ self._cycleHandlers = []
+ self._exported_methods = [
+ self.Process,
+ self.RegisterHandler,
+# self.RegisterDefaultHandler,
+ self.RegisterEventHandler,
+ self.UnregisterCycleHandler,
+ self.RegisterCycleHandler,
+ self.RegisterHandlerOnce,
+ self.UnregisterHandler,
+ self.RegisterProtocol,
+ self.WaitForResponse,
+ self.SendAndWaitForResponse,
+ self.send,
+ self.SendAndCallForResponse,
+ self.disconnect,
+ self.iter
+ ]
+
+ def dumpHandlers(self):
+ """
+ Return set of user-registered callbacks in it's internal format.
+ Used within the library to carry user handlers set over Dispatcher replugins.
+ """
+ return self.handlers
+
+ def restoreHandlers(self, handlers):
+ """
+ Restores user-registered callbacks structure from dump previously obtained via dumpHandlers.
+ Used within the library to carry user handlers set over Dispatcher replugins.
+ """
+ self.handlers = handlers
+
+ def _init(self):
+ """
+ Registers default namespaces/protocols/handlers. Used internally.
+ """
+ self.RegisterNamespace("unknown")
+ self.RegisterNamespace(NS_STREAMS)
+ self.RegisterNamespace(self._owner.defaultNamespace)
+ self.RegisterProtocol("iq", Iq)
+ self.RegisterProtocol("presence", Presence)
+ self.RegisterProtocol("message", Message)
+# self.RegisterDefaultHandler(self.returnStanzaHandler)
+ self.RegisterHandler("error", self.streamErrorHandler, xmlns=NS_STREAMS)
+
+ def plugin(self, owner):
+ """
+ Plug the Dispatcher instance into Client class instance and send initial stream header. Used internally.
+ """
+ self._init()
+ for method in self._old_owners_methods:
+ if method.__name__ == "send":
+ self._owner_send = method
+ break
+ self._owner.lastErrNode = None
+ self._owner.lastErr = None
+ self._owner.lastErrCode = None
+ self.StreamInit()
+
+ def plugout(self):
+ """
+ Prepares instance to be destructed.
+ """
+ self.Stream.dispatch = None
+ self.Stream.DEBUG = None
+ self.Stream.features = None
+ self.Stream.destroy()
+
+ def StreamInit(self):
+ """
+ Send an initial stream header.
+ """
+ self.Stream = simplexml.NodeBuilder()
+ self.Stream._dispatch_depth = 2
+ self.Stream.dispatch = self.dispatch
+ self.Stream.stream_header_received = self._check_stream_start
+ self._owner.debug_flags.append(simplexml.DBG_NODEBUILDER)
+ self.Stream.DEBUG = self._owner.DEBUG
+ self.Stream.features = None
+ self._metastream = Node("stream:stream")
+ self._metastream.setNamespace(self._owner.Namespace)
+ self._metastream.setAttr("version", "1.0")
+ self._metastream.setAttr("xmlns:stream", NS_STREAMS)
+ self._metastream.setAttr("to", self._owner.Server)
+ self._owner.send("<?xml version=\"1.0\"?>%s>" % str(self._metastream)[:-2])
+
+ def _check_stream_start(self, ns, tag, attrs):
+ if ns != NS_STREAMS or tag != "stream":
+ raise ValueError("Incorrect stream start: (%s,%s). Terminating." % (tag, ns))
+
+ def Process(self, timeout=8):
+ """
+ Check incoming stream for data waiting. If "timeout" is positive - block for as max. this time.
+ Returns:
+ 1) length of processed data if some data were processed;
+ 2) "0" string if no data were processed but link is alive;
+ 3) 0 (zero) if underlying connection is closed.
+ Take note that in case of disconnection detect during Process() call
+ disconnect handlers are called automatically.
+ """
+ for handler in self._cycleHandlers:
+ handler(self)
+ if self._pendingExceptions:
+ deferredRaise(self._pendingExceptions.pop())
+ if self._owner.Connection.pending_data(timeout):
+ try:
+ data = self._owner.Connection.receive()
+ except IOError:
+ return None
+ try:
+ self.Stream.Parse(data)
+ except ExpatError:
+ pass
+ if self._pendingExceptions:
+ deferredRaise(self._pendingExceptions.pop())
+ if data:
+ return len(data)
+ return "0"
+
+ def RegisterNamespace(self, xmlns, order="info"):
+ """
+ Creates internal structures for newly registered namespace.
+ You can register handlers for this namespace afterwards. By default one namespace
+ already registered (jabber:client or jabber:component:accept depending on context.
+ """
+ self.DEBUG("Registering namespace \"%s\"" % xmlns, order)
+ self.handlers[xmlns] = {}
+ self.RegisterProtocol("unknown", Protocol, xmlns=xmlns)
+ self.RegisterProtocol("default", Protocol, xmlns=xmlns)
+
+ def RegisterProtocol(self, tag_name, Proto, xmlns=None, order="info"):
+ """
+ Used to declare some top-level stanza name to dispatcher.
+ Needed to start registering handlers for such stanzas.
+ Iq, message and presence protocols are registered by default.
+ """
+ if not xmlns:
+ xmlns = self._owner.defaultNamespace
+ self.DEBUG("Registering protocol \"%s\" as %s(%s)" % (tag_name, Proto, xmlns), order)
+ self.handlers[xmlns][tag_name] = {"type": Proto, "default": []}
+
+ def RegisterNamespaceHandler(self, xmlns, handler, typ="", ns="", makefirst=0, system=0):
+ """
+ Register handler for processing all stanzas for specified namespace.
+ """
+ self.RegisterHandler("default", handler, typ, ns, xmlns, makefirst, system)
+
+ def RegisterHandler(self, name, handler, typ="", ns="", xmlns=None, makefirst=0, system=0):
+ """Register user callback as stanzas handler of declared type. Callback must take
+ (if chained, see later) arguments: dispatcher instance (for replying), incomed
+ return of previous handlers.
+ The callback must raise xmpp.NodeProcessed just before return if it want preven
+ callbacks to be called with the same stanza as argument _and_, more importantly
+ library from returning stanza to sender with error set (to be enabled in 0.2 ve
+ Arguments:
+ "name" - name of stanza. F.e. "iq".
+ "handler" - user callback.
+ "typ" - value of stanza's "type" attribute. If not specified any value match
+ "ns" - namespace of child that stanza must contain.
+ "chained" - chain together output of several handlers.
+ "makefirst" - insert handler in the beginning of handlers list instead of
+ adding it to the end. Note that more common handlers (i.e. w/o "typ" and
+ will be called first nevertheless).
+ "system" - call handler even if NodeProcessed Exception were raised already.
+ """
+ if not xmlns:
+ xmlns = self._owner.defaultNamespace
+ self.DEBUG("Registering handler %s for \"%s\" type->%s ns->%s(%s)" % (handler, name, typ, ns, xmlns), "info")
+ if not typ and not ns:
+ typ = "default"
+ if xmlns not in self.handlers:
+ self.RegisterNamespace(xmlns, "warn")
+ if name not in self.handlers[xmlns]:
+ self.RegisterProtocol(name, Protocol, xmlns, "warn")
+ if typ + ns not in self.handlers[xmlns][name]:
+ self.handlers[xmlns][name][typ + ns] = []
+ if makefirst:
+ self.handlers[xmlns][name][typ + ns].insert(0, {"func": handler, "system": system})
+ else:
+ self.handlers[xmlns][name][typ + ns].append({"func": handler, "system": system})
+
+ def RegisterHandlerOnce(self, name, handler, typ="", ns="", xmlns=None, makefirst=0, system=0):
+ """
+ Unregister handler after first call (not implemented yet).
+ """
+ if not xmlns:
+ xmlns = self._owner.defaultNamespace
+ self.RegisterHandler(name, handler, typ, ns, xmlns, makefirst, system)
+
+ def UnregisterHandler(self, name, handler, typ="", ns="", xmlns=None):
+ """
+ Unregister handler. "typ" and "ns" must be specified exactly the same as with registering.
+ """
+ if not xmlns:
+ xmlns = self._owner.defaultNamespace
+ if xmlns not in self.handlers:
+ return None
+ if not typ and not ns:
+ typ = "default"
+ for pack in self.handlers[xmlns][name][typ + ns]:
+ if handler == pack["func"]:
+ break
+ else:
+ pack = None
+ try:
+ self.handlers[xmlns][name][typ + ns].remove(pack)
+ except ValueError:
+ pass
+
+ def RegisterDefaultHandler(self, handler):
+ """
+ Specify the handler that will be used if no NodeProcessed exception were raised.
+ This is returnStanzaHandler by default.
+ """
+ self._defaultHandler = handler
+
+ def RegisterEventHandler(self, handler):
+ """
+ Register handler that will process events. F.e. "FILERECEIVED" event.
+ """
+ self._eventHandler = handler
+
+ def returnStanzaHandler(self, conn, stanza):
+ """
+ Return stanza back to the sender with <feature-not-implemennted/> error set.
+ """
+ if stanza.getType() in ("get", "set"):
+ conn.send(Error(stanza, ERR_FEATURE_NOT_IMPLEMENTED))
+
+ def streamErrorHandler(self, conn, error):
+ name, text = "error", error.getData()
+ for tag in error.getChildren():
+ if tag.getNamespace() == NS_XMPP_STREAMS:
+ if tag.getName() == "text":
+ text = tag.getData()
+ else:
+ name = tag.getName()
+ if name in stream_exceptions.keys():
+ exc = stream_exceptions[name]
+ else:
+ exc = StreamError
+ raise exc((name, text))
+
+ def RegisterCycleHandler(self, handler):
+ """
+ Register handler that will be called on every Dispatcher.Process() call.
+ """
+ if handler not in self._cycleHandlers:
+ self._cycleHandlers.append(handler)
+
+ def UnregisterCycleHandler(self, handler):
+ """
+ Unregister handler that will is called on every Dispatcher.Process() call.
+ """
+ if handler in self._cycleHandlers:
+ self._cycleHandlers.remove(handler)
+
+ def Event(self, realm, event, data):
+ """
+ Raise some event. Takes three arguments:
+ 1) "realm" - scope of event. Usually a namespace.
+ 2) "event" - the event itself. F.e. "SUCESSFULL SEND".
+ 3) data that comes along with event. Depends on event.
+ """
+ if self._eventHandler:
+ self._eventHandler(realm, event, data)
+
+ def dispatch(self, stanza, session=None, direct=0):
+ """
+ Main procedure that performs XMPP stanza recognition and calling apppropriate handlers for it.
+ Called internally.
+ """
+ if not session:
+ session = self
+ session.Stream._mini_dom = None
+ name = stanza.getName()
+ if not direct and self._owner._route:
+ if name == "route":
+ if stanza.getAttr("error") == None:
+ if len(stanza.getChildren()) == 1:
+ stanza = stanza.getChildren()[0]
+ name = stanza.getName()
+ else:
+ for each in stanza.getChildren():
+ self.dispatch(each, session, direct=1)
+ return None
+ elif name == "presence":
+ return None
+ elif name in ("features", "bind"):
+ pass
+ else:
+ raise UnsupportedStanzaType(name)
+ if name == "features":
+ session.Stream.features = stanza
+ xmlns = stanza.getNamespace()
+ if xmlns not in self.handlers:
+ self.DEBUG("Unknown namespace: " + xmlns, "warn")
+ xmlns = "unknown"
+ if name not in self.handlers[xmlns]:
+ self.DEBUG("Unknown stanza: " + name, "warn")
+ name = "unknown"
+ else:
+ self.DEBUG("Got %s/%s stanza" % (xmlns, name), "ok")
+ if isinstance(stanza, Node):
+ stanza = self.handlers[xmlns][name]["type"](node=stanza)
+ typ = stanza.getType()
+ if not typ:
+ typ = ""
+ stanza.props = stanza.getProperties()
+ ID = stanza.getID()
+ session.DEBUG("Dispatching %s stanza with type->%s props->%s id->%s" % (name, typ, stanza.props, ID), "ok")
+ ls = ["default"] # we will use all handlers:
+ if typ in self.handlers[xmlns][name]:
+ ls.append(typ) # from very common...
+ for prop in stanza.props:
+ if prop in self.handlers[xmlns][name]:
+ ls.append(prop)
+ if typ and (typ + prop) in self.handlers[xmlns][name]:
+ ls.append(typ + prop) # ...to very particular
+ chain = self.handlers[xmlns]["default"]["default"]
+ for key in ls:
+ if key:
+ chain = chain + self.handlers[xmlns][name][key]
+ output = ""
+ if ID in session._expected:
+ user = 0
+ if isinstance(session._expected[ID], tuple):
+ cb, args = session._expected.pop(ID)
+ session.DEBUG("Expected stanza arrived. Callback %s(%s) found!" % (cb, args), "ok")
+ try:
+ cb(session, stanza, **args)
+ except NodeProcessed:
+ pass
+ else:
+ session.DEBUG("Expected stanza arrived!", "ok")
+ session._expected[ID] = stanza
+ else:
+ user = 1
+ for handler in chain:
+ if user or handler["system"]:
+ try:
+ handler["func"](session, stanza)
+ except NodeProcessed:
+ user = 0
+ except Exception:
+ self._pendingExceptions.insert(0, sys.exc_info())
+ if user and self._defaultHandler:
+ self._defaultHandler(session, stanza)
+
+ def WaitForResponse(self, ID, timeout=DefaultTimeout):
+ """
+ Block and wait until stanza with specific "id" attribute will come.
+ If no such stanza is arrived within timeout, return None.
+ If operation failed for some reason then owner's attributes
+ lastErrNode, lastErr and lastErrCode are set accordingly.
+ """
+ self._expected[ID] = None
+ abort_time = time.time() + timeout
+ self.DEBUG("Waiting for ID:%s with timeout %s..." % (ID, timeout), "wait")
+ while not self._expected[ID]:
+ if not self.Process(0.04):
+ self._owner.lastErr = "Disconnect"
+ return None
+ if time.time() > abort_time:
+ self._owner.lastErr = "Timeout"
+ return None
+ resp = self._expected.pop(ID)
+ if resp.getErrorCode():
+ self._owner.lastErrNode = resp
+ self._owner.lastErr = resp.getError()
+ self._owner.lastErrCode = resp.getErrorCode()
+ return resp
+
+ def SendAndWaitForResponse(self, stanza, timeout=DefaultTimeout):
+ """
+ Put stanza on the wire and wait for recipient's response to it.
+ """
+ return self.WaitForResponse(self.send(stanza), timeout)
+
+ def SendAndCallForResponse(self, stanza, func, args={}):
+ """
+ Put stanza on the wire and call back when recipient replies.
+ Additional callback arguments can be specified in args.
+ """
+ self._expected[self.send(stanza)] = (func, args)
+
+ def send(self, stanza):
+ """
+ Serialize stanza and put it on the wire. Assign an unique ID to it before send.
+ Returns assigned ID.
+ """
+ if isinstance(stanza, basestring):
+ return self._owner_send(stanza)
+ if not isinstance(stanza, Protocol):
+ id = None
+ elif not stanza.getID():
+ global ID
+ ID += 1
+ id = repr(ID)
+ stanza.setID(id)
+ else:
+ id = stanza.getID()
+ if self._owner._registered_name and not stanza.getAttr("from"):
+ stanza.setAttr("from", self._owner._registered_name)
+ if self._owner._route and stanza.getName() != "bind":
+ to = self._owner.Server
+ if stanza.getTo() and stanza.getTo().getDomain():
+ to = stanza.getTo().getDomain()
+ frm = stanza.getFrom()
+ if frm.getDomain():
+ frm = frm.getDomain()
+ route = Protocol("route", to=to, frm=frm, payload=[stanza])
+ stanza = route
+ stanza.setNamespace(self._owner.Namespace)
+ stanza.setParent(self._metastream)
+ self._owner_send(stanza)
+ return id
+
+ def disconnect(self):
+ """
+ Send a stream terminator and and handle all incoming stanzas before stream closure.
+ """
+ if self._owner.connected:
+ self._owner_send("</stream:stream>")
+ while self.Process(1):
+ pass
+
+ iter = type(send)(Process.__code__, Process.__globals__, name = "iter", argdefs = Process.__defaults__, closure = Process.__closure__)
diff --git a/xmpp/features.py b/xmpp/features.py
index 34a81b0..86da766 100644
--- a/xmpp/features.py
+++ b/xmpp/features.py
@@ -1,230 +1,230 @@
-## features.py
-##
-## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2, or (at your option)
-## any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-
-# $Id: features.py, v1.26 2013/10/21 alkorgun Exp $
-
-"""
-This module contains variable stuff that is not worth splitting into separate modules.
-Here is:
- DISCO client and agents-to-DISCO and browse-to-DISCO emulators.
- IBR and password manager.
- jabber:iq:privacy methods
-All these methods takes "disp" first argument that should be already connected
-(and in most cases already authorised) dispatcher instance.
-"""
-
-from .protocol import *
-
-REGISTER_DATA_RECEIVED = "REGISTER DATA RECEIVED"
-
-def _discover(disp, ns, jid, node=None, fb2b=0, fb2a=1):
- """
- Try to obtain info from the remote object.
- If remote object doesn't support disco fall back to browse (if fb2b is true)
- and if it doesnt support browse (or fb2b is not true) fall back to agents protocol
- (if gb2a is true). Returns obtained info. Used internally.
- """
- iq = Iq(to=jid, typ="get", queryNS=ns)
- if node:
- iq.setQuerynode(node)
- rep = disp.SendAndWaitForResponse(iq)
- if fb2b and not isResultNode(rep):
- rep = disp.SendAndWaitForResponse(Iq(to=jid, typ="get", queryNS=NS_BROWSE)) # Fallback to browse
- if fb2a and not isResultNode(rep):
- rep = disp.SendAndWaitForResponse(Iq(to=jid, typ="get", queryNS=NS_AGENTS)) # Fallback to agents
- if isResultNode(rep):
- return [n for n in rep.getQueryPayload() if isinstance(n, Node)]
- return []
-
-def discoverItems(disp, jid, node=None):
- """
- Query remote object about any items that it contains. Return items list.
- """
- ret = []
- for i in _discover(disp, NS_DISCO_ITEMS, jid, node):
- if i.getName() == "agent" and i.getTag("name"):
- i.setAttr("name", i.getTagData("name"))
- ret.append(i.attrs)
- return ret
-
-def discoverInfo(disp, jid, node=None):
- """
- Query remote object about info that it publishes. Returns identities and features lists.
- """
- identities, features = [], []
- for i in _discover(disp, NS_DISCO_INFO, jid, node):
- if i.getName() == "identity":
- identities.append(i.attrs)
- elif i.getName() == "feature":
- features.append(i.getAttr("var"))
- elif i.getName() == "agent":
- if i.getTag("name"):
- i.setAttr("name", i.getTagData("name"))
- if i.getTag("description"):
- i.setAttr("name", i.getTagData("description"))
- identities.append(i.attrs)
- if i.getTag("groupchat"):
- features.append(NS_GROUPCHAT)
- if i.getTag("register"):
- features.append(NS_REGISTER)
- if i.getTag("search"):
- features.append(NS_SEARCH)
- return identities, features
-
-def getRegInfo(disp, host, info={}, sync=True):
- """
- Gets registration form from remote host.
- You can pre-fill the info dictionary.
- F.e. if you are requesting info on registering user joey than specify
- info as {"username": "joey"}. See JEP-0077 for details.
- "disp" must be connected dispatcher instance.
- """
- iq = Iq("get", NS_REGISTER, to=host)
- for i in info.keys():
- iq.setTagData(i, info[i])
- if sync:
- resp = disp.SendAndWaitForResponse(iq)
- _ReceivedRegInfo(disp.Dispatcher, resp, host)
- return resp
- else:
- disp.SendAndCallForResponse(iq, _ReceivedRegInfo, {"agent": host})
-
-def _ReceivedRegInfo(con, resp, agent):
- iq = Iq("get", NS_REGISTER, to=agent)
- if not isResultNode(resp):
- return None
- df = resp.getTag("query", namespace=NS_REGISTER).getTag("x", namespace=NS_DATA)
- if df:
- con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, DataForm(node=df)))
- return None
- df = DataForm(typ="form")
- for i in resp.getQueryPayload():
- if not isinstance(i, Iq):
- pass
- elif i.getName() == "instructions":
- df.addInstructions(i.getData())
- else:
- df.setField(i.getName()).setValue(i.getData())
- con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, df))
-
-def register(disp, host, info):
- """
- Perform registration on remote server with provided info.
- disp must be connected dispatcher instance.
- Returns true or false depending on registration result.
- If registration fails you can get additional info from the dispatcher's owner
- attributes lastErrNode, lastErr and lastErrCode.
- """
- iq = Iq("set", NS_REGISTER, to=host)
- if not isinstance(info, dict):
- info = info.asDict()
- for i in info.keys():
- iq.setTag("query").setTagData(i, info[i])
- resp = disp.SendAndWaitForResponse(iq)
- if isResultNode(resp):
- return 1
-
-def unregister(disp, host):
- """
- Unregisters with host (permanently removes account).
- disp must be connected and authorized dispatcher instance.
- Returns true on success.
- """
- resp = disp.SendAndWaitForResponse(Iq("set", NS_REGISTER, to=host, payload=[Node("remove")]))
- if isResultNode(resp):
- return 1
-
-def changePasswordTo(disp, newpassword, host=None):
- """
- Changes password on specified or current (if not specified) server.
- disp must be connected and authorized dispatcher instance.
- Returns true on success."""
- if not host:
- host = disp._owner.Server
- resp = disp.SendAndWaitForResponse(Iq("set", NS_REGISTER, to=host,
- payload=[
- Node("username", payload=[disp._owner.User]),
- Node("password", payload=[newpassword])
- ]))
- if isResultNode(resp):
- return 1
-
-def getPrivacyLists(disp):
- """
- Requests privacy lists from connected server.
- Returns dictionary of existing lists on success.
- """
- dict = {"lists": []}
- try:
- resp = disp.SendAndWaitForResponse(Iq("get", NS_PRIVACY))
- if not isResultNode(resp):
- return None
- for list in resp.getQueryPayload():
- if list.getName() == "list":
- dict["lists"].append(list.getAttr("name"))
- else:
- dict[list.getName()] = list.getAttr("name")
- except Exception:
- pass
- else:
- return dict
-
-def getPrivacyList(disp, listname):
- """
- Requests specific privacy list listname. Returns list of XML nodes (rules)
- taken from the server responce.
- """
- try:
- resp = disp.SendAndWaitForResponse(Iq("get", NS_PRIVACY, payload=[Node("list", {"name": listname})]))
- if isResultNode(resp):
- return resp.getQueryPayload()[0]
- except Exception:
- pass
-
-def setActivePrivacyList(disp, listname=None, typ="active"):
- """
- Switches privacy list "listname" to specified type.
- By default the type is "active". Returns true on success.
- """
- if listname:
- attrs = {"name": listname}
- else:
- attrs = {}
- resp = disp.SendAndWaitForResponse(Iq("set", NS_PRIVACY, payload=[Node(typ, attrs)]))
- if isResultNode(resp):
- return 1
-
-def setDefaultPrivacyList(disp, listname=None):
- """
- Sets the default privacy list as "listname". Returns true on success.
- """
- return setActivePrivacyList(disp, listname, "default")
-
-def setPrivacyList(disp, list):
- """
- Set the ruleset. "list" should be the simpleXML node formatted
- according to RFC 3921 (XMPP-IM) (I.e. Node("list", {"name": listname}, payload=[...]) )
- Returns true on success.
- """
- resp = disp.SendAndWaitForResponse(Iq("set", NS_PRIVACY, payload=[list]))
- if isResultNode(resp):
- return 1
-
-def delPrivacyList(disp, listname):
- """
- Deletes privacy list "listname". Returns true on success.
- """
- resp = disp.SendAndWaitForResponse(Iq("set", NS_PRIVACY, payload=[Node("list", {"name": listname})]))
- if isResultNode(resp):
- return 1
+## features.py
+##
+## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2, or (at your option)
+## any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+# $Id: features.py, v1.26 2013/10/21 alkorgun Exp $
+
+"""
+This module contains variable stuff that is not worth splitting into separate modules.
+Here is:
+ DISCO client and agents-to-DISCO and browse-to-DISCO emulators.
+ IBR and password manager.
+ jabber:iq:privacy methods
+All these methods takes "disp" first argument that should be already connected
+(and in most cases already authorised) dispatcher instance.
+"""
+
+from .protocol import *
+
+REGISTER_DATA_RECEIVED = "REGISTER DATA RECEIVED"
+
+def _discover(disp, ns, jid, node=None, fb2b=0, fb2a=1):
+ """
+ Try to obtain info from the remote object.
+ If remote object doesn't support disco fall back to browse (if fb2b is true)
+ and if it doesnt support browse (or fb2b is not true) fall back to agents protocol
+ (if gb2a is true). Returns obtained info. Used internally.
+ """
+ iq = Iq(to=jid, typ="get", queryNS=ns)
+ if node:
+ iq.setQuerynode(node)
+ rep = disp.SendAndWaitForResponse(iq)
+ if fb2b and not isResultNode(rep):
+ rep = disp.SendAndWaitForResponse(Iq(to=jid, typ="get", queryNS=NS_BROWSE)) # Fallback to browse
+ if fb2a and not isResultNode(rep):
+ rep = disp.SendAndWaitForResponse(Iq(to=jid, typ="get", queryNS=NS_AGENTS)) # Fallback to agents
+ if isResultNode(rep):
+ return [n for n in rep.getQueryPayload() if isinstance(n, Node)]
+ return []
+
+def discoverItems(disp, jid, node=None):
+ """
+ Query remote object about any items that it contains. Return items list.
+ """
+ ret = []
+ for i in _discover(disp, NS_DISCO_ITEMS, jid, node):
+ if i.getName() == "agent" and i.getTag("name"):
+ i.setAttr("name", i.getTagData("name"))
+ ret.append(i.attrs)
+ return ret
+
+def discoverInfo(disp, jid, node=None):
+ """
+ Query remote object about info that it publishes. Returns identities and features lists.
+ """
+ identities, features = [], []
+ for i in _discover(disp, NS_DISCO_INFO, jid, node):
+ if i.getName() == "identity":
+ identities.append(i.attrs)
+ elif i.getName() == "feature":
+ features.append(i.getAttr("var"))
+ elif i.getName() == "agent":
+ if i.getTag("name"):
+ i.setAttr("name", i.getTagData("name"))
+ if i.getTag("description"):
+ i.setAttr("name", i.getTagData("description"))
+ identities.append(i.attrs)
+ if i.getTag("groupchat"):
+ features.append(NS_GROUPCHAT)
+ if i.getTag("register"):
+ features.append(NS_REGISTER)
+ if i.getTag("search"):
+ features.append(NS_SEARCH)
+ return identities, features
+
+def getRegInfo(disp, host, info={}, sync=True):
+ """
+ Gets registration form from remote host.
+ You can pre-fill the info dictionary.
+ F.e. if you are requesting info on registering user joey than specify
+ info as {"username": "joey"}. See JEP-0077 for details.
+ "disp" must be connected dispatcher instance.
+ """
+ iq = Iq("get", NS_REGISTER, to=host)
+ for i in info.keys():
+ iq.setTagData(i, info[i])
+ if sync:
+ resp = disp.SendAndWaitForResponse(iq)
+ _ReceivedRegInfo(disp.Dispatcher, resp, host)
+ return resp
+ else:
+ disp.SendAndCallForResponse(iq, _ReceivedRegInfo, {"agent": host})
+
+def _ReceivedRegInfo(con, resp, agent):
+ iq = Iq("get", NS_REGISTER, to=agent)
+ if not isResultNode(resp):
+ return None
+ df = resp.getTag("query", namespace=NS_REGISTER).getTag("x", namespace=NS_DATA)
+ if df:
+ con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, DataForm(node=df)))
+ return None
+ df = DataForm(typ="form")
+ for i in resp.getQueryPayload():
+ if not isinstance(i, Iq):
+ pass
+ elif i.getName() == "instructions":
+ df.addInstructions(i.getData())
+ else:
+ df.setField(i.getName()).setValue(i.getData())
+ con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent, df))
+
+def register(disp, host, info):
+ """
+ Perform registration on remote server with provided info.
+ disp must be connected dispatcher instance.
+ Returns true or false depending on registration result.
+ If registration fails you can get additional info from the dispatcher's owner
+ attributes lastErrNode, lastErr and lastErrCode.
+ """
+ iq = Iq("set", NS_REGISTER, to=host)
+ if not isinstance(info, dict):
+ info = info.asDict()
+ for i in info.keys():
+ iq.setTag("query").setTagData(i, info[i])
+ resp = disp.SendAndWaitForResponse(iq)
+ if isResultNode(resp):
+ return 1
+
+def unregister(disp, host):
+ """
+ Unregisters with host (permanently removes account).
+ disp must be connected and authorized dispatcher instance.
+ Returns true on success.
+ """
+ resp = disp.SendAndWaitForResponse(Iq("set", NS_REGISTER, to=host, payload=[Node("remove")]))
+ if isResultNode(resp):
+ return 1
+
+def changePasswordTo(disp, newpassword, host=None):
+ """
+ Changes password on specified or current (if not specified) server.
+ disp must be connected and authorized dispatcher instance.
+ Returns true on success."""
+ if not host:
+ host = disp._owner.Server
+ resp = disp.SendAndWaitForResponse(Iq("set", NS_REGISTER, to=host,
+ payload=[
+ Node("username", payload=[disp._owner.User]),
+ Node("password", payload=[newpassword])
+ ]))
+ if isResultNode(resp):
+ return 1
+
+def getPrivacyLists(disp):
+ """
+ Requests privacy lists from connected server.
+ Returns dictionary of existing lists on success.
+ """
+ dict = {"lists": []}
+ try:
+ resp = disp.SendAndWaitForResponse(Iq("get", NS_PRIVACY))
+ if not isResultNode(resp):
+ return None
+ for list in resp.getQueryPayload():
+ if list.getName() == "list":
+ dict["lists"].append(list.getAttr("name"))
+ else:
+ dict[list.getName()] = list.getAttr("name")
+ except Exception:
+ pass
+ else:
+ return dict
+
+def getPrivacyList(disp, listname):
+ """
+ Requests specific privacy list listname. Returns list of XML nodes (rules)
+ taken from the server responce.
+ """
+ try:
+ resp = disp.SendAndWaitForResponse(Iq("get", NS_PRIVACY, payload=[Node("list", {"name": listname})]))
+ if isResultNode(resp):
+ return resp.getQueryPayload()[0]
+ except Exception:
+ pass
+
+def setActivePrivacyList(disp, listname=None, typ="active"):
+ """
+ Switches privacy list "listname" to specified type.
+ By default the type is "active". Returns true on success.
+ """
+ if listname:
+ attrs = {"name": listname}
+ else:
+ attrs = {}
+ resp = disp.SendAndWaitForResponse(Iq("set", NS_PRIVACY, payload=[Node(typ, attrs)]))
+ if isResultNode(resp):
+ return 1
+
+def setDefaultPrivacyList(disp, listname=None):
+ """
+ Sets the default privacy list as "listname". Returns true on success.
+ """
+ return setActivePrivacyList(disp, listname, "default")
+
+def setPrivacyList(disp, list):
+ """
+ Set the ruleset. "list" should be the simpleXML node formatted
+ according to RFC 3921 (XMPP-IM) (I.e. Node("list", {"name": listname}, payload=[...]) )
+ Returns true on success.
+ """
+ resp = disp.SendAndWaitForResponse(Iq("set", NS_PRIVACY, payload=[list]))
+ if isResultNode(resp):
+ return 1
+
+def delPrivacyList(disp, listname):
+ """
+ Deletes privacy list "listname". Returns true on success.
+ """
+ resp = disp.SendAndWaitForResponse(Iq("set", NS_PRIVACY, payload=[Node("list", {"name": listname})]))
+ if isResultNode(resp):
+ return 1
diff --git a/xmpp/filetransfer.py b/xmpp/filetransfer.py
index 2c7621f..1e277ec 100644
--- a/xmpp/filetransfer.py
+++ b/xmpp/filetransfer.py
@@ -1,226 +1,226 @@
-## filetransfer.py
-##
-## Copyright (C) 2004 Alexey "Snake" Nezhdanov
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2, or (at your option)
-## any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-
-# $Id: filetransfer.py, v1.7 2013/10/21 alkorgun Exp $
-
-"""
-This module contains IBB class that is the simple implementation of JEP-0047.
-Note that this is just a transport for data. You have to negotiate data transfer before
-(via StreamInitiation most probably). Unfortunately SI is not implemented yet.
-"""
-
-from base64 import encodestring, decodestring
-from .dispatcher import PlugIn
-from .protocol import *
-
-class IBB(PlugIn):
- """
- IBB used to transfer small-sized data chunk over estabilished xmpp connection.
- Data is split into small blocks (by default 3000 bytes each), encoded as base 64
- and sent to another entity that compiles these blocks back into the data chunk.
- This is very inefficiend but should work under any circumstances. Note that
- using IBB normally should be the last resort.
- """
- def __init__(self):
- """
- Initialise internal variables.
- """
- PlugIn.__init__(self)
- self.DBG_LINE = "ibb"
- self._exported_methods = [self.OpenStream]
- self._streams = {}
- self._ampnode = Node(NS_AMP + " amp",
- payload=[
- Node("rule", {"condition": "deliver-at", "value": "stored", "action": "error"}),
- Node("rule", {"condition": "match-resource", "value": "exact", "action": "error"})
- ])
-
- def plugin(self, owner):
- """
- Register handlers for receiving incoming datastreams. Used internally.
- """
- self._owner.RegisterHandlerOnce("iq", self.StreamOpenReplyHandler)
- self._owner.RegisterHandler("iq", self.IqHandler, ns=NS_IBB)
- self._owner.RegisterHandler("message", self.ReceiveHandler, ns=NS_IBB)
-
- def IqHandler(self, conn, stanza):
- """
- Handles streams state change. Used internally.
- """
- typ = stanza.getType()
- self.DEBUG("IqHandler called typ->%s" % typ, "info")
- if typ == "set" and stanza.getTag("open", namespace=NS_IBB):
- self.StreamOpenHandler(conn, stanza)
- elif typ == "set" and stanza.getTag("close", namespace=NS_IBB):
- self.StreamCloseHandler(conn, stanza)
- elif typ == "result":
- self.StreamCommitHandler(conn, stanza)
- elif typ == "error":
- self.StreamOpenReplyHandler(conn, stanza)
- else:
- conn.send(Error(stanza, ERR_BAD_REQUEST))
- raise NodeProcessed()
-
- def StreamOpenHandler(self, conn, stanza):
- """
- Handles opening of new incoming stream. Used internally.
- """
- err = None
- sid = stanza.getTagAttr("open", "sid")
- blocksize = stanza.getTagAttr("open", "block-size")
- self.DEBUG("StreamOpenHandler called sid->%s blocksize->%s" % (sid, blocksize), "info")
- try:
- blocksize = int(blocksize)
- except Exception:
- err = ERR_BAD_REQUEST
- if not sid or not blocksize:
- err = ERR_BAD_REQUEST
- elif sid in self._streams.keys():
- err = ERR_UNEXPECTED_REQUEST
- if err:
- rep = Error(stanza, err)
- else:
- self.DEBUG("Opening stream: id %s, block-size %s" % (sid, blocksize), "info")
- rep = Protocol("iq", stanza.getFrom(), "result", stanza.getTo(), {"id": stanza.getID()})
- self._streams[sid] = {
- "direction": "<" + str(stanza.getFrom()),
- "block-size": blocksize,
- "fp": open("/tmp/xmpp_file_" + sid, "w"),
- "seq": 0,
- "syn_id": stanza.getID()
- }
- conn.send(rep)
-
- def OpenStream(self, sid, to, fp, blocksize=3000):
- """
- Start new stream. You should provide stream id "sid", the endpoind jid "to",
- the file object containing info for send "fp". Also the desired blocksize can be specified.
- Take into account that recommended stanza size is 4k and IBB uses base64 encoding
- that increases size of data by 1/3.
- """
- if sid in self._streams.keys():
- return None
- if not JID(to).getResource():
- return None
- self._streams[sid] = {"direction": "|>" + to, "block-size": blocksize, "fp": fp, "seq": 0}
- self._owner.RegisterCycleHandler(self.SendHandler)
- syn = Protocol("iq", to, "set", payload=[Node(NS_IBB + " open", {"sid": sid, "block-size": blocksize})])
- self._owner.send(syn)
- self._streams[sid]["syn_id"] = syn.getID()
- return self._streams[sid]
-
- def SendHandler(self, conn):
- """
- Send next portion of data if it is time to do it. Used internally.
- """
- self.DEBUG("SendHandler called", "info")
- for sid in self._streams.keys():
- stream = self._streams[sid]
- if stream["direction"][:2] == "|>":
- cont = 1
- elif stream["direction"][0] == ">":
- chunk = stream["fp"].read(stream["block-size"])
- if chunk:
- datanode = Node(NS_IBB + " data", {"sid": sid, "seq": stream["seq"]}, encodestring(chunk))
- stream["seq"] += 1
- if stream["seq"] == 65536:
- stream["seq"] = 0
- conn.send(Protocol("message", stream["direction"][1:], payload=[datanode, self._ampnode]))
- else:
- conn.send(Protocol("iq", stream["direction"][1:], "set", payload=[Node(NS_IBB + " close", {"sid": sid})]))
- conn.Event(self.DBG_LINE, "SUCCESSFULL SEND", stream)
- del self._streams[sid]
- self._owner.UnregisterCycleHandler(self.SendHandler)
-
- def ReceiveHandler(self, conn, stanza):
- """
- Receive next portion of incoming datastream and store it write
- it to temporary file. Used internally.
- """
- sid, seq, data = stanza.getTagAttr("data", "sid"), stanza.getTagAttr("data", "seq"), stanza.getTagData("data")
- self.DEBUG("ReceiveHandler called sid->%s seq->%s" % (sid, seq), "info")
- try:
- seq = int(seq)
- data = decodestring(data)
- except Exception:
- seq = data = ""
- err = None
- if not sid in self._streams.keys():
- err = ERR_ITEM_NOT_FOUND
- else:
- stream = self._streams[sid]
- if not data:
- err = ERR_BAD_REQUEST
- elif seq != stream["seq"]:
- err = ERR_UNEXPECTED_REQUEST
- else:
- self.DEBUG("Successfull receive sid->%s %s+%s bytes" % (sid, stream["fp"].tell(), len(data)), "ok")
- stream["seq"] += 1
- stream["fp"].write(data)
- if err:
- self.DEBUG("Error on receive: %s" % err, "error")
- conn.send(Error(Iq(to=stanza.getFrom(), frm=stanza.getTo(), payload=[Node(NS_IBB + " close")]), err, reply=0))
-
- def StreamCloseHandler(self, conn, stanza):
- """
- Handle stream closure due to all data transmitted.
- Raise xmpppy event specifying successfull data receive.
- """
- sid = stanza.getTagAttr("close", "sid")
- self.DEBUG("StreamCloseHandler called sid->%s" % sid, "info")
- if sid in self._streams.keys():
- conn.send(stanza.buildReply("result"))
- conn.Event(self.DBG_LINE, "SUCCESSFULL RECEIVE", self._streams[sid])
- del self._streams[sid]
- else:
- conn.send(Error(stanza, ERR_ITEM_NOT_FOUND))
-
- def StreamBrokenHandler(self, conn, stanza):
- """
- Handle stream closure due to all some error while receiving data.
- Raise xmpppy event specifying unsuccessfull data receive.
- """
- syn_id = stanza.getID()
- self.DEBUG("StreamBrokenHandler called syn_id->%s" % syn_id, "info")
- for sid in self._streams.keys():
- stream = self._streams[sid]
- if stream["syn_id"] == syn_id:
- if stream["direction"][0] == "<":
- conn.Event(self.DBG_LINE, "ERROR ON RECEIVE", stream)
- else:
- conn.Event(self.DBG_LINE, "ERROR ON SEND", stream)
- del self._streams[sid]
-
- def StreamOpenReplyHandler(self, conn, stanza):
- """
- Handle remote side reply about is it agree or not to receive our datastream.
- Used internally. Raises xmpppy event specfiying if the data transfer is agreed upon.
- """
- syn_id = stanza.getID()
- self.DEBUG("StreamOpenReplyHandler called syn_id->%s" % syn_id, "info")
- for sid in self._streams.keys():
- stream = self._streams[sid]
- if stream["syn_id"] == syn_id:
- if stanza.getType() == "error":
- if stream["direction"][0] == "<":
- conn.Event(self.DBG_LINE, "ERROR ON RECEIVE", stream)
- else:
- conn.Event(self.DBG_LINE, "ERROR ON SEND", stream)
- del self._streams[sid]
- elif stanza.getType() == "result":
- if stream["direction"][0] == "|":
- stream["direction"] = stream["direction"][1:]
- conn.Event(self.DBG_LINE, "STREAM COMMITTED", stream)
- else:
- conn.send(Error(stanza, ERR_UNEXPECTED_REQUEST))
+## filetransfer.py
+##
+## Copyright (C) 2004 Alexey "Snake" Nezhdanov
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2, or (at your option)
+## any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+# $Id: filetransfer.py, v1.7 2013/10/21 alkorgun Exp $
+
+"""
+This module contains IBB class that is the simple implementation of JEP-0047.
+Note that this is just a transport for data. You have to negotiate data transfer before
+(via StreamInitiation most probably). Unfortunately SI is not implemented yet.
+"""
+
+from base64 import encodestring, decodestring
+from .dispatcher import PlugIn
+from .protocol import *
+
+class IBB(PlugIn):
+ """
+ IBB used to transfer small-sized data chunk over estabilished xmpp connection.
+ Data is split into small blocks (by default 3000 bytes each), encoded as base 64
+ and sent to another entity that compiles these blocks back into the data chunk.
+ This is very inefficiend but should work under any circumstances. Note that
+ using IBB normally should be the last resort.
+ """
+ def __init__(self):
+ """
+ Initialise internal variables.
+ """
+ PlugIn.__init__(self)
+ self.DBG_LINE = "ibb"
+ self._exported_methods = [self.OpenStream]
+ self._streams = {}
+ self._ampnode = Node(NS_AMP + " amp",
+ payload=[
+ Node("rule", {"condition": "deliver-at", "value": "stored", "action": "error"}),
+ Node("rule", {"condition": "match-resource", "value": "exact", "action": "error"})
+ ])
+
+ def plugin(self, owner):
+ """
+ Register handlers for receiving incoming datastreams. Used internally.
+ """
+ self._owner.RegisterHandlerOnce("iq", self.StreamOpenReplyHandler)
+ self._owner.RegisterHandler("iq", self.IqHandler, ns=NS_IBB)
+ self._owner.RegisterHandler("message", self.ReceiveHandler, ns=NS_IBB)
+
+ def IqHandler(self, conn, stanza):
+ """
+ Handles streams state change. Used internally.
+ """
+ typ = stanza.getType()
+ self.DEBUG("IqHandler called typ->%s" % typ, "info")
+ if typ == "set" and stanza.getTag("open", namespace=NS_IBB):
+ self.StreamOpenHandler(conn, stanza)
+ elif typ == "set" and stanza.getTag("close", namespace=NS_IBB):
+ self.StreamCloseHandler(conn, stanza)
+ elif typ == "result":
+ self.StreamCommitHandler(conn, stanza)
+ elif typ == "error":
+ self.StreamOpenReplyHandler(conn, stanza)
+ else:
+ conn.send(Error(stanza, ERR_BAD_REQUEST))
+ raise NodeProcessed()
+
+ def StreamOpenHandler(self, conn, stanza):
+ """
+ Handles opening of new incoming stream. Used internally.
+ """
+ err = None
+ sid = stanza.getTagAttr("open", "sid")
+ blocksize = stanza.getTagAttr("open", "block-size")
+ self.DEBUG("StreamOpenHandler called sid->%s blocksize->%s" % (sid, blocksize), "info")
+ try:
+ blocksize = int(blocksize)
+ except Exception:
+ err = ERR_BAD_REQUEST
+ if not sid or not blocksize:
+ err = ERR_BAD_REQUEST
+ elif sid in self._streams.keys():
+ err = ERR_UNEXPECTED_REQUEST
+ if err:
+ rep = Error(stanza, err)
+ else:
+ self.DEBUG("Opening stream: id %s, block-size %s" % (sid, blocksize), "info")
+ rep = Protocol("iq", stanza.getFrom(), "result", stanza.getTo(), {"id": stanza.getID()})
+ self._streams[sid] = {
+ "direction": "<" + str(stanza.getFrom()),
+ "block-size": blocksize,
+ "fp": open("/tmp/xmpp_file_" + sid, "w"),
+ "seq": 0,
+ "syn_id": stanza.getID()
+ }
+ conn.send(rep)
+
+ def OpenStream(self, sid, to, fp, blocksize=3000):
+ """
+ Start new stream. You should provide stream id "sid", the endpoind jid "to",
+ the file object containing info for send "fp". Also the desired blocksize can be specified.
+ Take into account that recommended stanza size is 4k and IBB uses base64 encoding
+ that increases size of data by 1/3.
+ """
+ if sid in self._streams.keys():
+ return None
+ if not JID(to).getResource():
+ return None
+ self._streams[sid] = {"direction": "|>" + to, "block-size": blocksize, "fp": fp, "seq": 0}
+ self._owner.RegisterCycleHandler(self.SendHandler)
+ syn = Protocol("iq", to, "set", payload=[Node(NS_IBB + " open", {"sid": sid, "block-size": blocksize})])
+ self._owner.send(syn)
+ self._streams[sid]["syn_id"] = syn.getID()
+ return self._streams[sid]
+
+ def SendHandler(self, conn):
+ """
+ Send next portion of data if it is time to do it. Used internally.
+ """
+ self.DEBUG("SendHandler called", "info")
+ for sid in self._streams.keys():
+ stream = self._streams[sid]
+ if stream["direction"][:2] == "|>":
+ cont = 1
+ elif stream["direction"][0] == ">":
+ chunk = stream["fp"].read(stream["block-size"])
+ if chunk:
+ datanode = Node(NS_IBB + " data", {"sid": sid, "seq": stream["seq"]}, encodestring(chunk))
+ stream["seq"] += 1
+ if stream["seq"] == 65536:
+ stream["seq"] = 0
+ conn.send(Protocol("message", stream["direction"][1:], payload=[datanode, self._ampnode]))
+ else:
+ conn.send(Protocol("iq", stream["direction"][1:], "set", payload=[Node(NS_IBB + " close", {"sid": sid})]))
+ conn.Event(self.DBG_LINE, "SUCCESSFULL SEND", stream)
+ del self._streams[sid]
+ self._owner.UnregisterCycleHandler(self.SendHandler)
+
+ def ReceiveHandler(self, conn, stanza):
+ """
+ Receive next portion of incoming datastream and store it write
+ it to temporary file. Used internally.
+ """
+ sid, seq, data = stanza.getTagAttr("data", "sid"), stanza.getTagAttr("data", "seq"), stanza.getTagData("data")
+ self.DEBUG("ReceiveHandler called sid->%s seq->%s" % (sid, seq), "info")
+ try:
+ seq = int(seq)
+ data = decodestring(data)
+ except Exception:
+ seq = data = ""
+ err = None
+ if not sid in self._streams.keys():
+ err = ERR_ITEM_NOT_FOUND
+ else:
+ stream = self._streams[sid]
+ if not data:
+ err = ERR_BAD_REQUEST
+ elif seq != stream["seq"]:
+ err = ERR_UNEXPECTED_REQUEST
+ else:
+ self.DEBUG("Successfull receive sid->%s %s+%s bytes" % (sid, stream["fp"].tell(), len(data)), "ok")
+ stream["seq"] += 1
+ stream["fp"].write(data)
+ if err:
+ self.DEBUG("Error on receive: %s" % err, "error")
+ conn.send(Error(Iq(to=stanza.getFrom(), frm=stanza.getTo(), payload=[Node(NS_IBB + " close")]), err, reply=0))
+
+ def StreamCloseHandler(self, conn, stanza):
+ """
+ Handle stream closure due to all data transmitted.
+ Raise xmpppy event specifying successfull data receive.
+ """
+ sid = stanza.getTagAttr("close", "sid")
+ self.DEBUG("StreamCloseHandler called sid->%s" % sid, "info")
+ if sid in self._streams.keys():
+ conn.send(stanza.buildReply("result"))
+ conn.Event(self.DBG_LINE, "SUCCESSFULL RECEIVE", self._streams[sid])
+ del self._streams[sid]
+ else:
+ conn.send(Error(stanza, ERR_ITEM_NOT_FOUND))
+
+ def StreamBrokenHandler(self, conn, stanza):
+ """
+ Handle stream closure due to all some error while receiving data.
+ Raise xmpppy event specifying unsuccessfull data receive.
+ """
+ syn_id = stanza.getID()
+ self.DEBUG("StreamBrokenHandler called syn_id->%s" % syn_id, "info")
+ for sid in self._streams.keys():
+ stream = self._streams[sid]
+ if stream["syn_id"] == syn_id:
+ if stream["direction"][0] == "<":
+ conn.Event(self.DBG_LINE, "ERROR ON RECEIVE", stream)
+ else:
+ conn.Event(self.DBG_LINE, "ERROR ON SEND", stream)
+ del self._streams[sid]
+
+ def StreamOpenReplyHandler(self, conn, stanza):
+ """
+ Handle remote side reply about is it agree or not to receive our datastream.
+ Used internally. Raises xmpppy event specfiying if the data transfer is agreed upon.
+ """
+ syn_id = stanza.getID()
+ self.DEBUG("StreamOpenReplyHandler called syn_id->%s" % syn_id, "info")
+ for sid in self._streams.keys():
+ stream = self._streams[sid]
+ if stream["syn_id"] == syn_id:
+ if stanza.getType() == "error":
+ if stream["direction"][0] == "<":
+ conn.Event(self.DBG_LINE, "ERROR ON RECEIVE", stream)
+ else:
+ conn.Event(self.DBG_LINE, "ERROR ON SEND", stream)
+ del self._streams[sid]
+ elif stanza.getType() == "result":
+ if stream["direction"][0] == "|":
+ stream["direction"] = stream["direction"][1:]
+ conn.Event(self.DBG_LINE, "STREAM COMMITTED", stream)
+ else:
+ conn.send(Error(stanza, ERR_UNEXPECTED_REQUEST))
diff --git a/xmpp/plugin.py b/xmpp/plugin.py
index 5c25e0a..ebf59c6 100644
--- a/xmpp/plugin.py
+++ b/xmpp/plugin.py
@@ -1,70 +1,70 @@
-## plugin.py
-##
-## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2, or (at your option)
-## any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-
-# $Id: plugin.py, v1.0 2013/10/21 alkorgun Exp $
-
-"""
-Provides library with all Non-SASL and SASL authentication mechanisms.
-Can be used both for client and transport authentication.
-"""
-
-class PlugIn:
- """
- Common xmpppy plugins infrastructure: plugging in/out, debugging.
- """
- def __init__(self):
- self._exported_methods = []
- self.DBG_LINE = self.__class__.__name__.lower()
-
- def PlugIn(self, owner):
- """
- Attach to main instance and register ourself and all our staff in it.
- """
- self._owner = owner
- if self.DBG_LINE not in owner.debug_flags:
- owner.debug_flags.append(self.DBG_LINE)
- self.DEBUG("Plugging %s into %s" % (self, self._owner), "start")
- if hasattr(owner, self.__class__.__name__):
- return self.DEBUG("Plugging ignored: another instance already plugged.", "error")
- self._old_owners_methods = []
- for method in self._exported_methods:
- if hasattr(owner, method.__name__):
- self._old_owners_methods.append(getattr(owner, method.__name__))
- setattr(owner, method.__name__, method)
- setattr(owner, self.__class__.__name__, self)
- if hasattr(self, "plugin"):
- return self.plugin(owner)
-
- def PlugOut(self):
- """
- Unregister all our staff from main instance and detach from it.
- """
- self.DEBUG("Plugging %s out of %s." % (self, self._owner), "stop")
- if hasattr(self, "plugout"):
- rn = self.plugout()
- else:
- rn = None
- self._owner.debug_flags.remove(self.DBG_LINE)
- for method in self._exported_methods:
- delattr(self._owner, method.__name__)
- for method in self._old_owners_methods:
- setattr(self._owner, method.__name__, method)
- delattr(self._owner, self.__class__.__name__)
- return rn
-
- def DEBUG(self, text, severity="info"):
- """
- Feed a provided debug line to main instance's debug facility along with our ID string.
- """
- self._owner.DEBUG(self.DBG_LINE, text, severity)
+## plugin.py
+##
+## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2, or (at your option)
+## any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+# $Id: plugin.py, v1.0 2013/10/21 alkorgun Exp $
+
+"""
+Provides library with all Non-SASL and SASL authentication mechanisms.
+Can be used both for client and transport authentication.
+"""
+
+class PlugIn:
+ """
+ Common xmpppy plugins infrastructure: plugging in/out, debugging.
+ """
+ def __init__(self):
+ self._exported_methods = []
+ self.DBG_LINE = self.__class__.__name__.lower()
+
+ def PlugIn(self, owner):
+ """
+ Attach to main instance and register ourself and all our staff in it.
+ """
+ self._owner = owner
+ if self.DBG_LINE not in owner.debug_flags:
+ owner.debug_flags.append(self.DBG_LINE)
+ self.DEBUG("Plugging %s into %s" % (self, self._owner), "start")
+ if hasattr(owner, self.__class__.__name__):
+ return self.DEBUG("Plugging ignored: another instance already plugged.", "error")
+ self._old_owners_methods = []
+ for method in self._exported_methods:
+ if hasattr(owner, method.__name__):
+ self._old_owners_methods.append(getattr(owner, method.__name__))
+ setattr(owner, method.__name__, method)
+ setattr(owner, self.__class__.__name__, self)
+ if hasattr(self, "plugin"):
+ return self.plugin(owner)
+
+ def PlugOut(self):
+ """
+ Unregister all our staff from main instance and detach from it.
+ """
+ self.DEBUG("Plugging %s out of %s." % (self, self._owner), "stop")
+ if hasattr(self, "plugout"):
+ rn = self.plugout()
+ else:
+ rn = None
+ self._owner.debug_flags.remove(self.DBG_LINE)
+ for method in self._exported_methods:
+ delattr(self._owner, method.__name__)
+ for method in self._old_owners_methods:
+ setattr(self._owner, method.__name__, method)
+ delattr(self._owner, self.__class__.__name__)
+ return rn
+
+ def DEBUG(self, text, severity="info"):
+ """
+ Feed a provided debug line to main instance's debug facility along with our ID string.
+ """
+ self._owner.DEBUG(self.DBG_LINE, text, severity)
diff --git a/xmpp/protocol.py b/xmpp/protocol.py
index a9dd68d..f09f8a7 100644
--- a/xmpp/protocol.py
+++ b/xmpp/protocol.py
@@ -1,1431 +1,1431 @@
-## protocol.py
-##
-## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2, or (at your option)
-## any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-
-# $Id: protocol.py, v1.64 2014/01/10 alkorgun Exp $
-
-"""
-Protocol module contains tools that is needed for processing of
-xmpp-related data structures.
-"""
-
-import time
-
-from .simplexml import Node, XML_ls, XMLescape, ustr
-
-NS_ACTIVITY = "http://jabber.org/protocol/activity" # XEP-0108
-NS_ADDRESS = "http://jabber.org/protocol/address" # XEP-0033
-NS_ADMIN = "http://jabber.org/protocol/admin" # XEP-0133
-NS_ADMIN_ADD_USER = NS_ADMIN + "#add-user" # XEP-0133
-NS_ADMIN_DELETE_USER = NS_ADMIN + "#delete-user" # XEP-0133
-NS_ADMIN_DISABLE_USER = NS_ADMIN + "#disable-user" # XEP-0133
-NS_ADMIN_REENABLE_USER = NS_ADMIN + "#reenable-user" # XEP-0133
-NS_ADMIN_END_USER_SESSION = NS_ADMIN + "#end-user-session" # XEP-0133
-NS_ADMIN_GET_USER_PASSWORD = NS_ADMIN + "#get-user-password" # XEP-0133
-NS_ADMIN_CHANGE_USER_PASSWORD = NS_ADMIN + "#change-user-password" # XEP-0133
-NS_ADMIN_GET_USER_ROSTER = NS_ADMIN + "#get-user-roster" # XEP-0133
-NS_ADMIN_GET_USER_LASTLOGIN = NS_ADMIN + "#get-user-lastlogin" # XEP-0133
-NS_ADMIN_USER_STATS = NS_ADMIN + "#user-stats" # XEP-0133
-NS_ADMIN_EDIT_BLACKLIST = NS_ADMIN + "#edit-blacklist" # XEP-0133
-NS_ADMIN_EDIT_WHITELIST = NS_ADMIN + "#edit-whitelist" # XEP-0133
-NS_ADMIN_REGISTERED_USERS_NUM = NS_ADMIN + "#get-registered-users-num" # XEP-0133
-NS_ADMIN_DISABLED_USERS_NUM = NS_ADMIN + "#get-disabled-users-num" # XEP-0133
-NS_ADMIN_ONLINE_USERS_NUM = NS_ADMIN + "#get-online-users-num" # XEP-0133
-NS_ADMIN_ACTIVE_USERS_NUM = NS_ADMIN + "#get-active-users-num" # XEP-0133
-NS_ADMIN_IDLE_USERS_NUM = NS_ADMIN + "#get-idle-users-num" # XEP-0133
-NS_ADMIN_REGISTERED_USERS_LIST = NS_ADMIN + "#get-registered-users-list" # XEP-0133
-NS_ADMIN_DISABLED_USERS_LIST = NS_ADMIN + "#get-disabled-users-list" # XEP-0133
-NS_ADMIN_ONLINE_USERS_LIST = NS_ADMIN + "#get-online-users-list" # XEP-0133
-NS_ADMIN_ACTIVE_USERS_LIST = NS_ADMIN + "#get-active-users-list" # XEP-0133
-NS_ADMIN_IDLE_USERS_LIST = NS_ADMIN + "#get-idle-users-list" # XEP-0133
-NS_ADMIN_ANNOUNCE = NS_ADMIN + "#announce" # XEP-0133
-NS_ADMIN_SET_MOTD = NS_ADMIN + "#set-motd" # XEP-0133
-NS_ADMIN_EDIT_MOTD = NS_ADMIN + "#edit-motd" # XEP-0133
-NS_ADMIN_DELETE_MOTD = NS_ADMIN + "#delete-motd" # XEP-0133
-NS_ADMIN_SET_WELCOME = NS_ADMIN + "#set-welcome" # XEP-0133
-NS_ADMIN_DELETE_WELCOME = NS_ADMIN + "#delete-welcome" # XEP-0133
-NS_ADMIN_EDIT_ADMIN = NS_ADMIN + "#edit-admin" # XEP-0133
-NS_ADMIN_RESTART = NS_ADMIN + "#restart" # XEP-0133
-NS_ADMIN_SHUTDOWN = NS_ADMIN + "#shutdown" # XEP-0133
-NS_AGENTS = "jabber:iq:agents" # XEP-0094 (historical)
-NS_AMP = "http://jabber.org/protocol/amp" # XEP-0079
-NS_AMP_ERRORS = NS_AMP + "#errors" # XEP-0079
-NS_AUTH = "jabber:iq:auth" # XEP-0078
-NS_AVATAR = "jabber:iq:avatar" # XEP-0008 (historical)
-NS_BIND = "urn:ietf:params:xml:ns:xmpp-bind" # RFC 3920
-NS_BROWSE = "jabber:iq:browse" # XEP-0011 (historical)
-NS_BYTESTREAM = "http://jabber.org/protocol/bytestreams" # XEP-0065
-NS_CAPS = "http://jabber.org/protocol/caps" # XEP-0115
-NS_CAPTCHA = "urn:xmpp:captcha" # XEP-0158
-NS_CHATSTATES = "http://jabber.org/protocol/chatstates" # XEP-0085
-NS_CLIENT = "jabber:client" # RFC 3921
-NS_COMMANDS = "http://jabber.org/protocol/commands" # XEP-0050
-NS_COMPONENT_ACCEPT = "jabber:component:accept" # XEP-0114
-NS_COMPONENT_1 = "http://jabberd.jabberstudio.org/ns/component/1.0" # Jabberd2
-NS_COMPRESS = "http://jabber.org/protocol/compress" # XEP-0138
-NS_DATA = "jabber:x:data" # XEP-0004
-NS_DATA_LAYOUT = "http://jabber.org/protocol/xdata-layout" # XEP-0141
-NS_DATA_VALIDATE = "http://jabber.org/protocol/xdata-validate" # XEP-0122
-NS_DELAY = "jabber:x:delay" # XEP-0091 (deprecated in favour of XEP-0203)
-NS_URN_DELAY = "urn:xmpp:delay" # XEP-0203
-
-
-NS_DIALBACK = "jabber:server:dialback" # RFC 3921
-NS_DISCO = "http://jabber.org/protocol/disco" # XEP-0030
-NS_DISCO_INFO = NS_DISCO + "#info" # XEP-0030
-NS_DISCO_ITEMS = NS_DISCO + "#items" # XEP-0030
-NS_ENCRYPTED = "jabber:x:encrypted" # XEP-0027
-NS_EVENT = "jabber:x:event" # XEP-0022 (deprecated)
-NS_FEATURE = "http://jabber.org/protocol/feature-neg" # XEP-0020
-NS_FILE = "http://jabber.org/protocol/si/profile/file-transfer" # XEP-0096
-NS_GATEWAY = "jabber:iq:gateway" # XEP-0100
-NS_GEOLOC = "http://jabber.org/protocol/geoloc" # XEP-0080
-NS_GROUPCHAT = "gc-1.0" # XEP-0045
-NS_HTTP_BIND = "http://jabber.org/protocol/httpbind" # XEP-0124
-NS_IBB = "http://jabber.org/protocol/ibb" # XEP-0047
-NS_INVISIBLE = "presence-invisible" # Jabberd2
-NS_IQ = "iq" # Jabberd2
-NS_LAST = "jabber:iq:last" # XEP-0012
-NS_MEDIA = "urn:xmpp:media-element" # XEP-0158
-NS_MESSAGE = "message" # Jabberd2
-NS_MOOD = "http://jabber.org/protocol/mood" # XEP-0107
-NS_MUC = "http://jabber.org/protocol/muc" # XEP-0045
-NS_MUC_ADMIN = NS_MUC + "#admin" # XEP-0045
-NS_MUC_OWNER = NS_MUC + "#owner" # XEP-0045
-NS_MUC_UNIQUE = NS_MUC + "#unique" # XEP-0045
-NS_MUC_USER = NS_MUC + "#user" # XEP-0045
-NS_MUC_REGISTER = NS_MUC + "#register" # XEP-0045
-NS_MUC_REQUEST = NS_MUC + "#request" # XEP-0045
-NS_MUC_ROOMCONFIG = NS_MUC + "#roomconfig" # XEP-0045
-NS_MUC_ROOMINFO = NS_MUC + "#roominfo" # XEP-0045
-NS_MUC_ROOMS = NS_MUC + "#rooms" # XEP-0045
-NS_MUC_TRAFIC = NS_MUC + "#traffic" # XEP-0045
-NS_NICK = "http://jabber.org/protocol/nick" # XEP-0172
-NS_OFFLINE = "http://jabber.org/protocol/offline" # XEP-0013
-NS_OOB = "jabber:x:oob" # XEP-0066
-NS_PHYSLOC = "http://jabber.org/protocol/physloc" # XEP-0112
-NS_PRESENCE = "presence" # Jabberd2
-NS_PRIVACY = "jabber:iq:privacy" # RFC 3921
-NS_PRIVATE = "jabber:iq:private" # XEP-0049
-NS_PUBSUB = "http://jabber.org/protocol/pubsub" # XEP-0060
-NS_RC = "http://jabber.org/protocol/rc" # XEP-0146
-NS_REGISTER = "jabber:iq:register" # XEP-0077
-NS_RECEIPTS = "urn:xmpp:receipts" # XEP-0184
-NS_ROSTER = "jabber:iq:roster" # RFC 3921
-NS_ROSTERX = "http://jabber.org/protocol/rosterx" # XEP-0144
-NS_RPC = "jabber:iq:rpc" # XEP-0009
-NS_SASL = "urn:ietf:params:xml:ns:xmpp-sasl" # RFC 3920
-NS_SEARCH = "jabber:iq:search" # XEP-0055
-NS_SERVER = "jabber:server" # RFC 3921
-NS_SESSION = "urn:ietf:params:xml:ns:xmpp-session" # RFC 3921
-NS_SI = "http://jabber.org/protocol/si" # XEP-0096
-NS_SI_PUB = "http://jabber.org/protocol/sipub" # XEP-0137
-NS_SIGNED = "jabber:x:signed" # XEP-0027
-NS_SOFTWAREINFO = "urn:xmpp:dataforms:softwareinfo" # XEP-0155
-NS_STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas" # RFC 3920
-NS_STATS = "http://jabber.org/protocol/stats" # XEP-0039
-NS_STREAMS = "http://etherx.jabber.org/streams" # RFC 3920
-NS_TIME = "jabber:iq:time" # XEP-0090 (deprecated)
-NS_TLS = "urn:ietf:params:xml:ns:xmpp-tls" # RFC 3920
-NS_URN_ATTENTION = "urn:xmpp:attention:0" # XEP-0224
-NS_URN_OOB = "urn:xmpp:bob" # XEP-0158
-NS_URN_TIME = "urn:xmpp:time" # XEP-0202
-NS_VACATION = "http://jabber.org/protocol/vacation" # XEP-0109
-NS_VCARD = "vcard-temp" # XEP-0054
-NS_VCARD_UPDATE = "vcard-temp:x:update" # XEP-0153
-NS_VERSION = "jabber:iq:version" # XEP-0092
-NS_WAITINGLIST = "http://jabber.org/protocol/waitinglist" # XEP-0130
-NS_XHTML_IM = "http://jabber.org/protocol/xhtml-im" # XEP-0071
-NS_XMPP_STREAMS = "urn:ietf:params:xml:ns:xmpp-streams" # RFC 3920
-NS_PING = "urn:xmpp:ping" # XEP-0199
-
-NS_MUC_FILTER = "http://jabber.ru/muc-filter"
-
-STREAM_NOT_AUTHORIZED = NS_XMPP_STREAMS + " not-authorized"
-STREAM_REMOTE_CONNECTION_FAILED = NS_XMPP_STREAMS + " remote-connection-failed"
-SASL_MECHANISM_TOO_WEAK = NS_SASL + " mechanism-too-weak"
-STREAM_XML_NOT_WELL_FORMED = NS_XMPP_STREAMS + " xml-not-well-formed"
-ERR_JID_MALFORMED = NS_STANZAS + " jid-malformed"
-STREAM_SEE_OTHER_HOST = NS_XMPP_STREAMS + " see-other-host"
-STREAM_BAD_NAMESPACE_PREFIX = NS_XMPP_STREAMS + " bad-namespace-prefix"
-ERR_SERVICE_UNAVAILABLE = NS_STANZAS + " service-unavailable"
-STREAM_CONNECTION_TIMEOUT = NS_XMPP_STREAMS + " connection-timeout"
-STREAM_UNSUPPORTED_VERSION = NS_XMPP_STREAMS + " unsupported-version"
-STREAM_IMPROPER_ADDRESSING = NS_XMPP_STREAMS + " improper-addressing"
-STREAM_UNDEFINED_CONDITION = NS_XMPP_STREAMS + " undefined-condition"
-SASL_NOT_AUTHORIZED = NS_SASL + " not-authorized"
-ERR_GONE = NS_STANZAS + " gone"
-SASL_TEMPORARY_AUTH_FAILURE = NS_SASL + " temporary-auth-failure"
-ERR_REMOTE_SERVER_NOT_FOUND = NS_STANZAS + " remote-server-not-found"
-ERR_UNEXPECTED_REQUEST = NS_STANZAS + " unexpected-request"
-ERR_RECIPIENT_UNAVAILABLE = NS_STANZAS + " recipient-unavailable"
-ERR_CONFLICT = NS_STANZAS + " conflict"
-STREAM_SYSTEM_SHUTDOWN = NS_XMPP_STREAMS + " system-shutdown"
-STREAM_BAD_FORMAT = NS_XMPP_STREAMS + " bad-format"
-ERR_SUBSCRIPTION_REQUIRED = NS_STANZAS + " subscription-required"
-STREAM_INTERNAL_SERVER_ERROR = NS_XMPP_STREAMS + " internal-server-error"
-ERR_NOT_AUTHORIZED = NS_STANZAS + " not-authorized"
-SASL_ABORTED = NS_SASL + " aborted"
-ERR_REGISTRATION_REQUIRED = NS_STANZAS + " registration-required"
-ERR_INTERNAL_SERVER_ERROR = NS_STANZAS + " internal-server-error"
-SASL_INCORRECT_ENCODING = NS_SASL + " incorrect-encoding"
-STREAM_HOST_GONE = NS_XMPP_STREAMS + " host-gone"
-STREAM_POLICY_VIOLATION = NS_XMPP_STREAMS + " policy-violation"
-STREAM_INVALID_XML = NS_XMPP_STREAMS + " invalid-xml"
-STREAM_CONFLICT = NS_XMPP_STREAMS + " conflict"
-STREAM_RESOURCE_CONSTRAINT = NS_XMPP_STREAMS + " resource-constraint"
-STREAM_UNSUPPORTED_ENCODING = NS_XMPP_STREAMS + " unsupported-encoding"
-ERR_NOT_ALLOWED = NS_STANZAS + " not-allowed"
-ERR_ITEM_NOT_FOUND = NS_STANZAS + " item-not-found"
-ERR_NOT_ACCEPTABLE = NS_STANZAS + " not-acceptable"
-STREAM_INVALID_FROM = NS_XMPP_STREAMS + " invalid-from"
-ERR_FEATURE_NOT_IMPLEMENTED = NS_STANZAS + " feature-not-implemented"
-ERR_BAD_REQUEST = NS_STANZAS + " bad-request"
-STREAM_INVALID_ID = NS_XMPP_STREAMS + " invalid-id"
-STREAM_HOST_UNKNOWN = NS_XMPP_STREAMS + " host-unknown"
-ERR_UNDEFINED_CONDITION = NS_STANZAS + " undefined-condition"
-SASL_INVALID_MECHANISM = NS_SASL + " invalid-mechanism"
-STREAM_RESTRICTED_XML = NS_XMPP_STREAMS + " restricted-xml"
-ERR_RESOURCE_CONSTRAINT = NS_STANZAS + " resource-constraint"
-ERR_REMOTE_SERVER_TIMEOUT = NS_STANZAS + " remote-server-timeout"
-SASL_INVALID_AUTHZID = NS_SASL + " invalid-authzid"
-ERR_PAYMENT_REQUIRED = NS_STANZAS + " payment-required"
-STREAM_INVALID_NAMESPACE = NS_XMPP_STREAMS + " invalid-namespace"
-ERR_REDIRECT = NS_STANZAS + " redirect"
-STREAM_UNSUPPORTED_STANZA_TYPE = NS_XMPP_STREAMS + " unsupported-stanza-type"
-ERR_FORBIDDEN = NS_STANZAS + " forbidden"
-
-ERRORS = {
- "urn:ietf:params:xml:ns:xmpp-sasl not-authorized": ["", "", "The authentication failed because the initiating entity did not provide valid credentials (this includes but is not limited to the case of an unknown username); sent in reply to a <response/> element or an <auth/> element with initial response data."],
- "urn:ietf:params:xml:ns:xmpp-stanzas payment-required": ["402", "auth", "The requesting entity is not authorized to access the requested service because payment is required."],
- "urn:ietf:params:xml:ns:xmpp-sasl mechanism-too-weak": ["", "", "The mechanism requested by the initiating entity is weaker than server policy permits for that initiating entity; sent in reply to a <response/> element or an <auth/> element with initial response data."],
- "urn:ietf:params:xml:ns:xmpp-streams unsupported-encoding": ["", "", "The initiating entity has encoded the stream in an encoding that is not supported by the server."],
- "urn:ietf:params:xml:ns:xmpp-stanzas remote-server-timeout": ["504", "wait", "A remote server or service specified as part or all of the JID of the intended recipient could not be contacted within a reasonable amount of time."],
- "urn:ietf:params:xml:ns:xmpp-streams remote-connection-failed": ["", "", "The server is unable to properly connect to a remote resource that is required for authentication or authorization."],
- "urn:ietf:params:xml:ns:xmpp-streams restricted-xml": ["", "", "The entity has attempted to send restricted XML features such as a comment, processing instruction, DTD, entity reference, or unescaped character."],
- "urn:ietf:params:xml:ns:xmpp-streams see-other-host": ["", "", "The server will not provide service to the initiating entity but is redirecting traffic to another host."],
- "urn:ietf:params:xml:ns:xmpp-streams xml-not-well-formed": ["", "", "The initiating entity has sent XML that is not well-formed."],
- "urn:ietf:params:xml:ns:xmpp-stanzas subscription-required": ["407", "auth", "The requesting entity is not authorized to access the requested service because a subscription is required."],
- "urn:ietf:params:xml:ns:xmpp-streams internal-server-error": ["", "", "The server has experienced a misconfiguration or an otherwise-undefined internal error that prevents it from servicing the stream."],
- "urn:ietf:params:xml:ns:xmpp-sasl invalid-mechanism": ["", "", "The initiating entity did not provide a mechanism or requested a mechanism that is not supported by the receiving entity; sent in reply to an <auth/> element."],
- "urn:ietf:params:xml:ns:xmpp-streams policy-violation": ["", "", "The entity has violated some local service policy."],
- "urn:ietf:params:xml:ns:xmpp-stanzas conflict": ["409", "cancel", "Access cannot be granted because an existing resource or session exists with the same name or address."],
- "urn:ietf:params:xml:ns:xmpp-streams unsupported-stanza-type": ["", "", "The initiating entity has sent a first-level child of the stream that is not supported by the server."],
- "urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding": ["", "", "The data provided by the initiating entity could not be processed because the [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003. encoding is incorrect (e.g., because the encoding does not adhere to the definition in Section 3 of [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003.); sent in reply to a <response/> element or an <auth/> element with initial response data."],
- "urn:ietf:params:xml:ns:xmpp-stanzas registration-required": ["407", "auth", "The requesting entity is not authorized to access the requested service because registration is required."],
- "urn:ietf:params:xml:ns:xmpp-streams invalid-id": ["", "", "The stream ID or dialback ID is invalid or does not match an ID previously provided."],
- "urn:ietf:params:xml:ns:xmpp-sasl invalid-authzid": ["", "", "The authzid provided by the initiating entity is invalid, either because it is incorrectly formatted or because the initiating entity does not have permissions to authorize that ID; sent in reply to a <response/> element or an <auth/> element with initial response data."],
- "urn:ietf:params:xml:ns:xmpp-stanzas bad-request": ["400", "modify", "The sender has sent XML that is malformed or that cannot be processed."],
- "urn:ietf:params:xml:ns:xmpp-streams not-authorized": ["", "", "The entity has attempted to send data before the stream has been authenticated, or otherwise is not authorized to perform an action related to stream negotiation."],
- "urn:ietf:params:xml:ns:xmpp-stanzas forbidden": ["403", "auth", "The requesting entity does not possess the required permissions to perform the action."],
- "urn:ietf:params:xml:ns:xmpp-sasl temporary-auth-failure": ["", "", "The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an <auth/> element or <response/> element."],
- "urn:ietf:params:xml:ns:xmpp-streams invalid-namespace": ["", "", "The streams namespace name is something other than \http://etherx.jabber.org/streams\" or the dialback namespace name is something other than \"jabber:server:dialback\"."],
- "urn:ietf:params:xml:ns:xmpp-stanzas feature-not-implemented": ["501", "cancel", "The feature requested is not implemented by the recipient or server and therefore cannot be processed."],
- "urn:ietf:params:xml:ns:xmpp-streams invalid-xml": ["", "", "The entity has sent invalid XML over the stream to a server that performs validation."],
- "urn:ietf:params:xml:ns:xmpp-stanzas item-not-found": ["404", "cancel", "The addressed JID or item requested cannot be found."],
- "urn:ietf:params:xml:ns:xmpp-streams host-gone": ["", "", "The value of the \"to\" attribute provided by the initiating entity in the stream header corresponds to a hostname that is no longer hosted by the server."],
- "urn:ietf:params:xml:ns:xmpp-stanzas recipient-unavailable": ["404", "wait", "The intended recipient is temporarily unavailable."],
- "urn:ietf:params:xml:ns:xmpp-stanzas not-acceptable": ["406", "cancel", "The recipient or server understands the request but is refusing to process it because it does not meet criteria defined by the recipient or server."],
- "urn:ietf:params:xml:ns:xmpp-streams invalid-from": ["cancel", "", "The JID or hostname provided in a \"from\" address does not match an authorized JID or validated domain negotiated between servers via SASL or dialback, or between a client and a server via authentication and resource authorization."],
- "urn:ietf:params:xml:ns:xmpp-streams bad-format": ["", "", "The entity has sent XML that cannot be processed."],
- "urn:ietf:params:xml:ns:xmpp-streams resource-constraint": ["", "", "The server lacks the system resources necessary to service the stream."],
- "urn:ietf:params:xml:ns:xmpp-stanzas undefined-condition": ["500", "", "The condition is undefined."],
- "urn:ietf:params:xml:ns:xmpp-stanzas redirect": ["302", "modify", "The recipient or server is redirecting requests for this information to another entity."],
- "urn:ietf:params:xml:ns:xmpp-streams bad-namespace-prefix": ["", "", "The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix."],
- "urn:ietf:params:xml:ns:xmpp-streams system-shutdown": ["", "", "The server is being shut down and all active streams are being closed."],
- "urn:ietf:params:xml:ns:xmpp-streams conflict": ["", "", "The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream."],
- "urn:ietf:params:xml:ns:xmpp-streams connection-timeout": ["", "", "The entity has not generated any traffic over the stream for some period of time."],
- "urn:ietf:params:xml:ns:xmpp-stanzas jid-malformed": ["400", "modify", "The value of the \"to\" attribute in the sender's stanza does not adhere to the syntax defined in Addressing Scheme."],
- "urn:ietf:params:xml:ns:xmpp-stanzas resource-constraint": ["500", "wait", "The server or recipient lacks the system resources necessary to service the request."],
- "urn:ietf:params:xml:ns:xmpp-stanzas remote-server-not-found": ["404", "cancel", "A remote server or service specified as part or all of the JID of the intended recipient does not exist."],
- "urn:ietf:params:xml:ns:xmpp-streams unsupported-version": ["", "", "The value of the \"version\" attribute provided by the initiating entity in the stream header specifies a version of XMPP that is not supported by the server."],
- "urn:ietf:params:xml:ns:xmpp-streams host-unknown": ["", "", "The value of the \"to\" attribute provided by the initiating entity in the stream header does not correspond to a hostname that is hosted by the server."],
- "urn:ietf:params:xml:ns:xmpp-stanzas unexpected-request": ["400", "wait", "The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order)."],
- "urn:ietf:params:xml:ns:xmpp-streams improper-addressing": ["", "", "A stanza sent between two servers lacks a \"to\" or \"from\" attribute (or the attribute has no value)."],
- "urn:ietf:params:xml:ns:xmpp-stanzas not-allowed": ["405", "cancel", "The recipient or server does not allow any entity to perform the action."],
- "urn:ietf:params:xml:ns:xmpp-stanzas internal-server-error": ["500", "wait", "The server could not process the stanza because of a misconfiguration or an otherwise-undefined internal server error."],
- "urn:ietf:params:xml:ns:xmpp-stanzas gone": ["302", "modify", "The recipient or server can no longer be contacted at this address."],
- "urn:ietf:params:xml:ns:xmpp-streams undefined-condition": ["", "", "The error condition is not one of those defined by the other conditions in this list."],
- "urn:ietf:params:xml:ns:xmpp-stanzas service-unavailable": ["503", "cancel", "The server or recipient does not currently provide the requested service."],
- "urn:ietf:params:xml:ns:xmpp-stanzas not-authorized": ["401", "auth", "The sender must provide proper credentials before being allowed to perform the action, or has provided improper credentials."],
- "urn:ietf:params:xml:ns:xmpp-sasl aborted": ["", "", "The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element."]
-}
-
-_errorcodes = {
- "302": "redirect",
- "400": "unexpected-request",
- "401": "not-authorized",
- "402": "payment-required",
- "403": "forbidden",
- "404": "remote-server-not-found",
- "405": "not-allowed",
- "406": "not-acceptable",
- "407": "subscription-required",
- "409": "conflict",
- "500": "undefined-condition",
- "501": "feature-not-implemented",
- "503": "service-unavailable",
- "504": "remote-server-timeout"
-}
-
-def isResultNode(node):
- """
- Returns true if the node is a positive reply.
- """
- return (node and node.getType() == "result")
-
-def isGetNode(node):
- """
- Returns true if the node is a positive reply.
- """
- return (node and node.getType() == "get")
-
-def isSetNode(node):
- """
- Returns true if the node is a positive reply.
- """
- return (node and node.getType() == "set")
-
-def isErrorNode(node):
- """
- Returns true if the node is a negative reply.
- """
- return (node and node.getType() == "error")
-
-class NodeProcessed(Exception):
- """
- Exception that should be raised by handler when the handling should be stopped.
- """
-
-class StreamError(Exception):
- """
- Base exception class for stream errors.
- """
-
-class BadFormat(StreamError): pass
-
-class BadNamespacePrefix(StreamError): pass
-
-class Conflict(StreamError): pass
-
-class ConnectionTimeout(StreamError): pass
-
-class HostGone(StreamError): pass
-
-class HostUnknown(StreamError): pass
-
-class ImproperAddressing(StreamError): pass
-
-class InternalServerError(StreamError): pass
-
-class InvalidFrom(StreamError): pass
-
-class InvalidID(StreamError): pass
-
-class InvalidNamespace(StreamError): pass
-
-class InvalidXML(StreamError): pass
-
-class NotAuthorized(StreamError): pass
-
-class PolicyViolation(StreamError): pass
-
-class RemoteConnectionFailed(StreamError): pass
-
-class ResourceConstraint(StreamError): pass
-
-class RestrictedXML(StreamError): pass
-
-class SeeOtherHost(StreamError): pass
-
-class SystemShutdown(StreamError): pass
-
-class UndefinedCondition(StreamError): pass
-
-class UnsupportedEncoding(StreamError): pass
-
-class UnsupportedStanzaType(StreamError): pass
-
-class UnsupportedVersion(StreamError): pass
-
-class XMLNotWellFormed(StreamError): pass
-
-stream_exceptions = {
- "bad-format": BadFormat,
- "bad-namespace-prefix": BadNamespacePrefix,
- "conflict": Conflict,
- "connection-timeout": ConnectionTimeout,
- "host-gone": HostGone,
- "host-unknown": HostUnknown,
- "improper-addressing": ImproperAddressing,
- "internal-server-error": InternalServerError,
- "invalid-from": InvalidFrom,
- "invalid-id": InvalidID,
- "invalid-namespace": InvalidNamespace,
- "invalid-xml": InvalidXML,
- "not-authorized": NotAuthorized,
- "policy-violation": PolicyViolation,
- "remote-connection-failed": RemoteConnectionFailed,
- "resource-constraint": ResourceConstraint,
- "restricted-xml": RestrictedXML,
- "see-other-host": SeeOtherHost,
- "system-shutdown": SystemShutdown,
- "undefined-condition": UndefinedCondition,
- "unsupported-encoding": UnsupportedEncoding,
- "unsupported-stanza-type": UnsupportedStanzaType,
- "unsupported-version": UnsupportedVersion,
- "xml-not-well-formed": XMLNotWellFormed
-}
-
-class JID:
- """
- JID object. JID can be built from string, modified, compared, serialized into string.
- """
- def __init__(self, jid=None, node="", domain="", resource=""):
- """
- Constructor. JID can be specified as string (jid argument) or as separate parts.
- Examples:
- JID("node@domain/resource")
- JID(node="node", domain="domain.org")
- """
- if not jid and not domain:
- raise ValueError("JID must contain at least domain name")
- elif isinstance(jid, self.__class__):
- self.node, self.domain, self.resource = jid.node, jid.domain, jid.resource
- elif domain:
- self.node, self.domain, self.resource = node, domain, resource
- else:
- if jid.find("@") + 1:
- self.node, jid = jid.split("@", 1)
- else:
- self.node = ""
- if jid.find("/") + 1:
- self.domain, self.resource = jid.split("/", 1)
- else:
- self.domain, self.resource = jid, ""
-
- def getNode(self):
- """
- Return the node part of the JID.
- """
- return self.node
-
- def setNode(self, node):
- """
- Set the node part of the JID to new value. Specify None to remove the node part.
- """
- self.node = node.lower()
-
- def getDomain(self):
- """
- Return the domain part of the JID.
- """
- return self.domain
-
- def setDomain(self, domain):
- """
- Set the domain part of the JID to new value.
- """
- self.domain = domain.lower()
-
- def getResource(self):
- """
- Return the resource part of the JID.
- """
- return self.resource
-
- def setResource(self, resource):
- """
- Set the resource part of the JID to new value. Specify None to remove the resource part.
- """
- self.resource = resource
-
- def getStripped(self):
- """
- Return the bare representation of JID. I.e. string value w/o resource.
- """
- return self.__str__(0)
-
- def __eq__(self, other):
- """
- Compare the JID to another instance or to string for equality.
- """
- try:
- other = JID(other)
- except ValueError:
- return False
- return self.resource == other.resource and self.__str__(0) == other.__str__(0)
-
- def __ne__(self, other):
- """
- Compare the JID to another instance or to string for non-equality.
- """
- return not self.__eq__(other)
-
- def bareMatch(self, other):
- """
- Compare the node and domain parts of the JID's for equality.
- """
- return self.__str__(0) == JID(other).__str__(0)
-
- def __str__(self, wresource=1):
- """
- Serialize JID into string.
- """
- jid = "@".join((self.node, self.domain)) if self.node else self.domain
- if wresource and self.resource:
- jid = "/".join((jid, self.resource))
- return jid
-
- def __hash__(self):
- """
- Produce hash of the JID, Allows to use JID objects as keys of the dictionary.
- """
- return hash(self.__str__())
-
-class Protocol(Node):
- """
- A "stanza" object class. Contains methods that are common for presences, iqs and messages.
- """
- def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, payload=[], timestamp=None, xmlns=None, node=None):
- """
- Constructor, name is the name of the stanza i.e. "message" or "presence" or "iq".
- to is the value of "to" attribure, "typ" - "type" attribute
- frn - from attribure, attrs - other attributes mapping, payload - same meaning as for simplexml payload definition
- timestamp - the time value that needs to be stamped over stanza
- xmlns - namespace of top stanza node
- node - parsed or unparsed stana to be taken as prototype.
- """
- if not attrs:
- attrs = {}
- if to:
- attrs["to"] = to
- if frm:
- attrs["from"] = frm
- if typ:
- attrs["type"] = typ
- Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node)
- if not node and xmlns:
- self.setNamespace(xmlns)
- if self["to"]:
- self.setTo(self["to"])
- if self["from"]:
- self.setFrom(self["from"])
- if node and isinstance(node, self.__class__) and self.__class__ == node.__class__ and "id" in self.attrs:
- del self.attrs["id"]
- self.timestamp = None
-
- delay = self.getTag("delay", namespace=NS_URN_DELAY) or self.getTag("x", namespace=NS_DELAY)
- if delay:
- if not self.getTimestamp() or delay.getAttr("stamp") < self.getTimestamp():
- self.setTimestamp(delay.getAttr("stamp"))
-
- if timestamp is not None:
- self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp=""
-
-
- def getTo(self):
- """
- Return value of the "to" attribute.
- """
- try:
- to = self["to"]
- except Exception:
- to = None
- return to
-
- def getFrom(self):
- """
- Return value of the "from" attribute.
- """
- try:
- frm = self["from"]
- except Exception:
- frm = None
- return frm
-
- def getTimestamp(self):
- """
- Return the timestamp in the "yyyymmddThhmmss" format.
- """
- return self.timestamp
-
- def getID(self):
- """
- Return the value of the "id" attribute.
- """
- return self.getAttr("id")
-
- def setTo(self, val):
- """
- Set the value of the "to" attribute.
- """
- self.setAttr("to", JID(val))
-
- def getType(self):
- """
- Return the value of the "type" attribute.
- """
- return self.getAttr("type")
-
- def setFrom(self, val):
- """
- Set the value of the "from" attribute.
- """
- self.setAttr("from", JID(val))
-
- def setType(self, val):
- """
- Set the value of the "type" attribute.
- """
- self.setAttr("type", val)
-
- def setID(self, val):
- """
- Set the value of the "id" attribute.
- """
- self.setAttr("id", val)
-
- def getError(self):
- """
- Return the error-condition (if present) or the textual description of the error (otherwise).
- """
- errtag = self.getTag("error")
- if errtag:
- for tag in errtag.getChildren():
- if tag.getName() != "text":
- return tag.getName()
- return errtag.getData()
-
- def getErrorCode(self):
- """
- Return the error code. Obsolette.
- """
- return self.getTagAttr("error", "code")
-
- def setError(self, error, code=None):
- """
- Set the error code. Obsolette. Use error-conditions instead.
- """
- if code:
- if str(code) in _errorcodes.keys():
- error = ErrorNode(_errorcodes[str(code)], text=error)
- else:
- error = ErrorNode(ERR_UNDEFINED_CONDITION, code=code, typ="cancel", text=error)
- elif isinstance(error, basestring):
- error = ErrorNode(error)
- self.setType("error")
- self.addChild(node=error)
-
- def setTimestamp(self, val=None):
- """
- Set the timestamp. timestamp should be the yyyymmddThhmmss string.
- """
- if not val:
- val = time.strftime("%Y%m%dT%H:%M:%S", time.gmtime())
- self.timestamp = val
- self.setTag("x", {"stamp": self.timestamp}, namespace=NS_DELAY)
-
- def getProperties(self):
- """
- Return the list of namespaces to which belongs the direct childs of element.
- """
- props = []
- for child in self.getChildren():
- prop = child.getNamespace()
- if prop not in props:
- props.append(prop)
- return props
-
- def __setitem__(self, item, val):
- """
- Set the item "item" to the value "val".
- """
- if item in ["to", "from"]:
- val = JID(val)
- return self.setAttr(item, val)
-
-class Message(Protocol):
- """
- XMPP Message stanza - "push" mechanism.
- """
- def __init__(self, to=None, body=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None):
- """
- Create message object. You can specify recipient, text of message, type of message
- any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
- Alternatively you can pass in the other XML object as the "node" parameted to replicate it as message.
- """
- Protocol.__init__(self, "message", to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
- if body:
- self.setBody(body)
- if subject:
- self.setSubject(subject)
-
- def getBody(self):
- """
- Returns text of the message.
- """
- return self.getTagData("body")
-
- def getSubject(self):
- """
- Returns subject of the message.
- """
- return self.getTagData("subject")
-
- def getThread(self):
- """
- Returns thread of the message.
- """
- return self.getTagData("thread")
-
- def setBody(self, val):
- """
- Sets the text of the message.
- """
- self.setTagData("body", val)
-
- def setSubject(self, val):
- """
- Sets the subject of the message.
- """
- self.setTagData("subject", val)
-
- def setThread(self, val):
- """
- Sets the thread of the message.
- """
- self.setTagData("thread", val)
-
- def buildReply(self, text=None):
- """
- Builds and returns another message object with specified text.
- The to, from type and thread properties of new message are pre-set as reply to this message.
- """
- msg = Message(to=self.getFrom(), frm=self.getTo(), body=text)
- thr = self.getThread()
- if thr:
- msg.setThread(thr)
- return msg
-
-class Presence(Protocol):
- """
- XMPP Presence object.
- """
- def __init__(self, to=None, typ=None, priority=None, show=None, status=None, attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, node=None):
- """
- Create presence object. You can specify recipient, type of message, priority, show and status values
- any additional attributes, sender of the presence, timestamp, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
- Alternatively you can pass in the other XML object as the "node" parameted to replicate it as presence.
- """
- Protocol.__init__(self, "presence", to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
- if priority:
- self.setPriority(priority)
- if show:
- self.setShow(show)
- if status:
- self.setStatus(status)
-
- def getPriority(self):
- """
- Returns the priority of the message.
- """
- return self.getTagData("priority")
-
- def getShow(self):
- """
- Returns the show value of the message.
- """
- return self.getTagData("show")
-
- def getStatus(self):
- """
- Returns the status string of the message.
- """
- return self.getTagData("status")
-
- def setPriority(self, val):
- """
- Sets the priority of the message.
- """
- self.setTagData("priority", val)
-
- def setShow(self, val):
- """
- Sets the show value of the message.
- """
- self.setTagData("show", val)
-
- def setStatus(self, val):
- """
- Sets the status string of the message.
- """
- self.setTagData("status", val)
-
- def _muc_getItemAttr(self, tag, attr):
- for xtag in self.getTags("x", namespace=NS_MUC_USER):
- for child in xtag.getTags(tag):
- return child.getAttr(attr)
-
- def _muc_getSubTagDataAttr(self, tag, attr):
- for xtag in self.getTags("x", namespace=NS_MUC_USER):
- for child in xtag.getTags("item"):
- for cchild in child.getTags(tag):
- return cchild.getData(), cchild.getAttr(attr)
- return None, None
-
- def getRole(self):
- """
- Returns the presence role (for groupchat).
- """
- return self._muc_getItemAttr("item", "role")
-
- def getAffiliation(self):
- """Returns the presence affiliation (for groupchat).
- """
- return self._muc_getItemAttr("item", "affiliation")
-
- def getNick(self):
- """
- Returns the nick value (for nick change in groupchat).
- """
- return self._muc_getItemAttr("item", "nick")
-
- def getJid(self):
- """
- Returns the presence jid (for groupchat).
- """
- return self._muc_getItemAttr("item", "jid")
-
- def getReason(self):
- """
- Returns the reason of the presence (for groupchat).
- """
- return self._muc_getSubTagDataAttr("reason", "")[0]
-
- def getActor(self):
- """
- Returns the reason of the presence (for groupchat).
- """
- return self._muc_getSubTagDataAttr("actor", "jid")[1]
-
- def getStatusCode(self):
- """
- Returns the status code of the presence (for groupchat).
- """
- return self._muc_getItemAttr("status", "code")
-
-class Iq(Protocol):
- """
- XMPP Iq object - get/set dialog mechanism.
- """
- def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, payload=[], xmlns=NS_CLIENT, node=None):
- """
- Create Iq object. You can specify type, query namespace
- any additional attributes, recipient of the iq, sender of the iq, any additional payload (f.e. jabber:x:data node) and namespace in one go.
- Alternatively you can pass in the other XML object as the "node" parameted to replicate it as an iq.
- """
- Protocol.__init__(self, "iq", to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node)
- if payload:
- self.setQueryPayload(payload)
- if queryNS:
- self.setQueryNS(queryNS)
-
- def getQuery(self, namespace=None):
- """
- Returns the query node.
- """
- return self.getTag("query", namespace=namespace)
-
- def getQueryNS(self):
- """
- Returns the namespace of the "query" child element.
- """
- tag = self.getTag("query")
- if tag:
- return tag.getNamespace()
-
- def getQuerynode(self):
- """
- Returns the "node" attribute value of the "query" child element.
- """
- return self.getTagAttr("query", "node")
-
- def getQueryPayload(self):
- """
- Returns the "query" child element payload.
- """
- tag = self.getTag("query")
- if tag:
- return tag.getPayload()
-
- def getQueryChildren(self):
- """
- Returns the "query" child element child nodes.
- """
- tag = self.getTag("query")
- if tag:
- return tag.getChildren()
-
- def setQuery(self, name=None):
- """
- Changes the name of the query node, creates it if needed.
- Keep the existing name if none is given (use "query" if it's a creation).
- Returns the query node.
- """
- query = self.getQuery()
- if query is None:
- query = self.addChild("query")
- if name is not None:
- query.setName(name)
- return query
-
- def setQueryNS(self, namespace):
- """
- Set the namespace of the "query" child element.
- """
- self.setTag("query").setNamespace(namespace)
-
- def setQueryPayload(self, payload):
- """
- Set the "query" child element payload.
- """
- self.setTag("query").setPayload(payload)
-
- def setQuerynode(self, node):
- """
- Set the "node" attribute value of the "query" child element.
- """
- self.setTagAttr("query", "node", node)
-
- def buildReply(self, typ):
- """
- Builds and returns another Iq object of specified type.
- The to, from and query child node of new Iq are pre-set as reply to this Iq.
- """
- iq = Iq(typ, to=self.getFrom(), frm=self.getTo(), attrs={"id": self.getID()})
- if self.getTag("query"):
- iq.setQueryNS(self.getQueryNS())
- if self.getTagAttr("query", "node"):
- iq.setQuerynode(self.getQuerynode())
- return iq
-
-class ErrorNode(Node):
- """
- XMPP-style error element.
- In the case of stanza error should be attached to XMPP stanza.
- In the case of stream-level errors should be used separately.
- """
- def __init__(self, name, code=None, typ=None, text=None):
- """
- Create new error node object.
- Mandatory parameter: name - name of error condition.
- Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol.
- """
- if name in ERRORS:
- cod, type, txt = ERRORS[name]
- ns = name.split()[0]
- else:
- cod, ns, type, txt = "500", NS_STANZAS, "cancel", ""
- if typ:
- type = typ
- if code:
- cod = code
- if text:
- txt = text
- Node.__init__(self, "error", {}, [Node(name)])
- if type:
- self.setAttr("type", type)
- if not cod:
- self.setName("stream:error")
- if txt:
- self.addChild(node=Node(ns + " text", {}, [txt]))
- if cod:
- self.setAttr("code", cod)
-
-class Error(Protocol):
- """
- Used to quickly transform received stanza into error reply.
- """
- def __init__(self, node, error, reply=1):
- """
- Create error reply basing on the received "node" stanza and the "error" error condition.
- If the "node" is not the received stanza but locally created ("to" and "from" fields needs not swapping)
- specify the "reply" argument as false.
- """
- if reply:
- Protocol.__init__(self, to=node.getFrom(), frm=node.getTo(), node=node)
- else:
- Protocol.__init__(self, node=node)
- self.setError(error)
- if node.getType() == "error":
- self.__str__ = self.__dupstr__
-
- def __dupstr__(self, dup1=None, dup2=None):
- """
- Dummy function used as preventor of creating error node in reply to error node.
- I.e. you will not be able to serialize "double" error into string.
- """
- return ""
-
-class DataField(Node):
- """
- This class is used in the DataForm class to describe the single data item.
- If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122)
- then you will need to work with instances of this class.
- """
- def __init__(self, name=None, value=None, typ=None, required=0, label=None, desc=None, options=[], node=None):
- """
- Create new data field of specified name,value and type. Also "required", "desc" and "options" fields can be set.
- Alternatively other XML object can be passed in as the "node" parameted to replicate it as a new datafiled.
- """
- Node.__init__(self, "field", node=node)
- if name:
- self.setVar(name)
- if isinstance(value, (list, tuple)):
- self.setValues(value)
- elif value:
- self.setValue(value)
- if typ:
- self.setType(typ)
-# elif not typ and not node:
-# self.setType("text-single")
- if required:
- self.setRequired(required)
- if label:
- self.setLabel(label)
- if desc:
- self.setDesc(desc)
- if options:
- self.setOptions(options)
-
- def setRequired(self, req=1):
- """
- Change the state of the "required" flag.
- """
- if req:
- self.setTag("required")
- else:
- try:
- self.delChild("required")
- except ValueError:
- return None
-
- def isRequired(self):
- """
- Returns in this field a required one.
- """
- return self.getTag("required")
-
- def setLabel(self, label):
- """
- Set the label of this field.
- """
- self.setAttr("label", label)
-
- def getLabel(self):
- """
- Return the label of this field.
- """
- return self.getAttr("label")
-
- def setDesc(self, desc):
- """
- Set the description of this field.
- """
- self.setTagData("desc", desc)
-
- def getDesc(self):
- """
- Return the description of this field.
- """
- return self.getTagData("desc")
-
- def setValue(self, val):
- """
- Set the value of this field.
- """
- self.setTagData("value", val)
-
- def getValue(self):
- return self.getTagData("value")
-
- def setValues(self, ls):
- """
- Set the values of this field as values-list.
- Replaces all previous filed values! If you need to just add a value - use addValue method.
- """
- while self.getTag("value"):
- self.delChild("value")
- for val in ls:
- self.addValue(val)
-
- def addValue(self, val):
- """
- Add one more value to this field. Used in "get" iq's or such.
- """
- self.addChild("value", {}, [val])
-
- def getValues(self):
- """
- Return the list of values associated with this field.
- """
- ret = []
- for tag in self.getTags("value"):
- ret.append(tag.getData())
- return ret
-
- def getOptions(self):
- """
- Return label-option pairs list associated with this field.
- """
- ret = []
- for tag in self.getTags("option"):
- ret.append([tag.getAttr("label"), tag.getTagData("value")])
- return ret
-
- def setOptions(self, ls):
- """
- Set label-option pairs list associated with this field.
- """
- while self.getTag("option"):
- self.delChild("option")
- for opt in ls:
- self.addOption(opt)
-
- def addOption(self, opt):
- """
- Add one more label-option pair to this field.
- """
- if isinstance(opt, basestring):
- self.addChild("option").setTagData("value", opt)
- else:
- self.addChild("option", {"label": opt[0]}).setTagData("value", opt[1])
-
- def getType(self):
- """
- Get type of this field.
- """
- return self.getAttr("type")
-
- def setType(self, val):
- """
- Set type of this field.
- """
- return self.setAttr("type", val)
-
- def getVar(self):
- """
- Get "var" attribute value of this field.
- """
- return self.getAttr("var")
-
- def setVar(self, val):
- """
- Set "var" attribute value of this field.
- """
- return self.setAttr("var", val)
-
-class DataReported(Node):
- """
- This class is used in the DataForm class to describe the "reported data field" data items which are used in
- "multiple item form results" (as described in XEP-0004).
- Represents the fields that will be returned from a search. This information is useful when
- you try to use the jabber:iq:search namespace to return dynamic form information.
- """
- def __init__(self, node=None):
- """
- Create new empty "reported data" field. However, note that, according XEP-0004:
- * It MUST contain one or more DataFields.
- * Contained DataFields SHOULD possess a "type" and "label" attribute in addition to "var" attribute
- * Contained DataFields SHOULD NOT contain a <value/> element.
- Alternatively other XML object can be passed in as the "node" parameted to replicate it as a new
- dataitem.
- """
- Node.__init__(self, "reported", node=node)
- if node:
- newkids = []
- for n in self.getChildren():
- if n.getName() == "field":
- newkids.append(DataField(node=n))
- else:
- newkids.append(n)
- self.kids = newkids
-
- def getField(self, name):
- """
- Return the datafield object with name "name" (if exists).
- """
- return self.getTag("field", attrs={"var": name})
-
- def setField(self, name, typ=None, label=None, desc=None, options=[]):
- """
- Create if nessessary or get the existing datafield object with name "name" and return it.
- If created, attributes "type" and "label" are applied to new datafield.
- """
- field = self.getField(name)
- if not field:
- field = self.addChild(node=DataField(name, None, typ, 0, label, desc=desc, options=options))
- return field
-
- def asDict(self):
- """
- Represent dataitem as simple dictionary mapping of datafield names to their values.
- """
- ret = {}
- for field in self.getTags("field"):
- name = field.getAttr("var")
- typ = field.getType()
- if isinstance(typ, basestring) and typ.endswith("-multi"):
- val = []
- for i in field.getTags("value"):
- val.append(i.getData())
- else:
- val = field.getTagData("value")
- ret[name] = val
- if self.getTag("instructions"):
- ret["instructions"] = self.getInstructions()
- return ret
-
- def __getitem__(self, name):
- """
- Simple dictionary interface for getting datafields values by their names.
- """
- item = self.getField(name)
- if item:
- return item.getValue()
- raise IndexError("No such field")
-
- def __setitem__(self, name, val):
- """
- Simple dictionary interface for setting datafields values by their names.
- """
- return self.setField(name).setValue(val)
-
-class DataItem(Node):
- """
- This class is used in the DataForm class to describe data items which are used in "multiple
- item form results" (as described in XEP-0004).
- """
- def __init__(self, node=None):
- """
- Create new empty data item. However, note that, according XEP-0004, DataItem MUST contain ALL
- DataFields described in DataReported.
- Alternatively other XML object can be passed in as the "node" parameted to replicate it as a new
- dataitem.
- """
- Node.__init__(self, "item", node=node)
- if node:
- newkids = []
- for n in self.getChildren():
- if n.getName() == "field":
- newkids.append(DataField(node=n))
- else:
- newkids.append(n)
- self.kids = newkids
-
- def getField(self, name):
- """
- Return the datafield object with name "name" (if exists).
- """
- return self.getTag("field", attrs={"var": name})
-
- def setField(self, name, value=None, typ=None, desc=None, options=[]):
- """
- Create if nessessary or get the existing datafield object with name "name" and return it.
- """
- field = self.getField(name)
- if not field:
- field = self.addChild(node=DataField(name, value, typ, desc=desc, options=options))
- return field
-
- def asDict(self):
- """
- Represent dataitem as simple dictionary mapping of datafield names to their values.
- """
- ret = {}
- for field in self.getTags("field"):
- name = field.getAttr("var")
- typ = field.getType()
- if isinstance(typ, basestring) and typ.endswith("-multi"):
- val = []
- for i in field.getTags("value"):
- val.append(i.getData())
- else:
- val = field.getTagData("value")
- ret[name] = val
- if self.getTag("instructions"):
- ret["instructions"] = self.getInstructions()
- return ret
-
- def __getitem__(self, name):
- """
- Simple dictionary interface for getting datafields values by their names.
- """
- item = self.getField(name)
- if item:
- return item.getValue()
- raise IndexError("No such field")
-
- def __setitem__(self, name, val):
- """
- Simple dictionary interface for setting datafields values by their names.
- """
- return self.setField(name).setValue(val)
-
-class DataForm(Node):
- """
- DataForm class. Used for manipulating dataforms in XMPP.
- Relevant XEPs: 0004, 0068, 0122.
- Can be used in disco, pub-sub and many other applications.
- """
- def __init__(self, typ=None, data=[], title=None, node=None):
- """
- Create new dataform of type "typ"; "data" is the list of DataReported,
- DataItem and DataField instances that this dataform contains; "title"
- is the title string.
- You can specify the "node" argument as the other node to be used as
- base for constructing this dataform.
-
- Title and instructions is optional and SHOULD NOT contain newlines.
- Several instructions MAY be present.
- "typ" can be one of ("form" | "submit" | "cancel" | "result" )
- "typ" of reply iq can be ( "result" | "set" | "set" | "result" ) respectively.
- "cancel" form can not contain any fields. All other forms contains AT LEAST one field.
- "title" MAY be included in forms of type "form" and "result".
- """
- Node.__init__(self, "x", node=node)
- if node:
- newkids = []
- for n in self.getChildren():
- if n.getName() == "field":
- newkids.append(DataField(node=n))
- elif n.getName() == "item":
- newkids.append(DataItem(node=n))
- elif n.getName() == "reported":
- newkids.append(DataReported(node=n))
- else:
- newkids.append(n)
- self.kids = newkids
- if typ:
- self.setType(typ)
- self.setNamespace(NS_DATA)
- if title:
- self.setTitle(title)
- if isinstance(data, dict):
- newdata = []
- for name in data.keys():
- newdata.append(DataField(name, data[name]))
- data = newdata
- for child in data:
- if isinstance(child, basestring):
- self.addInstructions(child)
- elif isinstance(child, DataField):
- self.kids.append(child)
- elif isinstance(child, DataItem):
- self.kids.append(child)
- elif isinstance(child, DataReported):
- self.kids.append(child)
- else:
- self.kids.append(DataField(node=child))
-
- def getType(self):
- """
- Return the type of dataform.
- """
- return self.getAttr("type")
-
- def setType(self, typ):
- """
- Set the type of dataform.
- """
- self.setAttr("type", typ)
-
- def getTitle(self):
- """
- Return the title of dataform.
- """
- return self.getTagData("title")
-
- def setTitle(self, text):
- """
- Set the title of dataform.
- """
- self.setTagData("title", text)
-
- def getInstructions(self):
- """
- Return the instructions of dataform.
- """
- return self.getTagData("instructions")
-
- def setInstructions(self, text):
- """
- Set the instructions of dataform.
- """
- self.setTagData("instructions", text)
-
- def addInstructions(self, text):
- """
- Add one more instruction to the dataform.
- """
- self.addChild("instructions", {}, [text])
-
- def getField(self, name):
- """
- Return the datafield object with name "name" (if exists).
- """
- return self.getTag("field", attrs={"var": name})
-
- def setField(self, name, value=None, typ=None, desc=None, options=[]):
- """
- Create if nessessary or get the existing datafield object with name "name" and return it.
- """
- field = self.getField(name)
- if not field:
- field = self.addChild(node=DataField(name, value, typ, desc=desc, options=options))
- return field
-
- def asDict(self):
- """
- Represent dataform as simple dictionary mapping of datafield names to their values.
- """
- ret = {}
- for field in self.getTags("field"):
- name = field.getAttr("var")
- typ = field.getType()
- if isinstance(typ, basestring) and typ.endswith("-multi"):
- val = []
- for i in field.getTags("value"):
- val.append(i.getData())
- else:
- val = field.getTagData("value")
- ret[name] = val
- if self.getTag("instructions"):
- ret["instructions"] = self.getInstructions()
- return ret
-
- def __getitem__(self, name):
- """
- Simple dictionary interface for getting datafields values by their names.
- """
- item = self.getField(name)
- if item:
- return item.getValue()
- raise IndexError("No such field")
-
- def __setitem__(self, name, val):
- """
- Simple dictionary interface for setting datafields values by their names.
- """
- return self.setField(name).setValue(val)
+## protocol.py
+##
+## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2, or (at your option)
+## any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+# $Id: protocol.py, v1.64 2014/01/10 alkorgun Exp $
+
+"""
+Protocol module contains tools that is needed for processing of
+xmpp-related data structures.
+"""
+
+import time
+
+from .simplexml import Node, XML_ls, XMLescape, ustr
+
+NS_ACTIVITY = "http://jabber.org/protocol/activity" # XEP-0108
+NS_ADDRESS = "http://jabber.org/protocol/address" # XEP-0033
+NS_ADMIN = "http://jabber.org/protocol/admin" # XEP-0133
+NS_ADMIN_ADD_USER = NS_ADMIN + "#add-user" # XEP-0133
+NS_ADMIN_DELETE_USER = NS_ADMIN + "#delete-user" # XEP-0133
+NS_ADMIN_DISABLE_USER = NS_ADMIN + "#disable-user" # XEP-0133
+NS_ADMIN_REENABLE_USER = NS_ADMIN + "#reenable-user" # XEP-0133
+NS_ADMIN_END_USER_SESSION = NS_ADMIN + "#end-user-session" # XEP-0133
+NS_ADMIN_GET_USER_PASSWORD = NS_ADMIN + "#get-user-password" # XEP-0133
+NS_ADMIN_CHANGE_USER_PASSWORD = NS_ADMIN + "#change-user-password" # XEP-0133
+NS_ADMIN_GET_USER_ROSTER = NS_ADMIN + "#get-user-roster" # XEP-0133
+NS_ADMIN_GET_USER_LASTLOGIN = NS_ADMIN + "#get-user-lastlogin" # XEP-0133
+NS_ADMIN_USER_STATS = NS_ADMIN + "#user-stats" # XEP-0133
+NS_ADMIN_EDIT_BLACKLIST = NS_ADMIN + "#edit-blacklist" # XEP-0133
+NS_ADMIN_EDIT_WHITELIST = NS_ADMIN + "#edit-whitelist" # XEP-0133
+NS_ADMIN_REGISTERED_USERS_NUM = NS_ADMIN + "#get-registered-users-num" # XEP-0133
+NS_ADMIN_DISABLED_USERS_NUM = NS_ADMIN + "#get-disabled-users-num" # XEP-0133
+NS_ADMIN_ONLINE_USERS_NUM = NS_ADMIN + "#get-online-users-num" # XEP-0133
+NS_ADMIN_ACTIVE_USERS_NUM = NS_ADMIN + "#get-active-users-num" # XEP-0133
+NS_ADMIN_IDLE_USERS_NUM = NS_ADMIN + "#get-idle-users-num" # XEP-0133
+NS_ADMIN_REGISTERED_USERS_LIST = NS_ADMIN + "#get-registered-users-list" # XEP-0133
+NS_ADMIN_DISABLED_USERS_LIST = NS_ADMIN + "#get-disabled-users-list" # XEP-0133
+NS_ADMIN_ONLINE_USERS_LIST = NS_ADMIN + "#get-online-users-list" # XEP-0133
+NS_ADMIN_ACTIVE_USERS_LIST = NS_ADMIN + "#get-active-users-list" # XEP-0133
+NS_ADMIN_IDLE_USERS_LIST = NS_ADMIN + "#get-idle-users-list" # XEP-0133
+NS_ADMIN_ANNOUNCE = NS_ADMIN + "#announce" # XEP-0133
+NS_ADMIN_SET_MOTD = NS_ADMIN + "#set-motd" # XEP-0133
+NS_ADMIN_EDIT_MOTD = NS_ADMIN + "#edit-motd" # XEP-0133
+NS_ADMIN_DELETE_MOTD = NS_ADMIN + "#delete-motd" # XEP-0133
+NS_ADMIN_SET_WELCOME = NS_ADMIN + "#set-welcome" # XEP-0133
+NS_ADMIN_DELETE_WELCOME = NS_ADMIN + "#delete-welcome" # XEP-0133
+NS_ADMIN_EDIT_ADMIN = NS_ADMIN + "#edit-admin" # XEP-0133
+NS_ADMIN_RESTART = NS_ADMIN + "#restart" # XEP-0133
+NS_ADMIN_SHUTDOWN = NS_ADMIN + "#shutdown" # XEP-0133
+NS_AGENTS = "jabber:iq:agents" # XEP-0094 (historical)
+NS_AMP = "http://jabber.org/protocol/amp" # XEP-0079
+NS_AMP_ERRORS = NS_AMP + "#errors" # XEP-0079
+NS_AUTH = "jabber:iq:auth" # XEP-0078
+NS_AVATAR = "jabber:iq:avatar" # XEP-0008 (historical)
+NS_BIND = "urn:ietf:params:xml:ns:xmpp-bind" # RFC 3920
+NS_BROWSE = "jabber:iq:browse" # XEP-0011 (historical)
+NS_BYTESTREAM = "http://jabber.org/protocol/bytestreams" # XEP-0065
+NS_CAPS = "http://jabber.org/protocol/caps" # XEP-0115
+NS_CAPTCHA = "urn:xmpp:captcha" # XEP-0158
+NS_CHATSTATES = "http://jabber.org/protocol/chatstates" # XEP-0085
+NS_CLIENT = "jabber:client" # RFC 3921
+NS_COMMANDS = "http://jabber.org/protocol/commands" # XEP-0050
+NS_COMPONENT_ACCEPT = "jabber:component:accept" # XEP-0114
+NS_COMPONENT_1 = "http://jabberd.jabberstudio.org/ns/component/1.0" # Jabberd2
+NS_COMPRESS = "http://jabber.org/protocol/compress" # XEP-0138
+NS_DATA = "jabber:x:data" # XEP-0004
+NS_DATA_LAYOUT = "http://jabber.org/protocol/xdata-layout" # XEP-0141
+NS_DATA_VALIDATE = "http://jabber.org/protocol/xdata-validate" # XEP-0122
+NS_DELAY = "jabber:x:delay" # XEP-0091 (deprecated in favour of XEP-0203)
+NS_URN_DELAY = "urn:xmpp:delay" # XEP-0203
+
+
+NS_DIALBACK = "jabber:server:dialback" # RFC 3921
+NS_DISCO = "http://jabber.org/protocol/disco" # XEP-0030
+NS_DISCO_INFO = NS_DISCO + "#info" # XEP-0030
+NS_DISCO_ITEMS = NS_DISCO + "#items" # XEP-0030
+NS_ENCRYPTED = "jabber:x:encrypted" # XEP-0027
+NS_EVENT = "jabber:x:event" # XEP-0022 (deprecated)
+NS_FEATURE = "http://jabber.org/protocol/feature-neg" # XEP-0020
+NS_FILE = "http://jabber.org/protocol/si/profile/file-transfer" # XEP-0096
+NS_GATEWAY = "jabber:iq:gateway" # XEP-0100
+NS_GEOLOC = "http://jabber.org/protocol/geoloc" # XEP-0080
+NS_GROUPCHAT = "gc-1.0" # XEP-0045
+NS_HTTP_BIND = "http://jabber.org/protocol/httpbind" # XEP-0124
+NS_IBB = "http://jabber.org/protocol/ibb" # XEP-0047
+NS_INVISIBLE = "presence-invisible" # Jabberd2
+NS_IQ = "iq" # Jabberd2
+NS_LAST = "jabber:iq:last" # XEP-0012
+NS_MEDIA = "urn:xmpp:media-element" # XEP-0158
+NS_MESSAGE = "message" # Jabberd2
+NS_MOOD = "http://jabber.org/protocol/mood" # XEP-0107
+NS_MUC = "http://jabber.org/protocol/muc" # XEP-0045
+NS_MUC_ADMIN = NS_MUC + "#admin" # XEP-0045
+NS_MUC_OWNER = NS_MUC + "#owner" # XEP-0045
+NS_MUC_UNIQUE = NS_MUC + "#unique" # XEP-0045
+NS_MUC_USER = NS_MUC + "#user" # XEP-0045
+NS_MUC_REGISTER = NS_MUC + "#register" # XEP-0045
+NS_MUC_REQUEST = NS_MUC + "#request" # XEP-0045
+NS_MUC_ROOMCONFIG = NS_MUC + "#roomconfig" # XEP-0045
+NS_MUC_ROOMINFO = NS_MUC + "#roominfo" # XEP-0045
+NS_MUC_ROOMS = NS_MUC + "#rooms" # XEP-0045
+NS_MUC_TRAFIC = NS_MUC + "#traffic" # XEP-0045
+NS_NICK = "http://jabber.org/protocol/nick" # XEP-0172
+NS_OFFLINE = "http://jabber.org/protocol/offline" # XEP-0013
+NS_OOB = "jabber:x:oob" # XEP-0066
+NS_PHYSLOC = "http://jabber.org/protocol/physloc" # XEP-0112
+NS_PRESENCE = "presence" # Jabberd2
+NS_PRIVACY = "jabber:iq:privacy" # RFC 3921
+NS_PRIVATE = "jabber:iq:private" # XEP-0049
+NS_PUBSUB = "http://jabber.org/protocol/pubsub" # XEP-0060
+NS_RC = "http://jabber.org/protocol/rc" # XEP-0146
+NS_REGISTER = "jabber:iq:register" # XEP-0077
+NS_RECEIPTS = "urn:xmpp:receipts" # XEP-0184
+NS_ROSTER = "jabber:iq:roster" # RFC 3921
+NS_ROSTERX = "http://jabber.org/protocol/rosterx" # XEP-0144
+NS_RPC = "jabber:iq:rpc" # XEP-0009
+NS_SASL = "urn:ietf:params:xml:ns:xmpp-sasl" # RFC 3920
+NS_SEARCH = "jabber:iq:search" # XEP-0055
+NS_SERVER = "jabber:server" # RFC 3921
+NS_SESSION = "urn:ietf:params:xml:ns:xmpp-session" # RFC 3921
+NS_SI = "http://jabber.org/protocol/si" # XEP-0096
+NS_SI_PUB = "http://jabber.org/protocol/sipub" # XEP-0137
+NS_SIGNED = "jabber:x:signed" # XEP-0027
+NS_SOFTWAREINFO = "urn:xmpp:dataforms:softwareinfo" # XEP-0155
+NS_STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas" # RFC 3920
+NS_STATS = "http://jabber.org/protocol/stats" # XEP-0039
+NS_STREAMS = "http://etherx.jabber.org/streams" # RFC 3920
+NS_TIME = "jabber:iq:time" # XEP-0090 (deprecated)
+NS_TLS = "urn:ietf:params:xml:ns:xmpp-tls" # RFC 3920
+NS_URN_ATTENTION = "urn:xmpp:attention:0" # XEP-0224
+NS_URN_OOB = "urn:xmpp:bob" # XEP-0158
+NS_URN_TIME = "urn:xmpp:time" # XEP-0202
+NS_VACATION = "http://jabber.org/protocol/vacation" # XEP-0109
+NS_VCARD = "vcard-temp" # XEP-0054
+NS_VCARD_UPDATE = "vcard-temp:x:update" # XEP-0153
+NS_VERSION = "jabber:iq:version" # XEP-0092
+NS_WAITINGLIST = "http://jabber.org/protocol/waitinglist" # XEP-0130
+NS_XHTML_IM = "http://jabber.org/protocol/xhtml-im" # XEP-0071
+NS_XMPP_STREAMS = "urn:ietf:params:xml:ns:xmpp-streams" # RFC 3920
+NS_PING = "urn:xmpp:ping" # XEP-0199
+
+NS_MUC_FILTER = "http://jabber.ru/muc-filter"
+
+STREAM_NOT_AUTHORIZED = NS_XMPP_STREAMS + " not-authorized"
+STREAM_REMOTE_CONNECTION_FAILED = NS_XMPP_STREAMS + " remote-connection-failed"
+SASL_MECHANISM_TOO_WEAK = NS_SASL + " mechanism-too-weak"
+STREAM_XML_NOT_WELL_FORMED = NS_XMPP_STREAMS + " xml-not-well-formed"
+ERR_JID_MALFORMED = NS_STANZAS + " jid-malformed"
+STREAM_SEE_OTHER_HOST = NS_XMPP_STREAMS + " see-other-host"
+STREAM_BAD_NAMESPACE_PREFIX = NS_XMPP_STREAMS + " bad-namespace-prefix"
+ERR_SERVICE_UNAVAILABLE = NS_STANZAS + " service-unavailable"
+STREAM_CONNECTION_TIMEOUT = NS_XMPP_STREAMS + " connection-timeout"
+STREAM_UNSUPPORTED_VERSION = NS_XMPP_STREAMS + " unsupported-version"
+STREAM_IMPROPER_ADDRESSING = NS_XMPP_STREAMS + " improper-addressing"
+STREAM_UNDEFINED_CONDITION = NS_XMPP_STREAMS + " undefined-condition"
+SASL_NOT_AUTHORIZED = NS_SASL + " not-authorized"
+ERR_GONE = NS_STANZAS + " gone"
+SASL_TEMPORARY_AUTH_FAILURE = NS_SASL + " temporary-auth-failure"
+ERR_REMOTE_SERVER_NOT_FOUND = NS_STANZAS + " remote-server-not-found"
+ERR_UNEXPECTED_REQUEST = NS_STANZAS + " unexpected-request"
+ERR_RECIPIENT_UNAVAILABLE = NS_STANZAS + " recipient-unavailable"
+ERR_CONFLICT = NS_STANZAS + " conflict"
+STREAM_SYSTEM_SHUTDOWN = NS_XMPP_STREAMS + " system-shutdown"
+STREAM_BAD_FORMAT = NS_XMPP_STREAMS + " bad-format"
+ERR_SUBSCRIPTION_REQUIRED = NS_STANZAS + " subscription-required"
+STREAM_INTERNAL_SERVER_ERROR = NS_XMPP_STREAMS + " internal-server-error"
+ERR_NOT_AUTHORIZED = NS_STANZAS + " not-authorized"
+SASL_ABORTED = NS_SASL + " aborted"
+ERR_REGISTRATION_REQUIRED = NS_STANZAS + " registration-required"
+ERR_INTERNAL_SERVER_ERROR = NS_STANZAS + " internal-server-error"
+SASL_INCORRECT_ENCODING = NS_SASL + " incorrect-encoding"
+STREAM_HOST_GONE = NS_XMPP_STREAMS + " host-gone"
+STREAM_POLICY_VIOLATION = NS_XMPP_STREAMS + " policy-violation"
+STREAM_INVALID_XML = NS_XMPP_STREAMS + " invalid-xml"
+STREAM_CONFLICT = NS_XMPP_STREAMS + " conflict"
+STREAM_RESOURCE_CONSTRAINT = NS_XMPP_STREAMS + " resource-constraint"
+STREAM_UNSUPPORTED_ENCODING = NS_XMPP_STREAMS + " unsupported-encoding"
+ERR_NOT_ALLOWED = NS_STANZAS + " not-allowed"
+ERR_ITEM_NOT_FOUND = NS_STANZAS + " item-not-found"
+ERR_NOT_ACCEPTABLE = NS_STANZAS + " not-acceptable"
+STREAM_INVALID_FROM = NS_XMPP_STREAMS + " invalid-from"
+ERR_FEATURE_NOT_IMPLEMENTED = NS_STANZAS + " feature-not-implemented"
+ERR_BAD_REQUEST = NS_STANZAS + " bad-request"
+STREAM_INVALID_ID = NS_XMPP_STREAMS + " invalid-id"
+STREAM_HOST_UNKNOWN = NS_XMPP_STREAMS + " host-unknown"
+ERR_UNDEFINED_CONDITION = NS_STANZAS + " undefined-condition"
+SASL_INVALID_MECHANISM = NS_SASL + " invalid-mechanism"
+STREAM_RESTRICTED_XML = NS_XMPP_STREAMS + " restricted-xml"
+ERR_RESOURCE_CONSTRAINT = NS_STANZAS + " resource-constraint"
+ERR_REMOTE_SERVER_TIMEOUT = NS_STANZAS + " remote-server-timeout"
+SASL_INVALID_AUTHZID = NS_SASL + " invalid-authzid"
+ERR_PAYMENT_REQUIRED = NS_STANZAS + " payment-required"
+STREAM_INVALID_NAMESPACE = NS_XMPP_STREAMS + " invalid-namespace"
+ERR_REDIRECT = NS_STANZAS + " redirect"
+STREAM_UNSUPPORTED_STANZA_TYPE = NS_XMPP_STREAMS + " unsupported-stanza-type"
+ERR_FORBIDDEN = NS_STANZAS + " forbidden"
+
+ERRORS = {
+ "urn:ietf:params:xml:ns:xmpp-sasl not-authorized": ["", "", "The authentication failed because the initiating entity did not provide valid credentials (this includes but is not limited to the case of an unknown username); sent in reply to a <response/> element or an <auth/> element with initial response data."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas payment-required": ["402", "auth", "The requesting entity is not authorized to access the requested service because payment is required."],
+ "urn:ietf:params:xml:ns:xmpp-sasl mechanism-too-weak": ["", "", "The mechanism requested by the initiating entity is weaker than server policy permits for that initiating entity; sent in reply to a <response/> element or an <auth/> element with initial response data."],
+ "urn:ietf:params:xml:ns:xmpp-streams unsupported-encoding": ["", "", "The initiating entity has encoded the stream in an encoding that is not supported by the server."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas remote-server-timeout": ["504", "wait", "A remote server or service specified as part or all of the JID of the intended recipient could not be contacted within a reasonable amount of time."],
+ "urn:ietf:params:xml:ns:xmpp-streams remote-connection-failed": ["", "", "The server is unable to properly connect to a remote resource that is required for authentication or authorization."],
+ "urn:ietf:params:xml:ns:xmpp-streams restricted-xml": ["", "", "The entity has attempted to send restricted XML features such as a comment, processing instruction, DTD, entity reference, or unescaped character."],
+ "urn:ietf:params:xml:ns:xmpp-streams see-other-host": ["", "", "The server will not provide service to the initiating entity but is redirecting traffic to another host."],
+ "urn:ietf:params:xml:ns:xmpp-streams xml-not-well-formed": ["", "", "The initiating entity has sent XML that is not well-formed."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas subscription-required": ["407", "auth", "The requesting entity is not authorized to access the requested service because a subscription is required."],
+ "urn:ietf:params:xml:ns:xmpp-streams internal-server-error": ["", "", "The server has experienced a misconfiguration or an otherwise-undefined internal error that prevents it from servicing the stream."],
+ "urn:ietf:params:xml:ns:xmpp-sasl invalid-mechanism": ["", "", "The initiating entity did not provide a mechanism or requested a mechanism that is not supported by the receiving entity; sent in reply to an <auth/> element."],
+ "urn:ietf:params:xml:ns:xmpp-streams policy-violation": ["", "", "The entity has violated some local service policy."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas conflict": ["409", "cancel", "Access cannot be granted because an existing resource or session exists with the same name or address."],
+ "urn:ietf:params:xml:ns:xmpp-streams unsupported-stanza-type": ["", "", "The initiating entity has sent a first-level child of the stream that is not supported by the server."],
+ "urn:ietf:params:xml:ns:xmpp-sasl incorrect-encoding": ["", "", "The data provided by the initiating entity could not be processed because the [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003. encoding is incorrect (e.g., because the encoding does not adhere to the definition in Section 3 of [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003.); sent in reply to a <response/> element or an <auth/> element with initial response data."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas registration-required": ["407", "auth", "The requesting entity is not authorized to access the requested service because registration is required."],
+ "urn:ietf:params:xml:ns:xmpp-streams invalid-id": ["", "", "The stream ID or dialback ID is invalid or does not match an ID previously provided."],
+ "urn:ietf:params:xml:ns:xmpp-sasl invalid-authzid": ["", "", "The authzid provided by the initiating entity is invalid, either because it is incorrectly formatted or because the initiating entity does not have permissions to authorize that ID; sent in reply to a <response/> element or an <auth/> element with initial response data."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas bad-request": ["400", "modify", "The sender has sent XML that is malformed or that cannot be processed."],
+ "urn:ietf:params:xml:ns:xmpp-streams not-authorized": ["", "", "The entity has attempted to send data before the stream has been authenticated, or otherwise is not authorized to perform an action related to stream negotiation."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas forbidden": ["403", "auth", "The requesting entity does not possess the required permissions to perform the action."],
+ "urn:ietf:params:xml:ns:xmpp-sasl temporary-auth-failure": ["", "", "The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an <auth/> element or <response/> element."],
+ "urn:ietf:params:xml:ns:xmpp-streams invalid-namespace": ["", "", "The streams namespace name is something other than \http://etherx.jabber.org/streams\" or the dialback namespace name is something other than \"jabber:server:dialback\"."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas feature-not-implemented": ["501", "cancel", "The feature requested is not implemented by the recipient or server and therefore cannot be processed."],
+ "urn:ietf:params:xml:ns:xmpp-streams invalid-xml": ["", "", "The entity has sent invalid XML over the stream to a server that performs validation."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas item-not-found": ["404", "cancel", "The addressed JID or item requested cannot be found."],
+ "urn:ietf:params:xml:ns:xmpp-streams host-gone": ["", "", "The value of the \"to\" attribute provided by the initiating entity in the stream header corresponds to a hostname that is no longer hosted by the server."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas recipient-unavailable": ["404", "wait", "The intended recipient is temporarily unavailable."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas not-acceptable": ["406", "cancel", "The recipient or server understands the request but is refusing to process it because it does not meet criteria defined by the recipient or server."],
+ "urn:ietf:params:xml:ns:xmpp-streams invalid-from": ["cancel", "", "The JID or hostname provided in a \"from\" address does not match an authorized JID or validated domain negotiated between servers via SASL or dialback, or between a client and a server via authentication and resource authorization."],
+ "urn:ietf:params:xml:ns:xmpp-streams bad-format": ["", "", "The entity has sent XML that cannot be processed."],
+ "urn:ietf:params:xml:ns:xmpp-streams resource-constraint": ["", "", "The server lacks the system resources necessary to service the stream."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas undefined-condition": ["500", "", "The condition is undefined."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas redirect": ["302", "modify", "The recipient or server is redirecting requests for this information to another entity."],
+ "urn:ietf:params:xml:ns:xmpp-streams bad-namespace-prefix": ["", "", "The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix."],
+ "urn:ietf:params:xml:ns:xmpp-streams system-shutdown": ["", "", "The server is being shut down and all active streams are being closed."],
+ "urn:ietf:params:xml:ns:xmpp-streams conflict": ["", "", "The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream."],
+ "urn:ietf:params:xml:ns:xmpp-streams connection-timeout": ["", "", "The entity has not generated any traffic over the stream for some period of time."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas jid-malformed": ["400", "modify", "The value of the \"to\" attribute in the sender's stanza does not adhere to the syntax defined in Addressing Scheme."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas resource-constraint": ["500", "wait", "The server or recipient lacks the system resources necessary to service the request."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas remote-server-not-found": ["404", "cancel", "A remote server or service specified as part or all of the JID of the intended recipient does not exist."],
+ "urn:ietf:params:xml:ns:xmpp-streams unsupported-version": ["", "", "The value of the \"version\" attribute provided by the initiating entity in the stream header specifies a version of XMPP that is not supported by the server."],
+ "urn:ietf:params:xml:ns:xmpp-streams host-unknown": ["", "", "The value of the \"to\" attribute provided by the initiating entity in the stream header does not correspond to a hostname that is hosted by the server."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas unexpected-request": ["400", "wait", "The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order)."],
+ "urn:ietf:params:xml:ns:xmpp-streams improper-addressing": ["", "", "A stanza sent between two servers lacks a \"to\" or \"from\" attribute (or the attribute has no value)."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas not-allowed": ["405", "cancel", "The recipient or server does not allow any entity to perform the action."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas internal-server-error": ["500", "wait", "The server could not process the stanza because of a misconfiguration or an otherwise-undefined internal server error."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas gone": ["302", "modify", "The recipient or server can no longer be contacted at this address."],
+ "urn:ietf:params:xml:ns:xmpp-streams undefined-condition": ["", "", "The error condition is not one of those defined by the other conditions in this list."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas service-unavailable": ["503", "cancel", "The server or recipient does not currently provide the requested service."],
+ "urn:ietf:params:xml:ns:xmpp-stanzas not-authorized": ["401", "auth", "The sender must provide proper credentials before being allowed to perform the action, or has provided improper credentials."],
+ "urn:ietf:params:xml:ns:xmpp-sasl aborted": ["", "", "The receiving entity acknowledges an <abort/> element sent by the initiating entity; sent in reply to the <abort/> element."]
+}
+
+_errorcodes = {
+ "302": "redirect",
+ "400": "unexpected-request",
+ "401": "not-authorized",
+ "402": "payment-required",
+ "403": "forbidden",
+ "404": "remote-server-not-found",
+ "405": "not-allowed",
+ "406": "not-acceptable",
+ "407": "subscription-required",
+ "409": "conflict",
+ "500": "undefined-condition",
+ "501": "feature-not-implemented",
+ "503": "service-unavailable",
+ "504": "remote-server-timeout"
+}
+
+def isResultNode(node):
+ """
+ Returns true if the node is a positive reply.
+ """
+ return (node and node.getType() == "result")
+
+def isGetNode(node):
+ """
+ Returns true if the node is a positive reply.
+ """
+ return (node and node.getType() == "get")
+
+def isSetNode(node):
+ """
+ Returns true if the node is a positive reply.
+ """
+ return (node and node.getType() == "set")
+
+def isErrorNode(node):
+ """
+ Returns true if the node is a negative reply.
+ """
+ return (node and node.getType() == "error")
+
+class NodeProcessed(Exception):
+ """
+ Exception that should be raised by handler when the handling should be stopped.
+ """
+
+class StreamError(Exception):
+ """
+ Base exception class for stream errors.
+ """
+
+class BadFormat(StreamError): pass
+
+class BadNamespacePrefix(StreamError): pass
+
+class Conflict(StreamError): pass
+
+class ConnectionTimeout(StreamError): pass
+
+class HostGone(StreamError): pass
+
+class HostUnknown(StreamError): pass
+
+class ImproperAddressing(StreamError): pass
+
+class InternalServerError(StreamError): pass
+
+class InvalidFrom(StreamError): pass
+
+class InvalidID(StreamError): pass
+
+class InvalidNamespace(StreamError): pass
+
+class InvalidXML(StreamError): pass
+
+class NotAuthorized(StreamError): pass
+
+class PolicyViolation(StreamError): pass
+
+class RemoteConnectionFailed(StreamError): pass
+
+class ResourceConstraint(StreamError): pass
+
+class RestrictedXML(StreamError): pass
+
+class SeeOtherHost(StreamError): pass
+
+class SystemShutdown(StreamError): pass
+
+class UndefinedCondition(StreamError): pass
+
+class UnsupportedEncoding(StreamError): pass
+
+class UnsupportedStanzaType(StreamError): pass
+
+class UnsupportedVersion(StreamError): pass
+
+class XMLNotWellFormed(StreamError): pass
+
+stream_exceptions = {
+ "bad-format": BadFormat,
+ "bad-namespace-prefix": BadNamespacePrefix,
+ "conflict": Conflict,
+ "connection-timeout": ConnectionTimeout,
+ "host-gone": HostGone,
+ "host-unknown": HostUnknown,
+ "improper-addressing": ImproperAddressing,
+ "internal-server-error": InternalServerError,
+ "invalid-from": InvalidFrom,
+ "invalid-id": InvalidID,
+ "invalid-namespace": InvalidNamespace,
+ "invalid-xml": InvalidXML,
+ "not-authorized": NotAuthorized,
+ "policy-violation": PolicyViolation,
+ "remote-connection-failed": RemoteConnectionFailed,
+ "resource-constraint": ResourceConstraint,
+ "restricted-xml": RestrictedXML,
+ "see-other-host": SeeOtherHost,
+ "system-shutdown": SystemShutdown,
+ "undefined-condition": UndefinedCondition,
+ "unsupported-encoding": UnsupportedEncoding,
+ "unsupported-stanza-type": UnsupportedStanzaType,
+ "unsupported-version": UnsupportedVersion,
+ "xml-not-well-formed": XMLNotWellFormed
+}
+
+class JID:
+ """
+ JID object. JID can be built from string, modified, compared, serialized into string.
+ """
+ def __init__(self, jid=None, node="", domain="", resource=""):
+ """
+ Constructor. JID can be specified as string (jid argument) or as separate parts.
+ Examples:
+ JID("node@domain/resource")
+ JID(node="node", domain="domain.org")
+ """
+ if not jid and not domain:
+ raise ValueError("JID must contain at least domain name")
+ elif isinstance(jid, self.__class__):
+ self.node, self.domain, self.resource = jid.node, jid.domain, jid.resource
+ elif domain:
+ self.node, self.domain, self.resource = node, domain, resource
+ else:
+ if jid.find("@") + 1:
+ self.node, jid = jid.split("@", 1)
+ else:
+ self.node = ""
+ if jid.find("/") + 1:
+ self.domain, self.resource = jid.split("/", 1)
+ else:
+ self.domain, self.resource = jid, ""
+
+ def getNode(self):
+ """
+ Return the node part of the JID.
+ """
+ return self.node
+
+ def setNode(self, node):
+ """
+ Set the node part of the JID to new value. Specify None to remove the node part.
+ """
+ self.node = node.lower()
+
+ def getDomain(self):
+ """
+ Return the domain part of the JID.
+ """
+ return self.domain
+
+ def setDomain(self, domain):
+ """
+ Set the domain part of the JID to new value.
+ """
+ self.domain = domain.lower()
+
+ def getResource(self):
+ """
+ Return the resource part of the JID.
+ """
+ return self.resource
+
+ def setResource(self, resource):
+ """
+ Set the resource part of the JID to new value. Specify None to remove the resource part.
+ """
+ self.resource = resource
+
+ def getStripped(self):
+ """
+ Return the bare representation of JID. I.e. string value w/o resource.
+ """
+ return self.__str__(0)
+
+ def __eq__(self, other):
+ """
+ Compare the JID to another instance or to string for equality.
+ """
+ try:
+ other = JID(other)
+ except ValueError:
+ return False
+ return self.resource == other.resource and self.__str__(0) == other.__str__(0)
+
+ def __ne__(self, other):
+ """
+ Compare the JID to another instance or to string for non-equality.
+ """
+ return not self.__eq__(other)
+
+ def bareMatch(self, other):
+ """
+ Compare the node and domain parts of the JID's for equality.
+ """
+ return self.__str__(0) == JID(other).__str__(0)
+
+ def __str__(self, wresource=1):
+ """
+ Serialize JID into string.
+ """
+ jid = "@".join((self.node, self.domain)) if self.node else self.domain
+ if wresource and self.resource:
+ jid = "/".join((jid, self.resource))
+ return jid
+
+ def __hash__(self):
+ """
+ Produce hash of the JID, Allows to use JID objects as keys of the dictionary.
+ """
+ return hash(self.__str__())
+
+class Protocol(Node):
+ """
+ A "stanza" object class. Contains methods that are common for presences, iqs and messages.
+ """
+ def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, payload=[], timestamp=None, xmlns=None, node=None):
+ """
+ Constructor, name is the name of the stanza i.e. "message" or "presence" or "iq".
+ to is the value of "to" attribure, "typ" - "type" attribute
+ frn - from attribure, attrs - other attributes mapping, payload - same meaning as for simplexml payload definition
+ timestamp - the time value that needs to be stamped over stanza
+ xmlns - namespace of top stanza node
+ node - parsed or unparsed stana to be taken as prototype.
+ """
+ if not attrs:
+ attrs = {}
+ if to:
+ attrs["to"] = to
+ if frm:
+ attrs["from"] = frm
+ if typ:
+ attrs["type"] = typ
+ Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node)
+ if not node and xmlns:
+ self.setNamespace(xmlns)
+ if self["to"]:
+ self.setTo(self["to"])
+ if self["from"]:
+ self.setFrom(self["from"])
+ if node and isinstance(node, self.__class__) and self.__class__ == node.__class__ and "id" in self.attrs:
+ del self.attrs["id"]
+ self.timestamp = None
+
+ delay = self.getTag("delay", namespace=NS_URN_DELAY) or self.getTag("x", namespace=NS_DELAY)
+ if delay:
+ if not self.getTimestamp() or delay.getAttr("stamp") < self.getTimestamp():
+ self.setTimestamp(delay.getAttr("stamp"))
+
+ if timestamp is not None:
+ self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp=""
+
+
+ def getTo(self):
+ """
+ Return value of the "to" attribute.
+ """
+ try:
+ to = self["to"]
+ except Exception:
+ to = None
+ return to
+
+ def getFrom(self):
+ """
+ Return value of the "from" attribute.
+ """
+ try:
+ frm = self["from"]
+ except Exception:
+ frm = None
+ return frm
+
+ def getTimestamp(self):
+ """
+ Return the timestamp in the "yyyymmddThhmmss" format.
+ """
+ return self.timestamp
+
+ def getID(self):
+ """
+ Return the value of the "id" attribute.
+ """
+ return self.getAttr("id")
+
+ def setTo(self, val):
+ """
+ Set the value of the "to" attribute.
+ """
+ self.setAttr("to", JID(val))
+
+ def getType(self):
+ """
+ Return the value of the "type" attribute.
+ """
+ return self.getAttr("type")
+
+ def setFrom(self, val):
+ """
+ Set the value of the "from" attribute.
+ """
+ self.setAttr("from", JID(val))
+
+ def setType(self, val):
+ """
+ Set the value of the "type" attribute.
+ """
+ self.setAttr("type", val)
+
+ def setID(self, val):
+ """
+ Set the value of the "id" attribute.
+ """
+ self.setAttr("id", val)
+
+ def getError(self):
+ """
+ Return the error-condition (if present) or the textual description of the error (otherwise).
+ """
+ errtag = self.getTag("error")
+ if errtag:
+ for tag in errtag.getChildren():
+ if tag.getName() != "text":
+ return tag.getName()
+ return errtag.getData()
+
+ def getErrorCode(self):
+ """
+ Return the error code. Obsolette.
+ """
+ return self.getTagAttr("error", "code")
+
+ def setError(self, error, code=None):
+ """
+ Set the error code. Obsolette. Use error-conditions instead.
+ """
+ if code:
+ if str(code) in _errorcodes.keys():
+ error = ErrorNode(_errorcodes[str(code)], text=error)
+ else:
+ error = ErrorNode(ERR_UNDEFINED_CONDITION, code=code, typ="cancel", text=error)
+ elif isinstance(error, basestring):
+ error = ErrorNode(error)
+ self.setType("error")
+ self.addChild(node=error)
+
+ def setTimestamp(self, val=None):
+ """
+ Set the timestamp. timestamp should be the yyyymmddThhmmss string.
+ """
+ if not val:
+ val = time.strftime("%Y%m%dT%H:%M:%S", time.gmtime())
+ self.timestamp = val
+ self.setTag("x", {"stamp": self.timestamp}, namespace=NS_DELAY)
+
+ def getProperties(self):
+ """
+ Return the list of namespaces to which belongs the direct childs of element.
+ """
+ props = []
+ for child in self.getChildren():
+ prop = child.getNamespace()
+ if prop not in props:
+ props.append(prop)
+ return props
+
+ def __setitem__(self, item, val):
+ """
+ Set the item "item" to the value "val".
+ """
+ if item in ["to", "from"]:
+ val = JID(val)
+ return self.setAttr(item, val)
+
+class Message(Protocol):
+ """
+ XMPP Message stanza - "push" mechanism.
+ """
+ def __init__(self, to=None, body=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None):
+ """
+ Create message object. You can specify recipient, text of message, type of message
+ any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
+ Alternatively you can pass in the other XML object as the "node" parameted to replicate it as message.
+ """
+ Protocol.__init__(self, "message", to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
+ if body:
+ self.setBody(body)
+ if subject:
+ self.setSubject(subject)
+
+ def getBody(self):
+ """
+ Returns text of the message.
+ """
+ return self.getTagData("body")
+
+ def getSubject(self):
+ """
+ Returns subject of the message.
+ """
+ return self.getTagData("subject")
+
+ def getThread(self):
+ """
+ Returns thread of the message.
+ """
+ return self.getTagData("thread")
+
+ def setBody(self, val):
+ """
+ Sets the text of the message.
+ """
+ self.setTagData("body", val)
+
+ def setSubject(self, val):
+ """
+ Sets the subject of the message.
+ """
+ self.setTagData("subject", val)
+
+ def setThread(self, val):
+ """
+ Sets the thread of the message.
+ """
+ self.setTagData("thread", val)
+
+ def buildReply(self, text=None):
+ """
+ Builds and returns another message object with specified text.
+ The to, from type and thread properties of new message are pre-set as reply to this message.
+ """
+ msg = Message(to=self.getFrom(), frm=self.getTo(), body=text)
+ thr = self.getThread()
+ if thr:
+ msg.setThread(thr)
+ return msg
+
+class Presence(Protocol):
+ """
+ XMPP Presence object.
+ """
+ def __init__(self, to=None, typ=None, priority=None, show=None, status=None, attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, node=None):
+ """
+ Create presence object. You can specify recipient, type of message, priority, show and status values
+ any additional attributes, sender of the presence, timestamp, any additional payload (f.e. jabber:x:delay element) and namespace in one go.
+ Alternatively you can pass in the other XML object as the "node" parameted to replicate it as presence.
+ """
+ Protocol.__init__(self, "presence", to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node)
+ if priority:
+ self.setPriority(priority)
+ if show:
+ self.setShow(show)
+ if status:
+ self.setStatus(status)
+
+ def getPriority(self):
+ """
+ Returns the priority of the message.
+ """
+ return self.getTagData("priority")
+
+ def getShow(self):
+ """
+ Returns the show value of the message.
+ """
+ return self.getTagData("show")
+
+ def getStatus(self):
+ """
+ Returns the status string of the message.
+ """
+ return self.getTagData("status")
+
+ def setPriority(self, val):
+ """
+ Sets the priority of the message.
+ """
+ self.setTagData("priority", val)
+
+ def setShow(self, val):
+ """
+ Sets the show value of the message.
+ """
+ self.setTagData("show", val)
+
+ def setStatus(self, val):
+ """
+ Sets the status string of the message.
+ """
+ self.setTagData("status", val)
+
+ def _muc_getItemAttr(self, tag, attr):
+ for xtag in self.getTags("x", namespace=NS_MUC_USER):
+ for child in xtag.getTags(tag):
+ return child.getAttr(attr)
+
+ def _muc_getSubTagDataAttr(self, tag, attr):
+ for xtag in self.getTags("x", namespace=NS_MUC_USER):
+ for child in xtag.getTags("item"):
+ for cchild in child.getTags(tag):
+ return cchild.getData(), cchild.getAttr(attr)
+ return None, None
+
+ def getRole(self):
+ """
+ Returns the presence role (for groupchat).
+ """
+ return self._muc_getItemAttr("item", "role")
+
+ def getAffiliation(self):
+ """Returns the presence affiliation (for groupchat).
+ """
+ return self._muc_getItemAttr("item", "affiliation")
+
+ def getNick(self):
+ """
+ Returns the nick value (for nick change in groupchat).
+ """
+ return self._muc_getItemAttr("item", "nick")
+
+ def getJid(self):
+ """
+ Returns the presence jid (for groupchat).
+ """
+ return self._muc_getItemAttr("item", "jid")
+
+ def getReason(self):
+ """
+ Returns the reason of the presence (for groupchat).
+ """
+ return self._muc_getSubTagDataAttr("reason", "")[0]
+
+ def getActor(self):
+ """
+ Returns the reason of the presence (for groupchat).
+ """
+ return self._muc_getSubTagDataAttr("actor", "jid")[1]
+
+ def getStatusCode(self):
+ """
+ Returns the status code of the presence (for groupchat).
+ """
+ return self._muc_getItemAttr("status", "code")
+
+class Iq(Protocol):
+ """
+ XMPP Iq object - get/set dialog mechanism.
+ """
+ def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, payload=[], xmlns=NS_CLIENT, node=None):
+ """
+ Create Iq object. You can specify type, query namespace
+ any additional attributes, recipient of the iq, sender of the iq, any additional payload (f.e. jabber:x:data node) and namespace in one go.
+ Alternatively you can pass in the other XML object as the "node" parameted to replicate it as an iq.
+ """
+ Protocol.__init__(self, "iq", to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node)
+ if payload:
+ self.setQueryPayload(payload)
+ if queryNS:
+ self.setQueryNS(queryNS)
+
+ def getQuery(self, namespace=None):
+ """
+ Returns the query node.
+ """
+ return self.getTag("query", namespace=namespace)
+
+ def getQueryNS(self):
+ """
+ Returns the namespace of the "query" child element.
+ """
+ tag = self.getTag("query")
+ if tag:
+ return tag.getNamespace()
+
+ def getQuerynode(self):
+ """
+ Returns the "node" attribute value of the "query" child element.
+ """
+ return self.getTagAttr("query", "node")
+
+ def getQueryPayload(self):
+ """
+ Returns the "query" child element payload.
+ """
+ tag = self.getTag("query")
+ if tag:
+ return tag.getPayload()
+
+ def getQueryChildren(self):
+ """
+ Returns the "query" child element child nodes.
+ """
+ tag = self.getTag("query")
+ if tag:
+ return tag.getChildren()
+
+ def setQuery(self, name=None):
+ """
+ Changes the name of the query node, creates it if needed.
+ Keep the existing name if none is given (use "query" if it's a creation).
+ Returns the query node.
+ """
+ query = self.getQuery()
+ if query is None:
+ query = self.addChild("query")
+ if name is not None:
+ query.setName(name)
+ return query
+
+ def setQueryNS(self, namespace):
+ """
+ Set the namespace of the "query" child element.
+ """
+ self.setTag("query").setNamespace(namespace)
+
+ def setQueryPayload(self, payload):
+ """
+ Set the "query" child element payload.
+ """
+ self.setTag("query").setPayload(payload)
+
+ def setQuerynode(self, node):
+ """
+ Set the "node" attribute value of the "query" child element.
+ """
+ self.setTagAttr("query", "node", node)
+
+ def buildReply(self, typ):
+ """
+ Builds and returns another Iq object of specified type.
+ The to, from and query child node of new Iq are pre-set as reply to this Iq.
+ """
+ iq = Iq(typ, to=self.getFrom(), frm=self.getTo(), attrs={"id": self.getID()})
+ if self.getTag("query"):
+ iq.setQueryNS(self.getQueryNS())
+ if self.getTagAttr("query", "node"):
+ iq.setQuerynode(self.getQuerynode())
+ return iq
+
+class ErrorNode(Node):
+ """
+ XMPP-style error element.
+ In the case of stanza error should be attached to XMPP stanza.
+ In the case of stream-level errors should be used separately.
+ """
+ def __init__(self, name, code=None, typ=None, text=None):
+ """
+ Create new error node object.
+ Mandatory parameter: name - name of error condition.
+ Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol.
+ """
+ if name in ERRORS:
+ cod, type, txt = ERRORS[name]
+ ns = name.split()[0]
+ else:
+ cod, ns, type, txt = "500", NS_STANZAS, "cancel", ""
+ if typ:
+ type = typ
+ if code:
+ cod = code
+ if text:
+ txt = text
+ Node.__init__(self, "error", {}, [Node(name)])
+ if type:
+ self.setAttr("type", type)
+ if not cod:
+ self.setName("stream:error")
+ if txt:
+ self.addChild(node=Node(ns + " text", {}, [txt]))
+ if cod:
+ self.setAttr("code", cod)
+
+class Error(Protocol):
+ """
+ Used to quickly transform received stanza into error reply.
+ """
+ def __init__(self, node, error, reply=1):
+ """
+ Create error reply basing on the received "node" stanza and the "error" error condition.
+ If the "node" is not the received stanza but locally created ("to" and "from" fields needs not swapping)
+ specify the "reply" argument as false.
+ """
+ if reply:
+ Protocol.__init__(self, to=node.getFrom(), frm=node.getTo(), node=node)
+ else:
+ Protocol.__init__(self, node=node)
+ self.setError(error)
+ if node.getType() == "error":
+ self.__str__ = self.__dupstr__
+
+ def __dupstr__(self, dup1=None, dup2=None):
+ """
+ Dummy function used as preventor of creating error node in reply to error node.
+ I.e. you will not be able to serialize "double" error into string.
+ """
+ return ""
+
+class DataField(Node):
+ """
+ This class is used in the DataForm class to describe the single data item.
+ If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122)
+ then you will need to work with instances of this class.
+ """
+ def __init__(self, name=None, value=None, typ=None, required=0, label=None, desc=None, options=[], node=None):
+ """
+ Create new data field of specified name,value and type. Also "required", "desc" and "options" fields can be set.
+ Alternatively other XML object can be passed in as the "node" parameted to replicate it as a new datafiled.
+ """
+ Node.__init__(self, "field", node=node)
+ if name:
+ self.setVar(name)
+ if isinstance(value, (list, tuple)):
+ self.setValues(value)
+ elif value:
+ self.setValue(value)
+ if typ:
+ self.setType(typ)
+# elif not typ and not node:
+# self.setType("text-single")
+ if required:
+ self.setRequired(required)
+ if label:
+ self.setLabel(label)
+ if desc:
+ self.setDesc(desc)
+ if options:
+ self.setOptions(options)
+
+ def setRequired(self, req=1):
+ """
+ Change the state of the "required" flag.
+ """
+ if req:
+ self.setTag("required")
+ else:
+ try:
+ self.delChild("required")
+ except ValueError:
+ return None
+
+ def isRequired(self):
+ """
+ Returns in this field a required one.
+ """
+ return self.getTag("required")
+
+ def setLabel(self, label):
+ """
+ Set the label of this field.
+ """
+ self.setAttr("label", label)
+
+ def getLabel(self):
+ """
+ Return the label of this field.
+ """
+ return self.getAttr("label")
+
+ def setDesc(self, desc):
+ """
+ Set the description of this field.
+ """
+ self.setTagData("desc", desc)
+
+ def getDesc(self):
+ """
+ Return the description of this field.
+ """
+ return self.getTagData("desc")
+
+ def setValue(self, val):
+ """
+ Set the value of this field.
+ """
+ self.setTagData("value", val)
+
+ def getValue(self):
+ return self.getTagData("value")
+
+ def setValues(self, ls):
+ """
+ Set the values of this field as values-list.
+ Replaces all previous filed values! If you need to just add a value - use addValue method.
+ """
+ while self.getTag("value"):
+ self.delChild("value")
+ for val in ls:
+ self.addValue(val)
+
+ def addValue(self, val):
+ """
+ Add one more value to this field. Used in "get" iq's or such.
+ """
+ self.addChild("value", {}, [val])
+
+ def getValues(self):
+ """
+ Return the list of values associated with this field.
+ """
+ ret = []
+ for tag in self.getTags("value"):
+ ret.append(tag.getData())
+ return ret
+
+ def getOptions(self):
+ """
+ Return label-option pairs list associated with this field.
+ """
+ ret = []
+ for tag in self.getTags("option"):
+ ret.append([tag.getAttr("label"), tag.getTagData("value")])
+ return ret
+
+ def setOptions(self, ls):
+ """
+ Set label-option pairs list associated with this field.
+ """
+ while self.getTag("option"):
+ self.delChild("option")
+ for opt in ls:
+ self.addOption(opt)
+
+ def addOption(self, opt):
+ """
+ Add one more label-option pair to this field.
+ """
+ if isinstance(opt, basestring):
+ self.addChild("option").setTagData("value", opt)
+ else:
+ self.addChild("option", {"label": opt[0]}).setTagData("value", opt[1])
+
+ def getType(self):
+ """
+ Get type of this field.
+ """
+ return self.getAttr("type")
+
+ def setType(self, val):
+ """
+ Set type of this field.
+ """
+ return self.setAttr("type", val)
+
+ def getVar(self):
+ """
+ Get "var" attribute value of this field.
+ """
+ return self.getAttr("var")
+
+ def setVar(self, val):
+ """
+ Set "var" attribute value of this field.
+ """
+ return self.setAttr("var", val)
+
+class DataReported(Node):
+ """
+ This class is used in the DataForm class to describe the "reported data field" data items which are used in
+ "multiple item form results" (as described in XEP-0004).
+ Represents the fields that will be returned from a search. This information is useful when
+ you try to use the jabber:iq:search namespace to return dynamic form information.
+ """
+ def __init__(self, node=None):
+ """
+ Create new empty "reported data" field. However, note that, according XEP-0004:
+ * It MUST contain one or more DataFields.
+ * Contained DataFields SHOULD possess a "type" and "label" attribute in addition to "var" attribute
+ * Contained DataFields SHOULD NOT contain a <value/> element.
+ Alternatively other XML object can be passed in as the "node" parameted to replicate it as a new
+ dataitem.
+ """
+ Node.__init__(self, "reported", node=node)
+ if node:
+ newkids = []
+ for n in self.getChildren():
+ if n.getName() == "field":
+ newkids.append(DataField(node=n))
+ else:
+ newkids.append(n)
+ self.kids = newkids
+
+ def getField(self, name):
+ """
+ Return the datafield object with name "name" (if exists).
+ """
+ return self.getTag("field", attrs={"var": name})
+
+ def setField(self, name, typ=None, label=None, desc=None, options=[]):
+ """
+ Create if nessessary or get the existing datafield object with name "name" and return it.
+ If created, attributes "type" and "label" are applied to new datafield.
+ """
+ field = self.getField(name)
+ if not field:
+ field = self.addChild(node=DataField(name, None, typ, 0, label, desc=desc, options=options))
+ return field
+
+ def asDict(self):
+ """
+ Represent dataitem as simple dictionary mapping of datafield names to their values.
+ """
+ ret = {}
+ for field in self.getTags("field"):
+ name = field.getAttr("var")
+ typ = field.getType()
+ if isinstance(typ, basestring) and typ.endswith("-multi"):
+ val = []
+ for i in field.getTags("value"):
+ val.append(i.getData())
+ else:
+ val = field.getTagData("value")
+ ret[name] = val
+ if self.getTag("instructions"):
+ ret["instructions"] = self.getInstructions()
+ return ret
+
+ def __getitem__(self, name):
+ """
+ Simple dictionary interface for getting datafields values by their names.
+ """
+ item = self.getField(name)
+ if item:
+ return item.getValue()
+ raise IndexError("No such field")
+
+ def __setitem__(self, name, val):
+ """
+ Simple dictionary interface for setting datafields values by their names.
+ """
+ return self.setField(name).setValue(val)
+
+class DataItem(Node):
+ """
+ This class is used in the DataForm class to describe data items which are used in "multiple
+ item form results" (as described in XEP-0004).
+ """
+ def __init__(self, node=None):
+ """
+ Create new empty data item. However, note that, according XEP-0004, DataItem MUST contain ALL
+ DataFields described in DataReported.
+ Alternatively other XML object can be passed in as the "node" parameted to replicate it as a new
+ dataitem.
+ """
+ Node.__init__(self, "item", node=node)
+ if node:
+ newkids = []
+ for n in self.getChildren():
+ if n.getName() == "field":
+ newkids.append(DataField(node=n))
+ else:
+ newkids.append(n)
+ self.kids = newkids
+
+ def getField(self, name):
+ """
+ Return the datafield object with name "name" (if exists).
+ """
+ return self.getTag("field", attrs={"var": name})
+
+ def setField(self, name, value=None, typ=None, desc=None, options=[]):
+ """
+ Create if nessessary or get the existing datafield object with name "name" and return it.
+ """
+ field = self.getField(name)
+ if not field:
+ field = self.addChild(node=DataField(name, value, typ, desc=desc, options=options))
+ return field
+
+ def asDict(self):
+ """
+ Represent dataitem as simple dictionary mapping of datafield names to their values.
+ """
+ ret = {}
+ for field in self.getTags("field"):
+ name = field.getAttr("var")
+ typ = field.getType()
+ if isinstance(typ, basestring) and typ.endswith("-multi"):
+ val = []
+ for i in field.getTags("value"):
+ val.append(i.getData())
+ else:
+ val = field.getTagData("value")
+ ret[name] = val
+ if self.getTag("instructions"):
+ ret["instructions"] = self.getInstructions()
+ return ret
+
+ def __getitem__(self, name):
+ """
+ Simple dictionary interface for getting datafields values by their names.
+ """
+ item = self.getField(name)
+ if item:
+ return item.getValue()
+ raise IndexError("No such field")
+
+ def __setitem__(self, name, val):
+ """
+ Simple dictionary interface for setting datafields values by their names.
+ """
+ return self.setField(name).setValue(val)
+
+class DataForm(Node):
+ """
+ DataForm class. Used for manipulating dataforms in XMPP.
+ Relevant XEPs: 0004, 0068, 0122.
+ Can be used in disco, pub-sub and many other applications.
+ """
+ def __init__(self, typ=None, data=[], title=None, node=None):
+ """
+ Create new dataform of type "typ"; "data" is the list of DataReported,
+ DataItem and DataField instances that this dataform contains; "title"
+ is the title string.
+ You can specify the "node" argument as the other node to be used as
+ base for constructing this dataform.
+
+ Title and instructions is optional and SHOULD NOT contain newlines.
+ Several instructions MAY be present.
+ "typ" can be one of ("form" | "submit" | "cancel" | "result" )
+ "typ" of reply iq can be ( "result" | "set" | "set" | "result" ) respectively.
+ "cancel" form can not contain any fields. All other forms contains AT LEAST one field.
+ "title" MAY be included in forms of type "form" and "result".
+ """
+ Node.__init__(self, "x", node=node)
+ if node:
+ newkids = []
+ for n in self.getChildren():
+ if n.getName() == "field":
+ newkids.append(DataField(node=n))
+ elif n.getName() == "item":
+ newkids.append(DataItem(node=n))
+ elif n.getName() == "reported":
+ newkids.append(DataReported(node=n))
+ else:
+ newkids.append(n)
+ self.kids = newkids
+ if typ:
+ self.setType(typ)
+ self.setNamespace(NS_DATA)
+ if title:
+ self.setTitle(title)
+ if isinstance(data, dict):
+ newdata = []
+ for name in data.keys():
+ newdata.append(DataField(name, data[name]))
+ data = newdata
+ for child in data:
+ if isinstance(child, basestring):
+ self.addInstructions(child)
+ elif isinstance(child, DataField):
+ self.kids.append(child)
+ elif isinstance(child, DataItem):
+ self.kids.append(child)
+ elif isinstance(child, DataReported):
+ self.kids.append(child)
+ else:
+ self.kids.append(DataField(node=child))
+
+ def getType(self):
+ """
+ Return the type of dataform.
+ """
+ return self.getAttr("type")
+
+ def setType(self, typ):
+ """
+ Set the type of dataform.
+ """
+ self.setAttr("type", typ)
+
+ def getTitle(self):
+ """
+ Return the title of dataform.
+ """
+ return self.getTagData("title")
+
+ def setTitle(self, text):
+ """
+ Set the title of dataform.
+ """
+ self.setTagData("title", text)
+
+ def getInstructions(self):
+ """
+ Return the instructions of dataform.
+ """
+ return self.getTagData("instructions")
+
+ def setInstructions(self, text):
+ """
+ Set the instructions of dataform.
+ """
+ self.setTagData("instructions", text)
+
+ def addInstructions(self, text):
+ """
+ Add one more instruction to the dataform.
+ """
+ self.addChild("instructions", {}, [text])
+
+ def getField(self, name):
+ """
+ Return the datafield object with name "name" (if exists).
+ """
+ return self.getTag("field", attrs={"var": name})
+
+ def setField(self, name, value=None, typ=None, desc=None, options=[]):
+ """
+ Create if nessessary or get the existing datafield object with name "name" and return it.
+ """
+ field = self.getField(name)
+ if not field:
+ field = self.addChild(node=DataField(name, value, typ, desc=desc, options=options))
+ return field
+
+ def asDict(self):
+ """
+ Represent dataform as simple dictionary mapping of datafield names to their values.
+ """
+ ret = {}
+ for field in self.getTags("field"):
+ name = field.getAttr("var")
+ typ = field.getType()
+ if isinstance(typ, basestring) and typ.endswith("-multi"):
+ val = []
+ for i in field.getTags("value"):
+ val.append(i.getData())
+ else:
+ val = field.getTagData("value")
+ ret[name] = val
+ if self.getTag("instructions"):
+ ret["instructions"] = self.getInstructions()
+ return ret
+
+ def __getitem__(self, name):
+ """
+ Simple dictionary interface for getting datafields values by their names.
+ """
+ item = self.getField(name)
+ if item:
+ return item.getValue()
+ raise IndexError("No such field")
+
+ def __setitem__(self, name, val):
+ """
+ Simple dictionary interface for setting datafields values by their names.
+ """
+ return self.setField(name).setValue(val)
diff --git a/xmpp/roster.py b/xmpp/roster.py
index b91000e..5d4209a 100644
--- a/xmpp/roster.py
+++ b/xmpp/roster.py
@@ -1,282 +1,282 @@
-## roster.py
-##
-## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2, or (at your option)
-## any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-
-# $Id: roster.py, v1.21 2013/10/21 alkorgun Exp $
-
-"""
-Simple roster implementation. Can be used though for different tasks like
-mass-renaming of contacts.
-"""
-
-from .plugin import PlugIn
-from .protocol import *
-
-class Roster(PlugIn):
- """
- Defines a plenty of methods that will allow you to manage roster.
- Also automatically track presences from remote JIDs taking into
- account that every JID can have multiple resources connected. Does not
- currently support "error" presences.
- You can also use mapping interface for access to the internal representation of
- contacts in roster.
- """
- def __init__(self):
- """
- Init internal variables.
- """
- PlugIn.__init__(self)
- self.DBG_LINE = "roster"
- self._data = {}
- self.set = None
- self._exported_methods = [self.getRoster]
-
- def plugin(self, owner, request=1):
- """
- Register presence and subscription trackers in the owner's dispatcher.
- Also request roster from server if the "request" argument is set.
- Used internally.
- """
- self._owner.RegisterHandler("iq", self.RosterIqHandler, "result", NS_ROSTER)
- self._owner.RegisterHandler("iq", self.RosterIqHandler, "set", NS_ROSTER)
- self._owner.RegisterHandler("presence", self.PresenceHandler)
- if request:
- self.Request()
-
- def Request(self, force=0):
- """
- Request roster from server if it were not yet requested
- (or if the "force" argument is set).
- """
- if self.set is None:
- self.set = 0
- elif not force:
- return None
- self._owner.send(Iq("get", NS_ROSTER))
- self.DEBUG("Roster requested from server", "start")
-
- def getRoster(self):
- """
- Requests roster from server if neccessary and returns self.
- """
- if not self.set:
- self.Request()
- while not self.set:
- self._owner.Process(10)
- return self
-
- def RosterIqHandler(self, dis, stanza):
- """
- Subscription tracker. Used internally for setting items state in
- internal roster representation.
- """
- if not stanza or not stanza.getTag("query"):
- raise NodeProcessed()
- for item in stanza.getTag("query").getTags("item"):
- jid = item.getAttr("jid")
- if item.getAttr("subscription") == "remove":
- if jid in self._data:
- del self._data[jid]
- raise NodeProcessed() # a MUST
- self.DEBUG("Setting roster item %s..." % jid, "ok")
- if jid not in self._data:
- self._data[jid] = {}
- self._data[jid]["name"] = item.getAttr("name")
- self._data[jid]["ask"] = item.getAttr("ask")
- self._data[jid]["subscription"] = item.getAttr("subscription")
- self._data[jid]["groups"] = []
- if "resources" not in self._data[jid]:
- self._data[jid]["resources"] = {}
- for group in item.getTags("group"):
- self._data[jid]["groups"].append(group.getData())
- self._data["@".join((self._owner.User, self._owner.Server))] = {"resources": {}, "name": None, "ask": None, "subscription": None, "groups": None, }
- self.set = 1
- raise NodeProcessed() # a MUST. Otherwise you'll get back an <iq type='error'/>
-
- def PresenceHandler(self, dis, pres):
- """
- Presence tracker. Used internally for setting items' resources state in
- internal roster representation.
- """
- jid = JID(pres.getFrom())
- if jid.getStripped() not in self._data:
- self._data[jid.getStripped()] = {"name": None, "ask": None, "subscription": "none", "groups": ["Not in roster"], "resources": {}}
- item = self._data[jid.getStripped()]
- typ = pres.getType()
- if not typ:
- self.DEBUG("Setting roster item %s for resource %s..." % (jid.getStripped(), jid.getResource()), "ok")
- item["resources"][jid.getResource()] = res = {"show": None, "status": None, "priority": "0", "timestamp": None}
- if pres.getTag("show"):
- res["show"] = pres.getShow()
- if pres.getTag("status"):
- res["status"] = pres.getStatus()
- if pres.getTag("priority"):
- res["priority"] = pres.getPriority()
- if not pres.getTimestamp():
- pres.setTimestamp()
- res["timestamp"] = pres.getTimestamp()
- elif typ == "unavailable" and jid.getResource() in item["resources"]:
- del item["resources"][jid.getResource()]
- # Need to handle type="error" also
-
- def _getItemData(self, jid, dataname):
- """
- Return specific jid's representation in internal format. Used internally.
- """
- jid = jid[:(jid + "/").find("/")]
- return self._data[jid][dataname]
-
- def _getResourceData(self, jid, dataname):
- """
- Return specific jid's resource representation in internal format. Used internally.
- """
- if jid.find("/") + 1:
- jid, resource = jid.split("/", 1)
- if resource in self._data[jid]["resources"]:
- return self._data[jid]["resources"][resource][dataname]
- elif self._data[jid]["resources"].keys():
- lastpri = -129
- for r in self._data[jid]["resources"].keys():
- if int(self._data[jid]["resources"][r]["priority"]) > lastpri:
- resource, lastpri = r, int(self._data[jid]["resources"][r]["priority"])
- return self._data[jid]["resources"][resource][dataname]
-
- def delItem(self, jid):
- """
- Delete contact "jid" from roster.
- """
- self._owner.send(Iq("set", NS_ROSTER, payload=[Node("item", {"jid": jid, "subscription": "remove"})]))
-
- def getAsk(self, jid):
- """
- Returns "ask" value of contact "jid".
- """
- return self._getItemData(jid, "ask")
-
- def getGroups(self, jid):
- """
- Returns groups list that contact "jid" belongs to.
- """
- return self._getItemData(jid, "groups")
-
- def getName(self, jid):
- """
- Returns name of contact "jid".
- """
- return self._getItemData(jid, "name")
-
- def getPriority(self, jid):
- """
- Returns priority of contact "jid". "jid" should be a full (not bare) JID.
- """
- return self._getResourceData(jid, "priority")
-
- def getRawRoster(self):
- """
- Returns roster representation in internal format.
- """
- return self._data
-
- def getRawItem(self, jid):
- """
- Returns roster item "jid" representation in internal format.
- """
- return self._data[jid[:(jid + "/").find("/")]]
-
- def getShow(self, jid):
- """
- Returns "show" value of contact "jid". "jid" should be a full (not bare) JID.
- """
- return self._getResourceData(jid, "show")
-
- def getStatus(self, jid):
- """
- Returns "status" value of contact "jid". "jid" should be a full (not bare) JID.
- """
- return self._getResourceData(jid, "status")
-
- def getSubscription(self, jid):
- """
- Returns "subscription" value of contact "jid".
- """
- return self._getItemData(jid, "subscription")
-
- def getResources(self, jid):
- """
- Returns list of connected resources of contact "jid".
- """
- return self._data[jid[:(jid + "/").find("/")]]["resources"].keys()
-
- def setItem(self, jid, name=None, groups=[]):
- """
- Creates/renames contact "jid" and sets the groups list that it now belongs to.
- """
- iq = Iq("set", NS_ROSTER)
- query = iq.getTag("query")
- attrs = {"jid": jid}
- if name:
- attrs["name"] = name
- item = query.setTag("item", attrs)
- for group in groups:
- item.addChild(node=Node("group", payload=[group]))
- self._owner.send(iq)
-
- def getItems(self):
- """
- Return list of all [bare] JIDs that the roster is currently tracks.
- """
- return self._data.keys()
-
- def keys(self):
- """
- Same as getItems. Provided for the sake of dictionary interface.
- """
- return self._data.keys()
-
- def __getitem__(self, item):
- """
- Get the contact in the internal format. Raises KeyError if JID "item" is not in roster.
- """
- return self._data[item]
-
- def getItem(self, item):
- """
- Get the contact in the internal format (or None if JID "item" is not in roster).
- """
- if item in self._data:
- return self._data[item]
-
- def Subscribe(self, jid):
- """
- Send subscription request to JID "jid".
- """
- self._owner.send(Presence(jid, "subscribe"))
-
- def Unsubscribe(self, jid):
- """
- Ask for removing our subscription for JID "jid".
- """
- self._owner.send(Presence(jid, "unsubscribe"))
-
- def Authorize(self, jid):
- """
- Authorise JID "jid". Works only if these JID requested auth previously.
- """
- self._owner.send(Presence(jid, "subscribed"))
-
- def Unauthorize(self, jid):
- """
- Unauthorise JID "jid". Use for declining authorisation request
- or for removing existing authorization.
- """
- self._owner.send(Presence(jid, "unsubscribed"))
+## roster.py
+##
+## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2, or (at your option)
+## any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+# $Id: roster.py, v1.21 2013/10/21 alkorgun Exp $
+
+"""
+Simple roster implementation. Can be used though for different tasks like
+mass-renaming of contacts.
+"""
+
+from .plugin import PlugIn
+from .protocol import *
+
+class Roster(PlugIn):
+ """
+ Defines a plenty of methods that will allow you to manage roster.
+ Also automatically track presences from remote JIDs taking into
+ account that every JID can have multiple resources connected. Does not
+ currently support "error" presences.
+ You can also use mapping interface for access to the internal representation of
+ contacts in roster.
+ """
+ def __init__(self):
+ """
+ Init internal variables.
+ """
+ PlugIn.__init__(self)
+ self.DBG_LINE = "roster"
+ self._data = {}
+ self.set = None
+ self._exported_methods = [self.getRoster]
+
+ def plugin(self, owner, request=1):
+ """
+ Register presence and subscription trackers in the owner's dispatcher.
+ Also request roster from server if the "request" argument is set.
+ Used internally.
+ """
+ self._owner.RegisterHandler("iq", self.RosterIqHandler, "result", NS_ROSTER)
+ self._owner.RegisterHandler("iq", self.RosterIqHandler, "set", NS_ROSTER)
+ self._owner.RegisterHandler("presence", self.PresenceHandler)
+ if request:
+ self.Request()
+
+ def Request(self, force=0):
+ """
+ Request roster from server if it were not yet requested
+ (or if the "force" argument is set).
+ """
+ if self.set is None:
+ self.set = 0
+ elif not force:
+ return None
+ self._owner.send(Iq("get", NS_ROSTER))
+ self.DEBUG("Roster requested from server", "start")
+
+ def getRoster(self):
+ """
+ Requests roster from server if neccessary and returns self.
+ """
+ if not self.set:
+ self.Request()
+ while not self.set:
+ self._owner.Process(10)
+ return self
+
+ def RosterIqHandler(self, dis, stanza):
+ """
+ Subscription tracker. Used internally for setting items state in
+ internal roster representation.
+ """
+ if not stanza or not stanza.getTag("query"):
+ raise NodeProcessed()
+ for item in stanza.getTag("query").getTags("item"):
+ jid = item.getAttr("jid")
+ if item.getAttr("subscription") == "remove":
+ if jid in self._data:
+ del self._data[jid]
+ raise NodeProcessed() # a MUST
+ self.DEBUG("Setting roster item %s..." % jid, "ok")
+ if jid not in self._data:
+ self._data[jid] = {}
+ self._data[jid]["name"] = item.getAttr("name")
+ self._data[jid]["ask"] = item.getAttr("ask")
+ self._data[jid]["subscription"] = item.getAttr("subscription")
+ self._data[jid]["groups"] = []
+ if "resources" not in self._data[jid]:
+ self._data[jid]["resources"] = {}
+ for group in item.getTags("group"):
+ self._data[jid]["groups"].append(group.getData())
+ self._data["@".join((self._owner.User, self._owner.Server))] = {"resources": {}, "name": None, "ask": None, "subscription": None, "groups": None, }
+ self.set = 1
+ raise NodeProcessed() # a MUST. Otherwise you'll get back an <iq type='error'/>
+
+ def PresenceHandler(self, dis, pres):
+ """
+ Presence tracker. Used internally for setting items' resources state in
+ internal roster representation.
+ """
+ jid = JID(pres.getFrom())
+ if jid.getStripped() not in self._data:
+ self._data[jid.getStripped()] = {"name": None, "ask": None, "subscription": "none", "groups": ["Not in roster"], "resources": {}}
+ item = self._data[jid.getStripped()]
+ typ = pres.getType()
+ if not typ:
+ self.DEBUG("Setting roster item %s for resource %s..." % (jid.getStripped(), jid.getResource()), "ok")
+ item["resources"][jid.getResource()] = res = {"show": None, "status": None, "priority": "0", "timestamp": None}
+ if pres.getTag("show"):
+ res["show"] = pres.getShow()
+ if pres.getTag("status"):
+ res["status"] = pres.getStatus()
+ if pres.getTag("priority"):
+ res["priority"] = pres.getPriority()
+ if not pres.getTimestamp():
+ pres.setTimestamp()
+ res["timestamp"] = pres.getTimestamp()
+ elif typ == "unavailable" and jid.getResource() in item["resources"]:
+ del item["resources"][jid.getResource()]
+ # Need to handle type="error" also
+
+ def _getItemData(self, jid, dataname):
+ """
+ Return specific jid's representation in internal format. Used internally.
+ """
+ jid = jid[:(jid + "/").find("/")]
+ return self._data[jid][dataname]
+
+ def _getResourceData(self, jid, dataname):
+ """
+ Return specific jid's resource representation in internal format. Used internally.
+ """
+ if jid.find("/") + 1:
+ jid, resource = jid.split("/", 1)
+ if resource in self._data[jid]["resources"]:
+ return self._data[jid]["resources"][resource][dataname]
+ elif self._data[jid]["resources"].keys():
+ lastpri = -129
+ for r in self._data[jid]["resources"].keys():
+ if int(self._data[jid]["resources"][r]["priority"]) > lastpri:
+ resource, lastpri = r, int(self._data[jid]["resources"][r]["priority"])
+ return self._data[jid]["resources"][resource][dataname]
+
+ def delItem(self, jid):
+ """
+ Delete contact "jid" from roster.
+ """
+ self._owner.send(Iq("set", NS_ROSTER, payload=[Node("item", {"jid": jid, "subscription": "remove"})]))
+
+ def getAsk(self, jid):
+ """
+ Returns "ask" value of contact "jid".
+ """
+ return self._getItemData(jid, "ask")
+
+ def getGroups(self, jid):
+ """
+ Returns groups list that contact "jid" belongs to.
+ """
+ return self._getItemData(jid, "groups")
+
+ def getName(self, jid):
+ """
+ Returns name of contact "jid".
+ """
+ return self._getItemData(jid, "name")
+
+ def getPriority(self, jid):
+ """
+ Returns priority of contact "jid". "jid" should be a full (not bare) JID.
+ """
+ return self._getResourceData(jid, "priority")
+
+ def getRawRoster(self):
+ """
+ Returns roster representation in internal format.
+ """
+ return self._data
+
+ def getRawItem(self, jid):
+ """
+ Returns roster item "jid" representation in internal format.
+ """
+ return self._data[jid[:(jid + "/").find("/")]]
+
+ def getShow(self, jid):
+ """
+ Returns "show" value of contact "jid". "jid" should be a full (not bare) JID.
+ """
+ return self._getResourceData(jid, "show")
+
+ def getStatus(self, jid):
+ """
+ Returns "status" value of contact "jid". "jid" should be a full (not bare) JID.
+ """
+ return self._getResourceData(jid, "status")
+
+ def getSubscription(self, jid):
+ """
+ Returns "subscription" value of contact "jid".
+ """
+ return self._getItemData(jid, "subscription")
+
+ def getResources(self, jid):
+ """
+ Returns list of connected resources of contact "jid".
+ """
+ return self._data[jid[:(jid + "/").find("/")]]["resources"].keys()
+
+ def setItem(self, jid, name=None, groups=[]):
+ """
+ Creates/renames contact "jid" and sets the groups list that it now belongs to.
+ """
+ iq = Iq("set", NS_ROSTER)
+ query = iq.getTag("query")
+ attrs = {"jid": jid}
+ if name:
+ attrs["name"] = name
+ item = query.setTag("item", attrs)
+ for group in groups:
+ item.addChild(node=Node("group", payload=[group]))
+ self._owner.send(iq)
+
+ def getItems(self):
+ """
+ Return list of all [bare] JIDs that the roster is currently tracks.
+ """
+ return self._data.keys()
+
+ def keys(self):
+ """
+ Same as getItems. Provided for the sake of dictionary interface.
+ """
+ return self._data.keys()
+
+ def __getitem__(self, item):
+ """
+ Get the contact in the internal format. Raises KeyError if JID "item" is not in roster.
+ """
+ return self._data[item]
+
+ def getItem(self, item):
+ """
+ Get the contact in the internal format (or None if JID "item" is not in roster).
+ """
+ if item in self._data:
+ return self._data[item]
+
+ def Subscribe(self, jid):
+ """
+ Send subscription request to JID "jid".
+ """
+ self._owner.send(Presence(jid, "subscribe"))
+
+ def Unsubscribe(self, jid):
+ """
+ Ask for removing our subscription for JID "jid".
+ """
+ self._owner.send(Presence(jid, "unsubscribe"))
+
+ def Authorize(self, jid):
+ """
+ Authorise JID "jid". Works only if these JID requested auth previously.
+ """
+ self._owner.send(Presence(jid, "subscribed"))
+
+ def Unauthorize(self, jid):
+ """
+ Unauthorise JID "jid". Use for declining authorisation request
+ or for removing existing authorization.
+ """
+ self._owner.send(Presence(jid, "unsubscribed"))
diff --git a/xmpp/transports.py b/xmpp/transports.py
index 471208e..29ce128 100644
--- a/xmpp/transports.py
+++ b/xmpp/transports.py
@@ -1,479 +1,479 @@
-## transports.py
-##
-## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2, or (at your option)
-## any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-## GNU General Public License for more details.
-
-# $Id: transports.py, v1.38 2014/02/16 alkorgun Exp $
-
-"""
-This module contains the low-level implementations of xmpppy connect methods or
-(in other words) transports for xmpp-stanzas.
-Currently here is three transports:
-direct TCP connect - TCPsocket class
-proxied TCP connect - HTTPPROXYsocket class (CONNECT proxies)
-TLS connection - TLS class. Can be used for SSL connections also.
-
-Transports are stackable so you - f.e. TLS use HTPPROXYsocket or TCPsocket as more low-level transport.
-
-Also exception 'error' is defined to allow capture of this module specific exceptions.
-"""
-
-import sys
-import socket
-if sys.hexversion >= 0x20600F0:
- import ssl
-import thread
-import time
-from . import dispatcher
-
-from base64 import encodestring
-from select import select
-from .simplexml import ustr
-from .plugin import PlugIn
-from .protocol import *
-
-# http://pydns.sourceforge.net
-try:
- import DNS as dns
-except ImportError:
- dns = None
-
-DATA_RECEIVED = 'DATA RECEIVED'
-DATA_SENT = 'DATA SENT'
-DBG_CONNECT_PROXY = 'CONNECTproxy'
-
-BUFLEN = 8192
-SEND_INTERVAL = 0
-
-TCP_KEEPINTVL = 60
-TCP_KEEPIDLE = 60
-
-
-class SendSemaphore(object):
-
- def __init__(self):
- self.__lock = thread.allocate_lock()
- self.__released = 0
- self.interval = SEND_INTERVAL
-
- def set_send_interval(self, interval):
- self.interval = interval
-
- def acquire(self, blocking=1):
- rc = self.__lock.acquire(blocking)
- if blocking and self.interval:
- elapsed = time.time() - self.__released
- if elapsed < self.interval:
- time.sleep(self.interval - elapsed)
- return rc
-
- __enter__ = acquire
-
- def release(self):
- self.__released = time.time()
- self.__lock.release()
-
- def __exit__(self, *args):
- self.release()
-
-
-class error:
- """
- An exception to be raised in case of low-level errors in methods of 'transports' module.
- """
- def __init__(self, comment):
- """
- Cache the descriptive string.
- """
- self._comment = comment
-
- def __str__(self):
- """
- Serialize exception into pre-cached descriptive string.
- """
- return self._comment
-
-
-def configureSocket(sock):
- # see man(7) tcp
- try:
- # enable keepalive probes
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
- # the interval between subsequential keepalive probes, regardless of what the connection has exchanged in the meantime
- # overrides tcp_keepalive_intvl
- sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, TCP_KEEPINTVL)
- # the interval between the last data packet sent (simple ACKs are not considered data) and the first keepalive probe;
- # after the connection is marked to need keepalive, this counter is not used any further
- # overrides tcp_keepalive_time
- sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, TCP_KEEPIDLE)
- except (AttributeError, OSError):
- pass
-
-
-class TCPsocket(PlugIn):
- """
- This class defines direct TCP connection method.
- """
- def __init__(self, server=None, use_srv=True):
- """
- Cache connection point 'server'. 'server' is the tuple of (host, port)
- absolutely the same as standard tcp socket uses. However library will lookup for
- ('_xmpp-client._tcp.' + host) SRV record in DNS and connect to the found (if it is)
- server instead.
- """
- PlugIn.__init__(self)
- self.DBG_LINE = "socket"
- self._sequence = SendSemaphore()
- self.set_send_interval = self._sequence.set_send_interval
- self._exported_methods = [self.send, self.disconnect, self.set_send_interval]
- self._server, self.use_srv = server, use_srv
-
- def srv_lookup(self, server):
- """
- SRV resolver. Takes server=(host, port) as argument. Returns new (host, port) pair.
- """
- if dns:
- query = "_xmpp-client._tcp.%s" % server[0]
- try:
- dns.DiscoverNameServers()
- dns__ = dns.Request()
- response = dns__.req(query, qtype="SRV")
- if response.answers:
- # Sort by priority, according to RFC 2782.
- answers = sorted(response.answers, key=lambda a: a["data"][0])
- (port, host) = answers[0]["data"][2:][:2]
- server = str(host), int(port)
- except dns.DNSError:
- self.DEBUG("An error occurred while looking up %s." % query, "warn")
- return server
-
- def plugin(self, owner):
- """
- Fire up connection. Return non-empty string on success.
- Also registers self.disconnected method in the owner's dispatcher.
- Called internally.
- """
- if not self._server:
- self._server = (self._owner.Server, 5222)
- if self.use_srv:
- server = self.srv_lookup(self._server)
- else:
- server = self._server
- if not self.connect(server):
- return None
- self._owner.Connection = self
- self._owner.RegisterDisconnectHandler(self.disconnected)
- return "ok"
-
- def getHost(self):
- """
- Returns the 'host' value that is connection is [will be] made to.
- """
- return self._server[0]
-
- def getPort(self):
- """
- Returns the 'port' value that is connection is [will be] made to.
- """
- return self._server[1]
-
- def connect(self, server=None):
- """
- Try to connect to the given host/port.
- Returns non-empty string on success.
- """
- if not server:
- server = self._server
- host, port = server
- socktype = socket.SOCK_STREAM
- try:
- lookup = reversed(socket.getaddrinfo(host, int(port), 0, socktype))
- except Exception:
- addr = (host, int(port))
- if ":" in host:
- af = socket.AF_INET6
- addr = addr.__add__((0, 0))
- else:
- af = socket.AF_INET
- lookup = [(af, socktype, 1, 6, addr)]
- for af, socktype, proto, cn, addr in lookup:
- try:
- self._sock = socket.socket(af, socktype)
- self._sock.connect(addr)
- self._send = self._sock.sendall
- self._recv = self._sock.recv
- except socket.error as error:
- if getattr(self, "_sock", None):
- self._sock.close()
- try:
- code, error = error
- except Exception:
- code = -1
- self.DEBUG("Failed to connect to remote host %s: %s (%s)" % (repr(server), error, code), "error")
- except Exception:
- pass
- else:
- configureSocket(self._sock)
- self.DEBUG("Successfully connected to remote host %s." % repr(server), "start")
- return "ok"
-
- def plugout(self):
- """
- Disconnect from the remote server and unregister self.disconnected method from
- the owner's dispatcher.
- """
- if getattr(self, "_sock", None):
- self._sock.close()
- if hasattr(self._owner, "Connection"):
- del self._owner.Connection
- self._owner.UnregisterDisconnectHandler(self.disconnected)
-
- def receive(self):
- """
- Reads all pending incoming data.
- In case of disconnection calls owner's disconnected() method and then raises IOError exception.
- """
- try:
- data = self._recv(BUFLEN)
- except socket.sslerror as e:
- self._seen_data = 0
- if e[0] in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
- return ""
- self.DEBUG("Socket error while receiving data.", "error")
- sys.exc_clear()
- self._owner.disconnected()
- raise IOError("Disconnected!")
- except Exception:
- data = ""
- while self.pending_data(0):
- try:
- add = self._recv(BUFLEN)
- except Exception:
- break
- if not add:
- break
- data += add
- if data:
- self._seen_data = 1
- self.DEBUG(data, "got")
- if hasattr(self._owner, "Dispatcher"):
- self._owner.Dispatcher.Event("", DATA_RECEIVED, data)
- else:
- self.DEBUG("Socket error while receiving data.", "error")
- sys.exc_clear()
- self._owner.disconnected()
- raise IOError("Disconnected!")
- return data
-
- def send(self, data):
- """
- Writes raw outgoing data. Blocks until done.
- If supplied data is unicode string, encodes it to utf-8 before send.
- """
- if isinstance(data, unicode):
- data = data.encode("utf-8")
- elif not isinstance(data, str):
- data = ustr(data).encode("utf-8")
- with self._sequence:
- try:
- self._send(data)
- except (Exception, socket.error):
- self.DEBUG("Socket error while sending data.", "error")
- self._owner.disconnected()
- else:
- if not data.strip():
- data = repr(data)
- self.DEBUG(data, "sent")
- if hasattr(self._owner, "Dispatcher"):
- self._owner.Dispatcher.Event("", DATA_SENT, data)
-
- def pending_data(self, timeout=0):
- """
- Returns true if there is a data ready to be read.
- """
- return select([self._sock], [], [], timeout)[0]
-
- def disconnect(self):
- """
- Closes the socket.
- """
- self.DEBUG("Closing socket.", "stop")
- self._sock.close()
-
- def disconnected(self):
- """
- Called when a Network Error or disconnection occurs.
- Designed to be overidden.
- """
- self.DEBUG("Socket operation failed.", "error")
-
-class HTTPPROXYsocket(TCPsocket):
- """
- HTTP (CONNECT) proxy connection class. Uses TCPsocket as the base class
- redefines only connect method. Allows to use HTTP proxies like squid with
- (optionally) simple authentication (using login and password).
- """
- def __init__(self, proxy, server, use_srv=True):
- """
- Caches proxy and target addresses.
- 'proxy' argument is a dictionary with mandatory keys 'host' and 'port' (proxy address)
- and optional keys 'user' and 'password' to use for authentication.
- 'server' argument is a tuple of host and port - just like TCPsocket uses.
- """
- TCPsocket.__init__(self, server, use_srv)
- self.DBG_LINE = DBG_CONNECT_PROXY
- self._proxy = proxy
-
- def plugin(self, owner):
- """
- Starts connection. Used interally. Returns non-empty string on success.
- """
- owner.debug_flags.append(DBG_CONNECT_PROXY)
- return TCPsocket.plugin(self, owner)
-
- def connect(self, dupe=None):
- """
- Starts connection. Connects to proxy, supplies login and password to it
- (if were specified while creating instance). Instructs proxy to make
- connection to the target server. Returns non-empty sting on success.
- """
- if not TCPsocket.connect(self, (self._proxy["host"], self._proxy["port"])):
- return None
- self.DEBUG("Proxy server contacted, performing authentification.", "start")
- connector = [
- "CONNECT %s:%s HTTP/1.0" % self._server,
- "Proxy-Connection: Keep-Alive",
- "Pragma: no-cache",
- "Host: %s:%s" % self._server,
- "User-Agent: HTTPPROXYsocket/v0.1"
- ]
- if "user" in self._proxy and "password" in self._proxy:
- credentials = "%s:%s" % (self._proxy["user"], self._proxy["password"])
- credentials = encodestring(credentials).strip()
- connector.append("Proxy-Authorization: Basic " + credentials)
- connector.append("\r\n")
- self.send("\r\n".join(connector))
- try:
- reply = self.receive().replace("\r", "")
- except IOError:
- self.DEBUG("Proxy suddenly disconnected.", "error")
- self._owner.disconnected()
- return None
- try:
- proto, code, desc = reply.split("\n")[0].split(" ", 2)
- except Exception:
- raise error("Invalid proxy reply")
- if code != "200":
- self.DEBUG("Invalid proxy reply: %s %s %s" % (proto, code, desc), "error")
- self._owner.disconnected()
- return None
- while reply.find("\n\n") == -1:
- try:
- reply += self.receive().replace("\r", "")
- except IOError:
- self.DEBUG("Proxy suddenly disconnected.", "error")
- self._owner.disconnected()
- return None
- self.DEBUG("Authentification successfull. Jabber server contacted.", "ok")
- return "ok"
-
- def DEBUG(self, text, severity):
- """
- Overwrites DEBUG tag to allow debug output be presented as 'CONNECTproxy'.
- """
- return self._owner.DEBUG(DBG_CONNECT_PROXY, text, severity)
-
-class TLS(PlugIn):
- """
- TLS connection used to encrypts already estabilished tcp connection.
- """
- def PlugIn(self, owner, now=0):
- """
- If the 'now' argument is true then starts using encryption immidiatedly.
- If 'now' in false then starts encryption as soon as TLS feature is
- declared by the server (if it were already declared - it is ok).
- """
- if hasattr(owner, "TLS"):
- return None
- PlugIn.PlugIn(self, owner)
- DBG_LINE = "TLS"
- if now:
- return self._startSSL()
- if self._owner.Dispatcher.Stream.features:
- try:
- self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
- except NodeProcessed:
- pass
- else:
- self._owner.RegisterHandlerOnce("features", self.FeaturesHandler, xmlns=NS_STREAMS)
- self.starttls = None
-
- def plugout(self, now=0):
- """
- Unregisters TLS handler's from owner's dispatcher. Take note that encription
- can not be stopped once started. You can only break the connection and start over.
- """
- self._owner.UnregisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS)
- self._owner.UnregisterHandler("proceed", self.StartTLSHandler, xmlns=NS_TLS)
- self._owner.UnregisterHandler("failure", self.StartTLSHandler, xmlns=NS_TLS)
-
- def FeaturesHandler(self, conn, feats):
- """
- Used to analyse server <features/> tag for TLS support.
- If TLS is supported starts the encryption negotiation. Used internally.
- """
- if not feats.getTag("starttls", namespace=NS_TLS):
- self.DEBUG("TLS unsupported by remote server.", "warn")
- return None
- self.DEBUG("TLS supported by remote server. Requesting TLS start.", "ok")
- self._owner.RegisterHandlerOnce("proceed", self.StartTLSHandler, xmlns=NS_TLS)
- self._owner.RegisterHandlerOnce("failure", self.StartTLSHandler, xmlns=NS_TLS)
- self._owner.Connection.send("<starttls xmlns=\"%s\"/>" % NS_TLS)
- raise NodeProcessed()
-
- def pending_data(self, timeout=0):
- """
- Returns true if there possible is a data ready to be read.
- """
- return self._tcpsock._seen_data or select([self._tcpsock._sock], [], [], timeout)[0]
-
- def _startSSL(self):
- tcpsock = self._owner.Connection
- if sys.hexversion >= 0x20600F0:
- tcpsock._sslObj = ssl.wrap_socket(tcpsock._sock, None, None)
- else:
- tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
- tcpsock._sslIssuer = tcpsock._sslObj.issuer()
- tcpsock._sslServer = tcpsock._sslObj.server()
- tcpsock._recv = tcpsock._sslObj.read
- tcpsock._send = tcpsock._sslObj.write
- tcpsock._seen_data = 1
- self._tcpsock = tcpsock
- tcpsock.pending_data = self.pending_data
- tcpsock._sock.setblocking(0)
- self.starttls = "success"
-
- def StartTLSHandler(self, conn, starttls):
- """
- Handle server reply if TLS is allowed to process. Behaves accordingly.
- Used internally.
- """
- if starttls.getNamespace() != NS_TLS:
- return None
- self.starttls = starttls.getName()
- if self.starttls == "failure":
- self.DEBUG("Got starttls response: " + self.starttls, "error")
- return None
- self.DEBUG("Got starttls proceed response. Switching to TLS/SSL...", "ok")
- self._startSSL()
- self._owner.Dispatcher.PlugOut()
- dispatcher.Dispatcher().PlugIn(self._owner)
+## transports.py
+##
+## Copyright (C) 2003-2004 Alexey "Snake" Nezhdanov
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2, or (at your option)
+## any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+
+# $Id: transports.py, v1.38 2014/02/16 alkorgun Exp $
+
+"""
+This module contains the low-level implementations of xmpppy connect methods or
+(in other words) transports for xmpp-stanzas.
+Currently here is three transports:
+direct TCP connect - TCPsocket class
+proxied TCP connect - HTTPPROXYsocket class (CONNECT proxies)
+TLS connection - TLS class. Can be used for SSL connections also.
+
+Transports are stackable so you - f.e. TLS use HTPPROXYsocket or TCPsocket as more low-level transport.
+
+Also exception 'error' is defined to allow capture of this module specific exceptions.
+"""
+
+import sys
+import socket
+if sys.hexversion >= 0x20600F0:
+ import ssl
+import thread
+import time
+from . import dispatcher
+
+from base64 import encodestring
+from select import select
+from .simplexml import ustr
+from .plugin import PlugIn
+from .protocol import *
+
+# http://pydns.sourceforge.net
+try:
+ import DNS as dns
+except ImportError:
+ dns = None
+
+DATA_RECEIVED = 'DATA RECEIVED'
+DATA_SENT = 'DATA SENT'
+DBG_CONNECT_PROXY = 'CONNECTproxy'
+
+BUFLEN = 8192
+SEND_INTERVAL = 0
+
+TCP_KEEPINTVL = 60
+TCP_KEEPIDLE = 60
+
+
+class SendSemaphore(object):
+
+ def __init__(self):
+ self.__lock = thread.allocate_lock()
+ self.__released = 0
+ self.interval = SEND_INTERVAL
+
+ def set_send_interval(self, interval):
+ self.interval = interval
+
+ def acquire(self, blocking=1):
+ rc = self.__lock.acquire(blocking)
+ if blocking and self.interval:
+ elapsed = time.time() - self.__released
+ if elapsed < self.interval:
+ time.sleep(self.interval - elapsed)
+ return rc
+
+ __enter__ = acquire
+
+ def release(self):
+ self.__released = time.time()
+ self.__lock.release()
+
+ def __exit__(self, *args):
+ self.release()
+
+
+class error:
+ """
+ An exception to be raised in case of low-level errors in methods of 'transports' module.
+ """
+ def __init__(self, comment):
+ """
+ Cache the descriptive string.
+ """
+ self._comment = comment
+
+ def __str__(self):
+ """
+ Serialize exception into pre-cached descriptive string.
+ """
+ return self._comment
+
+
+def configureSocket(sock):
+ # see man(7) tcp
+ try:
+ # enable keepalive probes
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+ # the interval between subsequential keepalive probes, regardless of what the connection has exchanged in the meantime
+ # overrides tcp_keepalive_intvl
+ sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, TCP_KEEPINTVL)
+ # the interval between the last data packet sent (simple ACKs are not considered data) and the first keepalive probe;
+ # after the connection is marked to need keepalive, this counter is not used any further
+ # overrides tcp_keepalive_time
+ sock.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, TCP_KEEPIDLE)
+ except (AttributeError, OSError):
+ pass
+
+
+class TCPsocket(PlugIn):
+ """
+ This class defines direct TCP connection method.
+ """
+ def __init__(self, server=None, use_srv=True):
+ """
+ Cache connection point 'server'. 'server' is the tuple of (host, port)
+ absolutely the same as standard tcp socket uses. However library will lookup for
+ ('_xmpp-client._tcp.' + host) SRV record in DNS and connect to the found (if it is)
+ server instead.
+ """
+ PlugIn.__init__(self)
+ self.DBG_LINE = "socket"
+ self._sequence = SendSemaphore()
+ self.set_send_interval = self._sequence.set_send_interval
+ self._exported_methods = [self.send, self.disconnect, self.set_send_interval]
+ self._server, self.use_srv = server, use_srv
+
+ def srv_lookup(self, server):
+ """
+ SRV resolver. Takes server=(host, port) as argument. Returns new (host, port) pair.
+ """
+ if dns:
+ query = "_xmpp-client._tcp.%s" % server[0]
+ try:
+ dns.DiscoverNameServers()
+ dns__ = dns.Request()
+ response = dns__.req(query, qtype="SRV")
+ if response.answers:
+ # Sort by priority, according to RFC 2782.
+ answers = sorted(response.answers, key=lambda a: a["data"][0])
+ (port, host) = answers[0]["data"][2:][:2]
+ server = str(host), int(port)
+ except dns.DNSError:
+ self.DEBUG("An error occurred while looking up %s." % query, "warn")
+ return server
+
+ def plugin(self, owner):
+ """
+ Fire up connection. Return non-empty string on success.
+ Also registers self.disconnected method in the owner's dispatcher.
+ Called internally.
+ """
+ if not self._server:
+ self._server = (self._owner.Server, 5222)
+ if self.use_srv:
+ server = self.srv_lookup(self._server)
+ else:
+ server = self._server
+ if not self.connect(server):
+ return None
+ self._owner.Connection = self
+ self._owner.RegisterDisconnectHandler(self.disconnected)
+ return "ok"
+
+ def getHost(self):
+ """
+ Returns the 'host' value that is connection is [will be] made to.
+ """
+ return self._server[0]
+
+ def getPort(self):
+ """
+ Returns the 'port' value that is connection is [will be] made to.
+ """
+ return self._server[1]
+
+ def connect(self, server=None):
+ """
+ Try to connect to the given host/port.
+ Returns non-empty string on success.
+ """
+ if not server:
+ server = self._server
+ host, port = server
+ socktype = socket.SOCK_STREAM
+ try:
+ lookup = reversed(socket.getaddrinfo(host, int(port), 0, socktype))
+ except Exception:
+ addr = (host, int(port))
+ if ":" in host:
+ af = socket.AF_INET6
+ addr = addr.__add__((0, 0))
+ else:
+ af = socket.AF_INET
+ lookup = [(af, socktype, 1, 6, addr)]
+ for af, socktype, proto, cn, addr in lookup:
+ try:
+ self._sock = socket.socket(af, socktype)
+ self._sock.connect(addr)
+ self._send = self._sock.sendall
+ self._recv = self._sock.recv
+ except socket.error as error:
+ if getattr(self, "_sock", None):
+ self._sock.close()
+ try:
+ code, error = error
+ except Exception:
+ code = -1
+ self.DEBUG("Failed to connect to remote host %s: %s (%s)" % (repr(server), error, code), "error")
+ except Exception:
+ pass
+ else:
+ configureSocket(self._sock)
+ self.DEBUG("Successfully connected to remote host %s." % repr(server), "start")
+ return "ok"
+
+ def plugout(self):
+ """
+ Disconnect from the remote server and unregister self.disconnected method from
+ the owner's dispatcher.
+ """
+ if getattr(self, "_sock", None):
+ self._sock.close()
+ if hasattr(self._owner, "Connection"):
+ del self._owner.Connection
+ self._owner.UnregisterDisconnectHandler(self.disconnected)
+
+ def receive(self):
+ """
+ Reads all pending incoming data.
+ In case of disconnection calls owner's disconnected() method and then raises IOError exception.
+ """
+ try:
+ data = self._recv(BUFLEN)
+ except socket.sslerror as e:
+ self._seen_data = 0
+ if e[0] in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE):
+ return ""
+ self.DEBUG("Socket error while receiving data.", "error")
+ sys.exc_clear()
+ self._owner.disconnected()
+ raise IOError("Disconnected!")
+ except Exception:
+ data = ""
+ while self.pending_data(0):
+ try:
+ add = self._recv(BUFLEN)
+ except Exception:
+ break
+ if not add:
+ break
+ data += add
+ if data:
+ self._seen_data = 1
+ self.DEBUG(data, "got")
+ if hasattr(self._owner, "Dispatcher"):
+ self._owner.Dispatcher.Event("", DATA_RECEIVED, data)
+ else:
+ self.DEBUG("Socket error while receiving data.", "error")
+ sys.exc_clear()
+ self._owner.disconnected()
+ raise IOError("Disconnected!")
+ return data
+
+ def send(self, data):
+ """
+ Writes raw outgoing data. Blocks until done.
+ If supplied data is unicode string, encodes it to utf-8 before send.
+ """
+ if isinstance(data, unicode):
+ data = data.encode("utf-8")
+ elif not isinstance(data, str):
+ data = ustr(data).encode("utf-8")
+ with self._sequence:
+ try:
+ self._send(data)
+ except (Exception, socket.error):
+ self.DEBUG("Socket error while sending data.", "error")
+ self._owner.disconnected()
+ else:
+ if not data.strip():
+ data = repr(data)
+ self.DEBUG(data, "sent")
+ if hasattr(self._owner, "Dispatcher"):
+ self._owner.Dispatcher.Event("", DATA_SENT, data)
+
+ def pending_data(self, timeout=0):
+ """
+ Returns true if there is a data ready to be read.
+ """
+ return select([self._sock], [], [], timeout)[0]
+
+ def disconnect(self):
+ """
+ Closes the socket.
+ """
+ self.DEBUG("Closing socket.", "stop")
+ self._sock.close()
+
+ def disconnected(self):
+ """
+ Called when a Network Error or disconnection occurs.
+ Designed to be overidden.
+ """
+ self.DEBUG("Socket operation failed.", "error")
+
+class HTTPPROXYsocket(TCPsocket):
+ """
+ HTTP (CONNECT) proxy connection class. Uses TCPsocket as the base class
+ redefines only connect method. Allows to use HTTP proxies like squid with
+ (optionally) simple authentication (using login and password).
+ """
+ def __init__(self, proxy, server, use_srv=True):
+ """
+ Caches proxy and target addresses.
+ 'proxy' argument is a dictionary with mandatory keys 'host' and 'port' (proxy address)
+ and optional keys 'user' and 'password' to use for authentication.
+ 'server' argument is a tuple of host and port - just like TCPsocket uses.
+ """
+ TCPsocket.__init__(self, server, use_srv)
+ self.DBG_LINE = DBG_CONNECT_PROXY
+ self._proxy = proxy
+
+ def plugin(self, owner):
+ """
+ Starts connection. Used interally. Returns non-empty string on success.
+ """
+ owner.debug_flags.append(DBG_CONNECT_PROXY)
+ return TCPsocket.plugin(self, owner)
+
+ def connect(self, dupe=None):
+ """
+ Starts connection. Connects to proxy, supplies login and password to it
+ (if were specified while creating instance). Instructs proxy to make
+ connection to the target server. Returns non-empty sting on success.
+ """
+ if not TCPsocket.connect(self, (self._proxy["host"], self._proxy["port"])):
+ return None
+ self.DEBUG("Proxy server contacted, performing authentification.", "start")
+ connector = [
+ "CONNECT %s:%s HTTP/1.0" % self._server,
+ "Proxy-Connection: Keep-Alive",
+ "Pragma: no-cache",
+ "Host: %s:%s" % self._server,
+ "User-Agent: HTTPPROXYsocket/v0.1"
+ ]
+ if "user" in self._proxy and "password" in self._proxy:
+ credentials = "%s:%s" % (self._proxy["user"], self._proxy["password"])
+ credentials = encodestring(credentials).strip()
+ connector.append("Proxy-Authorization: Basic " + credentials)
+ connector.append("\r\n")
+ self.send("\r\n".join(connector))
+ try:
+ reply = self.receive().replace("\r", "")
+ except IOError:
+ self.DEBUG("Proxy suddenly disconnected.", "error")
+ self._owner.disconnected()
+ return None
+ try:
+ proto, code, desc = reply.split("\n")[0].split(" ", 2)
+ except Exception:
+ raise error("Invalid proxy reply")
+ if code != "200":
+ self.DEBUG("Invalid proxy reply: %s %s %s" % (proto, code, desc), "error")
+ self._owner.disconnected()
+ return None
+ while reply.find("\n\n") == -1:
+ try:
+ reply += self.receive().replace("\r", "")
+ except IOError:
+ self.DEBUG("Proxy suddenly disconnected.", "error")
+ self._owner.disconnected()
+ return None
+ self.DEBUG("Authentification successfull. Jabber server contacted.", "ok")
+ return "ok"
+
+ def DEBUG(self, text, severity):
+ """
+ Overwrites DEBUG tag to allow debug output be presented as 'CONNECTproxy'.
+ """
+ return self._owner.DEBUG(DBG_CONNECT_PROXY, text, severity)
+
+class TLS(PlugIn):
+ """
+ TLS connection used to encrypts already estabilished tcp connection.
+ """
+ def PlugIn(self, owner, now=0):
+ """
+ If the 'now' argument is true then starts using encryption immidiatedly.
+ If 'now' in false then starts encryption as soon as TLS feature is
+ declared by the server (if it were already declared - it is ok).
+ """
+ if hasattr(owner, "TLS"):
+ return None
+ PlugIn.PlugIn(self, owner)
+ DBG_LINE = "TLS"
+ if now:
+ return self._startSSL()
+ if self._owner.Dispatcher.Stream.features:
+ try:
+ self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features)
+ except NodeProcessed:
+ pass
+ else:
+ self._owner.RegisterHandlerOnce("features", self.FeaturesHandler, xmlns=NS_STREAMS)
+ self.starttls = None
+
+ def plugout(self, now=0):
+ """
+ Unregisters TLS handler's from owner's dispatcher. Take note that encription
+ can not be stopped once started. You can only break the connection and start over.
+ """
+ self._owner.UnregisterHandler("features", self.FeaturesHandler, xmlns=NS_STREAMS)
+ self._owner.UnregisterHandler("proceed", self.StartTLSHandler, xmlns=NS_TLS)
+ self._owner.UnregisterHandler("failure", self.StartTLSHandler, xmlns=NS_TLS)
+
+ def FeaturesHandler(self, conn, feats):
+ """
+ Used to analyse server <features/> tag for TLS support.
+ If TLS is supported starts the encryption negotiation. Used internally.
+ """
+ if not feats.getTag("starttls", namespace=NS_TLS):
+ self.DEBUG("TLS unsupported by remote server.", "warn")
+ return None
+ self.DEBUG("TLS supported by remote server. Requesting TLS start.", "ok")
+ self._owner.RegisterHandlerOnce("proceed", self.StartTLSHandler, xmlns=NS_TLS)
+ self._owner.RegisterHandlerOnce("failure", self.StartTLSHandler, xmlns=NS_TLS)
+ self._owner.Connection.send("<starttls xmlns=\"%s\"/>" % NS_TLS)
+ raise NodeProcessed()
+
+ def pending_data(self, timeout=0):
+ """
+ Returns true if there possible is a data ready to be read.
+ """
+ return self._tcpsock._seen_data or select([self._tcpsock._sock], [], [], timeout)[0]
+
+ def _startSSL(self):
+ tcpsock = self._owner.Connection
+ if sys.hexversion >= 0x20600F0:
+ tcpsock._sslObj = ssl.wrap_socket(tcpsock._sock, None, None)
+ else:
+ tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
+ tcpsock._sslIssuer = tcpsock._sslObj.issuer()
+ tcpsock._sslServer = tcpsock._sslObj.server()
+ tcpsock._recv = tcpsock._sslObj.read
+ tcpsock._send = tcpsock._sslObj.write
+ tcpsock._seen_data = 1
+ self._tcpsock = tcpsock
+ tcpsock.pending_data = self.pending_data
+ tcpsock._sock.setblocking(0)
+ self.starttls = "success"
+
+ def StartTLSHandler(self, conn, starttls):
+ """
+ Handle server reply if TLS is allowed to process. Behaves accordingly.
+ Used internally.
+ """
+ if starttls.getNamespace() != NS_TLS:
+ return None
+ self.starttls = starttls.getName()
+ if self.starttls == "failure":
+ self.DEBUG("Got starttls response: " + self.starttls, "error")
+ return None
+ self.DEBUG("Got starttls proceed response. Switching to TLS/SSL...", "ok")
+ self._startSSL()
+ self._owner.Dispatcher.PlugOut()
+ dispatcher.Dispatcher().PlugIn(self._owner)