summaryrefslogtreecommitdiff
path: root/sleekxmpp/stanza/iq.py
blob: c735ae64e7971004a1fe2ac5b09e2541648e188e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
"""
    SleekXMPP: The Sleek XMPP Library
    Copyright (C) 2010  Nathanael C. Fritz
    This file is part of SleekXMPP.

    See the file LICENSE for copying permission.
"""

from sleekxmpp.stanza import Error
from sleekxmpp.stanza.rootstanza import RootStanza
from sleekxmpp.xmlstream import RESPONSE_TIMEOUT
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
from sleekxmpp.xmlstream.handler import Waiter
from sleekxmpp.xmlstream.matcher import MatcherId


class Iq(RootStanza):

    """
    XMPP <iq> stanzas, or info/query stanzas, are XMPP's method of
    requesting and modifying information, similar to HTTP's GET and
    POST methods.

    Each <iq> stanza must have an 'id' value which associates the
    stanza with the response stanza. XMPP entities must always
    be given a response <iq> stanza with a type of 'result' after
    sending a stanza of type 'get' or 'set'.

    Most uses cases for <iq> stanzas will involve adding a <query>
    element whose namespace indicates the type of information
    desired. However, some custom XMPP applications use <iq> stanzas
    as a carrier stanza for an application-specific protocol instead.

    Example <iq> Stanzas:
        <iq to="user@example.com" type="get" id="314">
          <query xmlns="http://jabber.org/protocol/disco#items" />
        </iq>

        <iq to="user@localhost" type="result" id="17">
          <query xmlns='jabber:iq:roster'>
            <item jid='otheruser@example.net'
                  name='John Doe'
                  subscription='both'>
              <group>Friends</group>
            </item>
          </query>
        </iq>

    Stanza Interface:
        query -- The namespace of the <query> element if one exists.

    Methods:
        __init__   -- Overrides StanzaBase.__init__.
        unhandled  -- Send error if there are no handlers.
        setPayload -- Overrides StanzaBase.setPayload.
        setQuery   -- Add or modify a <query> element.
        getQuery   -- Return the namespace of the <query> element.
        delQuery   -- Remove the <query> element.
        reply      -- Overrides StanzaBase.reply
        send       -- Overrides StanzaBase.send
    """

    namespace = 'jabber:client'
    name = 'iq'
    interfaces = set(('type', 'to', 'from', 'id', 'query'))
    types = set(('get', 'result', 'set', 'error'))
    plugin_attrib = name

    def __init__(self, *args, **kwargs):
        """
        Initialize a new <iq> stanza with an 'id' value.

        Overrides StanzaBase.__init__.
        """
        StanzaBase.__init__(self, *args, **kwargs)
        if self['id'] == '':
            if self.stream is not None:
                self['id'] = self.stream.getNewId()
            else:
                self['id'] = '0'

    def unhandled(self):
        """
        Send a feature-not-implemented error if the stanza is not handled.

        Overrides StanzaBase.unhandled.
        """
        if self['type'] in ('get', 'set'):
            self.reply()
            self['error']['condition'] = 'feature-not-implemented'
            self['error']['text'] = 'No handlers registered for this request.'
            self.send()

    def setPayload(self, value):
        """
        Set the XML contents of the <iq> stanza.

        Arguments:
            value -- An XML object to use as the <iq> stanza's contents
        """
        self.clear()
        StanzaBase.setPayload(self, value)
        return self

    def setQuery(self, value):
        """
        Add or modify a <query> element.

        Query elements are differentiated by their namespace.

        Arguments:
            value -- The namespace of the <query> element.
        """
        query = self.xml.find("{%s}query" % value)
        if query is None and value:
            self.clear()
            query = ET.Element("{%s}query" % value)
            self.xml.append(query)
        return self

    def getQuery(self):
        """Return the namespace of the <query> element."""
        for child in self.xml.getchildren():
            if child.tag.endswith('query'):
                ns = child.tag.split('}')[0]
                if '{' in ns:
                    ns = ns[1:]
                return ns
        return ''

    def delQuery(self):
        """Remove the <query> element."""
        for child in self.xml.getchildren():
            if child.tag.endswith('query'):
                self.xml.remove(child)
        return self

    def reply(self):
        """
        Send a reply <iq> stanza.

        Overrides StanzaBase.reply

        Sets the 'type' to 'result' in addition to the default
        StanzaBase.reply behavior.
        """
        self['type'] = 'result'
        StanzaBase.reply(self)
        return self

    def send(self, block=True, timeout=RESPONSE_TIMEOUT):
        """
        Send an <iq> stanza over the XML stream.

        The send call can optionally block until a response is received or
        a timeout occurs. Be aware that using blocking in non-threaded event
        handlers can drastically impact performance.

        Overrides StanzaBase.send

        Arguments:
            block   -- Specify if the send call will block until a response
                       is received, or a timeout occurs. Defaults to True.
            timeout -- The length of time (in seconds) to wait for a response
                       before exiting the send call if blocking is used.
                       Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
        """
        if block and self['type'] in ('get', 'set'):
            waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id']))
            self.stream.registerHandler(waitfor)
            StanzaBase.send(self)
            return waitfor.wait(timeout)
        else:
            return StanzaBase.send(self)