gloox  1.0
adhoc.cpp
1 /*
2  Copyright (c) 2004-2009 by Jakob Schroeter <js@camaya.net>
3  This file is part of the gloox library. http://camaya.net/gloox
4 
5  This software is distributed under a license. The full license
6  agreement can be found in the file LICENSE in this distribution.
7  This software may not be copied, modified, sold or distributed
8  other than expressed in the named license agreement.
9 
10  This software is distributed without any warranty.
11 */
12 
13 
14 #include "adhoc.h"
15 #include "adhochandler.h"
16 #include "adhoccommandprovider.h"
17 #include "disco.h"
18 #include "error.h"
19 #include "discohandler.h"
20 #include "clientbase.h"
21 #include "dataform.h"
22 #include "util.h"
23 
24 namespace gloox
25 {
26 
27  static const char* cmdActionStringValues[] =
28  {
29  "execute", "cancel", "prev", "next", "complete"
30  };
31 
32  static inline const std::string actionString( Adhoc::Command::Action action )
33  {
34  return util::lookup2( action, cmdActionStringValues );
35  }
36 
37  static const char* cmdStatusStringValues[] =
38  {
39  "executing", "completed", "canceled"
40  };
41 
42  static inline const std::string statusString( Adhoc::Command::Status status )
43  {
44  return util::lookup( status, cmdStatusStringValues );
45  }
46 
47  static const char* cmdNoteStringValues[] =
48  {
49  "info", "warn", "error"
50  };
51 
52  static inline const std::string noteString( Adhoc::Command::Note::Severity sev )
53  {
54  return util::lookup( sev, cmdNoteStringValues );
55  }
56 
57  // ---- Adhoc::Command::Note ----
58  Adhoc::Command::Note::Note( const Tag* tag )
59  : m_severity( InvalidSeverity )
60  {
61  if( !tag || tag->name() != "note" )
62  return;
63 
64  m_severity = (Severity)util::deflookup( tag->findAttribute( "type" ), cmdNoteStringValues, Info );
65  m_note = tag->cdata();
66  }
67 
69  {
70  if( m_note.empty() || m_severity == InvalidSeverity )
71  return 0;
72 
73  Tag* n = new Tag( "note", m_note );
74  n->addAttribute( TYPE, noteString( m_severity ) );
75  return n;
76  }
77  // ---- ~Adhoc::Command::Note ----
78 
79  // ---- Adhoc::Command ----
81  DataForm* form )
82  : StanzaExtension( ExtAdhocCommand ), m_node( node ), m_form( form ), m_action( action ),
83  m_status( InvalidStatus ), m_actions( 0 )
84  {
85  }
86 
87  Adhoc::Command::Command( const std::string& node, const std::string& sessionid, Status status,
88  DataForm* form )
89  : StanzaExtension( ExtAdhocCommand ), m_node( node ), m_sessionid( sessionid ),
90  m_form( form ), m_action( InvalidAction ), m_status( status ), m_actions( 0 )
91  {
92  }
93 
94  Adhoc::Command::Command( const std::string& node, const std::string& sessionid,
96  DataForm* form )
97  : StanzaExtension( ExtAdhocCommand ), m_node( node ), m_sessionid( sessionid ),
98  m_form( form ), m_action( action ), m_actions( 0 )
99  {
100  }
101 
102  Adhoc::Command::Command( const std::string& node, const std::string& sessionid, Status status,
103  Action executeAction, int allowedActions,
104  DataForm* form )
105  : StanzaExtension( ExtAdhocCommand ), m_node( node ), m_sessionid( sessionid ),
106  m_form( form ), m_action( executeAction ), m_status( status ), m_actions( allowedActions )
107  {
108  }
109 
111  : StanzaExtension( ExtAdhocCommand ), m_form( 0 ), m_actions( 0 )
112  {
113  if( !tag || tag->name() != "command" || tag->xmlns() != XMLNS_ADHOC_COMMANDS )
114  return;
115 
116  m_node = tag->findAttribute( "node" );
117  m_sessionid = tag->findAttribute( "sessionid" );
118  m_status = (Status)util::lookup( tag->findAttribute( "status" ), cmdStatusStringValues );
119 
120  Tag* a = tag->findChild( "actions" );
121  if( a )
122  {
123  // Multi-stage response
124  m_action = (Action)util::deflookup2( a->findAttribute( "action" ), cmdActionStringValues, Complete );
125  if( a->hasChild( "prev" ) )
126  m_actions |= Previous;
127  if( a->hasChild( "next" ) )
128  m_actions |= Next;
129  if( a->hasChild( "complete" ) )
130  m_actions |= Complete;
131  }
132  else
133  {
134  m_action = (Action)util::deflookup2( tag->findAttribute( "action" ), cmdActionStringValues, Execute );
135  }
136 
137  const ConstTagList& l = tag->findTagList( "/command/note" );
138  ConstTagList::const_iterator it = l.begin();
139  for( ; it != l.end(); ++it )
140  m_notes.push_back( new Note( (*it) ) );
141 
142  Tag* x = tag->findChild( "x", "xmlns", XMLNS_X_DATA );
143  if( x )
144  m_form = new DataForm( x );
145  }
146 
148  {
149  util::clearList( m_notes );
150  delete m_form;
151  }
152 
153  const std::string& Adhoc::Command::filterString() const
154  {
155  static const std::string filter = "/iq/command[@xmlns='" + XMLNS_ADHOC_COMMANDS + "']";
156  return filter;
157  }
158 
160  {
161  if( m_node.empty() )
162  return 0;
163 
164  Tag* c = new Tag( "command" );
166  c->addAttribute( "node", m_node );
167  if( m_actions != 0 )
168  {
169  // Multi-stage command response
170 
171  if( m_status != InvalidStatus )
172  c->addAttribute( "status", statusString( m_status ) );
173  else
174  c->addAttribute( "status", statusString( Executing ) );
175 
176  Tag* actions = new Tag( c, "actions" );
177 
178  if( m_action != InvalidAction )
179  c->addAttribute( "execute", actionString( m_action ) );
180  else
181  c->addAttribute( "execute", actionString( Complete ) );
182 
183  if( ( m_actions & Previous ) == Previous )
184  new Tag( actions, "prev" );
185  if( ( m_actions & Next ) == Next )
186  new Tag( actions, "next" );
187  if( ( m_actions & Complete ) == Complete )
188  new Tag( actions, "complete" );
189  }
190  else
191  {
192  // Single-stage command request/response or Multi-stage command request
193 
194  if( m_action != InvalidAction )
195  c->addAttribute( "action", actionString( m_action ) );
196  if( m_status != InvalidStatus )
197  c->addAttribute( "status", statusString( m_status ) );
198  }
199 
200  if ( !m_sessionid.empty() )
201  c->addAttribute( "sessionid", m_sessionid );
202 
203  if( m_form && *m_form )
204  c->addChild( m_form->tag() );
205 
206  NoteList::const_iterator it = m_notes.begin();
207  for( ; it != m_notes.end(); ++it )
208  c->addChild( (*it)->tag() );
209 
210  return c;
211  }
212  // ---- ~Adhoc::Command ----
213 
214  // ---- Adhoc ----
216  : m_parent( parent )
217  {
218  if( !m_parent || !m_parent->disco() )
219  return;
220 
221  m_parent->disco()->addFeature( XMLNS_ADHOC_COMMANDS );
222  m_parent->disco()->registerNodeHandler( this, XMLNS_ADHOC_COMMANDS );
223  m_parent->disco()->registerNodeHandler( this, EmptyString );
224  m_parent->registerIqHandler( this, ExtAdhocCommand );
225  m_parent->registerStanzaExtension( new Adhoc::Command() );
226  }
227 
229  {
230  if( !m_parent || !m_parent->disco() )
231  return;
232 
233  m_parent->disco()->removeFeature( XMLNS_ADHOC_COMMANDS );
234  m_parent->disco()->removeNodeHandler( this, XMLNS_ADHOC_COMMANDS );
235  m_parent->disco()->removeNodeHandler( this, EmptyString );
236  m_parent->removeIqHandler( this, ExtAdhocCommand );
237  m_parent->removeIDHandler( this );
239  }
240 
241  StringList Adhoc::handleDiscoNodeFeatures( const JID& /*from*/, const std::string& /*node*/ )
242  {
243  StringList features;
244  features.push_back( XMLNS_ADHOC_COMMANDS );
245  return features;
246 // return StringList( 1, XMLNS_ADHOC_COMMANDS );
247  }
248 
249  Disco::ItemList Adhoc::handleDiscoNodeItems( const JID& from, const JID& /*to*/, const std::string& node )
250  {
251  Disco::ItemList l;
252  if( node.empty() )
253  {
254  l.push_back( new Disco::Item( m_parent->jid(), XMLNS_ADHOC_COMMANDS, "Ad-Hoc Commands" ) );
255  }
256  else if( node == XMLNS_ADHOC_COMMANDS )
257  {
258  StringMap::const_iterator it = m_items.begin();
259  for( ; it != m_items.end(); ++it )
260  {
261  AdhocCommandProviderMap::const_iterator itp = m_adhocCommandProviders.find( (*it).first );
262  if( itp != m_adhocCommandProviders.end()
263  && (*itp).second
264  && (*itp).second->handleAdhocAccessRequest( from, (*it).first ) )
265  {
266  l.push_back( new Disco::Item( m_parent->jid(), (*it).first, (*it).second ) );
267  }
268  }
269  }
270  return l;
271  }
272 
273  Disco::IdentityList Adhoc::handleDiscoNodeIdentities( const JID& /*from*/, const std::string& node )
274  {
276  StringMap::const_iterator it = m_items.find( node );
277  l.push_back( new Disco::Identity( "automation",
278  node == XMLNS_ADHOC_COMMANDS ? "command-list" : "command-node",
279  it == m_items.end() ? "Ad-Hoc Commands" : (*it).second ) );
280  return l;
281  }
282 
283  bool Adhoc::handleIq( const IQ& iq )
284  {
285  if( iq.subtype() != IQ::Set )
286  return false;
287 
289  if( !ac || ac->node().empty())
290  return false;
291 
292  AdhocCommandProviderMap::const_iterator it = m_adhocCommandProviders.find( ac->node() );
293  if( it != m_adhocCommandProviders.end() )
294  {
295  const std::string& sess = ac->sessionID().empty() ? m_parent->getID() : ac->sessionID();
296  m_activeSessions[sess] = iq.id();
297  (*it).second->handleAdhocCommand( iq.from(), *ac, sess );
298  return true;
299  }
300 
301  return false;
302  }
303 
304  void Adhoc::handleIqID( const IQ& iq, int context )
305  {
306  if( context != ExecuteAdhocCommand )
307  return;
308 
309  AdhocTrackMap::iterator it = m_adhocTrackMap.find( iq.id() );
310  if( it == m_adhocTrackMap.end() || (*it).second.context != context
311  || (*it).second.remote != iq.from() )
312  return;
313 
314  switch( iq.subtype() )
315  {
316  case IQ::Error:
317  (*it).second.ah->handleAdhocError( iq.from(), iq.error() );
318  break;
319  case IQ::Result:
320  {
322  if( ac )
323  (*it).second.ah->handleAdhocExecutionResult( iq.from(), *ac );
324  break;
325  }
326  default:
327  break;
328  }
329  m_adhocTrackMap.erase( it );
330  }
331 
332  void Adhoc::registerAdhocCommandProvider( AdhocCommandProvider* acp, const std::string& command,
333  const std::string& name )
334  {
335  if( !m_parent || !m_parent->disco() )
336  return;
337 
338  m_parent->disco()->registerNodeHandler( this, command );
339  m_adhocCommandProviders[command] = acp;
340  m_items[command] = name;
341  }
342 
343  void Adhoc::handleDiscoInfo( const JID& from, const Disco::Info& info, int context )
344  {
345  if( context != CheckAdhocSupport )
346  return;
347 
348  AdhocTrackMap::iterator it = m_adhocTrackMap.begin();
349  for( ; it != m_adhocTrackMap.end() && (*it).second.context != context
350  && (*it).second.remote != from; ++it )
351  ;
352  if( it == m_adhocTrackMap.end() )
353  return;
354 
355  (*it).second.ah->handleAdhocSupport( from, info.hasFeature( XMLNS_ADHOC_COMMANDS ) );
356  m_adhocTrackMap.erase( it );
357  }
358 
359  void Adhoc::handleDiscoItems( const JID& from, const Disco::Items& items, int context )
360  {
361  if( context != FetchAdhocCommands )
362  return;
363 
364  AdhocTrackMap::iterator it = m_adhocTrackMap.begin();
365  for( ; it != m_adhocTrackMap.end(); ++it )
366  {
367  if( (*it).second.context == context && (*it).second.remote == from )
368  {
369  StringMap commands;
370  const Disco::ItemList& l = items.items();
371  Disco::ItemList::const_iterator it2 = l.begin();
372  for( ; it2 != l.end(); ++it2 )
373  {
374  commands[(*it2)->node()] = (*it2)->name();
375  }
376  (*it).second.ah->handleAdhocCommands( from, commands );
377 
378  m_adhocTrackMap.erase( it );
379  break;
380  }
381  }
382  }
383 
384  void Adhoc::handleDiscoError( const JID& from, const Error* error, int context )
385  {
386  AdhocTrackMap::iterator it = m_adhocTrackMap.begin();
387  for( ; it != m_adhocTrackMap.end(); ++it )
388  {
389  if( (*it).second.context == context && (*it).second.remote == from )
390  {
391  (*it).second.ah->handleAdhocError( from, error );
392 
393  m_adhocTrackMap.erase( it );
394  }
395  }
396  }
397 
398  void Adhoc::checkSupport( const JID& remote, AdhocHandler* ah )
399  {
400  if( !remote || !ah || !m_parent || !m_parent->disco() )
401  return;
402 
403  TrackStruct track;
404  track.remote = remote;
405  track.context = CheckAdhocSupport;
406  track.ah = ah;
407  const std::string& id = m_parent->getID();
408  m_adhocTrackMap[id] = track;
409  m_parent->disco()->getDiscoInfo( remote, EmptyString, this, CheckAdhocSupport, id );
410  }
411 
412  void Adhoc::getCommands( const JID& remote, AdhocHandler* ah )
413  {
414  if( !remote || !ah || !m_parent || !m_parent->disco() )
415  return;
416 
417  TrackStruct track;
418  track.remote = remote;
419  track.context = FetchAdhocCommands;
420  track.ah = ah;
421  const std::string& id = m_parent->getID();
422  m_adhocTrackMap[id] = track;
423  m_parent->disco()->getDiscoItems( remote, XMLNS_ADHOC_COMMANDS, this, FetchAdhocCommands, id );
424  }
425 
426  void Adhoc::execute( const JID& remote, const Adhoc::Command* command, AdhocHandler* ah )
427  {
428  if( !remote || !command || !m_parent || !ah )
429  return;
430 
431  const std::string& id = m_parent->getID();
432  IQ iq( IQ::Set, remote, id );
433  iq.addExtension( command );
434 
435  TrackStruct track;
436  track.remote = remote;
437  track.context = ExecuteAdhocCommand;
438  track.session = command->sessionID();
439  track.ah = ah;
440  m_adhocTrackMap[id] = track;
441 
442  m_parent->send( iq, this, ExecuteAdhocCommand );
443  }
444 
445  void Adhoc::respond( const JID& remote, const Adhoc::Command* command, const Error* error )
446  {
447  if( !remote || !command || !m_parent )
448  return;
449 
450  StringMap::iterator it = m_activeSessions.find( command->sessionID() );
451  if( it == m_activeSessions.end() )
452  return;
453 
454  IQ re( error ? IQ::Error : IQ::Result, remote, (*it).second );
455  re.addExtension( command );
456  if( error )
457  re.addExtension( error );
458  m_parent->send( re );
459  m_activeSessions.erase( it );
460  }
461 
462  void Adhoc::removeAdhocCommandProvider( const std::string& command )
463  {
464  if( !m_parent || !m_parent->disco() )
465  return;
466 
467  m_parent->disco()->removeNodeHandler( this, command );
468  m_adhocCommandProviders.erase( command );
469  m_items.erase( command );
470  }
471 
472 }