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