basexmpp.py 29.5 KB
Newer Older
Lance Stout's avatar
Lance Stout committed
1
# -*- coding: utf-8 -*-
Nathan Fritz's avatar
Nathan Fritz committed
2
"""
louiz’'s avatar
louiz’ committed
3
    slixmpp.basexmpp
Lance Stout's avatar
Lance Stout committed
4
    ~~~~~~~~~~~~~~~~~~
Nathan Fritz's avatar
Nathan Fritz committed
5

Lance Stout's avatar
Lance Stout committed
6
7
8
    This module provides the common XMPP functionality
    for both clients and components.

louiz’'s avatar
louiz’ committed
9
    Part of Slixmpp: The Slick XMPP Library
Lance Stout's avatar
Lance Stout committed
10
11
12

    :copyright: (c) 2011 Nathanael C. Fritz
    :license: MIT, see LICENSE for more details
Nathan Fritz's avatar
Nathan Fritz committed
13
"""
14
15

import logging
16
import threading
Nathan Fritz's avatar
Nathan Fritz committed
17

louiz’'s avatar
louiz’ committed
18
19
20
from slixmpp import plugins, roster, stanza
from slixmpp.api import APIRegistry
from slixmpp.exceptions import IqError, IqTimeout
Nathan Fritz's avatar
Nathan Fritz committed
21

louiz’'s avatar
louiz’ committed
22
23
24
25
from slixmpp.stanza import Message, Presence, Iq, StreamError
from slixmpp.stanza.roster import Roster
from slixmpp.stanza.nick import Nick
from slixmpp.stanza.htmlim import HTMLIM
Nathan Fritz's avatar
Nathan Fritz committed
26

louiz’'s avatar
louiz’ committed
27
28
29
30
31
from slixmpp.xmlstream import XMLStream, JID
from slixmpp.xmlstream import ET, register_stanza_plugin
from slixmpp.xmlstream.matcher import MatchXPath
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.stanzabase import XML_NS
32

louiz’'s avatar
louiz’ committed
33
from slixmpp.plugins import PluginManager, load_plugin
Lance Stout's avatar
Lance Stout committed
34

35

36
37
log = logging.getLogger(__name__)

38
39
40
41
42
43
44
class BaseXMPP(XMLStream):

    """
    The BaseXMPP class adapts the generic XMLStream class for use
    with XMPP. It also provides a plugin mechanism to easily extend
    and add support for new XMPP features.

Lance Stout's avatar
Lance Stout committed
45
46
    :param default_ns: Ensure that the correct default XML namespace
                       is used during initialization.
47
48
    """

49
    def __init__(self, jid='', default_ns='jabber:client'):
50
51
52
        XMLStream.__init__(self)

        self.default_ns = default_ns
53
        self.stream_ns = 'http://etherx.jabber.org/streams'
Lance Stout's avatar
Lance Stout committed
54
        self.namespace_map[self.stream_ns] = 'stream'
55

Lance Stout's avatar
Lance Stout committed
56
57
58
        #: An identifier for the stream as given by the server.
        self.stream_id = None

59
        #: The JabberID (JID) requested for this connection.
60
        self.requested_jid = JID(jid, cache_lock=True)
61
62
63
64

        #: The JabberID (JID) used by this connection,
        #: as set after session binding. This may even be a
        #: different bare JID than what was requested.
65
        self.boundjid = JID(jid, cache_lock=True)
66

Lance Stout's avatar
Lance Stout committed
67
        self._expected_server_name = self.boundjid.host
68
69
70
71
72
        self._redirect_attempts = 0

        #: The maximum number of consecutive see-other-host
        #: redirections that will be followed before quitting.
        self.max_redirects = 5
73

74
75
        self.session_bind_event = threading.Event()

Lance Stout's avatar
Lance Stout committed
76
        #: A dictionary mapping plugin names to plugins.
Lance Stout's avatar
Lance Stout committed
77
        self.plugin = PluginManager(self)
Lance Stout's avatar
Lance Stout committed
78
79
80
81

        #: Configuration options for whitelisted plugins.
        #: If a plugin is registered without any configuration,
        #: and there is an entry here, it will be used.
82
        self.plugin_config = {}
Lance Stout's avatar
Lance Stout committed
83
84
85

        #: A list of plugins that will be loaded if
        #: :meth:`register_plugins` is called.
86
        self.plugin_whitelist = []
87

Lance Stout's avatar
Lance Stout committed
88
89
90
        #: The main roster object. This roster supports multiple
        #: owner JIDs, as in the case for components. For clients
        #: which only have a single JID, see :attr:`client_roster`.
Lance Stout's avatar
Lance Stout committed
91
        self.roster = roster.Roster(self)
92
        self.roster.add(self.boundjid)
Lance Stout's avatar
Lance Stout committed
93
94
95
96
97

        #: The single roster for the bound JID. This is the
        #: equivalent of::
        #:
        #:     self.roster[self.boundjid.bare]
98
        self.client_roster = self.roster[self.boundjid]
99

Lance Stout's avatar
Lance Stout committed
100
101
102
        #: The distinction between clients and components can be
        #: important, primarily for choosing how to handle the
        #: ``'to'`` and ``'from'`` JIDs of stanzas.
103
        self.is_component = False
Lance Stout's avatar
Lance Stout committed
104

105
106
107
108
109
110
111
112
113
114
115
        #: Messages may optionally be tagged with ID values. Setting
        #: :attr:`use_message_ids` to `True` will assign all outgoing
        #: messages an ID. Some plugin features require enabling
        #: this option.
        self.use_message_ids = False

        #: Presence updates may optionally be tagged with ID values.
        #: Setting :attr:`use_message_ids` to `True` will assign all
        #: outgoing messages an ID.
        self.use_presence_ids = False

116
117
118
        #: The API registry is a way to process callbacks based on
        #: JID+node combinations. Each callback in the registry is
        #: marked with:
Lance Stout's avatar
Lance Stout committed
119
        #:
120
121
122
123
124
125
126
127
128
129
130
131
        #:   - An API name, e.g. xep_0030
        #:   - The name of an action, e.g. get_info
        #:   - The JID that will be affected
        #:   - The node that will be affected
        #:
        #: API handlers with no JID or node will act as global handlers,
        #: while those with a JID and no node will service all nodes
        #: for a JID, and handlers with both a JID and node will be
        #: used only for that specific combination. The handler that
        #: provides the most specificity will be used.
        self.api = APIRegistry(self)

Lance Stout's avatar
Lance Stout committed
132
133
134
        #: Flag indicating that the initial presence broadcast has
        #: been sent. Until this happens, some servers may not
        #: behave as expected when sending stanzas.
135
136
        self.sentpresence = False

louiz’'s avatar
louiz’ committed
137
        #: A reference to :mod:`slixmpp.stanza` to make accessing
Lance Stout's avatar
Lance Stout committed
138
        #: stanza classes easier.
139
        self.stanza = stanza
Lance Stout's avatar
Lance Stout committed
140

141
142
143
144
145
146
147
148
149
        self.register_handler(
            Callback('IM',
                     MatchXPath('{%s}message/{%s}body' % (self.default_ns,
                                                          self.default_ns)),
                     self._handle_message))
        self.register_handler(
            Callback('Presence',
                     MatchXPath("{%s}presence" % self.default_ns),
                     self._handle_presence))
150

151
152
153
154
        self.register_handler(
            Callback('Stream Error',
                     MatchXPath("{%s}error" % self.stream_ns),
                     self._handle_stream_error))
155

156
157
        self.add_event_handler('session_start',
                               self._handle_session_start)
158
159
        self.add_event_handler('disconnected',
                               self._handle_disconnected)
Lance Stout's avatar
Lance Stout committed
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
        self.add_event_handler('presence_available',
                               self._handle_available)
        self.add_event_handler('presence_dnd',
                               self._handle_available)
        self.add_event_handler('presence_xa',
                               self._handle_available)
        self.add_event_handler('presence_chat',
                               self._handle_available)
        self.add_event_handler('presence_away',
                               self._handle_available)
        self.add_event_handler('presence_unavailable',
                               self._handle_unavailable)
        self.add_event_handler('presence_subscribe',
                               self._handle_subscribe)
        self.add_event_handler('presence_subscribed',
                               self._handle_subscribed)
        self.add_event_handler('presence_unsubscribe',
                               self._handle_unsubscribe)
        self.add_event_handler('presence_unsubscribed',
                               self._handle_unsubscribed)
        self.add_event_handler('roster_subscription_request',
                               self._handle_new_subscription)
182
183

        # Set up the XML stream with XMPP's root stanzas.
Lance Stout's avatar
Lance Stout committed
184
185
186
        self.register_stanza(Message)
        self.register_stanza(Iq)
        self.register_stanza(Presence)
187
        self.register_stanza(StreamError)
188
189

        # Initialize a few default stanza plugins.
Lance Stout's avatar
Lance Stout committed
190
191
        register_stanza_plugin(Iq, Roster)
        register_stanza_plugin(Message, Nick)
192

193
    def start_stream_handler(self, xml):
Lance Stout's avatar
Lance Stout committed
194
        """Save the stream ID once the streams have been established.
195

Lance Stout's avatar
Lance Stout committed
196
        :param xml: The incoming stream's root element.
197
        """
198
        self.stream_id = xml.get('id', '')
199
200
        self.stream_version = xml.get('version', '')
        self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None)
201

202
203
204
205
        if not self.is_component and not self.stream_version:
            log.warning('Legacy XMPP 0.9 protocol detected.')
            self.event('legacy_protocol')

206
    def init_plugins(self):
207
        for name in self.plugin:
Lance Stout's avatar
Lance Stout committed
208
209
210
211
            if not hasattr(self.plugin[name], 'post_inited'):
                if hasattr(self.plugin[name], 'post_init'):
                    self.plugin[name].post_init()
                self.plugin[name].post_inited = True
212
213

    def register_plugin(self, plugin, pconfig={}, module=None):
Lance Stout's avatar
Lance Stout committed
214
        """Register and configure  a plugin for use in this stream.
215

Lance Stout's avatar
Lance Stout committed
216
        :param plugin: The name of the plugin class. Plugin names must
217
                       be unique.
Lance Stout's avatar
Lance Stout committed
218
219
220
        :param pconfig: A dictionary of configuration data for the plugin.
                        Defaults to an empty dictionary.
        :param module: Optional refence to the module containing the plugin
221
222
                       class if using custom plugins.
        """
Lance Stout's avatar
Lance Stout committed
223
224
225
226
227
228

        # Use the global plugin config cache, if applicable
        if not pconfig:
            pconfig = self.plugin_config.get(plugin, {})

        if not self.plugin.registered(plugin):
229
            load_plugin(plugin, module)
Lance Stout's avatar
Lance Stout committed
230
        self.plugin.enable(plugin, pconfig)
231
232

    def register_plugins(self):
Lance Stout's avatar
Lance Stout committed
233
        """Register and initialize all built-in plugins.
234
235

        Optionally, the list of plugins loaded may be limited to those
Lance Stout's avatar
Lance Stout committed
236
        contained in :attr:`plugin_whitelist`.
237

Lance Stout's avatar
Lance Stout committed
238
        Plugin configurations stored in :attr:`plugin_config` will be used.
239
240
241
242
243
244
245
246
        """
        if self.plugin_whitelist:
            plugin_list = self.plugin_whitelist
        else:
            plugin_list = plugins.__all__

        for plugin in plugin_list:
            if plugin in plugins.__all__:
Lance Stout's avatar
Lance Stout committed
247
                self.register_plugin(plugin)
248
249
250
251
            else:
                raise NameError("Plugin %s not in plugins.__all__." % plugin)

    def __getitem__(self, key):
Lance Stout's avatar
Lance Stout committed
252
        """Return a plugin given its name, if it has been registered."""
253
254
255
        if key in self.plugin:
            return self.plugin[key]
        else:
Lance Stout's avatar
Lance Stout committed
256
            log.warning("Plugin '%s' is not loaded.", key)
257
258
259
            return False

    def get(self, key, default):
Lance Stout's avatar
Lance Stout committed
260
        """Return a plugin given its name, if it has been registered."""
261
262
263
264
        return self.plugin.get(key, default)

    def Message(self, *args, **kwargs):
        """Create a Message stanza associated with this stream."""
265
266
267
        msg = Message(self, *args, **kwargs)
        msg['lang'] = self.default_lang
        return msg
268
269
270
271
272
273
274

    def Iq(self, *args, **kwargs):
        """Create an Iq stanza associated with this stream."""
        return Iq(self, *args, **kwargs)

    def Presence(self, *args, **kwargs):
        """Create a Presence stanza associated with this stream."""
275
276
277
        pres = Presence(self, *args, **kwargs)
        pres['lang'] = self.default_lang
        return pres
278

Lance Stout's avatar
Lance Stout committed
279
    def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None):
Lance Stout's avatar
Lance Stout committed
280
281
282
283
        """Create a new Iq stanza with a given Id and from JID.

        :param id: An ideally unique ID value for this stanza thread.
                   Defaults to 0.
louiz’'s avatar
louiz’ committed
284
        :param ifrom: The from :class:`~slixmpp.xmlstream.jid.JID`
Lance Stout's avatar
Lance Stout committed
285
                      to use for this stanza.
louiz’'s avatar
louiz’ committed
286
        :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID`
Lance Stout's avatar
Lance Stout committed
287
                    for this stanza.
louiz’'s avatar
louiz’ committed
288
        :param itype: The :class:`~slixmpp.stanza.iq.Iq`'s type,
Lance Stout's avatar
Lance Stout committed
289
290
291
                      one of: ``'get'``, ``'set'``, ``'result'``,
                      or ``'error'``.
        :param iquery: Optional namespace for adding a query element.
292
        """
293
294
295
296
297
        iq = self.Iq()
        iq['id'] = str(id)
        iq['to'] = ito
        iq['from'] = ifrom
        iq['type'] = itype
Lance Stout's avatar
Lance Stout committed
298
        iq['query'] = iquery
299
        return iq
300

301
    def make_iq_get(self, queryxmlns=None, ito=None, ifrom=None, iq=None):
louiz’'s avatar
louiz’ committed
302
        """Create an :class:`~slixmpp.stanza.iq.Iq` stanza of type ``'get'``.
303
304
305

        Optionally, a query element may be added.

Lance Stout's avatar
Lance Stout committed
306
        :param queryxmlns: The namespace of the query to use.
louiz’'s avatar
louiz’ committed
307
        :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID`
Lance Stout's avatar
Lance Stout committed
308
                    for this stanza.
louiz’'s avatar
louiz’ committed
309
        :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID`
Lance Stout's avatar
Lance Stout committed
310
311
312
                      to use for this stanza.
        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
313
        """
314
315
316
317
318
319
320
321
322
        if not iq:
            iq = self.Iq()
        iq['type'] = 'get'
        iq['query'] = queryxmlns
        if ito:
            iq['to'] = ito
        if ifrom:
            iq['from'] = ifrom
        return iq
323

324
    def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None):
325
        """
louiz’'s avatar
louiz’ committed
326
        Create an :class:`~slixmpp.stanza.iq.Iq` stanza of type
Lance Stout's avatar
Lance Stout committed
327
328
329
        ``'result'`` with the given ID value.

        :param id: An ideally unique ID value. May use :meth:`new_id()`.
louiz’'s avatar
louiz’ committed
330
        :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID`
Lance Stout's avatar
Lance Stout committed
331
                    for this stanza.
louiz’'s avatar
louiz’ committed
332
        :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID`
Lance Stout's avatar
Lance Stout committed
333
334
335
                      to use for this stanza.
        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
336
        """
337
338
339
340
341
342
343
344
345
346
347
        if not iq:
            iq = self.Iq()
            if id is None:
                id = self.new_id()
            iq['id'] = id
        iq['type'] = 'result'
        if ito:
            iq['to'] = ito
        if ifrom:
            iq['from'] = ifrom
        return iq
348

349
    def make_iq_set(self, sub=None, ito=None, ifrom=None, iq=None):
350
        """
louiz’'s avatar
louiz’ committed
351
        Create an :class:`~slixmpp.stanza.iq.Iq` stanza of type ``'set'``.
352
353
354
355

        Optionally, a substanza may be given to use as the
        stanza's payload.

Lance Stout's avatar
Lance Stout committed
356
        :param sub: Either an
louiz’'s avatar
louiz’ committed
357
                    :class:`~slixmpp.xmlstream.stanzabase.ElementBase`
Lance Stout's avatar
Lance Stout committed
358
                    stanza object or an
Lance Stout's avatar
Lance Stout committed
359
                    :class:`~xml.etree.ElementTree.Element` XML object
louiz’'s avatar
louiz’ committed
360
361
                    to use as the :class:`~slixmpp.stanza.iq.Iq`'s payload.
        :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID`
Lance Stout's avatar
Lance Stout committed
362
                    for this stanza.
louiz’'s avatar
louiz’ committed
363
        :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID`
Lance Stout's avatar
Lance Stout committed
364
365
366
                      to use for this stanza.
        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
367
        """
368
369
370
        if not iq:
            iq = self.Iq()
        iq['type'] = 'set'
371
372
        if sub != None:
            iq.append(sub)
373
374
375
376
        if ito:
            iq['to'] = ito
        if ifrom:
            iq['from'] = ifrom
377
378
379
        return iq

    def make_iq_error(self, id, type='cancel',
380
381
                      condition='feature-not-implemented',
                      text=None, ito=None, ifrom=None, iq=None):
382
        """
louiz’'s avatar
louiz’ committed
383
        Create an :class:`~slixmpp.stanza.iq.Iq` stanza of type ``'error'``.
Lance Stout's avatar
Lance Stout committed
384
385

        :param id: An ideally unique ID value. May use :meth:`new_id()`.
Lance Stout's avatar
Lance Stout committed
386
        :param type: The type of the error, such as ``'cancel'`` or
Lance Stout's avatar
Lance Stout committed
387
                     ``'modify'``. Defaults to ``'cancel'``.
Lance Stout's avatar
Lance Stout committed
388
        :param condition: The error condition. Defaults to
Lance Stout's avatar
Lance Stout committed
389
390
                          ``'feature-not-implemented'``.
        :param text: A message describing the cause of the error.
louiz’'s avatar
louiz’ committed
391
        :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID`
Lance Stout's avatar
Lance Stout committed
392
                    for this stanza.
louiz’'s avatar
louiz’ committed
393
        :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID`
Lance Stout's avatar
Lance Stout committed
394
395
396
                      to use for this stanza.
        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
397
        """
398
399
400
401
402
403
404
405
406
407
        if not iq:
            iq = self.Iq()
        iq['id'] = id
        iq['error']['type'] = type
        iq['error']['condition'] = condition
        iq['error']['text'] = text
        if ito:
            iq['to'] = ito
        if ifrom:
            iq['from'] = ifrom
408
409
        return iq

410
    def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None):
411
        """
louiz’'s avatar
louiz’ committed
412
        Create or modify an :class:`~slixmpp.stanza.iq.Iq` stanza
Lance Stout's avatar
Lance Stout committed
413
414
415
416
417
        to use the given query namespace.

        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
        :param xmlns: The query's namespace.
louiz’'s avatar
louiz’ committed
418
        :param ito: The destination :class:`~slixmpp.xmlstream.jid.JID`
Lance Stout's avatar
Lance Stout committed
419
                    for this stanza.
louiz’'s avatar
louiz’ committed
420
        :param ifrom: The ``'from'`` :class:`~slixmpp.xmlstream.jid.JID`
Lance Stout's avatar
Lance Stout committed
421
                      to use for this stanza.
422
423
424
425
        """
        if not iq:
            iq = self.Iq()
        iq['query'] = xmlns
426
427
428
429
        if ito:
            iq['to'] = ito
        if ifrom:
            iq['from'] = ifrom
430
431
432
        return iq

    def make_query_roster(self, iq=None):
Lance Stout's avatar
Lance Stout committed
433
        """Create a roster query element.
434

Lance Stout's avatar
Lance Stout committed
435
436
        :param iq: Optionally use an existing stanza instead
                   of generating a new one.
437
438
439
440
441
442
443
444
        """
        if iq:
            iq['query'] = 'jabber:iq:roster'
        return ET.Element("{jabber:iq:roster}query")

    def make_message(self, mto, mbody=None, msubject=None, mtype=None,
                     mhtml=None, mfrom=None, mnick=None):
        """
Lance Stout's avatar
Lance Stout committed
445
        Create and initialize a new
louiz’'s avatar
louiz’ committed
446
        :class:`~slixmpp.stanza.message.Message` stanza.
Lance Stout's avatar
Lance Stout committed
447
448
449
450
451
452
453
454
455
456
457

        :param mto: The recipient of the message.
        :param mbody: The main contents of the message.
        :param msubject: Optional subject for the message.
        :param mtype: The message's type, such as ``'chat'`` or
                      ``'groupchat'``.
        :param mhtml: Optional HTML body content in the form of a string.
        :param mfrom: The sender of the message. if sending from a client,
                      be aware that some servers require that the full JID
                      of the sender be used.
        :param mnick: Optional nickname of the sender.
458
459
460
461
462
463
464
465
466
467
468
        """
        message = self.Message(sto=mto, stype=mtype, sfrom=mfrom)
        message['body'] = mbody
        message['subject'] = msubject
        if mnick is not None:
            message['nick'] = mnick
        if mhtml is not None:
            message['html']['body'] = mhtml
        return message

    def make_presence(self, pshow=None, pstatus=None, ppriority=None,
Lance Stout's avatar
Lance Stout committed
469
                      pto=None, ptype=None, pfrom=None, pnick=None):
470
        """
Lance Stout's avatar
Lance Stout committed
471
        Create and initialize a new
louiz’'s avatar
louiz’ committed
472
        :class:`~slixmpp.stanza.presence.Presence` stanza.
Lance Stout's avatar
Lance Stout committed
473
474
475
476
477
478
479
480

        :param pshow: The presence's show value.
        :param pstatus: The presence's status message.
        :param ppriority: This connection's priority.
        :param pto: The recipient of a directed presence.
        :param ptype: The type of presence, such as ``'subscribe'``.
        :param pfrom: The sender of the presence.
        :param pnick: Optional nickname of the presence's sender.
481
482
483
484
        """
        presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto)
        if pshow is not None:
            presence['type'] = pshow
Lance Stout's avatar
Lance Stout committed
485
        if pfrom is None and self.is_component:
louiz’'s avatar
louiz’ committed
486
            presence['from'] = self.boundjid.full
487
488
        presence['priority'] = ppriority
        presence['status'] = pstatus
Lance Stout's avatar
Lance Stout committed
489
        presence['nick'] = pnick
490
491
492
493
494
        return presence

    def send_message(self, mto, mbody, msubject=None, mtype=None,
                     mhtml=None, mfrom=None, mnick=None):
        """
Lance Stout's avatar
Lance Stout committed
495
        Create, initialize, and send a new
louiz’'s avatar
louiz’ committed
496
        :class:`~slixmpp.stanza.message.Message` stanza.
Lance Stout's avatar
Lance Stout committed
497
498
499
500
501
502
503
504
505
506
507

        :param mto: The recipient of the message.
        :param mbody: The main contents of the message.
        :param msubject: Optional subject for the message.
        :param mtype: The message's type, such as ``'chat'`` or
                      ``'groupchat'``.
        :param mhtml: Optional HTML body content in the form of a string.
        :param mfrom: The sender of the message. if sending from a client,
                      be aware that some servers require that the full JID
                      of the sender be used.
        :param mnick: Optional nickname of the sender.
508
        """
Lance Stout's avatar
Lance Stout committed
509
510
        self.make_message(mto, mbody, msubject, mtype,
                          mhtml, mfrom, mnick).send()
511
512

    def send_presence(self, pshow=None, pstatus=None, ppriority=None,
Lance Stout's avatar
Lance Stout committed
513
                      pto=None, pfrom=None, ptype=None, pnick=None):
514
        """
Lance Stout's avatar
Lance Stout committed
515
        Create, initialize, and send a new
louiz’'s avatar
louiz’ committed
516
        :class:`~slixmpp.stanza.presence.Presence` stanza.
Lance Stout's avatar
Lance Stout committed
517
518
519
520
521
522
523
524

        :param pshow: The presence's show value.
        :param pstatus: The presence's status message.
        :param ppriority: This connection's priority.
        :param pto: The recipient of a directed presence.
        :param ptype: The type of presence, such as ``'subscribe'``.
        :param pfrom: The sender of the presence.
        :param pnick: Optional nickname of the presence's sender.
Lance Stout's avatar
Lance Stout committed
525
        """
Lance Stout's avatar
Lance Stout committed
526
        self.make_presence(pshow, pstatus, ppriority, pto,
527
                           ptype, pfrom, pnick).send()
528
529
530
531

    def send_presence_subscription(self, pto, pfrom=None,
                                   ptype='subscribe', pnick=None):
        """
Lance Stout's avatar
Lance Stout committed
532
        Create, initialize, and send a new
louiz’'s avatar
louiz’ committed
533
        :class:`~slixmpp.stanza.presence.Presence` stanza of
Lance Stout's avatar
Lance Stout committed
534
        type ``'subscribe'``.
535

Lance Stout's avatar
Lance Stout committed
536
537
538
539
        :param pto: The recipient of a directed presence.
        :param pfrom: The sender of the presence.
        :param ptype: The type of presence, such as ``'subscribe'``.
        :param pnick: Optional nickname of the presence's sender.
540
        """
541
542
543
544
        self.make_presence(ptype=ptype,
                           pfrom=pfrom,
                           pto=JID(pto).bare,
                           pnick=pnick).send()
545

546
547
    @property
    def jid(self):
Lance Stout's avatar
Lance Stout committed
548
        """Attribute accessor for bare jid"""
549
        log.warning("jid property deprecated. Use boundjid.bare")
550
551
552
553
        return self.boundjid.bare

    @jid.setter
    def jid(self, value):
554
        log.warning("jid property deprecated. Use boundjid.bare")
555
556
557
558
        self.boundjid.bare = value

    @property
    def fulljid(self):
Lance Stout's avatar
Lance Stout committed
559
        """Attribute accessor for full jid"""
560
        log.warning("fulljid property deprecated. Use boundjid.full")
561
562
563
564
        return self.boundjid.full

    @fulljid.setter
    def fulljid(self, value):
565
        log.warning("fulljid property deprecated. Use boundjid.full")
566
        self.boundjid.full = value
Lance Stout's avatar
Lance Stout committed
567

568
569
    @property
    def resource(self):
Lance Stout's avatar
Lance Stout committed
570
        """Attribute accessor for jid resource"""
571
        log.warning("resource property deprecated. Use boundjid.resource")
572
573
574
575
        return self.boundjid.resource

    @resource.setter
    def resource(self, value):
576
        log.warning("fulljid property deprecated. Use boundjid.resource")
577
        self.boundjid.resource = value
Lance Stout's avatar
Lance Stout committed
578

579
580
    @property
    def username(self):
Lance Stout's avatar
Lance Stout committed
581
        """Attribute accessor for jid usernode"""
582
        log.warning("username property deprecated. Use boundjid.user")
583
584
585
586
        return self.boundjid.user

    @username.setter
    def username(self, value):
587
        log.warning("username property deprecated. Use boundjid.user")
588
589
590
591
        self.boundjid.user = value

    @property
    def server(self):
Lance Stout's avatar
Lance Stout committed
592
        """Attribute accessor for jid host"""
593
        log.warning("server property deprecated. Use boundjid.host")
594
595
596
597
        return self.boundjid.server

    @server.setter
    def server(self, value):
598
        log.warning("server property deprecated. Use boundjid.host")
599
600
        self.boundjid.server = value

601
602
    @property
    def auto_authorize(self):
Lance Stout's avatar
Lance Stout committed
603
        """Auto accept or deny subscription requests.
604

Lance Stout's avatar
Lance Stout committed
605
606
607
        If ``True``, auto accept subscription requests.
        If ``False``, auto deny subscription requests.
        If ``None``, don't automatically respond.
608
609
610
611
612
613
614
615
616
        """
        return self.roster.auto_authorize

    @auto_authorize.setter
    def auto_authorize(self, value):
        self.roster.auto_authorize = value

    @property
    def auto_subscribe(self):
Lance Stout's avatar
Lance Stout committed
617
        """Auto send requests for mutual subscriptions.
618

Lance Stout's avatar
Lance Stout committed
619
        If ``True``, auto send mutual subscription requests.
620
621
622
623
624
625
626
        """
        return self.roster.auto_subscribe

    @auto_subscribe.setter
    def auto_subscribe(self, value):
        self.roster.auto_subscribe = value

627
628
    def set_jid(self, jid):
        """Rip a JID apart and claim it as our own."""
Lance Stout's avatar
Lance Stout committed
629
        log.debug("setting jid to %s", jid)
630
        self.boundjid = JID(jid, cache_lock=True)
631
632
633
634
635
636
637
638
639
640

    def getjidresource(self, fulljid):
        if '/' in fulljid:
            return fulljid.split('/', 1)[-1]
        else:
            return ''

    def getjidbare(self, fulljid):
        return fulljid.split('/', 1)[0]

641
642
643
644
    def _handle_session_start(self, event):
        """Reset redirection attempt count."""
        self._redirect_attempts = 0

645
646
    def _handle_disconnected(self, event):
        """When disconnected, reset the roster"""
647
        self.roster.reset()
648
        self.session_bind_event.clear()
649

650
651
652
    def _handle_stream_error(self, error):
        self.event('stream_error', error)

653
654
        if error['condition'] == 'see-other-host':
            other_host = error['see_other_host']
655
656
657
658
659
660
661
662
663
            if not other_host:
                log.warning("No other host specified.")
                return

            if self._redirect_attempts > self.max_redirects:
                log.error("Exceeded maximum number of redirection attempts.")
                return

            self._redirect_attempts += 1
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682

            host = other_host
            port = 5222

            if '[' in other_host and ']' in other_host:
                host = other_host.split(']')[0][1:]
            elif ':' in other_host:
                host = other_host.split(':')[0]

            port_sec = other_host.split(']')[-1]
            if ':' in port_sec:
                port = int(port_sec.split(':')[1])

            self.address = (host, port)
            self.default_domain = host
            self.dns_records = None
            self.reconnect_delay = None
            self.reconnect()

683
684
    def _handle_message(self, msg):
        """Process incoming message stanzas."""
685
686
        if not self.is_component and not msg['to'].bare:
            msg['to'] = self.boundjid
687
688
        self.event('message', msg)

689
690
    def _handle_available(self, pres):
        self.roster[pres['to']][pres['from']].handle_available(pres)
691

692
693
    def _handle_unavailable(self, pres):
        self.roster[pres['to']][pres['from']].handle_unavailable(pres)
694

695
    def _handle_new_subscription(self, pres):
Lance Stout's avatar
Lance Stout committed
696
        """Attempt to automatically handle subscription requests.
Lance Stout's avatar
Lance Stout committed
697
698

        Subscriptions will be approved if the request is from
Lance Stout's avatar
Lance Stout committed
699
700
701
        a whitelisted JID, of :attr:`auto_authorize` is True. They
        will be rejected if :attr:`auto_authorize` is False. Setting
        :attr:`auto_authorize` to ``None`` will disable automatic
Lance Stout's avatar
Lance Stout committed
702
703
704
        subscription handling (except for whitelisted JIDs).

        If a subscription is accepted, a request for a mutual
Lance Stout's avatar
Lance Stout committed
705
        subscription will be sent if :attr:`auto_subscribe` is ``True``.
Lance Stout's avatar
Lance Stout committed
706
        """
707
708
        roster = self.roster[pres['to']]
        item = self.roster[pres['to']][pres['from']]
709
710
        if item['whitelisted']:
            item.authorize()
711
712
            if roster.auto_subscribe:
                item.subscribe()
713
714
715
716
717
718
719
        elif roster.auto_authorize:
            item.authorize()
            if roster.auto_subscribe:
                item.subscribe()
        elif roster.auto_authorize == False:
            item.unauthorize()

720
721
722
723
724
725
726
727
728
729
730
731
732
733
    def _handle_removed_subscription(self, pres):
        self.roster[pres['to']][pres['from']].handle_unauthorize(pres)

    def _handle_subscribe(self, pres):
        self.roster[pres['to']][pres['from']].handle_subscribe(pres)

    def _handle_subscribed(self, pres):
        self.roster[pres['to']][pres['from']].handle_subscribed(pres)

    def _handle_unsubscribe(self, pres):
        self.roster[pres['to']][pres['from']].handle_unsubscribe(pres)

    def _handle_unsubscribed(self, pres):
        self.roster[pres['to']][pres['from']].handle_unsubscribed(pres)
Lance Stout's avatar
Lance Stout committed
734

735
    def _handle_presence(self, presence):
Lance Stout's avatar
Lance Stout committed
736
        """Process incoming presence stanzas.
737
738
739

        Update the roster with presence information.
        """
740
741
742
        if not self.is_component and not presence['to'].bare:
            presence['to'] = self.boundjid

743
744
        self.event('presence', presence)
        self.event('presence_%s' % presence['type'], presence)
745
746
747
748
749
750
751
752
753
754

        # Check for changes in subscription state.
        if presence['type'] in ('subscribe', 'subscribed',
                                'unsubscribe', 'unsubscribed'):
            self.event('changed_subscription', presence)
            return
        elif not presence['type'] in ('available', 'unavailable') and \
             not presence['type'] in presence.showtypes:
            return

755
    def exception(self, exception):
Lance Stout's avatar
Lance Stout committed
756
        """Process any uncaught exceptions, notably
louiz’'s avatar
louiz’ committed
757
758
        :class:`~slixmpp.exceptions.IqError` and
        :class:`~slixmpp.exceptions.IqTimeout` exceptions.
759

Lance Stout's avatar
Lance Stout committed
760
        :param exception: An unhandled :class:`Exception` object.
761
762
763
        """
        if isinstance(exception, IqError):
            iq = exception.iq
Lance Stout's avatar
Lance Stout committed
764
765
            log.error('%s: %s', iq['error']['condition'],
                                iq['error']['text'])
766
767
768
            log.warning('You should catch IqError exceptions')
        elif isinstance(exception, IqTimeout):
            iq = exception.iq
Lance Stout's avatar
Lance Stout committed
769
            log.error('Request timed out: %s', iq)
770
            log.warning('You should catch IqTimeout exceptions')
771
772
773
774
775
        elif isinstance(exception, SyntaxError):
            # Hide stream parsing errors that occur when the
            # stream is disconnected (they've been handled, we
            # don't need to make a mess in the logs).
            pass
776
777
778
779
        else:
            log.exception(exception)


780
781
# Restore the old, lowercased name for backwards compatibility.
basexmpp = BaseXMPP
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797

# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.
BaseXMPP.registerPlugin = BaseXMPP.register_plugin
BaseXMPP.makeIq = BaseXMPP.make_iq
BaseXMPP.makeIqGet = BaseXMPP.make_iq_get
BaseXMPP.makeIqResult = BaseXMPP.make_iq_result
BaseXMPP.makeIqSet = BaseXMPP.make_iq_set
BaseXMPP.makeIqError = BaseXMPP.make_iq_error
BaseXMPP.makeIqQuery = BaseXMPP.make_iq_query
BaseXMPP.makeQueryRoster = BaseXMPP.make_query_roster
BaseXMPP.makeMessage = BaseXMPP.make_message
BaseXMPP.makePresence = BaseXMPP.make_presence
BaseXMPP.sendMessage = BaseXMPP.send_message
BaseXMPP.sendPresence = BaseXMPP.send_presence
BaseXMPP.sendPresenceSubscription = BaseXMPP.send_presence_subscription