~ubuntu-branches/ubuntu/lucid/gajim/lucid-security

« back to all changes in this revision

Viewing changes to src/common/xmpp/commands.py

  • Committer: Maia Kozheva
  • Date: 2009-11-25 08:32:36 UTC
  • mfrom: (1.1.16 upstream)
  • Revision ID: sikon@maia-desktop-20091125083236-hkxrujhn3amehuve
Merged new upstream release 0.13

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
## $Id: commands.py,v 1.11 2005/11/30 17:03:11 normanr Exp $
2
 
 
3
 
## Ad-Hoc Command manager
4
 
## Mike Albon (c) 5th January 2005
5
 
 
6
 
##   This program is free software; you can redistribute it and/or modify
7
 
##   it under the terms of the GNU General Public License as published by
8
 
##   the Free Software Foundation; either version 2, or (at your option)
9
 
##   any later version.
10
 
##
11
 
##   This program is distributed in the hope that it will be useful,
12
 
##   but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 
##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 
##   GNU General Public License for more details.
15
 
 
16
 
 
17
 
"""This module is a ad-hoc command processor for xmpppy. It uses the plug-in mechanism like most of the core library. It depends on a DISCO browser manager.
18
 
 
19
 
There are 3 classes here, a command processor Commands like the Browser, and a command template plugin Command, and an example command.
20
 
 
21
 
To use this module:
22
 
    
23
 
    Instansiate the module with the parent transport and disco browser manager as parameters.
24
 
    'Plug in' commands using the command template.
25
 
    The command feature must be added to existing disco replies where neccessary.
26
 
    
27
 
What it supplies:
28
 
    
29
 
    Automatic command registration with the disco browser manager.
30
 
    Automatic listing of commands in the public command list.
31
 
    A means of handling requests, by redirection though the command manager.
32
 
"""
33
 
 
34
 
from protocol import *
35
 
from client import PlugIn
36
 
 
37
 
class Commands(PlugIn):
38
 
    """Commands is an ancestor of PlugIn and can be attached to any session.
39
 
    
40
 
    The commands class provides a lookup and browse mechnism. It follows the same priciple of the Browser class, for Service Discovery to provide the list of commands, it adds the 'list' disco type to your existing disco handler function. 
41
 
    
42
 
    How it works:
43
 
        The commands are added into the existing Browser on the correct nodes. When the command list is built the supplied discovery handler function needs to have a 'list' option in type. This then gets enumerated, all results returned as None are ignored.
44
 
        The command executed is then called using it's Execute method. All session management is handled by the command itself.
45
 
    """
46
 
    def __init__(self, browser):
47
 
        """Initialises class and sets up local variables"""
48
 
        PlugIn.__init__(self)
49
 
        DBG_LINE='commands'
50
 
        self._exported_methods=[]
51
 
        self._handlers={'':{}}
52
 
        self._browser = browser
53
 
    
54
 
    def plugin(self, owner):
55
 
        """Makes handlers within the session"""
56
 
        # Plug into the session and the disco manager
57
 
        # We only need get and set, results are not needed by a service provider, only a service user.
58
 
        owner.RegisterHandler('iq',self._CommandHandler,typ='set',ns=NS_COMMANDS)
59
 
        owner.RegisterHandler('iq',self._CommandHandler,typ='get',ns=NS_COMMANDS)
60
 
        self._browser.setDiscoHandler(self._DiscoHandler,node=NS_COMMANDS,jid='')
61
 
        
62
 
    def plugout(self):
63
 
        """Removes handlers from the session"""
64
 
        # unPlug from the session and the disco manager
65
 
        self._owner.UnregisterHandler('iq',self_CommandHandler,ns=NS_COMMANDS)
66
 
        for jid in self._handlers:
67
 
            self._browser.delDiscoHandler(self._DiscoHandler,node=NS_COMMANDS)
68
 
        
69
 
    def _CommandHandler(self,conn,request):
70
 
        """The internal method to process the routing of command execution requests"""
71
 
        # This is the command handler itself.
72
 
        # We must:
73
 
        #   Pass on command execution to command handler
74
 
        #   (Do we need to keep session details here, or can that be done in the command?)
75
 
        jid = str(request.getTo())
76
 
        try:
77
 
            node = request.getTagAttr('command','node')
78
 
        except Exception:
79
 
            conn.send(Error(request,ERR_BAD_REQUEST))
80
 
            raise NodeProcessed
81
 
        if jid in self._handlers:
82
 
            if node in self._handlers[jid]:
83
 
                self._handlers[jid][node]['execute'](conn,request)
84
 
            else:
85
 
                conn.send(Error(request,ERR_ITEM_NOT_FOUND))
86
 
                raise NodeProcessed
87
 
        elif node in self._handlers['']:
88
 
                self._handlers[''][node]['execute'](conn,request)
89
 
        else:
90
 
            conn.send(Error(request,ERR_ITEM_NOT_FOUND))
91
 
            raise NodeProcessed
92
 
    
93
 
    def _DiscoHandler(self,conn,request,typ):
94
 
        """The internal method to process service discovery requests"""
95
 
        # This is the disco manager handler.
96
 
        if typ == 'items':
97
 
            # We must:
98
 
            #    Generate a list of commands and return the list
99
 
            #    * This handler does not handle individual commands disco requests.
100
 
            # Pseudo:
101
 
            #   Enumerate the 'item' disco of each command for the specified jid
102
 
            #   Build responce and send
103
 
            #   To make this code easy to write we add an 'list' disco type, it returns a tuple or 'none' if not advertised
104
 
            list = []
105
 
            items = []
106
 
            jid = str(request.getTo())
107
 
            # Get specific jid based results
108
 
            if jid in self._handlers:
109
 
                for each in self._handlers[jid].keys():
110
 
                    items.append((jid,each))
111
 
            else:
112
 
                # Get generic results
113
 
                for each in self._handlers[''].keys():
114
 
                    items.append(('',each))
115
 
            if items != []:
116
 
                for each in items:
117
 
                    i = self._handlers[each[0]][each[1]]['disco'](conn,request,'list')
118
 
                    if i is not None:
119
 
                        list.append(Node(tag='item',attrs={'jid':i[0],'node':i[1],'name':i[2]}))
120
 
                iq = request.buildReply('result')
121
 
                if request.getQuerynode(): iq.setQuerynode(request.getQuerynode())
122
 
                iq.setQueryPayload(list)
123
 
                conn.send(iq)
124
 
            else:
125
 
                conn.send(Error(request,ERR_ITEM_NOT_FOUND))
126
 
            raise NodeProcessed
127
 
        elif typ == 'info':
128
 
            return {'ids':[{'category':'automation','type':'command-list'}],'features':[]}
129
 
    
130
 
    def addCommand(self,name,cmddisco,cmdexecute,jid=''):
131
 
        """The method to call if adding a new command to the session, the requred parameters of cmddisco and cmdexecute are the methods to enable that command to be executed"""
132
 
        # This command takes a command object and the name of the command for registration
133
 
        # We must:
134
 
        #   Add item into disco
135
 
        #   Add item into command list
136
 
        if jid not in self._handlers:
137
 
            self._handlers[jid]={}
138
 
            self._browser.setDiscoHandler(self._DiscoHandler,node=NS_COMMANDS,jid=jid)
139
 
        if name in self._handlers[jid]:
140
 
            raise NameError,'Command Exists'
141
 
        else:
142
 
            self._handlers[jid][name]={'disco':cmddisco,'execute':cmdexecute}
143
 
        # Need to add disco stuff here
144
 
        self._browser.setDiscoHandler(cmddisco,node=name,jid=jid)
145
 
    
146
 
    def delCommand(self,name,jid=''):
147
 
        """Removed command from the session"""
148
 
        # This command takes a command object and the name used for registration
149
 
        # We must:
150
 
        #   Remove item from disco
151
 
        #   Remove item from command list
152
 
        if jid not in self._handlers:
153
 
            raise NameError,'Jid not found'
154
 
        if name not in self._handlers[jid]:
155
 
            raise NameError, 'Command not found'
156
 
        else:
157
 
            #Do disco removal here
158
 
            command = self.getCommand(name,jid)['disco']
159
 
            del self._handlers[jid][name]
160
 
            self._browser.delDiscoHandler(command,node=name,jid=jid)
161
 
        
162
 
    def getCommand(self,name,jid=''):
163
 
        """Returns the command tuple"""
164
 
        # This gets the command object with name
165
 
        # We must:
166
 
        #   Return item that matches this name
167
 
        if jid not in self._handlers:
168
 
            raise NameError,'Jid not found'
169
 
        elif name not in self._handlers[jid]:
170
 
            raise NameError,'Command not found'
171
 
        else:
172
 
            return self._handlers[jid][name]
173
 
        
174
 
class Command_Handler_Prototype(PlugIn):
175
 
    """This is a prototype command handler, as each command uses a disco method 
176
 
       and execute method you can implement it any way you like, however this is 
177
 
       my first attempt at making a generic handler that you can hang process 
178
 
       stages on too. There is an example command below.
179
 
    
180
 
    The parameters are as follows:
181
 
    name : the name of the command within the jabber environment
182
 
    description : the natural language description
183
 
    discofeatures : the features supported by the command
184
 
    initial : the initial command in the from of {'execute':commandname}
185
 
    
186
 
    All stages set the 'actions' dictionary for each session to represent the possible options available.
187
 
    """
188
 
    name = 'examplecommand'
189
 
    count = 0
190
 
    description = 'an example command'
191
 
    discofeatures = [NS_COMMANDS,NS_DATA]
192
 
    # This is the command template
193
 
    def __init__(self,jid=''):
194
 
        """Set up the class"""
195
 
        PlugIn.__init__(self)
196
 
        DBG_LINE='command'
197
 
        self.sessioncount = 0
198
 
        self.sessions = {}
199
 
        # Disco information for command list pre-formatted as a tuple
200
 
        self.discoinfo = {'ids':[{'category':'automation','type':'command-node','name':self.description}],'features': self.discofeatures}
201
 
        self._jid = jid
202
 
        
203
 
    def plugin(self,owner):
204
 
        """Plug command into the commands class"""
205
 
        # The owner in this instance is the Command Processor
206
 
        self._commands = owner
207
 
        self._owner = owner._owner
208
 
        self._commands.addCommand(self.name,self._DiscoHandler,self.Execute,jid=self._jid)
209
 
    
210
 
    def plugout(self):
211
 
        """Remove command from the commands class"""
212
 
        self._commands.delCommand(name,self._jid)
213
 
 
214
 
    def getSessionID(self):
215
 
        """Returns an id for the command session"""
216
 
        self.count = self.count+1
217
 
        return 'cmd-%s-%d'%(self.name,self.count)
218
 
    
219
 
    def Execute(self,conn,request):
220
 
        """The method that handles all the commands, and routes them to the correct method for that stage."""
221
 
        # New request or old?
222
 
        try:
223
 
            session = request.getTagAttr('command','sessionid')
224
 
        except Exception:
225
 
            session = None
226
 
        try:
227
 
            action = request.getTagAttr('command','action')
228
 
        except Exception:
229
 
            action = None
230
 
        if action is None: action = 'execute'
231
 
        # Check session is in session list
232
 
        if session in self.sessions:
233
 
            if self.sessions[session]['jid']==request.getFrom():
234
 
                # Check action is vaild
235
 
                if action in self.sessions[session]['actions']:
236
 
                    # Execute next action
237
 
                    self.sessions[session]['actions'][action](conn,request)
238
 
                else:
239
 
                    # Stage not presented as an option
240
 
                    self._owner.send(Error(request,ERR_BAD_REQUEST))
241
 
                    raise NodeProcessed
242
 
            else:
243
 
                # Jid and session don't match. Go away imposter
244
 
                self._owner.send(Error(request,ERR_BAD_REQUEST))
245
 
                raise NodeProcessed
246
 
        elif session is not None:
247
 
            # Not on this sessionid you won't.
248
 
            self._owner.send(Error(request,ERR_BAD_REQUEST))
249
 
            raise NodeProcessed
250
 
        else:
251
 
            # New session
252
 
            self.initial[action](conn,request)
253
 
    
254
 
    def _DiscoHandler(self,conn,request,type_):
255
 
        """The handler for discovery events"""
256
 
        if type_ == 'list':
257
 
            return (request.getTo(),self.name,self.description)
258
 
        elif type_ == 'items':
259
 
            return []
260
 
        elif type_ == 'info':
261
 
            return self.discoinfo
262
 
        
263
 
class TestCommand(Command_Handler_Prototype):
264
 
    """ Example class. You should read source if you wish to understate how it works. 
265
 
        Generally, it presents a "master" that giudes user through to calculate something.
266
 
    """
267
 
    name = 'testcommand'
268
 
    description = 'a noddy example command'
269
 
    def __init__(self,jid=''):
270
 
        """ Init internal constants. """
271
 
        Command_Handler_Prototype.__init__(self,jid)
272
 
        self.pi = 3.14
273
 
        self.initial = {'execute':self.cmdFirstStage}
274
 
    
275
 
    def cmdFirstStage(self,conn,request):
276
 
        """ Determine """
277
 
        # This is the only place this should be repeated as all other stages should have SessionIDs
278
 
        try:
279
 
            session = request.getTagAttr('command','sessionid')
280
 
        except Exception:
281
 
            session = None
282
 
        if session is None:
283
 
            session = self.getSessionID()
284
 
            sessions[session]={'jid':request.getFrom(),'actions':{'cancel':self.cmdCancel,'next':self.cmdSecondStage},'data':{'type':None}}
285
 
        # As this is the first stage we only send a form
286
 
        reply = request.buildReply('result')
287
 
        form = DataForm(title='Select type of operation',data=['Use the combobox to select the type of calculation you would like to do, then click Next',DataField(name='calctype',label='Calculation Type',value=sessions[session]['data']['type'],options=[['circlediameter','Calculate the Diameter of a circle'],['circlearea','Calculate the area of a circle']],typ='list-single',required=1)])
288
 
        replypayload = [Node('actions',attrs={'execute':'next'},payload=[Node('next')]),form]
289
 
        reply.addChild(name='command',attrs={'xmlns':NS_COMMAND,'node':request.getTagAttr('command','node'),'sessionid':session,'status':'executing'},payload=replypayload)
290
 
        self._owner.send(reply)
291
 
        raise NodeProcessed
292
 
 
293
 
    def cmdSecondStage(self,conn,request):
294
 
        form = DataForm(node = result.getTag(name='command').getTag(name='x',namespace=NS_DATA))
295
 
        sessions[request.getTagAttr('command','sessionid')]['data']['type']=form.getField('calctype')
296
 
        sessions[request.getTagAttr('command','sessionid')]['actions']={'cancel':self.cmdCancel,None:self.cmdThirdStage,'previous':cmdFirstStage}
297
 
        # The form generation is split out to another method as it may be called by cmdThirdStage
298
 
        self.cmdSecondStageReply(conn,request)
299
 
        
300
 
    def cmdSecondStageReply(self,conn,request):
301
 
        reply = request.buildReply('result')
302
 
        form = DataForm(title = 'Enter the radius', data=['Enter the radius of the circle (numbers only)',DataField(label='Radius',name='radius',typ='text-single')])
303
 
        replypayload = [Node('actions',attrs={'execute':'complete'},payload=[Node('complete'),Node('prev')]),form]
304
 
        reply.addChild(name='command',attrs={'xmlns':NS_COMMAND,'node':request.getTagAttr('command','node'),'sessionid':request.getTagAttr('command','sessionid'),'status':'executing'},payload=replypayload)
305
 
        self._owner.send(reply)
306
 
        raise NodeProcessed
307
 
        
308
 
    def cmdThirdStage(self,conn,request):
309
 
        form = DataForm(node = result.getTag(name='command').getTag(name='x',namespace=NS_DATA))
310
 
        try:
311
 
            num = float(form.getField('radius'))
312
 
        except Exception:
313
 
            self.cmdSecondStageReply(conn,request)
314
 
        if sessions[request.getTagAttr('command','sessionid')]['data']['type'] == 'circlearea':
315
 
            result = num*(pi**2)
316
 
        else:
317
 
            result = num*2*pi
318
 
        reply = result.buildReply(request)
319
 
        form = DataForm(typ='result',data=[DataField(label='result',name='result',value=result)])
320
 
        reply.addChild(name='command',attrs={'xmlns':NS_COMMAND,'node':request.getTagAttr('command','node'),'sessionid':request.getTagAttr('command','sessionid'),'status':'completed'},payload=form)
321
 
        self._owner.send(reply)
322
 
        raise NodeProcessed
323
 
        
324
 
    def cmdCancel(self,conn,request):
325
 
        reply = request.buildReply('result')
326
 
        reply.addChild(name='command',attrs={'xmlns':NS_COMMAND,'node':request.getTagAttr('command','node'),'sessionid':request.getTagAttr('command','sessionid'),'status':'cancelled'})
327
 
        self._owner.send(reply)
328
 
        del sessions[request.getTagAttr('command','sessionid')]
329
 
            
330
 
    
331
 
 
332
 
# vim: se ts=3:
 
 
b'\\ No newline at end of file'