diff options
Diffstat (limited to 'sleekxmpp/xmlstream')
25 files changed, 0 insertions, 3511 deletions
diff --git a/sleekxmpp/xmlstream/__init__.py b/sleekxmpp/xmlstream/__init__.py deleted file mode 100644 index 67b20c5..0000000 --- a/sleekxmpp/xmlstream/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.jid import JID -from sleekxmpp.xmlstream.scheduler import Scheduler -from sleekxmpp.xmlstream.stanzabase import StanzaBase, ElementBase, ET -from sleekxmpp.xmlstream.stanzabase import register_stanza_plugin -from sleekxmpp.xmlstream.tostring import tostring -from sleekxmpp.xmlstream.xmlstream import XMLStream, RESPONSE_TIMEOUT -from sleekxmpp.xmlstream.xmlstream import RestartStream - -__all__ = ['JID', 'Scheduler', 'StanzaBase', 'ElementBase', - 'ET', 'StateMachine', 'tostring', 'XMLStream', - 'RESPONSE_TIMEOUT', 'RestartStream'] diff --git a/sleekxmpp/xmlstream/filesocket.py b/sleekxmpp/xmlstream/filesocket.py deleted file mode 100644 index 441ff87..0000000 --- a/sleekxmpp/xmlstream/filesocket.py +++ /dev/null @@ -1,41 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from socket import _fileobject -import socket - - -class FileSocket(_fileobject): - - """ - Create a file object wrapper for a socket to work around - issues present in Python 2.6 when using sockets as file objects. - - The parser for xml.etree.cElementTree requires a file, but we will - be reading from the XMPP connection socket instead. - """ - - def read(self, size=4096): - """Read data from the socket as if it were a file.""" - data = self._sock.recv(size) - if data is not None: - return data - - -class Socket26(socket._socketobject): - - """ - A custom socket implementation that uses our own FileSocket class - to work around issues in Python 2.6 when using sockets as files. - """ - - def makefile(self, mode='r', bufsize=-1): - """makefile([mode[, bufsize]]) -> file object - Return a regular file object corresponding to the socket. The mode - and bufsize arguments are as for the built-in open() function.""" - return FileSocket(self._sock, mode, bufsize) diff --git a/sleekxmpp/xmlstream/handler/__init__.py b/sleekxmpp/xmlstream/handler/__init__.py deleted file mode 100644 index 7bcf0b7..0000000 --- a/sleekxmpp/xmlstream/handler/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.handler.callback import Callback -from sleekxmpp.xmlstream.handler.waiter import Waiter -from sleekxmpp.xmlstream.handler.xmlcallback import XMLCallback -from sleekxmpp.xmlstream.handler.xmlwaiter import XMLWaiter - -__all__ = ['Callback', 'Waiter', 'XMLCallback', 'XMLWaiter'] diff --git a/sleekxmpp/xmlstream/handler/base.py b/sleekxmpp/xmlstream/handler/base.py deleted file mode 100644 index 9c704ec..0000000 --- a/sleekxmpp/xmlstream/handler/base.py +++ /dev/null @@ -1,89 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -class BaseHandler(object): - - """ - Base class for stream handlers. Stream handlers are matched with - incoming stanzas so that the stanza may be processed in some way. - Stanzas may be matched with multiple handlers. - - Handler execution may take place in two phases. The first is during - the stream processing itself. The second is after stream processing - and during SleekXMPP's main event loop. The prerun method is used - for execution during stream processing, and the run method is used - during the main event loop. - - Attributes: - name -- The name of the handler. - stream -- The stream this handler is assigned to. - - Methods: - match -- Compare a stanza with the handler's matcher. - prerun -- Handler execution during stream processing. - run -- Handler execution during the main event loop. - check_delete -- Indicate if the handler may be removed from use. - """ - - def __init__(self, name, matcher, stream=None): - """ - Create a new stream handler. - - Arguments: - name -- The name of the handler. - matcher -- A matcher object from xmlstream.matcher that will be - used to determine if a stanza should be accepted by - this handler. - stream -- The XMLStream instance the handler should monitor. - """ - self.checkDelete = self.check_delete - - self.name = name - self.stream = stream - self._destroy = False - self._payload = None - self._matcher = matcher - if stream is not None: - stream.registerHandler(self) - - def match(self, xml): - """ - Compare a stanza or XML object with the handler's matcher. - - Arguments - xml -- An XML or stanza object. - """ - return self._matcher.match(xml) - - def prerun(self, payload): - """ - Prepare the handler for execution while the XML stream is being - processed. - - Arguments: - payload -- A stanza object. - """ - self._payload = payload - - def run(self, payload): - """ - Execute the handler after XML stream processing and during the - main event loop. - - Arguments: - payload -- A stanza object. - """ - self._payload = payload - - def check_delete(self): - """ - Check if the handler should be removed from the list of stream - handlers. - """ - return self._destroy diff --git a/sleekxmpp/xmlstream/handler/callback.py b/sleekxmpp/xmlstream/handler/callback.py deleted file mode 100644 index f0a7285..0000000 --- a/sleekxmpp/xmlstream/handler/callback.py +++ /dev/null @@ -1,84 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.handler.base import BaseHandler - - -class Callback(BaseHandler): - - """ - The Callback handler will execute a callback function with - matched stanzas. - - The handler may execute the callback either during stream - processing or during the main event loop. - - Callback functions are all executed in the same thread, so be - aware if you are executing functions that will block for extended - periods of time. Typically, you should signal your own events using the - SleekXMPP object's event() method to pass the stanza off to a threaded - event handler for further processing. - - Methods: - prerun -- Overrides BaseHandler.prerun - run -- Overrides BaseHandler.run - """ - - def __init__(self, name, matcher, pointer, thread=False, - once=False, instream=False, stream=None): - """ - Create a new callback handler. - - Arguments: - name -- The name of the handler. - matcher -- A matcher object for matching stanza objects. - pointer -- The function to execute during callback. - thread -- DEPRECATED. Remains only for backwards compatibility. - once -- Indicates if the handler should be used only - once. Defaults to False. - instream -- Indicates if the callback should be executed - during stream processing instead of in the - main event loop. - stream -- The XMLStream instance this handler should monitor. - """ - BaseHandler.__init__(self, name, matcher, stream) - self._pointer = pointer - self._once = once - self._instream = instream - - def prerun(self, payload): - """ - Execute the callback during stream processing, if - the callback was created with instream=True. - - Overrides BaseHandler.prerun - - Arguments: - payload -- The matched stanza object. - """ - BaseHandler.prerun(self, payload) - if self._instream: - self.run(payload, True) - - def run(self, payload, instream=False): - """ - Execute the callback function with the matched stanza payload. - - Overrides BaseHandler.run - - Arguments: - payload -- The matched stanza object. - instream -- Force the handler to execute during - stream processing. Used only by prerun. - Defaults to False. - """ - if not self._instream or instream: - BaseHandler.run(self, payload) - self._pointer(payload) - if self._once: - self._destroy = True diff --git a/sleekxmpp/xmlstream/handler/waiter.py b/sleekxmpp/xmlstream/handler/waiter.py deleted file mode 100644 index a4bc354..0000000 --- a/sleekxmpp/xmlstream/handler/waiter.py +++ /dev/null @@ -1,101 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging -try: - import queue -except ImportError: - import Queue as queue - -from sleekxmpp.xmlstream import StanzaBase, RESPONSE_TIMEOUT -from sleekxmpp.xmlstream.handler.base import BaseHandler - - -log = logging.getLogger(__name__) - - -class Waiter(BaseHandler): - - """ - The Waiter handler allows an event handler to block - until a particular stanza has been received. The handler - will either be given the matched stanza, or False if the - waiter has timed out. - - Methods: - check_delete -- Overrides BaseHandler.check_delete - prerun -- Overrides BaseHandler.prerun - run -- Overrides BaseHandler.run - wait -- Wait for a stanza to arrive and return it to - an event handler. - """ - - def __init__(self, name, matcher, stream=None): - """ - Create a new Waiter. - - Arguments: - name -- The name of the waiter. - matcher -- A matcher object to detect the desired stanza. - stream -- Optional XMLStream instance to monitor. - """ - BaseHandler.__init__(self, name, matcher, stream=stream) - self._payload = queue.Queue() - - def prerun(self, payload): - """ - Store the matched stanza. - - Overrides BaseHandler.prerun - - Arguments: - payload -- The matched stanza object. - """ - self._payload.put(payload) - - def run(self, payload): - """ - Do not process this handler during the main event loop. - - Overrides BaseHandler.run - - Arguments: - payload -- The matched stanza object. - """ - pass - - def wait(self, timeout=RESPONSE_TIMEOUT): - """ - Block an event handler while waiting for a stanza to arrive. - - Be aware that this will impact performance if called from a - non-threaded event handler. - - Will return either the received stanza, or False if the waiter - timed out. - - Arguments: - timeout -- The number of seconds to wait for the stanza to - arrive. Defaults to the global default timeout - value sleekxmpp.xmlstream.RESPONSE_TIMEOUT. - """ - try: - stanza = self._payload.get(True, timeout) - except queue.Empty: - stanza = False - log.warning("Timed out waiting for %s" % self.name) - self.stream.removeHandler(self.name) - return stanza - - def check_delete(self): - """ - Always remove waiters after use. - - Overrides BaseHandler.check_delete - """ - return True diff --git a/sleekxmpp/xmlstream/handler/xmlcallback.py b/sleekxmpp/xmlstream/handler/xmlcallback.py deleted file mode 100644 index 11607ff..0000000 --- a/sleekxmpp/xmlstream/handler/xmlcallback.py +++ /dev/null @@ -1,36 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.handler import Callback - - -class XMLCallback(Callback): - - """ - The XMLCallback class is identical to the normal Callback class, - except that XML contents of matched stanzas will be processed instead - of the stanza objects themselves. - - Methods: - run -- Overrides Callback.run - """ - - def run(self, payload, instream=False): - """ - Execute the callback function with the matched stanza's - XML contents, instead of the stanza itself. - - Overrides BaseHandler.run - - Arguments: - payload -- The matched stanza object. - instream -- Force the handler to execute during - stream processing. Used only by prerun. - Defaults to False. - """ - Callback.run(self, payload.xml, instream) diff --git a/sleekxmpp/xmlstream/handler/xmlwaiter.py b/sleekxmpp/xmlstream/handler/xmlwaiter.py deleted file mode 100644 index 5201caf..0000000 --- a/sleekxmpp/xmlstream/handler/xmlwaiter.py +++ /dev/null @@ -1,33 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.handler import Waiter - - -class XMLWaiter(Waiter): - - """ - The XMLWaiter class is identical to the normal Waiter class - except that it returns the XML contents of the stanza instead - of the full stanza object itself. - - Methods: - prerun -- Overrides Waiter.prerun - """ - - def prerun(self, payload): - """ - Store the XML contents of the stanza to return to the - waiting event handler. - - Overrides Waiter.prerun - - Arguments: - payload -- The matched stanza object. - """ - Waiter.prerun(self, payload.xml) diff --git a/sleekxmpp/xmlstream/jid.py b/sleekxmpp/xmlstream/jid.py deleted file mode 100644 index 33d845a..0000000 --- a/sleekxmpp/xmlstream/jid.py +++ /dev/null @@ -1,123 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -class JID(object): - """ - A representation of a Jabber ID, or JID. - - Each JID may have three components: a user, a domain, and an optional - resource. For example: user@domain/resource - - When a resource is not used, the JID is called a bare JID. - The JID is a full JID otherwise. - - Attributes: - jid -- Alias for 'full'. - full -- The value of the full JID. - bare -- The value of the bare JID. - user -- The username portion of the JID. - domain -- The domain name portion of the JID. - server -- Alias for 'domain'. - resource -- The resource portion of the JID. - - Methods: - reset -- Use a new JID value. - regenerate -- Recreate the JID from its components. - """ - - def __init__(self, jid): - """Initialize a new JID""" - self.reset(jid) - - def reset(self, jid): - """ - Start fresh from a new JID string. - - Arguments: - jid - The new JID value. - """ - self._full = self._jid = str(jid) - self._domain = None - self._resource = None - self._user = None - self._bare = None - - def __getattr__(self, name): - """ - Handle getting the JID values, using cache if available. - - Arguments: - name -- One of: user, server, domain, resource, - full, or bare. - """ - if name == 'resource': - if self._resource is None and '/' in self._jid: - self._resource = self._jid.split('/', 1)[-1] - return self._resource or "" - elif name == 'user': - if self._user is None: - if '@' in self._jid: - self._user = self._jid.split('@', 1)[0] - else: - self._user = self._user - return self._user or "" - elif name in ('server', 'domain', 'host'): - if self._domain is None: - self._domain = self._jid.split('@', 1)[-1].split('/', 1)[0] - return self._domain or "" - elif name == 'full': - return self._jid or "" - elif name == 'bare': - if self._bare is None: - self._bare = self._jid.split('/', 1)[0] - return self._bare or "" - - def __setattr__(self, name, value): - """ - Edit a JID by updating it's individual values, resetting the - generated JID in the end. - - Arguments: - name -- The name of the JID part. One of: user, domain, - server, resource, full, jid, or bare. - value -- The new value for the JID part. - """ - if name in ('resource', 'user', 'domain'): - object.__setattr__(self, "_%s" % name, value) - self.regenerate() - elif name in ('server', 'domain', 'host'): - self.domain = value - elif name in ('full', 'jid'): - self.reset(value) - self.regenerate() - elif name == 'bare': - if '@' in value: - u, d = value.split('@', 1) - object.__setattr__(self, "_user", u) - object.__setattr__(self, "_domain", d) - else: - object.__setattr__(self, "_user", '') - object.__setattr__(self, "_domain", value) - self.regenerate() - else: - object.__setattr__(self, name, value) - - def regenerate(self): - """Generate a new JID based on current values, useful after editing.""" - jid = "" - if self.user: - jid = "%s@" % self.user - jid += self.domain - if self.resource: - jid += "/%s" % self.resource - self.reset(jid) - - def __str__(self): - """Use the full JID as the string value.""" - return self.full diff --git a/sleekxmpp/xmlstream/matcher/__init__.py b/sleekxmpp/xmlstream/matcher/__init__.py deleted file mode 100644 index 1038d1b..0000000 --- a/sleekxmpp/xmlstream/matcher/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.matcher.id import MatcherId -from sleekxmpp.xmlstream.matcher.many import MatchMany -from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath -from sleekxmpp.xmlstream.matcher.xmlmask import MatchXMLMask -from sleekxmpp.xmlstream.matcher.xpath import MatchXPath - -__all__ = ['MatcherId', 'MatchMany', 'StanzaPath', - 'MatchXMLMask', 'MatchXPath'] diff --git a/sleekxmpp/xmlstream/matcher/base.py b/sleekxmpp/xmlstream/matcher/base.py deleted file mode 100644 index 701ab32..0000000 --- a/sleekxmpp/xmlstream/matcher/base.py +++ /dev/null @@ -1,34 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -class MatcherBase(object): - - """ - Base class for stanza matchers. Stanza matchers are used to pick - stanzas out of the XML stream and pass them to the appropriate - stream handlers. - """ - - def __init__(self, criteria): - """ - Create a new stanza matcher. - - Arguments: - criteria -- Object to compare some aspect of a stanza - against. - """ - self._criteria = criteria - - def match(self, xml): - """ - Check if a stanza matches the stored criteria. - - Meant to be overridden. - """ - return False diff --git a/sleekxmpp/xmlstream/matcher/id.py b/sleekxmpp/xmlstream/matcher/id.py deleted file mode 100644 index 0c8ce2d..0000000 --- a/sleekxmpp/xmlstream/matcher/id.py +++ /dev/null @@ -1,32 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.matcher.base import MatcherBase - - -class MatcherId(MatcherBase): - - """ - The ID matcher selects stanzas that have the same stanza 'id' - interface value as the desired ID. - - Methods: - match -- Overrides MatcherBase.match. - """ - - def match(self, xml): - """ - Compare the given stanza's 'id' attribute to the stored - id value. - - Overrides MatcherBase.match. - - Arguments: - xml -- The stanza to compare against. - """ - return xml['id'] == self._criteria diff --git a/sleekxmpp/xmlstream/matcher/many.py b/sleekxmpp/xmlstream/matcher/many.py deleted file mode 100644 index f470ec9..0000000 --- a/sleekxmpp/xmlstream/matcher/many.py +++ /dev/null @@ -1,40 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.matcher.base import MatcherBase - - -class MatchMany(MatcherBase): - - """ - The MatchMany matcher may compare a stanza against multiple - criteria. It is essentially an OR relation combining multiple - matchers. - - Each of the criteria must implement a match() method. - - Methods: - match -- Overrides MatcherBase.match. - """ - - def match(self, xml): - """ - Match a stanza against multiple criteria. The match is successful - if one of the criteria matches. - - Each of the criteria must implement a match() method. - - Overrides MatcherBase.match. - - Arguments: - xml -- The stanza object to compare against. - """ - for m in self._criteria: - if m.match(xml): - return True - return False diff --git a/sleekxmpp/xmlstream/matcher/stanzapath.py b/sleekxmpp/xmlstream/matcher/stanzapath.py deleted file mode 100644 index f8ff283..0000000 --- a/sleekxmpp/xmlstream/matcher/stanzapath.py +++ /dev/null @@ -1,38 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.matcher.base import MatcherBase - - -class StanzaPath(MatcherBase): - - """ - The StanzaPath matcher selects stanzas that match a given "stanza path", - which is similar to a normal XPath except that it uses the interfaces and - plugins of the stanza instead of the actual, underlying XML. - - In most cases, the stanza path and XPath should be identical, but be - aware that differences may occur. - - Methods: - match -- Overrides MatcherBase.match. - """ - - def match(self, stanza): - """ - Compare a stanza against a "stanza path". A stanza path is similar to - an XPath expression, but uses the stanza's interfaces and plugins - instead of the underlying XML. For most cases, the stanza path and - XPath should be identical, but be aware that differences may occur. - - Overrides MatcherBase.match. - - Arguments: - stanza -- The stanza object to compare against. - """ - return stanza.match(self._criteria) diff --git a/sleekxmpp/xmlstream/matcher/xmlmask.py b/sleekxmpp/xmlstream/matcher/xmlmask.py deleted file mode 100644 index 6ebb437..0000000 --- a/sleekxmpp/xmlstream/matcher/xmlmask.py +++ /dev/null @@ -1,159 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import logging - -from xml.parsers.expat import ExpatError - -from sleekxmpp.xmlstream.stanzabase import ET -from sleekxmpp.xmlstream.matcher.base import MatcherBase - - -# Flag indicating if the builtin XPath matcher should be used, which -# uses namespaces, or a custom matcher that ignores namespaces. -# Changing this will affect ALL XMLMask matchers. -IGNORE_NS = False - - -log = logging.getLogger(__name__) - - -class MatchXMLMask(MatcherBase): - - """ - The XMLMask matcher selects stanzas whose XML matches a given - XML pattern, or mask. For example, message stanzas with body elements - could be matched using the mask: - - <message xmlns="jabber:client"><body /></message> - - Use of XMLMask is discouraged, and XPath or StanzaPath should be used - instead. - - The use of namespaces in the mask comparison is controlled by - IGNORE_NS. Setting IGNORE_NS to True will disable namespace based matching - for ALL XMLMask matchers. - - Methods: - match -- Overrides MatcherBase.match. - setDefaultNS -- Set the default namespace for the mask. - """ - - def __init__(self, criteria): - """ - Create a new XMLMask matcher. - - Arguments: - criteria -- Either an XML object or XML string to use as a mask. - """ - MatcherBase.__init__(self, criteria) - if isinstance(criteria, str): - self._criteria = ET.fromstring(self._criteria) - self.default_ns = 'jabber:client' - - def setDefaultNS(self, ns): - """ - Set the default namespace to use during comparisons. - - Arguments: - ns -- The new namespace to use as the default. - """ - self.default_ns = ns - - def match(self, xml): - """ - Compare a stanza object or XML object against the stored XML mask. - - Overrides MatcherBase.match. - - Arguments: - xml -- The stanza object or XML object to compare against. - """ - if hasattr(xml, 'xml'): - xml = xml.xml - return self._mask_cmp(xml, self._criteria, True) - - def _mask_cmp(self, source, mask, use_ns=False, default_ns='__no_ns__'): - """ - Compare an XML object against an XML mask. - - Arguments: - source -- The XML object to compare against the mask. - mask -- The XML object serving as the mask. - use_ns -- Indicates if namespaces should be respected during - the comparison. - default_ns -- The default namespace to apply to elements that - do not have a specified namespace. - Defaults to "__no_ns__". - """ - use_ns = not IGNORE_NS - - if source is None: - # If the element was not found. May happend during recursive calls. - return False - - # Convert the mask to an XML object if it is a string. - if not hasattr(mask, 'attrib'): - try: - mask = ET.fromstring(mask) - except ExpatError: - log.warning("Expat error: %s\nIn parsing: %s" % ('', mask)) - - if not use_ns: - # Compare the element without using namespaces. - source_tag = source.tag.split('}', 1)[-1] - mask_tag = mask.tag.split('}', 1)[-1] - if source_tag != mask_tag: - return False - else: - # Compare the element using namespaces - mask_ns_tag = "{%s}%s" % (self.default_ns, mask.tag) - if source.tag not in [mask.tag, mask_ns_tag]: - return False - - # If the mask includes text, compare it. - if mask.text and source.text != mask.text: - return False - - # Compare attributes. The stanza must include the attributes - # defined by the mask, but may include others. - for name, value in mask.attrib.items(): - if source.attrib.get(name, "__None__") != value: - return False - - # Recursively check subelements. - for subelement in mask: - if use_ns: - if not self._mask_cmp(source.find(subelement.tag), - subelement, use_ns): - return False - else: - if not self._mask_cmp(self._get_child(source, subelement.tag), - subelement, use_ns): - return False - - # Everything matches. - return True - - def _get_child(self, xml, tag): - """ - Return a child element given its tag, ignoring namespace values. - - Returns None if the child was not found. - - Arguments: - xml -- The XML object to search for the given child tag. - tag -- The name of the subelement to find. - """ - tag = tag.split('}')[-1] - try: - children = [c.tag.split('}')[-1] for c in xml.getchildren()] - index = children.index(tag) - except ValueError: - return None - return xml.getchildren()[index] diff --git a/sleekxmpp/xmlstream/matcher/xpath.py b/sleekxmpp/xmlstream/matcher/xpath.py deleted file mode 100644 index 669c9f1..0000000 --- a/sleekxmpp/xmlstream/matcher/xpath.py +++ /dev/null @@ -1,79 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from sleekxmpp.xmlstream.stanzabase import ET -from sleekxmpp.xmlstream.matcher.base import MatcherBase - - -# Flag indicating if the builtin XPath matcher should be used, which -# uses namespaces, or a custom matcher that ignores namespaces. -# Changing this will affect ALL XPath matchers. -IGNORE_NS = False - - -class MatchXPath(MatcherBase): - - """ - The XPath matcher selects stanzas whose XML contents matches a given - XPath expression. - - Note that using this matcher may not produce expected behavior when using - attribute selectors. For Python 2.6 and 3.1, the ElementTree find method - does not support the use of attribute selectors. If you need to support - Python 2.6 or 3.1, it might be more useful to use a StanzaPath matcher. - - If the value of IGNORE_NS is set to true, then XPath expressions will - be matched without using namespaces. - - Methods: - match -- Overrides MatcherBase.match. - """ - - def match(self, xml): - """ - Compare a stanza's XML contents to an XPath expression. - - If the value of IGNORE_NS is set to true, then XPath expressions - will be matched without using namespaces. - - Note that in Python 2.6 and 3.1 the ElementTree find method does - not support attribute selectors in the XPath expression. - - Arguments: - xml -- The stanza object to compare against. - """ - if hasattr(xml, 'xml'): - xml = xml.xml - x = ET.Element('x') - x.append(xml) - - if not IGNORE_NS: - # Use builtin, namespace respecting, XPath matcher. - if x.find(self._criteria) is not None: - return True - return False - else: - # Remove namespaces from the XPath expression. - criteria = [] - for ns_block in self._criteria.split('{'): - criteria.extend(ns_block.split('}')[-1].split('/')) - - # Walk the XPath expression. - xml = x - for tag in criteria: - if not tag: - # Skip empty tag name artifacts from the cleanup phase. - continue - - children = [c.tag.split('}')[-1] for c in xml.getchildren()] - try: - index = children.index(tag) - except ValueError: - return False - xml = xml.getchildren()[index] - return True diff --git a/sleekxmpp/xmlstream/scheduler.py b/sleekxmpp/xmlstream/scheduler.py deleted file mode 100644 index 1435910..0000000 --- a/sleekxmpp/xmlstream/scheduler.py +++ /dev/null @@ -1,207 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import time -import threading -import logging -try: - import queue -except ImportError: - import Queue as queue - - -log = logging.getLogger(__name__) - - -class Task(object): - - """ - A scheduled task that will be executed by the scheduler - after a given time interval has passed. - - Attributes: - name -- The name of the task. - seconds -- The number of seconds to wait before executing. - callback -- The function to execute. - args -- The arguments to pass to the callback. - kwargs -- The keyword arguments to pass to the callback. - repeat -- Indicates if the task should repeat. - Defaults to False. - qpointer -- A pointer to an event queue for queuing callback - execution instead of executing immediately. - - Methods: - run -- Either queue or execute the callback. - reset -- Reset the task's timer. - """ - - def __init__(self, name, seconds, callback, args=None, - kwargs=None, repeat=False, qpointer=None): - """ - Create a new task. - - Arguments: - name -- The name of the task. - seconds -- The number of seconds to wait before executing. - callback -- The function to execute. - args -- The arguments to pass to the callback. - kwargs -- The keyword arguments to pass to the callback. - repeat -- Indicates if the task should repeat. - Defaults to False. - qpointer -- A pointer to an event queue for queuing callback - execution instead of executing immediately. - """ - self.name = name - self.seconds = seconds - self.callback = callback - self.args = args or tuple() - self.kwargs = kwargs or {} - self.repeat = repeat - self.next = time.time() + self.seconds - self.qpointer = qpointer - - def run(self): - """ - Execute the task's callback. - - If an event queue was supplied, place the callback in the queue; - otherwise, execute the callback immediately. - """ - if self.qpointer is not None: - self.qpointer.put(('schedule', self.callback, self.args)) - else: - self.callback(*self.args, **self.kwargs) - self.reset() - return self.repeat - - def reset(self): - """ - Reset the task's timer so that it will repeat. - """ - self.next = time.time() + self.seconds - - -class Scheduler(object): - - """ - A threaded scheduler that allows for updates mid-execution unlike the - scheduler in the standard library. - - http://docs.python.org/library/sched.html#module-sched - - Attributes: - addq -- A queue storing added tasks. - schedule -- A list of tasks in order of execution times. - thread -- If threaded, the thread processing the schedule. - run -- Indicates if the scheduler is running. - parentqueue -- A parent event queue in control of this scheduler. - - Methods: - add -- Add a new task to the schedule. - process -- Process and schedule tasks. - quit -- Stop the scheduler. - """ - - def __init__(self, parentqueue=None, parentstop=None): - """ - Create a new scheduler. - - Arguments: - parentqueue -- A separate event queue controlling this scheduler. - """ - self.addq = queue.Queue() - self.schedule = [] - self.thread = None - self.run = False - self.parentqueue = parentqueue - self.parentstop = parentstop - - def process(self, threaded=True): - """ - Begin accepting and processing scheduled tasks. - - Arguments: - threaded -- Indicates if the scheduler should execute in its own - thread. Defaults to True. - """ - if threaded: - self.thread = threading.Thread(name='sheduler_process', - target=self._process) - self.thread.start() - else: - self._process() - - def _process(self): - """Process scheduled tasks.""" - self.run = True - try: - while self.run and (self.parentstop is None or not self.parentstop.isSet()): - wait = 1 - updated = False - if self.schedule: - wait = self.schedule[0].next - time.time() - try: - if wait <= 0.0: - newtask = self.addq.get(False) - else: - if wait >= 3.0: - wait = 3.0 - newtask = self.addq.get(True, wait) - except queue.Empty: - cleanup = [] - for task in self.schedule: - if time.time() >= task.next: - updated = True - if not task.run(): - cleanup.append(task) - else: - break - for task in cleanup: - x = self.schedule.pop(self.schedule.index(task)) - else: - updated = True - self.schedule.append(newtask) - finally: - if updated: - self.schedule = sorted(self.schedule, - key=lambda task: task.next) - except KeyboardInterrupt: - self.run = False - if self.parentstop is not None: - log.debug("stopping parent") - self.parentstop.set() - except SystemExit: - self.run = False - if self.parentstop is not None: - self.parentstop.set() - log.debug("Quitting Scheduler thread") - if self.parentqueue is not None: - self.parentqueue.put(('quit', None, None)) - - def add(self, name, seconds, callback, args=None, - kwargs=None, repeat=False, qpointer=None): - """ - Schedule a new task. - - Arguments: - name -- The name of the task. - seconds -- The number of seconds to wait before executing. - callback -- The function to execute. - args -- The arguments to pass to the callback. - kwargs -- The keyword arguments to pass to the callback. - repeat -- Indicates if the task should repeat. - Defaults to False. - qpointer -- A pointer to an event queue for queuing callback - execution instead of executing immediately. - """ - self.addq.put(Task(name, seconds, callback, args, - kwargs, repeat, qpointer)) - - def quit(self): - """Shutdown the scheduler.""" - self.run = False diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py deleted file mode 100644 index aabd386..0000000 --- a/sleekxmpp/xmlstream/stanzabase.py +++ /dev/null @@ -1,1165 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import copy -import logging -import sys -import weakref -from xml.etree import cElementTree as ET - -from sleekxmpp.xmlstream import JID -from sleekxmpp.xmlstream.tostring import tostring - - -log = logging.getLogger(__name__) - - -# Used to check if an argument is an XML object. -XML_TYPE = type(ET.Element('xml')) - - -def register_stanza_plugin(stanza, plugin): - """ - Associate a stanza object as a plugin for another stanza. - - Arguments: - stanza -- The class of the parent stanza. - plugin -- The class of the plugin stanza. - """ - tag = "{%s}%s" % (plugin.namespace, plugin.name) - stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin - stanza.plugin_tag_map[tag] = plugin - - -# To maintain backwards compatibility for now, preserve the camel case name. -registerStanzaPlugin = register_stanza_plugin - - -class ElementBase(object): - - """ - The core of SleekXMPP's stanza XML manipulation and handling is provided - by ElementBase. ElementBase wraps XML cElementTree objects and enables - access to the XML contents through dictionary syntax, similar in style - to the Ruby XMPP library Blather's stanza implementation. - - Stanzas are defined by their name, namespace, and interfaces. For - example, a simplistic Message stanza could be defined as: - - >>> class Message(ElementBase): - ... name = "message" - ... namespace = "jabber:client" - ... interfaces = set(('to', 'from', 'type', 'body')) - ... sub_interfaces = set(('body',)) - - The resulting Message stanza's contents may be accessed as so: - - >>> message['to'] = "user@example.com" - >>> message['body'] = "Hi!" - >>> message['body'] - "Hi!" - >>> del message['body'] - >>> message['body'] - "" - - The interface values map to either custom access methods, stanza - XML attributes, or (if the interface is also in sub_interfaces) the - text contents of a stanza's subelement. - - Custom access methods may be created by adding methods of the - form "getInterface", "setInterface", or "delInterface", where - "Interface" is the titlecase version of the interface name. - - Stanzas may be extended through the use of plugins. A plugin - is simply a stanza that has a plugin_attrib value. For example: - - >>> class MessagePlugin(ElementBase): - ... name = "custom_plugin" - ... namespace = "custom" - ... interfaces = set(('useful_thing', 'custom')) - ... plugin_attrib = "custom" - - The plugin stanza class must be associated with its intended - container stanza by using register_stanza_plugin as so: - - >>> register_stanza_plugin(Message, MessagePlugin) - - The plugin may then be accessed as if it were built-in to the parent - stanza. - - >>> message['custom']['useful_thing'] = 'foo' - - If a plugin provides an interface that is the same as the plugin's - plugin_attrib value, then the plugin's interface may be accessed - directly from the parent stanza, as so: - - >>> message['custom'] = 'bar' # Same as using message['custom']['custom'] - - Class Attributes: - name -- The name of the stanza's main element. - namespace -- The namespace of the stanza's main element. - interfaces -- A set of attribute and element names that may - be accessed using dictionary syntax. - sub_interfaces -- A subset of the set of interfaces which map - to subelements instead of attributes. - subitem -- A set of stanza classes which are allowed to - be added as substanzas. - types -- A set of generic type attribute values. - plugin_attrib -- The interface name that the stanza uses to be - accessed as a plugin from another stanza. - plugin_attrib_map -- A mapping of plugin attribute names with the - associated plugin stanza classes. - plugin_tag_map -- A mapping of plugin stanza tag names with - the associated plugin stanza classes. - - Instance Attributes: - xml -- The stanza's XML contents. - parent -- The parent stanza of this stanza. - plugins -- A map of enabled plugin names with the - initialized plugin stanza objects. - values -- A dictionary of the stanza's interfaces - and interface values, including plugins. - - Methods: - setup -- Initialize the stanza's XML contents. - enable -- Instantiate a stanza plugin. - Alias for init_plugin. - init_plugin -- Instantiate a stanza plugin. - _get_stanza_values -- Return a dictionary of stanza interfaces and - their values. - _set_stanza_values -- Set stanza interface values given a dictionary - of interfaces and values. - __getitem__ -- Return the value of a stanza interface. - __setitem__ -- Set the value of a stanza interface. - __delitem__ -- Remove the value of a stanza interface. - _set_attr -- Set an attribute value of the main - stanza element. - _del_attr -- Remove an attribute from the main - stanza element. - _get_attr -- Return an attribute's value from the main - stanza element. - _get_sub_text -- Return the text contents of a subelement. - _set_sub_ext -- Set the text contents of a subelement. - _del_sub -- Remove a subelement. - match -- Compare the stanza against an XPath expression. - find -- Return subelement matching an XPath expression. - findall -- Return subelements matching an XPath expression. - get -- Return the value of a stanza interface, with an - optional default value. - keys -- Return the set of interface names accepted by - the stanza. - append -- Add XML content or a substanza to the stanza. - appendxml -- Add XML content to the stanza. - pop -- Remove a substanza. - next -- Return the next iterable substanza. - _fix_ns -- Apply the stanza's namespace to non-namespaced - elements in an XPath expression. - """ - - name = 'stanza' - plugin_attrib = 'plugin' - namespace = 'jabber:client' - interfaces = set(('type', 'to', 'from', 'id', 'payload')) - types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) - sub_interfaces = tuple() - plugin_attrib_map = {} - plugin_tag_map = {} - subitem = None - - def __init__(self, xml=None, parent=None): - """ - Create a new stanza object. - - Arguments: - xml -- Initialize the stanza with optional existing XML. - parent -- Optional stanza object that contains this stanza. - """ - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.initPlugin = self.init_plugin - self._getAttr = self._get_attr - self._setAttr = self._set_attr - self._delAttr = self._del_attr - self._getSubText = self._get_sub_text - self._setSubText = self._set_sub_text - self._delSub = self._del_sub - self.getStanzaValues = self._get_stanza_values - self.setStanzaValues = self._set_stanza_values - - self.xml = xml - self.plugins = {} - self.iterables = [] - self._index = 0 - if parent is None: - self.parent = None - else: - self.parent = weakref.ref(parent) - - ElementBase.values = property(ElementBase._get_stanza_values, - ElementBase._set_stanza_values) - - if self.setup(xml): - # If we generated our own XML, then everything is ready. - return - - # Initialize values using provided XML - for child in self.xml.getchildren(): - if child.tag in self.plugin_tag_map: - plugin = self.plugin_tag_map[child.tag] - self.plugins[plugin.plugin_attrib] = plugin(child, self) - if self.subitem is not None: - for sub in self.subitem: - if child.tag == "{%s}%s" % (sub.namespace, sub.name): - self.iterables.append(sub(child, self)) - break - - def setup(self, xml=None): - """ - Initialize the stanza's XML contents. - - Will return True if XML was generated according to the stanza's - definition. - - Arguments: - xml -- Optional XML object to use for the stanza's content - instead of generating XML. - """ - if self.xml is None: - self.xml = xml - - if self.xml is None: - # Generate XML from the stanza definition - for ename in self.name.split('/'): - new = ET.Element("{%s}%s" % (self.namespace, ename)) - if self.xml is None: - self.xml = new - else: - last_xml.append(new) - last_xml = new - if self.parent is not None: - self.parent().xml.append(self.xml) - - # We had to generate XML - return True - else: - # We did not generate XML - return False - - def enable(self, attrib): - """ - Enable and initialize a stanza plugin. - - Alias for init_plugin. - - Arguments: - attrib -- The stanza interface for the plugin. - """ - return self.init_plugin(attrib) - - def init_plugin(self, attrib): - """ - Enable and initialize a stanza plugin. - - Arguments: - attrib -- The stanza interface for the plugin. - """ - if attrib not in self.plugins: - plugin_class = self.plugin_attrib_map[attrib] - self.plugins[attrib] = plugin_class(parent=self) - return self - - def _get_stanza_values(self): - """ - Return a dictionary of the stanza's interface values. - - Stanza plugin values are included as nested dictionaries. - """ - values = {} - for interface in self.interfaces: - values[interface] = self[interface] - for plugin, stanza in self.plugins.items(): - values[plugin] = stanza._get_stanza_values() - if self.iterables: - iterables = [] - for stanza in self.iterables: - iterables.append(stanza._get_stanza_values()) - iterables[-1].update({ - '__childtag__': "{%s}%s" % (stanza.namespace, - stanza.name)}) - values['substanzas'] = iterables - return values - - def _set_stanza_values(self, values): - """ - Set multiple stanza interface values using a dictionary. - - Stanza plugin values may be set using nested dictionaries. - - Arguments: - values -- A dictionary mapping stanza interface with values. - Plugin interfaces may accept a nested dictionary that - will be used recursively. - """ - for interface, value in values.items(): - if interface == 'substanzas': - for subdict in value: - if '__childtag__' in subdict: - for subclass in self.subitem: - child_tag = "{%s}%s" % (subclass.namespace, - subclass.name) - if subdict['__childtag__'] == child_tag: - sub = subclass(parent=self) - sub._set_stanza_values(subdict) - self.iterables.append(sub) - break - elif interface in self.interfaces: - self[interface] = value - elif interface in self.plugin_attrib_map: - if interface not in self.plugins: - self.init_plugin(interface) - self.plugins[interface]._set_stanza_values(value) - return self - - def __getitem__(self, attrib): - """ - Return the value of a stanza interface using dictionary-like syntax. - - Example: - >>> msg['body'] - 'Message contents' - - Stanza interfaces are typically mapped directly to the underlying XML - object, but can be overridden by the presence of a get_attrib method - (or get_foo where the interface is named foo, etc). - - The search order for interface value retrieval for an interface - named 'foo' is: - 1. The list of substanzas. - 2. The result of calling get_foo. - 3. The result of calling getFoo. - 4. The contents of the foo subelement, if foo is a sub interface. - 5. The value of the foo attribute of the XML object. - 6. The plugin named 'foo' - 7. An empty string. - - Arguments: - attrib -- The name of the requested stanza interface. - """ - if attrib == 'substanzas': - return self.iterables - elif attrib in self.interfaces: - get_method = "get_%s" % attrib.lower() - get_method2 = "get%s" % attrib.title() - if hasattr(self, get_method): - return getattr(self, get_method)() - elif hasattr(self, get_method2): - return getattr(self, get_method2)() - else: - if attrib in self.sub_interfaces: - return self._get_sub_text(attrib) - else: - return self._get_attr(attrib) - elif attrib in self.plugin_attrib_map: - if attrib not in self.plugins: - self.init_plugin(attrib) - return self.plugins[attrib] - else: - return '' - - def __setitem__(self, attrib, value): - """ - Set the value of a stanza interface using dictionary-like syntax. - - Example: - >>> msg['body'] = "Hi!" - >>> msg['body'] - 'Hi!' - - Stanza interfaces are typically mapped directly to the underlying XML - object, but can be overridden by the presence of a set_attrib method - (or set_foo where the interface is named foo, etc). - - The effect of interface value assignment for an interface - named 'foo' will be one of: - 1. Delete the interface's contents if the value is None. - 2. Call set_foo, if it exists. - 3. Call setFoo, if it exists. - 4. Set the text of a foo element, if foo is in sub_interfaces. - 5. Set the value of a top level XML attribute name foo. - 6. Attempt to pass value to a plugin named foo using the plugin's - foo interface. - 7. Do nothing. - - Arguments: - attrib -- The name of the stanza interface to modify. - value -- The new value of the stanza interface. - """ - if attrib in self.interfaces: - if value is not None: - set_method = "set_%s" % attrib.lower() - set_method2 = "set%s" % attrib.title() - if hasattr(self, set_method): - getattr(self, set_method)(value,) - elif hasattr(self, set_method2): - getattr(self, set_method2)(value,) - else: - if attrib in self.sub_interfaces: - return self._set_sub_text(attrib, text=value) - else: - self._set_attr(attrib, value) - else: - self.__delitem__(attrib) - elif attrib in self.plugin_attrib_map: - if attrib not in self.plugins: - self.init_plugin(attrib) - self.plugins[attrib][attrib] = value - return self - - def __delitem__(self, attrib): - """ - Delete the value of a stanza interface using dictionary-like syntax. - - Example: - >>> msg['body'] = "Hi!" - >>> msg['body'] - 'Hi!' - >>> del msg['body'] - >>> msg['body'] - '' - - Stanza interfaces are typically mapped directly to the underlyig XML - object, but can be overridden by the presence of a del_attrib method - (or del_foo where the interface is named foo, etc). - - The effect of deleting a stanza interface value named foo will be - one of: - 1. Call del_foo, if it exists. - 2. Call delFoo, if it exists. - 3. Delete foo element, if foo is in sub_interfaces. - 4. Delete top level XML attribute named foo. - 5. Remove the foo plugin, if it was loaded. - 6. Do nothing. - - Arguments: - attrib -- The name of the affected stanza interface. - """ - if attrib in self.interfaces: - del_method = "del_%s" % attrib.lower() - del_method2 = "del%s" % attrib.title() - if hasattr(self, del_method): - getattr(self, del_method)() - elif hasattr(self, del_method2): - getattr(self, del_method2)() - else: - if attrib in self.sub_interfaces: - return self._del_sub(attrib) - else: - self._del_attr(attrib) - elif attrib in self.plugin_attrib_map: - if attrib in self.plugins: - xml = self.plugins[attrib].xml - del self.plugins[attrib] - self.xml.remove(xml) - return self - - def _set_attr(self, name, value): - """ - Set the value of a top level attribute of the underlying XML object. - - If the new value is None or an empty string, then the attribute will - be removed. - - Arguments: - name -- The name of the attribute. - value -- The new value of the attribute, or None or '' to - remove it. - """ - if value is None or value == '': - self.__delitem__(name) - else: - self.xml.attrib[name] = value - - def _del_attr(self, name): - """ - Remove a top level attribute of the underlying XML object. - - Arguments: - name -- The name of the attribute. - """ - if name in self.xml.attrib: - del self.xml.attrib[name] - - def _get_attr(self, name, default=''): - """ - Return the value of a top level attribute of the underlying - XML object. - - In case the attribute has not been set, a default value can be - returned instead. An empty string is returned if no other default - is supplied. - - Arguments: - name -- The name of the attribute. - default -- Optional value to return if the attribute has not - been set. An empty string is returned otherwise. - """ - return self.xml.attrib.get(name, default) - - def _get_sub_text(self, name, default=''): - """ - Return the text contents of a sub element. - - In case the element does not exist, or it has no textual content, - a default value can be returned instead. An empty string is returned - if no other default is supplied. - - Arguments: - name -- The name or XPath expression of the element. - default -- Optional default to return if the element does - not exists. An empty string is returned otherwise. - """ - name = self._fix_ns(name) - stanza = self.xml.find(name) - if stanza is None or stanza.text is None: - return default - else: - return stanza.text - - def _set_sub_text(self, name, text=None, keep=False): - """ - Set the text contents of a sub element. - - In case the element does not exist, a element will be created, - and its text contents will be set. - - If the text is set to an empty string, or None, then the - element will be removed, unless keep is set to True. - - Arguments: - name -- The name or XPath expression of the element. - text -- The new textual content of the element. If the text - is an empty string or None, the element will be removed - unless the parameter keep is True. - keep -- Indicates if the element should be kept if its text is - removed. Defaults to False. - """ - path = self._fix_ns(name, split=True) - element = self.xml.find(name) - - if not text and not keep: - return self._del_sub(name) - - if element is None: - # We need to add the element. If the provided name was - # an XPath expression, some of the intermediate elements - # may already exist. If so, we want to use those instead - # of generating new elements. - last_xml = self.xml - walked = [] - for ename in path: - walked.append(ename) - element = self.xml.find("/".join(walked)) - if element is None: - element = ET.Element(ename) - last_xml.append(element) - last_xml = element - element = last_xml - - element.text = text - return element - - def _del_sub(self, name, all=False): - """ - Remove sub elements that match the given name or XPath. - - If the element is in a path, then any parent elements that become - empty after deleting the element may also be deleted if requested - by setting all=True. - - Arguments: - name -- The name or XPath expression for the element(s) to remove. - all -- If True, remove all empty elements in the path to the - deleted element. Defaults to False. - """ - path = self._fix_ns(name, split=True) - original_target = path[-1] - - for level, _ in enumerate(path): - # Generate the paths to the target elements and their parent. - element_path = "/".join(path[:len(path) - level]) - parent_path = "/".join(path[:len(path) - level - 1]) - - elements = self.xml.findall(element_path) - parent = self.xml.find(parent_path) - - if elements: - if parent is None: - parent = self.xml - for element in elements: - if element.tag == original_target or \ - not element.getchildren(): - # Only delete the originally requested elements, and - # any parent elements that have become empty. - parent.remove(element) - if not all: - # If we don't want to delete elements up the tree, stop - # after deleting the first level of elements. - return - - def match(self, xpath): - """ - Compare a stanza object with an XPath expression. If the XPath matches - the contents of the stanza object, the match is successful. - - The XPath expression may include checks for stanza attributes. - For example: - presence@show=xa@priority=2/status - Would match a presence stanza whose show value is set to 'xa', has a - priority value of '2', and has a status element. - - Arguments: - xpath -- The XPath expression to check against. It may be either a - string or a list of element names with attribute checks. - """ - if isinstance(xpath, str): - xpath = self._fix_ns(xpath, split=True, propagate_ns=False) - - # Extract the tag name and attribute checks for the first XPath node. - components = xpath[0].split('@') - tag = components[0] - attributes = components[1:] - - if tag not in (self.name, "{%s}%s" % (self.namespace, self.name)) and \ - tag not in self.plugins and tag not in self.plugin_attrib: - # The requested tag is not in this stanza, so no match. - return False - - # Check the rest of the XPath against any substanzas. - matched_substanzas = False - for substanza in self.iterables: - if xpath[1:] == []: - break - matched_substanzas = substanza.match(xpath[1:]) - if matched_substanzas: - break - - # Check attribute values. - for attribute in attributes: - name, value = attribute.split('=') - if self[name] != value: - return False - - # Check sub interfaces. - if len(xpath) > 1: - next_tag = xpath[1] - if next_tag in self.sub_interfaces and self[next_tag]: - return True - - # Attempt to continue matching the XPath using the stanza's plugins. - if not matched_substanzas and len(xpath) > 1: - # Convert {namespace}tag@attribs to just tag - next_tag = xpath[1].split('@')[0].split('}')[-1] - if next_tag in self.plugins: - return self.plugins[next_tag].match(xpath[1:]) - else: - return False - - # Everything matched. - return True - - def find(self, xpath): - """ - Find an XML object in this stanza given an XPath expression. - - Exposes ElementTree interface for backwards compatibility. - - Note that matching on attribute values is not supported in Python 2.6 - or Python 3.1 - - Arguments: - xpath -- An XPath expression matching a single desired element. - """ - return self.xml.find(xpath) - - def findall(self, xpath): - """ - Find multiple XML objects in this stanza given an XPath expression. - - Exposes ElementTree interface for backwards compatibility. - - Note that matching on attribute values is not supported in Python 2.6 - or Python 3.1. - - Arguments: - xpath -- An XPath expression matching multiple desired elements. - """ - return self.xml.findall(xpath) - - def get(self, key, default=None): - """ - Return the value of a stanza interface. If the found value is None - or an empty string, return the supplied default value. - - Allows stanza objects to be used like dictionaries. - - Arguments: - key -- The name of the stanza interface to check. - default -- Value to return if the stanza interface has a value - of None or "". Will default to returning None. - """ - value = self[key] - if value is None or value == '': - return default - return value - - def keys(self): - """ - Return the names of all stanza interfaces provided by the - stanza object. - - Allows stanza objects to be used like dictionaries. - """ - out = [] - out += [x for x in self.interfaces] - out += [x for x in self.plugins] - if self.iterables: - out.append('substanzas') - return out - - def append(self, item): - """ - Append either an XML object or a substanza to this stanza object. - - If a substanza object is appended, it will be added to the list - of iterable stanzas. - - Allows stanza objects to be used like lists. - - Arguments: - item -- Either an XML object or a stanza object to add to - this stanza's contents. - """ - if not isinstance(item, ElementBase): - if type(item) == XML_TYPE: - return self.appendxml(item) - else: - raise TypeError - self.xml.append(item.xml) - self.iterables.append(item) - return self - - def appendxml(self, xml): - """ - Append an XML object to the stanza's XML. - - The added XML will not be included in the list of - iterable substanzas. - - Arguments: - xml -- The XML object to add to the stanza. - """ - self.xml.append(xml) - return self - - def pop(self, index=0): - """ - Remove and return the last substanza in the list of - iterable substanzas. - - Allows stanza objects to be used like lists. - - Arguments: - index -- The index of the substanza to remove. - """ - substanza = self.iterables.pop(index) - self.xml.remove(substanza.xml) - return substanza - - def next(self): - """ - Return the next iterable substanza. - """ - return self.__next__() - - @property - def attrib(self): - """ - DEPRECATED - - For backwards compatibility, stanza.attrib returns the stanza itself. - - Older implementations of stanza objects used XML objects directly, - requiring the use of .attrib to access attribute values. - - Use of the dictionary syntax with the stanza object itself for - accessing stanza interfaces is preferred. - """ - return self - - def _fix_ns(self, xpath, split=False, propagate_ns=True): - """ - Apply the stanza's namespace to elements in an XPath expression. - - Arguments: - xpath -- The XPath expression to fix with namespaces. - split -- Indicates if the fixed XPath should be left as a - list of element names with namespaces. Defaults to - False, which returns a flat string path. - propagate_ns -- Overrides propagating parent element namespaces - to child elements. Useful if you wish to simply - split an XPath that has non-specified namespaces, - and child and parent namespaces are known not to - always match. Defaults to True. - """ - fixed = [] - # Split the XPath into a series of blocks, where a block - # is started by an element with a namespace. - ns_blocks = xpath.split('{') - for ns_block in ns_blocks: - if '}' in ns_block: - # Apply the found namespace to following elements - # that do not have namespaces. - namespace = ns_block.split('}')[0] - elements = ns_block.split('}')[1].split('/') - else: - # Apply the stanza's namespace to the following - # elements since no namespace was provided. - namespace = self.namespace - elements = ns_block.split('/') - - for element in elements: - if element: - # Skip empty entry artifacts from splitting. - if propagate_ns: - tag = '{%s}%s' % (namespace, element) - else: - tag = element - fixed.append(tag) - if split: - return fixed - return '/'.join(fixed) - - def __eq__(self, other): - """ - Compare the stanza object with another to test for equality. - - Stanzas are equal if their interfaces return the same values, - and if they are both instances of ElementBase. - - Arguments: - other -- The stanza object to compare against. - """ - if not isinstance(other, ElementBase): - return False - - # Check that this stanza is a superset of the other stanza. - values = self._get_stanza_values() - for key in other.keys(): - if key not in values or values[key] != other[key]: - return False - - # Check that the other stanza is a superset of this stanza. - values = other._get_stanza_values() - for key in self.keys(): - if key not in values or values[key] != self[key]: - return False - - # Both stanzas are supersets of each other, therefore they - # must be equal. - return True - - def __ne__(self, other): - """ - Compare the stanza object with another to test for inequality. - - Stanzas are not equal if their interfaces return different values, - or if they are not both instances of ElementBase. - - Arguments: - other -- The stanza object to compare against. - """ - return not self.__eq__(other) - - def __bool__(self): - """ - Stanza objects should be treated as True in boolean contexts. - - Python 3.x version. - """ - return True - - def __nonzero__(self): - """ - Stanza objects should be treated as True in boolean contexts. - - Python 2.x version. - """ - return True - - def __len__(self): - """ - Return the number of iterable substanzas contained in this stanza. - """ - return len(self.iterables) - - def __iter__(self): - """ - Return an iterator object for iterating over the stanza's substanzas. - - The iterator is the stanza object itself. Attempting to use two - iterators on the same stanza at the same time is discouraged. - """ - self._index = 0 - return self - - def __next__(self): - """ - Return the next iterable substanza. - """ - self._index += 1 - if self._index > len(self.iterables): - self._index = 0 - raise StopIteration - return self.iterables[self._index - 1] - - def __copy__(self): - """ - Return a copy of the stanza object that does not share the same - underlying XML object. - """ - return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent) - - def __str__(self): - """ - Return a string serialization of the underlying XML object. - """ - return tostring(self.xml, xmlns='', stanza_ns=self.namespace) - - def __repr__(self): - """ - Use the stanza's serialized XML as its representation. - """ - return self.__str__() - - -class StanzaBase(ElementBase): - - """ - StanzaBase provides the foundation for all other stanza objects used by - SleekXMPP, and defines a basic set of interfaces common to nearly - all stanzas. These interfaces are the 'id', 'type', 'to', and 'from' - attributes. An additional interface, 'payload', is available to access - the XML contents of the stanza. Most stanza objects will provided more - specific interfaces, however. - - Stanza Interface: - from -- A JID object representing the sender's JID. - id -- An optional id value that can be used to associate stanzas - with their replies. - payload -- The XML contents of the stanza. - to -- A JID object representing the recipient's JID. - type -- The type of stanza, typically will be 'normal', 'error', - 'get', or 'set', etc. - - Attributes: - stream -- The XMLStream instance that will handle sending this stanza. - tag -- The namespaced version of the stanza's name. - - Methods: - set_type -- Set the type of the stanza. - get_to -- Return the stanza recipients JID. - set_to -- Set the stanza recipient's JID. - get_from -- Return the stanza sender's JID. - set_from -- Set the stanza sender's JID. - get_payload -- Return the stanza's XML contents. - set_payload -- Append to the stanza's XML contents. - del_payload -- Remove the stanza's XML contents. - clear -- Reset the stanza's XML contents. - reply -- Reset the stanza and modify the 'to' and 'from' - attributes to prepare for sending a reply. - error -- Set the stanza's type to 'error'. - unhandled -- Callback for when the stanza is not handled by a - stream handler. - exception -- Callback for if an exception is raised while - handling the stanza. - send -- Send the stanza using the stanza's stream. - """ - - name = 'stanza' - namespace = 'jabber:client' - interfaces = set(('type', 'to', 'from', 'id', 'payload')) - types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) - sub_interfaces = tuple() - - def __init__(self, stream=None, xml=None, stype=None, - sto=None, sfrom=None, sid=None): - """ - Create a new stanza. - - Arguments: - stream -- Optional XMLStream responsible for sending this stanza. - xml -- Optional XML contents to initialize stanza values. - stype -- Optional stanza type value. - sto -- Optional string or JID object of the recipient's JID. - sfrom -- Optional string or JID object of the sender's JID. - sid -- Optional ID value for the stanza. - """ - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.setType = self.set_type - self.getTo = self.get_to - self.setTo = self.set_to - self.getFrom = self.get_from - self.setFrom = self.set_from - self.getPayload = self.get_payload - self.setPayload = self.set_payload - self.delPayload = self.del_payload - - self.stream = stream - if stream is not None: - self.namespace = stream.default_ns - ElementBase.__init__(self, xml) - if stype is not None: - self['type'] = stype - if sto is not None: - self['to'] = sto - if sfrom is not None: - self['from'] = sfrom - self.tag = "{%s}%s" % (self.namespace, self.name) - - def set_type(self, value): - """ - Set the stanza's 'type' attribute. - - Only type values contained in StanzaBase.types are accepted. - - Arguments: - value -- One of the values contained in StanzaBase.types - """ - if value in self.types: - self.xml.attrib['type'] = value - return self - - def get_to(self): - """Return the value of the stanza's 'to' attribute.""" - return JID(self._get_attr('to')) - - def set_to(self, value): - """ - Set the 'to' attribute of the stanza. - - Arguments: - value -- A string or JID object representing the recipient's JID. - """ - return self._set_attr('to', str(value)) - - def get_from(self): - """Return the value of the stanza's 'from' attribute.""" - return JID(self._get_attr('from')) - - def set_from(self, value): - """ - Set the 'from' attribute of the stanza. - - Arguments: - from -- A string or JID object representing the sender's JID. - """ - return self._set_attr('from', str(value)) - - def get_payload(self): - """Return a list of XML objects contained in the stanza.""" - return self.xml.getchildren() - - def set_payload(self, value): - """ - Add XML content to the stanza. - - Arguments: - value -- Either an XML or a stanza object, or a list - of XML or stanza objects. - """ - if not isinstance(value, list): - value = [value] - for val in value: - self.append(val) - return self - - def del_payload(self): - """Remove the XML contents of the stanza.""" - self.clear() - return self - - def clear(self): - """ - Remove all XML element contents and plugins. - - Any attribute values will be preserved. - """ - for child in self.xml.getchildren(): - self.xml.remove(child) - for plugin in list(self.plugins.keys()): - del self.plugins[plugin] - return self - - def reply(self): - """ - Reset the stanza and swap its 'from' and 'to' attributes to prepare - for sending a reply stanza. - - For client streams, the 'from' attribute is removed. - """ - # if it's a component, use from - if self.stream and hasattr(self.stream, "is_component") and \ - self.stream.is_component: - self['from'], self['to'] = self['to'], self['from'] - else: - self['to'] = self['from'] - del self['from'] - self.clear() - return self - - def error(self): - """Set the stanza's type to 'error'.""" - self['type'] = 'error' - return self - - def unhandled(self): - """ - Called when no handlers have been registered to process this - stanza. - - Meant to be overridden. - """ - pass - - def exception(self, e): - """ - Handle exceptions raised during stanza processing. - - Meant to be overridden. - """ - log.exception('Error handling {%s}%s stanza' % (self.namespace, - self.name)) - - def send(self): - """Queue the stanza to be sent on the XML stream.""" - self.stream.sendRaw(self.__str__()) - - def __copy__(self): - """ - Return a copy of the stanza object that does not share the - same underlying XML object, but does share the same XML stream. - """ - return self.__class__(xml=copy.deepcopy(self.xml), - stream=self.stream) - - def __str__(self): - """Serialize the stanza's XML to a string.""" - return tostring(self.xml, xmlns='', - stanza_ns=self.namespace, - stream=self.stream) diff --git a/sleekxmpp/xmlstream/test.py b/sleekxmpp/xmlstream/test.py deleted file mode 100644 index a45fb8b..0000000 --- a/sleekxmpp/xmlstream/test.py +++ /dev/null @@ -1,23 +0,0 @@ -import xmlstream -import time -import socket -from handler.callback import Callback -from matcher.xpath import MatchXPath - -def server(): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind(('localhost', 5228)) - s.listen(1) - servers = [] - while True: - conn, addr = s.accept() - server = xmlstream.XMLStream(conn, 'localhost', 5228) - server.registerHandler(Callback('test', MatchXPath('test'), testHandler)) - server.process() - servers.append(server) - -def testHandler(xml): - print("weeeeeeeee!") - -server() diff --git a/sleekxmpp/xmlstream/test.xml b/sleekxmpp/xmlstream/test.xml deleted file mode 100644 index d20dd82..0000000 --- a/sleekxmpp/xmlstream/test.xml +++ /dev/null @@ -1,2 +0,0 @@ -<stream> -</stream> diff --git a/sleekxmpp/xmlstream/testclient.py b/sleekxmpp/xmlstream/testclient.py deleted file mode 100644 index 50eb6c5..0000000 --- a/sleekxmpp/xmlstream/testclient.py +++ /dev/null @@ -1,13 +0,0 @@ -import socket -import time - -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -s.connect(('localhost', 5228)) -s.send("<stream>") -#s.flush() -s.send("<test/>") -s.send("<test/>") -s.send("<test/>") -s.send("</stream>") -#s.flush() -s.close() diff --git a/sleekxmpp/xmlstream/tostring/__init__.py b/sleekxmpp/xmlstream/tostring/__init__.py deleted file mode 100644 index 5852cba..0000000 --- a/sleekxmpp/xmlstream/tostring/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import sys - -# Import the correct tostring and xml_escape functions based on the Python -# version in order to properly handle Unicode. - -if sys.version_info < (3, 0): - from sleekxmpp.xmlstream.tostring.tostring26 import tostring, xml_escape -else: - from sleekxmpp.xmlstream.tostring.tostring import tostring, xml_escape - -__all__ = ['tostring', 'xml_escape'] diff --git a/sleekxmpp/xmlstream/tostring/tostring.py b/sleekxmpp/xmlstream/tostring/tostring.py deleted file mode 100644 index d8f5c5b..0000000 --- a/sleekxmpp/xmlstream/tostring/tostring.py +++ /dev/null @@ -1,95 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - - -def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''): - """ - Serialize an XML object to a Unicode string. - - If namespaces are provided using xmlns or stanza_ns, then elements - that use those namespaces will not include the xmlns attribute in - the output. - - Arguments: - xml -- The XML object to serialize. If the value is None, - then the XML object contained in this stanza - object will be used. - xmlns -- Optional namespace of an element wrapping the XML - object. - stanza_ns -- The namespace of the stanza object that contains - the XML object. - stream -- The XML stream that generated the XML object. - outbuffer -- Optional buffer for storing serializations during - recursive calls. - """ - # Add previous results to the start of the output. - output = [outbuffer] - - # Extract the element's tag name. - tag_name = xml.tag.split('}', 1)[-1] - - # Extract the element's namespace if it is defined. - if '}' in xml.tag: - tag_xmlns = xml.tag.split('}', 1)[0][1:] - else: - tag_xmlns = '' - - # Output the tag name and derived namespace of the element. - namespace = '' - if tag_xmlns not in ['', xmlns, stanza_ns]: - namespace = ' xmlns="%s"' % tag_xmlns - if stream and tag_xmlns in stream.namespace_map: - mapped_namespace = stream.namespace_map[tag_xmlns] - if mapped_namespace: - tag_name = "%s:%s" % (mapped_namespace, tag_name) - output.append("<%s" % tag_name) - output.append(namespace) - - # Output escaped attribute values. - for attrib, value in xml.attrib.items(): - if '{' not in attrib: - value = xml_escape(value) - output.append(' %s="%s"' % (attrib, value)) - - if len(xml) or xml.text: - # If there are additional child elements to serialize. - output.append(">") - if xml.text: - output.append(xml_escape(xml.text)) - if len(xml): - for child in xml.getchildren(): - output.append(tostring(child, tag_xmlns, stanza_ns, stream)) - output.append("</%s>" % tag_name) - elif xml.text: - # If we only have text content. - output.append(">%s</%s>" % (xml_escape(xml.text), tag_name)) - else: - # Empty element. - output.append(" />") - if xml.tail: - # If there is additional text after the element. - output.append(xml_escape(xml.tail)) - return ''.join(output) - - -def xml_escape(text): - """ - Convert special characters in XML to escape sequences. - - Arguments: - text -- The XML text to convert. - """ - text = list(text) - escapes = {'&': '&', - '<': '<', - '>': '>', - "'": ''', - '"': '"'} - for i, c in enumerate(text): - text[i] = escapes.get(c, c) - return ''.join(text) diff --git a/sleekxmpp/xmlstream/tostring/tostring26.py b/sleekxmpp/xmlstream/tostring/tostring26.py deleted file mode 100644 index 0ee432c..0000000 --- a/sleekxmpp/xmlstream/tostring/tostring26.py +++ /dev/null @@ -1,101 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from __future__ import unicode_literals -import types - - -def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''): - """ - Serialize an XML object to a Unicode string. - - If namespaces are provided using xmlns or stanza_ns, then elements - that use those namespaces will not include the xmlns attribute in - the output. - - Arguments: - xml -- The XML object to serialize. If the value is None, - then the XML object contained in this stanza - object will be used. - xmlns -- Optional namespace of an element wrapping the XML - object. - stanza_ns -- The namespace of the stanza object that contains - the XML object. - stream -- The XML stream that generated the XML object. - outbuffer -- Optional buffer for storing serializations during - recursive calls. - """ - # Add previous results to the start of the output. - output = [outbuffer] - - # Extract the element's tag name. - tag_name = xml.tag.split('}', 1)[-1] - - # Extract the element's namespace if it is defined. - if '}' in xml.tag: - tag_xmlns = xml.tag.split('}', 1)[0][1:] - else: - tag_xmlns = u'' - - # Output the tag name and derived namespace of the element. - namespace = u'' - if tag_xmlns not in ['', xmlns, stanza_ns]: - namespace = u' xmlns="%s"' % tag_xmlns - if stream and tag_xmlns in stream.namespace_map: - mapped_namespace = stream.namespace_map[tag_xmlns] - if mapped_namespace: - tag_name = u"%s:%s" % (mapped_namespace, tag_name) - output.append(u"<%s" % tag_name) - output.append(namespace) - - # Output escaped attribute values. - for attrib, value in xml.attrib.items(): - if '{' not in attrib: - value = xml_escape(value) - output.append(u' %s="%s"' % (attrib, value)) - - if len(xml) or xml.text: - # If there are additional child elements to serialize. - output.append(u">") - if xml.text: - output.append(xml_escape(xml.text)) - if len(xml): - for child in xml.getchildren(): - output.append(tostring(child, tag_xmlns, stanza_ns, stream)) - output.append(u"</%s>" % tag_name) - elif xml.text: - # If we only have text content. - output.append(u">%s</%s>" % (xml_escape(xml.text), tag_name)) - else: - # Empty element. - output.append(u" />") - if xml.tail: - # If there is additional text after the element. - output.append(xml_escape(xml.tail)) - return u''.join(output) - - -def xml_escape(text): - """ - Convert special characters in XML to escape sequences. - - Arguments: - text -- The XML text to convert. - """ - if type(text) != types.UnicodeType: - text = list(unicode(text, 'utf-8', 'ignore')) - else: - text = list(text) - escapes = {u'&': u'&', - u'<': u'<', - u'>': u'>', - u"'": u''', - u'"': u'"'} - for i, c in enumerate(text): - text[i] = escapes.get(c, c) - return u''.join(text) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py deleted file mode 100644 index 30b76ce..0000000 --- a/sleekxmpp/xmlstream/xmlstream.py +++ /dev/null @@ -1,948 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -from __future__ import with_statement, unicode_literals - -import copy -import logging -import socket as Socket -import ssl -import sys -import threading -import time -import types -import signal -try: - import queue -except ImportError: - import Queue as queue - -from sleekxmpp.thirdparty.statemachine import StateMachine -from sleekxmpp.xmlstream import Scheduler, tostring -from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET - -# In Python 2.x, file socket objects are broken. A patched socket -# wrapper is provided for this case in filesocket.py. -if sys.version_info < (3, 0): - from sleekxmpp.xmlstream.filesocket import FileSocket, Socket26 - - -# The time in seconds to wait before timing out waiting for response stanzas. -RESPONSE_TIMEOUT = 10 - -# The number of threads to use to handle XML stream events. This is not the -# same as the number of custom event handling threads. HANDLER_THREADS must -# be at least 1. -HANDLER_THREADS = 1 - -# Flag indicating if the SSL library is available for use. -SSL_SUPPORT = True - - -log = logging.getLogger(__name__) - - -class RestartStream(Exception): - """ - Exception to restart stream processing, including - resending the stream header. - """ - - -class XMLStream(object): - """ - An XML stream connection manager and event dispatcher. - - The XMLStream class abstracts away the issues of establishing a - connection with a server and sending and receiving XML "stanzas". - A stanza is a complete XML element that is a direct child of a root - document element. Two streams are used, one for each communication - direction, over the same socket. Once the connection is closed, both - streams should be complete and valid XML documents. - - Three types of events are provided to manage the stream: - Stream -- Triggered based on received stanzas, similar in concept - to events in a SAX XML parser. - Custom -- Triggered manually. - Scheduled -- Triggered based on time delays. - - Typically, stanzas are first processed by a stream event handler which - will then trigger custom events to continue further processing, - especially since custom event handlers may run in individual threads. - - - Attributes: - address -- The hostname and port of the server. - default_ns -- The default XML namespace that will be applied - to all non-namespaced stanzas. - event_queue -- A queue of stream, custom, and scheduled - events to be processed. - filesocket -- A filesocket created from the main connection socket. - Required for ElementTree.iterparse. - namespace_map -- Optional mapping of namespaces to namespace prefixes. - scheduler -- A scheduler object for triggering events - after a given period of time. - send_queue -- A queue of stanzas to be sent on the stream. - socket -- The connection to the server. - ssl_support -- Indicates if a SSL library is available for use. - ssl_version -- The version of the SSL protocol to use. - Defaults to ssl.PROTOCOL_TLSv1. - state -- A state machine for managing the stream's - connection state. - stream_footer -- The start tag and any attributes for the stream's - root element. - stream_header -- The closing tag of the stream's root element. - use_ssl -- Flag indicating if SSL should be used. - use_tls -- Flag indicating if TLS should be used. - stop -- threading Event used to stop all threads. - auto_reconnect-- Flag to determine whether we auto reconnect. - - Methods: - add_event_handler -- Add a handler for a custom event. - add_handler -- Shortcut method for registerHandler. - connect -- Connect to the given server. - del_event_handler -- Remove a handler for a custom event. - disconnect -- Disconnect from the server and terminate - processing. - event -- Trigger a custom event. - get_id -- Return the current stream ID. - incoming_filter -- Optionally filter stanzas before processing. - new_id -- Generate a new, unique ID value. - process -- Read XML stanzas from the stream and apply - matching stream handlers. - reconnect -- Reestablish a connection to the server. - register_handler -- Add a handler for a stream event. - register_stanza -- Add a new stanza object type that may appear - as a direct child of the stream's root. - remove_handler -- Remove a stream handler. - remove_stanza -- Remove a stanza object type. - schedule -- Schedule an event handler to execute after a - given delay. - send -- Send a stanza object on the stream. - send_raw -- Send a raw string on the stream. - send_xml -- Send an XML string on the stream. - set_socket -- Set the stream's socket and generate a new - filesocket. - start_stream_handler -- Perform any stream initialization such - as handshakes. - start_tls -- Establish a TLS connection and restart - the stream. - """ - - def __init__(self, socket=None, host='', port=0): - """ - Establish a new XML stream. - - Arguments: - socket -- Use an existing socket for the stream. - Defaults to None to generate a new socket. - host -- The name of the target server. - Defaults to the empty string. - port -- The port to use for the connection. - Defaults to 0. - """ - # To comply with PEP8, method names now use underscores. - # Deprecated method names are re-mapped for backwards compatibility. - self.startTLS = self.start_tls - self.registerStanza = self.register_stanza - self.removeStanza = self.remove_stanza - self.registerHandler = self.register_handler - self.removeHandler = self.remove_handler - self.setSocket = self.set_socket - self.sendRaw = self.send_raw - self.getId = self.get_id - self.getNewId = self.new_id - self.sendXML = self.send_xml - - self.ssl_support = SSL_SUPPORT - self.ssl_version = ssl.PROTOCOL_TLSv1 - - self.state = StateMachine(('disconnected', 'connected')) - self.state._set_state('disconnected') - - self.address = (host, int(port)) - self.filesocket = None - self.set_socket(socket) - - if sys.version_info < (3, 0): - self.socket_class = Socket26 - else: - self.socket_class = Socket.socket - - self.use_ssl = False - self.use_tls = False - - self.default_ns = '' - self.stream_header = "<stream>" - self.stream_footer = "</stream>" - - self.stop = threading.Event() - self.stream_end_event = threading.Event() - self.stream_end_event.set() - self.event_queue = queue.Queue() - self.send_queue = queue.Queue() - self.scheduler = Scheduler(self.event_queue, self.stop) - - self.namespace_map = {} - - self.__thread = {} - self.__root_stanza = [] - self.__handlers = [] - self.__event_handlers = {} - self.__event_handlers_lock = threading.Lock() - - self._id = 0 - self._id_lock = threading.Lock() - - self.auto_reconnect = True - self.is_client = False - - try: - if hasattr(signal, 'SIGHUP'): - signal.signal(signal.SIGHUP, self._handle_kill) - if hasattr(signal, 'SIGTERM'): - # Used in Windows - signal.signal(signal.SIGTERM, self._handle_kill) - except: - log.debug("Can not set interrupt signal handlers. " + \ - "SleekXMPP is not running from a main thread.") - - def _handle_kill(self, signum, frame): - """ - Capture kill event and disconnect cleanly after first - spawning the "killed" event. - """ - self.event("killed", direct=True) - self.disconnect() - - def new_id(self): - """ - Generate and return a new stream ID in hexadecimal form. - - Many stanzas, handlers, or matchers may require unique - ID values. Using this method ensures that all new ID values - are unique in this stream. - """ - with self._id_lock: - self._id += 1 - return self.get_id() - - def get_id(self): - """ - Return the current unique stream ID in hexadecimal form. - """ - return "%X" % self._id - - def connect(self, host='', port=0, use_ssl=False, - use_tls=True, reattempt=True): - """ - Create a new socket and connect to the server. - - Setting reattempt to True will cause connection attempts to be made - every second until a successful connection is established. - - Arguments: - host -- The name of the desired server for the connection. - port -- Port to connect to on the server. - use_ssl -- Flag indicating if SSL should be used. - use_tls -- Flag indicating if TLS should be used. - reattempt -- Flag indicating if the socket should reconnect - after disconnections. - """ - if host and port: - self.address = (host, int(port)) - - self.is_client = True - # Respect previous SSL and TLS usage directives. - if use_ssl is not None: - self.use_ssl = use_ssl - if use_tls is not None: - self.use_tls = use_tls - - # Repeatedly attempt to connect until a successful connection - # is established. - connected = self.state.transition('disconnected', 'connected', - func=self._connect) - while reattempt and not connected: - connected = self.state.transition('disconnected', 'connected', - func=self._connect) - return connected - - def _connect(self): - self.stop.clear() - self.socket = self.socket_class(Socket.AF_INET, Socket.SOCK_STREAM) - self.socket.settimeout(None) - if self.use_ssl and self.ssl_support: - log.debug("Socket Wrapped for SSL") - ssl_socket = ssl.wrap_socket(self.socket) - if hasattr(self.socket, 'socket'): - # We are using a testing socket, so preserve the top - # layer of wrapping. - self.socket.socket = ssl_socket - else: - self.socket = ssl_socket - - try: - log.debug("Connecting to %s:%s" % self.address) - self.socket.connect(self.address) - self.set_socket(self.socket, ignore=True) - #this event is where you should set your application state - self.event("connected", direct=True) - return True - except Socket.error as serr: - error_msg = "Could not connect to %s:%s. Socket Error #%s: %s" - log.error(error_msg % (self.address[0], self.address[1], - serr.errno, serr.strerror)) - time.sleep(1) - return False - - def disconnect(self, reconnect=False): - """ - Terminate processing and close the XML streams. - - Optionally, the connection may be reconnected and - resume processing afterwards. - - Arguments: - reconnect -- Flag indicating if the connection - and processing should be restarted. - Defaults to False. - """ - self.state.transition('connected', 'disconnected', wait=0.0, - func=self._disconnect, args=(reconnect,)) - - def _disconnect(self, reconnect=False): - # Send the end of stream marker. - self.send_raw(self.stream_footer) - # Wait for confirmation that the stream was - # closed in the other direction. - if not reconnect: - self.auto_reconnect = False - self.stream_end_event.wait(4) - if not self.auto_reconnect: - self.stop.set() - try: - self.socket.close() - self.filesocket.close() - self.socket.shutdown(Socket.SHUT_RDWR) - except Socket.error as serr: - pass - finally: - #clear your application state - self.event("disconnected", direct=True) - return True - - def reconnect(self): - """ - Reset the stream's state and reconnect to the server. - """ - log.debug("reconnecting...") - self.state.transition('connected', 'disconnected', wait=2.0, - func=self._disconnect, args=(True,)) - log.debug("connecting...") - return self.state.transition('disconnected', 'connected', - wait=2.0, func=self._connect) - - def set_socket(self, socket, ignore=False): - """ - Set the socket to use for the stream. - - The filesocket will be recreated as well. - - Arguments: - socket -- The new socket to use. - ignore -- don't set the state - """ - self.socket = socket - if socket is not None: - # ElementTree.iterparse requires a file. - # 0 buffer files have to be binary. - - # Use the correct fileobject type based on the Python - # version to work around a broken implementation in - # Python 2.x. - if sys.version_info < (3, 0): - self.filesocket = FileSocket(self.socket) - else: - self.filesocket = self.socket.makefile('rb', 0) - if not ignore: - self.state._set_state('connected') - - def start_tls(self): - """ - Perform handshakes for TLS. - - If the handshake is successful, the XML stream will need - to be restarted. - """ - if self.ssl_support: - log.info("Negotiating TLS") - log.info("Using SSL version: %s" % str(self.ssl_version)) - ssl_socket = ssl.wrap_socket(self.socket, - ssl_version=self.ssl_version, - do_handshake_on_connect=False) - if hasattr(self.socket, 'socket'): - # We are using a testing socket, so preserve the top - # layer of wrapping. - self.socket.socket = ssl_socket - else: - self.socket = ssl_socket - self.socket.do_handshake() - self.set_socket(self.socket) - return True - else: - log.warning("Tried to enable TLS, but ssl module not found.") - return False - - def start_stream_handler(self, xml): - """ - Perform any initialization actions, such as handshakes, once the - stream header has been sent. - - Meant to be overridden. - """ - pass - - def register_stanza(self, stanza_class): - """ - Add a stanza object class as a known root stanza. A root stanza is - one that appears as a direct child of the stream's root element. - - Stanzas that appear as substanzas of a root stanza do not need to - be registered here. That is done using register_stanza_plugin() from - sleekxmpp.xmlstream.stanzabase. - - Stanzas that are not registered will not be converted into - stanza objects, but may still be processed using handlers and - matchers. - - Arguments: - stanza_class -- The top-level stanza object's class. - """ - self.__root_stanza.append(stanza_class) - - def remove_stanza(self, stanza_class): - """ - Remove a stanza from being a known root stanza. A root stanza is - one that appears as a direct child of the stream's root element. - - Stanzas that are not registered will not be converted into - stanza objects, but may still be processed using handlers and - matchers. - """ - del self.__root_stanza[stanza_class] - - def add_handler(self, mask, pointer, name=None, disposable=False, - threaded=False, filter=False, instream=False): - """ - A shortcut method for registering a handler using XML masks. - - Arguments: - mask -- An XML snippet matching the structure of the - stanzas that will be passed to this handler. - pointer -- The handler function itself. - name -- A unique name for the handler. A name will - be generated if one is not provided. - disposable -- Indicates if the handler should be discarded - after one use. - threaded -- Deprecated. Remains for backwards compatibility. - filter -- Deprecated. Remains for backwards compatibility. - instream -- Indicates if the handler should execute during - stream processing and not during normal event - processing. - """ - # To prevent circular dependencies, we must load the matcher - # and handler classes here. - from sleekxmpp.xmlstream.matcher import MatchXMLMask - from sleekxmpp.xmlstream.handler import XMLCallback - - if name is None: - name = 'add_handler_%s' % self.getNewId() - self.registerHandler(XMLCallback(name, MatchXMLMask(mask), pointer, - once=disposable, instream=instream)) - - def register_handler(self, handler, before=None, after=None): - """ - Add a stream event handler that will be executed when a matching - stanza is received. - - Arguments: - handler -- The handler object to execute. - """ - if handler.stream is None: - self.__handlers.append(handler) - handler.stream = self - - def remove_handler(self, name): - """ - Remove any stream event handlers with the given name. - - Arguments: - name -- The name of the handler. - """ - idx = 0 - for handler in self.__handlers: - if handler.name == name: - self.__handlers.pop(idx) - return True - idx += 1 - return False - - def add_event_handler(self, name, pointer, - threaded=False, disposable=False): - """ - Add a custom event handler that will be executed whenever - its event is manually triggered. - - Arguments: - name -- The name of the event that will trigger - this handler. - pointer -- The function to execute. - threaded -- If set to True, the handler will execute - in its own thread. Defaults to False. - disposable -- If set to True, the handler will be - discarded after one use. Defaults to False. - """ - if not name in self.__event_handlers: - self.__event_handlers[name] = [] - self.__event_handlers[name].append((pointer, threaded, disposable)) - - def del_event_handler(self, name, pointer): - """ - Remove a function as a handler for an event. - - Arguments: - name -- The name of the event. - pointer -- The function to remove as a handler. - """ - if not name in self.__event_handlers: - return - - # Need to keep handlers that do not use - # the given function pointer - def filter_pointers(handler): - return handler[0] != pointer - - self.__event_handlers[name] = filter(filter_pointers, - self.__event_handlers[name]) - - def event_handled(self, name): - """ - Indicates if an event has any associated handlers. - - Returns the number of registered handlers. - - Arguments: - name -- The name of the event to check. - """ - return len(self.__event_handlers.get(name, [])) - - def event(self, name, data={}, direct=False): - """ - Manually trigger a custom event. - - Arguments: - name -- The name of the event to trigger. - data -- Data that will be passed to each event handler. - Defaults to an empty dictionary. - direct -- Runs the event directly if True, skipping the - event queue. All event handlers will run in the - same thread. - """ - for handler in self.__event_handlers.get(name, []): - if direct: - try: - handler[0](copy.copy(data)) - except Exception as e: - error_msg = 'Error processing event handler: %s' - log.exception(error_msg % str(handler[0])) - if hasattr(data, 'exception'): - data.exception(e) - else: - self.event_queue.put(('event', handler, copy.copy(data))) - - if handler[2]: - # If the handler is disposable, we will go ahead and - # remove it now instead of waiting for it to be - # processed in the queue. - with self.__event_handlers_lock: - try: - h_index = self.__event_handlers[name].index(handler) - self.__event_handlers[name].pop(h_index) - except: - pass - - def schedule(self, name, seconds, callback, args=None, - kwargs=None, repeat=False): - """ - Schedule a callback function to execute after a given delay. - - Arguments: - name -- A unique name for the scheduled callback. - seconds -- The time in seconds to wait before executing. - callback -- A pointer to the function to execute. - args -- A tuple of arguments to pass to the function. - kwargs -- A dictionary of keyword arguments to pass to - the function. - repeat -- Flag indicating if the scheduled event should - be reset and repeat after executing. - """ - self.scheduler.add(name, seconds, callback, args, kwargs, - repeat, qpointer=self.event_queue) - - def incoming_filter(self, xml): - """ - Filter incoming XML objects before they are processed. - - Possible uses include remapping namespaces, or correcting elements - from sources with incorrect behavior. - - Meant to be overridden. - """ - return xml - - def send(self, data, mask=None, timeout=RESPONSE_TIMEOUT): - """ - A wrapper for send_raw for sending stanza objects. - - May optionally block until an expected response is received. - - Arguments: - data -- The stanza object to send on the stream. - mask -- Deprecated. An XML snippet matching the structure - of the expected response. Execution will block - in this thread until the response is received - or a timeout occurs. - timeout -- Time in seconds to wait for a response before - continuing. Defaults to RESPONSE_TIMEOUT. - """ - if hasattr(mask, 'xml'): - mask = mask.xml - data = str(data) - if mask is not None: - log.warning("Use of send mask waiters is deprecated.") - wait_for = Waiter("SendWait_%s" % self.new_id(), - MatchXMLMask(mask)) - self.register_handler(wait_for) - self.send_raw(data) - if mask is not None: - return wait_for.wait(timeout) - - def send_raw(self, data): - """ - Send raw data across the stream. - - Arguments: - data -- Any string value. - """ - self.send_queue.put(data) - return True - - def send_xml(self, data, mask=None, timeout=RESPONSE_TIMEOUT): - """ - Send an XML object on the stream, and optionally wait - for a response. - - Arguments: - data -- The XML object to send on the stream. - mask -- Deprecated. An XML snippet matching the structure - of the expected response. Execution will block - in this thread until the response is received - or a timeout occurs. - timeout -- Time in seconds to wait for a response before - continuing. Defaults to RESPONSE_TIMEOUT. - """ - return self.send(tostring(data), mask, timeout) - - def process(self, threaded=True): - """ - Initialize the XML streams and begin processing events. - - The number of threads used for processing stream events is determined - by HANDLER_THREADS. - - Arguments: - threaded -- If threaded=True then event dispatcher will run - in a separate thread, allowing for the stream to be - used in the background for another application. - Defaults to True. - - Event handlers and the send queue will be threaded - regardless of this parameter's value. - """ - self.scheduler.process(threaded=True) - - def start_thread(name, target): - self.__thread[name] = threading.Thread(name=name, target=target) - self.__thread[name].start() - - for t in range(0, HANDLER_THREADS): - log.debug("Starting HANDLER THREAD") - start_thread('stream_event_handler_%s' % t, self._event_runner) - - start_thread('send_thread', self._send_thread) - - if threaded: - # Run the XML stream in the background for another application. - start_thread('process', self._process) - else: - self._process() - - def _process(self): - """ - Start processing the XML streams. - - Processing will continue after any recoverable errors - if reconnections are allowed. - """ - firstrun = True - - # The body of this loop will only execute once per connection. - # Additional passes will be made only if an error occurs and - # reconnecting is permitted. - while firstrun or (self.auto_reconnect and not self.stop.isSet()): - firstrun = False - try: - if self.is_client: - self.send_raw(self.stream_header) - # The call to self.__read_xml will block and prevent - # the body of the loop from running until a disconnect - # occurs. After any reconnection, the stream header will - # be resent and processing will resume. - while not self.stop.isSet() and self.__read_xml(): - # Ensure the stream header is sent for any - # new connections. - if self.is_client: - self.send_raw(self.stream_header) - except KeyboardInterrupt: - log.debug("Keyboard Escape Detected in _process") - self.stop.set() - except SystemExit: - log.debug("SystemExit in _process") - self.stop.set() - except Socket.error: - log.exception('Socket Error') - except: - if not self.stop.isSet(): - log.exception('Connection error.') - if not self.stop.isSet() and self.auto_reconnect: - self.reconnect() - else: - self.disconnect() - self.event_queue.put(('quit', None, None)) - self.scheduler.run = False - - def __read_xml(self): - """ - Parse the incoming XML stream, raising stream events for - each received stanza. - """ - depth = 0 - root = None - for (event, xml) in ET.iterparse(self.filesocket, (b'end', b'start')): - if event == b'start': - if depth == 0: - # We have received the start of the root element. - root = xml - # Perform any stream initialization actions, such - # as handshakes. - self.stream_end_event.clear() - self.start_stream_handler(root) - depth += 1 - if event == b'end': - depth -= 1 - if depth == 0: - # The stream's root element has closed, - # terminating the stream. - log.debug("End of stream recieved") - self.stream_end_event.set() - return False - elif depth == 1: - # We only raise events for stanzas that are direct - # children of the root element. - try: - self.__spawn_event(xml) - except RestartStream: - return True - if root: - # Keep the root element empty of children to - # save on memory use. - root.clear() - log.debug("Ending read XML loop") - - def _build_stanza(self, xml, default_ns=None): - """ - Create a stanza object from a given XML object. - - If a specialized stanza type is not found for the XML, then - a generic StanzaBase stanza will be returned. - - Arguments: - xml -- The XML object to convert into a stanza object. - default_ns -- Optional default namespace to use instead of the - stream's current default namespace. - """ - if default_ns is None: - default_ns = self.default_ns - stanza_type = StanzaBase - for stanza_class in self.__root_stanza: - if xml.tag == "{%s}%s" % (default_ns, stanza_class.name): - stanza_type = stanza_class - break - stanza = stanza_type(self, xml) - return stanza - - def __spawn_event(self, xml): - """ - Analyze incoming XML stanzas and convert them into stanza - objects if applicable and queue stream events to be processed - by matching handlers. - - Arguments: - xml -- The XML stanza to analyze. - """ - log.debug("RECV: %s" % tostring(xml, - xmlns=self.default_ns, - stream=self)) - # Apply any preprocessing filters. - xml = self.incoming_filter(xml) - - # Convert the raw XML object into a stanza object. If no registered - # stanza type applies, a generic StanzaBase stanza will be used. - stanza_type = StanzaBase - for stanza_class in self.__root_stanza: - if xml.tag == "{%s}%s" % (self.default_ns, stanza_class.name): - stanza_type = stanza_class - break - stanza = stanza_type(self, xml) - - # Match the stanza against registered handlers. Handlers marked - # to run "in stream" will be executed immediately; the rest will - # be queued. - unhandled = True - for handler in self.__handlers: - if handler.match(stanza): - stanza_copy = stanza_type(self, copy.deepcopy(xml)) - handler.prerun(stanza_copy) - self.event_queue.put(('stanza', handler, stanza_copy)) - try: - if handler.check_delete(): - self.__handlers.pop(self.__handlers.index(handler)) - except: - pass # not thread safe - unhandled = False - - # Some stanzas require responses, such as Iq queries. A default - # handler will be executed immediately for this case. - if unhandled: - stanza.unhandled() - - def _threaded_event_wrapper(self, func, args): - """ - Capture exceptions for event handlers that run - in individual threads. - - Arguments: - func -- The event handler to execute. - args -- Arguments to the event handler. - """ - try: - func(*args) - except Exception as e: - error_msg = 'Error processing event handler: %s' - log.exception(error_msg % str(func)) - if hasattr(args[0], 'exception'): - args[0].exception(e) - - def _event_runner(self): - """ - Process the event queue and execute handlers. - - The number of event runner threads is controlled by HANDLER_THREADS. - - Stream event handlers will all execute in this thread. Custom event - handlers may be spawned in individual threads. - """ - log.debug("Loading event runner") - try: - while not self.stop.isSet(): - try: - event = self.event_queue.get(True, timeout=5) - except queue.Empty: - event = None - if event is None: - continue - - etype, handler = event[0:2] - args = event[2:] - - if etype == 'stanza': - try: - handler.run(args[0]) - except Exception as e: - error_msg = 'Error processing stream handler: %s' - log.exception(error_msg % handler.name) - args[0].exception(e) - elif etype == 'schedule': - try: - log.debug(args) - handler(*args[0]) - except: - log.exception('Error processing scheduled task') - elif etype == 'event': - func, threaded, disposable = handler - try: - if threaded: - x = threading.Thread( - name="Event_%s" % str(func), - target=self._threaded_event_wrapper, - args=(func, args)) - x.start() - else: - func(*args) - except Exception as e: - error_msg = 'Error processing event handler: %s' - log.exception(error_msg % str(func)) - if hasattr(args[0], 'exception'): - args[0].exception(e) - elif etype == 'quit': - log.debug("Quitting event runner thread") - return False - except KeyboardInterrupt: - log.debug("Keyboard Escape Detected in _event_runner") - self.disconnect() - return - except SystemExit: - self.disconnect() - self.event_queue.put(('quit', None, None)) - return - - def _send_thread(self): - """ - Extract stanzas from the send queue and send them on the stream. - """ - try: - while not self.stop.isSet(): - try: - data = self.send_queue.get(True, 1) - except queue.Empty: - continue - log.debug("SEND: %s" % data) - try: - self.socket.send(data.encode('utf-8')) - except: - log.warning("Failed to send %s" % data) - self.disconnect(self.auto_reconnect) - except KeyboardInterrupt: - log.debug("Keyboard Escape Detected in _send_thread") - self.disconnect() - return - except SystemExit: - self.disconnect() - self.event_queue.put(('quit', None, None)) - return |
