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