diff --git a/src/common/xmpp/auth_nb.py b/src/common/xmpp/auth_nb.py index e5e51f6b8..b85b51689 100644 --- a/src/common/xmpp/auth_nb.py +++ b/src/common/xmpp/auth_nb.py @@ -13,12 +13,14 @@ ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -''' + +""" Provides plugs for SASL and NON-SASL authentication mechanisms. -Can be used both for client and transport authentication. +Can be used both for client and transport authentication See client_nb.py -''' +""" + from protocol import NS_SASL, NS_SESSION, NS_STREAMS, NS_BIND, NS_AUTH from protocol import Node, NodeProcessed, isResultNode, Iq, Protocol, JID from plugin import PlugIn @@ -49,7 +51,8 @@ SASL_UNSUPPORTED = 'not-supported' SASL_IN_PROCESS = 'in-process' def challenge_splitter(data): - ''' Helper function that creates a dict from challenge string. + """ + Helper function that creates a dict from challenge string Sample challenge string: username="example.org",realm="somerealm",\ @@ -59,7 +62,7 @@ def challenge_splitter(data): Expected result for challan: dict['qop'] = ('auth','auth-int','auth-conf') dict['realm'] = 'somerealm' - ''' + """ X_KEYWORD, X_VALUE, X_END = 0, 1, 2 quotes_open = False keyword, value = '', '' @@ -112,16 +115,17 @@ def challenge_splitter(data): class SASL(PlugIn): - ''' + """ Implements SASL authentication. Can be plugged into NonBlockingClient - to start authentication. - ''' + to start authentication + """ + def __init__(self, username, password, on_sasl): - ''' + """ :param user: XMPP username :param password: XMPP password :param on_sasl: Callback, will be called after each SASL auth-step. - ''' + """ PlugIn.__init__(self) self.username = username self.password = password @@ -141,7 +145,9 @@ class SASL(PlugIn): self.startsasl = None def plugout(self): - ''' Remove SASL handlers from owner's dispatcher. Used internally. ''' + """ + Remove SASL handlers from owner's dispatcher. Used internally + """ if 'features' in self._owner.__dict__: self._owner.UnregisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS) @@ -156,13 +162,13 @@ class SASL(PlugIn): xmlns=NS_SASL) def auth(self): - ''' + """ Start authentication. Result can be obtained via "SASL.startsasl" - attribute and will be either SASL_SUCCESS or SASL_FAILURE. + attribute and will be either SASL_SUCCESS or SASL_FAILURE Note that successfull auth will take at least two Dispatcher.Process() calls. - ''' + """ if self.startsasl: pass elif self._owner.Dispatcher.Stream.features: @@ -176,7 +182,9 @@ class SASL(PlugIn): self.FeaturesHandler, xmlns=NS_STREAMS) def FeaturesHandler(self, conn, feats): - ''' Used to determine if server supports SASL auth. Used internally. ''' + """ + Used to determine if server supports SASL auth. Used internally + """ if not feats.getTag('mechanisms', namespace=NS_SASL): self.startsasl='not-supported' log.error('SASL not supported by server') @@ -235,7 +243,9 @@ class SASL(PlugIn): return def SASLHandler(self, conn, challenge): - ''' Perform next SASL auth step. Used internally. ''' + """ + Perform next SASL auth step. Used internally + """ if challenge.getNamespace() != NS_SASL: return ### Handle Auth result @@ -359,12 +369,15 @@ class SASL(PlugIn): class NonBlockingNonSASL(PlugIn): - ''' + """ Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and - transport authentication. - ''' + transport authentication + """ + def __init__(self, user, password, resource, on_auth): - ''' Caches username, password and resource for auth. ''' + """ + Caches username, password and resource for auth + """ PlugIn.__init__(self) self.user = user if password is None: @@ -375,10 +388,10 @@ class NonBlockingNonSASL(PlugIn): self.on_auth = on_auth def plugin(self, owner): - ''' + """ Determine the best auth method (digest/0k/plain) and use it for auth. - Returns used method name on success. Used internally. - ''' + Returns used method name on success. Used internally + """ log.info('Querying server about possible auth methods') self.owner = owner @@ -438,10 +451,11 @@ class NonBlockingNonSASL(PlugIn): class NonBlockingBind(PlugIn): - ''' + """ Bind some JID to the current connection to allow router know of our - location. Must be plugged after successful SASL auth. - ''' + location. Must be plugged after successful SASL auth + """ + def __init__(self): PlugIn.__init__(self) self.bound = None @@ -459,10 +473,10 @@ class NonBlockingBind(PlugIn): xmlns=NS_STREAMS) def FeaturesHandler(self, conn, feats): - ''' + """ Determine if server supports resource binding and set some internal - attributes accordingly. - ''' + attributes accordingly + """ if not feats.getTag('bind', namespace=NS_BIND): log.error('Server does not requested binding.') # we try to bind resource anyway @@ -476,14 +490,16 @@ class NonBlockingBind(PlugIn): self.bound = [] def plugout(self): - ''' Remove Bind handler from owner's dispatcher. Used internally. ''' + """ + Remove Bind handler from owner's dispatcher. Used internally + """ self._owner.UnregisterHandler('features', self.FeaturesHandler, xmlns=NS_STREAMS) def NonBlockingBind(self, resource=None, on_bound=None): - ''' Perform binding. - Use provided resource name or random (if not provided). - ''' + """ + Perform binding. Use provided resource name or random (if not provided). + """ self.on_bound = on_bound self._resource = resource if self._resource: diff --git a/src/common/xmpp/bosh.py b/src/common/xmpp/bosh.py index bb6ea4dd8..2658668ba 100644 --- a/src/common/xmpp/bosh.py +++ b/src/common/xmpp/bosh.py @@ -125,11 +125,12 @@ class NonBlockingBOSH(NonBlockingTransport): log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.get_state(), self.fd)) def on_http_request_possible(self): - ''' + """ Called when HTTP request it's possible to send a HTTP request. It can be when - socket is connected or when HTTP response arrived. + socket is connected or when HTTP response arrived + There should be always one pending request to BOSH CM. - ''' + """ log.debug('on_http_req possible, state:\n%s' % self.get_current_state()) if self.get_state()==DISCONNECTED: return @@ -149,14 +150,18 @@ class NonBlockingBOSH(NonBlockingTransport): def get_socket_in(self, state): - ''' gets sockets in desired state ''' + """ + Get sockets in desired state + """ for s in self.http_socks: if s.get_state()==state: return s return None def get_free_socket(self): - ''' Selects and returns socket eligible for sending a data to.''' + """ + Select and returns socket eligible for sending a data to + """ if self.http_pipelining: return self.get_socket_in(CONNECTED) else: @@ -176,10 +181,10 @@ class NonBlockingBOSH(NonBlockingTransport): def send_BOSH(self, payload): - ''' + """ Tries to send a stanza in payload by appeding it to a buffer and plugging a free socket for writing. - ''' + """ total_pending_reqs = sum([s.pending_requests for s in self.http_socks]) # when called after HTTP response (Payload=None) and when there are already @@ -236,15 +241,16 @@ class NonBlockingBOSH(NonBlockingTransport): log.error('=====!!!!!!!!====> Couldn\'t get free socket in plug_socket())') def build_stanza(self, socket): - ''' - Builds a BOSH body tag from data in buffers and adds key, rid and ack - attributes to it. + """ + Build a BOSH body tag from data in buffers and adds key, rid and ack + attributes to it + This method is called from _do_send() of underlying transport. This is to - ensure rid and keys will be processed in correct order. If I generate them - before plugging a socket for write (and did it for two sockets/HTTP - connections) in parallel, they might be sent in wrong order, which results - in violating the BOSH session and server-side disconnect. - ''' + ensure rid and keys will be processed in correct order. If I generate + them before plugging a socket for write (and did it for two sockets/HTTP + connections) in parallel, they might be sent in wrong order, which + results in violating the BOSH session and server-side disconnect. + """ if self.prio_bosh_stanzas: stanza, add_payload = self.prio_bosh_stanzas.pop(0) if add_payload: @@ -285,10 +291,11 @@ class NonBlockingBOSH(NonBlockingTransport): self.wait_cb_time) def on_persistent_fallback(self, socket): - ''' - Called from underlying transport when server closes TCP connection. + """ + Called from underlying transport when server closes TCP connection + :param socket: disconnected transport object - ''' + """ if socket.http_persistent: log.warn('Fallback to nonpersistent HTTP (no pipelining as well)') socket.http_persistent = False @@ -302,9 +309,10 @@ class NonBlockingBOSH(NonBlockingTransport): def handle_body_attrs(self, stanza_attrs): - ''' - Called for each incoming body stanza from dispatcher. Checks body attributes. - ''' + """ + Called for each incoming body stanza from dispatcher. Checks body + attributes. + """ self.remove_bosh_wait_timeout() if self.after_init: @@ -345,7 +353,9 @@ class NonBlockingBOSH(NonBlockingTransport): def append_stanza(self, stanza): - ''' appends stanza to a buffer to send ''' + """ + Append stanza to a buffer to send + """ if stanza: if isinstance(stanza, tuple): # stanza is tuple of BOSH stanza and bool value for whether to add payload @@ -378,7 +388,9 @@ class NonBlockingBOSH(NonBlockingTransport): def boshify_stanzas(self, stanzas=[], body_attrs=None): - ''' wraps zero to many stanzas by body tag with xmlns and sid ''' + """ + Wraps zero to many stanzas by body tag with xmlns and sid + """ log.debug('boshify_staza - type is: %s, stanza is %s' % (type(stanzas), stanzas)) tag = BOSHBody(attrs={'sid': self.bosh_sid}) tag.setPayload(stanzas) @@ -470,10 +482,10 @@ def get_rand_number(): class AckChecker(): - ''' + """ Class for generating rids and generating and checking acknowledgements in - BOSH messages. - ''' + BOSH messages + """ def __init__(self): self.rid = get_rand_number() self.ack = 1 @@ -516,9 +528,9 @@ class AckChecker(): class KeyStack(): - ''' + """ Class implementing key sequences for BOSH messages - ''' + """ def __init__(self, count): self.count = count self.keys = [] diff --git a/src/common/xmpp/c14n.py b/src/common/xmpp/c14n.py index bccce8155..521f3144b 100644 --- a/src/common/xmpp/c14n.py +++ b/src/common/xmpp/c14n.py @@ -18,7 +18,10 @@ ## along with Gajim. If not, see . ## -''' XML canonicalisation methods (for XEP-0116) ''' +""" +XML canonicalisation methods (for XEP-0116) +""" + from simplexml import ustr def c14n(node, is_buggy): diff --git a/src/common/xmpp/client_nb.py b/src/common/xmpp/client_nb.py index 7d82ae4e3..b2b1d3b35 100644 --- a/src/common/xmpp/client_nb.py +++ b/src/common/xmpp/client_nb.py @@ -16,9 +16,10 @@ # $Id: client.py,v 1.52 2006/01/02 19:40:55 normanr Exp $ -''' +""" Client class establishs connection to XMPP Server and handles authentication -''' +""" + import socket import transports_nb, dispatcher_nb, auth_nb, roster_nb, protocol, bosh from protocol import NS_TLS @@ -28,21 +29,22 @@ log = logging.getLogger('gajim.c.x.client_nb') class NonBlockingClient: - ''' + """ Client class is XMPP connection mountpoint. Objects for authentication, network communication, roster, xml parsing ... are plugged to client object. Client implements the abstract behavior - mostly negotioation and callbacks - handling, whereas underlying modules take care of feature-specific logic. - ''' + handling, whereas underlying modules take care of feature-specific logic + """ + def __init__(self, domain, idlequeue, caller=None): - ''' - Caches connection data: + """ + Caches connection data :param domain: domain - for to: attribute (from account info) :param idlequeue: processing idlequeue :param caller: calling object - it has to implement methods _event_dispatcher which is called from dispatcher instance - ''' + """ self.Namespace = protocol.NS_CLIENT self.defaultNamespace = self.Namespace @@ -69,10 +71,10 @@ class NonBlockingClient: self.protocol_type = 'XMPP' def disconnect(self, message=''): - ''' - Called on disconnection - disconnect callback is picked based on state - of the client. - ''' + """ + Called on disconnection - disconnect callback is picked based on state of + the client. + """ # to avoid recursive calls if self.disconnecting: return @@ -132,9 +134,10 @@ class NonBlockingClient: self.disconnecting = False def connect(self, on_connect, on_connect_failure, hostname=None, port=5222, - on_proxy_failure=None, proxy=None, secure_tuple=('plain', None, None)): - ''' - Open XMPP connection (open XML streams in both directions). + on_proxy_failure=None, proxy=None, secure_tuple=('plain', None, + None)): + """ + Open XMPP connection (open XML streams in both directions) :param on_connect: called after stream is successfully opened :param on_connect_failure: called when error occures during connection @@ -150,7 +153,7 @@ class NonBlockingClient: 'tls' - TLS established after negotiation with starttls, or 'plain'. cacerts, mycerts - see tls_nb.NonBlockingTLS constructor for more details - ''' + """ self.on_connect = on_connect self.on_connect_failure=on_connect_failure self.on_proxy_failure = on_proxy_failure @@ -223,7 +226,11 @@ class NonBlockingClient: on_success=self._try_next_ip) def _resolve_hostname(self, hostname, port, on_success): - ''' wrapper for getaddinfo call. FIXME: getaddinfo blocks''' + """ + Wrapper for getaddinfo call + + FIXME: getaddinfo blocks + """ try: self.ip_addresses = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM) @@ -234,7 +241,9 @@ class NonBlockingClient: on_success() def _try_next_ip(self, err_message=None): - '''Iterates over IP addresses tries to connect to it''' + """ + Iterate over IP addresses tries to connect to it + """ if err_message: log.debug('While looping over DNS A records: %s' % err_message) if self.ip_addresses == []: @@ -249,18 +258,20 @@ class NonBlockingClient: on_connect_failure=self._try_next_ip) def incoming_stream_version(self): - ''' gets version of xml stream''' + """ + Get version of xml stream + """ if 'version' in self.Dispatcher.Stream._document_attrs: return self.Dispatcher.Stream._document_attrs['version'] else: return None def _xmpp_connect(self, socket_type=None): - ''' - Starts XMPP connecting process - opens the XML stream. Is called after TCP + """ + Start XMPP connecting process - open the XML stream. Is called after TCP connection is established or after switch to TLS when successfully negotiated with . - ''' + """ # socket_type contains info which transport connection was established if not socket_type: if self.Connection.ssl_lib: @@ -273,19 +284,19 @@ class NonBlockingClient: self._xmpp_connect_machine() def _xmpp_connect_machine(self, mode=None, data=None): - ''' - Finite automaton taking care of stream opening and features tag - handling. Calls _on_stream_start when stream is started, and disconnect() - on failure. - ''' + """ + Finite automaton taking care of stream opening and features tag handling. + Calls _on_stream_start when stream is started, and disconnect() on + failure. + """ log.info('-------------xmpp_connect_machine() >> mode: %s, data: %s...' % (mode, str(data)[:20])) def on_next_receive(mode): - ''' - Sets desired on_receive callback on transport based on the state of + """ + Set desired on_receive callback on transport based on the state of connect_machine. - ''' + """ log.info('setting %s on next receive' % mode) if mode is None: self.onreceive(None) # switch to Dispatcher.ProcessNonBlocking @@ -346,10 +357,10 @@ class NonBlockingClient: self._on_stream_start() def _on_stream_start(self): - ''' + """ Called after XMPP stream is opened. TLS negotiation may follow if supported and desired. - ''' + """ self.stream_started = True self.onreceive(None) @@ -381,7 +392,9 @@ class NonBlockingClient: assert False, 'Stream opened for unsupported connection' def _tls_negotiation_handler(self, con=None, tag=None): - ''' takes care of TLS negotioation with ''' + """ + Take care of TLS negotioation with + """ log.info('-------------tls_negotiaton_handler() >> tag: %s' % tag) if not con and not tag: # starting state when we send the @@ -407,15 +420,17 @@ class NonBlockingClient: on_fail = lambda: self.disconnect('error while etabilishing TLS')) def _on_connect(self): - ''' preceeds call of on_connect callback ''' + """ + Preceed call of on_connect callback + """ self.onreceive(None) self.on_connect(self, self.connected) def raise_event(self, event_type, data): - ''' - Raises event to connection instance. DATA_SENT and DATA_RECIVED events + """ + Raise event to connection instance. DATA_SENT and DATA_RECIVED events are used in XML console to show XMPP traffic - ''' + """ log.info('raising event from transport: :::::%s::::\n_____________\n%s\n_____________\n' % (event_type,data)) if hasattr(self, 'Dispatcher'): self.Dispatcher.Event('', event_type, data) @@ -425,9 +440,9 @@ class NonBlockingClient: ############################################################################### def auth(self, user, password, resource='', sasl=True, on_auth=None): - ''' + """ Authenticate connnection and bind resource. If resource is not provided - random one or library name used. + random one or library name used :param user: XMPP username :param password: XMPP password @@ -435,7 +450,7 @@ class NonBlockingClient: :param sasl: Boolean indicating if SASL shall be used. (default: True) :param on_auth: Callback, called after auth. On auth failure, argument is None. - ''' + """ self._User, self._Password = user, password self._Resource, self._sasl = resource, sasl self.on_auth = on_auth @@ -443,7 +458,9 @@ class NonBlockingClient: return def _on_old_auth(self, res): - ''' Callback used by NON-SASL auth. On auth failure, res is None. ''' + """ + Callback used by NON-SASL auth. On auth failure, res is None + """ if res: self.connected += '+old_auth' self.on_auth(self, 'old_auth') @@ -451,7 +468,9 @@ class NonBlockingClient: self.on_auth(self, None) def _on_sasl_auth(self, res): - ''' Used internally. On auth failure, res is None. ''' + """ + Used internally. On auth failure, res is None + """ self.onreceive(None) if res: self.connected += '+sasl' @@ -460,7 +479,9 @@ class NonBlockingClient: self.on_auth(self, None) def _on_doc_attrs(self): - ''' Plug authentication objects and start auth. ''' + """ + Plug authentication objects and start auth + """ if self._sasl: auth_nb.SASL.get_instance(self._User, self._Password, self._on_start_sasl).PlugIn(self) @@ -474,7 +495,9 @@ class NonBlockingClient: return True def _on_start_sasl(self, data=None): - ''' Callback used by SASL, called on each auth step.''' + """ + Callback used by SASL, called on each auth step + """ if data: self.Dispatcher.ProcessNonBlocking(data) if not 'SASL' in self.__dict__: @@ -504,20 +527,26 @@ class NonBlockingClient: return True def initRoster(self, version=''): - ''' Plug in the roster. ''' + """ + Plug in the roster + """ if not self.__dict__.has_key('NonBlockingRoster'): return roster_nb.NonBlockingRoster.get_instance(version=version).PlugIn(self) def getRoster(self, on_ready=None, force=False): - ''' Return the Roster instance, previously plugging it in and - requesting roster from server if needed. ''' + """ + Return the Roster instance, previously plugging it in and requesting + roster from server if needed + """ if self.__dict__.has_key('NonBlockingRoster'): return self.NonBlockingRoster.getRoster(on_ready, force) return None def sendPresence(self, jid=None, typ=None, requestRoster=0): - ''' Send some specific presence state. - Can also request roster from server if according agrument is set.''' + """ + Send some specific presence state. Can also request roster from server if + according agrument is set + """ if requestRoster: # FIXME: used somewhere? roster_nb.NonBlockingRoster.get_instance().PlugIn(self) @@ -528,31 +557,38 @@ class NonBlockingClient: ############################################################################### def RegisterDisconnectHandler(self,handler): - ''' Register handler that will be called on disconnect.''' + """ + Register handler that will be called on disconnect + """ self.disconnect_handlers.append(handler) def UnregisterDisconnectHandler(self,handler): - ''' Unregister handler that is called on disconnect.''' + """ + Unregister handler that is called on disconnect + """ self.disconnect_handlers.remove(handler) def DisconnectHandler(self): - ''' + """ Default disconnect handler. Just raises an IOError. If you choosed to use this class in your production client, override this method or at least unregister it. - ''' + """ raise IOError('Disconnected from server.') def get_connect_type(self): - ''' Returns connection state. F.e.: None / 'tls' / 'plain+non_sasl'. ''' + """ + Return connection state. F.e.: None / 'tls' / 'plain+non_sasl' + """ return self.connected def get_peerhost(self): - ''' + """ Gets the ip address of the account, from which is made connection to the - server (e.g. IP and port of gajim's socket). + server (e.g. IP and port of gajim's socket) + We will create listening socket on the same ip - ''' + """ # FIXME: tuple (ip, port) is expected (and checked for) but port num is # useless return self.socket.peerhost diff --git a/src/common/xmpp/dispatcher_nb.py b/src/common/xmpp/dispatcher_nb.py index 477d3e6c4..469c47f4a 100644 --- a/src/common/xmpp/dispatcher_nb.py +++ b/src/common/xmpp/dispatcher_nb.py @@ -15,10 +15,10 @@ ## GNU General Public License for more details. -''' +""" Main xmpp decision making logic. Provides library with methods to assign -different handlers to different XMPP stanzas and namespaces. -''' +different handlers to different XMPP stanzas and namespaces +""" import simplexml, sys, locale from xml.parsers.expat import ExpatError @@ -36,7 +36,7 @@ XML_DECLARATION = '' # FIXME: ugly class Dispatcher(): - ''' + """ Why is this here - I needed to redefine Dispatcher for BOSH and easiest way was to inherit original Dispatcher (now renamed to XMPPDispatcher). Trouble is that reference used to access dispatcher instance is in Client attribute @@ -46,7 +46,8 @@ class Dispatcher(): If having two kinds of dispatcher will go well, I will rewrite the dispatcher references in other scripts - ''' + """ + def PlugIn(self, client_obj, after_SASL=False, old_features=None): if client_obj.protocol_type == 'XMPP': XMPPDispatcher().PlugIn(client_obj) @@ -57,22 +58,23 @@ class Dispatcher(): @classmethod def get_instance(cls, *args, **kwargs): - ''' - Factory Method for object creation. + """ + Factory Method for object creation Use this instead of directly initializing the class in order to make unit testing much easier. - ''' + """ return cls(*args, **kwargs) class XMPPDispatcher(PlugIn): - ''' - Handles XMPP stream and is the first who takes control over a fresh stanza. + """ + Handles XMPP stream and is the first who takes control over a fresh stanza Is plugged into NonBlockingClient but can be replugged to restart handled stream headers (used by SASL f.e.). - ''' + """ + def __init__(self): PlugIn.__init__(self) self.handlers = {} @@ -94,25 +96,24 @@ class XMPPDispatcher(PlugIn): return repr(outgoingID) def dumpHandlers(self): - ''' - Return set of user-registered callbacks in it's internal format. - Used within the library to carry user handlers set over Dispatcher - replugins. - ''' + """ + Return set of user-registered callbacks in it's internal format. Used + within the library to carry user handlers set over Dispatcher replugins + """ return self.handlers def restoreHandlers(self, handlers): - ''' - Restores user-registered callbacks structure from dump previously - obtained via dumpHandlers. Used within the library to carry user - handlers set over Dispatcher replugins. - ''' + """ + Restore user-registered callbacks structure from dump previously obtained + via dumpHandlers. Used within the library to carry user handlers set over + Dispatcher replugins. + """ self.handlers = handlers def _init(self): - ''' - Registers default namespaces/protocols/handlers. Used internally. - ''' + """ + Register default namespaces/protocols/handlers. Used internally + """ # FIXME: inject dependencies, do not rely that they are defined by our # owner self.RegisterNamespace('unknown') @@ -126,10 +127,10 @@ class XMPPDispatcher(PlugIn): self.on_responses = {} def plugin(self, owner): - ''' - Plug the Dispatcher instance into Client class instance and send - initial stream header. Used internally. - ''' + """ + Plug the Dispatcher instance into Client class instance and send initial + stream header. Used internally + """ self._init() self._owner.lastErrNode = None self._owner.lastErr = None @@ -140,7 +141,9 @@ class XMPPDispatcher(PlugIn): self.StreamInit() def plugout(self): - ''' Prepares instance to be destructed. ''' + """ + Prepare instance to be destructed + """ self.Stream.dispatch = None self.Stream.features = None self.Stream.destroy() @@ -148,7 +151,9 @@ class XMPPDispatcher(PlugIn): self.Stream = None def StreamInit(self): - ''' Send an initial stream header. ''' + """ + Send an initial stream header + """ self.Stream = simplexml.NodeBuilder() self.Stream.dispatch = self.dispatch self.Stream._dispatch_depth = 2 @@ -170,15 +175,15 @@ class XMPPDispatcher(PlugIn): % (tag, ns)) def ProcessNonBlocking(self, data): - ''' - Check incoming stream for data waiting. + """ + Check incoming stream for data waiting :param data: data received from transports/IO sockets :return: 1) length of processed data if some data were processed; 2) '0' string if no data were processed but link is alive; 3) 0 (zero) if underlying connection is closed. - ''' + """ # FIXME: # When an error occurs we disconnect the transport directly. Client's # disconnect method will never be called. @@ -211,41 +216,42 @@ class XMPPDispatcher(PlugIn): return len(data) def RegisterNamespace(self, xmlns, order='info'): - ''' - Creates internal structures for newly registered namespace. + """ + Create internal structures for newly registered namespace + You can register handlers for this namespace afterwards. By default one namespace is already registered (jabber:client or jabber:component:accept depending on context. - ''' + """ log.debug('Registering namespace "%s"' % xmlns) self.handlers[xmlns] = {} self.RegisterProtocol('unknown', Protocol, xmlns=xmlns) self.RegisterProtocol('default', Protocol, xmlns=xmlns) def RegisterProtocol(self, tag_name, Proto, xmlns=None, order='info'): - ''' - Used to declare some top-level stanza name to dispatcher. - Needed to start registering handlers for such stanzas. + """ + Used to declare some top-level stanza name to dispatcher - Iq, message and presence protocols are registered by default. - ''' + Needed to start registering handlers for such stanzas. Iq, message and + presence protocols are registered by default. + """ if not xmlns: xmlns=self._owner.defaultNamespace log.debug('Registering protocol "%s" as %s(%s)' %(tag_name, Proto, xmlns)) self.handlers[xmlns][tag_name] = {type:Proto, 'default':[]} def RegisterNamespaceHandler(self, xmlns, handler, typ='', ns='', - makefirst=0, system=0): - ''' - Register handler for processing all stanzas for specified namespace. - ''' + makefirst=0, system=0): + """ + Register handler for processing all stanzas for specified namespace + """ self.RegisterHandler('default', handler, typ, ns, xmlns, makefirst, system) def RegisterHandler(self, name, handler, typ='', ns='', xmlns=None, makefirst=False, system=False): - ''' - Register user callback as stanzas handler of declared type. + """ + Register user callback as stanzas handler of declared type Callback arguments: dispatcher instance (for replying), incoming return of previous handlers. @@ -263,7 +269,7 @@ class XMPPDispatcher(PlugIn): and " will be called first nevertheless. :param system: call handler even if NodeProcessed Exception were raised already. - ''' + """ if not xmlns: xmlns=self._owner.defaultNamespace log.debug('Registering handler %s for "%s" type->%s ns->%s(%s)' % @@ -284,18 +290,20 @@ class XMPPDispatcher(PlugIn): 'system':system}) def RegisterHandlerOnce(self, name, handler, typ='', ns='', xmlns=None, - makefirst=0, system=0): - ''' Unregister handler after first call (not implemented yet). ''' + makefirst=0, system=0): + """ + Unregister handler after first call (not implemented yet) + """ # FIXME Drop or implement if not xmlns: xmlns = self._owner.defaultNamespace self.RegisterHandler(name, handler, typ, ns, xmlns, makefirst, system) def UnregisterHandler(self, name, handler, typ='', ns='', xmlns=None): - ''' + """ Unregister handler. "typ" and "ns" must be specified exactly the same as with registering. - ''' + """ if not xmlns: xmlns = self._owner.defaultNamespace if not typ and not ns: @@ -314,58 +322,59 @@ class XMPPDispatcher(PlugIn): pass def RegisterDefaultHandler(self, handler): - ''' + """ Specify the handler that will be used if no NodeProcessed exception were raised. This is returnStanzaHandler by default. - ''' + """ self._defaultHandler = handler def RegisterEventHandler(self, handler): - ''' - Register handler that will process events. F.e. - "FILERECEIVED" event. See common/connection: _event_dispatcher() - ''' + """ + Register handler that will process events. F.e. "FILERECEIVED" event. See + common/connection: _event_dispatcher() + """ self._eventHandler = handler def returnStanzaHandler(self, conn, stanza): - ''' - Return stanza back to the sender with error set - ''' + """ + Return stanza back to the sender with error + set + """ if stanza.getType() in ('get','set'): conn._owner.send(Error(stanza, ERR_FEATURE_NOT_IMPLEMENTED)) def RegisterCycleHandler(self, handler): - ''' - Register handler that will be called on every Dispatcher.Process() call. - ''' + """ + Register handler that will be called on every Dispatcher.Process() call + """ if handler not in self._cycleHandlers: self._cycleHandlers.append(handler) def UnregisterCycleHandler(self, handler): - ''' + """ Unregister handler that will is called on every Dispatcher.Process() call - ''' + """ if handler in self._cycleHandlers: self._cycleHandlers.remove(handler) def Event(self, realm, event, data): - ''' - Raise some event. + """ + Raise some event :param realm: scope of event. Usually a namespace. :param event: the event itself. F.e. "SUCCESSFUL SEND". :param data: data that comes along with event. Depends on event. - ''' + """ if self._eventHandler: self._eventHandler(realm, event, data) else: log.warning('Received unhandled event: %s' % event) def dispatch(self, stanza, session=None, direct=0): - ''' + """ Main procedure that performs XMPP stanza recognition and calling - apppropriate handlers for it. Called by simplexml. - ''' + apppropriate handlers for it. Called by simplexml + """ # FIXME: Where do we set session and direct. Why? What are those intended # to do? @@ -450,9 +459,9 @@ class XMPPDispatcher(PlugIn): self._defaultHandler(session, stanza) def _WaitForData(self, data): - ''' + """ Internal wrapper around ProcessNonBlocking. Will check for - ''' + """ if data is None: return res = self.ProcessNonBlocking(data) @@ -481,12 +490,12 @@ class XMPPDispatcher(PlugIn): del self._expected[_id] def SendAndWaitForResponse(self, stanza, timeout=None, func=None, args=None): - ''' + """ Send stanza and wait for recipient's response to it. Will call transports - on_timeout callback if response is not retrieved in time. + on_timeout callback if response is not retrieved in time Be aware: Only timeout of latest call of SendAndWait is active. - ''' + """ if timeout is None: timeout = DEFAULT_TIMEOUT_SECONDS _waitid = self.send(stanza) @@ -499,15 +508,17 @@ class XMPPDispatcher(PlugIn): return _waitid def SendAndCallForResponse(self, stanza, func=None, args=None): - ''' Put stanza on the wire and call back when recipient replies. - Additional callback arguments can be specified in args. ''' + """ + Put stanza on the wire and call back when recipient replies. Additional + callback arguments can be specified in args + """ self.SendAndWaitForResponse(stanza, 0, func, args) def send(self, stanza, now=False): - ''' - Wraps transports send method when plugged into NonBlockingClient. - Makes sure stanzas get ID and from tag. - ''' + """ + Wrap transports send method when plugged into NonBlockingClient. Makes + sure stanzas get ID and from tag. + """ ID = None if type(stanza) not in [type(''), type(u'')]: if isinstance(stanza, Protocol): @@ -529,7 +540,9 @@ class BOSHDispatcher(XMPPDispatcher): XMPPDispatcher.PlugIn(self, owner) def StreamInit(self): - ''' Send an initial stream header. ''' + """ + Send an initial stream header + """ self.Stream = simplexml.NodeBuilder() self.Stream.dispatch = self.dispatch self.Stream._dispatch_depth = 2 @@ -549,7 +562,9 @@ class BOSHDispatcher(XMPPDispatcher): self._owner.Connection.send_init(after_SASL=self.after_SASL) def StreamTerminate(self): - ''' Send a stream terminator. ''' + """ + Send a stream terminator + """ self._owner.Connection.send_terminator() def ProcessNonBlocking(self, data=None): diff --git a/src/common/xmpp/features_nb.py b/src/common/xmpp/features_nb.py index b713e38b2..ec35b22b0 100644 --- a/src/common/xmpp/features_nb.py +++ b/src/common/xmpp/features_nb.py @@ -15,10 +15,10 @@ # $Id: features.py,v 1.22 2005/09/30 20:13:04 mikealbon Exp $ -''' +""" Different stuff that wasn't worth separating it into modules (Registration, Privacy Lists, ...) -''' +""" from protocol import NS_REGISTER, NS_PRIVACY, NS_DATA, Iq, isResultNode, Node @@ -38,13 +38,13 @@ def _on_default_response(disp, iq, cb): REGISTER_DATA_RECEIVED = 'REGISTER DATA RECEIVED' def getRegInfo(disp, host, info={}, sync=True): - ''' - Gets registration form from remote host. Info dict can be prefilled + """ + Get registration form from remote host. Info dict can be prefilled :param disp: plugged dispatcher instance :param info: dict, like {'username':'joey'}. See JEP-0077 for details. - ''' + """ iq=Iq('get',NS_REGISTER,to=host) for i in info.keys(): iq.setTagData(i,info[i]) @@ -77,12 +77,12 @@ def _ReceivedRegInfo(con, resp, agent): con.Event(NS_REGISTER, REGISTER_DATA_RECEIVED, (agent,df,False,'')) def register(disp, host, info, cb): - ''' - Perform registration on remote server with provided info. + """ + Perform registration on remote server with provided info If registration fails you can get additional info from the dispatcher's owner attributes lastErrNode, lastErr and lastErrCode. - ''' + """ iq=Iq('set', NS_REGISTER, to=host) if not isinstance(info, dict): info=info.asDict() @@ -91,17 +91,17 @@ def register(disp, host, info, cb): disp.SendAndCallForResponse(iq, cb) def unregister(disp, host, cb): - ''' + """ Unregisters with host (permanently removes account). Returns true on success - ''' + """ iq = Iq('set', NS_REGISTER, to=host, payload=[Node('remove')]) _on_default_response(disp, iq, cb) def changePasswordTo(disp, newpassword, host=None, cb = None): - ''' - Changes password on specified or current (if not specified) server. - Returns true on success. - ''' + """ + Changes password on specified or current (if not specified) server. Returns + true on success. + """ if not host: host = disp._owner.Server iq = Iq('set',NS_REGISTER,to=host, payload=[Node('username', @@ -123,10 +123,10 @@ PRIVACY_LIST_RECEIVED = 'PRIVACY LIST RECEIVED' PRIVACY_LISTS_ACTIVE_DEFAULT = 'PRIVACY LISTS ACTIVE DEFAULT' def getPrivacyLists(disp): - ''' - Requests privacy lists from connected server. - Returns dictionary of existing lists on success. - ''' + """ + Request privacy lists from connected server. Returns dictionary of existing + lists on success. + """ iq = Iq('get', NS_PRIVACY) def _on_response(resp): dict_ = {'lists': []} @@ -157,10 +157,10 @@ def getActiveAndDefaultPrivacyLists(disp): disp.SendAndCallForResponse(iq, _on_response) def getPrivacyList(disp, listname): - ''' - Requests specific privacy list listname. Returns list of XML nodes (rules) + """ + Request specific privacy list listname. Returns list of XML nodes (rules) taken from the server responce. - ''' + """ def _on_response(resp): if not isResultNode(resp): disp.Event(NS_PRIVACY, PRIVACY_LIST_RECEIVED, (False)) @@ -170,10 +170,10 @@ def getPrivacyList(disp, listname): disp.SendAndCallForResponse(iq, _on_response) def setActivePrivacyList(disp, listname=None, typ='active', cb=None): - ''' - Switches privacy list 'listname' to specified type. - By default the type is 'active'. Returns true on success. - ''' + """ + Switch privacy list 'listname' to specified type. By default the type is + 'active'. Returns true on success. + """ if listname: attrs={'name':listname} else: @@ -182,15 +182,20 @@ def setActivePrivacyList(disp, listname=None, typ='active', cb=None): _on_default_response(disp, iq, cb) def setDefaultPrivacyList(disp, listname=None): - ''' Sets the default privacy list as 'listname'. Returns true on success. ''' + """ + Set the default privacy list as 'listname'. Returns true on success + """ return setActivePrivacyList(disp, listname,'default') def setPrivacyList(disp, listname, tags): - ''' - Set the ruleset. + """ + Set the ruleset - 'list' should be the simpleXML node formatted according to RFC 3921 (XMPP-IM) I.e. Node('list',{'name':listname},payload=[...]). Returns true on success. - ''' + 'list' should be the simpleXML node formatted according to RFC 3921 + (XMPP-IM) I.e. Node('list',{'name':listname},payload=[...]). + + Returns true on success. + """ iq = Iq('set', NS_PRIVACY, xmlns = '') list_query = iq.getTag('query').setTag('list', {'name': listname}) for item in tags: diff --git a/src/common/xmpp/idlequeue.py b/src/common/xmpp/idlequeue.py index 63cb26bf6..6b1edaf18 100644 --- a/src/common/xmpp/idlequeue.py +++ b/src/common/xmpp/idlequeue.py @@ -12,10 +12,12 @@ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -''' -Idlequeues are Gajim's network heartbeat. Transports can be plugged as -idle objects and be informed about possible IO. -''' + +""" +Idlequeues are Gajim's network heartbeat. Transports can be plugged as idle +objects and be informed about possible IO +""" + import os import select import logging @@ -45,7 +47,9 @@ IS_CLOSED = 16 # channel closed def get_idlequeue(): - ''' Get an appropriate idlequeue ''' + """ + Get an appropriate idlequeue + """ if os.name == 'nt': # gobject.io_add_watch does not work on windows return SelectIdleQueue() @@ -59,34 +63,44 @@ def get_idlequeue(): class IdleObject: - ''' - Idle listener interface. Listed methods are called by IdleQueue. - ''' + """ + Idle listener interface. Listed methods are called by IdleQueue. + """ + def __init__(self): self.fd = -1 #: filedescriptor, must be unique for each IdleObject def pollend(self): - ''' called on stream failure ''' + """ + Called on stream failure + """ pass def pollin(self): - ''' called on new read event ''' + """ + Called on new read event + """ pass def pollout(self): - ''' called on new write event (connect in sockets is a pollout) ''' + """ + Called on new write event (connect in sockets is a pollout) + """ pass def read_timeout(self): - ''' called when timeout happened ''' + """ + Called when timeout happened + """ pass class IdleCommand(IdleObject): - ''' + """ Can be subclassed to execute commands asynchronously by the idlequeue. Result will be optained via file descriptor of created pipe - ''' + """ + def __init__(self, on_result): IdleObject.__init__(self) # how long (sec.) to wait for result ( 0 - forever ) @@ -111,7 +125,9 @@ class IdleCommand(IdleObject): return ['echo', 'da'] def _compose_command_line(self): - ''' return one line representation of command and its arguments ''' + """ + Return one line representation of command and its arguments + """ return reduce(lambda left, right: left + ' ' + right, self._compose_command_args()) @@ -187,7 +203,7 @@ class IdleCommand(IdleObject): class IdleQueue: - ''' + """ IdleQueue provide three distinct time based features. Uses select.poll() 1. Alarm timeout: Execute a callback after foo seconds @@ -195,7 +211,8 @@ class IdleQueue: has been set, but not removed in time. 3. Check file descriptor of plugged objects for read, write and error events - ''' + """ + # (timeout, boolean) # Boolean is True if timeout is specified in seconds, False means miliseconds PROCESS_TIMEOUT = (200, False) @@ -215,13 +232,15 @@ class IdleQueue: self._init_idle() def _init_idle(self): - ''' Hook method for subclassed. Will be called by __init__. ''' + """ + Hook method for subclassed. Will be called by __init__ + """ self.selector = select.poll() def set_alarm(self, alarm_cb, seconds): - ''' - Sets up a new alarm. alarm_cb will be called after specified seconds. - ''' + """ + Set up a new alarm. alarm_cb will be called after specified seconds. + """ alarm_time = self.current_time() + seconds # almost impossible, but in case we have another alarm_cb at this time if alarm_time in self.alarms: @@ -231,10 +250,10 @@ class IdleQueue: return alarm_time def remove_alarm(self, alarm_cb, alarm_time): - ''' - Removes alarm callback alarm_cb scheduled on alarm_time. - Returns True if it was removed sucessfully, otherwise False - ''' + """ + Remove alarm callback alarm_cb scheduled on alarm_time. Returns True if + it was removed sucessfully, otherwise False + """ if not alarm_time in self.alarms: return False i = -1 @@ -251,7 +270,9 @@ class IdleQueue: return False def remove_timeout(self, fd, timeout=None): - ''' Removes the read timeout ''' + """ + Remove the read timeout + """ log.info('read timeout removed for fd %s' % fd) if fd in self.read_timeouts: if timeout: @@ -263,12 +284,12 @@ class IdleQueue: del(self.read_timeouts[fd]) def set_read_timeout(self, fd, seconds, func=None): - ''' - Sets a new timeout. If it is not removed after specified seconds, - func or obj.read_timeout() will be called. + """ + Seta a new timeout. If it is not removed after specified seconds, + func or obj.read_timeout() will be called A filedescriptor fd can have several timeouts. - ''' + """ log_txt = 'read timeout set for fd %s on %s seconds' % (fd, seconds) if func: log_txt += ' with function ' + str(func) @@ -280,10 +301,10 @@ class IdleQueue: self.read_timeouts[fd] = {timeout: func} def _check_time_events(self): - ''' + """ Execute and remove alarm callbacks and execute func() or read_timeout() - for plugged objects if specified time has ellapsed. - ''' + for plugged objects if specified time has ellapsed + """ log.info('check time evs') current_time = self.current_time() @@ -313,13 +334,13 @@ class IdleQueue: del(self.alarms[alarm_time]) def plug_idle(self, obj, writable=True, readable=True): - ''' - Plug an IdleObject into idlequeue. Filedescriptor fd must be set. + """ + Plug an IdleObject into idlequeue. Filedescriptor fd must be set :param obj: the IdleObject :param writable: True if obj has data to sent :param readable: True if obj expects data to be reiceived - ''' + """ if obj.fd == -1: return if obj.fd in self.queue: @@ -339,11 +360,15 @@ class IdleQueue: self._add_idle(obj.fd, flags) def _add_idle(self, fd, flags): - ''' Hook method for subclasses, called by plug_idle ''' + """ + Hook method for subclasses, called by plug_idle + """ self.selector.register(fd, flags) def unplug_idle(self, fd): - ''' Removed plugged IdleObject, specified by filedescriptor fd. ''' + """ + Remove plugged IdleObject, specified by filedescriptor fd + """ if fd in self.queue: del(self.queue[fd]) self._remove_idle(fd) @@ -353,7 +378,9 @@ class IdleQueue: return time() def _remove_idle(self, fd): - ''' Hook method for subclassed, called by unplug_idle ''' + """ + Hook method for subclassed, called by unplug_idle + """ self.selector.unregister(fd) def _process_events(self, fd, flags): @@ -379,13 +406,13 @@ class IdleQueue: return False def process(self): - ''' - Process idlequeue. Check for any pending timeout or alarm events. - Call IdleObjects on possible and requested read, write and error events - on their file descriptors. + """ + Process idlequeue. Check for any pending timeout or alarm events. Call + IdleObjects on possible and requested read, write and error events on + their file descriptors Call this in regular intervals. - ''' + """ if not self.queue: # check for timeouts/alert also when there are no active fds self._check_time_events() @@ -403,24 +430,26 @@ class IdleQueue: class SelectIdleQueue(IdleQueue): - ''' + """ Extends IdleQueue to use select.select() for polling - This class exisists for the sake of gtk2.8 on windows, which - doesn't seem to support io_add_watch properly (yet) - ''' + This class exisists for the sake of gtk2.8 on windows, which doesn't seem to + support io_add_watch properly (yet) + """ + def _init_idle(self): - ''' - Creates a dict, which maps file/pipe/sock descriptor to glib event id - ''' + """ + Create a dict, which maps file/pipe/sock descriptor to glib event id + """ self.read_fds = {} self.write_fds = {} self.error_fds = {} def _add_idle(self, fd, flags): - ''' this method is called when we plug a new idle object. - Remove descriptor to read/write/error lists, according flags - ''' + """ + This method is called when we plug a new idle object. Remove descriptor + to read/write/error lists, according flags + """ if flags & 3: self.read_fds[fd] = fd if flags & 4: @@ -428,9 +457,10 @@ class SelectIdleQueue(IdleQueue): self.error_fds[fd] = fd def _remove_idle(self, fd): - ''' this method is called when we unplug a new idle object. - Remove descriptor from read/write/error lists - ''' + """ + This method is called when we unplug a new idle object. Remove descriptor + from read/write/error lists + """ if fd in self.read_fds: del(self.read_fds[fd]) if fd in self.write_fds: @@ -466,27 +496,29 @@ class SelectIdleQueue(IdleQueue): class GlibIdleQueue(IdleQueue): - ''' - Extends IdleQueue to use glib io_add_wath, instead of select/poll - In another 'non gui' implementation of Gajim IdleQueue can be used safetly. - ''' + """ + Extends IdleQueue to use glib io_add_wath, instead of select/poll In another + 'non gui' implementation of Gajim IdleQueue can be used safetly + """ + # (timeout, boolean) # Boolean is True if timeout is specified in seconds, False means miliseconds PROCESS_TIMEOUT = (2, True) def _init_idle(self): - ''' + """ Creates a dict, which maps file/pipe/sock descriptor to glib event id - ''' + """ self.events = {} # time() is already called in glib, we just get the last value # overrides IdleQueue.current_time() self.current_time = gobject.get_current_time def _add_idle(self, fd, flags): - ''' this method is called when we plug a new idle object. - Start listening for events from fd - ''' + """ + This method is called when we plug a new idle object. Start listening for + events from fd + """ res = gobject.io_add_watch(fd, flags, self._process_events, priority=gobject.PRIORITY_LOW) # store the id of the watch, so that we can remove it on unplug @@ -501,9 +533,10 @@ class GlibIdleQueue(IdleQueue): raise def _remove_idle(self, fd): - ''' this method is called when we unplug a new idle object. - Stop listening for events from fd - ''' + """ + This method is called when we unplug a new idle object. Stop listening + for events from fd + """ if not fd in self.events: return gobject.source_remove(self.events[fd]) diff --git a/src/common/xmpp/plugin.py b/src/common/xmpp/plugin.py index 7f5d91ee1..2e4368eb2 100644 --- a/src/common/xmpp/plugin.py +++ b/src/common/xmpp/plugin.py @@ -14,33 +14,34 @@ # $Id: client.py,v 1.52 2006/01/02 19:40:55 normanr Exp $ -''' -Provides PlugIn class functionality to develop extentions for xmpppy. -''' +""" +Provides PlugIn class functionality to develop extentions for xmpppy +""" import logging log = logging.getLogger('gajim.c.x.plugin') class PlugIn: - ''' + """ Abstract xmpppy plugin infrastructure code, providing plugging in/out and - debugging functionality. + debugging functionality Inherit to develop pluggable objects. No code change on the owner class required (the object where we plug into) For every instance of PlugIn class the 'owner' is the class in what the plug was plugged. - ''' + """ + def __init__(self): self._exported_methods=[] def PlugIn(self, owner): - ''' + """ Attach to owner and register ourself and our _exported_methods in it. If defined by a subclass, call self.plugin(owner) to execute hook - code after plugging. - ''' + code after plugging + """ self._owner=owner log.info('Plugging %s __INTO__ %s' % (self, self._owner)) if self.__class__.__name__ in owner.__dict__: @@ -63,11 +64,11 @@ class PlugIn: return self.plugin(owner) def PlugOut(self): - ''' + """ Unregister our _exported_methods from owner and detach from it. If defined by a subclass, call self.plugout() after unplugging to execute - hook code. - ''' + hook code + """ log.info('Plugging %s __OUT__ of %s.' % (self, self._owner)) for method in self._exported_methods: del self._owner.__dict__[method.__name__] @@ -85,13 +86,13 @@ class PlugIn: @classmethod def get_instance(cls, *args, **kwargs): - ''' - Factory Method for object creation. + """ + Factory Method for object creation Use this instead of directly initializing the class in order to make unit testing easier. For testing, this method can be patched to inject mock objects. - ''' + """ return cls(*args, **kwargs) # vim: se ts=3: diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 5eb31b7d3..ff19678d8 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -14,14 +14,15 @@ # $Id: protocol.py,v 1.52 2006/01/09 22:08:57 normanr Exp $ -''' +""" Protocol module contains tools that are needed for processing of xmpp-related -data structures, including jabber-objects like JID or different stanzas and sub- -stanzas) handling routines. -''' +data structures, including jabber-objects like JID or different stanzas and +sub- stanzas) handling routines +""" from simplexml import Node, NodeBuilder import time + NS_ACTIVITY ='http://jabber.org/protocol/activity' # XEP-0108 NS_ADDRESS ='http://jabber.org/protocol/address' # XEP-0033 NS_AGENTS ='jabber:iq:agents' @@ -126,7 +127,7 @@ NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # XEP-0122 NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams' NS_RECEIPTS ='urn:xmpp:receipts' -xmpp_stream_error_conditions=''' +xmpp_stream_error_conditions = ''' bad-format -- -- -- The entity has sent XML that cannot be processed. bad-namespace-prefix -- -- -- The entity has sent a namespace prefix that is unsupported, or has sent no namespace prefix on an element that requires such a prefix. conflict -- -- -- The server is closing the active stream for this entity because a new stream has been initiated that conflicts with the existing stream. @@ -151,7 +152,8 @@ unsupported-encoding -- -- -- The initiating entity has encoded the stream in unsupported-stanza-type -- -- -- The initiating entity has sent a first-level child of the stream that is not supported by the server. unsupported-version -- -- -- The value of the 'version' attribute provided by the initiating entity in the stream header specifies a version of XMPP that is not supported by the server. xml-not-well-formed -- -- -- The initiating entity has sent XML that is not well-formed.''' -xmpp_stanza_error_conditions=''' + +xmpp_stanza_error_conditions = ''' bad-request -- 400 -- modify -- The sender has sent XML that is malformed or that cannot be processed. conflict -- 409 -- cancel -- Access cannot be granted because an existing resource or session exists with the same name or address. feature-not-implemented -- 501 -- cancel -- The feature requested is not implemented by the recipient or server and therefore cannot be processed. @@ -174,7 +176,8 @@ service-unavailable -- 503 -- cancel -- The server or recipient does not current subscription-required -- 407 -- auth -- The requesting entity is not authorized to access the requested service because a subscription is required. undefined-condition -- 500 -- -- Undefined Condition unexpected-request -- 400 -- wait -- The recipient or server understood the request but was not expecting it at this time (e.g., the request was out of order).''' -sasl_error_conditions=''' + +sasl_error_conditions = ''' aborted -- -- -- The receiving entity acknowledges an element sent by the initiating entity; sent in reply to the element. incorrect-encoding -- -- -- The data provided by the initiating entity could not be processed because the [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003. encoding is incorrect (e.g., because the encoding does not adhere to the definition in Section 3 of [BASE64]Josefsson, S., The Base16, Base32, and Base64 Data Encodings, July 2003.); sent in reply to a element or an element with initial response data. invalid-authzid -- -- -- The authzid provided by the initiating entity is invalid, either because it is incorrectly formatted or because the initiating entity does not have permissions to authorize that ID; sent in reply to a element or an element with initial response data. @@ -183,53 +186,115 @@ mechanism-too-weak -- -- -- The mechanism requested by the initiating entity i not-authorized -- -- -- The authentication failed because the initiating entity did not provide valid credentials (this includes but is not limited to the case of an unknown username); sent in reply to a element or an element with initial response data. temporary-auth-failure -- -- -- The authentication failed because of a temporary error condition within the receiving entity; sent in reply to an element or element.''' -ERRORS,_errorcodes={},{} -for ns,errname,errpool in ((NS_XMPP_STREAMS,'STREAM',xmpp_stream_error_conditions), +ERRORS, _errorcodes = {}, {} +for ns, errname, errpool in ((NS_XMPP_STREAMS, 'STREAM', xmpp_stream_error_conditions), (NS_STANZAS ,'ERR' ,xmpp_stanza_error_conditions), (NS_SASL ,'SASL' ,sasl_error_conditions)): for err in errpool.split('\n')[1:]: - cond,code,typ,text=err.split(' -- ') - name=errname+'_'+cond.upper().replace('-','_') - locals()[name]=ns+' '+cond - ERRORS[ns+' '+cond]=[code,typ,text] - if code: _errorcodes[code]=cond -del ns,errname,errpool,err,cond,code,typ,text + cond, code, typ, text = err.split(' -- ') + name = errname + '_' + cond.upper().replace('-', '_') + locals()[name] = ns + ' ' + cond + ERRORS[ns + ' ' + cond] = [code, typ, text] + if code: + _errorcodes[code] = cond +del ns, errname, errpool, err, cond, code, typ, text def isResultNode(node): - ''' Returns true if the node is a positive reply. ''' - return node and node.getType()=='result' + """ + Return true if the node is a positive reply + """ + return node and node.getType() == 'result' + def isErrorNode(node): - ''' Returns true if the node is a negative reply. ''' - return node and node.getType()=='error' + """ + Return true if the node is a negative reply + """ + return node and node.getType() == 'error' class NodeProcessed(Exception): - ''' Exception that should be raised by handler when the handling should be stopped. ''' + """ + Exception that should be raised by handler when the handling should be + stopped + """ + pass + class StreamError(Exception): - ''' Base exception class for stream errors.''' -class BadFormat(StreamError): pass -class BadNamespacePrefix(StreamError): pass -class Conflict(StreamError): pass -class ConnectionTimeout(StreamError): pass -class HostGone(StreamError): pass -class HostUnknown(StreamError): pass -class ImproperAddressing(StreamError): pass -class InternalServerError(StreamError): pass -class InvalidFrom(StreamError): pass -class InvalidID(StreamError): pass -class InvalidNamespace(StreamError): pass -class InvalidXML(StreamError): pass -class NotAuthorized(StreamError): pass -class PolicyViolation(StreamError): pass -class RemoteConnectionFailed(StreamError): pass -class ResourceConstraint(StreamError): pass -class RestrictedXML(StreamError): pass -class SeeOtherHost(StreamError): pass -class SystemShutdown(StreamError): pass -class UndefinedCondition(StreamError): pass -class UnsupportedEncoding(StreamError): pass -class UnsupportedStanzaType(StreamError): pass -class UnsupportedVersion(StreamError): pass -class XMLNotWellFormed(StreamError): pass + """ + Base exception class for stream errors + """ + pass + +class BadFormat(StreamError): + pass + +class BadNamespacePrefix(StreamError): + pass + +class Conflict(StreamError): + pass + +class ConnectionTimeout(StreamError): + pass + +class HostGone(StreamError): + pass + +class HostUnknown(StreamError): + pass + +class ImproperAddressing(StreamError): + pass + +class InternalServerError(StreamError): + pass + +class InvalidFrom(StreamError): + pass + +class InvalidID(StreamError): + pass + +class InvalidNamespace(StreamError): + pass + +class InvalidXML(StreamError): + pass + +class NotAuthorized(StreamError): + pass + +class PolicyViolation(StreamError): + pass + +class RemoteConnectionFailed(StreamError): + pass + +class ResourceConstraint(StreamError): + pass + +class RestrictedXML(StreamError): + pass + +class SeeOtherHost(StreamError): + pass + +class SystemShutdown(StreamError): + pass + +class UndefinedCondition(StreamError): + pass + +class UnsupportedEncoding(StreamError): + pass + +class UnsupportedStanzaType(StreamError): + pass + +class UnsupportedVersion(StreamError): + pass + +class XMLNotWellFormed(StreamError): + pass stream_exceptions = {'bad-format': BadFormat, 'bad-namespace-prefix': BadNamespacePrefix, @@ -257,250 +322,430 @@ stream_exceptions = {'bad-format': BadFormat, 'xml-not-well-formed': XMLNotWellFormed} class JID: - ''' JID object. JID can be built from string, modified, compared, serialised into string. ''' + """ + JID can be built from string, modified, compared, serialised into string + """ + def __init__(self, jid=None, node='', domain='', resource=''): - ''' Constructor. JID can be specified as string (jid argument) or as separate parts. - Examples: - JID('node@domain/resource') - JID(node='node',domain='domain.org') - ''' - if not jid and not domain: raise ValueError('JID must contain at least domain name') - elif type(jid)==type(self): self.node,self.domain,self.resource=jid.node,jid.domain,jid.resource - elif domain: self.node,self.domain,self.resource=node,domain,resource + """ + JID can be specified as string (jid argument) or as separate parts + + Examples: + JID('node@domain/resource') + JID(node='node',domain='domain.org') + """ + if not jid and not domain: + raise ValueError('JID must contain at least domain name') + elif type(jid) == type(self): + self.node, self.domain, self.resource = jid.node, jid.domain, jid.resource + elif domain: + self.node, self.domain, self.resource = node, domain, resource else: - if jid.find('@')+1: self.node,jid=jid.split('@',1) - else: self.node='' - if jid.find('/')+1: self.domain,self.resource=jid.split('/',1) - else: self.domain,self.resource=jid,'' + if jid.find('@') + 1: + self.node,jid = jid.split('@', 1) + else: + self.node = '' + if jid.find('/')+1: + self.domain, self.resource = jid.split('/',1) + else: + self.domain, self.resource = jid, '' + def getNode(self): - ''' Return the node part of the JID ''' + """ + Return the node part of the JID + """ return self.node - def setNode(self,node): - ''' Set the node part of the JID to new value. Specify None to remove the node part.''' - self.node=node.lower() + + def setNode(self, node): + """ + Set the node part of the JID to new value. Specify None to remove the node part + """ + self.node = node.lower() + def getDomain(self): - ''' Return the domain part of the JID ''' + """ + Return the domain part of the JID + """ return self.domain - def setDomain(self,domain): - ''' Set the domain part of the JID to new value.''' - self.domain=domain.lower() + + def setDomain(self, domain): + """ + Set the domain part of the JID to new value + """ + self.domain = domain.lower() + def getResource(self): - ''' Return the resource part of the JID ''' + """ + Return the resource part of the JID + """ return self.resource - def setResource(self,resource): - ''' Set the resource part of the JID to new value. Specify None to remove the resource part.''' - self.resource=resource + + def setResource(self, resource): + """ + Set the resource part of the JID to new value. Specify None to remove the + resource part + """ + self.resource = resource + def getStripped(self): - ''' Return the bare representation of JID. I.e. string value w/o resource. ''' + """ + Return the bare representation of JID. I.e. string value w/o resource + """ return self.__str__(0) + def __eq__(self, other): - ''' Compare the JID to another instance or to string for equality. ''' - try: other=JID(other) - except ValueError: return 0 - return self.resource==other.resource and self.__str__(0) == other.__str__(0) + """ + Compare the JID to another instance or to string for equality + """ + try: + other = JID(other) + except ValueError: + return 0 + return self.resource == other.resource and self.__str__(0) == other.__str__(0) + def __ne__(self, other): - ''' Compare the JID to another instance or to string for non-equality. ''' + """ + Compare the JID to another instance or to string for non-equality + """ return not self.__eq__(other) + def bareMatch(self, other): - ''' Compare the node and domain parts of the JID's for equality. ''' + """ + Compare the node and domain parts of the JID's for equality + """ return self.__str__(0) == JID(other).__str__(0) - def __str__(self,wresource=1): - ''' Serialise JID into string. ''' - if self.node: jid=self.node+'@'+self.domain - else: jid=self.domain - if wresource and self.resource: return jid+'/'+self.resource + + def __str__(self, wresource=1): + """ + Serialise JID into string + """ + if self.node: + jid = self.node + '@' + self.domain + else: + jid = self.domain + if wresource and self.resource: + return jid + '/' + self.resource return jid + def __hash__(self): - ''' Produce hash of the JID, Allows to use JID objects as keys of the dictionary. ''' - return hash(self.__str__()) + """ + Produce hash of the JID, Allows to use JID objects as keys of the dictionary + """ + return hash(str(self)) class BOSHBody(Node): - ''' + """ tag that wraps usual XMPP stanzas in XMPP over BOSH - ''' + """ + def __init__(self, attrs={}, payload=[], node=None): Node.__init__(self, tag='body', attrs=attrs, payload=payload, node=node) self.setNamespace(NS_HTTP_BIND) class Protocol(Node): - ''' A "stanza" object class. Contains methods that are common for presences, iqs and messages. ''' - def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, payload=[], timestamp=None, xmlns=None, node=None): - ''' Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq'. - to is the value of 'to' attribure, 'typ' - 'type' attribute - frn - from attribure, attrs - other attributes mapping, - payload - same meaning as for simplexml payload definition - timestamp - the time value that needs to be stamped over stanza - xmlns - namespace of top stanza node - node - parsed or unparsed stana to be taken as prototype. - ''' - if not attrs: attrs={} - if to: attrs['to']=to - if frm: attrs['from']=frm - if typ: attrs['type']=typ + """ + A "stanza" object class. Contains methods that are common for presences, iqs + and messages + """ + + def __init__(self, name=None, to=None, typ=None, frm=None, attrs={}, + payload=[], timestamp=None, xmlns=None, node=None): + """ + Constructor, name is the name of the stanza i.e. 'message' or 'presence' or 'iq' + + to is the value of 'to' attribure, 'typ' - 'type' attribute + frn - from attribure, attrs - other attributes mapping, + payload - same meaning as for simplexml payload definition + timestamp - the time value that needs to be stamped over stanza + xmlns - namespace of top stanza node + node - parsed or unparsed stana to be taken as prototype. + """ + if not attrs: + attrs = {} + if to: + attrs['to'] = to + if frm: + attrs['from'] = frm + if typ: + attrs['type'] = typ Node.__init__(self, tag=name, attrs=attrs, payload=payload, node=node) - if not node and xmlns: self.setNamespace(xmlns) - if self['to']: self.setTo(self['to']) - if self['from']: self.setFrom(self['from']) - if node and type(self)==type(node) and self.__class__==node.__class__ and self.attrs.has_key('id'): del self.attrs['id'] - self.timestamp=None - for d in self.getTags('delay',namespace=NS_DELAY2): + if not node and xmlns: + self.setNamespace(xmlns) + if self['to']: + self.setTo(self['to']) + if self['from']: + self.setFrom(self['from']) + if node and type(self )== type(node) and self.__class__ == node.__class__ and self.attrs.has_key('id'): + del self.attrs['id'] + self.timestamp = None + for d in self.getTags('delay', namespace=NS_DELAY2): try: if d.getAttr('stamp') < self.getTimestamp2(): self.setTimestamp(d.getAttr('stamp')) except Exception: pass if not self.timestamp: - for x in self.getTags('x',namespace=NS_DELAY): + for x in self.getTags('x', namespace=NS_DELAY): try: if x.getAttr('stamp') < self.getTimestamp(): self.setTimestamp(x.getAttr('stamp')) except Exception: pass - if timestamp is not None: self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp='' + if timestamp is not None: + self.setTimestamp(timestamp) # To auto-timestamp stanza just pass timestamp='' + def getTo(self): - ''' Return value of the 'to' attribute. ''' - try: return self['to'] - except: return None + """ + Return value of the 'to' attribute + """ + try: + return self['to'] + except: + return None + def getFrom(self): - ''' Return value of the 'from' attribute. ''' - try: return self['from'] - except: return None + """ + Return value of the 'from' attribute + """ + try: + return self['from'] + except: + return None + def getTimestamp(self): - ''' Return the timestamp in the 'yyyymmddThhmmss' format. ''' - if self.timestamp: return self.timestamp + """ + Return the timestamp in the 'yyyymmddThhmmss' format + """ + if self.timestamp: + return self.timestamp return time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) + def getTimestamp2(self): - """ Return the timestamp in the 'yyyymmddThhmmss' format. """ - if self.timestamp: return self.timestamp + """ + Return the timestamp in the 'yyyymmddThhmmss' format + """ + if self.timestamp: + return self.timestamp return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) + def getID(self): - ''' Return the value of the 'id' attribute. ''' + """ + Return the value of the 'id' attribute + """ return self.getAttr('id') - def setTo(self,val): - ''' Set the value of the 'to' attribute. ''' + + def setTo(self, val): + """ + Set the value of the 'to' attribute + """ self.setAttr('to', JID(val)) + def getType(self): - ''' Return the value of the 'type' attribute. ''' + """ + Return the value of the 'type' attribute + """ return self.getAttr('type') - def setFrom(self,val): - ''' Set the value of the 'from' attribute. ''' + + def setFrom(self, val): + """ + Set the value of the 'from' attribute + """ self.setAttr('from', JID(val)) - def setType(self,val): - ''' Set the value of the 'type' attribute. ''' + + def setType(self, val): + """ + Set the value of the 'type' attribute + """ self.setAttr('type', val) - def setID(self,val): - ''' Set the value of the 'id' attribute. ''' + + def setID(self, val): + """ + Set the value of the 'id' attribute + """ self.setAttr('id', val) + def getError(self): - ''' Return the error-condition (if present) or the textual description of the error (otherwise). ''' - errtag=self.getTag('error') + """ + Return the error-condition (if present) or the textual description of the + error (otherwise) + """ + errtag = self.getTag('error') if errtag: for tag in errtag.getChildren(): - if tag.getName()<>'text': return tag.getName() + if tag.getName() <> 'text': + return tag.getName() return errtag.getData() + def getErrorMsg(self): - ''' Return the textual description of the error (if present) or the error condition ''' - errtag=self.getTag('error') + """ + Return the textual description of the error (if present) or the error condition + """ + errtag = self.getTag('error') if errtag: for tag in errtag.getChildren(): - if tag.getName()=='text': return tag.getData() + if tag.getName() == 'text': + return tag.getData() return self.getError() + def getErrorCode(self): - ''' Return the error code. Obsolete. ''' + """ + Return the error code. Obsolete. + """ return self.getTagAttr('error','code') + def setError(self,error,code=None): - ''' Set the error code. Obsolete. Use error-conditions instead. ''' + """ + Set the error code. Obsolete. Use error-conditions instead + """ if code: - if str(code) in _errorcodes.keys(): error=ErrorNode(_errorcodes[str(code)],text=error) - else: error=ErrorNode(ERR_UNDEFINED_CONDITION,code=code,typ='cancel',text=error) - elif type(error) in [type(''),type(u'')]: error=ErrorNode(error) + if str(code) in _errorcodes.keys(): + error = ErrorNode(_errorcodes[str(code)], text=error) + else: + error = ErrorNode(ERR_UNDEFINED_CONDITION, code=code, typ='cancel', text=error) + elif type(error) in [type(''),type(u'')]: + error=ErrorNode(error) self.setType('error') self.addChild(node=error) - def setTimestamp(self,val=None): - '''Set the timestamp. timestamp should be the yyyymmddThhmmss string.''' - if not val: val=time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) + + def setTimestamp(self, val=None): + """ + Set the timestamp. timestamp should be the yyyymmddThhmmss string + """ + if not val: + val = time.strftime('%Y%m%dT%H:%M:%S', time.gmtime()) self.timestamp=val - self.setTag('x',{'stamp':self.timestamp},namespace=NS_DELAY) + self.setTag('x', {'stamp': self.timestamp}, namespace=NS_DELAY) + def getProperties(self): - ''' Return the list of namespaces to which belongs the direct childs of element''' - props=[] + """ + Return the list of namespaces to which belongs the direct childs of element + """ + props = [] for child in self.getChildren(): - prop=child.getNamespace() - if prop not in props: props.append(prop) + prop = child.getNamespace() + if prop not in props: + props.append(prop) return props - def __setitem__(self,item,val): - ''' Set the item 'item' to the value 'val'.''' - if item in ['to','from']: val=JID(val) - return self.setAttr(item,val) + + def __setitem__(self, item, val): + """ + Set the item 'item' to the value 'val' + """ + if item in ['to','from']: + val = JID(val) + return self.setAttr(item, val) class Message(Protocol): - ''' XMPP Message stanza - "push" mechanism.''' - def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None): - ''' Create message object. You can specify recipient, text of message, type of message - any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go. - Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. ''' - Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) - if body: self.setBody(body) - if xhtml: self.setXHTML(xhtml) - if subject is not None: self.setSubject(subject) - def getBody(self): - ''' Returns text of the message. ''' - return self.getTagData('body') - def getXHTML(self, xmllang=None): - ''' Returns serialized xhtml-im element text of the message. + """ + XMPP Message stanza - "push" mechanism + """ - TODO: Returning a DOM could make rendering faster.''' + def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, + attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, + node=None): + """ + You can specify recipient, text of message, type of message any + additional attributes, sender of the message, any additional payload + (f.e. jabber:x:delay element) and namespace in one go. + + Alternatively you can pass in the other XML object as the 'node' + parameted to replicate it as message + """ + Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, + payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) + if body: + self.setBody(body) + if xhtml: + self.setXHTML(xhtml) + if subject is not None: + self.setSubject(subject) + + def getBody(self): + """ + Return text of the message + """ + return self.getTagData('body') + + def getXHTML(self, xmllang=None): + """ + Return serialized xhtml-im element text of the message + + TODO: Returning a DOM could make rendering faster. + """ xhtml = self.getTag('html') if xhtml: if xmllang: - body = xhtml.getTag('body', attrs={'xml:lang':xmllang}) + body = xhtml.getTag('body', attrs={'xml:lang': xmllang}) else: body = xhtml.getTag('body') return str(body) return None - def getSubject(self): - ''' Returns subject of the message. ''' - return self.getTagData('subject') - def getThread(self): - ''' Returns thread of the message. ''' - return self.getTagData('thread') - def setBody(self,val): - ''' Sets the text of the message. ''' - self.setTagData('body',val) - def setXHTML(self,val,xmllang=None): - ''' Sets the xhtml text of the message (XEP-0071). - The parameter is the "inner html" to the body.''' + def getSubject(self): + """ + Return subject of the message + """ + return self.getTagData('subject') + + def getThread(self): + """ + Return thread of the message + """ + return self.getTagData('thread') + + def setBody(self, val): + """ + Set the text of the message""" + self.setTagData('body', val) + + def setXHTML(self, val, xmllang=None): + """ + Sets the xhtml text of the message (XEP-0071). The parameter is the + "inner html" to the body. + """ try: if xmllang: - dom = NodeBuilder('' + val + '').getDom() + dom = NodeBuilder('%s' % (NS_XHTML, xmllang, val)).getDom() else: - dom = NodeBuilder(''+val+'',0).getDom() + dom = NodeBuilder('%s, 0' % (NS_XHTM, val)).getDom() if self.getTag('html'): self.getTag('html').addChild(node=dom) else: - self.setTag('html',namespace=NS_XHTML_IM).addChild(node=dom) + self.setTag('html', namespace=NS_XHTML_IM).addChild(node=dom) except Exception, e: print "Error", e - #FIXME: log. we could not set xhtml (parse error, whatever) - def setSubject(self,val): - ''' Sets the subject of the message. ''' - self.setTagData('subject',val) - def setThread(self,val): - ''' Sets the thread of the message. ''' - self.setTagData('thread',val) - def buildReply(self,text=None): - ''' Builds and returns another message object with specified text. - The to, from and thread properties of new message are pre-set as reply to this message. ''' - m=Message(to=self.getFrom(),frm=self.getTo(),body=text) - th=self.getThread() - if th: m.setThread(th) + # FIXME: log. we could not set xhtml (parse error, whatever) + + def setSubject(self, val): + """ + Set the subject of the message + """ + self.setTagData('subject', val) + + def setThread(self, val): + """ + Set the thread of the message + """ + self.setTagData('thread', val) + + def buildReply(self, text=None): + """ + Builds and returns another message object with specified text. The to, + from and thread properties of new message are pre-set as reply to this + message + """ + m = Message(to=self.getFrom(), frm=self.getTo(), body=text) + th = self.getThread() + if th: + m.setThread(th) return m + def getStatusCode(self): - '''Returns the status code of the message (for groupchat config - change)''' + """ + Return the status code of the message (for groupchat config change) + """ attrs = [] for xtag in self.getTags('x'): for child in xtag.getTags('status'): @@ -508,68 +753,117 @@ class Message(Protocol): return attrs class Presence(Protocol): - ''' XMPP Presence object.''' - def __init__(self, to=None, typ=None, priority=None, show=None, status=None, attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, node=None): - ''' Create presence object. You can specify recipient, type of message, priority, show and status values - any additional attributes, sender of the presence, timestamp, any additional payload (f.e. jabber:x:delay element) and namespace in one go. - Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as presence. ''' - Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) - if priority: self.setPriority(priority) - if show: self.setShow(show) - if status: self.setStatus(status) - def getPriority(self): - ''' Returns the priority of the message. ''' - return self.getTagData('priority') - def getShow(self): - ''' Returns the show value of the message. ''' - return self.getTagData('show') - def getStatus(self): - ''' Returns the status string of the message. ''' - return self.getTagData('status') - def setPriority(self,val): - ''' Sets the priority of the message. ''' - self.setTagData('priority',val) - def setShow(self,val): - ''' Sets the show value of the message. ''' - self.setTagData('show',val) - def setStatus(self,val): - ''' Sets the status string of the message. ''' - self.setTagData('status',val) - def _muc_getItemAttr(self,tag,attr): + def __init__(self, to=None, typ=None, priority=None, show=None, status=None, + attrs={}, frm=None, timestamp=None, payload=[], xmlns=NS_CLIENT, + node=None): + """ + You can specify recipient, type of message, priority, show and status + values any additional attributes, sender of the presence, timestamp, any + additional payload (f.e. jabber:x:delay element) and namespace in one go. + Alternatively you can pass in the other XML object as the 'node' + parameted to replicate it as presence + """ + Protocol.__init__(self, 'presence', to=to, typ=typ, attrs=attrs, frm=frm, + payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) + if priority: + self.setPriority(priority) + if show: + self.setShow(show) + if status: + self.setStatus(status) + + def getPriority(self): + """ + Return the priority of the message + """ + return self.getTagData('priority') + + def getShow(self): + """ + Return the show value of the message + """ + return self.getTagData('show') + + def getStatus(self): + """ + Return the status string of the message + """ + return self.getTagData('status') + + def setPriority(self, val): + """ + Set the priority of the message + """ + self.setTagData('priority', val) + + def setShow(self, val): + """ + Set the show value of the message + """ + self.setTagData('show', val) + + def setStatus(self, val): + """ + Set the status string of the message + """ + self.setTagData('status', val) + + def _muc_getItemAttr(self, tag, attr): for xtag in self.getTags('x'): if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): continue for child in xtag.getTags(tag): return child.getAttr(attr) - def _muc_getSubTagDataAttr(self,tag,attr): + + def _muc_getSubTagDataAttr(self, tag, attr): for xtag in self.getTags('x'): if xtag.getNamespace() not in (NS_MUC_USER, NS_MUC_ADMIN): continue for child in xtag.getTags('item'): for cchild in child.getTags(tag): - return cchild.getData(),cchild.getAttr(attr) - return None,None + return cchild.getData(), cchild.getAttr(attr) + return None, None + def getRole(self): - '''Returns the presence role (for groupchat)''' - return self._muc_getItemAttr('item','role') + """ + Return the presence role (for groupchat) + """ + return self._muc_getItemAttr('item', 'role') def getAffiliation(self): - '''Returns the presence affiliation (for groupchat)''' - return self._muc_getItemAttr('item','affiliation') + """ + Return the presence affiliation (for groupchat) + """ + return self._muc_getItemAttr('item', 'affiliation') + def getNewNick(self): - '''Returns the status code of the presence (for groupchat)''' - return self._muc_getItemAttr('item','nick') + """ + Return the status code of the presence (for groupchat) + """ + return self._muc_getItemAttr('item', 'nick') + def getJid(self): - '''Returns the presence jid (for groupchat)''' - return self._muc_getItemAttr('item','jid') + """ + Return the presence jid (for groupchat) + """ + return self._muc_getItemAttr('item', 'jid') + def getReason(self): - '''Returns the reason of the presence (for groupchat)''' - return self._muc_getSubTagDataAttr('reason','')[0] + """ + Returns the reason of the presence (for groupchat) + """ + return self._muc_getSubTagDataAttr('reason', '')[0] + def getActor(self): - '''Returns the reason of the presence (for groupchat)''' - return self._muc_getSubTagDataAttr('actor','jid')[1] + """ + Return the reason of the presence (for groupchat) + """ + return self._muc_getSubTagDataAttr('actor', 'jid')[1] + def getStatusCode(self): - '''Returns the status code of the presence (for groupchat)''' + """ + Return the status code of the presence (for groupchat) + """ attrs = [] for xtag in self.getTags('x'): for child in xtag.getTags('status'): @@ -577,244 +871,435 @@ class Presence(Protocol): return attrs class Iq(Protocol): - ''' XMPP Iq object - get/set dialog mechanism. ''' - def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, payload=[], xmlns=NS_CLIENT, node=None): - ''' Create Iq object. You can specify type, query namespace - any additional attributes, recipient of the iq, sender of the iq, any additional payload (f.e. jabber:x:data node) and namespace in one go. - Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as an iq. ''' + """ + XMPP Iq object - get/set dialog mechanism + """ + + def __init__(self, typ=None, queryNS=None, attrs={}, to=None, frm=None, + payload=[], xmlns=NS_CLIENT, node=None): + """ + You can specify type, query namespace any additional attributes, + recipient of the iq, sender of the iq, any additional payload (f.e. + jabber:x:data node) and namespace in one go. + + Alternatively you can pass in the other XML object as the 'node' + parameted to replicate it as an iq + """ Protocol.__init__(self, 'iq', to=to, typ=typ, attrs=attrs, frm=frm, xmlns=xmlns, node=node) - if payload: self.setQueryPayload(payload) - if queryNS: self.setQueryNS(queryNS) + if payload: + self.setQueryPayload(payload) + if queryNS: + self.setQueryNS(queryNS) + def getQueryNS(self): - ''' Return the namespace of the 'query' child element.''' - tag=self.getTag('query') - if tag: return tag.getNamespace() + """ + Return the namespace of the 'query' child element + """ + tag = self.getTag('query') + if tag: + return tag.getNamespace() + def getQuerynode(self): - ''' Return the 'node' attribute value of the 'query' child element.''' - return self.getTagAttr('query','node') + """ + Return the 'node' attribute value of the 'query' child element + """ + return self.getTagAttr('query', 'node') + def getQueryPayload(self): - ''' Return the 'query' child element payload.''' - tag=self.getTag('query') - if tag: return tag.getPayload() + """ + Return the 'query' child element payload + """ + tag = self.getTag('query') + if tag: + return tag.getPayload() + def getQueryChildren(self): - ''' Return the 'query' child element child nodes.''' - tag=self.getTag('query') - if tag: return tag.getChildren() - def setQueryNS(self,namespace): - ''' Set the namespace of the 'query' child element.''' + """ + Return the 'query' child element child nodes + """ + tag = self.getTag('query') + if tag: + return tag.getChildren() + + def setQueryNS(self, namespace): + """ + Set the namespace of the 'query' child element + """ self.setTag('query').setNamespace(namespace) - def setQueryPayload(self,payload): - ''' Set the 'query' child element payload.''' + + def setQueryPayload(self, payload): + """ + Set the 'query' child element payload + """ self.setTag('query').setPayload(payload) - def setQuerynode(self,node): - ''' Set the 'node' attribute value of the 'query' child element.''' - self.setTagAttr('query','node',node) - def buildReply(self,typ): - ''' Builds and returns another Iq object of specified type. - The to, from and query child node of new Iq are pre-set as reply to this Iq. ''' - iq=Iq(typ,to=self.getFrom(),frm=self.getTo(),attrs={'id':self.getID()}) - if self.getTag('query'): iq.setQueryNS(self.getQueryNS()) + + def setQuerynode(self, node): + """ + Set the 'node' attribute value of the 'query' child element + """ + self.setTagAttr('query', 'node', node) + + def buildReply(self, typ): + """ + Build and return another Iq object of specified type. The to, from and + query child node of new Iq are pre-set as reply to this Iq. + """ + iq = Iq(typ, to=self.getFrom(), frm=self.getTo(), attrs={'id': self.getID()}) + if self.getTag('query'): + iq.setQueryNS(self.getQueryNS()) return iq class ErrorNode(Node): - ''' XMPP-style error element. - In the case of stanza error should be attached to XMPP stanza. - In the case of stream-level errors should be used separately. ''' - def __init__(self,name,code=None,typ=None,text=None): - ''' Create new error node object. - Mandatory parameter: name - name of error condition. - Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol.''' + """ + XMPP-style error element + + In the case of stanza error should be attached to XMPP stanza. + In the case of stream-level errors should be used separately. + """ + + def __init__(self, name, code=None, typ=None, text=None): + """ + Mandatory parameter: name - name of error condition. + Optional parameters: code, typ, text. Used for backwards compartibility with older jabber protocol. + """ if name in ERRORS: - cod,type_,txt=ERRORS[name] - ns=name.split()[0] - else: cod,ns,type_,txt='500',NS_STANZAS,'cancel','' - if typ: type_=typ - if code: cod=code - if text: txt=text - Node.__init__(self,'error',{},[Node(name)]) - if type_: self.setAttr('type',type_) - if not cod: self.setName('stream:error') - if txt: self.addChild(node=Node(ns+' text',{},[txt])) - if cod: self.setAttr('code',cod) + cod, type_, txt = ERRORS[name] + ns = name.split()[0] + else: + cod, ns, type_, txt = '500', NS_STANZAS, 'cancel', '' + if typ: + type_ = typ + if code: + cod = code + if text: + txt = text + Node.__init__(self,'error', {}, [Node(name)]) + if type_: + self.setAttr('type', type_) + if not cod: + self.setName('stream:error') + if txt: + self.addChild(node=Node(ns + ' text', {}, [txt])) + if cod: + self.setAttr('code', cod) class Error(Protocol): - ''' Used to quickly transform received stanza into error reply.''' - def __init__(self,node,error,reply=1): - ''' Create error reply basing on the received 'node' stanza and the 'error' error condition. - If the 'node' is not the received stanza but locally created ('to' and 'from' fields needs not swapping) - specify the 'reply' argument as false.''' - if reply: Protocol.__init__(self,to=node.getFrom(),frm=node.getTo(),node=node) - else: Protocol.__init__(self,node=node) + """ + Used to quickly transform received stanza into error reply + """ + + def __init__(self, node, error, reply=1): + """ + Create error reply basing on the received 'node' stanza and the 'error' + error condition + + If the 'node' is not the received stanza but locally created ('to' and + 'from' fields needs not swapping) specify the 'reply' argument as false. + """ + if reply: + Protocol.__init__(self, to=node.getFrom(), frm=node.getTo(), node=node) + else: + Protocol.__init__(self, node=node) self.setError(error) - if node.getType()=='error': self.__str__=self.__dupstr__ - def __dupstr__(self,dup1=None,dup2=None): - ''' Dummy function used as preventor of creating error node in reply to error node. - I.e. you will not be able to serialise "double" error into string. - ''' + if node.getType() == 'error': + self.__str__ = self.__dupstr__ + + def __dupstr__(self, dup1=None, dup2=None): + """ + Dummy function used as preventor of creating error node in reply to error + node. I.e. you will not be able to serialise "double" error into string. + """ return '' class DataField(Node): - ''' This class is used in the DataForm class to describe the single data item. - If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) - then you will need to work with instances of this class. ''' - def __init__(self,name=None,value=None,typ=None,required=0,desc=None,options=[],node=None): - ''' Create new data field of specified name,value and type. - Also 'required','desc' and 'options' fields can be set. - Alternatively other XML object can be passed in as the 'node' parameted to replicate it as a new datafiled. - ''' - Node.__init__(self,'field',node=node) - if name: self.setVar(name) - if isinstance(value, (list, tuple)): self.setValues(value) - elif value: self.setValue(value) - if typ: self.setType(typ) - elif not typ and not node: self.setType('text-single') - if required: self.setRequired(required) - if desc: self.setDesc(desc) - if options: self.setOptions(options) - def setRequired(self,req=1): - ''' Change the state of the 'required' flag. ''' - if req: self.setTag('required') + """ + This class is used in the DataForm class to describe the single data item + + If you are working with jabber:x:data (XEP-0004, XEP-0068, XEP-0122) then + you will need to work with instances of this class. + """ + + def __init__(self, name=None, value=None, typ=None, required=0, desc=None, + options=[], node=None): + """ + Create new data field of specified name,value and type + + Also 'required','desc' and 'options' fields can be set. Alternatively + other XML object can be passed in as the 'node' parameted to replicate it + as a new datafiled. + """ + Node.__init__(self, 'field', node=node) + if name: + self.setVar(name) + if isinstance(value, (list, tuple)): + self.setValues(value) + elif value: + self.setValue(value) + if typ: + self.setType(typ) + elif not typ and not node: + self.setType('text-single') + if required: + self.setRequired(required) + if desc: + self.setDesc(desc) + if options: + self.setOptions(options) + + def setRequired(self, req=1): + """ + Change the state of the 'required' flag + """ + if req: + self.setTag('required') else: - try: self.delChild('required') - except ValueError: return + try: + self.delChild('required') + except ValueError: + return + def isRequired(self): - ''' Returns in this field a required one. ''' + """ + Return in this field a required one + """ return self.getTag('required') - def setDesc(self,desc): - ''' Set the description of this field. ''' - self.setTagData('desc',desc) + + def setDesc(self, desc): + """ + Set the description of this field + """ + self.setTagData('desc', desc) + def getDesc(self): - ''' Return the description of this field. ''' + """ + Return the description of this field + """ return self.getTagData('desc') - def setValue(self,val): - ''' Set the value of this field. ''' - self.setTagData('value',val) + + def setValue(self, val): + """ + Set the value of this field + """ + self.setTagData('value', val) + def getValue(self): return self.getTagData('value') - def setValues(self,lst): - ''' Set the values of this field as values-list. - Replaces all previous filed values! If you need to just add a value - use addValue method.''' - while self.getTag('value'): self.delChild('value') - for val in lst: self.addValue(val) - def addValue(self,val): - ''' Add one more value to this field. Used in 'get' iq's or such.''' - self.addChild('value',{},[val]) + + def setValues(self, lst): + """ + Set the values of this field as values-list. Replaces all previous filed + values! If you need to just add a value - use addValue method + """ + while self.getTag('value'): + self.delChild('value') + for val in lst: + self.addValue(val) + + def addValue(self, val): + """ + Add one more value to this field. Used in 'get' iq's or such + """ + self.addChild('value', {}, [val]) + def getValues(self): - ''' Return the list of values associated with this field.''' - ret=[] - for tag in self.getTags('value'): ret.append(tag.getData()) + """ + Return the list of values associated with this field + """ + ret = [] + for tag in self.getTags('value'): + ret.append(tag.getData()) return ret + def getOptions(self): - ''' Return label-option pairs list associated with this field.''' - ret=[] - for tag in self.getTags('option'): ret.append([tag.getAttr('label'),tag.getTagData('value')]) + """ + Return label-option pairs list associated with this field + """ + ret = [] + for tag in self.getTags('option'): + ret.append([tag.getAttr('label'), tag.getTagData('value')]) return ret - def setOptions(self,lst): - ''' Set label-option pairs list associated with this field.''' - while self.getTag('option'): self.delChild('option') - for opt in lst: self.addOption(opt) - def addOption(self,opt): - ''' Add one more label-option pair to this field.''' - if isinstance(opt, basestring): self.addChild('option').setTagData('value',opt) - else: self.addChild('option',{'label':opt[0]}).setTagData('value',opt[1]) + + def setOptions(self, lst): + """ + Set label-option pairs list associated with this field + """ + while self.getTag('option'): + self.delChild('option') + for opt in lst: + self.addOption(opt) + + def addOption(self, opt): + """ + Add one more label-option pair to this field + """ + if isinstance(opt, basestring): + self.addChild('option').setTagData('value', opt) + else: + self.addChild('option', {'label': opt[0]}).setTagData('value', opt[1]) + def getType(self): - ''' Get type of this field. ''' + """ + Get type of this field + """ return self.getAttr('type') - def setType(self,val): - ''' Set type of this field. ''' - return self.setAttr('type',val) + + def setType(self, val): + """ + Set type of this field + """ + return self.setAttr('type', val) + def getVar(self): - ''' Get 'var' attribute value of this field. ''' + """ + Get 'var' attribute value of this field + """ return self.getAttr('var') - def setVar(self,val): - ''' Set 'var' attribute value of this field. ''' + + def setVar(self, val): + """ + Set 'var' attribute value of this field + """ return self.setAttr('var',val) class DataForm(Node): - ''' DataForm class. Used for manipulating dataforms in XMPP. - Relevant XEPs: 0004, 0068, 0122. - Can be used in disco, pub-sub and many other applications.''' - def __init__(self, typ=None, data=[], title=None, node=None): - ''' - Create new dataform of type 'typ'. 'data' is the list of DataField - instances that this dataform contains, 'title' - the title string. - You can specify the 'node' argument as the other node to be used as - base for constructing this dataform. + """ + Used for manipulating dataforms in XMPP - title and instructions is optional and SHOULD NOT contain newlines. - Several instructions MAY be present. - 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' ) - 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively. - 'cancel' form can not contain any fields. All other forms contains AT LEAST one field. - 'title' MAY be included in forms of type "form" and "result" - ''' - Node.__init__(self,'x',node=node) + Relevant XEPs: 0004, 0068, 0122. Can be used in disco, pub-sub and many + other applications. + """ + def __init__(self, typ=None, data=[], title=None, node=None): + """ + Create new dataform of type 'typ'. 'data' is the list of DataField + instances that this dataform contains, 'title' - the title string. You + can specify the 'node' argument as the other node to be used as base for + constructing this dataform + + title and instructions is optional and SHOULD NOT contain newlines. + Several instructions MAY be present. + 'typ' can be one of ('form' | 'submit' | 'cancel' | 'result' ) + 'typ' of reply iq can be ( 'result' | 'set' | 'set' | 'result' ) respectively. + 'cancel' form can not contain any fields. All other forms contains AT LEAST one field. + 'title' MAY be included in forms of type "form" and "result" + """ + Node.__init__(self, 'x', node=node) if node: - newkids=[] + newkids = [] for n in self.getChildren(): - if n.getName()=='field': newkids.append(DataField(node=n)) - else: newkids.append(n) - self.kids=newkids - if typ: self.setType(typ) + if n.getName() == 'field': + newkids.append(DataField(node=n)) + else: + newkids.append(n) + self.kids = newkids + if typ: + self.setType(typ) self.setNamespace(NS_DATA) - if title: self.setTitle(title) + if title: + self.setTitle(title) if isinstance(data, dict): - newdata=[] - for name in data.keys(): newdata.append(DataField(name,data[name])) - data=newdata + newdata = [] + for name in data.keys(): + newdata.append(DataField(name, data[name])) + data = newdata for child in data: - if isinstance(child, basestring): self.addInstructions(child) - elif child.__class__.__name__=='DataField': self.kids.append(child) - else: self.kids.append(DataField(node=child)) + if isinstance(child, basestring): + self.addInstructions(child) + elif child.__class__.__name__ == 'DataField': + self.kids.append(child) + else: + self.kids.append(DataField(node=child)) + def getType(self): - ''' Return the type of dataform. ''' + """ + Return the type of dataform + """ return self.getAttr('type') - def setType(self,typ): - ''' Set the type of dataform. ''' - self.setAttr('type',typ) + + def setType(self, typ): + """ + Set the type of dataform + """ + self.setAttr('type', typ) + def getTitle(self): - ''' Return the title of dataform. ''' + """ + Return the title of dataform + """ return self.getTagData('title') - def setTitle(self,text): - ''' Set the title of dataform. ''' - self.setTagData('title',text) + + def setTitle(self, text): + """ + Set the title of dataform + """ + self.setTagData('title', text) + def getInstructions(self): - ''' Return the instructions of dataform. ''' + """ + Return the instructions of dataform + """ return self.getTagData('instructions') - def setInstructions(self,text): - ''' Set the instructions of dataform. ''' - self.setTagData('instructions',text) - def addInstructions(self,text): - ''' Add one more instruction to the dataform. ''' - self.addChild('instructions',{},[text]) - def getField(self,name): - ''' Return the datafield object with name 'name' (if exists). ''' - return self.getTag('field',attrs={'var':name}) - def setField(self,name): - ''' Create if nessessary or get the existing datafield object with name 'name' and return it. ''' - f=self.getField(name) - if f: return f + + def setInstructions(self, text): + """ + Set the instructions of dataform + """ + self.setTagData('instructions', text) + + def addInstructions(self, text): + """ + Add one more instruction to the dataform + """ + self.addChild('instructions', {}, [text]) + + def getField(self, name): + """ + Return the datafield object with name 'name' (if exists) + """ + return self.getTag('field', attrs={'var': name}) + + def setField(self, name): + """ + Create if nessessary or get the existing datafield object with name + 'name' and return it + """ + f = self.getField(name) + if f: + return f return self.addChild(node=DataField(name)) + def asDict(self): - ''' Represent dataform as simple dictionary mapping of datafield names to their values.''' - ret={} + """ + Represent dataform as simple dictionary mapping of datafield names to + their values + """ + ret = {} for field in self.getTags('field'): - name=field.getAttr('var') - typ=field.getType() + name = field.getAttr('var') + typ = field.getType() if isinstance(typ, basestring) and typ.endswith('-multi'): - val=[] - for i in field.getTags('value'): val.append(i.getData()) - else: val=field.getTagData('value') - ret[name]=val - if self.getTag('instructions'): ret['instructions']=self.getInstructions() + val = [] + for i in field.getTags('value'): + val.append(i.getData()) + else: + val = field.getTagData('value') + ret[name] = val + if self.getTag('instructions'): + ret['instructions'] = self.getInstructions() return ret - def __getitem__(self,name): - ''' Simple dictionary interface for getting datafields values by their names.''' - item=self.getField(name) - if item: return item.getValue() + + def __getitem__(self, name): + """ + Simple dictionary interface for getting datafields values by their names + """ + item = self.getField(name) + if item: + return item.getValue() raise IndexError('No such field') - def __setitem__(self,name,val): - ''' Simple dictionary interface for setting datafields values by their names.''' + + def __setitem__(self, name, val): + """ + Simple dictionary interface for setting datafields values by their names + """ return self.setField(name).setValue(val) # vim: se ts=3: