diff --git a/Contributors b/Contributors index e4cbe38..6070995 100644 --- a/Contributors +++ b/Contributors @@ -10,14 +10,17 @@ We will add you! * Andrea Anzani * Pier Luigi Fiorini + * Dario Casalinuovo == Code == Contributors in alphabetical order by last name: * Alexander Botero-Lowry + * Oliver Ruiz Dorantes * Rene Gollent + * Pier Luigi Fiorini == Icons == @@ -26,3 +29,10 @@ We will add you! * Caya * AIM protocol * ICQ protocol + * GoogleTalk protocol + * MSN protocol + + Mark Erben + + * Facebook protocol + diff --git a/Jamrules b/Jamrules index eb4ff51..bee60b3 100644 --- a/Jamrules +++ b/Jamrules @@ -45,11 +45,23 @@ LOCATE on $(HCACHEFILE) $(JCACHEFILE) = $(GENERATED_DIR) ; # Perform configuration checks include [ FDirName $(JAM_DIR) CheckRules ] ; CheckGccPlatform ; + #CheckCurl ; #if ! $(HAVE_CURL) { # Echo "** Caya needs Curl" ; #} +CheckCaya ; +if ! $(HAVE_CAYA) { + Echo "** Caya library is needed!" ; + Exit 1 ; +} + +CheckOpenSSL ; +if ! $(HAVE_OPENSSL) { + Echo "** MSN, Jabber, GoogleTalk and Facebook protocols are disabled for lack of OpenSSL" ; +} + # Include jam scripts include [ FDirName $(JAM_DIR) HelperRules ] ; include [ FDirName $(JAM_DIR) ConfigRules ] ; diff --git a/License b/License index e797c5b..61d0d9f 100644 --- a/License +++ b/License @@ -1,4 +1,6 @@ + Copyright (c) 2009-2010, Andrea Anzani +Copyright (c) 2009-2012, Dario Casalinuovo Copyright (c) 2009-2010, Pier Luigi Fiorini Redistribution and use in source and binary forms, with or without modification, @@ -24,3 +26,345 @@ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/*************************************************************/ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/build/jam/CheckRules b/build/jam/CheckRules index 6fbc213..a444159 100644 --- a/build/jam/CheckRules +++ b/build/jam/CheckRules @@ -75,3 +75,59 @@ rule CheckCurl HAVE_CURL = $(haveLibs) ; } } + +rule CheckLibYahoo2 +{ + # CheckLibYahoo2 + # Check for LibYahoo2 and defined HAVE_LIBYAHOO2 according, it also defines + # LIBYAHOO2_INCLUDE_DIR and LIBYAHOO2_LIBRARY_DIR with location of respectively + # include and library files. + + HAVE_LIBYAHOO2 = ; + LIBYAHOO2_INCLUDE_DIR = ; + LIBYAHOO2_LIBRARY_DIR = ; + + local haveHeaders = [ Glob $(COMMON_INCLUDE_DIRECTORY)/libyahoo2 : yahoo2.h ] ; + if $(haveHeaders) { + LIBYAHOO2_INCLUDE_DIR = $(COMMON_INCLUDE_DIRECTORY)/libyahoo2 ; + + local haveLibs = [ Glob $(COMMON_LIB_DIRECTORY) : libyahoo2.so ] ; + if $(haveLibs) { + LIBYAHOO2_LIBRARY_DIR = $(COMMON_LIB_DIRECTORY) ; + + Echo Yahoo Headers: $(LIBYAHOO2_INCLUDE_DIR) ; + Echo Yahoo Libs: $(LIBYAHOO2_LIBRARY_DIR) ; + } + + HAVE_LIBYAHOO2 = $(haveLibs) ; + } +} + +rule CheckCaya +{ + # CheckCaya + # Check for Caya and defined HAVE_CAYA according, it also defines + # CAYA_INCLUDE_DIR and CAYA_LIBRARY_DIR with location of respectively + # include and library files. + + HAVE_CAYA = ; + CAYA_INCLUDE_DIR = ; + CAYA_LIBRARY_DIR = ; + + local haveHeaders = [ Glob $(COMMON_INCLUDE_DIRECTORY)/caya : CayaProtocol.h ] ; + if $(haveHeaders) { + CAYA_INCLUDE_DIR = $(COMMON_INCLUDE_DIRECTORY)/caya ; + +# local haveLibs = [ Glob $(COMMON_LIB_DIRECTORY) : libinfopopper.so ] ; +# if $(haveLibs) { +# CAYA_LIBRARY_DIR = $(COMMON_LIB_DIRECTORY) ; + + Echo Caya Headers: $(CAYA_INCLUDE_DIR) ; +# Echo Caya Libs: $(CAYA_LIBRARY_DIR) ; +# } + +# HAVE_CAYA = $(haveLibs) ; + } + + HAVE_CAYA = $(haveHeaders) ; +} diff --git a/icons/Facebook b/icons/Facebook new file mode 100644 index 0000000..7b56976 Binary files /dev/null and b/icons/Facebook differ diff --git a/icons/GoogleTalk b/icons/GoogleTalk new file mode 100644 index 0000000..e6d63e7 Binary files /dev/null and b/icons/GoogleTalk differ diff --git a/icons/Jabber b/icons/Jabber new file mode 100644 index 0000000..7d41a05 Binary files /dev/null and b/icons/Jabber differ diff --git a/libs/Jamfile b/libs/Jamfile index 9ac465f..c16948c 100644 --- a/libs/Jamfile +++ b/libs/Jamfile @@ -6,3 +6,7 @@ SubInclude TOP libs librunview ; SubInclude TOP libs libsupport ; SubInclude TOP libs libinterface ; SubInclude TOP libs libimcomm ; +SubInclude TOP libs libgloox ; +SubInclude TOP libs libmsn ; +SubInclude TOP libs libyahoo2 ; + diff --git a/libs/libgloox/Jamfile b/libs/libgloox/Jamfile new file mode 100644 index 0000000..2e99205 --- /dev/null +++ b/libs/libgloox/Jamfile @@ -0,0 +1,106 @@ +SubDir TOP libs libgloox ; + +SubDirSysHdrs [ FDirName $(TOP) ] ; +SubDirSysHdrs [ FDirName $(TOP) libs ] ; +SubDirSysHdrs [ FDirName $(OPENSSL_INCLUDE_DIR) ] ; + +SEARCH_SOURCE += [ FDirName $(TOP) libs ] ; + +StaticLibrary libgloox.a : + adhoc.cpp + amp.cpp + annotations.cpp + attention.cpp + base64.cpp + bookmarkstorage.cpp + capabilities.cpp + chatstate.cpp + chatstatefilter.cpp + client.cpp + clientbase.cpp + component.cpp + compressiondefault.cpp + compressionzlib.cpp + connectionbosh.cpp + connectionhttpproxy.cpp + connectionsocks5proxy.cpp + connectiontcpbase.cpp + connectiontcpclient.cpp + connectiontcpserver.cpp + connectiontls.cpp + connectiontlsserver.cpp + dataform.cpp + dataformfield.cpp + dataformfieldcontainer.cpp + dataformitem.cpp + dataformreported.cpp + delayeddelivery.cpp + disco.cpp + dns.cpp + error.cpp + eventdispatcher.cpp + featureneg.cpp + flexoff.cpp + gloox.cpp + gpgencrypted.cpp + gpgsigned.cpp + inbandbytestream.cpp + instantmucroom.cpp + iq.cpp + jid.cpp + lastactivity.cpp + logsink.cpp + md5.cpp + message.cpp + messageevent.cpp + messageeventfilter.cpp + messagefilter.cpp + messagesession.cpp + mucmessagesession.cpp + mucroom.cpp + mutex.cpp + nickname.cpp + nonsaslauth.cpp + oob.cpp + parser.cpp + prep.cpp + presence.cpp + privacyitem.cpp + privacymanager.cpp + privatexml.cpp + pubsubevent.cpp + pubsubitem.cpp + pubsubmanager.cpp + receipt.cpp + registration.cpp + rosteritem.cpp + rostermanager.cpp + search.cpp + sha.cpp + shim.cpp + simanager.cpp + siprofileft.cpp + socks5bytestream.cpp + socks5bytestreammanager.cpp + socks5bytestreamserver.cpp + softwareversion.cpp + stanza.cpp + stanzaextensionfactory.cpp + subscription.cpp + tag.cpp + tlsdefault.cpp + tlsgnutlsbase.cpp + tlsgnutlsclient.cpp + tlsgnutlsclientanon.cpp + tlsgnutlsserveranon.cpp + tlsopensslbase.cpp + tlsopensslclient.cpp + tlsopensslserver.cpp + tlsschannel.cpp + uniquemucroom.cpp + util.cpp + vcard.cpp + vcardmanager.cpp + vcardupdate.cpp + xhtmlim.cpp +; diff --git a/libs/libgloox/adhoc.cpp b/libs/libgloox/adhoc.cpp new file mode 100644 index 0000000..629ed37 --- /dev/null +++ b/libs/libgloox/adhoc.cpp @@ -0,0 +1,472 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "adhoc.h" +#include "adhochandler.h" +#include "adhoccommandprovider.h" +#include "disco.h" +#include "error.h" +#include "discohandler.h" +#include "clientbase.h" +#include "dataform.h" +#include "util.h" + +namespace gloox +{ + + static const char* cmdActionStringValues[] = + { + "execute", "cancel", "prev", "next", "complete" + }; + + static inline const std::string actionString( Adhoc::Command::Action action ) + { + return util::lookup2( action, cmdActionStringValues ); + } + + static const char* cmdStatusStringValues[] = + { + "executing", "completed", "canceled" + }; + + static inline const std::string statusString( Adhoc::Command::Status status ) + { + return util::lookup( status, cmdStatusStringValues ); + } + + static const char* cmdNoteStringValues[] = + { + "info", "warn", "error" + }; + + static inline const std::string noteString( Adhoc::Command::Note::Severity sev ) + { + return util::lookup( sev, cmdNoteStringValues ); + } + + // ---- Adhoc::Command::Note ---- + Adhoc::Command::Note::Note( const Tag* tag ) + : m_severity( InvalidSeverity ) + { + if( !tag || tag->name() != "note" ) + return; + + m_severity = (Severity)util::deflookup( tag->findAttribute( "type" ), cmdNoteStringValues, Info ); + m_note = tag->cdata(); + } + + Tag* Adhoc::Command::Note::tag() const + { + if( m_note.empty() || m_severity == InvalidSeverity ) + return 0; + + Tag* n = new Tag( "note", m_note ); + n->addAttribute( TYPE, noteString( m_severity ) ); + return n; + } + // ---- ~Adhoc::Command::Note ---- + + // ---- Adhoc::Command ---- + Adhoc::Command::Command( const std::string& node, Adhoc::Command::Action action, + DataForm* form ) + : StanzaExtension( ExtAdhocCommand ), m_node( node ), m_form( form ), m_action( action ), + m_status( InvalidStatus ), m_actions( 0 ) + { + } + + Adhoc::Command::Command( const std::string& node, const std::string& sessionid, Status status, + DataForm* form ) + : StanzaExtension( ExtAdhocCommand ), m_node( node ), m_sessionid( sessionid ), + m_form( form ), m_action( InvalidAction ), m_status( status ), m_actions( 0 ) + { + } + + Adhoc::Command::Command( const std::string& node, const std::string& sessionid, + Adhoc::Command::Action action, + DataForm* form ) + : StanzaExtension( ExtAdhocCommand ), m_node( node ), m_sessionid( sessionid ), + m_form( form ), m_action( action ), m_actions( 0 ) + { + } + + Adhoc::Command::Command( const std::string& node, const std::string& sessionid, Status status, + Action executeAction, int allowedActions, + DataForm* form ) + : StanzaExtension( ExtAdhocCommand ), m_node( node ), m_sessionid( sessionid ), + m_form( form ), m_action( executeAction ), m_status( status ), m_actions( allowedActions ) + { + } + + Adhoc::Command::Command( const Tag* tag ) + : StanzaExtension( ExtAdhocCommand ), m_form( 0 ), m_actions( 0 ) + { + if( !tag || tag->name() != "command" || tag->xmlns() != XMLNS_ADHOC_COMMANDS ) + return; + + m_node = tag->findAttribute( "node" ); + m_sessionid = tag->findAttribute( "sessionid" ); + m_status = (Status)util::lookup( tag->findAttribute( "status" ), cmdStatusStringValues ); + + Tag* a = tag->findChild( "actions" ); + if( a ) + { + // Multi-stage response + m_action = (Action)util::deflookup2( a->findAttribute( "action" ), cmdActionStringValues, Complete ); + if( a->hasChild( "prev" ) ) + m_actions |= Previous; + if( a->hasChild( "next" ) ) + m_actions |= Next; + if( a->hasChild( "complete" ) ) + m_actions |= Complete; + } + else + { + m_action = (Action)util::deflookup2( tag->findAttribute( "action" ), cmdActionStringValues, Execute ); + } + + const ConstTagList& l = tag->findTagList( "/command/note" ); + ConstTagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + m_notes.push_back( new Note( (*it) ) ); + + Tag* x = tag->findChild( "x", "xmlns", XMLNS_X_DATA ); + if( x ) + m_form = new DataForm( x ); + } + + Adhoc::Command::~Command() + { + util::clearList( m_notes ); + delete m_form; + } + + const std::string& Adhoc::Command::filterString() const + { + static const std::string filter = "/iq/command[@xmlns='" + XMLNS_ADHOC_COMMANDS + "']"; + return filter; + } + + Tag* Adhoc::Command::tag() const + { + if( m_node.empty() ) + return 0; + + Tag* c = new Tag( "command" ); + c->setXmlns( XMLNS_ADHOC_COMMANDS ); + c->addAttribute( "node", m_node ); + if( m_actions != 0 ) + { + // Multi-stage command response + + if( m_status != InvalidStatus ) + c->addAttribute( "status", statusString( m_status ) ); + else + c->addAttribute( "status", statusString( Executing ) ); + + Tag* actions = new Tag( c, "actions" ); + + if( m_action != InvalidAction ) + c->addAttribute( "execute", actionString( m_action ) ); + else + c->addAttribute( "execute", actionString( Complete ) ); + + if( ( m_actions & Previous ) == Previous ) + new Tag( actions, "prev" ); + if( ( m_actions & Next ) == Next ) + new Tag( actions, "next" ); + if( ( m_actions & Complete ) == Complete ) + new Tag( actions, "complete" ); + } + else + { + // Single-stage command request/response or Multi-stage command request + + if( m_action != InvalidAction ) + c->addAttribute( "action", actionString( m_action ) ); + if( m_status != InvalidStatus ) + c->addAttribute( "status", statusString( m_status ) ); + } + + if ( !m_sessionid.empty() ) + c->addAttribute( "sessionid", m_sessionid ); + + if( m_form && *m_form ) + c->addChild( m_form->tag() ); + + NoteList::const_iterator it = m_notes.begin(); + for( ; it != m_notes.end(); ++it ) + c->addChild( (*it)->tag() ); + + return c; + } + // ---- ~Adhoc::Command ---- + + // ---- Adhoc ---- + Adhoc::Adhoc( ClientBase* parent ) + : m_parent( parent ) + { + if( !m_parent || !m_parent->disco() ) + return; + + m_parent->disco()->addFeature( XMLNS_ADHOC_COMMANDS ); + m_parent->disco()->registerNodeHandler( this, XMLNS_ADHOC_COMMANDS ); + m_parent->disco()->registerNodeHandler( this, EmptyString ); + m_parent->registerIqHandler( this, ExtAdhocCommand ); + m_parent->registerStanzaExtension( new Adhoc::Command() ); + } + + Adhoc::~Adhoc() + { + if( !m_parent || !m_parent->disco() ) + return; + + m_parent->disco()->removeFeature( XMLNS_ADHOC_COMMANDS ); + m_parent->disco()->removeNodeHandler( this, XMLNS_ADHOC_COMMANDS ); + m_parent->disco()->removeNodeHandler( this, EmptyString ); + m_parent->removeIqHandler( this, ExtAdhocCommand ); + m_parent->removeIDHandler( this ); + m_parent->removeStanzaExtension( ExtAdhocCommand ); + } + + StringList Adhoc::handleDiscoNodeFeatures( const JID& /*from*/, const std::string& /*node*/ ) + { + StringList features; + features.push_back( XMLNS_ADHOC_COMMANDS ); + return features; +// return StringList( 1, XMLNS_ADHOC_COMMANDS ); + } + + Disco::ItemList Adhoc::handleDiscoNodeItems( const JID& from, const JID& /*to*/, const std::string& node ) + { + Disco::ItemList l; + if( node.empty() ) + { + l.push_back( new Disco::Item( m_parent->jid(), XMLNS_ADHOC_COMMANDS, "Ad-Hoc Commands" ) ); + } + else if( node == XMLNS_ADHOC_COMMANDS ) + { + StringMap::const_iterator it = m_items.begin(); + for( ; it != m_items.end(); ++it ) + { + AdhocCommandProviderMap::const_iterator itp = m_adhocCommandProviders.find( (*it).first ); + if( itp != m_adhocCommandProviders.end() + && (*itp).second + && (*itp).second->handleAdhocAccessRequest( from, (*it).first ) ) + { + l.push_back( new Disco::Item( m_parent->jid(), (*it).first, (*it).second ) ); + } + } + } + return l; + } + + Disco::IdentityList Adhoc::handleDiscoNodeIdentities( const JID& /*from*/, const std::string& node ) + { + Disco::IdentityList l; + StringMap::const_iterator it = m_items.find( node ); + l.push_back( new Disco::Identity( "automation", + node == XMLNS_ADHOC_COMMANDS ? "command-list" : "command-node", + it == m_items.end() ? "Ad-Hoc Commands" : (*it).second ) ); + return l; + } + + bool Adhoc::handleIq( const IQ& iq ) + { + if( iq.subtype() != IQ::Set ) + return false; + + const Adhoc::Command* ac = iq.findExtension( ExtAdhocCommand ); + if( !ac || ac->node().empty()) + return false; + + AdhocCommandProviderMap::const_iterator it = m_adhocCommandProviders.find( ac->node() ); + if( it != m_adhocCommandProviders.end() ) + { + const std::string& sess = ac->sessionID().empty() ? m_parent->getID() : ac->sessionID(); + m_activeSessions[sess] = iq.id(); + (*it).second->handleAdhocCommand( iq.from(), *ac, sess ); + return true; + } + + return false; + } + + void Adhoc::handleIqID( const IQ& iq, int context ) + { + if( context != ExecuteAdhocCommand ) + return; + + AdhocTrackMap::iterator it = m_adhocTrackMap.find( iq.id() ); + if( it == m_adhocTrackMap.end() || (*it).second.context != context + || (*it).second.remote != iq.from() ) + return; + + switch( iq.subtype() ) + { + case IQ::Error: + (*it).second.ah->handleAdhocError( iq.from(), iq.error() ); + break; + case IQ::Result: + { + const Adhoc::Command* ac = iq.findExtension( ExtAdhocCommand ); + if( ac ) + (*it).second.ah->handleAdhocExecutionResult( iq.from(), *ac ); + break; + } + default: + break; + } + m_adhocTrackMap.erase( it ); + } + + void Adhoc::registerAdhocCommandProvider( AdhocCommandProvider* acp, const std::string& command, + const std::string& name ) + { + if( !m_parent || !m_parent->disco() ) + return; + + m_parent->disco()->registerNodeHandler( this, command ); + m_adhocCommandProviders[command] = acp; + m_items[command] = name; + } + + void Adhoc::handleDiscoInfo( const JID& from, const Disco::Info& info, int context ) + { + if( context != CheckAdhocSupport ) + return; + + AdhocTrackMap::iterator it = m_adhocTrackMap.begin(); + for( ; it != m_adhocTrackMap.end() && (*it).second.context != context + && (*it).second.remote != from; ++it ) + ; + if( it == m_adhocTrackMap.end() ) + return; + + (*it).second.ah->handleAdhocSupport( from, info.hasFeature( XMLNS_ADHOC_COMMANDS ) ); + m_adhocTrackMap.erase( it ); + } + + void Adhoc::handleDiscoItems( const JID& from, const Disco::Items& items, int context ) + { + if( context != FetchAdhocCommands ) + return; + + AdhocTrackMap::iterator it = m_adhocTrackMap.begin(); + for( ; it != m_adhocTrackMap.end(); ++it ) + { + if( (*it).second.context == context && (*it).second.remote == from ) + { + StringMap commands; + const Disco::ItemList& l = items.items(); + Disco::ItemList::const_iterator it2 = l.begin(); + for( ; it2 != l.end(); ++it2 ) + { + commands[(*it2)->node()] = (*it2)->name(); + } + (*it).second.ah->handleAdhocCommands( from, commands ); + + m_adhocTrackMap.erase( it ); + break; + } + } + } + + void Adhoc::handleDiscoError( const JID& from, const Error* error, int context ) + { + AdhocTrackMap::iterator it = m_adhocTrackMap.begin(); + for( ; it != m_adhocTrackMap.end(); ++it ) + { + if( (*it).second.context == context && (*it).second.remote == from ) + { + (*it).second.ah->handleAdhocError( from, error ); + + m_adhocTrackMap.erase( it ); + } + } + } + + void Adhoc::checkSupport( const JID& remote, AdhocHandler* ah ) + { + if( !remote || !ah || !m_parent || !m_parent->disco() ) + return; + + TrackStruct track; + track.remote = remote; + track.context = CheckAdhocSupport; + track.ah = ah; + const std::string& id = m_parent->getID(); + m_adhocTrackMap[id] = track; + m_parent->disco()->getDiscoInfo( remote, EmptyString, this, CheckAdhocSupport, id ); + } + + void Adhoc::getCommands( const JID& remote, AdhocHandler* ah ) + { + if( !remote || !ah || !m_parent || !m_parent->disco() ) + return; + + TrackStruct track; + track.remote = remote; + track.context = FetchAdhocCommands; + track.ah = ah; + const std::string& id = m_parent->getID(); + m_adhocTrackMap[id] = track; + m_parent->disco()->getDiscoItems( remote, XMLNS_ADHOC_COMMANDS, this, FetchAdhocCommands, id ); + } + + void Adhoc::execute( const JID& remote, const Adhoc::Command* command, AdhocHandler* ah ) + { + if( !remote || !command || !m_parent || !ah ) + return; + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Set, remote, id ); + iq.addExtension( command ); + + TrackStruct track; + track.remote = remote; + track.context = ExecuteAdhocCommand; + track.session = command->sessionID(); + track.ah = ah; + m_adhocTrackMap[id] = track; + + m_parent->send( iq, this, ExecuteAdhocCommand ); + } + + void Adhoc::respond( const JID& remote, const Adhoc::Command* command, const Error* error ) + { + if( !remote || !command || !m_parent ) + return; + + StringMap::iterator it = m_activeSessions.find( command->sessionID() ); + if( it == m_activeSessions.end() ) + return; + + IQ re( error ? IQ::Error : IQ::Result, remote, (*it).second ); + re.addExtension( command ); + if( error ) + re.addExtension( error ); + m_parent->send( re ); + m_activeSessions.erase( it ); + } + + void Adhoc::removeAdhocCommandProvider( const std::string& command ) + { + if( !m_parent || !m_parent->disco() ) + return; + + m_parent->disco()->removeNodeHandler( this, command ); + m_adhocCommandProviders.erase( command ); + m_items.erase( command ); + } + +} diff --git a/libs/libgloox/adhoc.h b/libs/libgloox/adhoc.h new file mode 100644 index 0000000..849fbf8 --- /dev/null +++ b/libs/libgloox/adhoc.h @@ -0,0 +1,487 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef ADHOC_H__ +#define ADHOC_H__ + +#include "dataform.h" +#include "disco.h" +#include "disconodehandler.h" +#include "discohandler.h" +#include "iqhandler.h" +#include "stanzaextension.h" + +#include +#include +#include + +namespace gloox +{ + + class ClientBase; + class Stanza; + class AdhocHandler; + class AdhocCommandProvider; + + /** + * @brief This class implements a provider for XEP-0050 (Ad-hoc Commands). + * + * The current, not complete, implementation is probably best suited for fire-and-forget + * type of commands. Any additional feature, like multiple stages, etc., would have to be + * added separately. + * + * To offer commands to remote entities, use this class as follows:
+ * Create a class that will handle command execution requests and derive it from + * AdhocCommandProvider. Instantiate an Adhoc object and register your + * AdhocCommandProvider-derived object with the Adhoc object using + * registerAdhocCommandProvider(). The additional parameters to that method are the internal + * name of the command as used in the code, and the public name of the command as it + * will be shown to an end user: + * @code + * MyClass::someFunc() + * { + * Adhoc* m_adhoc = new Adhoc( m_client ); + * + * // this might be a bot monitoring a weather station, for example + * m_adhoc->registerAdhocCommandProvider( this, "getTemp", "Retrieve current temperature" ); + * m_adhoc->registerAdhocCommandProvider( this, "getPressure", "Retrieve current air pressure" ); + * [...] + * } + * @endcode + * In this example, MyClass is AdhocCommandProvider-derived so it is obviously the command handler, too. + * + * And that's about it you can do with the Adhoc class. Of course you can have a AdhocCommandProvider + * handle more than one command, just register it with the Adhoc object for every desired command, + * like shown above. + * + * What the Adhoc object does when you install a new command is tell the supplied Disco object + * to advertise these commands to clients using the 'Service Discovery' protocol to learn about + * this implementation's features. These clients can then call and execute the command. Of course you + * are free to implement access restrictions to not let anyone mess with your bot, for example. + * However, the commands offered using Service Discovery are publically visible in any case. + * + * To execute commands offered by a remote entity:
+ * ...TBC... + * + * XEP version: 1.2 + * @author Jakob Schroeter + */ + class GLOOX_API Adhoc : public DiscoNodeHandler, public DiscoHandler, public IqHandler + { + public: + /** + * @brief An abstraction of an Adhoc Command element (from Adhoc Commands, XEP-0050) + * as a StanzaExtension. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Command : public StanzaExtension + { + friend class Adhoc; + + public: + + /** + * Specifies the action to undertake with the given command. + */ + enum Action + { + Execute = 1, /**< The command should be executed or continue to be executed. + * This is the default value. */ + Cancel = 2, /**< The command should be canceled. */ + Previous = 4, /**< The command should be digress to the previous stage of + * execution. */ + Next = 8, /**< The command should progress to the next stage of + * execution. */ + Complete = 16, /**< The command should be completed (if possible). */ + InvalidAction = 32 /**< The action is unknown or invalid. */ + }; + + /** + * Describes the current status of a command. + */ + enum Status + { + Executing, /**< The command is being executed. */ + Completed, /**< The command has completed. The command session has ended. */ + Canceled, /**< The command has been canceled. The command session has ended. */ + InvalidStatus /**< The status is unknown or invalid. */ + }; + + /** + * An abstraction of a command note. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Note + { + + friend class Command; + + public: + /** + * Specifies the severity of a note. + */ + enum Severity + { + Info, /**< The note is informational only. This is not really an + * exceptional condition. */ + Warning, /**< The note indicates a warning. Possibly due to illogical + * (yet valid) data. */ + Error, /**< The note indicates an error. The text should indicate the + * reason for the error. */ + InvalidSeverity /**< The note type is unknown or invalid. */ + }; + + /** + * A convenience constructor. + * @param sev The note's severity. + * @param note The note's content. + */ + Note( Severity sev, const std::string& note ) + : m_severity( sev ), m_note( note ) {} + + /** + * Destructor. + */ + ~Note() {} + + /** + * Returns the note's severity. + * @return The note's severity. + */ + Severity severity() const { return m_severity; } + + /** + * Returns the note's content. + * @return The note's content. + */ + const std::string& content() const { return m_note; } + + /** + * Returns a Tag representation of the Note. + * @return A Tag representation. + */ + Tag* tag() const; + + private: +#ifdef ADHOC_COMMANDS_TEST + public: +#endif + /** + * Constructs a new Note from the given Tag. + * @param tag The Tag to parse. + */ + Note( const Tag* tag ); + + Severity m_severity; /**< The note's severity. */ + std::string m_note; /**< The note's content. */ + }; + + /** + * A list of command notes. + */ + typedef std::list NoteList; + + /** + * Creates a Command object that can be used to perform the provided Action. + * This constructor is used best to continue execution of a multi stage command + * (for which the session ID must be known). + * @param node The node (command) to perform the action on. + * @param sessionid The session ID of an already running adhoc command session. + * @param action The action to perform. + * @param form An optional DataForm to include in the request. Will be deleted in Command's + * destructor. + */ + Command( const std::string& node, const std::string& sessionid, Action action, + DataForm* form = 0 ); + + /** + * Creates a Command object that can be used to perform the provided Action. + * This constructor is used best to reply to an execute request. + * @param node The node (command) to perform the action on. + * @param sessionid The (possibly newly created) session ID of the adhoc command session. + * @param status The execution status. + * @param form An optional DataForm to include in the reply. Will be deleted in Command's + * destructor. + */ + Command( const std::string& node, const std::string& sessionid, Status status, + DataForm* form = 0 ); + + /** + * Creates a Command object that can be used to perform the provided Action. + * This constructor is used best to reply to a multi stage command that is not yet completed + * (for which the session ID must be known). + * @param node The node (command) to perform the action on. + * @param sessionid The (possibly newly created) session ID of the adhoc command session. + * @param status The execution status. + * @param executeAction The action to execute. + * @param allowedActions Allowed reply actions. + * @param form An optional DataForm to include in the reply. Will be deleted in Command's + * destructor. + */ + Command( const std::string& node, const std::string& sessionid, Status status, + Action executeAction, int allowedActions = Complete, + DataForm* form = 0 ); + + /** + * Creates a Command object that can be used to perform the provided Action. + * This constructor is used best to execute the initial step of a command + * (single or multi stage). + * @param node The node (command) to perform the action on. + * @param action The action to perform. + * @param form An optional DataForm to include in the request. Will be deleted in Command's + * destructor. + */ + Command( const std::string& node, Action action, + DataForm* form = 0 ); + + /** + * Creates a Command object from the given Tag. + * @param tag A <command> tag in the adhoc commands' namespace. + */ + Command( const Tag* tag = 0 ); + + /** + * Virtual destructor. + */ + virtual ~Command(); + + /** + * Returns the node identifier (the command). + * @return The node identifier. + */ + const std::string& node() const { return m_node; } + + /** + * Returns the command's session ID, if any. + * @return The command's session ID. + */ + const std::string& sessionID() const { return m_sessionid; } + + /** + * Returns the execution status for a command. Only valid for execution + * results. + * @return The execution status for a command. + */ + Status status() const { return m_status; } + + /** + * Returns the command's action. + * @return The command's action. + */ + Action action() const { return m_action; } + + /** + * Returns the ORed actions that are allowed to be executed on the + * current stage. + * @return An int containing the ORed actions. + */ + int actions() const { return m_actions; } + + /** + * Returns the list of notes associated with the command. + * @return The list of notes. + */ + const NoteList& notes() const { return m_notes; } + + /** + * Use this function to add a note to the command. + * @param note A pointer to a Note object. The Command will own + * the Note. + */ + void addNote( const Note* note ) { m_notes.push_back( note ); } + + /** + * Returns the command's embedded DataForm. + * @return The command's embedded DataForm. May be 0. + */ + const DataForm* form() const { return m_form; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Command( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + Command* c = new Command(); + + NoteList::const_iterator it = m_notes.begin(); + for( ; it != m_notes.end(); ++it ) + c->m_notes.push_back( new Note( *(*it) ) ); + + c->m_node = m_node; + c->m_sessionid = m_sessionid; + c->m_form = m_form ? static_cast( m_form->clone() ) : 0; + c->m_action = m_action; + c->m_status = m_status; + c->m_actions = m_actions; + + return c; + } + + private: +#ifdef ADHOC_COMMANDS_TEST + public: +#endif + NoteList m_notes; + + std::string m_node; + std::string m_sessionid; + DataForm* m_form; + Action m_action; + Status m_status; + int m_actions; + }; + + /** + * Constructor. + * Creates a new Adhoc client that registers as IqHandler with a ClientBase. + * @param parent The ClientBase used for XMPP communication. + */ + Adhoc( ClientBase* parent ); + + /** + * Virtual destructor. + */ + virtual ~Adhoc(); + + /** + * This function queries the given remote entity for Adhoc Commands support. + * @param remote The remote entity's JID. + * @param ah The object handling the result of this request. + */ + void checkSupport( const JID& remote, AdhocHandler* ah ); + + /** + * Retrieves a list of commands from the remote entity. You should check whether the remote + * entity actually supports Adhoc Commands by means of checkSupport(). + * @param remote The remote entity's JID. + * @param ah The object handling the result of this request. + */ + void getCommands( const JID& remote, AdhocHandler* ah ); + + /** + * Executes or continues the given command on the given remote entity. + * To construct the @c command object, it is recommended to use either + * Command( const std::string&, Action ) to begin execution of a command, or + * Command( const std::string&, const std::string&, Action ) to continue execution + * of a command. + * @param remote The remote entity's JID. + * @param command The command to execute. + * @param ah The object handling the result of this request. + */ + void execute( const JID& remote, const Adhoc::Command* command, AdhocHandler* ah ); + + /** + * Use this function to respond to an execution request submitted by means + * of AdhocCommandProvider::handleAdhocCommand(). + * It is recommended to use + * Command( const std::string&, const std::string&, Status, DataForm* ) + * to construct the @c command object. + * Optionally, an Error object can be included. In that case the IQ sent is of type @c error. + * @param remote The requester's JID. + * @param command The response. The Adhoc object will own and delete the + * command object pointed to. + * @param error An optional Error obejct to include. + */ + void respond( const JID& remote, const Adhoc::Command* command, const Error* error = 0 ); + + /** + * Using this function, you can register a AdhocCommandProvider -derived object as + * handler for a specific Ad-hoc Command as defined in XEP-0050. + * @param acp The obejct to register as handler for the specified command. + * @param command The node name of the command. Will be announced in disco#items. + * @param name The natural-language name of the command. Will be announced in disco#items. + */ + void registerAdhocCommandProvider( AdhocCommandProvider* acp, const std::string& command, + const std::string& name ); + + /** + * Use this function to unregister an adhoc command previously registered using + * registerAdhocCommandProvider(). + * @param command The command to unregister. + */ + void removeAdhocCommandProvider( const std::string& command ); + + // reimplemented from DiscoNodeHandler + virtual StringList handleDiscoNodeFeatures( const JID& from, const std::string& node ); + + // reimplemented from DiscoNodeHandler + virtual Disco::IdentityList handleDiscoNodeIdentities( const JID& from, + const std::string& node ); + + // reimplemented from DiscoNodeHandler + virtual Disco::ItemList handleDiscoNodeItems( const JID& from, const JID& to, const std::string& node ); + + // reimplemented from IqHandler + virtual bool handleIq( const IQ& iq ); + + // reimplemented from IqHandler + virtual void handleIqID( const IQ& iq, int context ); + + // reimplemented from DiscoHandler + virtual void handleDiscoInfo( const JID& from, const Disco::Info& info, int context ); + + // reimplemented from DiscoHandler + virtual void handleDiscoItems( const JID& from, const Disco::Items& items, int context ); + + // reimplemented from DiscoHandler + virtual void handleDiscoError( const JID& from, const Error* error, int context ); + + private: +#ifdef ADHOC_TEST + public: +#endif + typedef std::map AdhocCommandProviderMap; + AdhocCommandProviderMap m_adhocCommandProviders; + + enum AdhocContext + { + CheckAdhocSupport, + FetchAdhocCommands, + ExecuteAdhocCommand + }; + + struct TrackStruct + { + JID remote; + AdhocContext context; + std::string session; + AdhocHandler* ah; + }; + typedef std::map AdhocTrackMap; + AdhocTrackMap m_adhocTrackMap; + + ClientBase* m_parent; + + StringMap m_items; + StringMap m_activeSessions; + + }; + +} + +#endif // ADHOC_H__ diff --git a/libs/libgloox/adhoccommandprovider.h b/libs/libgloox/adhoccommandprovider.h new file mode 100644 index 0000000..97bd937 --- /dev/null +++ b/libs/libgloox/adhoccommandprovider.h @@ -0,0 +1,79 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef ADHOCCOMMANDPROVIDER_H__ +#define ADHOCCOMMANDPROVIDER_H__ + +#include "tag.h" +#include "jid.h" +#include "adhoc.h" + +#include +#include +#include + +namespace gloox +{ + + /** + * @brief A virtual interface for an Ad-hoc Command Provider according to XEP-0050. + * + * Derived classes can be registered as Command Providers with the Adhoc object. + * + * @author Jakob Schroeter + */ + class GLOOX_API AdhocCommandProvider + { + public: + /** + * Virtual destructor. + */ + virtual ~AdhocCommandProvider() {} + + /** + * This function is called when an Ad-hoc Command needs to be handled. + * The callee is responsible for the whole command execution, i.e. session + * handling etc. + * @param from The sender of the command request. + * @param command The name of the command to be executed. + * @param sessionID The session ID. Either newly generated or taken from the command. + * When responding, its value must be passed to Adhoc::Command's constructor. + */ + virtual void handleAdhocCommand( const JID& from, const Adhoc::Command& command, + const std::string& sessionID ) = 0; + + /** + * This function gets called for each registered command when a remote + * entity requests the list of available commands. + * @param from The requesting entity. + * @param command The command's name. + * @return @b True if the remote entity is allowed to see the command, @b false if not. + * @note The return value of this function does not influence + * the execution of a command. That is, you have to + * implement additional access control at the execution + * stage. + * @note This function should not block. + */ + virtual bool handleAdhocAccessRequest( const JID& from, const std::string& command ) + { + (void)from; + (void)command; + return true; + } + + }; + +} + +#endif // ADHOCCOMMANDPROVIDER_H__ diff --git a/libs/libgloox/adhochandler.h b/libs/libgloox/adhochandler.h new file mode 100644 index 0000000..8b83065 --- /dev/null +++ b/libs/libgloox/adhochandler.h @@ -0,0 +1,75 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef ADHOCHANDLER_H__ +#define ADHOCHANDLER_H__ + +#include "adhoc.h" + +namespace gloox +{ + + /** + * @brief A virtual interface for an Ad-hoc Command users according to XEP-0050. + * + * Derived classes can be registered with the Adhoc object to receive notifications + * about Adhoc Commands remote entities support. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API AdhocHandler + { + public: + /** + * Virtual destructor. + */ + virtual ~AdhocHandler() {} + + /** + * This function is called in response to a call to Adhoc::checkSupport(). + * @param remote The queried remote entity's JID. + * @param support Whether the remote entity supports Adhoc Commands. + */ + virtual void handleAdhocSupport( const JID& remote, bool support ) = 0; + + /** + * This function is called in response to a call to Adhoc::getCommands() + * and delivers a list of supported commands. + * @param remote The queried remote entity's JID. + * @param commands A map of supported commands and their human-readable name. + * The map may be empty. + */ + virtual void handleAdhocCommands( const JID& remote, const StringMap& commands ) = 0; + + /** + * This function is called in response to a call to Adhoc::getCommands() or + * Adhoc::checkSupport() or Adhoc::execute() in case the respective request returned + * an error. + * @param remote The queried remote entity's JID. + * @param error The error condition. May be 0. + */ + virtual void handleAdhocError( const JID& remote, const Error* error ) = 0; + + /** + * This function is called in response to a remote command execution. + * @param remote The remote entity's JID. + * @param command The command being executed. + */ + virtual void handleAdhocExecutionResult( const JID& remote, const Adhoc::Command& command ) = 0; + }; + +} + +#endif // ADHOCHANDLER_H__ diff --git a/libs/libgloox/amp.cpp b/libs/libgloox/amp.cpp new file mode 100644 index 0000000..3ff2c2c --- /dev/null +++ b/libs/libgloox/amp.cpp @@ -0,0 +1,189 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "amp.h" +#include "tag.h" +#include "util.h" + +namespace gloox +{ + + static const char* conditionValues[] = + { + "deliver", "expire-at", "match-resource" + }; + + static const char* actionValues[] = + { + "alert", "error", "drop", "notify" + }; + + static const char* deliverValues[] = + { + "direct", "forward", "gateway", "none", "stored" + }; + + static const char* matchResourceValues[] = + { + "any", "exact", "other" + }; + + static const char* statusValues[] = + { + "alert", "notify" + }; + + // ---- AMP::Rule ---- + AMP::Rule::Rule( DeliverType deliver, ActionType action ) + : m_condition( ConditionDeliver ), m_deliver( deliver ), m_action( action ) + { + } + + AMP::Rule::Rule( const std::string& date, ActionType action ) + : m_condition( ConditionExpireAt ), m_expireat( new std::string( date ) ), m_action( action ) + { + } + + AMP::Rule::Rule( MatchResourceType match, ActionType action ) + : m_condition( ConditionMatchResource ), m_matchresource( match ), m_action( action ) + { + } + + AMP::Rule::Rule( const std::string& condition, const std::string& action, + const std::string& value ) + { + m_condition = (ConditionType)util::lookup( condition, conditionValues ); + m_action = (ActionType)util::lookup( action, actionValues ); + switch( m_condition ) + { + case ConditionDeliver: + m_deliver = (DeliverType)util::lookup( value, deliverValues ); + break; + case ConditionExpireAt: + m_expireat = new std::string( value ); + break; + case ConditionMatchResource: + m_matchresource = (MatchResourceType)util::lookup( value, matchResourceValues ); + break; + default: + case ConditionInvalid: // shouldn't happen + break; + } + } + + AMP::Rule::~Rule() + { + if( m_condition == ConditionExpireAt && m_expireat ) + delete m_expireat; + } + + Tag* AMP::Rule::tag() const + { + if( m_condition == ConditionInvalid || m_action == ActionInvalid + || ( m_condition == ConditionDeliver && m_deliver == DeliverInvalid ) + || ( m_condition == ConditionMatchResource && m_matchresource == MatchResourceInvalid ) + || ( m_condition == ConditionExpireAt && !m_expireat ) ) + return 0; + + Tag* rule = new Tag( "rule" ); + rule->addAttribute( "condition", util::lookup( m_condition, conditionValues ) ); + rule->addAttribute( "action", util::lookup( m_action, actionValues ) ); + + switch( m_condition ) + { + case ConditionDeliver: + rule->addAttribute( "value", util::lookup( m_deliver, deliverValues ) ); + break; + case ConditionExpireAt: + rule->addAttribute( "value", *m_expireat ); + break; + case ConditionMatchResource: + rule->addAttribute( "value", util::lookup( m_matchresource, matchResourceValues ) ); + break; + default: + break; + } + return rule; + } + // ---- AMP::Rule ---- + + // ---- AMP ---- + AMP::AMP( bool perhop ) + : StanzaExtension( ExtAMP ), m_perhop( perhop ), m_status( StatusInvalid ) + { + m_valid = true; + } + + AMP::AMP( const Tag* tag ) + : StanzaExtension( ExtAMP ), m_perhop( false ) + { + if( !tag || tag->name() != "amp" || tag->xmlns() != XMLNS_AMP ) + return; + + const ConstTagList& rules = tag->findTagList( "/amp/rule" ); + ConstTagList::const_iterator it = rules.begin(); + for( ; it != rules.end(); ++it ) + { + m_rules.push_back( new Rule( (*it)->findAttribute( "condition" ), + (*it)->findAttribute( "action" ), + (*it)->findAttribute( "value" ) ) ); + } + + m_from = tag->findAttribute( "from" ); + m_to = tag->findAttribute( "to" ); + m_status = (Status)util::lookup( tag->findAttribute( "status" ), statusValues ); + if( tag->hasAttribute( "per-hop", "true" ) || tag->hasAttribute( "per-hop", "1" ) ) + m_perhop = true; + m_valid = true; + } + + AMP::~AMP() + { + util::clearList( m_rules ); + } + + void AMP::addRule( const Rule* rule ) + { + if( rule ) + m_rules.push_back( rule ); + } + + const std::string& AMP::filterString() const + { + static const std::string filter = "/message/amp[@xmlns='" + XMLNS_AMP + "']"; + return filter; + } + + Tag* AMP::tag() const + { + if( !m_valid || !m_rules.size() ) + return 0; + + Tag* amp = new Tag( "amp" ); + amp->setXmlns( XMLNS_AMP ); + if( m_from ) + amp->addAttribute( "from", m_from.full() ); + if( m_to ) + amp->addAttribute( "to", m_to.full() ); + if( m_status != StatusInvalid ) + amp->addAttribute( "status", util::lookup( m_status, statusValues ) ); + if( m_perhop ) + amp->addAttribute( "per-hop", "true" ); + RuleList::const_iterator it = m_rules.begin(); + for( ; it != m_rules.end(); ++it ) + amp->addChild( (*it)->tag() ); + + return amp; + } + +} diff --git a/libs/libgloox/amp.h b/libs/libgloox/amp.h new file mode 100644 index 0000000..dc44d09 --- /dev/null +++ b/libs/libgloox/amp.h @@ -0,0 +1,243 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef AMP_H__ +#define AMP_H__ + +#include "stanzaextension.h" +#include "jid.h" + +#include +#include + +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief This is an implementation of XEP-0079 (Advanced Message Processing) + * as a StanzaExtension. + * + * XEP Version: 1.2 + * @author Jakob Schroeter + * @author Vincent Thomasset + * @since 1.0 + */ + class GLOOX_API AMP : public StanzaExtension + { + + public: + /** + * Possible types for a rule's condition. + */ + enum ConditionType + { + ConditionDeliver, /**< Ensures (non-)delivery of the message */ + ConditionExpireAt, /**< Ensures delivery only before a certain time (UTC) */ + ConditionMatchResource, /**< Ensures delivery only to a specific resource type */ + ConditionInvalid /**< Invalid condition */ + }; + + /** + * Possible actions to take when the corresponding condition is met. + */ + enum ActionType + { + + ActionAlert, /**< Sends back a message stanza with an 'alert' status */ + ActionError, /**< Sends back a message stanza with an error type */ + ActionDrop, /**< Silently ignore the message */ + ActionNotify, /**< Sends back a message stanza with a 'notify' status */ + ActionInvalid /**< Invalid action */ + }; + + /** + * Possible delivery rules. + */ + enum DeliverType + { + DeliverDirect, /**< The message would be immediately delivered to the intended + * recipient or routed to the next hop. */ + DeliverForward, /**< The message would be forwarded to another XMPP address or + * account. */ + DeliverGateway, /**< The message would be sent through a gateway to an address + * or account on a non-XMPP system. */ + DeliverNone, /**< The message would not be delivered at all (e.g., because + * the intended recipient is offline and message storage is + * not enabled). */ + DeliverStored, /**< The message would be stored offline for later delivery + * to the intended recipient. */ + DeliverInvalid /**< Invalid deliver value */ + }; + + /** + * Possible resource matching rules. + */ + enum MatchResourceType + { + MatchResourceAny, /**< Destination resource matches any value, effectively + * ignoring the intended resource. */ + MatchResourceExact, /**< Destination resource exactly matches the intended + * resource. */ + MatchResourceOther, /**< Destination resource matches any value except for + * the intended resource. */ + MatchResourceInvalid /**< Invalid match-resource value */ + }; + + /** + * Available Stati. + */ + enum Status + { + StatusAlert, /**< The message is a reply to a @c Alert rule. */ + StatusNotify, /**< The message is a reply to a @c Notify rule. */ + StatusInvalid /**< Invalid status. */ + }; + + /** + * Describes an AMP rule. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Rule + { + public: + /** + * Creates a new AMP rule object with a condition of 'deliver'. + * @param deliver The delivery type. + * @param action The rule's action. + */ + Rule( DeliverType deliver, ActionType action ); + + /** + * Creates a new AMP rule object with a condition of 'expire-at'. + * @param date The expiry date/time in the format defined in XEP-0082. + * @param action The rule's action. + */ + Rule( const std::string& date, ActionType action ); + + /** + * Creates a new AMP rule object with a condition of 'match-resource'. + * @param match The match type. + * @param action The rule's action. + */ + Rule( MatchResourceType match, ActionType action ); + + /** + * Creates a new AMP rule object from the given strings. + * @param condition The rule's condition. + * @param action The rule's action. + * @param value The rule's value. + */ + Rule( const std::string& condition, const std::string& action, + const std::string& value ); + + /** + * Destructor. + */ + ~Rule(); + + /** + * Creates a Tag representation from the current rule. + * @return A Tag representation of the rule. + */ + Tag* tag() const; + + private: + ConditionType m_condition; + union + { + DeliverType m_deliver; + MatchResourceType m_matchresource; + std::string* m_expireat; + }; + ActionType m_action; + + }; + + /** + * A list of AMP rules. + */ + typedef std::list RuleList; + + /** + * Constructs a new object. + * @param perhop Indicates whether the ruleset should be applied to all hops, + * or at the edge servers only. Default: @c false (edge servers only) + */ + AMP( bool perhop = false ); + + /** + * Constructs a new object from the given Tag. + * @param tag The AMP Tag to parse. + */ + AMP( const Tag* tag ); + + /** + * Adds the given rule to the list of rules. + * @param rule The rule to add. + */ + void addRule( const Rule* rule ); + + /** + * Returns the current list of rules for inspection. + * @return The current list of rules. + */ + const RuleList& rules() const { return m_rules; } + + /** + * @brief Virtual Destructor. + */ + virtual ~AMP(); + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new AMP( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + AMP* a = new AMP(); + a->m_perhop = m_perhop; + RuleList::const_iterator it = m_rules.begin(); + for( ; it != m_rules.end(); ++it ) + a->m_rules.push_back( new Rule( *(*it) ) ); + a->m_status = m_status; + a->m_from = m_from; + a->m_to = m_to; + return a; + } + + private: + bool m_perhop; + RuleList m_rules; + Status m_status; + JID m_from; + JID m_to; + }; + +} + +#endif // AMP_H__ diff --git a/libs/libgloox/annotations.cpp b/libs/libgloox/annotations.cpp new file mode 100644 index 0000000..bc3ac04 --- /dev/null +++ b/libs/libgloox/annotations.cpp @@ -0,0 +1,90 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "annotations.h" +#include "clientbase.h" + + +namespace gloox +{ + + Annotations::Annotations( ClientBase* parent ) + : PrivateXML( parent ), + m_annotationsHandler( 0 ) + { + } + + Annotations::~Annotations() + { + } + + void Annotations::storeAnnotations( const AnnotationsList& aList ) + { + Tag* s = new Tag( "storage", XMLNS, XMLNS_ANNOTATIONS ); + + AnnotationsList::const_iterator it = aList.begin(); + for( ; it != aList.end(); ++it ) + { + Tag* n = new Tag( s, "note", (*it).note ); + n->addAttribute( "jid", (*it).jid ); + n->addAttribute( "cdate", (*it).cdate ); + n->addAttribute( "mdate", (*it).mdate ); + } + + storeXML( s, this ); + } + + void Annotations::requestAnnotations() + { + requestXML( "storage", XMLNS_ANNOTATIONS, this ); + } + + void Annotations::handlePrivateXML( const Tag* xml ) + { + if( !xml ) + return; + + AnnotationsList aList; + const TagList& l = xml->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + if( (*it)->name() == "note" ) + { + const std::string& jid = (*it)->findAttribute( "jid" ); + const std::string& note = (*it)->cdata(); + + if( !jid.empty() && !note.empty() ) + { + const std::string& cdate = (*it)->findAttribute( "cdate" ); + const std::string& mdate = (*it)->findAttribute( "mdate" ); + AnnotationsListItem item; + item.jid = jid; + item.cdate = cdate; + item.mdate = mdate; + item.note = note; + aList.push_back( item ); + } + } + } + + if( m_annotationsHandler ) + m_annotationsHandler->handleAnnotations( aList ); + } + + void Annotations::handlePrivateXMLResult( const std::string& /*uid*/, PrivateXMLResult /*result*/ ) + { + } + +} diff --git a/libs/libgloox/annotations.h b/libs/libgloox/annotations.h new file mode 100644 index 0000000..ff65ba0 --- /dev/null +++ b/libs/libgloox/annotations.h @@ -0,0 +1,147 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef ANNOTATIONS_H__ +#define ANNOTATIONS_H__ + +#include "macros.h" + +#include "annotationshandler.h" +#include "privatexml.h" +#include "privatexmlhandler.h" + +#include +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief This is an implementation of XEP-0145 (Annotations). + * + * You can use this class to store arbitrary notes about a roster item on the server + * (and to retrieve them later on). + * To retrieve all stored annotations for the current user's roster you have to create + * a class which inherits from AnnotationsHandler. This handler receives retrieved notes. + * + * @code + * class MyClass : public AnnotationsHandler + * { + * public: + * // ... + * void myFuncRetrieve(); + * void myFuncStore(); + * void handleAnnotations( const AnnotationsList &aList ); + * + * private: + * Annotations* m_notes; + * AnnotationsList m_list; + * }; + * + * void MyClass::myFuncRetrieve() + * { + * [...] + * m_notes = new Annotations( m_client ); + * m_notes->requestAnnotations(); + * } + * + * void MyClass::handleAnnotations( const AnnotationsList &aList ) + * { + * m_list = aList; + * } + * @endcode + * + * To store an additional note you have to fetch the currently stored notes first, + * add your new note to the list of notes, and transfer them all together back to the + * server. This protocol does not support storage of 'deltas', that is, when saving + * notes all previously saved notes are overwritten. + * + * @code + * void MyClass::myFuncStore() + * { + * annotationsListItem item; + * item.jid = "me@example.com"; + * item.cdate = "2006-02-04T15:23:21Z"; + * item.note = "some guy at example.com"; + * m_list.push_back( item ); + * + * item.jid = "abc@def.com"; + * item.cdate = "2006-01-24T15:23:21Z"; + * item.mdate = "2006-02-04T05:11:46Z"; + * item.note = "some other guy"; + * m_list.push_back( item ); + * + * m_notes->storeAnnotations( m_list ); + * } + * @endcode + * + * @author Jakob Schroeter + * @since 0.3 + */ + class GLOOX_API Annotations : public PrivateXML, public PrivateXMLHandler + { + public: + /** + * Constructs a new Annotations object. + * @param parent The ClientBase to use for communication. + */ + Annotations( ClientBase* parent ); + + /** + * Virtual destructor. + */ + virtual ~Annotations(); + + /** + * Use this function to store notes (annotations to contacts in a roster) on the server. + * Make sure you store the whole set of annotations, not a 'delta'. + * @param aList A list of notes to store. + */ + void storeAnnotations( const AnnotationsList& aList ); + + /** + * Use this function to initiate retrieval of annotations. Use registerAnnotationsHandler() + * to register an object which will receive the lists of notes. + */ + void requestAnnotations(); + + /** + * Use this function to register a AnnotationsHandler. + * @param ah The AnnotationsHandler which shall receive retrieved notes. + */ + void registerAnnotationsHandler( AnnotationsHandler* ah ) + { m_annotationsHandler = ah; } + + /** + * Use this function to un-register the AnnotationsHandler. + */ + void removeAnnotationsHandler() + { m_annotationsHandler = 0; } + + // reimplemented from PrivateXMLHandler + virtual void handlePrivateXML( const Tag* xml ); + + // reimplemented from PrivateXMLHandler + virtual void handlePrivateXMLResult( const std::string& uid, PrivateXMLResult pxResult ); + + private: + AnnotationsHandler* m_annotationsHandler; + + }; + +} + +#endif // ANNOTATIONS_H__ diff --git a/libs/libgloox/annotationshandler.h b/libs/libgloox/annotationshandler.h new file mode 100644 index 0000000..99178ea --- /dev/null +++ b/libs/libgloox/annotationshandler.h @@ -0,0 +1,66 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef ANNOTATIONSHANDLER_H__ +#define ANNOTATIONSHANDLER_H__ + +#include "macros.h" + +#include +#include + +namespace gloox +{ + + /** + * This describes a single note item. + */ + struct AnnotationsListItem + { + std::string jid; /**< The JID of the roster item this note is about */ + std::string cdate; /**< Creation date of this note. */ + std::string mdate; /**< Date of last modification of this note. */ + std::string note; /**< The note. */ + }; + + /** + * A list of note items. + */ + typedef std::list AnnotationsList; + + /** + * @brief A virtual interface which can be reimplemented to receive notes with help of + * the Annotations object. + * + * @author Jakob Schroeter + * @since 0.3 + */ + class GLOOX_API AnnotationsHandler + { + public: + /** + * Virtual destructor. + */ + virtual ~AnnotationsHandler() {} + + /** + * This function is called when notes arrive from the server. + * @param aList A list of notes. + */ + virtual void handleAnnotations( const AnnotationsList &aList ) = 0; + }; + +} + +#endif // ANNOTATIONSHANDLER_H__ diff --git a/libs/libgloox/attention.cpp b/libs/libgloox/attention.cpp new file mode 100644 index 0000000..437e804 --- /dev/null +++ b/libs/libgloox/attention.cpp @@ -0,0 +1,43 @@ +/* + Copyright (c) 2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "attention.h" +#include "tag.h" + +namespace gloox +{ + + Attention::Attention() + : StanzaExtension( ExtAttention ) + { + } + + Attention::~Attention() + { + } + + const std::string& Attention::filterString() const + { + static const std::string filter = "/message/attention[@xmlns='" + XMLNS_ATTENTION + "']"; + return filter; + } + + Tag* Attention::tag() const + { + Tag* t = new Tag( "attention" ); + t->setXmlns( XMLNS_ATTENTION ); + return t; + } + +} diff --git a/libs/libgloox/attention.h b/libs/libgloox/attention.h new file mode 100644 index 0000000..1a81af5 --- /dev/null +++ b/libs/libgloox/attention.h @@ -0,0 +1,70 @@ +/* + Copyright (c) 2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef ATTENTION_H__ +#define ATTENTION_H__ + + +#include "stanzaextension.h" + +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief This is an implementation of XEP-0224 as a StanzaExtension. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Attention : public StanzaExtension + { + + public: + /** + * Constructs a new object from the given Tag. + */ + Attention(); + + /** + * Virtual Destructor. + */ + virtual ~Attention(); + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* /*tag*/ ) const + { + return new Attention(); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new Attention(); + } + + }; + +} + +#endif// ATTENTION_H__ diff --git a/libs/libgloox/base64.cpp b/libs/libgloox/base64.cpp new file mode 100644 index 0000000..3c0253f --- /dev/null +++ b/libs/libgloox/base64.cpp @@ -0,0 +1,126 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "base64.h" + +namespace gloox +{ + + namespace Base64 + { + + static const std::string alphabet64( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ); + static const char pad = '='; + static const char np = (char)std::string::npos; + static char table64vals[] = + { + 62, np, np, np, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, np, np, np, np, np, + np, np, 0, 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, np, np, np, np, np, np, 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 + }; + + inline char table64( unsigned char c ) + { + return ( c < 43 || c > 122 ) ? np : table64vals[c-43]; + } + + const std::string encode64( const std::string& input ) + { + std::string encoded; + char c; + const std::string::size_type length = input.length(); + + encoded.reserve( length * 2 ); + + for( std::string::size_type i = 0; i < length; ++i ) + { + c = static_cast( ( input[i] >> 2 ) & 0x3f ); + encoded += alphabet64[c]; + + c = static_cast( ( input[i] << 4 ) & 0x3f ); + if( ++i < length ) + c = static_cast( c | static_cast( ( input[i] >> 4 ) & 0x0f ) ); + encoded += alphabet64[c]; + + if( i < length ) + { + c = static_cast( ( input[i] << 2 ) & 0x3c ); + if( ++i < length ) + c = static_cast( c | static_cast( ( input[i] >> 6 ) & 0x03 ) ); + encoded += alphabet64[c]; + } + else + { + ++i; + encoded += pad; + } + + if( i < length ) + { + c = static_cast( input[i] & 0x3f ); + encoded += alphabet64[c]; + } + else + { + encoded += pad; + } + } + + return encoded; + } + + const std::string decode64( const std::string& input ) + { + char c, d; + const std::string::size_type length = input.length(); + std::string decoded; + + decoded.reserve( length ); + + for( std::string::size_type i = 0; i < length; ++i ) + { + c = table64(input[i]); + ++i; + d = table64(input[i]); + c = static_cast( ( c << 2 ) | ( ( d >> 4 ) & 0x3 ) ); + decoded += c; + if( ++i < length ) + { + c = input[i]; + if( pad == c ) + break; + + c = table64(input[i]); + d = static_cast( ( ( d << 4 ) & 0xf0 ) | ( ( c >> 2 ) & 0xf ) ); + decoded += d; + } + + if( ++i < length ) + { + d = input[i]; + if( pad == d ) + break; + + d = table64(input[i]); + c = static_cast( ( ( c << 6 ) & 0xc0 ) | d ); + decoded += c; + } + } + + return decoded; + } + + } + +} diff --git a/libs/libgloox/base64.h b/libs/libgloox/base64.h new file mode 100644 index 0000000..158528f --- /dev/null +++ b/libs/libgloox/base64.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef BASE64_H__ +#define BASE64_H__ + +#include "macros.h" + +#include + +namespace gloox +{ + + /** + * @brief An implementation of the Base64 data encoding (RFC 3548) + * + * @author Jakob Schroeter + * @since 0.8 + */ + namespace Base64 + { + + /** + * Base64-encodes the input according to RFC 3548. + * @param input The data to encode. + * @return The encoded string. + */ + GLOOX_API const std::string encode64( const std::string& input ); + + /** + * Base64-decodes the input according to RFC 3548. + * @param input The encoded data. + * @return The decoded data. + */ + GLOOX_API const std::string decode64( const std::string& input ); + + } + +} + +#endif // BASE64_H__ diff --git a/libs/libgloox/bookmarkhandler.h b/libs/libgloox/bookmarkhandler.h new file mode 100644 index 0000000..141b3f9 --- /dev/null +++ b/libs/libgloox/bookmarkhandler.h @@ -0,0 +1,82 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef BOOKMARKHANDLER_H__ +#define BOOKMARKHANDLER_H__ + +#include "macros.h" + +#include +#include + +namespace gloox +{ + + /** + * This describes a single bookmarked URL item. + */ + struct BookmarkListItem + { + std::string name; /**< A human readable name of the bookmark. */ + std::string url; /**< The URL of the bookmark. */ + }; + + /** + * This describes a single bookmarked conference item. + */ + struct ConferenceListItem + { + std::string name; /**< A human readable name of the conference room. */ + std::string jid; /**< The address of the room. */ + std::string nick; /**< The nick name to use in this room. */ + std::string password; /**< The password to use for a protected room. */ + bool autojoin; /**< The conference shall be joined automatically on login. */ + }; + + /** + * A list of URL items. + */ + typedef std::list BookmarkList; + + /** + * A list of conference items. + */ + typedef std::list ConferenceList; + + /** + * @brief A virtual interface which can be reimplemented to receive bookmarks with help of a + * BookmarkStorage object. + * + * @author Jakob Schroeter + * @since 0.3 + */ + class GLOOX_API BookmarkHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~BookmarkHandler() {} + + /** + * This function is called when bookmarks arrive from the server. + * @param bList A list of URL bookmarks. + * @param cList A list of conference bookmarks. + */ + virtual void handleBookmarks( const BookmarkList &bList, const ConferenceList &cList ) = 0; + }; + +} + +#endif // BOOKMARKHANDLER_H__ diff --git a/libs/libgloox/bookmarkstorage.cpp b/libs/libgloox/bookmarkstorage.cpp new file mode 100644 index 0000000..a5c1904 --- /dev/null +++ b/libs/libgloox/bookmarkstorage.cpp @@ -0,0 +1,118 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "bookmarkstorage.h" +#include "clientbase.h" + + +namespace gloox +{ + + BookmarkStorage::BookmarkStorage( ClientBase* parent ) + : PrivateXML( parent ), + m_bookmarkHandler( 0 ) + { + } + + BookmarkStorage::~BookmarkStorage() + { + } + + void BookmarkStorage::storeBookmarks( const BookmarkList& bList, const ConferenceList& cList ) + { + Tag* s = new Tag( "storage" ); + s->addAttribute( XMLNS, XMLNS_BOOKMARKS ); + + BookmarkList::const_iterator itb = bList.begin(); + for( ; itb != bList.end(); ++itb ) + { + Tag* i = new Tag( s, "url", "name", (*itb).name ); + i->addAttribute( "url", (*itb).url ); + } + + ConferenceList::const_iterator itc = cList.begin(); + for( ; itc != cList.end(); ++itc ) + { + Tag* i = new Tag( s, "conference", "name", (*itc).name ); + i->addAttribute( "jid", (*itc).jid ); + i->addAttribute( "autojoin", (*itc).autojoin ? "true" : "false" ); + + new Tag( i, "nick", (*itc).nick ); + new Tag( i, "password", (*itc).password ); + } + + storeXML( s, this ); + } + + void BookmarkStorage::requestBookmarks() + { + requestXML( "storage", XMLNS_BOOKMARKS, this ); + } + + void BookmarkStorage::handlePrivateXML( const Tag* xml ) + { + if( !xml ) + return; + + BookmarkList bList; + ConferenceList cList; + const TagList& l = xml->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + if( (*it)->name() == "url" ) + { + const std::string& url = (*it)->findAttribute( "url" ); + const std::string& name = (*it)->findAttribute( "name" ); + + if( !url.empty() && !name.empty() ) + { + BookmarkListItem item; + item.url = url; + item.name = name; + bList.push_back( item ); + } + } + else if( (*it)->name() == "conference" ) + { + const std::string& jid = (*it)->findAttribute( "jid" ); + const std::string& name = (*it)->findAttribute( "name" ); + + if( !jid.empty() && !name.empty() ) + { + const std::string& join = (*it)->findAttribute( "autojoin" ); + ConferenceListItem item; + item.jid = jid; + item.name = name; + const Tag* nick = (*it)->findChild( "nick" ); + if( nick ) + item.nick = nick->cdata(); + const Tag* pwd = (*it)->findChild( "password" ); + if( pwd ) + item.password = pwd->cdata(); + item.autojoin = ( join == "true" || join == "1" ); + cList.push_back( item ); + } + } + } + + if( m_bookmarkHandler ) + m_bookmarkHandler->handleBookmarks( bList, cList ); + } + + void BookmarkStorage::handlePrivateXMLResult( const std::string& /*uid*/, PrivateXMLResult /*result*/ ) + { + } + +} diff --git a/libs/libgloox/bookmarkstorage.h b/libs/libgloox/bookmarkstorage.h new file mode 100644 index 0000000..c3c7fb0 --- /dev/null +++ b/libs/libgloox/bookmarkstorage.h @@ -0,0 +1,150 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef BOOKMARKSTORAGE_H__ +#define BOOKMARKSTORAGE_H__ + +#include "macros.h" + +#include "bookmarkhandler.h" +#include "privatexml.h" +#include "privatexmlhandler.h" + +#include +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief This is an implementation of XEP-0048 (Bookmark Storage). + * + * You can use this class to store bookmarks to multi-user chat rooms or ordinary URLs + * on the server (and to retrieve them later on). + * To retrieve all stored bookmarks for the current user you have to create a class which + * inherits from BookmarkHandler. This handler receives retrieved bookmarks. + * + * @code + * class MyClass : public BookmarkHandler + * { + * public: + * // ... + * void myFuncRetrieve(); + * void myFuncStore(); + * void handleBookmarks( const BookmarkList &bList, const ConferenceList &cList ); + * + * private: + * BookmarkStorage* m_bs; + * BookmarkList m_bList; + * ConferenceList m_cList; + * }; + * + * void MyClass::myFuncRetrieve() + * { + * m_bs = new BookmarkStorage( m_client ); + * m_bs->requestBookmarks(); + * } + * + * void MyClass::handleBookmarks( const BookmarkList &bList, const ConferenceList &cList ) + * { + * m_bList = bList; + * m_cList = cList; + * } + * @endcode + * + * + * To store additional bookmarks you have to fetch the currently stored ones first, + * add your new bookmark to the list, and transfer them all together back to the + * server. This protocol does not support storage of 'deltas', that is, when saving + * bookmarks all previously saved bookmarks are overwritten. + * + * @code + * void MyClass::myFuncStore() + * { + * BookmarkListItem bi; + * bi.url = "http://www.jabber.org"; + * bi.name = "my favourite IM protocol"; + * m_bList.push_back( bi ); + * + * conferenceListItem ci + * ci.name = "jabber/xmpp development room"; + * ci.jid = "jdev@conference.jabber.org"; + * ci.nick = "myNick"; + * ci.password = EmptyString; + * ci.autojoin = true; + * m_cList.push_back( ci ); + * + * m_bs->storeBookmarks( m_bList, m_cList ); + * } + * @endcode + * + * @author Jakob Schroeter + * @since 0.3 + */ + class GLOOX_API BookmarkStorage : public PrivateXML, public PrivateXMLHandler + { + public: + /** + * Constructs a new BookmarkStorage object. + * @param parent The ClientBase to use for communication. + */ + BookmarkStorage( ClientBase* parent ); + + /** + * Virtual destructor. + */ + virtual ~BookmarkStorage(); + + /** + * Use this function to store a number of URL and conference bookmarks on the server. + * Make sure you store the whole set of bookmarks, not a 'delta'. + * @param bList A list of URLs to store. + * @param cList A list of conferences to store. + */ + void storeBookmarks( const BookmarkList& bList, const ConferenceList& cList ); + + /** + * Use this function to initiate retrieval of bookmarks. Use registerBookmarkHandler() + * to register an object which will receive the lists of bookmarks. + */ + void requestBookmarks(); + + /** + * Use this function to register a BookmarkHandler. + * @param bmh The BookmarkHandler which shall receive retrieved bookmarks. + */ + void registerBookmarkHandler( BookmarkHandler* bmh ) + { m_bookmarkHandler = bmh; } + + /** + * Use this function to un-register the BookmarkHandler. + */ + void removeBookmarkHandler() + { m_bookmarkHandler = 0; } + + // reimplemented from PrivateXMLHandler + virtual void handlePrivateXML( const Tag* xml ); + + // reimplemented from PrivateXMLHandler + virtual void handlePrivateXMLResult( const std::string& uid, PrivateXMLResult pxResult ); + + private: + BookmarkHandler* m_bookmarkHandler; + }; + +} + +#endif // BOOKMARKSTORAGE_H__ diff --git a/libs/libgloox/bytestream.h b/libs/libgloox/bytestream.h new file mode 100644 index 0000000..b790f51 --- /dev/null +++ b/libs/libgloox/bytestream.h @@ -0,0 +1,180 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef BYTESTREAM_H__ +#define BYTESTREAM_H__ + +#include "jid.h" +#include "logsink.h" + +#include + +namespace gloox +{ + + class BytestreamDataHandler; + + /** + * @brief An abstraction of a single bytestream. + * + * Used as a base class for InBand Bytestreams as well as SOCKS5 Bytestreams. + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Bytestream + { + public: + /** + * Available stream types. + */ + enum StreamType + { + S5B, /**< SOCKS5 Bytestream */ + IBB /**< In-Band Bytestream */ + }; + + /** + * Creates a new Bytestream. + * @param type The stream type. + * @param logInstance A Logsink to use for logging. Obtain it from ClientBase::logInstance(). + * @param initiator The initiator of the stream (usually the sender). + * @param target The target of the stream (usually the receiver). + * @param sid The stream's ID. + */ + Bytestream( StreamType type, LogSink& logInstance, const JID& initiator, const JID& target, + const std::string& sid ) + : m_handler( 0 ), m_logInstance( logInstance ), m_initiator( initiator ), m_target( target ), + m_type( type ), m_sid( sid ), m_open( false ) + {} + + /** + * Virtual destructor. + */ + virtual ~Bytestream() {} + + /** + * Returns whether the bytestream is open, that is, accepted by both parties and ready + * to send/receive data. + * @return Whether or not the bytestream is open. + */ + bool isOpen() const { return m_open; } + + /** + * This function starts the connection process. + * @return @b True if a connection to a remote entity could be established, @b false + * otherwise. + * @note If @b false is returned you should pass this Bytestream object + * to SIProfileFT::dispose() for deletion. + * @note Make sure you have a BytestreamDataHandler registered (using + * registerBytestreamDataHandler()) before calling this function. + */ + virtual bool connect() = 0; + + /** + * Closes the bytestream. + */ + virtual void close() = 0; + + /** + * Use this function to send a chunk of data over an open bytestream. + * If the stream is not open or has been closed again + * (by the remote entity or locally), nothing is sent and @b false is returned. + * This function does any base64 encoding for you, if necessary. + * @param data The block of data to send. + * @return @b True if the data has been sent (no guarantee of receipt), @b false + * in case of an error. + */ + virtual bool send( const std::string& data ) = 0; + + /** + * Call this function repeatedly to receive data. You should even do this + * if you use the bytestream to merely @b send data. May be a NOOP, depending on the actual + * stream type. + * @param timeout The timeout to use for select in microseconds. Default of -1 means blocking. + * @return The state of the connection. + */ + virtual ConnectionError recv( int timeout = -1 ) = 0; + + /** + * Lets you retrieve the stream's ID. + * @return The stream's ID. + */ + const std::string& sid() const { return m_sid; } + + /** + * Returns the stream's type. + * @return The stream's type. + */ + StreamType type() const { return m_type; } + + /** + * Returns the target entity's JID. If this bytestream is remote-initiated, this is + * the local JID. If it is local-initiated, this is the remote entity's JID. + * @return The target's JID. + */ + const JID& target() const { return m_target; } + + /** + * Returns the initiating entity's JID. If this bytestream is remote-initiated, this is + * the remote entity's JID. If it is local-initiated, this is the local JID. + * @return The initiator's JID. + */ + const JID& initiator() const { return m_initiator; } + + /** + * Use this function to register an object that will receive any notifications from + * the Bytestream instance. Only one BytestreamDataHandler can be registered + * at any one time. + * @param bdh The BytestreamDataHandler-derived object to receive notifications. + */ + void registerBytestreamDataHandler( BytestreamDataHandler* bdh ) + { m_handler = bdh; } + + /** + * Removes the registered BytestreamDataHandler. + */ + void removeBytestreamDataHandler() + { m_handler = 0; } + + protected: + /** A handler for incoming data and open/close events. */ + BytestreamDataHandler* m_handler; + + /** A LogSink instance to use for logging. */ + const LogSink& m_logInstance; + + /** The initiator's JID. */ + const JID m_initiator; + + /** The target's JID. */ + const JID m_target; + + /** The stream type. */ + StreamType m_type; + + /** The stream ID. */ + std::string m_sid; + + /** Indicates whether or not the stream is open. */ + bool m_open; + + private: + Bytestream& operator=( const Bytestream& ); + + }; + +} + +#endif // BYTESTREAM_H__ diff --git a/libs/libgloox/bytestreamdatahandler.h b/libs/libgloox/bytestreamdatahandler.h new file mode 100644 index 0000000..8f0ff0a --- /dev/null +++ b/libs/libgloox/bytestreamdatahandler.h @@ -0,0 +1,81 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef BYTESTREAMDATAHANDLER_H__ +#define BYTESTREAMDATAHANDLER_H__ + +#include "macros.h" + +#include + +namespace gloox +{ + + class Bytestream; + class IQ; + + /** + * @brief A virtual interface that allows implementors to receive data + * sent over a SOCKS5 Bytestream as defined in XEP-0066, or an In-Band Bytestream + * as defined in XEP-0047. You'll also need it for sending of data. + * + * An BytestreamDataHandler is registered with a Bytestream. + * + * See SIProfileFT for more information regarding file transfer. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API BytestreamDataHandler + { + public: + /** + * Virtual destructor. + */ + virtual ~BytestreamDataHandler() {} + + /** + * Reimplement this function to receive data which is sent over the bytestream. + * The data received here is (probably) only a single chunk of the complete data (depending + * on the amount of data you want to send). + * @param bs The bytestream. + * @param data The actual stream payload. + */ + virtual void handleBytestreamData( Bytestream* bs, const std::string& data ) = 0; + + /** + * Notifies about an error occuring while using a bytestream. + * When this handler is called the stream has already been closed. + * @param bs The bytestream. + * @param iq The error stanza. + */ + virtual void handleBytestreamError( Bytestream* bs, const IQ& iq ) = 0; + + /** + * Notifies the handler that the given bytestream has been acknowledged + * and is ready to send/receive data. + * @param bs The opened bytestream. + */ + virtual void handleBytestreamOpen( Bytestream* bs ) = 0; + + /** + * Notifies the handler that the given bytestream has been closed. + * @param bs The closed bytestream. + */ + virtual void handleBytestreamClose( Bytestream* bs ) = 0; + + }; + +} + +#endif // BYTESTREAMDATAHANDLER_H__ diff --git a/libs/libgloox/bytestreamhandler.h b/libs/libgloox/bytestreamhandler.h new file mode 100644 index 0000000..a083f5b --- /dev/null +++ b/libs/libgloox/bytestreamhandler.h @@ -0,0 +1,90 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef BYTESTREAMHANDLER_H__ +#define BYTESTREAMHANDLER_H__ + +#include "macros.h" +#include "jid.h" +#include "bytestream.h" +#include "iq.h" + +namespace gloox +{ + + /** + * @brief A virtual interface that allows to receive new incoming Bytestream requests + * from remote entities. + * + * You should not need to use this interface directly. + * + * See SIProfileFT on how to implement file transfer in general. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API BytestreamHandler + { + public: + /** + * Virtual destructor. + */ + virtual ~BytestreamHandler() {} + + /** + * Notifies the implementor of a new incoming bytestream request. + * You have to call either + * BytestreamManager::acceptBytestream() or + * BytestreamManager::rejectBytestream(), to accept or reject the bytestream + * request, respectively. + * @param sid The bytestream's id, to be passed to BytestreamManager::acceptBytestream() + * and BytestreamManager::rejectBytestream(), respectively. + * @param from The remote initiator of the bytestream request. + */ + virtual void handleIncomingBytestreamRequest( const std::string& sid, const JID& from ) = 0; + + /** + * Notifies the implementor of a new incoming bytestream. The bytestream is not yet ready to + * send data. + * To initialize the bytestream and to prepare it for data transfer, register a + * BytestreamDataHandler with it and call its connect() method. + * To not block your application while the data transfer lasts, you most + * likely want to put the bytestream into its own thread or process (before calling connect() on it). + * It is safe to do so without additional synchronization. + * When you are finished using the bytestream, use SIProfileFT::dispose() to get rid of it. + * @param bs The bytestream. + */ + virtual void handleIncomingBytestream( Bytestream* bs ) = 0; + + /** + * Notifies the implementor of successful establishing of an outgoing bytestream request. + * The stream has been accepted by the remote entity and is ready to send data. + * The BytestreamHandler does @b not become the owner of the Bytestream object. + * Use SIProfileFT::dispose() to get rid of the bytestream object after it has been closed. + * @param bs The new bytestream. + */ + virtual void handleOutgoingBytestream( Bytestream* bs ) = 0; + + /** + * Notifies the handler of errors occuring when a bytestream was requested. + * For example, if the remote entity does not implement SOCKS5 bytestreams. + * @param iq The error stanza. + * @param sid The request's SID. + */ + virtual void handleBytestreamError( const IQ& iq, const std::string& sid ) = 0; + + }; + +} + +#endif // BYTESTREAMHANDLER_H__ diff --git a/libs/libgloox/capabilities.cpp b/libs/libgloox/capabilities.cpp new file mode 100644 index 0000000..0bdbc00 --- /dev/null +++ b/libs/libgloox/capabilities.cpp @@ -0,0 +1,183 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "capabilities.h" + +#include "base64.h" +#include "disco.h" +#include "dataform.h" +#include "sha.h" +#include "tag.h" + +namespace gloox +{ + + Capabilities::Capabilities( Disco* disco ) + : StanzaExtension( ExtCaps ), m_disco( disco ), m_node( GLOOX_CAPS_NODE ), + m_hash( "sha-1" ), m_valid( false ) + { + if( m_disco ) + m_valid = true; + } + + Capabilities::Capabilities( const Tag* tag ) + : StanzaExtension( ExtCaps ), m_disco( 0 ), m_valid( false ) + { + if( !tag || tag->name() != "c" || !tag->hasAttribute( XMLNS, XMLNS_CAPS ) + || !tag->hasAttribute( "node" ) || !tag->hasAttribute( "ver" ) ) + return; + + m_node = tag->findAttribute( "node" ); + m_ver = tag->findAttribute( "ver" ); + m_hash = tag->findAttribute( "hash" ); + m_valid = true; + } + + Capabilities::~Capabilities() + { + if( m_disco ) + m_disco->removeNodeHandlers( const_cast( this ) ); + } + + const std::string Capabilities::ver() const + { + if( !m_disco ) + return m_ver; + + SHA sha; + sha.feed( generate( m_disco->identities(), m_disco->features( true ), m_disco->form() ) ); + const std::string& hash = Base64::encode64( sha.binary() ); + m_disco->removeNodeHandlers( const_cast( this ) ); + m_disco->registerNodeHandler( const_cast( this ), m_node + '#' + hash ); + return hash; + } + + std::string Capabilities::generate( const Disco::IdentityList& il, const StringList& features, const DataForm* form ) + { + StringList sl; + Disco::IdentityList::const_iterator it = il.begin(); + for( ; it != il.end(); ++it ) + { + std::string id = (*it)->category(); + id += '/'; + id += (*it)->type(); + id += '/'; + // FIXME add xml:lang caps here. see XEP-0115 Section 5 + id += '/'; + id += (*it)->name(); + sl.push_back( id ); + } + sl.sort(); + + std::string s; + StringList::const_iterator it2 = sl.begin(); + for( ; it2 != sl.end(); ++it2 ) + { + s += (*it2); + s += '<'; + } + + StringList f = features; + f.sort(); + it2 = f.begin(); + for( ; it2 != f.end(); ++it2 ) + { + s += (*it2); + s += '<'; + } + + if( form ) + { + DataForm::FieldList::const_iterator it3 = form->fields().begin(); + typedef std::map MapSSL; + + MapSSL m; + for( ; it3 != form->fields().end(); ++it3 ) + { + if( (*it3)->name() == "FORM_TYPE" ) + { + s += (*it3)->value(); + s += '<'; + } + else + m.insert( std::make_pair( (*it3)->name(), (*it3)->values() ) ); + } + + MapSSL::iterator it4 = m.begin(); + for( ; it4 != m.end(); ++it4 ) + { + s += it4->first; + s += '<'; + it2 = it4->second.begin(); + for( ; it2 != it4->second.end(); ++it2 ) + { + s += (*it2); + s += '<'; + } + } + } + return s; + } + + std::string Capabilities::generate( const Disco::Info* info ) + { + return info ? generate( info->identities(), info->features(), info->form() ) : EmptyString; + } + + std::string Capabilities::generate( const Disco* disco ) + { + return disco ? generate( disco->identities(), disco->features(), disco->form() ) : EmptyString; + } + + const std::string& Capabilities::filterString() const + { + static const std::string filter = "/presence/c[@xmlns='" + XMLNS_CAPS + "']"; + return filter; + } + + Tag* Capabilities::tag() const + { + if( !m_valid || m_node.empty() ) + return 0; + + Tag* t = new Tag( "c" ); + t->setXmlns( XMLNS_CAPS ); + t->addAttribute( "hash", m_hash ); + t->addAttribute( "node", m_node ); + t->addAttribute( "ver", ver() ); + return t; + } + + StringList Capabilities::handleDiscoNodeFeatures( const JID&, const std::string& ) + { + return m_disco->features(); + } + + Disco::IdentityList Capabilities::handleDiscoNodeIdentities( const JID&, const std::string& ) + { + const Disco::IdentityList& il = m_disco->identities(); + Disco::IdentityList ret; + Disco::IdentityList::const_iterator it = il.begin(); + for( ; it != il.end(); ++it ) + { + ret.push_back( new Disco::Identity( *(*it) ) ); + } + return ret; + } + + Disco::ItemList Capabilities::handleDiscoNodeItems( const JID&, const JID&, const std::string& ) + { + return Disco::ItemList(); + } + +} diff --git a/libs/libgloox/capabilities.h b/libs/libgloox/capabilities.h new file mode 100644 index 0000000..a5d63e3 --- /dev/null +++ b/libs/libgloox/capabilities.h @@ -0,0 +1,132 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef CAPABILITIES_H__ +#define CAPABILITIES_H__ + +#include "disconodehandler.h" +#include "stanzaextension.h" +#include "tag.h" + +#include + +namespace gloox +{ + + class Disco; + class Tag; + + /** + * @brief This is an implementation of XEP-0115 (Entity Capabilities). + * + * XEP Version: 1.5-15 + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Capabilities : public StanzaExtension, public DiscoNodeHandler + { + + public: + /** + * Constructs a new object and fills it according to the parameters. + * @param disco The current Client's Disco object. + */ + Capabilities( Disco* disco ); + + /** + * Constructs a new object from the given Tag. + * @param tag The Tag to parse. + */ + Capabilities( const Tag* tag = 0 ); + + /** + * Virtual Destructor. + */ + virtual ~Capabilities(); + + /** + * Returns the client's identifying node. + * @return The node. + */ + const std::string& node() const { return m_node; } + + /** + * Sets the client's identifying node. + * @param node The node. + */ + void setNode( const std::string& node ) { m_node = node; } + + /** + * Returns the client's identifying ver string. + * @return The ver string. + */ + const std::string ver() const; + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Capabilities( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new Capabilities( *this ); + } + + // reimplemented from DiscoNodeHandler + virtual StringList handleDiscoNodeFeatures( const JID& from, const std::string& node ); + + // reimplemented from DiscoNodeHandler + virtual Disco::IdentityList handleDiscoNodeIdentities( const JID& from, + const std::string& node ); + + // reimplemented from DiscoNodeHandler + virtual Disco::ItemList handleDiscoNodeItems( const JID& from, const JID& to, + const std::string& node = EmptyString ); + + private: + /** + * Returns the hash function used for creating the caps info. + * @return The current hash function's name. + */ + const std::string& hash() const { return m_hash; } + + /** + * Use this function to set the hash function to use. + * @param hash The hash function. + * @todo Convert to using an enum and make public. + */ + void setHash( const std::string& hash ) { m_hash = hash; } + + static std::string generate( const Disco::IdentityList& identities, + const StringList& features, const DataForm* form = 0 ); + static std::string generate( const Disco::Info* info ); + static std::string generate( const Disco* disco ); + + Disco* m_disco; + std::string m_node; + std::string m_hash; + std::string m_ver; + bool m_valid; + }; + +} + +#endif // CAPABILITIES_H__ diff --git a/libs/libgloox/chatstate.cpp b/libs/libgloox/chatstate.cpp new file mode 100644 index 0000000..8a2a400 --- /dev/null +++ b/libs/libgloox/chatstate.cpp @@ -0,0 +1,60 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "chatstate.h" +#include "tag.h" +#include "util.h" + +namespace gloox +{ + + /* chat state type values */ + static const char* stateValues [] = { + "active", + "composing", + "paused", + "inactive", + "gone" + }; + + static inline ChatStateType chatStateType( const std::string& type ) + { + return (ChatStateType)util::lookup2( type, stateValues ); + } + + ChatState::ChatState( const Tag* tag ) + : StanzaExtension( ExtChatState ) + { + if( tag ) + m_state = chatStateType( tag->name() ); + } + + const std::string& ChatState::filterString() const + { + static const std::string filter = + "/message/active[@xmlns='" + XMLNS_CHAT_STATES + "']" + "|/message/composing[@xmlns='" + XMLNS_CHAT_STATES + "']" + "|/message/paused[@xmlns='" + XMLNS_CHAT_STATES + "']" + "|/message/inactive[@xmlns='" + XMLNS_CHAT_STATES + "']" + "|/message/gone[@xmlns='" + XMLNS_CHAT_STATES + "']"; + return filter; + } + + Tag* ChatState::tag() const + { + if( m_state == ChatStateInvalid ) + return 0; + + return new Tag( util::lookup2( m_state, stateValues ), XMLNS, XMLNS_CHAT_STATES ); + } + +} diff --git a/libs/libgloox/chatstate.h b/libs/libgloox/chatstate.h new file mode 100644 index 0000000..6f1c426 --- /dev/null +++ b/libs/libgloox/chatstate.h @@ -0,0 +1,87 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef CHATSTATE_H__ +#define CHATSTATE_H__ + +#include "gloox.h" +#include "stanzaextension.h" + +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief An implementation of Chat State Notifications (XEP-0085) as a StanzaExtension. + * + * @author Vincent Thomasset + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API ChatState : public StanzaExtension + { + public: + + /** + * Constructs a new object from the given Tag. + * @param tag A Tag to parse. + */ + ChatState( const Tag* tag ); + + /** + * Constructs a new object of the given type. + * @param state The chat state. + */ + ChatState( ChatStateType state ) + : StanzaExtension( ExtChatState ), m_state( state ) + {} + + /** + * Virtual destructor. + */ + virtual ~ChatState() {} + + /** + * Returns the object's state. + * @return The object's state. + */ + ChatStateType state() const { return m_state; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new ChatState( tag ); + } + + // reimplemented from StanzaExtension + Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new ChatState( *this ); + } + + private: + ChatStateType m_state; + + }; + +} + +#endif // CHATSTATE_H__ diff --git a/libs/libgloox/chatstatefilter.cpp b/libs/libgloox/chatstatefilter.cpp new file mode 100644 index 0000000..8644689 --- /dev/null +++ b/libs/libgloox/chatstatefilter.cpp @@ -0,0 +1,65 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "chatstatefilter.h" +#include "chatstatehandler.h" +#include "messageeventhandler.h" +#include "messagesession.h" +#include "message.h" +#include "chatstate.h" + +namespace gloox +{ + + ChatStateFilter::ChatStateFilter( MessageSession* parent ) + : MessageFilter( parent ), m_chatStateHandler( 0 ), m_lastSent( ChatStateGone ), + m_enableChatStates( true ) + { + } + + ChatStateFilter::~ChatStateFilter() + { + } + + void ChatStateFilter::filter( Message& msg ) + { + if( m_enableChatStates && m_chatStateHandler ) + { + const ChatState* state = msg.findExtension( ExtChatState ); + + m_enableChatStates = state && state->state() != ChatStateInvalid; + if( m_enableChatStates && msg.body().empty() ) + m_chatStateHandler->handleChatState( msg.from(), state->state() ); + } + } + + void ChatStateFilter::setChatState( ChatStateType state ) + { + if( !m_enableChatStates || state == m_lastSent || state == ChatStateInvalid ) + return; + + Message m( Message::Chat, m_parent->target() ); + m.addExtension( new ChatState( state ) ); + + m_lastSent = state; + + send( m ); + } + + void ChatStateFilter::decorate( Message& msg ) + { + if( m_enableChatStates ) + msg.addExtension( new ChatState( ChatStateActive ) ); + } + +} diff --git a/libs/libgloox/chatstatefilter.h b/libs/libgloox/chatstatefilter.h new file mode 100644 index 0000000..b0f5a6b --- /dev/null +++ b/libs/libgloox/chatstatefilter.h @@ -0,0 +1,102 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef CHATSTATEFILTER_H__ +#define CHATSTATEFILTER_H__ + +#include "messagefilter.h" +#include "gloox.h" + +namespace gloox +{ + + class Tag; + class ChatStateHandler; + class MessageSession; + class Message; + + /** + * @brief This class adds Chat State Notifications (XEP-0085) support to a MessageSession. + * + * This implementation of Chat States is fully transparent to the user of the class. + * If the remote entity does not request chat states, ChatStateFilter will not send + * any, even if the user requests it. (This is required by the protocol specification.) + * You MUST annouce this capability by use of Disco (associated namespace is XMLNS_CHAT_STATES). + * (This is also required by the protocol specification.) + * + * @author Jakob Schroeter + * @since 0.8 + */ + class GLOOX_API ChatStateFilter : public MessageFilter + { + public: + /** + * Contstructs a new Chat State filter for a MessageSession. + * @param parent The MessageSession to decorate. + */ + ChatStateFilter( MessageSession* parent ); + + /** + * Virtual destructor. + */ + virtual ~ChatStateFilter(); + + /** + * Use this function to set a chat state as defined in XEP-0085. + * @note The Spec states that Chat States shall not be sent to an entity + * which did not request them. Reasonable effort is taken in this function to + * avoid spurious state sending. You should be safe to call this even if Message + * Events were not requested by the remote entity. However, + * calling setChatState( CHAT_STATE_COMPOSING ) for every keystroke still is + * discouraged. ;) + * @param state The state to set. + */ + void setChatState( ChatStateType state ); + + /** + * The ChatStateHandler registered here will receive Chat States according + * to XEP-0085. + * @param csh The ChatStateHandler to register. + */ + void registerChatStateHandler( ChatStateHandler* csh ) + { m_chatStateHandler = csh; } + + /** + * This function clears the internal pointer to the ChatStateHandler. + * Chat States will not be delivered anymore after calling this function until another + * ChatStateHandler is registered. + */ + void removeChatStateHandler() + { m_chatStateHandler = 0; } + + // reimplemented from MessageFilter + virtual void decorate( Message& msg ); + + // reimplemented from MessageFilter + virtual void filter( Message& msg ); + + protected: + /** A handler for incoming chat state changes. */ + ChatStateHandler* m_chatStateHandler; + + /** Holds the state sent last. */ + ChatStateType m_lastSent; + + /** Indicates whether or not chat states are currently enabled. */ + bool m_enableChatStates; + + }; + +} + +#endif // CHATSTATEFILTER_H__ diff --git a/libs/libgloox/chatstatehandler.h b/libs/libgloox/chatstatehandler.h new file mode 100644 index 0000000..8c302c8 --- /dev/null +++ b/libs/libgloox/chatstatehandler.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef CHATSTATEHANDLER_H__ +#define CHATSTATEHANDLER_H__ + +#include "gloox.h" + +namespace gloox +{ + + class JID; + + /** + * @brief A virtual interface that enables an object to be notified about + * a remote entity's Chat States (XEP-0085). + * + * @author Jakob Schroeter + * @since 0.8 + */ + class GLOOX_API ChatStateHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~ChatStateHandler() {} + + /** + * Notifies the ChatStateHandler that a different chat state has been set by the remote + * contact. + * @param from The originator of the Event. + * @param state The chat state set by the remote entity. + */ + virtual void handleChatState( const JID& from, ChatStateType state ) = 0; + + }; + +} + +#endif // CHATSTATEHANDLER_H__ diff --git a/libs/libgloox/client.cpp b/libs/libgloox/client.cpp new file mode 100644 index 0000000..6df569d --- /dev/null +++ b/libs/libgloox/client.cpp @@ -0,0 +1,603 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "config.h" + +#include "client.h" +#include "capabilities.h" +#include "rostermanager.h" +#include "disco.h" +#include "error.h" +#include "logsink.h" +#include "nonsaslauth.h" +#include "prep.h" +#include "stanzaextensionfactory.h" +#include "stanzaextension.h" +#include "tag.h" +#include "tlsbase.h" +#include "util.h" + +#if !defined( _WIN32 ) && !defined( _WIN32_WCE ) +# include +#endif + +#include + +namespace gloox +{ + + // ---- Client::ResourceBind ---- + Client::ResourceBind::ResourceBind( const std::string& resource, bool bind ) + : StanzaExtension( ExtResourceBind ), m_jid( JID() ), m_bind( bind ) + { + prep::resourceprep( resource, m_resource ); + m_valid = true; + } + + Client::ResourceBind::ResourceBind( const Tag* tag ) + : StanzaExtension( ExtResourceBind ), m_resource( EmptyString ), m_bind( true ) + { + if( !tag ) + return; + + if( tag->name() == "unbind" ) + m_bind = false; + else if( tag->name() == "bind" ) + m_bind = true; + else + return; + + if( tag->hasChild( "jid" ) ) + m_jid.setJID( tag->findChild( "jid" )->cdata() ); + else if( tag->hasChild( "resource" ) ) + m_resource = tag->findChild( "resource" )->cdata(); + + m_valid = true; + } + + Client::ResourceBind::~ResourceBind() + { + } + + const std::string& Client::ResourceBind::filterString() const + { + static const std::string filter = "/iq/bind[@xmlns='" + XMLNS_STREAM_BIND + "']" + "|/iq/unbind[@xmlns='" + XMLNS_STREAM_BIND + "']"; + return filter; + } + + Tag* Client::ResourceBind::tag() const + { + if( !m_valid ) + return 0; + + Tag* t = new Tag( m_bind ? "bind" : "unbind" ); + t->setXmlns( XMLNS_STREAM_BIND ); + + if( m_bind && m_resource.empty() && m_jid ) + new Tag( t, "jid", m_jid.full() ); + else + new Tag( t, "resource", m_resource ); + + return t; + } + // ---- ~Client::ResourceBind ---- + + // ---- Client::SessionCreation ---- + Tag* Client::SessionCreation::tag() const + { + Tag* t = new Tag( "session" ); + t->setXmlns( XMLNS_STREAM_SESSION ); + return t; + } + // ---- Client::SessionCreation ---- + + // ---- Client ---- + Client::Client( const std::string& server ) + : ClientBase( XMLNS_CLIENT, server ), + m_rosterManager( 0 ), m_auth( 0 ), + m_presence( Presence::Available, JID() ), m_resourceBound( false ), + m_forceNonSasl( false ), m_manageRoster( true ), + m_streamFeatures( 0 ) + { + m_jid.setServer( server ); + init(); + } + + Client::Client( const JID& jid, const std::string& password, int port ) + : ClientBase( XMLNS_CLIENT, password, EmptyString, port ), + m_rosterManager( 0 ), m_auth( 0 ), + m_presence( Presence::Available, JID() ), m_resourceBound( false ), + m_forceNonSasl( false ), m_manageRoster( true ), + m_streamFeatures( 0 ) + { + m_jid = jid; + m_server = m_jid.serverRaw(); + init(); + } + + Client::~Client() + { + delete m_rosterManager; + delete m_auth; + } + + void Client::init() + { + m_rosterManager = new RosterManager( this ); + m_disco->setIdentity( "client", "bot" ); + registerStanzaExtension( new ResourceBind( 0 ) ); + registerStanzaExtension( new Capabilities() ); + m_presenceExtensions.push_back( new Capabilities( m_disco ) ); + } + + void Client::setUsername( const std::string &username ) + { + m_jid.setUsername( username ); + } + + bool Client::handleNormalNode( Tag* tag ) + { + if( tag->name() == "features" && tag->xmlns() == XMLNS_STREAM ) + { + m_streamFeatures = getStreamFeatures( tag ); + + if( m_tls == TLSRequired && !m_encryptionActive + && ( !m_encryption || !( m_streamFeatures & StreamFeatureStartTls ) ) ) + { + logInstance().err( LogAreaClassClient, "Client is configured to require" + " TLS but either the server didn't offer TLS or" + " TLS support is not compiled in." ); + disconnect( ConnTlsNotAvailable ); + } + else if( m_tls > TLSDisabled && m_encryption && !m_encryptionActive + && ( m_streamFeatures & StreamFeatureStartTls ) ) + { + notifyStreamEvent( StreamEventEncryption ); + startTls(); + } + else if( m_compress && m_compression && !m_compressionActive + && ( m_streamFeatures & StreamFeatureCompressZlib ) ) + { + notifyStreamEvent( StreamEventCompression ); + logInstance().warn( LogAreaClassClient, "The server offers compression, but negotiating Compression at this stage is not recommended. See XEP-0170 for details. We'll continue anyway." ); + negotiateCompression( StreamFeatureCompressZlib ); + } + else if( m_sasl ) + { + if( m_authed ) + { + if( m_streamFeatures & StreamFeatureBind ) + { + notifyStreamEvent( StreamEventResourceBinding ); + bindResource( resource() ); + } + } + else if( !username().empty() && !password().empty() ) + { + if( !login() ) + { + logInstance().err( LogAreaClassClient, "The server doesn't support" + " any auth mechanisms we know about" ); + disconnect( ConnNoSupportedAuth ); + } + } + else if( !m_clientCerts.empty() && !m_clientKey.empty() + && m_streamFeatures & SaslMechExternal && m_availableSaslMechs & SaslMechExternal ) + { + notifyStreamEvent( StreamEventAuthentication ); + startSASL( SaslMechExternal ); + } +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + else if( m_streamFeatures & SaslMechGssapi && m_availableSaslMechs & SaslMechGssapi ) + { + notifyStreamEvent( StreamEventAuthentication ); + startSASL( SaslMechGssapi ); + } + else if( m_streamFeatures & SaslMechNTLM && m_availableSaslMechs & SaslMechNTLM ) + { + notifyStreamEvent( StreamEventAuthentication ); + startSASL( SaslMechNTLM ); + } +#endif + else if( m_streamFeatures & SaslMechAnonymous + && m_availableSaslMechs & SaslMechAnonymous ) + { + notifyStreamEvent( StreamEventAuthentication ); + startSASL( SaslMechAnonymous ); + } + else + { + notifyStreamEvent( StreamEventFinished ); + connected(); + } + } + else if( m_compress && m_compression && !m_compressionActive + && ( m_streamFeatures & StreamFeatureCompressZlib ) ) + { + notifyStreamEvent( StreamEventCompression ); + negotiateCompression( StreamFeatureCompressZlib ); + } +// else if( ( m_streamFeatures & StreamFeatureCompressDclz ) +// && m_connection->initCompression( StreamFeatureCompressDclz ) ) +// { +// negotiateCompression( StreamFeatureCompressDclz ); +// } + else if( m_streamFeatures & StreamFeatureIqAuth ) + { + notifyStreamEvent( StreamEventAuthentication ); + nonSaslLogin(); + } + else + { + logInstance().err( LogAreaClassClient, "fallback: the server doesn't " + "support any auth mechanisms we know about" ); + disconnect( ConnNoSupportedAuth ); + } + } + else + { + const std::string& name = tag->name(), + xmlns = tag->findAttribute( XMLNS ); + if( name == "proceed" && xmlns == XMLNS_STREAM_TLS ) + { + logInstance().dbg( LogAreaClassClient, "starting TLS handshake..." ); + + if( m_encryption ) + { + m_encryptionActive = true; + m_encryption->handshake(); + } + } + else if( name == "failure" ) + { + if( xmlns == XMLNS_STREAM_TLS ) + { + logInstance().err( LogAreaClassClient, "TLS handshake failed (server-side)!" ); + disconnect( ConnTlsFailed ); + } + else if( xmlns == XMLNS_COMPRESSION ) + { + logInstance().err( LogAreaClassClient, "Stream compression init failed!" ); + disconnect( ConnCompressionFailed ); + } + else if( xmlns == XMLNS_STREAM_SASL ) + { + logInstance().err( LogAreaClassClient, "SASL authentication failed!" ); + processSASLError( tag ); + disconnect( ConnAuthenticationFailed ); + } + } + else if( name == "compressed" && xmlns == XMLNS_COMPRESSION ) + { + logInstance().dbg( LogAreaClassClient, "Stream compression initialized" ); + m_compressionActive = true; + header(); + } + else if( name == "challenge" && xmlns == XMLNS_STREAM_SASL ) + { + logInstance().dbg( LogAreaClassClient, "Processing SASL challenge" ); + processSASLChallenge( tag->cdata() ); + } + else if( name == "success" && xmlns == XMLNS_STREAM_SASL ) + { + logInstance().dbg( LogAreaClassClient, "SASL authentication successful" ); + processSASLSuccess(); + setAuthed( true ); + header(); + } + else + return false; + } + + return true; + } + + int Client::getStreamFeatures( Tag* tag ) + { + if( tag->name() != "features" || tag->xmlns() != XMLNS_STREAM ) + return 0; + + int features = 0; + + if( tag->hasChild( "starttls", XMLNS, XMLNS_STREAM_TLS ) ) + features |= StreamFeatureStartTls; + + if( tag->hasChild( "mechanisms", XMLNS, XMLNS_STREAM_SASL ) ) + features |= getSaslMechs( tag->findChild( "mechanisms" ) ); + + if( tag->hasChild( "bind", XMLNS, XMLNS_STREAM_BIND ) ) + features |= StreamFeatureBind; + + if( tag->hasChild( "unbind", XMLNS, XMLNS_STREAM_BIND ) ) + features |= StreamFeatureUnbind; + + if( tag->hasChild( "session", XMLNS, XMLNS_STREAM_SESSION ) ) + features |= StreamFeatureSession; + + if( tag->hasChild( "auth", XMLNS, XMLNS_STREAM_IQAUTH ) ) + features |= StreamFeatureIqAuth; + + if( tag->hasChild( "register", XMLNS, XMLNS_STREAM_IQREGISTER ) ) + features |= StreamFeatureIqRegister; + + if( tag->hasChild( "compression", XMLNS, XMLNS_STREAM_COMPRESS ) ) + features |= getCompressionMethods( tag->findChild( "compression" ) ); + + if( features == 0 ) + features = StreamFeatureIqAuth; + + return features; + } + + int Client::getSaslMechs( Tag* tag ) + { + int mechs = SaslMechNone; + + const std::string mech = "mechanism"; + + if( tag->hasChildWithCData( mech, "DIGEST-MD5" ) ) + mechs |= SaslMechDigestMd5; + + if( tag->hasChildWithCData( mech, "PLAIN" ) ) + mechs |= SaslMechPlain; + + if( tag->hasChildWithCData( mech, "ANONYMOUS" ) ) + mechs |= SaslMechAnonymous; + + if( tag->hasChildWithCData( mech, "EXTERNAL" ) ) + mechs |= SaslMechExternal; + + if( tag->hasChildWithCData( mech, "GSSAPI" ) ) + mechs |= SaslMechGssapi; + + if( tag->hasChildWithCData( mech, "NTLM" ) ) + mechs |= SaslMechNTLM; + + return mechs; + } + + int Client::getCompressionMethods( Tag* tag ) + { + int meths = 0; + + if( tag->hasChildWithCData( "method", "zlib" ) ) + meths |= StreamFeatureCompressZlib; + + if( tag->hasChildWithCData( "method", "lzw" ) ) + meths |= StreamFeatureCompressDclz; + + return meths; + } + + bool Client::login() + { + bool retval = true; + + if( m_streamFeatures & SaslMechDigestMd5 && m_availableSaslMechs & SaslMechDigestMd5 + && !m_forceNonSasl ) + { + notifyStreamEvent( StreamEventAuthentication ); + startSASL( SaslMechDigestMd5 ); + } + else if( m_streamFeatures & SaslMechPlain && m_availableSaslMechs & SaslMechPlain + && !m_forceNonSasl ) + { + notifyStreamEvent( StreamEventAuthentication ); + startSASL( SaslMechPlain ); + } + else if( m_streamFeatures & StreamFeatureIqAuth || m_forceNonSasl ) + { + notifyStreamEvent( StreamEventAuthentication ); + nonSaslLogin(); + } + else + retval = false; + + return retval; + } + + void Client::handleIqIDForward( const IQ& iq, int context ) + { + switch( context ) + { + case CtxResourceUnbind: + // we don't store known resources anyway + break; + case CtxResourceBind: + processResourceBind( iq ); + break; + case CtxSessionEstablishment: + processCreateSession( iq ); + break; + default: + break; + } + } + + bool Client::bindOperation( const std::string& resource, bool bind ) + { + if( !( m_streamFeatures & StreamFeatureUnbind ) && m_resourceBound ) + return false; + + IQ iq( IQ::Set, JID(), getID() ); + iq.addExtension( new ResourceBind( resource, bind ) ); + + send( iq, this, bind ? CtxResourceBind : CtxResourceUnbind ); + return true; + } + + bool Client::selectResource( const std::string& resource ) + { + if( !( m_streamFeatures & StreamFeatureUnbind ) ) + return false; + + m_selectedResource = resource; + + return true; + } + + void Client::processResourceBind( const IQ& iq ) + { + switch( iq.subtype() ) + { + case IQ::Result: + { + const ResourceBind* rb = iq.findExtension( ExtResourceBind ); + if( !rb || !rb->jid() ) + { + notifyOnResourceBindError( 0 ); + break; + } + + m_jid = rb->jid(); + m_resourceBound = true; + m_selectedResource = m_jid.resource(); + notifyOnResourceBind( m_jid.resource() ); + + if( m_streamFeatures & StreamFeatureSession ) + createSession(); + else + connected(); + break; + } + case IQ::Error: + { + notifyOnResourceBindError( iq.error() ); + break; + } + default: + break; + } + } + + void Client::createSession() + { + notifyStreamEvent( StreamEventSessionCreation ); + IQ iq( IQ::Set, JID(), getID() ); + iq.addExtension( new SessionCreation() ); + send( iq, this, CtxSessionEstablishment ); + } + + void Client::processCreateSession( const IQ& iq ) + { + switch( iq.subtype() ) + { + case IQ::Result: + connected(); + break; + case IQ::Error: + notifyOnSessionCreateError( iq.error() ); + break; + default: + break; + } + } + + void Client::negotiateCompression( StreamFeature method ) + { + Tag* t = new Tag( "compress", XMLNS, XMLNS_COMPRESSION ); + + if( method == StreamFeatureCompressZlib ) + new Tag( t, "method", "zlib" ); + + if( method == StreamFeatureCompressDclz ) + new Tag( t, "method", "lzw" ); + + send( t ); + } + + void Client::setPresence( Presence::PresenceType pres, int priority, + const std::string& status ) + { + m_presence.setPresence( pres ); + m_presence.setPriority( priority ); + m_presence.addStatus( status ); + sendPresence( m_presence ); + } + + void Client::setPresence( const JID& to, Presence::PresenceType pres, int priority, + const std::string& status ) + { + Presence p( pres, to, status, priority ); + sendPresence( p ); + } + + void Client::sendPresence( Presence& pres ) + { + if( state() < StateConnected ) + return; + + send( pres ); + } + + void Client::disableRoster() + { + m_manageRoster = false; + delete m_rosterManager; + m_rosterManager = 0; + } + + void Client::nonSaslLogin() + { + if( !m_auth ) + m_auth = new NonSaslAuth( this ); + m_auth->doAuth( m_sid ); + } + + void Client::connected() + { + if( m_authed ) + { + if( m_manageRoster ) + { + notifyStreamEvent( StreamEventRoster ); + m_rosterManager->fill(); + } + else + rosterFilled(); + } + else + { + notifyStreamEvent( StreamEventFinished ); + notifyOnConnect(); + } + } + + void Client::rosterFilled() + { + sendPresence( m_presence ); + notifyStreamEvent( StreamEventFinished ); + notifyOnConnect(); + } + + void Client::disconnect() + { + disconnect( ConnUserDisconnected ); + } + + void Client::disconnect( ConnectionError reason ) + { + m_resourceBound = false; + m_authed = false; + m_streamFeatures = 0; + ClientBase::disconnect( reason ); + } + + void Client::cleanup() + { + m_authed = false; + m_resourceBound = false; + m_streamFeatures = 0; + } + +} diff --git a/libs/libgloox/client.h b/libs/libgloox/client.h new file mode 100644 index 0000000..5eb53f3 --- /dev/null +++ b/libs/libgloox/client.h @@ -0,0 +1,441 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef CLIENT_H__ +#define CLIENT_H__ + +#include "clientbase.h" +#include "presence.h" + +#include + +namespace gloox +{ + + class Capabilities; + class RosterManager; + class NonSaslAuth; + class IQ; + + /** + * @brief This class implements a basic Jabber Client. + * + * It supports @ref sasl_auth as well as TLS (Encryption), which can be + * switched on/off separately. They are used automatically if the server supports them. + * + * To use, create a new Client instance and feed it connection credentials, either in the Constructor or + * afterwards using the setters. You should then register packet handlers implementing the corresponding + * Interfaces (ConnectionListener, PresenceHandler, MessageHandler, IqHandler, SubscriptionHandler), + * and call @ref connect() to establish the connection to the server.
+ * + * @note While the MessageHandler interface is still available (and will be in future versions) + * it is now recommended to use the new @link gloox::MessageSession MessageSession @endlink for any + * serious messaging. + * + * Simple usage example: + * @code + * using namespace gloox; + * + * void TestProg::doIt() + * { + * Client* j = new Client( "user@server/resource", "password" ); + * j->registerPresenceHandler( this ); + * j->disco()->setVersion( "TestProg", "1.0" ); + * j->disco()->setIdentity( "client", "bot" ); + * j->connect(); + * } + * + * virtual void TestProg::presenceHandler( Presence* presence ) + * { + * // handle incoming presence packets here + * } + * @endcode + * + * However, you can skip the presence handling stuff if you make use of the RosterManager. + * + * By default, the library handles a few (incoming) IQ namespaces on the application's behalf. These + * include: + * @li jabber:iq:roster: by default the server-side roster is fetched and handled. Use + * @ref rosterManager() and @ref RosterManager to interact with the Roster. + * @li XEP-0092 (Software Version): If no version is specified, a name of "based on gloox" with + * gloox's current version is announced. + * @li XEP-0030 (Service Discovery): All supported/available services are announced. No items are + * returned. + * @note As of gloox 0.9, by default a priority of 0 is sent along with the initial presence. + * @note As of gloox 0.9, initial presence is automatically sent. Presence: available, Priority: 0. + * To disable sending of initial Presence use setPresence() with a value of Unavailable + * prior to connecting. + * + * @section sasl_auth SASL Authentication + * + * Besides the simple, IQ-based authentication (XEP-0078), gloox supports several SASL (Simple + * Authentication and Security Layer, RFC 2222) authentication mechanisms. + * @li DIGEST-MD5: This mechanism is preferred over all other mechanisms if username and password are + * provided to the Client instance. It is secure even without TLS encryption. + * @li PLAIN: This mechanism is used if DIGEST-MD5 is not available. It is @b not secure without + * encryption. + * @li ANONYMOUS This mechanism is used if neither username nor password are set. The server generates + * random, temporary username and resource and may restrict available services. + * @li EXTERNAL This mechanism is currently only available if client certificate and private key + * are provided. The server tries to figure out who the client is by external means -- for instance, + * using the provided certificate or even the IP address. (The restriction to certificate/key + * availability is likely to be lifted in the future.) + * + * Of course, all these mechanisms are not tried unless the server offers them. + * + * @author Jakob Schroeter + */ + class GLOOX_API Client : public ClientBase + { + public: + + friend class NonSaslAuth; + friend class Parser; + + /** + * Constructs a new Client which can be used for account registration only. + * SASL and TLS are on by default. The port will be determined by looking up SRV records. + * Alternatively, you can set the port explicitly by calling @ref setPort(). + * @param server The server to connect to. + */ + Client( const std::string& server ); + + /** + * Constructs a new Client. + * SASL and TLS are on by default. This should be the default constructor for most use cases. + * The server address will be taken from the JID. The actual host will be resolved using SRV + * records. The domain part of the JID is used as a fallback in case no SRV record is found, or + * you can set the server address separately by calling @ref setServer(). + * @param jid A full Jabber ID used for connecting to the server. + * @param password The password used for authentication. + * @param port The port to connect to. The default of -1 means to look up the port via DNS SRV. + */ + Client( const JID& jid, const std::string& password, int port = -1 ); + + /** + * Virtual destructor. + */ + virtual ~Client(); + + /** + * Use this function to bind an additional resource or to @b re-try to bind a + * resource in case previous binding failed and you were notified by means of + * ConnectionListener::onResourceBindError(). Use hasResourceBind() to find out if the + * server supports binding of multiple resources. bindResource() is a NOOP if it doesn't. + * @note ConnectionListener::onResourceBound() and ConnectionListener::onResourceBindError() + * will be called in case of success and failure, respectively. + * @param resource The resource identifier to bind. May be empty. In that case + * the server will assign a unique resource identifier. + * @return Returns @b true if binding of multiple resources is supported, @b false + * otherwise. A return value of @b true does not indicate that the resource was + * successfully bound. + * @note It is not necessary to call this function to bind the initial, main, resource. + * @since 1.0 + */ + bool bindResource( const std::string& resource ) + { return bindOperation( resource, true ); } + + /** + * Use this function to select a resource identifier that has been bound + * previously by means of bindResource(). It is not necessary to call this function + * if only one resource is bound. Use hasResourceBind() to find out if the + * server supports binding of multiple resources. selectResource() is a NOOP if it doesn't. + * @param resource A resource string that has been bound previously. + * @note If the resource string has not been bound previously, future sending of + * stanzas will fail. + */ + bool selectResource( const std::string& resource ); + + /** + * This function can be used to find out whether the server supports binding of multiple + * resources. + * @return @b True if binding of multiple resources is supported by the server, + * @b false otherwise. + */ + bool hasResourceBind() const { return ((m_streamFeatures & StreamFeatureUnbind) == StreamFeatureUnbind); } + + /** + * Use this function to unbind a resource identifier that has been bound + * previously by means of bindResource(). Use hasResourceBind() to find out if the + * server supports binding of multiple resources. unbindResource() is a NOOP if it doesn't. + * @param resource A resource string that has been bound previously. + * @note Servers are encouraged to terminate the connection should the only bound + * resource be unbound. + */ + bool unbindResource( const std::string& resource ) + { return bindOperation( resource, false ); } + + /** + * Returns the current prepped main resource. + * @return The resource used to connect. + */ + const std::string& resource() const { return m_jid.resource(); } + + /** + * Returns the current priority. + * @return The priority of the current resource. + */ + int priority() const { return m_presence.priority(); } + + /** + * Sets the username to use to connect to the XMPP server. + * @param username The username to authenticate with. + */ + void setUsername( const std::string &username ); + + /** + * Sets the main resource to use to connect to the XMPP server. + * @param resource The resource to use to log into the server. + */ + void setResource( const std::string &resource ) { m_jid.setResource( resource ); } + + /** + * Sends directed presence to the given JID. This is a NOOP if there's no active connection. + * To broadcast presence use setPresence( Presence::PresenceType, int, const std::string& ). + * @param to The JID to send directed Presence to. + * @param pres The presence to send. + * @param priority The priority to include. Legal values: -128 <= priority <= 127 + * @param status The optional status message to include. + * @note This function does not include any presence extensions (as added by + * means of addPresenceExtension()) to the stanza. + */ + void setPresence( const JID& to, Presence::PresenceType pres, int priority, + const std::string& status = EmptyString ); + + /** + * Use this function to set the entity's presence, that is, to broadcast presence to all + * subscribed entities. To send directed presence, use + * setPresence( const JID&, Presence::PresenceType, int, const std::string& ). + * If used prior to establishing a connection, the set values will be sent with + * the initial presence stanza. + * If used while a connection already is established, a presence stanza will be + * sent out immediately. + * @param pres The Presence value to set. + * @param priority An optional priority value. Legal values: -128 <= priority <= 127 + * @param status An optional message describing the presence state. + * @since 0.9 + */ + void setPresence( Presence::PresenceType pres, int priority, + const std::string& status = EmptyString ); + + /** + * Use this function to broadcast the entity's presence to all + * subscribed entities. This is a NOOP if there's no active connection. + * To send directed presence, use + * setPresence( const JID&, Presence::PresenceType, int, const std::string& ). + * If used while a connection already is established a repective presence stanza will be + * sent out immediately. Use presence() to modify the Presence object. + * @note When login is finished, initial presence will be sent automatically. + * So you do not need to call this function after login. + * @since 1.0 + */ + void setPresence() { sendPresence( m_presence ); } + + /** + * Returns the current presence. + * @return The current presence. + */ + Presence& presence() { return m_presence; } + + /** + * This is a temporary hack to enforce Non-SASL login. You should not need to use it. + * @param force Whether to force non-SASL auth. Default @b true. + * @deprecated Please update the server to properly support SASL instead. + */ + GLOOX_DEPRECATED void setForceNonSasl( bool force = true ) { m_forceNonSasl = force; } + + /** + * Disables the automatic roster management. + * You have to keep track of incoming presence yourself if + * you want to have a roster. + */ + void disableRoster(); + + /** + * This function gives access to the @c RosterManager object. + * @return A pointer to the RosterManager. + */ + RosterManager* rosterManager() { return m_rosterManager; } + + /** + * Disconnects from the server. + */ + void disconnect(); + + /** + * Initiates a login attempt (currently SASL External not supported). + * This is useful after registering a new account. Simply use setUsername() and setPassword(), + * and call login(). + * @return @b True if a login attempt could be started, @b false otherwise. A return + * value of @b true does not indicate that login was successful. + */ + bool login(); + + protected: + /** + * Initiates non-SASL login. + */ + void nonSaslLogin(); + + private: + /** + * @brief This is an implementation of a resource binding StanzaExtension. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class ResourceBind : public StanzaExtension + { + + public: + /** + * Constructs a new object with the given resource string. + * @param resource The resource to set. + * @param bind Indicates whether this is an bind or unbind request. + * Defaults to @b true (bind). + */ + ResourceBind( const std::string& resource, bool bind = true ); + + /** + * Constructs a new object from the given Tag. + * @param tag The Tag to parse. + */ + ResourceBind( const Tag* tag ); + + /** + * Destructor. + */ + ~ResourceBind(); + + /** + * Returns the requested resource. + * @return The requested resource. + */ + const std::string& resource() const { return m_resource; } + + /** + * Returns the assigned JID. + * @return The assigned JID. + */ + const JID& jid() const { return m_jid; } + + /** + * Use this function to find out whether the extension contains a + * bind or unbind request. + * @return @b True if the extension contains an unbind request, @b false otherwise. + */ + bool unbind() const { return !m_bind; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new ResourceBind( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new ResourceBind( *this ); + } + + private: + std::string m_resource; + JID m_jid; + bool m_bind; + }; + + /** + * @brief This is an implementation of a session creating StanzaExtension. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class SessionCreation : public StanzaExtension + { + + public: + /** + * Constructs a new object. + */ + SessionCreation() : StanzaExtension( ExtSessionCreation ) {} + + /** + * Destructor. + */ + ~SessionCreation() {} + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const { return EmptyString; } + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { (void)tag; return 0; } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { return 0; } + + }; + + virtual void handleStartNode() {} + virtual bool handleNormalNode( Tag* tag ); + virtual void disconnect( ConnectionError reason ); + virtual void handleIqIDForward( const IQ& iq, int context ); + + int getStreamFeatures( Tag* tag ); + int getSaslMechs( Tag* tag ); + int getCompressionMethods( Tag* tag ); + void processResourceBind( const IQ& iq ); + void processCreateSession( const IQ& iq ); + void sendPresence( Presence& pres ); + void createSession(); + void negotiateCompression( StreamFeature method ); + void connected(); + virtual void rosterFilled(); + virtual void cleanup(); + bool bindOperation( const std::string& resource, bool bind ); + + void init(); + + enum TrackContext + { + CtxResourceBind = 1000, // must be higher than the last element in ClientBase's TrackContext + CtxResourceUnbind, + CtxSessionEstablishment + }; + + RosterManager* m_rosterManager; + NonSaslAuth* m_auth; + + Presence m_presence; + + bool m_resourceBound; + bool m_forceNonSasl; + bool m_manageRoster; + + int m_streamFeatures; + + }; + +} + +#endif // CLIENT_H__ diff --git a/libs/libgloox/clientbase.cpp b/libs/libgloox/clientbase.cpp new file mode 100644 index 0000000..1619107 --- /dev/null +++ b/libs/libgloox/clientbase.cpp @@ -0,0 +1,1516 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "config.h" + +#include "clientbase.h" +#include "connectionbase.h" +#include "tlsbase.h" +#include "compressionbase.h" +#include "connectiontcpclient.h" +#include "disco.h" +#include "messagesessionhandler.h" +#include "tag.h" +#include "iq.h" +#include "message.h" +#include "subscription.h" +#include "presence.h" +#include "connectionlistener.h" +#include "iqhandler.h" +#include "messagehandler.h" +#include "presencehandler.h" +#include "rosterlistener.h" +#include "subscriptionhandler.h" +#include "loghandler.h" +#include "taghandler.h" +#include "mucinvitationhandler.h" +#include "mucroom.h" +#include "jid.h" +#include "base64.h" +#include "error.h" +#include "md5.h" +#include "util.h" +#include "tlsdefault.h" +#include "compressionzlib.h" +#include "stanzaextensionfactory.h" +#include "eventhandler.h" +#include "event.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include // for memset() + +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) +#include +#endif + +namespace gloox +{ + + // ---- ClientBase::Ping ---- + ClientBase::Ping::Ping() + : StanzaExtension( ExtPing ) + { + } + + ClientBase::Ping::~Ping() + { + } + + const std::string& ClientBase::Ping::filterString() const + { + static const std::string filter = "/iq/ping[@xmlns='" + XMLNS_XMPP_PING + "']"; + return filter; + } + // ---- ~ClientBase::Ping ---- + + // ---- ClientBase ---- + ClientBase::ClientBase( const std::string& ns, const std::string& server, int port ) + : m_connection( 0 ), m_encryption( 0 ), m_compression( 0 ), m_disco( 0 ), m_namespace( ns ), + m_xmllang( "en" ), m_server( server ), m_compressionActive( false ), m_encryptionActive( false ), + m_compress( true ), m_authed( false ), m_block( false ), m_sasl( true ), m_tls( TLSOptional ), m_port( port ), + m_availableSaslMechs( SaslMechAll ), + m_statisticsHandler( 0 ), m_mucInvitationHandler( 0 ), + m_messageSessionHandlerChat( 0 ), m_messageSessionHandlerGroupchat( 0 ), + m_messageSessionHandlerHeadline( 0 ), m_messageSessionHandlerNormal( 0 ), + m_parser( this ), m_seFactory( 0 ), m_authError( AuthErrorUndefined ), + m_streamError( StreamErrorUndefined ), m_streamErrorAppCondition( 0 ), + m_selectedSaslMech( SaslMechNone ), m_autoMessageSession( false ) + { + init(); + } + + ClientBase::ClientBase( const std::string& ns, const std::string& password, + const std::string& server, int port ) + : m_connection( 0 ), m_encryption( 0 ), m_compression( 0 ), m_disco( 0 ), m_namespace( ns ), + m_password( password ), + m_xmllang( "en" ), m_server( server ), m_compressionActive( false ), m_encryptionActive( false ), + m_compress( true ), m_authed( false ), m_block( false ), m_sasl( true ), m_tls( TLSOptional ), + m_port( port ), m_availableSaslMechs( SaslMechAll ), + m_statisticsHandler( 0 ), m_mucInvitationHandler( 0 ), + m_messageSessionHandlerChat( 0 ), m_messageSessionHandlerGroupchat( 0 ), + m_messageSessionHandlerHeadline( 0 ), m_messageSessionHandlerNormal( 0 ), + m_parser( this ), m_seFactory( 0 ), m_authError( AuthErrorUndefined ), + m_streamError( StreamErrorUndefined ), m_streamErrorAppCondition( 0 ), + m_selectedSaslMech( SaslMechNone ), m_autoMessageSession( false ) + { + init(); + } + + void ClientBase::init() + { + if( !m_disco ) + { + m_disco = new Disco( this ); + m_disco->setVersion( "based on gloox", GLOOX_VERSION ); + m_disco->addFeature( XMLNS_XMPP_PING ); + } + + registerStanzaExtension( new Error() ); + registerStanzaExtension( new Ping() ); + registerIqHandler( this, ExtPing ); + + m_streamError = StreamErrorUndefined; + m_block = false; + memset( &m_stats, 0, sizeof( m_stats ) ); + cleanup(); + } + + ClientBase::~ClientBase() + { + delete m_connection; + delete m_encryption; + delete m_compression; + delete m_seFactory; + m_seFactory = 0; // to avoid usage when Disco gets deleted below + delete m_disco; + m_disco = 0; + + util::clearList( m_messageSessions ); + + PresenceJidHandlerList::const_iterator it1 = m_presenceJidHandlers.begin(); + for( ; it1 != m_presenceJidHandlers.end(); ++it1 ) + delete (*it1).jid; + } + + ConnectionError ClientBase::recv( int timeout ) + { + if( !m_connection || m_connection->state() == StateDisconnected ) + return ConnNotConnected; + + return m_connection->recv( timeout ); + } + + bool ClientBase::connect( bool block ) + { + if( m_server.empty() ) + return false; + + if( !m_connection ) + m_connection = new ConnectionTCPClient( this, m_logInstance, m_server, m_port ); + + if( m_connection->state() >= StateConnecting ) + return true; + + if( !m_encryption ) + m_encryption = getDefaultEncryption(); + + if( !m_compression ) + m_compression = getDefaultCompression(); + + m_logInstance.dbg( LogAreaClassClientbase, "This is gloox " + GLOOX_VERSION + ", connecting to " + + m_server + ":" + util::int2string( m_port ) + "..." ); + m_block = block; + ConnectionError ret = m_connection->connect(); + if( ret != ConnNoError ) + return false; + + if( m_block ) + m_connection->receive(); + + return true; + } + + void ClientBase::handleTag( Tag* tag ) + { + if( !tag ) + { + logInstance().dbg( LogAreaClassClientbase, "stream closed" ); + disconnect( ConnStreamClosed ); + return; + } + + logInstance().dbg( LogAreaXmlIncoming, tag->xml() ); + ++m_stats.totalStanzasReceived; + + if( tag->name() == "stream" && tag->xmlns() == XMLNS_STREAM ) + { + const std::string& version = tag->findAttribute( "version" ); + if( !checkStreamVersion( version ) ) + { + logInstance().dbg( LogAreaClassClientbase, "This server is not XMPP-compliant" + " (it does not send a 'version' attribute). Please fix it or try another one.\n" ); + disconnect( ConnStreamVersionError ); + return; + } + + m_sid = tag->findAttribute( "id" ); + handleStartNode(); + } + else if( tag->name() == "error" && tag->xmlns() == XMLNS_STREAM ) + { + handleStreamError( tag ); + disconnect( ConnStreamError ); + } + else + { + if( !handleNormalNode( tag ) ) + { + if( tag->xmlns().empty() || tag->xmlns() == XMLNS_CLIENT ) + { + if( tag->name() == "iq" ) + { + IQ iq( tag ); + m_seFactory->addExtensions( iq, tag ); + notifyIqHandlers( iq ); + ++m_stats.iqStanzasReceived; + } + else if( tag->name() == "message" ) + { + Message msg( tag ); + m_seFactory->addExtensions( msg, tag ); + notifyMessageHandlers( msg ); + ++m_stats.messageStanzasReceived; + } + else if( tag->name() == "presence" ) + { + const std::string& type = tag->findAttribute( TYPE ); + if( type == "subscribe" || type == "unsubscribe" + || type == "subscribed" || type == "unsubscribed" ) + { + Subscription sub( tag ); + m_seFactory->addExtensions( sub, tag ); + notifySubscriptionHandlers( sub ); + ++m_stats.s10nStanzasReceived; + } + else + { + Presence pres( tag ); + m_seFactory->addExtensions( pres, tag ); + notifyPresenceHandlers( pres ); + ++m_stats.presenceStanzasReceived; + } + } + else + m_logInstance.err( LogAreaClassClientbase, "Received invalid stanza." ); + } + else + { + notifyTagHandlers( tag ); + } + } + } + + if( m_statisticsHandler ) + m_statisticsHandler->handleStatistics( getStatistics() ); + } + + void ClientBase::handleCompressedData( const std::string& data ) + { + if( m_encryption && m_encryptionActive ) + m_encryption->encrypt( data ); + else if( m_connection ) + m_connection->send( data ); + else + m_logInstance.err( LogAreaClassClientbase, "Compression finished, but chain broken" ); + } + + void ClientBase::handleDecompressedData( const std::string& data ) + { + parse( data ); + } + + void ClientBase::handleEncryptedData( const TLSBase* /*base*/, const std::string& data ) + { + if( m_connection ) + m_connection->send( data ); + else + m_logInstance.err( LogAreaClassClientbase, "Encryption finished, but chain broken" ); + } + + void ClientBase::handleDecryptedData( const TLSBase* /*base*/, const std::string& data ) + { + if( m_compression && m_compressionActive ) + m_compression->decompress( data ); + else + parse( data ); + } + + void ClientBase::handleHandshakeResult( const TLSBase* /*base*/, bool success, CertInfo &certinfo ) + { + if( success ) + { + if( !notifyOnTLSConnect( certinfo ) ) + { + logInstance().err( LogAreaClassClientbase, "Server's certificate rejected!" ); + disconnect( ConnTlsFailed ); + } + else + { + logInstance().dbg( LogAreaClassClientbase, "connection encryption active" ); + header(); + } + } + else + { + logInstance().err( LogAreaClassClientbase, "TLS handshake failed!" ); + disconnect( ConnTlsFailed ); + } + } + + void ClientBase::handleReceivedData( const ConnectionBase* /*connection*/, const std::string& data ) + { + if( m_encryption && m_encryptionActive ) + m_encryption->decrypt( data ); + else if( m_compression && m_compressionActive ) + m_compression->decompress( data ); + else + parse( data ); + } + + void ClientBase::handleConnect( const ConnectionBase* /*connection*/ ) + { + header(); + } + + void ClientBase::handleDisconnect( const ConnectionBase* /*connection*/, ConnectionError reason ) + { + if( m_connection ) + m_connection->cleanup(); + + if( m_encryption ) + m_encryption->cleanup(); + + if( m_compression ) + m_compression->cleanup(); + + m_encryptionActive = false; + m_compressionActive = false; + + notifyOnDisconnect( reason ); + } + + void ClientBase::disconnect( ConnectionError reason ) + { + if( !m_connection || m_connection->state() < StateConnecting ) + return; + + if( reason != ConnTlsFailed ) + send( "" ); + + m_connection->disconnect(); + m_connection->cleanup(); + + if( m_encryption ) + m_encryption->cleanup(); + + if( m_compression ) + m_compression->cleanup(); + + m_encryptionActive = false; + m_compressionActive = false; + + notifyOnDisconnect( reason ); + } + + void ClientBase::parse( const std::string& data ) + { + std::string copy = data; + int i = 0; + if( ( i = m_parser.feed( copy ) ) >= 0 ) + { + std::string error = "parse error (at pos "; + error += util::int2string( i ); + error += "): "; + m_logInstance.err( LogAreaClassClientbase, error + copy ); + Tag* e = new Tag( "stream:error" ); + new Tag( e, "restricted-xml", "xmlns", XMLNS_XMPP_STREAM ); + send( e ); + disconnect( ConnParseError ); + } + } + + void ClientBase::header() + { + std::string head = ""; + head += ""; + send( head ); + } + + bool ClientBase::hasTls() + { +#if defined( HAVE_GNUTLS ) || defined( HAVE_OPENSSL ) || defined( HAVE_WINTLS ) + return true; +#else + return false; +#endif + } + + void ClientBase::startTls() + { + send( new Tag( "starttls", XMLNS, XMLNS_STREAM_TLS ) ); + } + + void ClientBase::setServer( const std::string &server ) + { + m_server = server; + if( m_connection ) + m_connection->setServer( server ); + } + + void ClientBase::setClientCert( const std::string& clientKey, const std::string& clientCerts ) + { + m_clientKey = clientKey; + m_clientCerts = clientCerts; + } + + void ClientBase::startSASL( SaslMechanism type ) + { + m_selectedSaslMech = type; + + Tag* a = new Tag( "auth", XMLNS, XMLNS_STREAM_SASL ); + + switch( type ) + { + case SaslMechDigestMd5: + a->addAttribute( "mechanism", "DIGEST-MD5" ); + break; + case SaslMechPlain: + { + a->addAttribute( "mechanism", "PLAIN" ); + + std::string tmp; + if( m_authzid ) + tmp += m_authzid.bare(); + + tmp += '\0'; + if( !m_authcid.empty() ) + tmp += m_authcid; + else + tmp += m_jid.username(); + tmp += '\0'; + tmp += m_password; + a->setCData( Base64::encode64( tmp ) ); + break; + } + case SaslMechAnonymous: + a->addAttribute( "mechanism", "ANONYMOUS" ); + break; + case SaslMechExternal: + a->addAttribute( "mechanism", "EXTERNAL" ); + a->setCData( Base64::encode64( m_authzid ? m_authzid.bare() : m_jid.bare() ) ); + break; + case SaslMechGssapi: + { +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + a->addAttribute( "mechanism", "GSSAPI" ); +// The client calls GSS_Init_sec_context, passing in 0 for +// input_context_handle (initially) and a targ_name equal to output_name +// from GSS_Import_Name called with input_name_type of +// GSS_C_NT_HOSTBASED_SERVICE and input_name_string of +// "service@hostname" where "service" is the service name specified in +// the protocol's profile, and "hostname" is the fully qualified host +// name of the server. The client then responds with the resulting +// output_token. + std::string token; + a->setCData( Base64::encode64( token ) ); +// etc... see gssapi-sasl-draft.txt +#else + logInstance().err( LogAreaClassClientbase, + "SASL GSSAPI is not supported on this platform. You should never see this." ); +#endif + break; + } + case SaslMechNTLM: + { +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + a->addAttribute( "mechanism", "NTLM" ); + SEC_WINNT_AUTH_IDENTITY identity, *ident = 0; + memset( &identity, 0, sizeof( identity ) ); + if( m_jid.username().length() > 0 ) + { + identity.User = (unsigned char*)m_jid.username().c_str(); + identity.UserLength = (unsigned long)m_jid.username().length(); + identity.Domain = (unsigned char*)m_ntlmDomain.c_str(); + identity.DomainLength = (unsigned long)m_ntlmDomain.length(); + identity.Password = (unsigned char*)m_password.c_str(); + identity.PasswordLength = (unsigned long)m_password.length(); + identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + ident = &identity; + } + AcquireCredentialsHandle( 0, _T( "NTLM" ), SECPKG_CRED_OUTBOUND, 0, ident, 0, 0, &m_credHandle, 0 ); +#else + logInstance().err( LogAreaClassClientbase, + "SASL NTLM is not supported on this platform. You should never see this." ); +#endif + break; + } + default: + break; + } + + send( a ); + } + + void ClientBase::processSASLChallenge( const std::string& challenge ) + { + Tag* t = new Tag( "response", XMLNS, XMLNS_STREAM_SASL ); + + const std::string& decoded = Base64::decode64( challenge ); + + switch( m_selectedSaslMech ) + { + case SaslMechDigestMd5: + { + if( !decoded.compare( 0, 7, "rspauth" ) ) + break; + + std::string realm; + std::string::size_type end = 0; + std::string::size_type pos = decoded.find( "realm=" ); + if( pos != std::string::npos ) + { + end = decoded.find( '"', pos + 7 ); + realm = decoded.substr( pos + 7, end - ( pos + 7 ) ); + } + else + realm = m_jid.server(); + + pos = decoded.find( "nonce=" ); + if( pos == std::string::npos ) + return; + + end = decoded.find( '"', pos + 7 ); + while( decoded[end-1] == '\\' ) + end = decoded.find( '"', end + 1 ); + std::string nonce = decoded.substr( pos + 7, end - ( pos + 7 ) ); + + std::string cnonce; + char cn[4*8+1]; + for( int i = 0; i < 4; ++i ) + sprintf( cn + i*8, "%08x", rand() ); + cnonce.assign( cn, 4*8 ); + + MD5 md5; + md5.feed( m_jid.username() ); + md5.feed( ":" ); + md5.feed( realm ); + md5.feed( ":" ); + md5.feed( m_password ); + md5.finalize(); + const std::string& a1_h = md5.binary(); + md5.reset(); + md5.feed( a1_h ); + md5.feed( ":" ); + md5.feed( nonce ); + md5.feed( ":" ); + md5.feed( cnonce ); + md5.finalize(); + const std::string& a1 = md5.hex(); + md5.reset(); + md5.feed( "AUTHENTICATE:xmpp/" ); + md5.feed( m_jid.server() ); + md5.finalize(); + const std::string& a2 = md5.hex(); + md5.reset(); + md5.feed( a1 ); + md5.feed( ":" ); + md5.feed( nonce ); + md5.feed( ":00000001:" ); + md5.feed( cnonce ); + md5.feed( ":auth:" ); + md5.feed( a2 ); + md5.finalize(); + + std::string response = "username=\""; + response += m_jid.username(); + response += "\",realm=\""; + response += realm; + response += "\",nonce=\""; + response += nonce; + response += "\",cnonce=\""; + response += cnonce; + response += "\",nc=00000001,qop=auth,digest-uri=\"xmpp/"; + response += m_jid.server(); + response += "\",response="; + response += md5.hex(); + response += ",charset=utf-8"; + + if( m_authzid ) + response += ",authzid=" + m_authzid.bare(); + + t->setCData( Base64::encode64( response ) ); + + break; + } + case SaslMechGssapi: +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + // see gssapi-sasl-draft.txt +#else + m_logInstance.err( LogAreaClassClientbase, + "Huh, received GSSAPI challenge?! This should have never happened!" ); +#endif + break; + case SaslMechNTLM: + { +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + bool type1 = ( decoded.length() < 7 ) ? true : false; + + SecBuffer bufferIn = { type1 ? 0 : (unsigned long)decoded.length(), + SECBUFFER_TOKEN, + (void*)decoded.c_str() }; + SecBufferDesc secIn = { 0, 1, &bufferIn }; + + char buffer[4096]; + + SecBuffer bufferOut = { sizeof( buffer ), SECBUFFER_TOKEN, buffer }; + SecBufferDesc secOut = { 0, 1, &bufferOut }; + + TimeStamp timestamp; + unsigned long contextAttr; + + SECURITY_STATUS status = InitializeSecurityContext( &m_credHandle, type1 ? 0 : &m_ctxtHandle, + 0, ISC_REQ_MUTUAL_AUTH, 0, 0, &secIn, 0, + &m_ctxtHandle, &secOut, &contextAttr, + ×tamp ); + std::string response; + if( SUCCEEDED( status ) ) + { + response = std::string( (const char *)bufferOut.pvBuffer, bufferOut.cbBuffer ); + } + else + { + logInstance().err( LogAreaClassClientbase, + "InitializeSecurityContext() failed, return value " + + util::int2string( status ) ); + } + + t->setCData( Base64::encode64( response ) ); +#else + m_logInstance.err( LogAreaClassClientbase, + "Huh, received NTLM challenge?! This should have never happened!" ); +#endif + break; + } + + default: + // should never happen. + break; + } + + send( t ); + } + + void ClientBase::processSASLError( Tag* tag ) + { + if( tag->hasChild( "aborted" ) ) + m_authError = SaslAborted; + else if( tag->hasChild( "incorrect-encoding" ) ) + m_authError = SaslIncorrectEncoding; + else if( tag->hasChild( "invalid-authzid" ) ) + m_authError = SaslInvalidAuthzid; + else if( tag->hasChild( "invalid-mechanism" ) ) + m_authError = SaslInvalidMechanism; + else if( tag->hasChild( "malformed-request" ) ) + m_authError = SaslMalformedRequest; + else if( tag->hasChild( "mechanism-too-weak" ) ) + m_authError = SaslMechanismTooWeak; + else if( tag->hasChild( "not-authorized" ) ) + m_authError = SaslNotAuthorized; + else if( tag->hasChild( "temporary-auth-failure" ) ) + m_authError = SaslTemporaryAuthFailure; + +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + if( m_selectedSaslMech == SaslMechNTLM ) + { + FreeCredentialsHandle( &m_credHandle ); + DeleteSecurityContext( &m_ctxtHandle ); + } +#endif + } + + void ClientBase::processSASLSuccess() + { +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + if( m_selectedSaslMech == SaslMechNTLM ) + { + FreeCredentialsHandle( &m_credHandle ); + DeleteSecurityContext( &m_ctxtHandle ); + } +#endif + } + + void ClientBase::send( IQ& iq, IqHandler* ih, int context, bool del ) + { + if( ih && ( iq.subtype() == IQ::Set || iq.subtype() == IQ::Get ) ) + { + if( iq.id().empty() ) + iq.setID( getID() ); + + TrackStruct track; + track.ih = ih; + track.context = context; + track.del = del; + m_iqHandlerMapMutex.lock(); + m_iqIDHandlers[iq.id()] = track; + m_iqHandlerMapMutex.unlock(); + } + + send( iq ); + } + + void ClientBase::send( const IQ& iq ) + { + ++m_stats.iqStanzasSent; + Tag* tag = iq.tag(); + addFrom( tag ); + addNamespace( tag ); + send( tag ); + } + + void ClientBase::send( const Message& msg ) + { + ++m_stats.messageStanzasSent; + Tag* tag = msg.tag(); + addFrom( tag ); + addNamespace( tag ); + send( tag ); + } + + void ClientBase::send( const Subscription& sub ) + { + ++m_stats.s10nStanzasSent; + Tag* tag = sub.tag(); + addFrom( tag ); + addNamespace( tag ); + send( tag ); + } + + void ClientBase::send( Presence& pres ) + { + ++m_stats.presenceStanzasSent; + Tag* tag = pres.tag(); + StanzaExtensionList::const_iterator it = m_presenceExtensions.begin(); + for( ; it != m_presenceExtensions.end(); ++it ) + tag->addChild( (*it)->tag() ); + addFrom( tag ); + addNamespace( tag ); + send( tag ); + } + + void ClientBase::send( Tag* tag ) + { + if( !tag ) + return; + + send( tag->xml() ); + + ++m_stats.totalStanzasSent; + + if( m_statisticsHandler ) + m_statisticsHandler->handleStatistics( getStatistics() ); + + delete tag; + } + + void ClientBase::send( const std::string& xml ) + { + if( m_connection && m_connection->state() == StateConnected ) + { + if( m_compression && m_compressionActive ) + m_compression->compress( xml ); + else if( m_encryption && m_encryptionActive ) + m_encryption->encrypt( xml ); + else + m_connection->send( xml ); + + logInstance().dbg( LogAreaXmlOutgoing, xml ); + } + } + + void ClientBase::addFrom( Tag* tag ) + { + if( !m_authed /*for IQ Auth */ || !tag || tag->hasAttribute( "from" ) ) + return; + + if ( m_selectedResource.empty() ) + tag->addAttribute( "from", m_jid.bare() ); + else + tag->addAttribute( "from", m_jid.bare() + '/' + m_selectedResource ); + } + + void ClientBase::addNamespace( Tag* tag ) + { + if( !tag || !tag->xmlns().empty() ) + return; + + tag->setXmlns( m_namespace ); + } + + void ClientBase::registerStanzaExtension( StanzaExtension* ext ) + { + if( !m_seFactory ) + m_seFactory = new StanzaExtensionFactory(); + + m_seFactory->registerExtension( ext ); + } + + bool ClientBase::removeStanzaExtension( int ext ) + { + if( !m_seFactory ) + return false; + + return m_seFactory->removeExtension( ext ); + } + + StatisticsStruct ClientBase::getStatistics() + { + if( m_connection ) + m_connection->getStatistics( m_stats.totalBytesReceived, m_stats.totalBytesSent ); + + return m_stats; + } + + ConnectionState ClientBase::state() const + { + return m_connection ? m_connection->state() : StateDisconnected; + } + + void ClientBase::whitespacePing() + { + send( " " ); + } + + void ClientBase::xmppPing( const JID& to, EventHandler* eh ) + { + const std::string& id = getID(); + IQ iq( IQ::Get, to, id ); + iq.addExtension( new Ping() ); + m_dispatcher.registerEventHandler( eh, id ); + send( iq, this, XMPPPing ); + } + + bool ClientBase::handleIq( const IQ& iq ) + { + const Ping* p = iq.findExtension( ExtPing ); + if( !p || iq.subtype() != IQ::Get ) + return false; + + m_dispatcher.dispatch( Event( Event::PingPing, iq ) ); + IQ re( IQ::Result, iq.from(), iq.id() ); + send( re ); + + return true; + } + + void ClientBase::handleIqID( const IQ& iq, int context ) + { + if( context == XMPPPing ) + m_dispatcher.dispatch( Event( ( iq.subtype() == IQ::Result ) ? Event::PingPong + : Event::PingError, iq ), + iq.id(), true ); + else + handleIqIDForward( iq, context ); + } + + const std::string ClientBase::getID() + { + static unsigned int uniqueBaseID = (unsigned int)time( 0 ); + char r[21+1]; + sprintf( r, "uid:%08x:%08x", uniqueBaseID, rand() ); + std::string ret( r, 21 ); + return ret; + } + + bool ClientBase::checkStreamVersion( const std::string& version ) + { + if( version.empty() ) + return false; + + int major = 0; + int minor = 0; + int myMajor = atoi( XMPP_STREAM_VERSION_MAJOR.c_str() ); + + size_t dot = version.find( '.' ); + if( !version.empty() && dot && dot != std::string::npos ) + { + major = atoi( version.substr( 0, dot ).c_str() ); + minor = atoi( version.substr( dot ).c_str() ); + } + + return myMajor >= major; + } + + void ClientBase::setConnectionImpl( ConnectionBase* cb ) + { + if( m_connection ) + { + delete m_connection; + } + m_connection = cb; + } + + void ClientBase::setEncryptionImpl( TLSBase* tb ) + { + if( m_encryption ) + { + delete m_encryption; + } + m_encryption = tb; + } + + void ClientBase::setCompressionImpl( CompressionBase* cb ) + { + if( m_compression ) + { + delete m_compression; + } + m_compression = cb; + } + + void ClientBase::handleStreamError( Tag* tag ) + { + StreamError err = StreamErrorUndefined; + const TagList& c = tag->children(); + TagList::const_iterator it = c.begin(); + for( ; it != c.end(); ++it ) + { + const std::string& name = (*it)->name(); + if( name == "bad-format" ) + err = StreamErrorBadFormat; + else if( name == "bad-namespace-prefix" ) + err = StreamErrorBadNamespacePrefix; + else if( name == "conflict" ) + err = StreamErrorConflict; + else if( name == "connection-timeout" ) + err = StreamErrorConnectionTimeout; + else if( name == "host-gone" ) + err = StreamErrorHostGone; + else if( name == "host-unknown" ) + err = StreamErrorHostUnknown; + else if( name == "improper-addressing" ) + err = StreamErrorImproperAddressing; + else if( name == "internal-server-error" ) + err = StreamErrorInternalServerError; + else if( name == "invalid-from" ) + err = StreamErrorInvalidFrom; + else if( name == "invalid-id" ) + err = StreamErrorInvalidId; + else if( name == "invalid-namespace" ) + err = StreamErrorInvalidNamespace; + else if( name == "invalid-xml" ) + err = StreamErrorInvalidXml; + else if( name == "not-authorized" ) + err = StreamErrorNotAuthorized; + else if( name == "policy-violation" ) + err = StreamErrorPolicyViolation; + else if( name == "remote-connection-failed" ) + err = StreamErrorRemoteConnectionFailed; + else if( name == "resource-constraint" ) + err = StreamErrorResourceConstraint; + else if( name == "restricted-xml" ) + err = StreamErrorRestrictedXml; + else if( name == "see-other-host" ) + { + err = StreamErrorSeeOtherHost; + m_streamErrorCData = tag->findChild( "see-other-host" )->cdata(); + } + else if( name == "system-shutdown" ) + err = StreamErrorSystemShutdown; + else if( name == "undefined-condition" ) + err = StreamErrorUndefinedCondition; + else if( name == "unsupported-encoding" ) + err = StreamErrorUnsupportedEncoding; + else if( name == "unsupported-stanza-type" ) + err = StreamErrorUnsupportedStanzaType; + else if( name == "unsupported-version" ) + err = StreamErrorUnsupportedVersion; + else if( name == "xml-not-well-formed" ) + err = StreamErrorXmlNotWellFormed; + else if( name == "text" ) + { + const std::string& lang = (*it)->findAttribute( "xml:lang" ); + if( !lang.empty() ) + m_streamErrorText[lang] = (*it)->cdata(); + else + m_streamErrorText["default"] = (*it)->cdata(); + } + else + m_streamErrorAppCondition = (*it); + + if( err != StreamErrorUndefined && (*it)->hasAttribute( XMLNS, XMLNS_XMPP_STREAM ) ) + m_streamError = err; + } + } + + const std::string& ClientBase::streamErrorText( const std::string& lang ) const + { + StringMap::const_iterator it = m_streamErrorText.find( lang ); + return ( it != m_streamErrorText.end() ) ? (*it).second : EmptyString; + } + + void ClientBase::registerMessageSessionHandler( MessageSessionHandler* msh, int types ) + { + if( types & Message::Chat || types == 0 ) + m_messageSessionHandlerChat = msh; + + if( types & Message::Normal || types == 0 ) + m_messageSessionHandlerNormal = msh; + + if( types & Message::Groupchat || types == 0 ) + m_messageSessionHandlerGroupchat = msh; + + if( types & Message::Headline || types == 0 ) + m_messageSessionHandlerHeadline = msh; + } + + void ClientBase::registerPresenceHandler( PresenceHandler* ph ) + { + if( ph ) + m_presenceHandlers.push_back( ph ); + } + + void ClientBase::removePresenceHandler( PresenceHandler* ph ) + { + if( ph ) + m_presenceHandlers.remove( ph ); + } + + void ClientBase::registerPresenceHandler( const JID& jid, PresenceHandler* ph ) + { + if( ph && jid ) + { + JidPresHandlerStruct jph; + jph.jid = new JID( jid.bare() ); + jph.ph = ph; + m_presenceJidHandlers.push_back( jph ); + } + } + + void ClientBase::removePresenceHandler( const JID& jid, PresenceHandler* ph ) + { + PresenceJidHandlerList::iterator t; + PresenceJidHandlerList::iterator it = m_presenceJidHandlers.begin(); + while( it != m_presenceJidHandlers.end() ) + { + t = it; + ++it; + if( ( !ph || (*t).ph == ph ) && (*t).jid->bare() == jid.bare() ) + { + delete (*t).jid; + m_presenceJidHandlers.erase( t ); + } + } + } + + void ClientBase::removeIDHandler( IqHandler* ih ) + { + IqTrackMap::iterator t; + m_iqHandlerMapMutex.lock(); + IqTrackMap::iterator it = m_iqIDHandlers.begin(); + while( it != m_iqIDHandlers.end() ) + { + t = it; + ++it; + if( ih == (*t).second.ih ) + m_iqIDHandlers.erase( t ); + } + m_iqHandlerMapMutex.unlock(); + } + + void ClientBase::registerIqHandler( IqHandler* ih, int exttype ) + { + if( !ih ) + return; + + typedef IqHandlerMap::const_iterator IQci; + std::pair g = m_iqExtHandlers.equal_range( exttype ); + for( IQci it = g.first; it != g.second; ++it ) + if( (*it).second == ih ) + return; + + m_iqExtHandlers.insert( std::make_pair( exttype, ih ) ); + } + + void ClientBase::removeIqHandler( IqHandler* ih, int exttype ) + { + if( !ih ) + return; + + typedef IqHandlerMap::iterator IQi; + std::pair g = m_iqExtHandlers.equal_range( exttype ); + IQi it2; + IQi it = g.first; + while( it != g.second ) + { + it2 = it++; + if( (*it2).second == ih ) + m_iqExtHandlers.erase( it2 ); + } + } + + void ClientBase::registerMessageSession( MessageSession* session ) + { + if( session ) + m_messageSessions.push_back( session ); + } + + void ClientBase::disposeMessageSession( MessageSession* session ) + { + if( !session ) + return; + + MessageSessionList::iterator it = std::find( m_messageSessions.begin(), + m_messageSessions.end(), + session ); + if( it != m_messageSessions.end() ) + { + delete (*it); + m_messageSessions.erase( it ); + } + } + + void ClientBase::registerMessageHandler( MessageHandler* mh ) + { + if( mh ) + m_messageHandlers.push_back( mh ); + } + + void ClientBase::removeMessageHandler( MessageHandler* mh ) + { + if( mh ) + m_messageHandlers.remove( mh ); + } + + void ClientBase::registerSubscriptionHandler( SubscriptionHandler* sh ) + { + if( sh ) + m_subscriptionHandlers.push_back( sh ); + } + + void ClientBase::removeSubscriptionHandler( SubscriptionHandler* sh ) + { + if( sh ) + m_subscriptionHandlers.remove( sh ); + } + + void ClientBase::registerTagHandler( TagHandler* th, const std::string& tag, const std::string& xmlns ) + { + if( th && !tag.empty() ) + { + TagHandlerStruct ths; + ths.tag = tag; + ths.xmlns = xmlns; + ths.th = th; + m_tagHandlers.push_back( ths ); + } + } + + void ClientBase::removeTagHandler( TagHandler* th, const std::string& tag, const std::string& xmlns ) + { + if( th ) + { + TagHandlerList::iterator it = m_tagHandlers.begin(); + for( ; it != m_tagHandlers.end(); ++it ) + { + if( (*it).th == th && (*it).tag == tag && (*it).xmlns == xmlns ) + m_tagHandlers.erase( it ); + } + } + } + + void ClientBase::registerStatisticsHandler( StatisticsHandler* sh ) + { + if( sh ) + m_statisticsHandler = sh; + } + + void ClientBase::removeStatisticsHandler() + { + m_statisticsHandler = 0; + } + + void ClientBase::registerMUCInvitationHandler( MUCInvitationHandler* mih ) + { + if( mih ) + { + m_mucInvitationHandler = mih; + m_disco->addFeature( XMLNS_MUC ); + } + } + + void ClientBase::removeMUCInvitationHandler() + { + m_mucInvitationHandler = 0; + m_disco->removeFeature( XMLNS_MUC ); + } + + void ClientBase::registerConnectionListener( ConnectionListener* cl ) + { + if( cl ) + m_connectionListeners.push_back( cl ); + } + + void ClientBase::removeConnectionListener( ConnectionListener* cl ) + { + if( cl ) + m_connectionListeners.remove( cl ); + } + + void ClientBase::notifyOnConnect() + { + util::ForEach( m_connectionListeners, &ConnectionListener::onConnect ); + } + + void ClientBase::notifyOnDisconnect( ConnectionError e ) + { + util::ForEach( m_connectionListeners, &ConnectionListener::onDisconnect, e ); + init(); + } + + bool ClientBase::notifyOnTLSConnect( const CertInfo& info ) + { + ConnectionListenerList::const_iterator it = m_connectionListeners.begin(); + for( ; it != m_connectionListeners.end() && (*it)->onTLSConnect( info ); ++it ) + ; + return m_stats.encryption = ( it == m_connectionListeners.end() ); + } + + void ClientBase::notifyOnResourceBindError( const Error* error ) + { + util::ForEach( m_connectionListeners, &ConnectionListener::onResourceBindError, error ); + } + + void ClientBase::notifyOnResourceBind( const std::string& resource ) + { + util::ForEach( m_connectionListeners, &ConnectionListener::onResourceBind, resource ); + } + + void ClientBase::notifyOnSessionCreateError( const Error* error ) + { + util::ForEach( m_connectionListeners, &ConnectionListener::onSessionCreateError, error ); + } + + void ClientBase::notifyStreamEvent( StreamEvent event ) + { + util::ForEach( m_connectionListeners, &ConnectionListener::onStreamEvent, event ); + } + + void ClientBase::notifyPresenceHandlers( Presence& pres ) + { + bool match = false; + PresenceJidHandlerList::const_iterator t; + PresenceJidHandlerList::const_iterator itj = m_presenceJidHandlers.begin(); + while( itj != m_presenceJidHandlers.end() ) + { + t = itj++; + if( (*t).jid->bare() == pres.from().bare() && (*t).ph ) + { + (*t).ph->handlePresence( pres ); + match = true; + } + } + if( match ) + return; + + // FIXME remove this for() for 1.1: + PresenceHandlerList::const_iterator it = m_presenceHandlers.begin(); + for( ; it != m_presenceHandlers.end(); ++it ) + { + (*it)->handlePresence( pres ); + } + // FIXME and reinstantiate this: +// util::ForEach( m_presenceHandlers, &PresenceHandler::handlePresence, pres ); + } + + void ClientBase::notifySubscriptionHandlers( Subscription& s10n ) + { + // FIXME remove this for() for 1.1: + SubscriptionHandlerList::const_iterator it = m_subscriptionHandlers.begin(); + for( ; it != m_subscriptionHandlers.end(); ++it ) + { + (*it)->handleSubscription( s10n ); + } + // FIXME and reinstantiate this: +// util::ForEach( m_subscriptionHandlers, &SubscriptionHandler::handleSubscription, s10n ); + } + + void ClientBase::notifyIqHandlers( IQ& iq ) + { + m_iqHandlerMapMutex.lock(); + IqTrackMap::iterator it_id = m_iqIDHandlers.find( iq.id() ); + m_iqHandlerMapMutex.unlock(); + if( it_id != m_iqIDHandlers.end() && iq.subtype() & ( IQ::Result | IQ::Error ) ) + { + (*it_id).second.ih->handleIqID( iq, (*it_id).second.context ); + if( (*it_id).second.del ) + delete (*it_id).second.ih; + m_iqHandlerMapMutex.lock(); + m_iqIDHandlers.erase( it_id ); + m_iqHandlerMapMutex.unlock(); + return; + } + + if( iq.extensions().empty() ) + return; + + bool res = false; + + // FIXME remove for 1.1 +// typedef IqHandlerMapXmlns::const_iterator IQciXmlns +// Tag *tag = iq.tag()->xmlns(); +// std::pair g = m_iqNSHandlers.equal_range( tag->xmlns() ); +// for( IQciXmlns it = g.first; it != g.second; ++it ) +// { +// if( (*it).second->handleIq( iq ) ) +// res = true; +// } +// delete tag; + + typedef IqHandlerMap::const_iterator IQci; + const StanzaExtensionList& sel = iq.extensions(); + StanzaExtensionList::const_iterator itse = sel.begin(); + for( ; itse != sel.end(); ++itse ) + { + std::pair g = m_iqExtHandlers.equal_range( (*itse)->extensionType() ); + for( IQci it = g.first; it != g.second; ++it ) + { + if( (*it).second->handleIq( iq ) ) + res = true; + } + } + + if( !res && iq.subtype() & ( IQ::Get | IQ::Set ) ) + { + IQ re( IQ::Error, iq.from(), iq.id() ); + re.addExtension( new Error( StanzaErrorTypeCancel, StanzaErrorServiceUnavailable ) ); + send( re ); + } + } + + void ClientBase::notifyMessageHandlers( Message& msg ) + { + if( m_mucInvitationHandler ) + { + const MUCRoom::MUCUser* mu = msg.findExtension( ExtMUCUser ); + if( mu && mu->operation() != MUCRoom::OpInviteTo ) + { + + m_mucInvitationHandler->handleMUCInvitation( msg.from(), + mu->jid() ? JID( *(mu->jid()) ) : JID(), + mu->reason() ? *(mu->reason()) : EmptyString, + msg.body(), + mu->password() ? *(mu->password()) : EmptyString, + mu->continued(), + mu->thread() ? *(mu->thread()) : EmptyString ); + return; + } + } + + MessageSessionList::const_iterator it1 = m_messageSessions.begin(); + for( ; it1 != m_messageSessions.end(); ++it1 ) + { + if( (*it1)->target().full() == msg.from().full() && + ( msg.thread().empty() + || (*it1)->threadID() == msg.thread() + || (*it1)->honorThreadID() ) && +// FIXME don't use '== 0' here + ( (*it1)->types() & msg.subtype() || (*it1)->types() == 0 ) ) + { + (*it1)->handleMessage( msg ); + return; + } + } + + it1 = m_messageSessions.begin(); + for( ; it1 != m_messageSessions.end(); ++it1 ) + { + if( (*it1)->target().bare() == msg.from().bare() && + ( msg.thread().empty() + || (*it1)->threadID() == msg.thread() + || (*it1)->honorThreadID() ) && +// FIXME don't use '== 0' here + ( (*it1)->types() & msg.subtype() || (*it1)->types() == 0 ) ) + { + (*it1)->handleMessage( msg ); + return; + } + } + + MessageSessionHandler* msHandler = 0; + + switch( msg.subtype() ) + { + case Message::Chat: + msHandler = m_messageSessionHandlerChat; + break; + case Message::Normal: + msHandler = m_messageSessionHandlerNormal; + break; + case Message::Groupchat: + msHandler = m_messageSessionHandlerGroupchat; + break; + case Message::Headline: + msHandler = m_messageSessionHandlerHeadline; + break; + default: + break; + } + + if( msHandler ) + { + if( msg.subtype() == Message::Chat && msg.body().empty() ) + return; // don't want a new MS for empty messages + MessageSession* session = new MessageSession( this, msg.from(), true, msg.subtype() ); + msHandler->handleMessageSession( session ); + session->handleMessage( msg ); + } + else + { + // FIXME remove this for() for 1.1: + MessageHandlerList::const_iterator it = m_messageHandlers.begin(); + for( ; it != m_messageHandlers.end(); ++it ) + { + (*it)->handleMessage( msg ); + } + // FIXME and reinstantiate this: +// util::ForEach( m_messageHandlers, &MessageHandler::handleMessage, msg ); // FIXME remove for 1.1 + } + } + + void ClientBase::notifyTagHandlers( Tag* tag ) + { + TagHandlerList::const_iterator it = m_tagHandlers.begin(); + for( ; it != m_tagHandlers.end(); ++it ) + { + if( (*it).tag == tag->name() && tag->hasAttribute( XMLNS, (*it).xmlns ) ) + (*it).th->handleTag( tag ); + } + } + + void ClientBase::addPresenceExtension( StanzaExtension* se ) + { + if( !se ) + return; + + removePresenceExtension( se->extensionType() ); + m_presenceExtensions.push_back( se ); + } + + bool ClientBase::removePresenceExtension( int type ) + { + StanzaExtensionList::iterator it = m_presenceExtensions.begin(); + for( ; it != m_presenceExtensions.end(); ++it ) + { + if( (*it)->extensionType() == type ) + { + delete (*it); + m_presenceExtensions.erase( it ); + return true; + } + } + + return false; + } + + CompressionBase* ClientBase::getDefaultCompression() + { + if( !m_compress ) + return 0; + +#ifdef HAVE_ZLIB + CompressionBase* cmp = new CompressionZlib( this ); + if( cmp->init() ) + return cmp; + + delete cmp; +#endif + return 0; + } + + TLSBase* ClientBase::getDefaultEncryption() + { + if( m_tls == TLSDisabled || !hasTls() ) + return 0; + + TLSDefault* tls = new TLSDefault( this, m_server ); + if( tls->init( m_clientKey, m_clientCerts, m_cacerts ) ) + return tls; + else + { + delete tls; + return 0; + } + } + +} diff --git a/libs/libgloox/clientbase.h b/libs/libgloox/clientbase.h new file mode 100644 index 0000000..4aadbda --- /dev/null +++ b/libs/libgloox/clientbase.h @@ -0,0 +1,1030 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef CLIENTBASE_H__ +#define CLIENTBASE_H__ + +#include "macros.h" +#include "gloox.h" +#include "eventdispatcher.h" +#include "iqhandler.h" +#include "jid.h" +#include "logsink.h" +#include "mutex.h" +#include "taghandler.h" +#include "statisticshandler.h" +#include "tlshandler.h" +#include "compressiondatahandler.h" +#include "connectiondatahandler.h" +#include "parser.h" + +#include +#include +#include + +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) +#include +#define SECURITY_WIN32 +#include +#endif + +namespace gloox +{ + + class Disco; + class EventHandler; + class Event; + class Tag; + class IQ; + class Message; + class Presence; + class Subscription; + class MessageSessionHandler; + class ConnectionListener; + class MessageHandler; + class MessageSession; + class PresenceHandler; + class SubscriptionHandler; + class MUCInvitationHandler; + class TagHandler; + class TLSBase; + class ConnectionBase; + class CompressionBase; + class StanzaExtensionFactory; + + /** + * @brief This is the common base class for a Jabber/XMPP Client and a Jabber Component. + * + * It manages connection establishing, authentication, filter registration and invocation. + * You should normally use Client for client connections and Component for component connections. + * + * @author Jakob Schroeter + * @since 0.3 + */ + class GLOOX_API ClientBase : public TagHandler, public ConnectionDataHandler, + public CompressionDataHandler, public TLSHandler, + public IqHandler + { + + friend class RosterManager; + + public: + /** + * Constructs a new ClientBase. + * You should not need to use this class directly. Use Client or Component instead. + * @param ns The namespace which qualifies the stream. Either jabber:client or jabber:component:* + * @param server The server to connect to. + * @param port The port to connect to. The default of -1 means to look up the port via DNS SRV + * or to use a default port of 5222 as defined in XMPP Core. + */ + ClientBase( const std::string& ns, const std::string& server, int port = -1 ); + + /** + * Constructs a new ClientBase. + * You should not need to use this class directly. Use Client or Component instead. + * @param ns The namespace which qualifies the stream. Either jabber:client or jabber:component:* + * @param password The password to use for further authentication. + * @param server The server to connect to. + * @param port The port to connect to. The default of -1 means to look up the port via DNS SRV + * or to use a default port of 5222 as defined in XMPP: Core. + */ + ClientBase( const std::string& ns, const std::string& password, + const std::string& server, int port = -1 ); + + /** + * Virtual destrcuctor. + */ + virtual ~ClientBase(); + + /** + * Initiates the connection to a server. This function blocks as long as a connection is + * established. + * You can have the connection block 'til the end of the connection, or you can have it return + * immediately. If you choose the latter, its your responsibility to call @ref recv() every now + * and then to actually receive data from the socket and to feed the parser. + * @param block @b True for blocking, @b false for non-blocking connect. Defaults to @b true. + * @return @b False if prerequisits are not met (server not set) or if the connection was refused, + * @b true otherwise. + * @note Since 0.9 @link ConnectionListener::onDisconnect() onDisconnect() @endlink is called + * in addition to a return value of @b false. + */ + bool connect( bool block = true ); + + /** + * Use this periodically to receive data from the socket and to feed the parser. You need to use + * this only if you chose to connect in non-blocking mode. + * @param timeout The timeout in microseconds to use for select. Default of -1 means blocking + * until data was available. + * @return The state of the connection. + */ + ConnectionError recv( int timeout = -1 ); + + /** + * Reimplement this function to provide a username for connection purposes. + * @return The username. + */ + virtual const std::string& username() const { return m_jid.username(); } + + /** + * Returns the current Jabber ID. If an authorization ID has been set (using setAuthzid()) + * this authzid is returned. + * @return A reference to the Jabber ID. + * @note If you change the server part of the JID, the server of the connection is not synced. + * You have to do that manually using @ref setServer(). + */ + const JID& jid() { return m_authzid ? m_authzid : m_jid; } + + /** + * Switches usage of SASL on/off. Default: on. SASL should only be disabled if there are + * problems with using it. + * @param sasl Whether to switch SASL usage on or off. + */ + void setSasl( bool sasl ) { m_sasl = sasl; } + + /** + * Sets the TLS policy. Default: TLS will be used if available. TLS should only be + * disabled if there are problems with using it. + * @param tls The TLS policy. + */ + void setTls( TLSPolicy tls ) { m_tls = tls; } + + /** + * Switches usage of Stream Compression on/off (if available). Default: on if available. Stream + * Compression should only be disabled if there are problems with using it. + * @param compression Whether to switch Stream Compression usage on or off. + */ + void setCompression( bool compression ) { m_compress = compression; } + + /** + * Sets the port to connect to. This is not necessary if either the default port (5222) is used + * or SRV records exist which will be resolved. + * @param port The port to connect to. + */ + void setPort( int port ) { m_port = port; } + + /** + * Sets the XMPP server to connect to. + * @param server The server to connect to. Either IP or fully qualified domain name. + * @note If you change the server, the server part of the JID is not synced. You have to do that + * manually using @ref jid() and @ref JID::setServer(). + * @note This function also sets the server of the Connection(Base) in use. + */ + void setServer( const std::string &server ); + + /** + * Sets the password to use to connect to the XMPP server. + * @param password The password to use for authentication. + */ + void setPassword( const std::string &password ) { m_password = password; } + + /** + * Returns the current prepped server. + * @return The server used to connect. + */ + const std::string& server() const { return m_server; } + + /** + * Returns whether SASL is currently enabled (not necessarily used). + * @return The current SASL status. + */ + bool sasl() const { return m_sasl; } + + /** + * Returns whether TLS is currently enabled (not necessarily used). + * @return The current TLS status. + */ + TLSPolicy tls() const { return m_tls; } + + /** + * Returns whether Stream Compression is currently enabled (not necessarily used). + * @return The current Stream Compression status. + */ + bool compression() const { return m_compress; } + + /** + * Returns the port. The default of -1 means that the actual port will be looked up using + * SRV records, or the XMPP default port of 5222 will be used. + * @return The port used to connect. + */ + int port() const { return m_port; } + + /** + * Returns the current password. + * @return The password used to connect. + */ + virtual const std::string& password() const { return m_password; } + + /** + * This function gives access to the @c Disco object. + * @return A pointer to the Disco object. + */ + virtual Disco* disco() const { return m_disco; } + + /** + * Creates a string which is unique in the current instance and + * can be used as an ID for queries. + * @return A unique string suitable for query IDs. + */ + const std::string getID(); + + /** + * Sends the given Tag over an established connection. + * The ClientBase object becomes the owner of this Tag and will delete it after sending it. + * You should not rely on the existance of the Tag after it's been sent. If you still need + * it after sending it, use Tag::clone() to create a deep copy. + * @param tag The Tag to send. + */ + void send( Tag* tag ); + + /** + * Sends the given IQ stanza. The given IqHandler is registered to be notified of replies. This, + * of course, only works for IQs of type get or set. An ID is added if necessary. + * @param iq The IQ stanza to send. + * @param ih The handler to register for replies. + * @param context A value that allows for restoring context. + * @param del Whether or not delete the IqHandler object after its being called. + * Default: @b false. + */ + void send( IQ& iq, IqHandler* ih, int context, bool del = false ); + + /** + * A convenience function that sends the given IQ stanza. + * @param iq The IQ stanza to send. + */ + void send( const IQ& iq ); + + /** + * A convenience function that sends the given Message stanza. + * @param msg The Message stanza to send. + */ + void send( const Message& msg ); + + /** + * A convenience function that sends the given Subscription stanza. + * @param sub The Subscription stanza to send. + */ + void send( const Subscription& sub ); + + /** + * A convenience function that sends the given Presence stanza. + * @param pres The Presence stanza to send. + */ + void send( Presence& pres ); + + /** + * Returns whether authentication has taken place and was successful. + * @return @b True if authentication has been carried out @b and was successful, @b false otherwise. + */ + bool authed() const { return m_authed; } + + /** + * Returns the current connection status. + * @return The status of the connection. + */ + ConnectionState state() const; + + /** + * Retrieves the value of the xml:lang attribute of the initial stream. + * Default is 'en', i.e. if not changed by a call to @ref setXmlLang(). + */ + const std::string& xmlLang() const { return m_xmllang; } + + /** + * Sets the value for the xml:lang attribute of the initial stream. + * @param xmllang The language identifier for the stream. It must conform to + * section 2.12 of the XML specification and RFC 3066. + * Default is 'en'. + */ + void setXmlLang( const std::string& xmllang ) { m_xmllang = xmllang; } + + /** + * This function returns the concrete connection implementation currently in use. + * @return The concrete connection implementation. + * @since 0.9 + */ + ConnectionBase* connectionImpl() const { return m_connection; } + + /** + * Use this function if you have a class implementing a UDP, SCTP (or whatever) + * connection. This should be called before calling connect(). If there already is a + * connection implementation set (either manually or automatically), it gets deleted. + * @param cb The connection to use. + * @since 0.9 + */ + void setConnectionImpl( ConnectionBase* cb ); + + /** + * This function returns the concrete encryption implementation currently in use. + * @return The concrete encryption implementation. + * @since 0.9 + */ + TLSBase* encryptionImpl() const { return m_encryption; } + + /** + * Use this function if you have a class supporting hardware encryption (or whatever). + * This should be called before calling connect(). If there already is a + * encryption implementation set (either manually or automatically), it gets deleted. + * @param tb The encryption implementation to use. + * @since 0.9 + */ + void setEncryptionImpl( TLSBase* tb ); + + /** + * This function returns the concrete compression implementation currently in use. + * @return The concrete compression implementation. + * @since 0.9 + */ + CompressionBase* compressionImpl() const { return m_compression; } + + /** + * Use this function if you have a class supporting some fancy compression algorithm. + * This should be called before calling connect(). If there already is a + * compression implementation set (either manually or automatically), it gets deleted. + * @param cb The compression implementation to use. + * @since 0.9 + */ + void setCompressionImpl( CompressionBase* cb ); + + /** + * Sends a whitespace ping to the server. + * @since 0.9 + */ + void whitespacePing(); + + /** + * Sends a XMPP Ping (XEP-0199) to the given JID. + * @param to Then entity to ping. + * @param eh An EventHandler to inform about the reply. + * @since 0.9 + */ + void xmppPing( const JID& to, EventHandler* eh ); + + /** + * Use this function to set an authorization ID (authzid). Provided the server supports it + * and the user has sufficient rights, they could then authenticate as bob@example.net but + * act as alice@example.net. + * @param authzid The JID to authorize as. Only the bare JID is used. + * @since 0.9 + */ + void setAuthzid( const JID& authzid ) { m_authzid = authzid; } + + /** + * Use this function to set an authentication ID (authcid) for SASL PLAIN. + * The default authcid is the username, i.e. the JID's node part. This should work in most cases. + * If this is not what you want to use for authentication, use this function. + * @param authcid The authentication ID. + * @since 1.0 + * @note Right now this is used for SASL PLAIN authentication only. + */ + void setAuthcid( const std::string& authcid ) { m_authcid = authcid; } + + /** + * Use this function to limit SASL mechanisms gloox can use. By default, all + * supported mechanisms are allowed. To exclude one (or more) mechanisms, remove + * it from SaslMechAll like so: + * @code + * int mymechs = SaslMechAll ^ SaslMechDigestMd5; + * @endcode + * @param mechanisms Bitwise ORed @ref SaslMechanism. + * @since 0.9 + */ + void setSASLMechanisms( int mechanisms ) { m_availableSaslMechs = mechanisms; } + + /** + * Registers a new StanzaExtension with the StanzaExtensionFactory. + * @param ext The extension to register. + */ + void registerStanzaExtension( StanzaExtension* ext ); + + /** + * Removes the given StanzaExtension type from the StanzaExtensionFactory. + * @param ext The extension type. + * @return @b True if the given type was found (and removed), @b false otherwise. + */ + bool removeStanzaExtension( int ext ); + + /** + * Registers @c cl as object that receives connection notifications. + * @param cl The object to receive connection notifications. + */ + void registerConnectionListener( ConnectionListener* cl ); + + /** + * Registers @c ih as object that receives notifications for IQ stanzas + * that contain StanzaExtensions of the given type. The number of handlers + * per extension type is not limited. + * @param ih The object to receive IQ stanza notifications. + * @param exttype The extension type. See StanzaExtension and + * @link gloox::StanzaExtensionType StanzaExtensionType @endlink. + * @since 1.0 + */ + void registerIqHandler( IqHandler* ih, int exttype ); + + /** + * Removes the given IqHandler from the list of handlers of pending operations, added + * using trackID(). Necessary, for example, when closing a GUI element that has an + * operation pending. + * @param ih The IqHandler to remove. + * @since 0.8.7 + */ + void removeIDHandler( IqHandler* ih ); + + /** + * Registers @c mh as object that receives Message stanza notifications. + * @param mh The object to receive Message stanza notifications. + */ + void registerMessageHandler( MessageHandler* mh ); + + /** + * Removes the given object from the list of message handlers. + * @param mh The object to remove from the list. + */ + void removeMessageHandler( MessageHandler* mh ); + + /** + * Registers the given MessageSession to receive Messages incoming from the session's + * target JID. + * @note The ClientBase instance becomes the owner of the MessageSession, it will be deleted + * in ClientBase's destructor. To get rid of the session before that, use disposeMessageSession(). + * @param session The MessageSession to register. + * @note Since a MessageSession automatically registers itself with the ClientBase, there is no + * need to call this function directly. + */ + void registerMessageSession( MessageSession* session ); + + /** + * Removes the given MessageSession from the list of MessageSessions and deletes it. + * @param session The MessageSession to be deleted. + */ + void disposeMessageSession( MessageSession* session ); + + /** + * Registers @c ph as object that receives Presence stanza notifications. + * @param ph The object to receive Presence stanza notifications. + */ + void registerPresenceHandler( PresenceHandler* ph ); + + /** + * Registers a new PresenceHandler for the given JID. Presences received for this + * particular JID will not be forwarded to the generic PresenceHandler (and therefore + * the Roster). + * This functionality is primarily intended for the MUC implementation. + * @param jid The JID to 'watch'. + * @param ph The PresenceHandler to inform about presence changes from @c jid. + * @since 0.9 + */ + void registerPresenceHandler( const JID& jid, PresenceHandler* ph ); + + /** + * Registers @c sh as object that receives Subscription stanza notifications. + * @param sh The object to receive Subscription stanza notifications. + */ + void registerSubscriptionHandler( SubscriptionHandler* sh ); + + /** + * Registers @c th as object that receives incoming packts with a given root tag + * qualified by the given namespace. + * @param th The object to receive Subscription packet notifications. + * @param tag The element's name. + * @param xmlns The element's namespace. + */ + void registerTagHandler( TagHandler* th, const std::string& tag, + const std::string& xmlns ); + + /** + * Registers @c sh as object that receives up-to-date connection statistics each time + * a Stanza is received or sent. Alternatively, you can use getStatistics() manually. + * Only one StatisticsHandler per ClientBase at a time is possible. + * @param sh The StatisticsHandler to register. + */ + void registerStatisticsHandler( StatisticsHandler* sh ); + + /** + * Removes the given object from the list of connection listeners. + * @param cl The object to remove from the list. + */ + void removeConnectionListener( ConnectionListener* cl ); + + /** + * Removes the given IQ handler for the given extension type. + * @param ih The IqHandler. + * @param exttype The extension type. See + * @link gloox::StanzaExtensionType StanzaExtensionType @endlink. + * @since 1.0 + */ + void removeIqHandler( IqHandler* ih, int exttype ); + + /** + * Removes the given object from the list of presence handlers. + * @param ph The object to remove from the list. + */ + void removePresenceHandler( PresenceHandler* ph ); + + /** + * Removes the given object from the list of presence handlers for the given JID. + * @param jid The JID to remove the PresenceHandler(s) for. + * @param ph The PresenceHandler to remove from the list. If @c ph is 0, + * all handlers for the given JID will be removed. + */ + void removePresenceHandler( const JID& jid, PresenceHandler* ph ); + + /** + * Removes the given object from the list of subscription handlers. + * @param sh The object to remove from the list. + */ + void removeSubscriptionHandler( SubscriptionHandler* sh ); + + /** + * Removes the given object from the list of tag handlers for the given element and namespace. + * @param th The object to remove from the list. + * @param tag The element to remove the handler for. + * @param xmlns The namespace qualifying the element. + */ + void removeTagHandler( TagHandler* th, const std::string& tag, + const std::string& xmlns ); + + /** + * Removes the current StatisticsHandler. + */ + void removeStatisticsHandler(); + + /** + * Use this function to set a number of trusted root CA certificates which shall be + * used to verify a servers certificate. + * @param cacerts A list of absolute paths to CA root certificate files in PEM format. + */ + void setCACerts( const StringList& cacerts ) { m_cacerts = cacerts; } + + /** + * Use this function to set the user's certificate and private key. The certificate will + * be presented to the server upon request and can be used for SASL EXTERNAL authentication. + * The user's certificate file should be a bundle of more than one certificate in PEM format. + * The first one in the file should be the user's certificate, each cert following that one + * should have signed the previous one. + * @note These certificates are not necessarily the same as those used to verify the server's + * certificate. + * @param clientKey The absolute path to the user's private key in PEM format. + * @param clientCerts A path to a certificate bundle in PEM format. + */ + void setClientCert( const std::string& clientKey, const std::string& clientCerts ); + + /** + * Use this function to register a MessageSessionHandler with the Client. + * Optionally the MessageSessionHandler can receive only MessageSessions with a given + * message type. There can be only one handler per message type.
+ * A MessageSession will be created for every incoming + * message stanza if there is no MessageHandler registered for the originating JID. + * @param msh The MessageSessionHandler that will receive the newly created MessageSession. + * @param types ORed StanzaSubType's that describe the desired message types the handler + * shall receive. Only StanzaMessage* types are valid. A value of 0 means any type (default). + */ + void registerMessageSessionHandler( MessageSessionHandler* msh, int types = 0 ); + + /** + * Returns the LogSink instance for this ClientBase and all related objects. + * @return The LogSink instance used in the current ClientBase. + */ + LogSink& logInstance() { return m_logInstance; } + + /** + * Use this function to retrieve the type of the stream error after it occurs and you received a + * ConnectionError of type @b ConnStreamError from the ConnectionListener. + * @return The StreamError. + * @note The return value is only meaningful when called from ConnectionListener::onDisconnect(). + */ + StreamError streamError() const { return m_streamError; } + + /** + * Returns the text of a stream error for the given language if available. + * If the requested language is not available, the default text (without a xml:lang + * attribute) will be returned. + * @param lang The language identifier for the desired language. It must conform to + * section 2.12 of the XML specification and RFC 3066. If empty, the default body + * will be returned, if any. + * @return The describing text of a stream error. Empty if no stream error occured. + */ + const std::string& streamErrorText( const std::string& lang = "default" ) const; + + /** + * In case the defined-condition element of an stream error contains XML character data you can + * use this function to retrieve it. RFC 3920 only defines one condition (see-other-host)where + * this is possible. + * @return The cdata of the stream error's text element (only for see-other-host). + */ + const std::string& streamErrorCData() const { return m_streamErrorCData; } + + /** + * This function can be used to retrieve the application-specific error condition of a stream error. + * @return The application-specific error element of a stream error. 0 if no respective element was + * found or no error occured. + */ + const Tag* streamErrorAppCondition() const { return m_streamErrorAppCondition; } + + /** + * Use this function to retrieve the type of the authentication error after it occurs and you + * received a ConnectionError of type @b ConnAuthenticationFailed from the ConnectionListener. + * @return The type of the authentication, if any, @b AuthErrorUndefined otherwise. + */ + AuthenticationError authError() const { return m_authError; } + + /** + * Returns a StatisticsStruct containing byte and stanza counts for the current + * active connection. + * @return A struct containing the current connection's statistics. + */ + StatisticsStruct getStatistics(); + + /** + * Registers a MUCInvitationHandler with the ClientBase. + * @param mih The MUCInvitationHandler to register. + */ + void registerMUCInvitationHandler( MUCInvitationHandler* mih ); + + /** + * Removes the currently registered MUCInvitationHandler. + */ + void removeMUCInvitationHandler(); + + /** + * Adds a StanzaExtension that will be sent with every Presence stanza + * sent. Capabilities are included by default if you are using a Client. + * @param se A StanzaExtension to add. If an extension of the same type + * has been added previously it will be replaced by the new one. + * Use removePresenceExtension() to remove an extension. + */ + void addPresenceExtension( StanzaExtension* se ); + + /** + * Removes the StanzaExtension of the given type from the list of Presence + * StanzaExtensions. + * Use addPresenceExtension() to replace an already added type. + */ + bool removePresenceExtension( int type ); + + /** + * Returns the current list of Presence StanzaExtensions. + * @return The current list of Presence StanzaExtensions. + */ + const StanzaExtensionList& presenceExtensions() const { return m_presenceExtensions; } + + // reimplemented from ParserHandler + virtual void handleTag( Tag* tag ); + + // reimplemented from CompressionDataHandler + virtual void handleCompressedData( const std::string& data ); + + // reimplemented from CompressionDataHandler + virtual void handleDecompressedData( const std::string& data ); + + // reimplemented from ConnectionDataHandler + virtual void handleReceivedData( const ConnectionBase* connection, const std::string& data ); + + // reimplemented from ConnectionDataHandler + virtual void handleConnect( const ConnectionBase* connection ); + + // reimplemented from ConnectionDataHandler + virtual void handleDisconnect( const ConnectionBase* connection, ConnectionError reason ); + + // reimplemented from TLSHandler + virtual void handleEncryptedData( const TLSBase* base, const std::string& data ); + + // reimplemented from TLSHandler + virtual void handleDecryptedData( const TLSBase* base, const std::string& data ); + + // reimplemented from TLSHandler + virtual void handleHandshakeResult( const TLSBase* base, bool success, CertInfo &certinfo ); + + protected: + /** + * This function is called when resource binding yieled an error. + * @param error A pointer to an Error object that contains more + * information. May be 0. + */ + void notifyOnResourceBindError( const Error* error ); + + /** + * This function is called when binding a resource succeeded. + * @param resource The bound resource. + */ + void notifyOnResourceBind( const std::string& resource ); + + /** + * This function is called when session creation yieled an error. + * @param error A pointer to an Error object that contains more + * information. May be 0. + */ + void notifyOnSessionCreateError( const Error* error ); + + /** + * This function is called when the TLS handshake completed correctly. The return + * value is used to determine whether or not the client accepted the server's + * certificate. If @b false is returned the connection is closed. + * @param info Information on the server's certificate. + * @return @b True if the certificate seems trustworthy, @b false otherwise. + */ + bool notifyOnTLSConnect( const CertInfo& info ); + + /** + * This function is called to notify about successful connection. + */ + void notifyOnConnect(); + + /** + * This function is used to notify subscribers of stream events. + * @param event The event to publish. + */ + void notifyStreamEvent( StreamEvent event ); + + /** + * Disconnects the underlying stream and broadcasts the given reason. + * @param reason The reason for the disconnect. + */ + virtual void disconnect( ConnectionError reason ); + + /** + * Sends the stream header. + */ + void header(); + + /** + * Tells ClientBase that authentication was successful (or not). + * @param authed Whether or not authentication was successful. + */ + void setAuthed( bool authed ) { m_authed = authed; } + + /** + * If authentication failed, this function tells ClientBase + * the reason. + * @param e The reason for the authentication failure. + */ + void setAuthFailure( AuthenticationError e ) { m_authError = e; } + + /** + * Implementors of this function can check if they support the advertized stream version. + * The return value indicates whether or not the stream can be handled. A default + * implementation is provided. + * @param version The advertized stream version. + * @return @b True if the stream can be handled, @b false otherwise. + */ + virtual bool checkStreamVersion( const std::string& version ); + + /** + * Starts authentication using the given SASL mechanism. + * @param type A SASL mechanism to use for authentication. + */ + void startSASL( SaslMechanism type ); + + /** + * Releases SASL related resources. + */ + void processSASLSuccess(); + + /** + * Processes the given SASL challenge and sends a response. + * @param challenge The SASL challenge to process. + */ + void processSASLChallenge( const std::string& challenge ); + + /** + * Examines the given Tag for SASL errors. + * @param tag The Tag to parse. + */ + void processSASLError( Tag* tag ); + + /** + * Sets the domain to use in SASL NTLM authentication. + * @param domain The domain. + */ + void setNTLMDomain( const std::string& domain ) { m_ntlmDomain = domain; } + + /** + * Starts the TLS handshake. + */ + void startTls(); + + /** + * Indicates whether or not TLS is supported. + * @return @b True if TLS is supported, @b false otherwise. + */ + bool hasTls(); + + JID m_jid; /**< The 'self' JID. */ + JID m_authzid; /**< An optional authorization ID. See setAuthzid(). */ + std::string m_authcid; /**< An alternative authentication ID. See setAuthcid(). */ + ConnectionBase* m_connection; /**< The transport connection. */ + TLSBase* m_encryption; /**< Used for connection encryption. */ + CompressionBase* m_compression; /**< Used for connection compression. */ + Disco* m_disco; /**< The local Service Discovery client. */ + + /** A list of permanent presence extensions. */ + StanzaExtensionList m_presenceExtensions; + + std::string m_selectedResource; /**< The currently selected resource. + * See Client::selectResource() and Client::binRessource(). */ + std::string m_clientCerts; /**< TLS client certificates. */ + std::string m_clientKey; /**< TLS client private key. */ + std::string m_namespace; /**< Default namespace. */ + std::string m_password; /**< Client's password. */ + std::string m_xmllang; /**< Default value of the xml:lang attribute. */ + std::string m_server; /**< The server to connect to, if different from the + * JID's server. */ + std::string m_sid; /**< The stream ID. */ + bool m_compressionActive; /**< Indicates whether or not stream compression + * is currently activated. */ + bool m_encryptionActive; /**< Indicates whether or not stream encryption + * is currently activated. */ + bool m_compress; /**< Whether stream compression + * is desired at all. */ + bool m_authed; /**< Whether authentication has been completed successfully. */ + bool m_block; /**< Whether blocking connection is wanted. */ + bool m_sasl; /**< Whether SASL authentication is wanted. */ + TLSPolicy m_tls; /**< The current TLS policy. */ + int m_port; /**< The port to connect to, if not to be determined + * by querying the server's SRV records. */ + + int m_availableSaslMechs; /**< The SASL mechanisms the server offered. */ + + private: +#ifdef CLIENTBASE_TEST + public: +#endif + /** + * @brief This is an implementation of an XMPP Ping (XEP-199). + * + * @author Jakob Schroeter + * @since 1.0 + */ + class Ping : public StanzaExtension + { + + public: + /** + * Constructs a new object. + */ + Ping(); + + /** + * Destructor. + */ + virtual ~Ping(); + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + (void)tag; + return new Ping(); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const + { + return new Tag( "ping", "xmlns", XMLNS_XMPP_PING ); + } + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new Ping(); + } + + }; + + ClientBase( const ClientBase& ); + ClientBase& operator=( const ClientBase& ); + + /** + * This function is called right after the opening <stream:stream> was received. + */ + virtual void handleStartNode() = 0; + + /** + * This function is called for each Tag. Only stream initiation/negotiation should + * be done here. + * @param tag A Tag to handle. + */ + virtual bool handleNormalNode( Tag* tag ) = 0; + virtual void rosterFilled() = 0; + virtual void cleanup() {} + virtual void handleIqIDForward( const IQ& iq, int context ) { (void) iq; (void) context; } + + void parse( const std::string& data ); + void init(); + void handleStreamError( Tag* tag ); + TLSBase* getDefaultEncryption(); + CompressionBase* getDefaultCompression(); + + void notifyIqHandlers( IQ& iq ); + void notifyMessageHandlers( Message& msg ); + void notifyPresenceHandlers( Presence& presence ); + void notifySubscriptionHandlers( Subscription& s10n ); + void notifyTagHandlers( Tag* tag ); + void notifyOnDisconnect( ConnectionError e ); + void send( const std::string& xml ); + void addFrom( Tag* tag ); + void addNamespace( Tag* tag ); + + // reimplemented from IqHandler + virtual bool handleIq( const IQ& iq ); + + // reimplemented from IqHandler + virtual void handleIqID( const IQ& iq, int context ); + + struct TrackStruct + { + IqHandler* ih; + int context; + bool del; + }; + + struct TagHandlerStruct + { + TagHandler* th; + std::string xmlns; + std::string tag; + }; + + struct JidPresHandlerStruct + { + JID* jid; + PresenceHandler* ph; + }; + + enum TrackContext + { + XMPPPing + }; + + typedef std::list ConnectionListenerList; + typedef std::multimap IqHandlerMapXmlns; + typedef std::multimap IqHandlerMap; + typedef std::map IqTrackMap; + typedef std::map MessageHandlerMap; + typedef std::list MessageSessionList; + typedef std::list MessageHandlerList; + typedef std::list PresenceHandlerList; + typedef std::list PresenceJidHandlerList; + typedef std::list SubscriptionHandlerList; + typedef std::list TagHandlerList; + + ConnectionListenerList m_connectionListeners; + IqHandlerMapXmlns m_iqNSHandlers; + IqHandlerMap m_iqExtHandlers; + IqTrackMap m_iqIDHandlers; + MessageSessionList m_messageSessions; + MessageHandlerList m_messageHandlers; + PresenceHandlerList m_presenceHandlers; + PresenceJidHandlerList m_presenceJidHandlers; + SubscriptionHandlerList m_subscriptionHandlers; + TagHandlerList m_tagHandlers; + StringList m_cacerts; + StatisticsHandler * m_statisticsHandler; + MUCInvitationHandler * m_mucInvitationHandler; + MessageSessionHandler * m_messageSessionHandlerChat; + MessageSessionHandler * m_messageSessionHandlerGroupchat; + MessageSessionHandler * m_messageSessionHandlerHeadline; + MessageSessionHandler * m_messageSessionHandlerNormal; + + util::Mutex m_iqHandlerMapMutex; + + Parser m_parser; + LogSink m_logInstance; + StanzaExtensionFactory* m_seFactory; + EventDispatcher m_dispatcher; + + AuthenticationError m_authError; + StreamError m_streamError; + StringMap m_streamErrorText; + std::string m_streamErrorCData; + Tag* m_streamErrorAppCondition; + + StatisticsStruct m_stats; + + SaslMechanism m_selectedSaslMech; + + std::string m_ntlmDomain; + bool m_autoMessageSession; + +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + CredHandle m_credHandle; + CtxtHandle m_ctxtHandle; +#endif + + }; + +} + +#endif // CLIENTBASE_H__ diff --git a/libs/libgloox/component.cpp b/libs/libgloox/component.cpp new file mode 100644 index 0000000..9f2e202 --- /dev/null +++ b/libs/libgloox/component.cpp @@ -0,0 +1,62 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "component.h" + +#include "disco.h" +#include "stanza.h" +#include "prep.h" +#include "sha.h" + +#include + +namespace gloox +{ + + Component::Component( const std::string& ns, const std::string& server, + const std::string& component, const std::string& password, int port ) + : ClientBase( ns, password, server, port ) + { + m_jid.setServer( component ); + m_disco->setIdentity( "component", "generic" ); + } + + void Component::handleStartNode() + { + if( m_sid.empty() ) + return; + + notifyStreamEvent( StreamEventAuthentication ); + + SHA sha; + sha.feed( m_sid + m_password ); + sha.finalize(); + + Tag* h = new Tag( "handshake", sha.hex() ); + send( h ); + } + + bool Component::handleNormalNode( Tag* tag ) + { + if( tag->name() != "handshake" ) + return false; + + m_authed = true; + notifyStreamEvent( StreamEventFinished ); + notifyOnConnect(); + + return true; + } + +} diff --git a/libs/libgloox/component.h b/libs/libgloox/component.h new file mode 100644 index 0000000..d6c02d7 --- /dev/null +++ b/libs/libgloox/component.h @@ -0,0 +1,77 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef COMPONENT_H__ +#define COMPONENT_H__ + +#include "clientbase.h" + +#include + +namespace gloox +{ + + /** + * @brief This is an implementation of a basic jabber Component. + * + * It's using XEP-0114 (Jabber Component Protocol) to authenticate with a server. + * + * @author Jakob Schroeter + * @since 0.3 + */ + class GLOOX_API Component : public ClientBase + { + public: + /** + * Constructs a new Component. + * @param ns The namespace that qualifies the stream. Either @b jabber:component:accept or + * @b jabber:component:connect. See XEP-0114 for details. + * @param server The server to connect to. + * @param component The component's hostname. FQDN. + * @param password The component's password. + * @param port The port to connect to. The default of 5347 is the default port of the router + * in jabberd2. + */ + Component( const std::string& ns, const std::string& server, + const std::string& component, const std::string& password, int port = 5347 ); + + /** + * Virtual Destructor. + */ + virtual ~Component() {} + + /** + * Disconnects from the server. + */ + void disconnect() { ClientBase::disconnect( ConnUserDisconnected ); } + + protected: + // reimplemented from ClientBase + virtual void handleStartNode(); + + // reimplemented from ClientBase + virtual bool handleNormalNode( Tag* tag ); + + // reimplemented from ClientBase + virtual bool checkStreamVersion( const std::string& /*version*/ ) { return true; } + + private: + // reimplemented from ClientBase + virtual void rosterFilled() {} + + }; + +} + +#endif // COMPONENT_H__ diff --git a/libs/libgloox/compressionbase.h b/libs/libgloox/compressionbase.h new file mode 100644 index 0000000..c3a178b --- /dev/null +++ b/libs/libgloox/compressionbase.h @@ -0,0 +1,85 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef COMPRESSIONBASE_H__ +#define COMPRESSIONBASE_H__ + +#include "gloox.h" +#include "compressiondatahandler.h" + +#include + +namespace gloox +{ + + /** + * @brief This is an abstract base class for stream compression implementations. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API CompressionBase + { + public: + /** + * Contructor. + * @param cdh A CompressionDataHandler-derived object that will be notified + * about finished de/compression. + */ + CompressionBase( CompressionDataHandler* cdh ) : m_handler( cdh ), m_valid( false ) {} + + /** + * Virtual Destructor. + */ + virtual ~CompressionBase() {} + + /** + * This function initializes the compression module. + * it is mandatory to be called. + * @return @b True if the module was initialized successfully, false otherwise. + */ + virtual bool init() = 0; + + /** + * Compresses the given chunk of data. + * @param data The original (uncompressed) data. + */ + virtual void compress( const std::string& data ) = 0; + + /** + * Decompresses the given chunk of data. + * @param data The compressed data. + */ + virtual void decompress( const std::string& data ) = 0; + + /** + * Performs internal cleanup. + * @since 1.0 + */ + virtual void cleanup() = 0; + + protected: + /** A handler for compressed/uncompressed data. */ + CompressionDataHandler* m_handler; + + /** Whether the compression module can be used. */ + bool m_valid; + + }; + +} + +#endif // COMPRESSIONBASE_H__ diff --git a/libs/libgloox/compressiondatahandler.h b/libs/libgloox/compressiondatahandler.h new file mode 100644 index 0000000..0eb5178 --- /dev/null +++ b/libs/libgloox/compressiondatahandler.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef COMPRESSIONDATAHANDLER_H__ +#define COMPRESSIONDATAHANDLER_H__ + +#include "macros.h" + +#include + +namespace gloox +{ + + /** + * @brief An abstract base class used to receive de/compressed data from a + * CompressionBase-derived object. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API CompressionDataHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~CompressionDataHandler() {} + + /** + * This function is called when compression is finished. + * @param data The compressed data. + */ + virtual void handleCompressedData( const std::string& data ) = 0; + + /** + * This function is called when decompression is finished. + * @param data The decompressed data. + */ + virtual void handleDecompressedData( const std::string& data ) = 0; + + }; + +} + +#endif // COMPRESSIONDATAHANDLER_H__ diff --git a/libs/libgloox/compressiondefault.cpp b/libs/libgloox/compressiondefault.cpp new file mode 100644 index 0000000..066cc93 --- /dev/null +++ b/libs/libgloox/compressiondefault.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2009 by Jakob Schroeter + * This file is part of the gloox library. http://camaya.net/gloox + * + * This software is distributed under a license. The full license + * agreement can be found in the file LICENSE in this distribution. + * This software may not be copied, modified, sold or distributed + * other than expressed in the named license agreement. + * + * This software is distributed without any warranty. + */ + +#include "compressiondefault.h" + +#include "compressiondatahandler.h" + +#include "config.h" + +#if defined( HAVE_ZLIB ) +# define HAVE_COMPRESSION +# include "compressionzlib.h" +#endif + +// #if defined( HAVE_LZW ) +// # define HAVE_COMPRESSION +// # include "compressionlzw.h" +// #endif + +namespace gloox +{ + + CompressionDefault::CompressionDefault( CompressionDataHandler* cdh, Method method ) + : CompressionBase( cdh ), m_impl( 0 ) + { + switch( method ) + { + case MethodZlib: +#ifdef HAVE_ZLIB + m_impl = new CompressionZlib( cdh ); +#endif + break; + case MethodLZW: +#ifdef HAVE_LZW + m_impl = new CompressionLZW( cdh ); +#endif + break; + default: + break; + } + } + + CompressionDefault::~CompressionDefault() + { + delete m_impl; + } + + bool CompressionDefault::init() + { + return m_impl ? m_impl->init() : false; + } + + int CompressionDefault::types() + { + int types = 0; +#ifdef HAVE_ZLIB + types |= MethodZlib; +#endif +#ifdef HAVE_LZW + types |= MethodLZW; +#endif + return types; + } + + void CompressionDefault::compress( const std::string& data ) + { + if( m_impl ) + m_impl->compress( data ); + } + + void CompressionDefault::decompress( const std::string& data ) + { + if( m_impl ) + m_impl->decompress( data ); + } + + void CompressionDefault::cleanup() + { + if( m_impl ) + m_impl->cleanup(); + } + +} diff --git a/libs/libgloox/compressiondefault.h b/libs/libgloox/compressiondefault.h new file mode 100644 index 0000000..38b83f0 --- /dev/null +++ b/libs/libgloox/compressiondefault.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2009 by Jakob Schroeter + * This file is part of the gloox library. http://camaya.net/gloox + * + * This software is distributed under a license. The full license + * agreement can be found in the file LICENSE in this distribution. + * This software may not be copied, modified, sold or distributed + * other than expressed in the named license agreement. + * + * This software is distributed without any warranty. + */ + + +#ifndef COMPRESSIONDEFAULT_H__ +#define COMPRESSIONDEFAULT_H__ + +#include "compressionbase.h" + +namespace gloox +{ + + class CompressionDataHandler; + + /** + * @brief This is an abstraction of the various Compression implementations. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API CompressionDefault : public CompressionBase + { + public: + + /** + * Supported ctypes. + */ + enum Method + { + MethodZlib = 1, /**< Zlib compression. */ + MethodLZW = 2 /**< LZW compression. */ + }; + + /** + * Constructs a new compression wrapper. + * @param cdh The CompressionDataHandler to handle de/compressed data. + * @param method The desired compression method. + */ + CompressionDefault( CompressionDataHandler* cdh, Method method = MethodZlib ); + + /** + * Virtual Destructor. + */ + virtual ~CompressionDefault(); + + /** + * Returns an int holding the available compression types, ORed. + * @return An int holding the available compression types, ORed. + */ + static int types(); + + // reimplemented from CompressionBase + virtual bool init(); + + // reimplemented from CompressionBase + virtual void compress( const std::string& data ); + + // reimplemented from CompressionBase + virtual void decompress( const std::string& data ); + + // reimplemented from CompressionBase + virtual void cleanup(); + + private: + CompressionBase* m_impl; + + }; + +} + +#endif // COMPRESSIONDEFAULT_H__ diff --git a/libs/libgloox/compressionzlib.cpp b/libs/libgloox/compressionzlib.cpp new file mode 100644 index 0000000..4d0b937 --- /dev/null +++ b/libs/libgloox/compressionzlib.cpp @@ -0,0 +1,135 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "compressionzlib.h" + +#ifdef HAVE_ZLIB + +namespace gloox +{ + + CompressionZlib::CompressionZlib( CompressionDataHandler* cdh ) + : CompressionBase( cdh ) + { + } + + bool CompressionZlib::init() + { + int ret = Z_OK; + m_zinflate.zalloc = Z_NULL; + m_zinflate.zfree = Z_NULL; + m_zinflate.opaque = Z_NULL; + m_zinflate.avail_in = 0; + m_zinflate.next_in = Z_NULL; + ret = inflateInit( &m_zinflate ); + if( ret != Z_OK ) + return false; + + m_zdeflate.zalloc = Z_NULL; + m_zdeflate.zfree = Z_NULL; + m_zdeflate.opaque = Z_NULL; + m_zinflate.avail_in = 0; + m_zinflate.next_in = Z_NULL; + ret = deflateInit( &m_zdeflate, Z_BEST_COMPRESSION/*Z_DEFAULT_COMPRESSION*/ ); + if( ret != Z_OK ) + return false; + + m_valid = true; + return true; + } + + CompressionZlib::~CompressionZlib() + { + cleanup(); + } + + void CompressionZlib::compress( const std::string& data ) + { + if( !m_valid ) + init(); + + if( !m_valid || !m_handler || data.empty() ) + return; + + long unsigned int CHUNK = data.length() + ( data.length() / 100 ) + 13; + Bytef* out = new Bytef[CHUNK]; + char* in = const_cast( data.c_str() ); + + m_compressMutex.lock(); + + m_zdeflate.avail_in = static_cast( data.length() ); + m_zdeflate.next_in = (Bytef*)in; + + int ret; + std::string result; + do { + m_zdeflate.avail_out = static_cast( CHUNK ); + m_zdeflate.next_out = (Bytef*)out; + + ret = deflate( &m_zdeflate, Z_SYNC_FLUSH ); + result.append( (char*)out, CHUNK - m_zdeflate.avail_out ); + } while( m_zdeflate.avail_out == 0 ); + + m_compressMutex.unlock(); + + delete[] out; + + m_handler->handleCompressedData( result ); + } + + void CompressionZlib::decompress( const std::string& data ) + { + if( !m_valid ) + init(); + + if( !m_valid || !m_handler || data.empty() ) + return; + + int CHUNK = 50; + char* out = new char[CHUNK]; + char* in = const_cast( data.c_str() ); + + m_zinflate.avail_in = static_cast( data.length() ); + m_zinflate.next_in = (Bytef*)in; + + int ret = Z_OK; + std::string result; + do + { + m_zinflate.avail_out = CHUNK; + m_zinflate.next_out = (Bytef*)out; + + ret = inflate( &m_zinflate, Z_SYNC_FLUSH ); + result.append( out, CHUNK - m_zinflate.avail_out ); + } while( m_zinflate.avail_out == 0 ); + + delete[] out; + + m_handler->handleDecompressedData( result ); + } + + void CompressionZlib::cleanup() + { + if( !m_valid ) + return; + + inflateEnd( &m_zinflate ); + deflateEnd( &m_zdeflate ); + + m_valid = false; + } + +} + +#endif // HAVE_ZLIB diff --git a/libs/libgloox/compressionzlib.h b/libs/libgloox/compressionzlib.h new file mode 100644 index 0000000..3f7ba6e --- /dev/null +++ b/libs/libgloox/compressionzlib.h @@ -0,0 +1,74 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef COMPRESSIONZLIB_H__ +#define COMPRESSIONZLIB_H__ + +#include "compressionbase.h" +#include "mutex.h" + +#include "config.h" + +#ifdef HAVE_ZLIB + +#include + +#include + +namespace gloox +{ + /** + * An implementation of CompressionBase using zlib. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API CompressionZlib : public CompressionBase + { + public: + /** + * Contructor. + * @param cdh The CompressionDataHandler to receive de/compressed data. + */ + CompressionZlib( CompressionDataHandler* cdh ); + + /** + * Virtual Destructor. + */ + virtual ~CompressionZlib(); + + // reimplemented from CompressionBase + virtual bool init(); + + // reimplemented from CompressionBase + virtual void compress( const std::string& data ); + + // reimplemented from CompressionBase + virtual void decompress( const std::string& data ); + + // reimplemented from CompressionBase + virtual void cleanup(); + + private: + z_stream m_zinflate; + z_stream m_zdeflate; + + util::Mutex m_compressMutex; + + }; + +} + +#endif // HAVE_ZLIB + +#endif // COMPRESSIONZLIB_H__ diff --git a/libs/libgloox/config.h b/libs/libgloox/config.h new file mode 100644 index 0000000..fb5d2af --- /dev/null +++ b/libs/libgloox/config.h @@ -0,0 +1,27 @@ +/* + Copyright (c) 2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef CONFIG_H__ +#define CONFIG_H__ + +#if ( defined _WIN32 ) && !defined( __SYMBIAN32__ ) +# include "../config.h.win" +#elif defined( _WIN32_WCE ) +# include "../config.h.win" +#elif defined( __SYMBIAN32__ ) +# include "../config.h.symbian" +#else +# include "config.h.unix" // run ./configure to create config.h.unix +#endif + +#endif // CONFIG_H__ diff --git a/libs/libgloox/config.h.unix b/libs/libgloox/config.h.unix new file mode 100644 index 0000000..9d24b5b --- /dev/null +++ b/libs/libgloox/config.h.unix @@ -0,0 +1,123 @@ +/* config.h.unix. Generated from config.h.unix.in by configure. */ +/* config.h.unix.in. Generated from configure.ac by autoheader. */ + +/* Define to 1 if you have the header file. */ +#define HAVE_ARPA_NAMESER_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the `dn_skipname' function. */ +/* #undef HAVE_DN_SKIPNAME */ + +/* Define to 1 if you have the header file. */ +#define HAVE_ERRNO_H 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +/* #undef HAVE_GETADDRINFO */ + +/* Define to 1 if you want TLS support (GnuTLS). Undefine HAVE_OPENSSL. */ +/* #undef HAVE_GNUTLS */ + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `bind' library (-lbind). */ +/* #undef HAVE_LIBBIND */ + +/* Define to 1 if you want IDN support. */ +/* #undef HAVE_LIBIDN */ + +/* Define to 1 if you have the `network' library (-lnetwork). */ +#define HAVE_LIBNETWORK 1 + +/* Define to 1 if you have the `resolv' library (-lresolv). */ +/* #undef HAVE_LIBRESOLV */ + +/* Define to 1 if you have the `socket' library (-lsocket). */ +/* #undef HAVE_LIBSOCKET */ + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you want TLS support (OpenSSL). Undefine HAVE_GNUTLS. */ +#define HAVE_OPENSSL 1 + +/* Define if you have POSIX threads libraries and header files. */ +#define HAVE_PTHREAD 1 + +/* Define to 1 if you have the `res_query' function. */ +/* #undef HAVE_RES_QUERY */ + +/* Define to 1 if you have the `res_querydomain' function. */ +/* #undef HAVE_RES_QUERYDOMAIN */ + +/* Define to 1 if you have the `setsockopt' function. */ +#define HAVE_SETSOCKOPT 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you want Stream Compression support. */ +#define HAVE_ZLIB 1 + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define LT_OBJDIR ".libs/" + +/* Name of package */ +#define PACKAGE "gloox" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "js@camaya.net" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "gloox" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "gloox 1.0" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "gloox" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "1.0" + +/* Define to necessary symbol if this constant uses a non-standard name on + your system. */ +/* #undef PTHREAD_CREATE_JOINABLE */ + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Version number of package */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +/* #undef inline */ +#endif diff --git a/libs/libgloox/connectionbase.h b/libs/libgloox/connectionbase.h new file mode 100644 index 0000000..ca2a96c --- /dev/null +++ b/libs/libgloox/connectionbase.h @@ -0,0 +1,167 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef CONNECTIONBASE_H__ +#define CONNECTIONBASE_H__ + +#include "gloox.h" +#include "connectiondatahandler.h" + +#include + +namespace gloox +{ + + /** + * @brief An abstract base class for a connection. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API ConnectionBase + { + public: + /** + * Constructor. + * @param cdh An object derived from @ref ConnectionDataHandler that will receive + * received data. + */ + ConnectionBase( ConnectionDataHandler* cdh ) + : m_handler( cdh ), m_state( StateDisconnected ), m_port( -1 ) + {} + + /** + * Virtual destructor. + */ + virtual ~ConnectionBase() { cleanup(); } + + /** + * Used to initiate the connection. + * @return Returns the connection state. + */ + virtual ConnectionError connect() = 0; + + /** + * Use this periodically to receive data from the socket. + * @param timeout The timeout to use for select in microseconds. Default of -1 means blocking. + * @return The state of the connection. + */ + virtual ConnectionError recv( int timeout = -1 ) = 0; + + /** + * Use this function to send a string of data over the wire. The function returns only after + * all data has been sent. + * @param data The data to send. + * @return @b True if the data has been sent (no guarantee of receipt), @b false + * in case of an error. + */ + virtual bool send( const std::string& data ) = 0; + + /** + * Use this function to put the connection into 'receive mode', i.e. this function returns only + * when the connection is terminated. + * @return Returns a value indicating the disconnection reason. + */ + virtual ConnectionError receive() = 0; + + /** + * Disconnects an established connection. NOOP if no active connection exists. + */ + virtual void disconnect() = 0; + + /** + * This function is called after a disconnect to clean up internal state. It is also called by + * ConnectionBase's destructor. + */ + virtual void cleanup() {} + + /** + * Returns the current connection state. + * @return The state of the connection. + */ + ConnectionState state() const { return m_state; } + + /** + * Use this function to register a new ConnectionDataHandler. There can be only one + * ConnectionDataHandler at any one time. + * @param cdh The new ConnectionDataHandler. + */ + void registerConnectionDataHandler( ConnectionDataHandler* cdh ) { m_handler = cdh; } + + /** + * Sets the server to connect to. + * @param server The server to connect to. Either IP or fully qualified domain name. + * @param port The port to connect to. + */ + void setServer( const std::string &server, int port = -1 ) { m_server = server; m_port = port; } + + /** + * Returns the currently set server/IP. + * @return The server host/IP. + */ + const std::string& server() const { return m_server; } + + /** + * Returns the currently set port. + * @return The server port. + */ + int port() const { return m_port; } + + /** + * Returns the local port. + * @return The local port. + */ + virtual int localPort() const { return -1; } + + /** + * Returns the locally bound IP address. + * @return The locally bound IP address. + */ + virtual const std::string localInterface() const { return EmptyString; } + + /** + * Returns current connection statistics. + * @param totalIn The total number of bytes received. + * @param totalOut The total number of bytes sent. + */ + virtual void getStatistics( long int &totalIn, long int &totalOut ) = 0; + + /** + * This function returns a new instance of the current ConnectionBase-derived object. + * The idea is to be able to 'clone' ConnectionBase-derived objects without knowing of + * what type they are exactly. + * @return A new Connection* instance. + */ + virtual ConnectionBase* newInstance() const = 0; + + protected: + /** A handler for incoming data and connect/disconnect events. */ + ConnectionDataHandler* m_handler; + + /** Holds the current connection state. */ + ConnectionState m_state; + + /** Holds the server's name/address. */ + std::string m_server; + + /** Holds the port to connect to. */ + int m_port; + + }; + +} + +#endif // CONNECTIONBASE_H__ diff --git a/libs/libgloox/connectionbosh.cpp b/libs/libgloox/connectionbosh.cpp new file mode 100644 index 0000000..f0a1124 --- /dev/null +++ b/libs/libgloox/connectionbosh.cpp @@ -0,0 +1,641 @@ +/* + * Copyright (c) 2007-2009 by Jakob Schroeter + * This file is part of the gloox library. http://camaya.net/gloox + * + * This software is distributed under a license. The full license + * agreement can be found in the file LICENSE in this distribution. + * This software may not be copied, modified, sold or distributed + * other than expressed in the named license agreement. + * + * This software is distributed without any warranty. + */ + +#include "config.h" + +#include "gloox.h" + +#include "connectionbosh.h" +#include "logsink.h" +#include "prep.h" +#include "tag.h" +#include "util.h" + +#include +#include +#include +#include + +namespace gloox +{ + + ConnectionBOSH::ConnectionBOSH( ConnectionBase* connection, const LogSink& logInstance, + const std::string& boshHost, const std::string& xmppServer, + int xmppPort ) + : ConnectionBase( 0 ), + m_logInstance( logInstance ), m_parser( this ), m_boshHost( boshHost ), m_path( "/http-bind/" ), + m_rid( 0 ), m_initialStreamSent( false ), m_openRequests( 0 ), + m_maxOpenRequests( 2 ), m_wait( 30 ), m_hold( 2 ), m_streamRestart( false ), + m_lastRequestTime( std::time( 0 ) ), m_minTimePerRequest( 0 ), m_bufferContentLength( 0 ), + m_connMode( ModePipelining ) + { + initInstance( connection, xmppServer, xmppPort ); + } + + ConnectionBOSH::ConnectionBOSH( ConnectionDataHandler* cdh, ConnectionBase* connection, + const LogSink& logInstance, const std::string& boshHost, + const std::string& xmppServer, int xmppPort ) + : ConnectionBase( cdh ), + m_logInstance( logInstance ), m_parser( this ), m_boshHost( boshHost ), m_path( "/http-bind/" ), + m_rid( 0 ), m_initialStreamSent( false ), m_openRequests( 0 ), + m_maxOpenRequests( 2 ), m_wait( 30 ), m_hold( 2 ), m_streamRestart( false ), + m_lastRequestTime( std::time( 0 ) ), m_minTimePerRequest( 0 ), m_bufferContentLength( 0 ), + m_connMode( ModePipelining ) + { + initInstance( connection, xmppServer, xmppPort ); + } + + void ConnectionBOSH::initInstance( ConnectionBase* connection, const std::string& xmppServer, + const int xmppPort ) + { +// FIXME: check return value + prep::idna( xmppServer, m_server ); + m_port = xmppPort; + if( m_port != -1 ) + { + m_boshedHost = m_boshHost + ":" + util::int2string( m_port ); + } + + // drop this connection into our pool of available connections + if( connection ) + { + connection->registerConnectionDataHandler( this ); + m_connectionPool.push_back( connection ); + } + } + + ConnectionBOSH::~ConnectionBOSH() + { + util::clearList( m_activeConnections ); + util::clearList( m_connectionPool ); + } + + ConnectionBase* ConnectionBOSH::newInstance() const + { + ConnectionBase* pBaseConn = 0; + + if( !m_connectionPool.empty() ) + { + pBaseConn = m_connectionPool.front()->newInstance(); + } + else if( !m_activeConnections.empty() ) + { + pBaseConn = m_activeConnections.front()->newInstance(); + } + else + { + return 0; + } + + return new ConnectionBOSH( m_handler, pBaseConn, m_logInstance, + m_boshHost, m_server, m_port ); + } + + ConnectionError ConnectionBOSH::connect() + { + if( m_state >= StateConnecting ) + return ConnNoError; + + if( !m_handler ) + return ConnNotConnected; + + m_state = StateConnecting; + m_logInstance.dbg( LogAreaClassConnectionBOSH, + "bosh initiating connection to server: " + + ( ( m_connMode == ModePipelining ) ? std::string( "Pipelining" ) + : ( ( m_connMode == ModeLegacyHTTP ) ? std::string( "LegacyHTTP" ) + : std::string( "PersistentHTTP" ) ) ) ); + getConnection(); + return ConnNoError; // FIXME? + } + + void ConnectionBOSH::disconnect() + { + if( ( m_connMode == ModePipelining && m_activeConnections.empty() ) + || ( m_connectionPool.empty() && m_activeConnections.empty() ) ) + return; + + if( m_state != StateDisconnected ) + { + ++m_rid; + + std::string requestBody = ""; + m_sendBuffer = EmptyString; + } + sendRequest( requestBody ); + + m_logInstance.dbg( LogAreaClassConnectionBOSH, "bosh disconnection request sent" ); + } + else + { + m_logInstance.err( LogAreaClassConnectionBOSH, + "disconnecting from server in a non-graceful fashion" ); + } + + util::ForEach( m_activeConnections, &ConnectionBase::disconnect ); + util::ForEach( m_connectionPool, &ConnectionBase::disconnect ); + + m_state = StateDisconnected; + if( m_handler ) + m_handler->handleDisconnect( this, ConnUserDisconnected ); + } + + ConnectionError ConnectionBOSH::recv( int timeout ) + { + if( m_state == StateDisconnected ) + return ConnNotConnected; + + if( !m_connectionPool.empty() ) + m_connectionPool.front()->recv( 0 ); + if( !m_activeConnections.empty() ) + m_activeConnections.front()->recv( timeout ); + + // If there are no open requests then the spec allows us to send an empty request... + // (Some CMs do not obey this, it seems) + if( ( m_openRequests == 0 || m_sendBuffer.size() > 0 ) && m_state == StateConnected ) + { + m_logInstance.dbg( LogAreaClassConnectionBOSH, + "Sending empty request (or there is data in the send buffer)" ); + sendXML(); + } + + return ConnNoError; // FIXME? + } + + bool ConnectionBOSH::send( const std::string& data ) + { + + if( m_state == StateDisconnected ) + return false; + + if( data.substr( 0, 2 ) == " dropped" ); +// return true; +// } + } + else if( data == "
" ) + return true; + + m_sendBuffer += data; + sendXML(); + + return true; + } + + /* Sends XML. Wraps data in a tag, and then passes to sendRequest(). */ + bool ConnectionBOSH::sendXML() + { + if( m_state != StateConnected ) + { + m_logInstance.warn( LogAreaClassConnectionBOSH, + "Data sent before connection established (will be buffered)" ); + return false; + } + + if( m_sendBuffer.empty() ) + { + time_t now = time( 0 ); + unsigned int delta = (int)(now - m_lastRequestTime); + if( delta < m_minTimePerRequest && m_openRequests > 0 ) + { + m_logInstance.dbg( LogAreaClassConnectionBOSH, "Too little time between requests: " + util::int2string( delta ) + " seconds" ); + return false; + } + m_logInstance.dbg( LogAreaClassConnectionBOSH, "Send buffer is empty, sending empty request" ); + } + + ++m_rid; + + std::string requestBody = ""; + m_logInstance.dbg( LogAreaClassConnectionBOSH, "Restarting stream" ); + } + else + { + requestBody += ">" + m_sendBuffer + ""; + } + // Send a request. Force if we are not sending an empty request, or if there are no connections open + if( sendRequest( requestBody ) ) + { + m_logInstance.dbg( LogAreaClassConnectionBOSH, "Successfully sent m_sendBuffer" ); + m_sendBuffer = EmptyString; + m_streamRestart = false; + } + else + { + --m_rid; // I think... (may need to rethink when acks are implemented) + m_logInstance.warn( LogAreaClassConnectionBOSH, + "Unable to send. Connection not complete, or too many open requests," + " so added to buffer.\n" ); + } + + return true; + } + + /* Chooses the appropriate connection, or opens a new one if necessary. Wraps xml in HTTP and sends. */ + bool ConnectionBOSH::sendRequest( const std::string& xml ) + { + ConnectionBase* conn = getConnection(); + if( !conn ) + return false; + + std::string request = "POST " + m_path; + if( m_connMode == ModeLegacyHTTP ) + { + request += " HTTP/1.0\r\n"; + request += "Connection: close\r\n"; + } + else + request += " HTTP/1.1\r\n"; + + request += "Host: " + m_boshedHost + "\r\n"; + request += "Content-Type: text/xml; charset=utf-8\r\n"; + request += "Content-Length: " + util::int2string( xml.length() ) + "\r\n"; + request += "User-Agent: gloox/" + GLOOX_VERSION + "\r\n\r\n"; + request += xml; + + + if( conn->send( request ) ) + { + m_lastRequestTime = time( 0 ); + ++m_openRequests; + return true; + } +// else // FIXME What to do in this case? +// printf( "Error while trying to send on socket (state: %d)\n", conn->state() ); + + return false; + } + + bool ci_equal( char ch1, char ch2 ) + { + return std::toupper( (unsigned char)ch1 ) == std::toupper( (unsigned char)ch2 ); + } + + std::string::size_type ci_find( const std::string& str1, const std::string& str2 ) + { + std::string::const_iterator pos = std::search( str1.begin(), str1.end(), + str2.begin(), str2.end(), ci_equal ); + if( pos == str1.end() ) + return std::string::npos; + else + return std::distance( str1.begin(), pos ); + } + + const std::string ConnectionBOSH::getHTTPField( const std::string& field ) + { + std::string::size_type fp = ci_find( m_bufferHeader, "\r\n" + field + ": " ); + + if( fp == std::string::npos ) + return EmptyString; + + fp += field.length() + 4; + + const std::string::size_type fp2 = m_bufferHeader.find( "\r\n", fp ); + if( fp2 == std::string::npos ) + return EmptyString; + + return m_bufferHeader.substr( fp, fp2 - fp ); + } + + ConnectionError ConnectionBOSH::receive() + { + ConnectionError err = ConnNoError; + while( m_state != StateDisconnected && ( err = recv( 10 ) ) == ConnNoError ) + ; + return err == ConnNoError ? ConnNotConnected : err; + } + + void ConnectionBOSH::cleanup() + { + m_state = StateDisconnected; + + util::ForEach( m_activeConnections, &ConnectionBase::cleanup ); + util::ForEach( m_connectionPool, &ConnectionBase::cleanup ); + } + + void ConnectionBOSH::getStatistics( long int& totalIn, long int& totalOut ) + { + util::ForEach( m_activeConnections, &ConnectionBase::getStatistics, totalIn, totalOut ); + util::ForEach( m_connectionPool, &ConnectionBase::getStatistics, totalIn, totalOut ); + } + + void ConnectionBOSH::handleReceivedData( const ConnectionBase* /*connection*/, + const std::string& data ) + { + m_buffer += data; + std::string::size_type headerLength = 0; + while( ( headerLength = m_buffer.find( "\r\n\r\n" ) ) != std::string::npos ) + { + m_bufferHeader = m_buffer.substr( 0, headerLength+2 ); + + const std::string& statusCode = m_bufferHeader.substr( 9, 3 ); + if( statusCode != "200" ) + { + m_logInstance.warn( LogAreaClassConnectionBOSH, + "Received error via legacy HTTP status code: " + statusCode + + ". Disconnecting." ); + m_state = StateDisconnected; // As per XEP, consider connection broken + disconnect(); + } + + m_bufferContentLength = atol( getHTTPField( "Content-Length" ).c_str() ); + if( !m_bufferContentLength ) + return; + + if( m_connMode != ModeLegacyHTTP && ( getHTTPField( "Connection" ) == "close" + || m_bufferHeader.substr( 0, 8 ) == "HTTP/1.0" ) ) + { + m_logInstance.dbg( LogAreaClassConnectionBOSH, + "Server indicated lack of support for HTTP/1.1 - falling back to HTTP/1.0" ); + m_connMode = ModeLegacyHTTP; + } + + if( m_buffer.length() >= ( headerLength + 4 + m_bufferContentLength ) ) + { + putConnection(); + --m_openRequests; + std::string xml = m_buffer.substr( headerLength + 4, m_bufferContentLength ); + m_parser.feed( xml ); + m_buffer.erase( 0, headerLength + 4 + m_bufferContentLength ); + m_bufferContentLength = 0; + m_bufferHeader = EmptyString; + } + else + { + m_logInstance.warn( LogAreaClassConnectionBOSH, "buffer length mismatch" ); + break; + } + } + } + + void ConnectionBOSH::handleConnect( const ConnectionBase* /*connection*/ ) + { + if( m_state == StateConnecting ) + { + m_rid = rand() % 100000 + 1728679472; + + Tag requestBody( "body" ); + requestBody.setXmlns( XMLNS_HTTPBIND ); + requestBody.setXmlns( XMLNS_XMPP_BOSH, "xmpp" ); + + requestBody.addAttribute( "content", "text/xml; charset=utf-8" ); + requestBody.addAttribute( "hold", (long)m_hold ); + requestBody.addAttribute( "rid", (long)m_rid ); + requestBody.addAttribute( "ver", "1.6" ); + requestBody.addAttribute( "wait", (long)m_wait ); + requestBody.addAttribute( "ack", 0 ); + requestBody.addAttribute( "secure", "false" ); + requestBody.addAttribute( "route", "xmpp:" + m_server + ":5222" ); + requestBody.addAttribute( "xml:lang", "en" ); + requestBody.addAttribute( "xmpp:version", "1.0" ); + requestBody.addAttribute( "to", m_server ); + + m_logInstance.dbg( LogAreaClassConnectionBOSH, "sending bosh connection request" ); + sendRequest( requestBody.xml() ); + } + } + + void ConnectionBOSH::handleDisconnect( const ConnectionBase* /*connection*/, + ConnectionError reason ) + { + if( m_handler && m_state == StateConnecting ) + { + m_state = StateDisconnected; + m_handler->handleDisconnect( this, reason ); + return; + } + + switch( m_connMode ) // FIXME avoid that if we're disconnecting on purpose + { + case ModePipelining: + m_connMode = ModeLegacyHTTP; // Server seems not to support pipelining + m_logInstance.dbg( LogAreaClassConnectionBOSH, + "connection closed - falling back to HTTP/1.0 connection method" ); + break; + case ModeLegacyHTTP: + case ModePersistentHTTP: + // FIXME do we need to do anything here? +// printf( "A TCP connection %p was disconnected (reason: %d).\n", connection, reason ); + break; + } + } + + void ConnectionBOSH::handleTag( Tag* tag ) + { + if( !m_handler || tag->name() != "body" ) + return; + + if( m_streamRestart ) + { + m_streamRestart = false; + m_logInstance.dbg( LogAreaClassConnectionBOSH, "sending spoofed " ); + m_handler->handleReceivedData( this, "" + "" ); + } + + if( tag->hasAttribute( "sid" ) ) + { + m_state = StateConnected; + m_sid = tag->findAttribute( "sid" ); + + if( tag->hasAttribute( "requests" ) ) + { + const int serverRequests = atoi( tag->findAttribute( "requests" ).c_str() ); + if( serverRequests < m_maxOpenRequests ) + { + m_maxOpenRequests = serverRequests; + m_logInstance.dbg( LogAreaClassConnectionBOSH, + "bosh parameter 'requests' now set to " + tag->findAttribute( "requests" ) ); + } + } + if( tag->hasAttribute( "hold" ) ) + { + const int maxHold = atoi( tag->findAttribute( "hold" ).c_str() ); + if( maxHold < m_hold ) + { + m_hold = maxHold; + m_logInstance.dbg( LogAreaClassConnectionBOSH, + "bosh parameter 'hold' now set to " + tag->findAttribute( "hold" ) ); + } + } + if( tag->hasAttribute( "wait" ) ) + { + const int maxWait = atoi( tag->findAttribute( "wait" ).c_str() ); + if( maxWait < m_wait ) + { + m_wait = maxWait; + m_logInstance.dbg( LogAreaClassConnectionBOSH, + "bosh parameter 'wait' now set to " + tag->findAttribute( "wait" ) + + " seconds" ); + } + } + if( tag->hasAttribute( "polling" ) ) + { + const int minTime = atoi( tag->findAttribute( "polling" ).c_str() ); + m_minTimePerRequest = minTime; + m_logInstance.dbg( LogAreaClassConnectionBOSH, + "bosh parameter 'polling' now set to " + tag->findAttribute( "polling" ) + + " seconds" ); + } + + if( m_state < StateConnected ) + m_handler->handleConnect( this ); + + m_handler->handleReceivedData( this, "" // FIXME move to send() so that + // it is more clearly a response + // to the initial stream opener? + "" ); + } + + if( tag->findAttribute( "type" ) == "terminate" ) + { + m_logInstance.dbg( LogAreaClassConnectionBOSH, + "bosh connection closed by server: " + tag->findAttribute( "condition" ) ); + m_state = StateDisconnected; + m_handler->handleDisconnect( this, ConnStreamClosed ); + return; + } + + const TagList& stanzas = tag->children(); + TagList::const_iterator it = stanzas.begin(); + for( ; it != stanzas.end(); ++it ) + m_handler->handleReceivedData( this, (*it)->xml() ); + } + + ConnectionBase* ConnectionBOSH::getConnection() + { + if( m_openRequests > 0 && m_openRequests >= m_maxOpenRequests ) + { + m_logInstance.warn( LogAreaClassConnectionBOSH, + "Too many requests already open. Cannot send." ); + return 0; + } + + ConnectionBase* conn = 0; + switch( m_connMode ) + { + case ModePipelining: + if( !m_activeConnections.empty() ) + { + m_logInstance.dbg( LogAreaClassConnectionBOSH, "Using default connection for Pipelining." ); + return m_activeConnections.front(); + } + else if( !m_connectionPool.empty() ) + { + m_logInstance.warn( LogAreaClassConnectionBOSH, + "Pipelining selected, but no connection open. Opening one." ); + return activateConnection(); + } + else + m_logInstance.warn( LogAreaClassConnectionBOSH, + "No available connections to pipeline on." ); + break; + case ModeLegacyHTTP: + case ModePersistentHTTP: + { + if( !m_connectionPool.empty() ) + { + m_logInstance.dbg( LogAreaClassConnectionBOSH, "LegacyHTTP/PersistentHTTP selected, " + "using connection from pool." ); + return activateConnection(); + } + else if( !m_activeConnections.empty() ) + { + m_logInstance.dbg( LogAreaClassConnectionBOSH, "No connections in pool, creating a new one." ); + conn = m_activeConnections.front()->newInstance(); + conn->registerConnectionDataHandler( this ); + m_connectionPool.push_back( conn ); + conn->connect(); + } + else + m_logInstance.warn( LogAreaClassConnectionBOSH, + "No available connections to send on." ); + break; + } + } + return 0; + } + + ConnectionBase* ConnectionBOSH::activateConnection() + { + ConnectionBase* conn = m_connectionPool.front(); + m_connectionPool.pop_front(); + if( conn->state() == StateConnected ) + { + m_activeConnections.push_back( conn ); + return conn; + } + + m_logInstance.dbg( LogAreaClassConnectionBOSH, "Connecting pooled connection." ); + m_connectionPool.push_back( conn ); + conn->connect(); + return 0; + } + + void ConnectionBOSH::putConnection() + { + ConnectionBase* conn = m_activeConnections.front(); + + switch( m_connMode ) + { + case ModeLegacyHTTP: + m_logInstance.dbg( LogAreaClassConnectionBOSH, "Disconnecting LegacyHTTP connection" ); + conn->disconnect(); + conn->cleanup(); // This is necessary + m_activeConnections.pop_front(); + m_connectionPool.push_back( conn ); + break; + case ModePersistentHTTP: + m_logInstance.dbg( LogAreaClassConnectionBOSH, "Deactivating PersistentHTTP connection" ); + m_activeConnections.pop_front(); + m_connectionPool.push_back( conn ); + break; + case ModePipelining: + m_logInstance.dbg( LogAreaClassConnectionBOSH, "Keeping Pipelining connection" ); + default: + break; + } + } + +} diff --git a/libs/libgloox/connectionbosh.h b/libs/libgloox/connectionbosh.h new file mode 100644 index 0000000..8781c4c --- /dev/null +++ b/libs/libgloox/connectionbosh.h @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2007-2009 by Jakob Schroeter + * This file is part of the gloox library. http://camaya.net/gloox + * + * This software is distributed under a license. The full license + * agreement can be found in the file LICENSE in this distribution. + * This software may not be copied, modified, sold or distributed + * other than expressed in the named license agreement. + * + * This software is distributed without any warranty. + */ + +#ifndef CONNECTIONBOSH_H__ +#define CONNECTIONBOSH_H__ + +#include "gloox.h" +#include "connectionbase.h" +#include "logsink.h" +#include "taghandler.h" +#include "parser.h" + +#include +#include +#include + +namespace gloox +{ + + /** + * @brief This is an implementation of a BOSH (HTTP binding) connection. + * + * Usage: + * + * @code + * Client *c = new Client( ... ); + * c->setConnectionImpl( new ConnectionBOSH( c, + * new ConnectionTCPClient( c->logInstance(), httpServer, httpPort ), + * c->logInstance(), boshHost, xmpphost, xmppPort ) ); + * @endcode + * + * Make sure to pass the BOSH connection manager's host/port to the transport connection + * (ConnectionTCPClient in this case), and the XMPP server's host and port to the BOSH connection. + * You must also pass to BOSH the address of the BOSH server you are dealing with, this is used + * in the HTTP Host header. + * + * In the case of using ConnectionBOSH through a HTTP proxy, supply httpServer and httpPort as + * those of the proxy. In all cases, boshHost should be set to the hostname (not IP address) of + * the server running the BOSH connection manager. + * + * The reason why ConnectionBOSH doesn't manage its own ConnectionTCPClient is that it allows it + * to be used with other transports (like chained SOCKS5/HTTP proxies, or ConnectionTLS + * for HTTPS). + * + * @note To avoid problems, you should disable TLS in gloox by calling + * ClientBase::setTls( TLSDisabled ). + * + * Sample configurations for different servers can be found in the bosh_example.cpp file included + * with gloox in the @b src/examples/ directory. + * + * @author Matthew Wild + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API ConnectionBOSH : public ConnectionBase, ConnectionDataHandler, TagHandler + { + public: + /** + * Constructs a new ConnectionBOSH object. + * @param connection A transport connection. It should be configured to connect to + * the BOSH connection manager's (or a HTTP proxy's) host and port, @b not to the XMPP host. + * ConnectionBOSH will own the transport connection and delete it in its destructor. + * @param logInstance The log target. Obtain it from ClientBase::logInstance(). + * @param boshHost The hostname of the BOSH connection manager + * @param xmppServer A server to connect to. This is the XMPP server's address, @b not the + * connection manager's. + * @param xmppPort The port to connect to. This is the XMPP server's port, @b not the connection + * manager's. + * @note To properly use this object, you have to set a ConnectionDataHandler using + * registerConnectionDataHandler(). This is not necessary if this object is + * part of a 'connection chain', e.g. with ConnectionSOCKS5Proxy. + */ + ConnectionBOSH( ConnectionBase* connection, const LogSink& logInstance, const std::string& boshHost, + const std::string& xmppServer, int xmppPort = 5222 ); + + /** + * Constructs a new ConnectionBOSH object. + * @param cdh An ConnectionDataHandler-derived object that will handle incoming data. + * @param connection A transport connection. It should be configured to connect to + * the connection manager's (or proxy's) host and port, @b not to the XMPP host. ConnectionBOSH + * will own the transport connection and delete it in its destructor. + * @param logInstance The log target. Obtain it from ClientBase::logInstance(). + * @param boshHost The hostname of the BOSH connection manager (not any intermediate proxy) + * @param xmppServer A server to connect to. This is the XMPP server's address, @b not the connection + * manager's. + * @param xmppPort The port to connect to. This is the XMPP server's port, @b not the connection + * manager's. + */ + ConnectionBOSH( ConnectionDataHandler* cdh, ConnectionBase* connection, + const LogSink& logInstance, const std::string& boshHost, + const std::string& xmppServer, int xmppPort = 5222 ); + + /** + * Virtual destructor + */ + virtual ~ConnectionBOSH(); + + /** + * The supported connection modes. Usually auto-detected. + */ + enum ConnMode + { + ModeLegacyHTTP, /**< HTTP 1.0 connections, closed after receiving a response */ + ModePersistentHTTP, /**< HTTP 1.1 connections, re-used after receiving a response */ + ModePipelining /**< HTTP Pipelining (implies HTTP 1.1) a single connection is used */ + }; + + /** + * Sets the XMPP server to proxy to. + * @param xmppHost The XMPP server hostname (IP address). + * @param xmppPort The XMPP server port. + */ + void setServer( const std::string& xmppHost, unsigned short xmppPort = 5222 ) + { m_server = xmppHost; m_port = xmppPort; } + + /** + * Sets the path on the connection manager to request + * @param path The path, the default is "/http-bind/", which is the default for + * many connection managers. + */ + void setPath( const std::string& path ) { m_path = path; } + + /** + * Sets the connection mode + * @param mode The connection mode, @sa ConnMode + * @note In the case that a mode is selected that the connection manager + * or proxy does not support, gloox will fall back to using HTTP/1.0 connections, + * which should work with any server. + */ + void setMode( ConnMode mode ) { m_connMode = mode; } + + // reimplemented from ConnectionBase + virtual ConnectionError connect(); + + // reimplemented from ConnectionBase + virtual ConnectionError recv( int timeout = -1 ); + + // reimplemented from ConnectionBase + virtual bool send( const std::string& data ); + + // reimplemented from ConnectionBase + virtual ConnectionError receive(); + + // reimplemented from ConnectionBase + virtual void disconnect(); + + // reimplemented from ConnectionBase + virtual void cleanup(); + + // reimplemented from ConnectionBase + virtual void getStatistics( long int& totalIn, long int& totalOut ); + + // reimplemented from ConnectionDataHandler + virtual void handleReceivedData( const ConnectionBase* connection, const std::string& data ); + + // reimplemented from ConnectionDataHandler + virtual void handleConnect( const ConnectionBase* connection ); + + // reimplemented from ConnectionDataHandler + virtual void handleDisconnect( const ConnectionBase* connection, ConnectionError reason ); + + // reimplemented from ConnectionDataHandler + virtual ConnectionBase* newInstance() const; + + // reimplemented from TagHandler + virtual void handleTag( Tag* tag ); + + private: + ConnectionBOSH& operator=( const ConnectionBOSH& ); + void initInstance( ConnectionBase* connection, const std::string& xmppServer, const int xmppPort ); + bool sendRequest( const std::string& xml ); + bool sendXML(); + const std::string getHTTPField( const std::string& field ); + ConnectionBase* getConnection(); + ConnectionBase* activateConnection(); + void putConnection(); + + //ConnectionBase *m_connection; + const LogSink& m_logInstance; + + Parser m_parser; // Used for parsing XML section of responses + std::string m_boshHost; // The hostname of the BOSH connection manager + std::string m_boshedHost; // The hostname of the BOSH connection manager + : + port + std::string m_path; // The path part of the URL that we need to request + + // BOSH parameters + unsigned long m_rid; + std::string m_sid; + + bool m_initialStreamSent; + int m_openRequests; + int m_maxOpenRequests; + int m_wait; + int m_hold; + + bool m_streamRestart; // Set to true if we are waiting for an acknowledgement of a stream restart + + time_t m_lastRequestTime; + unsigned long m_minTimePerRequest; + + std::string m_buffer; // Buffer of received data + std::string m_bufferHeader; // HTTP header of data currently in buffer // FIXME doens't need to be member + std::string::size_type m_bufferContentLength; // Length of the data in the current response + + std::string m_sendBuffer; // Data waiting to be sent + + typedef std::list ConnectionList; + ConnectionList m_activeConnections; + ConnectionList m_connectionPool; + ConnMode m_connMode; + + }; + +} + +#endif // CONNECTIONBOSH_H__ diff --git a/libs/libgloox/connectiondatahandler.h b/libs/libgloox/connectiondatahandler.h new file mode 100644 index 0000000..424ca55 --- /dev/null +++ b/libs/libgloox/connectiondatahandler.h @@ -0,0 +1,66 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef CONNECTIONDATAHANDLER_H__ +#define CONNECTIONDATAHANDLER_H__ + +#include "gloox.h" + +#include + +namespace gloox +{ + + class ConnectionBase; + + /** + * @brief This is an abstract base class to receive events from a ConnectionBase-derived object. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API ConnectionDataHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~ConnectionDataHandler() {} + + /** + * This function is called for received from the underlying transport. + * @param connection The connection that received the data. + * @param data The data received. + */ + virtual void handleReceivedData( const ConnectionBase* connection, const std::string& data ) = 0; + + /** + * This function is called when e.g. the raw TCP connection was established. + * @param connection The connection. + */ + virtual void handleConnect( const ConnectionBase* connection ) = 0; + + /** + * This connection is called when e.g. the raw TCP connection was closed. + * @param connection The connection. + * @param reason The reason for the disconnect. + */ + virtual void handleDisconnect( const ConnectionBase* connection, ConnectionError reason ) = 0; + }; + +} + +#endif // CONNECTIONDATAHANDLER_H__ diff --git a/libs/libgloox/connectionhandler.h b/libs/libgloox/connectionhandler.h new file mode 100644 index 0000000..fc0d1eb --- /dev/null +++ b/libs/libgloox/connectionhandler.h @@ -0,0 +1,52 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef CONNECTIONHANDLER_H__ +#define CONNECTIONHANDLER_H__ + +#include "connectionbase.h" + +namespace gloox +{ + + /** + * @brief This is an abstract base class to receive incoming connection attempts. Do not + * confuse this with ConnectionListener, which is used with XMPP streams and has a + * completely different meaning. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API ConnectionHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~ConnectionHandler() {} + + /** + * This function is called to receive an incoming connection. + * @param server The server that the connection was made to. + * @param connection The incoming connection. + */ + virtual void handleIncomingConnection( ConnectionBase* server, ConnectionBase* connection ) = 0; + + }; + +} + +#endif // CONNECTIONHANDLER_H__ diff --git a/libs/libgloox/connectionhttpproxy.cpp b/libs/libgloox/connectionhttpproxy.cpp new file mode 100644 index 0000000..fa3223a --- /dev/null +++ b/libs/libgloox/connectionhttpproxy.cpp @@ -0,0 +1,215 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "gloox.h" + +#include "connectionhttpproxy.h" +#include "dns.h" +#include "logsink.h" +#include "prep.h" +#include "base64.h" +#include "util.h" + +#include + +namespace gloox +{ + + ConnectionHTTPProxy::ConnectionHTTPProxy( ConnectionBase* connection, + const LogSink& logInstance, + const std::string& server, int port ) + : ConnectionBase( 0 ), m_connection( connection ), + m_logInstance( logInstance ), m_http11( false ) + { +// FIXME check return value? + prep::idna( server, m_server ); + m_port = port; + + if( m_connection ) + m_connection->registerConnectionDataHandler( this ); + } + + ConnectionHTTPProxy::ConnectionHTTPProxy( ConnectionDataHandler* cdh, + ConnectionBase* connection, + const LogSink& logInstance, + const std::string& server, int port ) + : ConnectionBase( cdh ), m_connection( connection ), + m_logInstance( logInstance ) + { +// FIXME check return value? + prep::idna( server, m_server ); + m_port = port; + + if( m_connection ) + m_connection->registerConnectionDataHandler( this ); + } + + ConnectionHTTPProxy::~ConnectionHTTPProxy() + { + delete m_connection; + } + + ConnectionBase* ConnectionHTTPProxy::newInstance() const + { + ConnectionBase* conn = m_connection ? m_connection->newInstance() : 0; + return new ConnectionHTTPProxy( m_handler, conn, m_logInstance, m_server, m_port ); + } + + void ConnectionHTTPProxy::setConnectionImpl( ConnectionBase* connection ) + { + if( m_connection ) + delete m_connection; + + m_connection = connection; + } + + ConnectionError ConnectionHTTPProxy::connect() + { + if( m_connection && m_handler ) + { + m_state = StateConnecting; + return m_connection->connect(); + } + + return ConnNotConnected; + } + + void ConnectionHTTPProxy::disconnect() + { + m_state = StateDisconnected; + if( m_connection ) + m_connection->disconnect(); + } + + ConnectionError ConnectionHTTPProxy::recv( int timeout ) + { + return m_connection ? m_connection->recv( timeout ) : ConnNotConnected; + } + + ConnectionError ConnectionHTTPProxy::receive() + { + return m_connection ? m_connection->receive() : ConnNotConnected; + } + + bool ConnectionHTTPProxy::send( const std::string& data ) + { + return m_connection && m_connection->send( data ); + } + + void ConnectionHTTPProxy::cleanup() + { + m_state = StateDisconnected; + + if( m_connection ) + m_connection->cleanup(); + } + + void ConnectionHTTPProxy::getStatistics( long int& totalIn, long int& totalOut ) + { + if( m_connection ) + m_connection->getStatistics( totalIn, totalOut ); + else + totalIn = totalOut = 0; + } + + void ConnectionHTTPProxy::handleReceivedData( const ConnectionBase* /*connection*/, + const std::string& data ) + { + if( !m_handler ) + return; + + if( m_state == StateConnecting ) + { + m_proxyHandshakeBuffer += data; + if( ( !m_proxyHandshakeBuffer.compare( 0, 12, "HTTP/1.0 200" ) + || !m_proxyHandshakeBuffer.compare( 0, 12, "HTTP/1.1 200" ) ) + && !m_proxyHandshakeBuffer.compare( m_proxyHandshakeBuffer.length() - 4, 4, "\r\n\r\n" ) ) + { + m_proxyHandshakeBuffer = EmptyString; + m_state = StateConnected; + m_logInstance.dbg( LogAreaClassConnectionHTTPProxy, + "http proxy connection established" ); + m_handler->handleConnect( this ); + } + else if( !m_proxyHandshakeBuffer.compare( 9, 3, "407" ) ) + { + m_handler->handleDisconnect( this, ConnProxyAuthRequired ); + m_connection->disconnect(); + } + else if( !m_proxyHandshakeBuffer.compare( 9, 3, "403" ) + || !m_proxyHandshakeBuffer.compare( 9, 3, "404" ) ) + { + m_handler->handleDisconnect( this, ConnProxyAuthFailed ); + m_connection->disconnect(); + } + } + else if( m_state == StateConnected ) + m_handler->handleReceivedData( this, data ); + } + + void ConnectionHTTPProxy::handleConnect( const ConnectionBase* /*connection*/ ) + { + if( m_connection ) + { + std::string server = m_server; + int port = m_port; + if( port == -1 ) + { + const DNS::HostMap& servers = DNS::resolve( m_server, m_logInstance ); + if( !servers.empty() ) + { + const std::pair< std::string, int >& host = *servers.begin(); + server = host.first; + port = host.second; + } + } + std::string message = "Requesting http proxy connection to " + server + ":" + + util::int2string( port ); + m_logInstance.dbg( LogAreaClassConnectionHTTPProxy, message ); + + std::string os = "CONNECT " + server + ":" + util::int2string( port ) + " HTTP/1." + + util::int2string( m_http11 ? 1 : 0 ) + "\r\n" + "Host: " + server + "\r\n" + "Content-Length: 0\r\n" + "Proxy-Connection: Keep-Alive\r\n" + "Pragma: no-cache\r\n" + "User-Agent: gloox/" + GLOOX_VERSION + "\r\n"; + + if( !m_proxyUser.empty() && !m_proxyPwd.empty() ) + { + os += "Proxy-Authorization: Basic " + Base64::encode64( m_proxyUser + ":" + m_proxyPwd ) + + "\r\n"; + } + os += "\r\n"; + + if( !m_connection->send( os ) ) + { + m_state = StateDisconnected; + if( m_handler ) + m_handler->handleDisconnect( this, ConnIoError ); + } + } + } + + void ConnectionHTTPProxy::handleDisconnect( const ConnectionBase* /*connection*/, + ConnectionError reason ) + { + m_state = StateDisconnected; + m_logInstance.dbg( LogAreaClassConnectionHTTPProxy, "HTTP Proxy connection closed" ); + + if( m_handler ) + m_handler->handleDisconnect( this, reason ); + } + +} diff --git a/libs/libgloox/connectionhttpproxy.h b/libs/libgloox/connectionhttpproxy.h new file mode 100644 index 0000000..69ba399 --- /dev/null +++ b/libs/libgloox/connectionhttpproxy.h @@ -0,0 +1,171 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef CONNECTIONHTTPPROXY_H__ +#define CONNECTIONHTTPPROXY_H__ + +#include "gloox.h" +#include "connectionbase.h" +#include "logsink.h" + +#include + +namespace gloox +{ + + /** + * @brief This is an implementation of a simple HTTP Proxying connection. + * + * Usage: + * + * @code + * Client* c = new Client( ... ); + * ConnectionTCPClient* conn0 = new ConnectionTCPClient( c->logInstance(), + * proxyHost, proxyPort ); + * ConnectionHTTPProxy* conn1 = new ConnectionHTTPProxy( c, conn0, c->logInstance(), + * xmppHost, xmppPort ); + * c->setConnectionImpl( conn1 ); + * @endcode + * + * Make sure to pass the proxy host/port to the transport connection (ConnectionTCPClient in this case), + * and the XMPP host/port to the proxy connection. + * + * ConnectionHTTPProxy uses the CONNECT method to pass through the proxy. If your proxy does not + * allow this kind of connections, or if it kills connections after some time, you may want to use + * ConnectionBOSH instead or in addition. + * + * The reason why ConnectionHTTPProxy doesn't manage its own ConnectionTCPClient is that it allows it + * to be used with other transports (like IPv6 or chained SOCKS5/HTTP proxies). + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API ConnectionHTTPProxy : public ConnectionBase, public ConnectionDataHandler + { + public: + /** + * Constructs a new ConnectionHTTPProxy object. + * @param connection A transport connection. It should be configured to connect to + * the proxy host and port, @b not to the XMPP host. ConnectionHTTPProxy will own the + * transport connection and delete it in its destructor. + * @param logInstance The log target. Obtain it from ClientBase::logInstance(). + * @param server A server to connect to. This is the XMPP server's address, @b not the proxy. + * @param port The port to connect to. This is the XMPP server's port, @b not the proxy's. + * The default of -1 means that SRV records will be used to find out about the actual host:port. + * @note To properly use this object, you have to set a ConnectionDataHandler using + * registerConnectionDataHandler(). This is not necessary if this object is + * part of a 'connection chain', e.g. with ConnectionSOCKS5Proxy. + */ + ConnectionHTTPProxy( ConnectionBase* connection, const LogSink& logInstance, + const std::string& server, int port = -1 ); + + /** + * Constructs a new ConnectionHTTPProxy object. + * @param cdh An ConnectionDataHandler-derived object that will handle incoming data. + * @param connection A transport connection. It should be configured to connect to + * the proxy host and port, @b not to the XMPP host. ConnectionHTTPProxy will own the + * transport connection and delete it in its destructor. + * @param logInstance The log target. Obtain it from ClientBase::logInstance(). + * @param server A server to connect to. This is the XMPP server's address, @b not the proxy. + * @param port The port to connect to. This is the XMPP server's port, @b not the proxy's. + * The default of -1 means that SRV records will be used to find out about the actual host:port. + */ + ConnectionHTTPProxy( ConnectionDataHandler* cdh, ConnectionBase* connection, + const LogSink& logInstance, + const std::string& server, int port = -1 ); + + /** + * Virtual destructor + */ + virtual ~ConnectionHTTPProxy(); + + // reimplemented from ConnectionBase + virtual ConnectionError connect(); + + // reimplemented from ConnectionBase + virtual ConnectionError recv( int timeout = -1 ); + + // reimplemented from ConnectionBase + virtual bool send( const std::string& data ); + + // reimplemented from ConnectionBase + virtual ConnectionError receive(); + + // reimplemented from ConnectionBase + virtual void disconnect(); + + // reimplemented from ConnectionBase + virtual void cleanup(); + + // reimplemented from ConnectionBase + virtual void getStatistics( long int &totalIn, long int &totalOut ); + + // reimplemented from ConnectionDataHandler + virtual void handleReceivedData( const ConnectionBase* connection, const std::string& data ); + + // reimplemented from ConnectionDataHandler + virtual void handleConnect( const ConnectionBase* connection ); + + // reimplemented from ConnectionDataHandler + virtual void handleDisconnect( const ConnectionBase* connection, ConnectionError reason ); + + // reimplemented from ConnectionDataHandler + virtual ConnectionBase* newInstance() const; + + /** + * Sets the XMPP server to proxy to. + * @param host The XMPP server hostname (IP address). + * @param port The XMPP server port. The default of -1 means that SRV records will be used + * to find out about the actual host:port. + */ + void setServer( const std::string& host, int port = -1 ) + { m_server = host; m_port = port; } + + /** + * Sets proxy authorization credentials. + * @param user The user name to use for proxy authorization. + * @param password The password to use for proxy authorization. + */ + void setProxyAuth( const std::string& user, const std::string& password ) + { m_proxyUser = user; m_proxyPwd = password; } + + /** + * Sets the underlying transport connection. A possibly existing connection will be deleted. + * @param connection The ConnectionBase to replace the current connection, if any. + */ + void setConnectionImpl( ConnectionBase* connection ); + + /** + * Switches usage of HTTP/1.1 on or off. + * @param http11 Set this to @b true to connect through a HTTP/1.1-only proxy, or @b false + * to use HTTP/1.0. Defaults to HTTP/1.0 which should work with 99.9% of proxies. + */ + void setHTTP11( bool http11 ) { m_http11 = http11; } + + private: + ConnectionHTTPProxy &operator=( const ConnectionHTTPProxy& ); + + ConnectionBase* m_connection; + const LogSink& m_logInstance; + + std::string m_proxyUser; + std::string m_proxyPwd; + std::string m_proxyHandshakeBuffer; + + bool m_http11; + + }; + +} + +#endif // CONNECTIONHTTPPROXY_H__ diff --git a/libs/libgloox/connectionlistener.h b/libs/libgloox/connectionlistener.h new file mode 100644 index 0000000..27940cf --- /dev/null +++ b/libs/libgloox/connectionlistener.h @@ -0,0 +1,106 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef CONNECTIONLISTENER_H__ +#define CONNECTIONLISTENER_H__ + +#include "gloox.h" + +namespace gloox +{ + + class Error; + + /** + * @brief Derived classes can be registered as ConnectionListeners with the Client. + * + * This interface is mandatory to implement if a connection is to be made TLS-encrypted. + * In onTLSConnect(), the server's certificate information needs to be checked, and @b true + * returned if the certificate is to be accepted. + * + * @author Jakob Schroeter + */ + class GLOOX_API ConnectionListener + { + public: + /** + * Virtual Destructor. + */ + virtual ~ConnectionListener() {} + + /** + * This function notifies about successful connections. It will be called either after all + * authentication is finished if username/password were supplied, or after a connection has + * been established if no credentials were supplied. Depending on the setting of AutoPresence, + * a presence stanza is sent or not. + */ + virtual void onConnect() = 0; + + /** + * This function notifies about disconnection and its reason. + * If @b e indicates a stream error, you can use @ref ClientBase::streamError() to find out + * what exactly went wrong, and @ref ClientBase::streamErrorText() to retrieve any explaining text + * sent along with the error. + * If @b e indicates an authentication error, you can use @ref ClientBase::authError() + * to get a finer grained reason. + * @param e The reason for the disconnection. + */ + virtual void onDisconnect( ConnectionError e ) = 0; + + /** + * This function will be called when a resource has been bound to the stream. It + * will be called for any bound resource, including the main one. + * @note The bound resource may be different from the one requested. The server + * has the authority to change/overwrite the requested resource. + * @param resource The resource string. + * @since 1.0 + */ + virtual void onResourceBind( const std::string& resource ) { (void)resource; } + + /** + * This function is called (by a Client object) if an error occurs while trying to bind a resource. + * @param error A pointer to an Error object that contains more + * information. May be 0. + */ + virtual void onResourceBindError( const Error* error ) { (void) (error); } + + /** + * This function is called (by a Client object) if an error occurs while trying to establish + * a session. + * @param error A pointer to an Error object that contains more + * information. May be 0. + */ + virtual void onSessionCreateError( const Error* error ) { (void) (error); } + + /** + * This function is called when the connection was TLS/SSL secured. + * @param info Comprehensive info on the certificate. + * @return @b True if cert credentials are accepted, @b false otherwise. If @b false is returned + * the connection is terminated. + */ + virtual bool onTLSConnect( const CertInfo& info ) = 0; + + /** + * This function is called for certain stream events. Notifications are purely informational + * and implementation is optional. Not all StreamEvents will necessarily be emitted for + * a given connection. + * @param event A stream event. + * @since 0.9 + */ + virtual void onStreamEvent( StreamEvent event ) { (void) (event); } + + }; + +} + +#endif // CONNECTIONLISTENER_H__ diff --git a/libs/libgloox/connectionsocks5proxy.cpp b/libs/libgloox/connectionsocks5proxy.cpp new file mode 100644 index 0000000..0ee42f5 --- /dev/null +++ b/libs/libgloox/connectionsocks5proxy.cpp @@ -0,0 +1,377 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "config.h" + +#include "gloox.h" + +#include "connectionsocks5proxy.h" +#include "dns.h" +#include "logsink.h" +#include "prep.h" +#include "base64.h" +#include "util.h" + +#include +#include + +#include + +#if ( !defined( _WIN32 ) && !defined( _WIN32_WCE ) ) || defined( __SYMBIAN32__ ) +# include +#endif + +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) +# include +#elif defined( _WIN32_WCE ) +# include +#endif + +namespace gloox +{ + + ConnectionSOCKS5Proxy::ConnectionSOCKS5Proxy( ConnectionBase* connection, + const LogSink& logInstance, + const std::string& server, + int port, bool ip ) + : ConnectionBase( 0 ), m_connection( connection ), + m_logInstance( logInstance ), m_s5state( S5StateDisconnected ), m_ip( ip ) + { +// FIXME check return value? + prep::idna( server, m_server ); + m_port = port; + + if( m_connection ) + m_connection->registerConnectionDataHandler( this ); + } + + ConnectionSOCKS5Proxy::ConnectionSOCKS5Proxy( ConnectionDataHandler* cdh, + ConnectionBase* connection, + const LogSink& logInstance, + const std::string& server, + int port, bool ip ) + : ConnectionBase( cdh ), m_connection( connection ), + m_logInstance( logInstance ), m_s5state( S5StateDisconnected ), m_ip( ip ) + { +// FIXME check return value? + prep::idna( server, m_server ); + m_port = port; + + if( m_connection ) + m_connection->registerConnectionDataHandler( this ); + } + + ConnectionSOCKS5Proxy::~ConnectionSOCKS5Proxy() + { + if( m_connection ) + delete m_connection; + } + + ConnectionBase* ConnectionSOCKS5Proxy::newInstance() const + { + ConnectionBase* conn = m_connection ? m_connection->newInstance() : 0; + return new ConnectionSOCKS5Proxy( m_handler, conn, m_logInstance, m_server, m_port, m_ip ); + } + + void ConnectionSOCKS5Proxy::setConnectionImpl( ConnectionBase* connection ) + { + if( m_connection ) + delete m_connection; + + m_connection = connection; + } + + ConnectionError ConnectionSOCKS5Proxy::connect() + { +// FIXME CHECKME + if( m_connection && m_connection->state() == StateConnected && m_handler ) + { + m_state = StateConnected; + m_s5state = S5StateConnected; + return ConnNoError; + } + + if( m_connection && m_handler ) + { + m_state = StateConnecting; + m_s5state = S5StateConnecting; + return m_connection->connect(); + } + + return ConnNotConnected; + } + + void ConnectionSOCKS5Proxy::disconnect() + { + if( m_connection ) + m_connection->disconnect(); + cleanup(); + } + + ConnectionError ConnectionSOCKS5Proxy::recv( int timeout ) + { + if( m_connection ) + return m_connection->recv( timeout ); + else + return ConnNotConnected; + } + + ConnectionError ConnectionSOCKS5Proxy::receive() + { + if( m_connection ) + return m_connection->receive(); + else + return ConnNotConnected; + } + + bool ConnectionSOCKS5Proxy::send( const std::string& data ) + { +// if( m_s5state != S5StateConnected ) +// { +// printf( "p data sent: " ); +// const char* x = data.c_str(); +// for( unsigned int i = 0; i < data.length(); ++i ) +// printf( "%02X ", (const char)x[i] ); +// printf( "\n" ); +// } + + if( m_connection ) + return m_connection->send( data ); + + return false; + } + + void ConnectionSOCKS5Proxy::cleanup() + { + m_state = StateDisconnected; + m_s5state = S5StateDisconnected; + + if( m_connection ) + m_connection->cleanup(); + } + + void ConnectionSOCKS5Proxy::getStatistics( long int &totalIn, long int &totalOut ) + { + if( m_connection ) + m_connection->getStatistics( totalIn, totalOut ); + else + { + totalIn = 0; + totalOut = 0; + } + } + + void ConnectionSOCKS5Proxy::handleReceivedData( const ConnectionBase* /*connection*/, + const std::string& data ) + { +// if( m_s5state != S5StateConnected ) +// { +// printf( "data recv: " ); +// const char* x = data.c_str(); +// for( unsigned int i = 0; i < data.length(); ++i ) +// printf( "%02X ", (const char)x[i] ); +// printf( "\n" ); +// } + + if( !m_connection || !m_handler ) + return; + + ConnectionError connError = ConnNoError; + + switch( m_s5state ) + { + case S5StateConnecting: + if( data.length() != 2 || data[0] != 0x05 ) + connError = ConnIoError; + + if( data[1] == 0x00 ) // no auth + { + negotiate(); + } + else if( data[1] == 0x02 && !m_proxyUser.empty() && !m_proxyPwd.empty() ) // user/password auth + { + m_logInstance.dbg( LogAreaClassConnectionSOCKS5Proxy, + "authenticating to socks5 proxy as user " + m_proxyUser ); + m_s5state = S5StateAuthenticating; + char* d = new char[3 + m_proxyUser.length() + m_proxyPwd.length()]; + size_t pos = 0; + d[pos++] = 0x01; + d[pos++] = (char)m_proxyUser.length(); + strncpy( d + pos, m_proxyUser.c_str(), m_proxyUser.length() ); + pos += m_proxyUser.length(); + d[pos++] = (char)m_proxyPwd.length(); + strncpy( d + pos, m_proxyPwd.c_str(), m_proxyPwd.length() ); + pos += m_proxyPwd.length(); + + if( !send( std::string( d, pos ) ) ) + { + cleanup(); + m_handler->handleDisconnect( this, ConnIoError ); + } + delete[] d; + } + else + { + if( data[1] == (char)(unsigned char)0xFF && !m_proxyUser.empty() && !m_proxyPwd.empty() ) + connError = ConnProxyNoSupportedAuth; + else + connError = ConnProxyAuthRequired; + } + break; + case S5StateNegotiating: + if( data.length() >= 6 && data[0] == 0x05 ) + { + if( data[1] == 0x00 ) + { + m_state = StateConnected; + m_s5state = S5StateConnected; + m_handler->handleConnect( this ); + } + else // connection refused + connError = ConnConnectionRefused; + } + else + connError = ConnIoError; + break; + case S5StateAuthenticating: + if( data.length() == 2 && data[0] == 0x01 && data[1] == 0x00 ) + negotiate(); + else + connError = ConnProxyAuthFailed; + break; + case S5StateConnected: + m_handler->handleReceivedData( this, data ); + break; + default: + break; + } + + if( connError != ConnNoError ) + { + m_connection->disconnect(); + m_handler->handleDisconnect( this, connError ); + } + + } + + void ConnectionSOCKS5Proxy::negotiate() + { + m_s5state = S5StateNegotiating; + char* d = new char[m_ip ? 10 : 6 + m_server.length() + 1]; + size_t pos = 0; + d[pos++] = 0x05; // SOCKS version 5 + d[pos++] = 0x01; // command CONNECT + d[pos++] = 0x00; // reserved + int port = m_port; + std::string server = m_server; + if( m_ip ) // IP address + { + d[pos++] = 0x01; // IPv4 address + std::string s; + const size_t j = server.length(); + size_t l = 0; + for( size_t k = 0; k < j && l < 4; ++k ) + { + if( server[k] != '.' ) + s += server[k]; + + if( server[k] == '.' || k == j-1 ) + { + d[pos++] = static_cast( atoi( s.c_str() ) & 0xFF ); + s = EmptyString; + ++l; + } + } + } + else // hostname + { + if( port == -1 ) + { + const DNS::HostMap& servers = DNS::resolve( m_server, m_logInstance ); + if( servers.size() ) + { + const std::pair< std::string, int >& host = *servers.begin(); + server = host.first; + port = host.second; + } + } + d[pos++] = 0x03; // hostname + d[pos++] = (char)m_server.length(); + strncpy( d + pos, m_server.c_str(), m_server.length() ); + pos += m_server.length(); + } + int nport = htons( port ); + d[pos++] = static_cast( nport ); + d[pos++] = static_cast( nport >> 8 ); + + std::string message = "Requesting socks5 proxy connection to " + server + ":" + + util::int2string( port ); + m_logInstance.dbg( LogAreaClassConnectionSOCKS5Proxy, message ); + + if( !send( std::string( d, pos ) ) ) + { + cleanup(); + m_handler->handleDisconnect( this, ConnIoError ); + } + delete[] d; + } + + void ConnectionSOCKS5Proxy::handleConnect( const ConnectionBase* /*connection*/ ) + { + if( m_connection ) + { + std::string server = m_server; + int port = m_port; + if( port == -1 ) + { + const DNS::HostMap& servers = DNS::resolve( m_server, m_logInstance ); + if( !servers.empty() ) + { + const std::pair< std::string, int >& host = *servers.begin(); + server = host.first; + port = host.second; + } + } + m_logInstance.dbg( LogAreaClassConnectionSOCKS5Proxy, + "Attempting to negotiate socks5 proxy connection" ); + + const bool auth = !m_proxyUser.empty() && !m_proxyPwd.empty(); + const char d[4] = { + 0x05, // SOCKS version 5 + static_cast( auth ? 0x02 // two methods + : 0x01 ), // one method + 0x00, // method: no auth + 0x02 // method: username/password auth + }; + + if( !send( std::string( d, auth ? 4 : 3 ) ) ) + { + cleanup(); + if( m_handler ) + m_handler->handleDisconnect( this, ConnIoError ); + } + } + } + + void ConnectionSOCKS5Proxy::handleDisconnect( const ConnectionBase* /*connection*/, + ConnectionError reason ) + { + cleanup(); + m_logInstance.dbg( LogAreaClassConnectionSOCKS5Proxy, "socks5 proxy connection closed" ); + + if( m_handler ) + m_handler->handleDisconnect( this, reason ); + } + +} diff --git a/libs/libgloox/connectionsocks5proxy.h b/libs/libgloox/connectionsocks5proxy.h new file mode 100644 index 0000000..dfcbe1c --- /dev/null +++ b/libs/libgloox/connectionsocks5proxy.h @@ -0,0 +1,178 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef CONNECTIONSOCKS5PROXY_H__ +#define CONNECTIONSOCKS5PROXY_H__ + +#include "gloox.h" +#include "connectionbase.h" +#include "logsink.h" + +#include + +namespace gloox +{ + + /** + * @brief This is an implementation of a simple SOCKS5 Proxying connection (RFC 1928 + RFC 1929). + * + * To use with a SOCKS5 proxy: + * + * @code + * Client* c = new Client( ... ); + * c->setConnectionImpl( new ConnectionSOCKS5Proxy( c, + * new ConnectionTCPClient( c->logInstance(), proxyHost, proxyPort ), + * c->logInstance(), xmppHost, xmppPort ) ); + * @endcode + * + * Make sure to pass the proxy host/port to the transport connection (ConnectionTCPClient in this case), + * and the XMPP host/port to the proxy connection. + * + * The reason why ConnectionSOCKS5Proxy doesn't manage its own ConnectionTCPClient is that it allows it + * to be used with other transports (like IPv6 or chained HTTP/SOCKS5 proxies). + * + * @note This class is also used by the SOCKS5 bytestreams implementation (with slightly different + * semantics). + * + * @note Simple @b plain-text username/password authentication is supported. GSSAPI authentication + * is not supported. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API ConnectionSOCKS5Proxy : public ConnectionBase, public ConnectionDataHandler + { + public: + /** + * Constructs a new ConnectionSOCKS5Proxy object. + * @param connection A transport connection. It should be configured to connect to + * the proxy host and port, @b not to the (XMPP) host. ConnectionSOCKS5Proxy will own the + * transport connection and delete it in its destructor. + * @param logInstance The log target. Obtain it from ClientBase::logInstance(). + * @param server A server to connect to. This is the XMPP server's address, @b not the proxy. + * @param port The proxy's port to connect to. This is the (XMPP) server's port, @b not the proxy's. + * The default of -1 means that SRV records will be used to find out about the actual host:port. + * @param ip Indicates whether @c server is an IP address (true) or a host name (false). + * @note To properly use this object, you have to set a ConnectionDataHandler using + * registerConnectionDataHandler(). This is not necessary if this object is + * part of a 'connection chain', e.g. with ConnectionHTTPProxy. + */ + ConnectionSOCKS5Proxy( ConnectionBase* connection, const LogSink& logInstance, + const std::string& server, int port = -1, bool ip = false ); + + /** + * Constructs a new ConnectionSOCKS5Proxy object. + * @param cdh A ConnectionDataHandler-derived object that will handle incoming data. + * @param connection A transport connection. It should be configured to connect to + * the proxy host and port, @b not to the (XMPP) host. ConnectionSOCKS5Proxy will own the + * transport connection and delete it in its destructor. + * @param logInstance The log target. Obtain it from ClientBase::logInstance(). + * @param server A server to connect to. This is the XMPP server's address, @b not the proxy. + * @param port The proxy's port to connect to. This is the (XMPP) server's port, @b not the proxy's. + * The default of -1 means that SRV records will be used to find out about the actual host:port. + * @param ip Indicates whether @c server is an IP address (true) or a host name (false). + */ + ConnectionSOCKS5Proxy( ConnectionDataHandler* cdh, ConnectionBase* connection, + const LogSink& logInstance, + const std::string& server, int port = -1, bool ip = false ); + + /** + * Virtual destructor + */ + virtual ~ConnectionSOCKS5Proxy(); + + // reimplemented from ConnectionBase + virtual ConnectionError connect(); + + // reimplemented from ConnectionBase + virtual ConnectionError recv( int timeout = -1 ); + + // reimplemented from ConnectionBase + virtual bool send( const std::string& data ); + + // reimplemented from ConnectionBase + virtual ConnectionError receive(); + + // reimplemented from ConnectionBase + virtual void disconnect(); + + // reimplemented from ConnectionBase + virtual void cleanup(); + + // reimplemented from ConnectionBase + virtual void getStatistics( long int &totalIn, long int &totalOut ); + + // reimplemented from ConnectionDataHandler + virtual void handleReceivedData( const ConnectionBase* connection, const std::string& data ); + + // reimplemented from ConnectionDataHandler + virtual void handleConnect( const ConnectionBase* connection ); + + // reimplemented from ConnectionDataHandler + virtual void handleDisconnect( const ConnectionBase* connection, ConnectionError reason ); + + // reimplemented from ConnectionDataHandler + virtual ConnectionBase* newInstance() const; + + /** + * Sets the server to proxy to. + * @param host The server hostname (IP address). + * @param port The server port. The default of -1 means that SRV records will be used + * to find out about the actual host:port. + * @param ip Indicates whether @c host is an IP address (true) or a host name (false). + */ + void setServer( const std::string& host, int port = -1, bool ip = false ) + { m_server = host; m_port = port; m_ip = ip; } + + /** + * Sets proxy authorization credentials. + * @param user The user name to use for proxy authorization. + * @param password The password to use for proxy authorization. + */ + void setProxyAuth( const std::string& user, const std::string& password ) + { m_proxyUser = user; m_proxyPwd = password; } + + /** + * Sets the underlying transport connection. A possibly existing connection will be deleted. + * @param connection The ConnectionBase to replace the current connection, if any. + */ + void setConnectionImpl( ConnectionBase* connection ); + + private: + enum Socks5State + { + S5StateDisconnected, + S5StateConnecting, + S5StateNegotiating, + S5StateAuthenticating, + S5StateConnected + }; + + ConnectionSOCKS5Proxy &operator=( const ConnectionSOCKS5Proxy& ); + void negotiate(); + + ConnectionBase* m_connection; + const LogSink& m_logInstance; + + Socks5State m_s5state; + + std::string m_proxyUser; + std::string m_proxyPwd; + std::string m_proxyHandshakeBuffer; + bool m_ip; + + }; + +} + +#endif // CONNECTIONSOCKS5PROXY_H__ diff --git a/libs/libgloox/connectiontcpbase.cpp b/libs/libgloox/connectiontcpbase.cpp new file mode 100644 index 0000000..6a103a6 --- /dev/null +++ b/libs/libgloox/connectiontcpbase.cpp @@ -0,0 +1,200 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "gloox.h" + +#include "connectiontcpbase.h" +#include "dns.h" +#include "logsink.h" +#include "prep.h" +#include "mutexguard.h" + +#ifdef __MINGW32__ +# include +#endif + +#if ( !defined( _WIN32 ) && !defined( _WIN32_WCE ) ) || defined( __SYMBIAN32__ ) +# include +# include +# include +# include +# include +# include +# include +#elif ( defined( _WIN32 ) || defined( _WIN32_WCE ) ) && !defined( __SYMBIAN32__ ) +# include +typedef int socklen_t; +#endif + +#include + +#include +#include + +namespace gloox +{ + + ConnectionTCPBase::ConnectionTCPBase( const LogSink& logInstance, + const std::string& server, int port ) + : ConnectionBase( 0 ), + m_logInstance( logInstance ), m_buf( 0 ), m_socket( -1 ), m_totalBytesIn( 0 ), + m_totalBytesOut( 0 ), m_bufsize( 1024 ), m_cancel( true ) + { + init( server, port ); + } + + ConnectionTCPBase::ConnectionTCPBase( ConnectionDataHandler* cdh, const LogSink& logInstance, + const std::string& server, int port ) + : ConnectionBase( cdh ), + m_logInstance( logInstance ), m_buf( 0 ), m_socket( -1 ), m_totalBytesIn( 0 ), + m_totalBytesOut( 0 ), m_bufsize( 1024 ), m_cancel( true ) + { + init( server, port ); + } + + void ConnectionTCPBase::init( const std::string& server, int port ) + { +// FIXME check return value? + prep::idna( server, m_server ); + m_port = port; + m_buf = (char*)calloc( m_bufsize + 1, sizeof( char ) ); + } + + ConnectionTCPBase::~ConnectionTCPBase() + { + cleanup(); + free( m_buf ); + m_buf = 0; + } + + void ConnectionTCPBase::disconnect() + { + util::MutexGuard rm( m_recvMutex ); + m_cancel = true; + } + + bool ConnectionTCPBase::dataAvailable( int timeout ) + { + if( m_socket < 0 ) + return true; // let recv() catch the closed fd + + fd_set fds; + struct timeval tv; + + FD_ZERO( &fds ); + // the following causes a C4127 warning in VC++ Express 2008 and possibly other versions. + // however, the reason for the warning can't be fixed in gloox. + FD_SET( m_socket, &fds ); + + tv.tv_sec = timeout / 1000000; + tv.tv_usec = timeout % 1000000; + + return ( ( select( m_socket + 1, &fds, 0, 0, timeout == -1 ? 0 : &tv ) > 0 ) + && FD_ISSET( m_socket, &fds ) != 0 ); + } + + ConnectionError ConnectionTCPBase::receive() + { + if( m_socket < 0 ) + return ConnNotConnected; + + ConnectionError err = ConnNoError; + while( !m_cancel && ( err = recv( 10 ) ) == ConnNoError ) + ; + return err == ConnNoError ? ConnNotConnected : err; + } + + bool ConnectionTCPBase::send( const std::string& data ) + { + m_sendMutex.lock(); + + if( data.empty() || ( m_socket < 0 ) ) + { + m_sendMutex.unlock(); + return false; + } + + int sent = 0; + for( size_t num = 0, len = data.length(); sent != -1 && num < len; num += sent ) + { + sent = static_cast( ::send( m_socket, (data.c_str()+num), (int)(len - num), 0 ) ); + } + + m_totalBytesOut += (int)data.length(); + + m_sendMutex.unlock(); + + if( sent == -1 && m_handler ) + m_handler->handleDisconnect( this, ConnIoError ); + + return sent != -1; + } + + void ConnectionTCPBase::getStatistics( long int &totalIn, long int &totalOut ) + { + totalIn = m_totalBytesIn; + totalOut = m_totalBytesOut; + } + + void ConnectionTCPBase::cleanup() + { + if( !m_sendMutex.trylock() ) + return; + + if( !m_recvMutex.trylock() ) + { + m_sendMutex.unlock(); + return; + } + + if( m_socket >= 0 ) + { + DNS::closeSocket( m_socket, m_logInstance ); + m_socket = -1; + } + + m_state = StateDisconnected; + m_cancel = true; + m_totalBytesIn = 0; + m_totalBytesOut = 0; + + m_recvMutex.unlock(), + m_sendMutex.unlock(); + } + + int ConnectionTCPBase::localPort() const + { + struct sockaddr local; + socklen_t len = (socklen_t)sizeof( local ); + if( getsockname ( m_socket, &local, &len ) < 0 ) + return -1; + else + return ntohs( ((struct sockaddr_in *)&local)->sin_port ); + } + + const std::string ConnectionTCPBase::localInterface() const + { + struct sockaddr_in local; + socklen_t len = (socklen_t)sizeof( local ); + if( getsockname ( m_socket, (reinterpret_cast( &local )), &len ) < 0 ) + return EmptyString; + else + { +// char addr[INET_ADDRSTRLEN]; +// return inet_ntop( AF_INET, &(local.sin_addr), addr, sizeof( addr ) ); //FIXME is this portable? + return inet_ntoa( local.sin_addr ); + } + } + +} diff --git a/libs/libgloox/connectiontcpbase.h b/libs/libgloox/connectiontcpbase.h new file mode 100644 index 0000000..5ece2b1 --- /dev/null +++ b/libs/libgloox/connectiontcpbase.h @@ -0,0 +1,134 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef CONNECTIONTCPBASE_H__ +#define CONNECTIONTCPBASE_H__ + +#include "gloox.h" +#include "connectionbase.h" +#include "logsink.h" +#include "mutex.h" + +#include + +namespace gloox +{ + + namespace util + { + class Mutex; + } + + /** + * @brief This is a base class for a simple TCP connection. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API ConnectionTCPBase : public ConnectionBase + { + public: + /** + * Constructs a new ConnectionTCPBase object. + * @param logInstance The log target. Obtain it from ClientBase::logInstance(). + * @param server A server to connect to. + * @param port The port to connect to. The default of -1 means that XMPP SRV records + * will be used to find out about the actual host:port. + * @note To properly use this object, you have to set a ConnectionDataHandler using + * registerConnectionDataHandler(). This is not necessary if this object is + * part of a 'connection chain', e.g. with ConnectionHTTPProxy. + */ + ConnectionTCPBase( const LogSink& logInstance, const std::string& server, int port = -1 ); + + /** + * Constructs a new ConnectionTCPBase object. + * @param cdh An ConnectionDataHandler-derived object that will handle incoming data. + * @param logInstance The log target. Obtain it from ClientBase::logInstance(). + * @param server A server to connect to. + * @param port The port to connect to. The default of -1 means that SRV records will be used + * to find out about the actual host:port. + */ + ConnectionTCPBase( ConnectionDataHandler* cdh, const LogSink& logInstance, + const std::string& server, int port = -1 ); + + /** + * Virtual destructor + */ + virtual ~ConnectionTCPBase(); + + // reimplemented from ConnectionBase + virtual bool send( const std::string& data ); + + // reimplemented from ConnectionBase + virtual ConnectionError receive(); + + // reimplemented from ConnectionBase + virtual void disconnect(); + + // reimplemented from ConnectionBase + virtual void cleanup(); + + // reimplemented from ConnectionBase + virtual void getStatistics( long int &totalIn, long int &totalOut ); + + /** + * Gives access to the raw socket of this connection. Use it wisely. You can + * select()/poll() it and use ConnectionTCPBase::recv( -1 ) to fetch the data. + * @return The socket of the active connection, or -1 if no connection is established. + */ + int socket() const { return m_socket; } + + /** + * This function allows to set an existing socket with an established + * connection to use in this connection. You will still need to call connect() in order to + * negotiate the XMPP stream. You should not set a new socket after having called connect(). + * @param socket The existing socket. + */ + void setSocket( int socket ) { m_cancel = false; m_state = StateConnected; m_socket = socket; } + + /** + * Returns the local port. + * @return The local port. + */ + virtual int localPort() const; + + /** + * Returns the locally bound IP address. + * @return The locally bound IP address. + */ + virtual const std::string localInterface() const; + + protected: + ConnectionTCPBase& operator=( const ConnectionTCPBase& ); + void init( const std::string& server, int port ); + bool dataAvailable( int timeout = -1 ); + void cancel(); + + const LogSink& m_logInstance; + util::Mutex m_sendMutex; + util::Mutex m_recvMutex; + + char* m_buf; + int m_socket; + long int m_totalBytesIn; + long int m_totalBytesOut; + const int m_bufsize; + bool m_cancel; + + }; + +} + +#endif // CONNECTIONTCPBASE_H__ diff --git a/libs/libgloox/connectiontcpclient.cpp b/libs/libgloox/connectiontcpclient.cpp new file mode 100644 index 0000000..4b35bf6 --- /dev/null +++ b/libs/libgloox/connectiontcpclient.cpp @@ -0,0 +1,159 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "gloox.h" + +#include "connectiontcpclient.h" +#include "dns.h" +#include "logsink.h" +#include "mutexguard.h" + +#ifdef __MINGW32__ +# include +#endif + +#if ( !defined( _WIN32 ) && !defined( _WIN32_WCE ) ) || defined( __SYMBIAN32__ ) +# include +# include +# include +# include +#elif ( defined( _WIN32 ) || defined( _WIN32_WCE ) ) && !defined( __SYMBIAN32__ ) +# include +#endif + +#include +#include + +namespace gloox +{ + + ConnectionTCPClient::ConnectionTCPClient( const LogSink& logInstance, + const std::string& server, int port ) + : ConnectionTCPBase( logInstance, server, port ) + { + } + + ConnectionTCPClient::ConnectionTCPClient( ConnectionDataHandler* cdh, const LogSink& logInstance, + const std::string& server, int port ) + : ConnectionTCPBase( cdh, logInstance, server, port ) + { + } + + + ConnectionTCPClient::~ConnectionTCPClient() + { + } + + ConnectionBase* ConnectionTCPClient::newInstance() const + { + return new ConnectionTCPClient( m_handler, m_logInstance, m_server, m_port ); + } + + ConnectionError ConnectionTCPClient::connect() + { + m_sendMutex.lock(); +// FIXME CHECKME + if( !m_handler ) + { + m_sendMutex.unlock(); + return ConnNotConnected; + } + + if( m_socket >= 0 && m_state > StateDisconnected ) + { + m_sendMutex.unlock(); + return ConnNoError; + } + + m_state = StateConnecting; + + if( m_socket < 0 ) + { + if( m_port == -1 ) + m_socket = DNS::connect( m_server, m_logInstance ); + else + m_socket = DNS::connect( m_server, m_port, m_logInstance ); + } + + m_sendMutex.unlock(); + + if( m_socket < 0 ) + { + switch( m_socket ) + { + case -ConnConnectionRefused: + m_logInstance.err( LogAreaClassConnectionTCPClient, + m_server + ": connection refused" ); + break; + case -ConnDnsError: + m_logInstance.err( LogAreaClassConnectionTCPClient, + m_server + ": host not found" ); + break; + default: + m_logInstance.err( LogAreaClassConnectionTCPClient, + "Unknown error condition" ); + break; + } + m_handler->handleDisconnect( this, (ConnectionError)-m_socket ); + return (ConnectionError)-m_socket; + } + else + { + m_state = StateConnected; + } + + m_cancel = false; + m_handler->handleConnect( this ); + return ConnNoError; + } + + ConnectionError ConnectionTCPClient::recv( int timeout ) + { + m_recvMutex.lock(); + + if( m_cancel || m_socket < 0 ) + { + m_recvMutex.unlock(); + return ConnNotConnected; + } + + if( !dataAvailable( timeout ) ) + { + m_recvMutex.unlock(); + return ConnNoError; + } + + int size = static_cast( ::recv( m_socket, m_buf, m_bufsize, 0 ) ); + if( size > 0 ) + m_totalBytesIn += size; + + m_recvMutex.unlock(); + + if( size <= 0 ) + { + ConnectionError error = ( size ? ConnIoError : ConnStreamClosed ); + if( m_handler ) + m_handler->handleDisconnect( this, error ); + return error; + } + + m_buf[size] = '\0'; + + if( m_handler ) + m_handler->handleReceivedData( this, std::string( m_buf, size ) ); + + return ConnNoError; + } + +} diff --git a/libs/libgloox/connectiontcpclient.h b/libs/libgloox/connectiontcpclient.h new file mode 100644 index 0000000..0f3a5b5 --- /dev/null +++ b/libs/libgloox/connectiontcpclient.h @@ -0,0 +1,83 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef CONNECTIONTCPCLIENT_H__ +#define CONNECTIONTCPCLIENT_H__ + +#include "gloox.h" +#include "connectiontcpbase.h" +#include "logsink.h" + +#include + +namespace gloox +{ + + /** + * @brief This is an implementation of a simple TCP connection. + * + * You should only need to use this class directly if you need access to some special feature, like + * the raw socket(), or if you need HTTP proxy support (see @ref gloox::ConnectionHTTPProxy for more + * information). + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API ConnectionTCPClient : public ConnectionTCPBase + { + public: + /** + * Constructs a new ConnectionTCPClient object. + * @param logInstance The log target. Obtain it from ClientBase::logInstance(). + * @param server A server to connect to. + * @param port The port to connect to. The default of -1 means that XMPP SRV records + * will be used to find out about the actual host:port. + * @note To properly use this object, you have to set a ConnectionDataHandler using + * registerConnectionDataHandler(). This is not necessary if this object is + * part of a 'connection chain', e.g. with ConnectionHTTPProxy. + */ + ConnectionTCPClient( const LogSink& logInstance, const std::string& server, int port = -1 ); + + /** + * Constructs a new ConnectionTCPClient object. + * @param cdh An ConnectionDataHandler-derived object that will handle incoming data. + * @param logInstance The log target. Obtain it from ClientBase::logInstance(). + * @param server A server to connect to. + * @param port The port to connect to. The default of -1 means that SRV records will be used + * to find out about the actual host:port. + */ + ConnectionTCPClient( ConnectionDataHandler* cdh, const LogSink& logInstance, + const std::string& server, int port = -1 ); + + /** + * Virtual destructor + */ + virtual ~ConnectionTCPClient(); + + // reimplemented from ConnectionBase + virtual ConnectionError recv( int timeout = -1 ); + + // reimplemented from ConnectionBase + virtual ConnectionError connect(); + + // reimplemented from ConnectionBase + virtual ConnectionBase* newInstance() const; + + private: + ConnectionTCPClient &operator=( const ConnectionTCPClient & ); + + }; + +} + +#endif // CONNECTIONTCPCLIENT_H__ diff --git a/libs/libgloox/connectiontcpserver.cpp b/libs/libgloox/connectiontcpserver.cpp new file mode 100644 index 0000000..fa108e2 --- /dev/null +++ b/libs/libgloox/connectiontcpserver.cpp @@ -0,0 +1,163 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "gloox.h" + +#include "connectiontcpserver.h" +#include "connectiontcpclient.h" +#include "connectionhandler.h" +#include "dns.h" +#include "logsink.h" +#include "mutex.h" +#include "mutexguard.h" +#include "util.h" + +#ifdef __MINGW32__ +# include +#endif + +#if ( !defined( _WIN32 ) && !defined( _WIN32_WCE ) ) || defined( __SYMBIAN32__ ) +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) +# include +#elif defined( _WIN32_WCE ) +# include +#endif + +#include +#include + +#ifndef _WIN32_WCE +# include +#endif + +namespace gloox +{ + + ConnectionTCPServer::ConnectionTCPServer( ConnectionHandler* ch, const LogSink& logInstance, + const std::string& ip, int port ) + : ConnectionTCPBase( 0, logInstance, ip, port ), + m_connectionHandler( ch ) + { + } + + ConnectionTCPServer::~ConnectionTCPServer() + { + } + + ConnectionBase* ConnectionTCPServer::newInstance() const + { + return new ConnectionTCPServer( m_connectionHandler, m_logInstance, m_server, m_port ); + } + + ConnectionError ConnectionTCPServer::connect() + { + util::MutexGuard mg( &m_sendMutex ); + + if( m_socket >= 0 || m_state > StateDisconnected ) + return ConnNoError; + + m_state = StateConnecting; + + if( m_socket < 0 ) + m_socket = DNS::getSocket( m_logInstance ); + + if( m_socket < 0 ) + return ConnIoError; + + struct sockaddr_in local; + local.sin_family = AF_INET; + local.sin_port = static_cast( htons( m_port ) ); + local.sin_addr.s_addr = m_server.empty() ? INADDR_ANY : inet_addr( m_server.c_str() ); + memset( local.sin_zero, '\0', 8 ); + + if( bind( m_socket, (struct sockaddr*)&local, sizeof( struct sockaddr ) ) < 0 ) + { + std::string message = "bind() to " + ( m_server.empty() ? std::string( "*" ) : m_server ) + + " (" + inet_ntoa( local.sin_addr ) + ":" + util::int2string( m_port ) + ") failed. " +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + "WSAGetLastError: " + util::int2string( ::WSAGetLastError() ); +#else + "errno: " + util::int2string( errno ); +#endif + m_logInstance.dbg( LogAreaClassConnectionTCPServer, message ); + + return ConnIoError; + } + + if( listen( m_socket, 10 ) < 0 ) + { + std::string message = "listen on " + ( m_server.empty() ? std::string( "*" ) : m_server ) + + " (" + inet_ntoa( local.sin_addr ) + ":" + util::int2string( m_port ) + ") failed. " +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + "WSAGetLastError: " + util::int2string( ::WSAGetLastError() ); +#else + "errno: " + util::int2string( errno ); +#endif + m_logInstance.dbg( LogAreaClassConnectionTCPServer, message ); + + return ConnIoError; + } + + m_cancel = false; + return ConnNoError; + } + + ConnectionError ConnectionTCPServer::recv( int timeout ) + { + m_recvMutex.lock(); + + if( m_cancel || m_socket < 0 || !m_connectionHandler ) + { + m_recvMutex.unlock(); + return ConnNotConnected; + } + + if( !dataAvailable( timeout ) ) + { + m_recvMutex.unlock(); + return ConnNoError; + } + + struct sockaddr_in they; + int sin_size = sizeof( struct sockaddr_in ); +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + int newfd = static_cast( accept( static_cast( m_socket ), (struct sockaddr*)&they, &sin_size ) ); +#else + int newfd = accept( m_socket, (struct sockaddr*)&they, (socklen_t*)&sin_size ); +#endif + + m_recvMutex.unlock(); + + ConnectionTCPClient* conn = new ConnectionTCPClient( m_logInstance, inet_ntoa( they.sin_addr ), + ntohs( they.sin_port ) ); + conn->setSocket( newfd ); + m_connectionHandler->handleIncomingConnection( this, conn ); + + return ConnNoError; + } + +} diff --git a/libs/libgloox/connectiontcpserver.h b/libs/libgloox/connectiontcpserver.h new file mode 100644 index 0000000..ef0bd08 --- /dev/null +++ b/libs/libgloox/connectiontcpserver.h @@ -0,0 +1,77 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef CONNECTIONTCPSERVER_H__ +#define CONNECTIONTCPSERVER_H__ + +#include "gloox.h" +#include "connectiontcpbase.h" +#include "logsink.h" + +#include + +namespace gloox +{ + + class ConnectionHandler; + + /** + * @brief This is an implementation of a simple listening TCP connection. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API ConnectionTCPServer : public ConnectionTCPBase + { + public: + /** + * Constructs a new ConnectionTCPServer object. + * @param ch An ConnectionHandler-derived object that will handle incoming connections. + * @param logInstance The log target. Obtain it from ClientBase::logInstance(). + * @param ip The local IP address to listen on. This must @b not be a hostname. + * Leave this empty to listen on all local interfaces. + * @param port The port to listen on. + */ + ConnectionTCPServer( ConnectionHandler* ch, const LogSink& logInstance, + const std::string& ip, int port ); + + /** + * Virtual destructor + */ + virtual ~ConnectionTCPServer(); + + // reimplemented from ConnectionBase + virtual ConnectionError recv( int timeout = -1 ); + + /** + * This function actually starts @c listening on the port given in the + * constructor. + */ + // reimplemented from ConnectionBase + virtual ConnectionError connect(); + + // reimplemented from ConnectionBase + virtual ConnectionBase* newInstance() const; + + private: + ConnectionTCPServer &operator=( const ConnectionTCPServer & ); + + ConnectionHandler* m_connectionHandler; + + }; + +} + +#endif // CONNECTIONTCPSERVER_H__ diff --git a/libs/libgloox/connectiontls.cpp b/libs/libgloox/connectiontls.cpp new file mode 100644 index 0000000..7429bed --- /dev/null +++ b/libs/libgloox/connectiontls.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2007-2009 by Jakob Schroeter + * This file is part of the gloox library. http://camaya.net/gloox + * + * This software is distributed under a license. The full license + * agreement can be found in the file LICENSE in this distribution. + * This software may not be copied, modified, sold or distributed + * other than expressed in the named license agreement. + * + * This software is distributed without any warranty. + */ + + +#include "connectiontls.h" +#include "tlsdefault.h" + +namespace gloox +{ + + ConnectionTLS::ConnectionTLS( ConnectionDataHandler* cdh, ConnectionBase* conn, const LogSink& log ) + : ConnectionBase( cdh ), + m_connection( conn ), m_tls( 0 ), m_tlsHandler( 0 ), + m_log( log ) + { + if( m_connection ) + m_connection->registerConnectionDataHandler( this ); + } + + ConnectionTLS::ConnectionTLS( ConnectionBase* conn, const LogSink& log ) + : ConnectionBase( 0 ), + m_connection( conn ), m_tls( 0 ), m_tlsHandler( 0 ), m_log( log ) + { + if( m_connection ) + m_connection->registerConnectionDataHandler( this ); + } + + ConnectionTLS::~ConnectionTLS() + { + delete m_connection; + delete m_tls; + } + + void ConnectionTLS::setConnectionImpl( ConnectionBase* connection ) + { + if( m_connection ) + m_connection->registerConnectionDataHandler( 0 ); + + m_connection = connection; + + if( m_connection ) + m_connection->registerConnectionDataHandler( this ); + } + + ConnectionError ConnectionTLS::connect() + { + if( !m_connection ) + return ConnNotConnected; + + if( m_state == StateConnected ) + return ConnNoError; + + if( !m_tls ) + m_tls = getTLSBase( this, m_connection->server() ); + + if( !m_tls ) + return ConnTlsNotAvailable; + + if( !m_tls->init( m_clientKey, m_clientCerts, m_cacerts ) ) + return ConnTlsFailed; + +// m_tls->setCACerts( m_cacerts ); +// m_tls->setClientCert( m_clientKey, m_clientCerts ); + + m_state = StateConnecting; + + if( m_connection->state() != StateConnected ) + return m_connection->connect(); + + if( m_tls->handshake() ) + return ConnNoError; + else + return ConnTlsFailed; + } + + ConnectionError ConnectionTLS::recv( int timeout ) + { + if( m_connection->state() == StateConnected ) + { + return m_connection->recv( timeout ); + } + else + { + m_log.log( LogLevelWarning, LogAreaClassConnectionTLS, + "Attempt to receive data on a connection that is not connected (or is connecting)" ); + return ConnNotConnected; + } + } + + bool ConnectionTLS::send( const std::string& data ) + { + if( m_state != StateConnected ) + return false; + + m_tls->encrypt( data ); + return true; + } + + ConnectionError ConnectionTLS::receive() + { + if( m_connection ) + return m_connection->receive(); + else + return ConnNotConnected; + } + + void ConnectionTLS::disconnect() + { + if( m_connection ) + m_connection->disconnect(); + + cleanup(); + } + + void ConnectionTLS::cleanup() + { + if( m_connection ) + m_connection->cleanup(); + if( m_tls ) + m_tls->cleanup(); + + m_state = StateDisconnected; + } + + void ConnectionTLS::getStatistics( long int& totalIn, long int& totalOut ) + { + if( m_connection ) + m_connection->getStatistics( totalIn, totalOut ); + } + + ConnectionBase* ConnectionTLS::newInstance() const + { + ConnectionBase* newConn = 0; + if( m_connection ) + newConn = m_connection->newInstance(); + return new ConnectionTLS( m_handler, newConn, m_log ); + } + + void ConnectionTLS::handleReceivedData( const ConnectionBase* /*connection*/, const std::string& data ) + { + if( m_tls ) + m_tls->decrypt( data ); + } + + void ConnectionTLS::handleConnect( const ConnectionBase* /*connection*/ ) + { + if( m_tls ) + m_tls->handshake(); + } + + void ConnectionTLS::handleDisconnect( const ConnectionBase* /*connection*/, ConnectionError reason ) + { + if( m_handler ) + m_handler->handleDisconnect( this, reason ); + + cleanup(); + } + + void ConnectionTLS::handleEncryptedData( const TLSBase* /*tls*/, const std::string& data ) + { + if( m_connection ) + m_connection->send( data ); + } + + void ConnectionTLS::handleDecryptedData( const TLSBase* /*tls*/, const std::string& data ) + { + if( m_handler ) + m_handler->handleReceivedData( this, data ); + else + { + m_log.log( LogLevelDebug, LogAreaClassConnectionTLS, "Data received and decrypted but no handler" ); + } + } + + void ConnectionTLS::handleHandshakeResult( const TLSBase* tls, bool success, CertInfo& certinfo ) + { + if( success ) + { + m_state = StateConnected; + m_log.log( LogLevelDebug, LogAreaClassConnectionTLS, "TLS handshake succeeded" ); + if( m_tlsHandler ) + m_tlsHandler->handleHandshakeResult( tls, success, certinfo ); + if( m_handler ) + m_handler->handleConnect( this ); + } + else + { + m_state = StateDisconnected; + m_log.log( LogLevelWarning, LogAreaClassConnectionTLS, "TLS handshake failed" ); + if( m_tlsHandler ) + m_tlsHandler->handleHandshakeResult( tls, success, certinfo ); + } + } + +} diff --git a/libs/libgloox/connectiontls.h b/libs/libgloox/connectiontls.h new file mode 100644 index 0000000..fc197c0 --- /dev/null +++ b/libs/libgloox/connectiontls.h @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2007-2009 by Jakob Schroeter + * This file is part of the gloox library. http://camaya.net/gloox + * + * This software is distributed under a license. The full license + * agreement can be found in the file LICENSE in this distribution. + * This software may not be copied, modified, sold or distributed + * other than expressed in the named license agreement. + * + * This software is distributed without any warranty. + */ + +#ifndef CONNECTIONTLS_H__ +#define CONNECTIONTLS_H__ + +#include "gloox.h" +#include "logsink.h" +#include "connectionbase.h" +#include "tlsdefault.h" +#include "connectiondatahandler.h" + +#include + +namespace gloox +{ + + /** + * @brief This is an implementation of a TLS/SSL connection. + * + * You should not need to use this function directly. However, + * you can use it to connect to the legacy Jabber SSL port, + * 5223. + * + * Usage: + * @code + * Client *c = new Client( ... ); + * c->setConnectionImpl( new ConnectionTLS( c, + * new ConnectionTCP( c->logInstance(), server, 5223 ), + * c->logInstance()) ); + * @endcode + * + * Due to the need for handshaking data to be sent/received before the connection is fully + * established, be sure not to use the connection until ConnectionDataHandler::handleConnect() + * of the specified ConnectionDataHandler is called. + * + * @author Jakob Schroeter + * @author Matthew Wild + * @since 1.0 + */ + + class GLOOX_API ConnectionTLS : public TLSHandler, public ConnectionBase, public ConnectionDataHandler + { + public: + /** + * Constructs a new ConnectionTLS object. + * @param cdh The ConnectionDataHandler that will be notified of events from this connection + * @param conn A transport connection. It should be configured to connect to + * the server and port you wish to make the encrypted connection to. + * ConnectionTLS will own the transport connection and delete it in its destructor. + * @param log The log target. Obtain it from ClientBase::logInstance(). + */ + ConnectionTLS( ConnectionDataHandler* cdh, ConnectionBase* conn, const LogSink& log ); + + /** + * Constructs a new ConnectionTLS object. + * @param conn A transport connection. It should be configured to connect to + * the server and port you wish to make the encrypted connection to. + * ConnectionTLS will own the transport connection and delete it in its destructor. + * @param log The log target. Obtain it from ClientBase::logInstance(). + */ + ConnectionTLS( ConnectionBase* conn, const LogSink& log ); + + /** + * Virtual Destructor. + */ + virtual ~ConnectionTLS(); + + /** + * Use this function to set a number of trusted root CA certificates which shall be + * used to verify a servers certificate. + * @param cacerts A list of absolute paths to CA root certificate files in PEM format. + * @note This function is a wrapper for TLSBase::setCACerts(). + */ + void setCACerts( const StringList& cacerts ) + { + m_cacerts = cacerts; + } + + /** + * This function is used to retrieve certificate and connection info of a encrypted connection. + * @return Certificate information. + * @note This funcztion is a wrapper around TLSBase::fetchTLSInfo(). + */ + const CertInfo& fetchTLSInfo() const { return m_certInfo; } + + /** + * Use this function to set the user's certificate and private key. The certificate will + * be presented to the server upon request and can be used for SASL EXTERNAL authentication. + * The user's certificate file should be a bundle of more than one certificate in PEM format. + * The first one in the file should be the user's certificate, each cert following that one + * should have signed the previous one. + * @note These certificates are not necessarily the same as those used to verify the server's + * certificate. + * @param clientKey The absolute path to the user's private key in PEM format. + * @param clientCerts A path to a certificate bundle in PEM format. + * @note This function is a wrapper around TLSBase::setClientCert(). + */ + void setClientCert( const std::string& clientKey, const std::string& clientCerts ) + { + m_clientKey = clientKey; + m_clientCerts = clientCerts; + } + + /** + * Sets the transport connection. + * @param connection The transport connection to use. + */ + void setConnectionImpl( ConnectionBase* connection ); + + /** + * Registers an TLSHandler derived object. Only the handleHandshakeResult() + * function will be used after a handshake took place. + * You can review certificate info there. + * @param th The TLSHandler to register. + * @note If no handler is set, ConnectionTLS will accept + * any certificate and continue with the connection. + */ + void registerTLSHandler( TLSHandler* th ) { m_tlsHandler = th; } + + // reimplemented from ConnectionBase + virtual ConnectionError connect(); + + // reimplemented from ConnectionBase + virtual ConnectionError recv( int timeout = -1 ); + + // reimplemented from ConnectionBase + virtual bool send( const std::string& data ); + + // reimplemented from ConnectionBase + virtual ConnectionError receive(); + + // reimplemented from ConnectionBase + virtual void disconnect(); + + // reimplemented from ConnectionBase + virtual void cleanup(); + + // reimplemented from ConnectionBase + virtual void getStatistics( long int& totalIn, long int& totalOut ); + + // reimplemented from ConnectionDataHandler + virtual void handleReceivedData( const ConnectionBase* connection, const std::string& data ); + + // reimplemented from ConnectionDataHandler + virtual void handleConnect( const ConnectionBase* connection ); + + // reimplemented from ConnectionDataHandler + virtual void handleDisconnect( const ConnectionBase* connection, ConnectionError reason ); + + // reimplemented from ConnectionDataHandler + virtual ConnectionBase* newInstance() const; + + // reimplemented from TLSHandler + virtual void handleEncryptedData( const TLSBase*, const std::string& data ); + + // reimplemented from TLSHandler + virtual void handleDecryptedData( const TLSBase*, const std::string& data ); + + // reimplemented from TLSHandler + virtual void handleHandshakeResult( const TLSBase* base, bool success, CertInfo& certinfo ); + + protected: + /** + * Returns a TLS object (client). Reimplement to change the + * type of the object. + * @return A TLS object. + */ + virtual TLSBase* getTLSBase( TLSHandler* th, const std::string server ) + { + return new TLSDefault( th, server, TLSDefault::VerifyingClient ); + } + + ConnectionBase* m_connection; + TLSBase* m_tls; + TLSHandler* m_tlsHandler; + CertInfo m_certInfo; + const LogSink& m_log; + StringList m_cacerts; + std::string m_clientCerts; + std::string m_clientKey; + + private: + ConnectionTLS& operator=( const ConnectionTLS& ); + + }; + +} + +#endif // CONNECTIONTLS_H__ diff --git a/libs/libgloox/connectiontlsserver.cpp b/libs/libgloox/connectiontlsserver.cpp new file mode 100644 index 0000000..01aff74 --- /dev/null +++ b/libs/libgloox/connectiontlsserver.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2009 by Jakob Schroeter + * This file is part of the gloox library. http://camaya.net/gloox + * + * This software is distributed under a license. The full license + * agreement can be found in the file LICENSE in this distribution. + * This software may not be copied, modified, sold or distributed + * other than expressed in the named license agreement. + * + * This software is distributed without any warranty. + */ + +#include "connectiontlsserver.h" + +namespace gloox +{ + + ConnectionTLSServer::ConnectionTLSServer( ConnectionDataHandler* cdh, ConnectionBase* conn, + const LogSink& log ) + : ConnectionTLS( cdh, conn, log ) + { + } + + ConnectionTLSServer::ConnectionTLSServer( ConnectionBase* conn, const LogSink& log ) + : ConnectionTLS( conn, log ) + { + } + + ConnectionTLSServer::~ConnectionTLSServer() + { + } + + TLSBase* ConnectionTLSServer::getTLSBase( TLSHandler* th, const std::string server ) + { + return new TLSDefault( th, server, TLSDefault::VerifyingServer ); + } + + ConnectionBase* ConnectionTLSServer::newInstance() const + { + ConnectionBase* newConn = 0; + if( m_connection ) + newConn = m_connection->newInstance(); + return new ConnectionTLSServer( m_handler, newConn, m_log ); + } + +} diff --git a/libs/libgloox/connectiontlsserver.h b/libs/libgloox/connectiontlsserver.h new file mode 100644 index 0000000..7fb5637 --- /dev/null +++ b/libs/libgloox/connectiontlsserver.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2009 by Jakob Schroeter + * This file is part of the gloox library. http://camaya.net/gloox + * + * This software is distributed under a license. The full license + * agreement can be found in the file LICENSE in this distribution. + * This software may not be copied, modified, sold or distributed + * other than expressed in the named license agreement. + * + * This software is distributed without any warranty. + */ + +#ifndef CONNECTIONTLSSERVER_H__ +#define CONNECTIONTLSSERVER_H__ + +#include "macros.h" +#include "logsink.h" +#include "connectionbase.h" +#include "connectiontls.h" +#include "tlsdefault.h" +#include "tlshandler.h" + +#include + +namespace gloox +{ + + class ConnectionDataHandler; + + /** + * @brief This is an implementation of the server-side of a TLS/SSL connection. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API ConnectionTLSServer : public ConnectionTLS + { + public: + /** + * Constructs a new ConnectionTLSServer object. + * @param cdh The ConnectionDataHandler that will be notified of events from this connection + * @param conn A transport connection. It should be an established connection from + * a client that is about to perform a TLS handshake. + * ConnectionTLSServer will own the transport connection and delete it in its destructor. + * @param log The log target. Obtain it from ClientBase::logInstance(). + */ + ConnectionTLSServer( ConnectionDataHandler* cdh, ConnectionBase* conn, const LogSink& log ); + + /** + * Constructs a new ConnectionTLSServer object. + * @param conn A transport connection. It should be an established connection from + * a client that is about to perform a TLS handshake. + * ConnectionTLSServer will own the transport connection and delete it in its destructor. + * @param log The log target. Obtain it from ClientBase::logInstance(). + */ + ConnectionTLSServer( ConnectionBase* conn, const LogSink& log ); + + /** + * Virtual Destructor. + */ + virtual ~ConnectionTLSServer(); + + /** + * Returns a TLS server. + * @return A TLS server. + */ + virtual TLSBase* getTLSBase( TLSHandler* th, const std::string server ); + + // reimplemented from ConnectionTLS + virtual ConnectionBase* newInstance() const; + + private: + ConnectionTLSServer& operator=( const ConnectionTLSServer& ); + + }; + +} + +#endif // CONNECTIONTLSSERVER_H__ diff --git a/libs/libgloox/dataform.cpp b/libs/libgloox/dataform.cpp new file mode 100644 index 0000000..7cb6b12 --- /dev/null +++ b/libs/libgloox/dataform.cpp @@ -0,0 +1,137 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "dataform.h" +#include "dataformfield.h" +#include "dataformitem.h" +#include "dataformreported.h" +#include "util.h" +#include "tag.h" + +namespace gloox +{ + + DataForm::DataForm( FormType type, const StringList& instructions, const std::string& title ) + : StanzaExtension( ExtDataForm ), + m_type( type ), m_instructions( instructions ), m_title( title ), m_reported( 0 ) + { + } + + DataForm::DataForm( FormType type, const std::string& title ) + : StanzaExtension( ExtDataForm ), + m_type( type ), m_title( title ), m_reported( 0 ) + { + } + + DataForm::DataForm( const Tag* tag ) + : StanzaExtension( ExtDataForm ), + m_type( TypeInvalid ), m_reported( 0 ) + { + parse( tag ); + } + + DataForm::DataForm( const DataForm& form ) + : StanzaExtension( ExtDataForm ), DataFormFieldContainer( form ), + m_type( form.m_type ), m_instructions( form.m_instructions ), + m_title( form.m_title ), m_reported( form.m_reported ? new DataFormReported( form.m_reported->tag() ) : 0 ) + { + } + + DataForm::~DataForm() + { + util::clearList( m_items ); + delete m_reported; + m_reported = NULL; + } + + static const char* dfTypeValues[] = + { + "form", "submit", "cancel", "result" + }; + + bool DataForm::parse( const Tag* tag ) + { + if( !tag || tag->xmlns() != XMLNS_X_DATA || tag->name() != "x" ) + return false; + + const std::string& type = tag->findAttribute( TYPE ); + if( type.empty() ) + m_type = TypeForm; + else + { + m_type = (FormType)util::lookup( type, dfTypeValues ); + if( m_type == TypeInvalid ) + return false; + } + + const TagList& l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + if( (*it)->name() == "title" ) + m_title = (*it)->cdata(); + else if( (*it)->name() == "instructions" ) + m_instructions.push_back( (*it)->cdata() ); + else if( (*it)->name() == "field" ) + m_fields.push_back( new DataFormField( (*it) ) ); + else if( (*it)->name() == "reported" ) + { + if( m_reported == NULL ) + m_reported = new DataFormReported( (*it) ); + // else - Invalid data form - only one "reported" is allowed + } + else if( (*it)->name() == "item" ) + m_items.push_back( new DataFormItem( (*it) ) ); + } + + return true; + } + + const std::string& DataForm::filterString() const + { + static const std::string filter = "/message/x[@xmlns='" + XMLNS_X_DATA + "']"; + return filter; + } + + Tag* DataForm::tag() const + { + if( m_type == TypeInvalid ) + return 0; + + Tag* x = new Tag( "x" ); + x->setXmlns( XMLNS_X_DATA ); + x->addAttribute( TYPE, util::lookup( m_type, dfTypeValues ) ); + if( !m_title.empty() ) + new Tag( x, "title", m_title ); + + StringList::const_iterator it_i = m_instructions.begin(); + for( ; it_i != m_instructions.end(); ++it_i ) + new Tag( x, "instructions", (*it_i) ); + + FieldList::const_iterator it = m_fields.begin(); + for( ; it != m_fields.end(); ++it ) + x->addChild( (*it)->tag() ); + + if( m_reported != NULL ) + { + x->addChild( m_reported->tag() ); + } + + ItemList::const_iterator iti = m_items.begin(); + for( ; iti != m_items.end(); ++iti ) + x->addChild( (*iti)->tag() ); + + return x; + } + +} diff --git a/libs/libgloox/dataform.h b/libs/libgloox/dataform.h new file mode 100644 index 0000000..6fb75d5 --- /dev/null +++ b/libs/libgloox/dataform.h @@ -0,0 +1,197 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef DATAFORM_H__ +#define DATAFORM_H__ + +#include "dataformfieldcontainer.h" +#include "stanzaextension.h" + +#include +#include + +namespace gloox +{ + + class Tag; + class DataFormItem; + class DataFormReported; + + /** + * Describes the possible Form Types. + */ + enum FormType + { + TypeForm, /**< The forms-processing entity is asking the forms-submitting + * entity to complete a form. */ + TypeSubmit, /**< The forms-submitting entity is submitting data to the + * forms-processing entity. */ + TypeCancel, /**< The forms-submitting entity has cancelled submission of data + * to the forms-processing entity. */ + TypeResult, /**< The forms-processing entity is returning data (e.g., search + * results) to the forms-submitting entity, or the data is a + * generic data set. */ + TypeInvalid /**< The form is invalid. Only possible if the form was created + * from an Tag which doesn't correctly describe a Data Form. */ + }; + + /** + * @brief An abstraction of a XEP-0004 Data Form. + * + * + * + * @author Jakob Schroeter + * @since 0.7 + */ + class GLOOX_API DataForm : public StanzaExtension, public DataFormFieldContainer + { + public: + /** + * A list of DataFormItems. + */ + typedef std::list ItemList; + + /** + * Constructs a new, empty form. + * @param type The form type. + * @param instructions Natural-language instructions for filling out the form. Should not contain + * newlines (\\n, \\r). + * @param title The natural-language title of the form. Should not contain newlines (\\n, \\r). + */ + DataForm( FormType type, const StringList& instructions, const std::string& title = EmptyString ); + + /** + * Constructs a new, empty form without any instructions or title set. Probably best suited for + * result forms. + * @param type The form type. + * @param title The natural-language title of the form. Should not contain newlines (\\n, \\r). + * @since 0.9 + */ + DataForm( FormType type, const std::string& title = EmptyString ); + + /** + * Constructs a new DataForm from an existing Tag/XML representation. + * @param tag The existing form to parse. + */ + DataForm( const Tag* tag ); + + /** + * Creates a new DataForm, copying the given one. + * @param form The form to copy. + */ + DataForm( const DataForm& form ); + + /** + * Virtual destructor. + */ + virtual ~DataForm(); + + /** + * Use this function to retrieve the title of the form. + * @return The title of the form. + */ + const std::string& title() const { return m_title; } + + /** + * Use this function to set the title of the form. + * @param title The new title of the form. + * @note The title should not contain newlines (\\n, \\r). + */ + void setTitle( const std::string& title ) { m_title = title; } + + /** + * Retrieves the natural-language instructions for the form. + * @return The fill-in instructions for the form. + */ + const StringList& instructions() const { return m_instructions; } + + /** + * Use this function to set natural-language instructions for the form. + * @param instructions The instructions for the form. + * @note The instructions should not contain newlines (\\n, \\r). Instead, every line should be an + * element of the StringMap. This allows for platform dependent newline handling on the target + * platform. + */ + void setInstructions( const StringList& instructions ) { m_instructions = instructions; } + + /** + * Returns the reported field list in a DataForm. + * @return The reported section, containing 0..n fields. + */ + const DataFormReported* reported() const { return m_reported; } + + /** + * Returns a list of items in a DataForm. + * @return A list of items. + */ + const ItemList& items() const { return m_items; } + + /** + * Returns the form's type. + * @return The form's type. + * @since 0.9 + */ + FormType type() const { return m_type; } + + /** + * Sets the form's type. + * @param type The form's new type. + */ + void setType( FormType type ) { m_type = type; } + + /** + * Parses the given Tag and creates an appropriate DataForm representation. + * @param tag The Tag to parse. + * @return @b True on success, @b false otherwise. + * @since 0.9 + */ + bool parse( const Tag* tag ); + + /** + * Converts to @b true if the DataForm is valid, @b false otherwise. + */ + operator bool() const { return m_type != TypeInvalid; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new DataForm( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new DataForm( *this ); + } + + protected: + FormType m_type; + + private: + StringList m_instructions; + + std::string m_title; + DataFormReported* m_reported; + ItemList m_items; + + }; + +} + +#endif // DATAFORM_H__ diff --git a/libs/libgloox/dataformfield.cpp b/libs/libgloox/dataformfield.cpp new file mode 100644 index 0000000..462181c --- /dev/null +++ b/libs/libgloox/dataformfield.cpp @@ -0,0 +1,134 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "dataformfield.h" +#include "util.h" +#include "tag.h" + +namespace gloox +{ + + static const char* fieldTypeValues[] = + { + "boolean", "fixed", "hidden", "jid-multi", "jid-single", + "list-multi", "list-single", "text-multi", "text-private", "text-single", "" + }; + + DataFormField::DataFormField( FieldType type ) + : m_type( type ), m_required( false ) + { + } + + DataFormField::DataFormField( const std::string& name, const std::string& value, + const std::string& label, FieldType type ) + : m_type( type ), m_name( name ), m_label( label ), m_required( false ) + { + m_values.push_back( value ); + } + + DataFormField::DataFormField( const Tag* tag ) + : m_type( TypeInvalid ), m_required( false ) + { + if( !tag ) + return; + + const std::string& type = tag->findAttribute( TYPE ); + if( type.empty() ) + { + if( !tag->name().empty() ) + m_type = TypeNone; + } + else + m_type = (FieldType)util::lookup( type, fieldTypeValues ); + + if( tag->hasAttribute( "var" ) ) + m_name = tag->findAttribute( "var" ); + + if( tag->hasAttribute( "label" ) ) + m_label = tag->findAttribute( "label" ); + + const TagList& l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + if( (*it)->name() == "desc" ) + m_desc = (*it)->cdata(); + else if( (*it)->name() == "required" ) + m_required = true; + else if( (*it)->name() == "value" ) + { + if( m_type == TypeTextMulti || m_type == TypeListMulti || m_type == TypeJidMulti ) + addValue( (*it)->cdata() ); + else + setValue( (*it)->cdata() ); + } + else if( (*it)->name() == "option" ) + { + Tag* v = (*it)->findChild( "value" ); + if( v ) + m_options.insert( std::make_pair( (*it)->findAttribute( "label" ), v->cdata() ) ); + } + } + + } + + DataFormField::~DataFormField() + { + } + + Tag* DataFormField::tag() const + { + if( m_type == TypeInvalid ) + return 0; + + Tag* field = new Tag( "field" ); + field->addAttribute( TYPE, util::lookup( m_type, fieldTypeValues ) ); + field->addAttribute( "var", m_name ); + field->addAttribute( "label", m_label ); + if( m_required ) + new Tag( field, "required" ); + + if( !m_desc.empty() ) + new Tag( field, "desc", m_desc ); + + if( m_type == TypeListSingle || m_type == TypeListMulti ) + { + StringMultiMap::const_iterator it = m_options.begin(); + for( ; it != m_options.end(); ++it ) + { + Tag* option = new Tag( field, "option", "label", (*it).first ); + new Tag( option, "value", (*it).second ); + } + } + else if( m_type == TypeBoolean ) + { + if( m_values.size() == 0 || m_values.front() == "false" || m_values.front() == "0" ) + new Tag( field, "value", "0" ); + else + new Tag( field, "value", "1" ); + } + + if( m_type == TypeTextMulti || m_type == TypeListMulti || m_type == TypeJidMulti ) + { + StringList::const_iterator it = m_values.begin(); + for( ; it != m_values.end() ; ++it ) + new Tag( field, "value", (*it) ); + } + + if( m_values.size() && !( m_type == TypeTextMulti || m_type == TypeListMulti + || m_type == TypeBoolean || m_type == TypeJidMulti ) ) + new Tag( field, "value", m_values.front() ); + + return field; + } + +} diff --git a/libs/libgloox/dataformfield.h b/libs/libgloox/dataformfield.h new file mode 100644 index 0000000..c3e21c8 --- /dev/null +++ b/libs/libgloox/dataformfield.h @@ -0,0 +1,242 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef DATAFORMFIELD_H__ +#define DATAFORMFIELD_H__ + +#include "gloox.h" + +#include +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief An abstraction of a single field in a XEP-0004 Data Form. + * + * @author Jakob Schroeter + * @since 0.7 + */ + class GLOOX_API DataFormField + { + + public: + /** + * Describes the possible types of a Data Form Field. + */ + enum FieldType + { + TypeBoolean, /**< The field enables an entity to gather or provide an either-or + * choice between two options. The default value is "false". */ + TypeFixed, /**< The field is intended for data description (e.g., + * human-readable text such as "section" headers) rather than data + * gathering or provision. The <value/> child SHOULD NOT contain + * newlines (the \\n and \\r characters); instead an application SHOULD + * generate multiple fixed fields, each with one <value/> child. */ + TypeHidden, /**< The field is not shown to the entity providing information, but + * instead is returned with the form. */ + TypeJidMulti, /**< The field enables an entity to gather or provide multiple Jabber + * IDs.*/ + TypeJidSingle, /**< The field enables an entity to gather or provide a single Jabber + * ID.*/ + TypeListMulti, /**< The field enables an entity to gather or provide one or more options + * from among many. */ + TypeListSingle, /**< The field enables an entity to gather or provide one option from + * among many. */ + TypeTextMulti, /**< The field enables an entity to gather or provide multiple lines of + * text. */ + TypeTextPrivate, /**< The field enables an entity to gather or provide a single line or + * word of text, which shall be obscured in an interface + * (e.g., *****). */ + TypeTextSingle, /**< The field enables an entity to gather or provide a single line or + * word of text, which may be shown in an interface. This field type is + * the default and MUST be assumed if an entity receives a field type it + * does not understand.*/ + TypeNone, /**< The field is child of either a <reported> or <item> + * element or has no type attribute. */ + TypeInvalid /**< The field is invalid. Only possible if the field was created from + * a Tag not correctly describing a Data Form Field. */ + }; + + public: + + /** + * Constructs a new DataForm field. + * @param type The type of the field. Default: text-single. + */ + DataFormField( FieldType type = TypeTextSingle ); + + /** + * Constructs a new DataForm field and fills it with the given values. + * @param name The field's name (the value of the 'var' attribute). + * @param value The field's value. + * @param label The field's label. + * @param type The field's type. + * @since 0.9 + */ + DataFormField( const std::string& name, const std::string& value = EmptyString, + const std::string& label = EmptyString, FieldType type = TypeTextSingle ); + + /** + * Constructs a new Data Form Field from an existing tag that describes a field. + * @param tag The tag to parse. + */ + DataFormField( const Tag* tag ); + + /** + * Virtual destructor. + */ + virtual ~DataFormField(); + + /** + * Use this function to retrieve the optional values of a field. + * @return The options of a field. + */ + const StringMultiMap& options() const { return m_options; } + + /** + * Use this function to create a Tag representation of the form field. This is usually called by + * DataForm. + * @return A Tag hierarchically describing the form field, or NULL if the field is invalid (i.e. + * created from a Tag not correctly describing a Data Form Field). + */ + virtual Tag* tag() const; + + /** + * Use this function to retrieve the name of the field (the content of the 'var' attribute). + * @return The name of the field. + */ + const std::string& name() const { return m_name; } + + /** + * Sets the name (the content of the 'var' attribute) of the field. The name identifies the + * field uniquely in the form. + * @param name The new name of the field. + * @note Fields of type other than 'fixed' MUST have a name, if it is 'fixed', it MAY. + */ + void setName( const std::string& name ) { m_name = name; } + + /** + * Use this function to set the optional values of the field. The key of the map + * will be used as the label of the option, while the value will be used as ... the + * value. ;) + * @param options The optional values of a list* or *multi type of field. + */ + void setOptions( const StringMultiMap& options ) { m_options = options; } + + /** + * Adds a single option to the list of options. + * @param label The label of the option. + * @param value The value of the option. + * @since 0.9.4 + */ + void addOption( const std::string& label, const std::string& value ) + { m_options.insert( std::make_pair( label, value ) ); } + + /** + * Use this function to determine whether or not this field is required. + * @return Whether or not this field is required. + */ + bool required() const { return m_required; } + + /** + * Use this field to set this field to be required. + * @param required Whether or not this field is required. + */ + void setRequired( bool required ) { m_required = required; } + + /** + * Use this function to retrieve the describing label of this field. + * @return The describing label of this field. + */ + const std::string& label() const { return m_label; } + + /** + * Use this function to set the describing label of this field. + * @param label The describing label of this field. + */ + void setLabel( const std::string& label ) { m_label = label; } + + /** + * Use this function to retrieve the description of this field. + * @return The description of this field + */ + const std::string& description() const { return m_desc; } + + /** + * Use this function to set the description of this field. + * @param desc The description of this field. + */ + void setDescription( const std::string& desc ) { m_desc = desc; } + + /** + * Use this function to retrieve the value of this field. + * @return The value of this field. + */ + const std::string& value() const { return ( m_values.size() > 0 ) ? m_values.front() : EmptyString; } + + /** + * Use this function to set the value of this field. + * @param value The new value of this field. + */ + void setValue( const std::string& value ) { m_values.clear(); addValue( value ); } + + /** + * Use this function to retrieve the values of this field, if its of type 'text-multi'. + * @return The value of this field. + */ + const StringList& values() const { return m_values; } + + /** + * Use this function to set multiple values of this field, if it is of type 'text-multi'. If its not, + * use @ref setValue() instead. + * @param values The new values of this field. + */ + void setValues( const StringList& values ) { m_values = values; } + + /** + * Adds a single value to the list of values. + * @param value The value to add. + */ + void addValue( const std::string& value ) { m_values.push_back( value ); } + + /** + * Use this function to retrieve the type of this field. + * @return The type of this field. + */ + FieldType type() const { return m_type; } + + /** + * Converts to @b true if the FormBase is valid, @b false otherwise. + */ + operator bool() const { return m_type != TypeInvalid; } + + private: + FieldType m_type; + + StringMultiMap m_options; + StringList m_values; + + std::string m_name; + std::string m_desc; + std::string m_label; + + bool m_required; + }; + +} + +#endif // DATAFORMFIELD_H__ diff --git a/libs/libgloox/dataformfieldcontainer.cpp b/libs/libgloox/dataformfieldcontainer.cpp new file mode 100644 index 0000000..dc639bc --- /dev/null +++ b/libs/libgloox/dataformfieldcontainer.cpp @@ -0,0 +1,47 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "dataformfieldcontainer.h" +#include "util.h" + + +namespace gloox +{ + + DataFormFieldContainer::DataFormFieldContainer() + { + } + + DataFormFieldContainer::DataFormFieldContainer( const DataFormFieldContainer& dffc ) + { + FieldList::const_iterator it = dffc.m_fields.begin(); + for( ; it != dffc.m_fields.end(); ++it ) + { + m_fields.push_back( new DataFormField( *(*it) ) ); + } + } + + DataFormFieldContainer::~DataFormFieldContainer() + { + util::clearList( m_fields ); + } + + DataFormField* DataFormFieldContainer::field( const std::string& field ) const + { + FieldList::const_iterator it = m_fields.begin(); + for( ; it != m_fields.end() && (*it)->name() != field; ++it ) + ; + return it != m_fields.end() ? (*it) : 0; + } + +} diff --git a/libs/libgloox/dataformfieldcontainer.h b/libs/libgloox/dataformfieldcontainer.h new file mode 100644 index 0000000..fbd2378 --- /dev/null +++ b/libs/libgloox/dataformfieldcontainer.h @@ -0,0 +1,125 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef DATAFORMFIELDCONTAINER_H__ +#define DATAFORMFIELDCONTAINER_H__ + +#include "dataformfield.h" + +#include +#include + +namespace gloox +{ + + class DataFormField; + + /** + * @brief An abstract base class for a XEP-0004 Data Form. + * + * You shouldn't need to use this class directly. Use DataForm instead. + * + * @author Jakob Schroeter + * @since 0.7 + */ + class GLOOX_API DataFormFieldContainer + { + public: + /** + * Creates a new FieldContainer. + */ + DataFormFieldContainer(); + + /** + * Creates a new FieldContainer, copying all fields from the given FieldContainer. + * @param dffc The FieldContainer to copy. + */ + DataFormFieldContainer( const DataFormFieldContainer& dffc ); + + /** + * Virtual destructor. + */ + virtual ~DataFormFieldContainer(); + + /** + * A list of XEP-0004 Data Form Fields. + */ + typedef std::list FieldList; + + /** + * Use this function to check whether this form contains a field with the given name. + * @param field The name of the field (the content of the 'var' attribute). + * @return Whether or not the form contains the named field. + */ + bool hasField( const std::string& field ) const + { return DataFormFieldContainer::field( field ) != 0; } + + /** + * Use this function to fetch a pointer to a field of the form. If no such field exists, + * 0 is returned. + * @param field The name of the field (the content of the 'var' attribute). + * @return A copy of the field with the given name if it exists, 0 otherwise. + */ + DataFormField* field( const std::string& field ) const; + + /** + * Use this function to retrieve the list of fields of a form. + * @return The list of fields the form contains. + */ + FieldList& fields() { return m_fields; } + + /** + * Use this function to retrieve the const list of fields of a form. + * @return The const list of fields the form contains. + */ + const FieldList& fields() const { return m_fields; } + + /** + * Use this function to set the fields the form contains. + * @param fields The list of fields. + * @note Any previously set fields will be deleted. Always set all fields, not a delta. + */ + virtual void setFields( FieldList& fields ) { m_fields = fields; } + + /** + * Use this function to add a single field to the list of existing fields. + * @param field The field to add. + * @since 0.9 + */ + virtual void addField( DataFormField* field ) { m_fields.push_back( field ); } + + /** + * Adds a single new Field and returns a pointer to that field. + * @param type The field's type. + * @param name The field's name (the value of the 'var' attribute). + * @param value The field's value. + * @param label The field's label. + * @since 0.9.4 + */ + DataFormField* addField( DataFormField::FieldType type, const std::string& name, + const std::string& value = EmptyString, + const std::string& label = EmptyString ) + { + DataFormField* field = new DataFormField( name, value, label, type ); + m_fields.push_back( field ); + return field; + } + + protected: + FieldList m_fields; + + }; + +} + +#endif // DATAFORMFIELDCONTAINER_H__ diff --git a/libs/libgloox/dataformitem.cpp b/libs/libgloox/dataformitem.cpp new file mode 100644 index 0000000..af8f6ca --- /dev/null +++ b/libs/libgloox/dataformitem.cpp @@ -0,0 +1,54 @@ + /* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "dataformitem.h" + +#include "tag.h" + +namespace gloox +{ + + DataFormItem::DataFormItem() + { + } + + DataFormItem::DataFormItem( const Tag* tag ) + { + if( tag->name() != "item" ) + return; + + const TagList &l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + DataFormField* f = new DataFormField( (*it) ); + m_fields.push_back( f ); + } + } + + DataFormItem::~DataFormItem() + { + } + + Tag* DataFormItem::tag() const + { + Tag* i = new Tag ( "item" ); + DataFormFieldContainer::FieldList::const_iterator it = m_fields.begin(); + for( ; it != m_fields.end(); ++it ) + { + i->addChild( (*it)->tag() ); + } + return i; + } + +} diff --git a/libs/libgloox/dataformitem.h b/libs/libgloox/dataformitem.h new file mode 100644 index 0000000..2f95dde --- /dev/null +++ b/libs/libgloox/dataformitem.h @@ -0,0 +1,62 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef DATAFORMITEM_H__ +#define DATAFORMITEM_H__ + +#include "dataformfieldcontainer.h" + +namespace gloox +{ + + /** + * @brief An abstraction of an <item> element in a XEP-0004 Data Form of type result. + * + * There are some constraints regarding usage of this element you should be aware of. Check XEP-0004 + * section 3.4. This class does not enforce correct usage at this point. + * + * @author Jakob Schroeter + * @since 0.7 + */ + class GLOOX_API DataFormItem : public DataFormFieldContainer + { + public: + /** + * Creates an empty 'item' element you can add fields to. + */ + DataFormItem(); + + /** + * Creates a 'item' element and fills it with the 'field' elements contained in the given Tag. + * The Tag's root element must be a 'item' element. Its child element should be 'field' elements. + * @param tag The tag to read the 'field' elements from. + * @since 0.8.5 + */ + DataFormItem( const Tag* tag ); + + /** + * Virtual destructor. + */ + virtual ~DataFormItem(); + + /** + * Creates and returns a Tag representation of the current object. + * @return A Tag representation of the current object. + */ + virtual Tag* tag() const; + + }; + +} + +#endif // DATAFORMITEM_H__ diff --git a/libs/libgloox/dataformreported.cpp b/libs/libgloox/dataformreported.cpp new file mode 100644 index 0000000..4a7264f --- /dev/null +++ b/libs/libgloox/dataformreported.cpp @@ -0,0 +1,54 @@ + /* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "dataformreported.h" + +#include "tag.h" + +namespace gloox +{ + + DataFormReported::DataFormReported() + { + } + + DataFormReported::DataFormReported( Tag* tag ) + { + if( tag->name() != "reported" ) + return; + + const TagList &l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + DataFormField* f = new DataFormField( (*it) ); + m_fields.push_back( f ); + } + } + + DataFormReported::~DataFormReported() + { + } + + Tag* DataFormReported::tag() const + { + Tag* r = new Tag ( "reported" ); + DataFormFieldContainer::FieldList::const_iterator it = m_fields.begin(); + for( ; it != m_fields.end(); ++it ) + { + r->addChild( (*it)->tag() ); + } + return r; + } + +} diff --git a/libs/libgloox/dataformreported.h b/libs/libgloox/dataformreported.h new file mode 100644 index 0000000..91294c6 --- /dev/null +++ b/libs/libgloox/dataformreported.h @@ -0,0 +1,64 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef DATAFORMREPORTED_H__ +#define DATAFORMREPORTED_H__ + +#include "dataformfieldcontainer.h" + +namespace gloox +{ + + class Tag; + + /** + * @brief An abstraction of a <reported> element in a XEP-0004 Data Form of type result. + * + * There are some constraints regarding usage of this element you should be aware of. Check XEP-0004 + * section 3.4. This class does not enforce correct usage at this point. + * + * @author Jakob Schroeter + * @since 0.7 + */ + class GLOOX_API DataFormReported : public DataFormFieldContainer + { + public: + /** + * Creates an empty 'reported' element you can add fields to. + */ + DataFormReported(); + + /** + * Creates a 'reported' element and fills it with the 'field' elements contained in the given Tag. + * The Tag's root element must be a 'reported' element. Its child element should be 'field' elements. + * @param tag The tag to read the 'field' elements from. + * @since 0.8.5 + */ + DataFormReported( Tag* tag ); + + /** + * Virtual destructor. + */ + virtual ~DataFormReported(); + + /** + * Creates and returns a Tag representation of the current object. + * @return A Tag representation of the current object. + */ + virtual Tag* tag() const; + + }; + +} + +#endif // DATAFORMREPORTED_H__ diff --git a/libs/libgloox/delayeddelivery.cpp b/libs/libgloox/delayeddelivery.cpp new file mode 100644 index 0000000..71038fb --- /dev/null +++ b/libs/libgloox/delayeddelivery.cpp @@ -0,0 +1,74 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "delayeddelivery.h" + +#include "tag.h" + +namespace gloox +{ + + DelayedDelivery::DelayedDelivery( const JID& from, const std::string stamp, const std::string& reason ) + : StanzaExtension( ExtDelay ), m_from( from ), m_stamp( stamp ), m_reason( reason ), m_valid( false ) + { + if( !m_stamp.empty() ) + m_valid = true; + } + + + DelayedDelivery::DelayedDelivery( const Tag* tag ) + : StanzaExtension( ExtDelay ), m_valid( false ) + { + if( !tag || !tag->hasAttribute( "stamp" ) ) + return; + if( !( tag->name() == "x" && tag->hasAttribute( XMLNS, XMLNS_X_DELAY ) ) ) + if( !( tag->name() == "delay" && tag->hasAttribute( XMLNS, XMLNS_DELAY ) ) ) + return; + + m_reason = tag->cdata(); + m_stamp = tag->findAttribute( "stamp" ); + m_from = tag->findAttribute( "from" ); + m_valid = true; + } + + DelayedDelivery::~DelayedDelivery() + { + } + + const std::string& DelayedDelivery::filterString() const + { + static const std::string filter = + "/presence/delay[@xmlns='" + XMLNS_DELAY + "']" + "|/message/delay[@xmlns='" + XMLNS_DELAY + "']" + "|/presence/x[@xmlns='" + XMLNS_X_DELAY + "']" + "|/message/x[@xmlns='" + XMLNS_X_DELAY + "']"; + return filter; + } + + Tag* DelayedDelivery::tag() const + { + if( !m_valid ) + return 0; + + Tag* t = new Tag( "delay" ); + t->addAttribute( XMLNS, XMLNS_DELAY ); + if( m_from ) + t->addAttribute( "from", m_from.full() ); + if( !m_stamp.empty() ) + t->addAttribute( "stamp", m_stamp ); + if( !m_reason.empty() ) + t->setCData( m_reason ); + return t; + } + +} diff --git a/libs/libgloox/delayeddelivery.h b/libs/libgloox/delayeddelivery.h new file mode 100644 index 0000000..f9953fa --- /dev/null +++ b/libs/libgloox/delayeddelivery.h @@ -0,0 +1,130 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef DELAYEDDELIVERY_H__ +#define DELAYEDDELIVERY_H__ + +#include "gloox.h" +#include "jid.h" +#include "stanzaextension.h" + +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief This is an implementation of XEP-0203 (Delayed Delivery). + * + * The class also implements the deprecated XEP-0091 (Delayed Delivery) in a read-only fashion. + * It understands both XEP formats for input, but any output will conform to XEP-0203. + * + * XEP Version: 0.1 + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API DelayedDelivery : public StanzaExtension + { + + public: + /** + * Constructs a new object and fills it according to the parameters. + * @param from The JID of the original sender or the entity that delayed the sending. + * @param stamp The datetime stamp of the original send. + * @param reason An optional natural language reason for the delay. + */ + DelayedDelivery( const JID& from, const std::string stamp, + const std::string& reason = "" ); + + /** + * Constructs a new object from the given Tag. + * @param tag The Tag to parse. + */ + DelayedDelivery( const Tag* tag = 0 ); + + /** + * Virtual Destructor. + */ + virtual ~DelayedDelivery(); + + /** + * Returns the datetime when the stanza was originally sent. + * The format MUST adhere to the dateTime format specified in XEP-0082 and MUST + * be expressed in UTC. + * @return The original datetime. + */ + const std::string& stamp() const { return m_stamp; } + + /** + * Sets the original datetime. + * @param stamp The original datetime. + */ + void setStamp( const std::string& stamp ) { m_stamp = stamp; } + + /** + * Returns the JID of the original sender of the stanza or the entity that + * delayed the sending. + * The format MUST adhere to the dateTime format specified in XEP-0082 and MUST + * be expressed in UTC. + * @return The JID. + */ + const JID& from() const { return m_from; } + + /** + * Sets the JID of the origianl sender or the entity that delayed the sending. + * @param from The JID. + */ + void setFrom( const JID& from ) { m_from = from; } + + /** + * Returns a natural language reason for the delay. + * @return A natural language reason for the delay. + */ + const std::string& reason() const { return m_reason; } + + /** + * Sets the reason for the delay. + * @param reason The reason for the delay. + */ + void setReason( const std::string& reason ) { m_reason = reason; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new DelayedDelivery( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new DelayedDelivery( *this ); + } + + private: + JID m_from; + std::string m_stamp; + std::string m_reason; + bool m_valid; + }; + +} + +#endif // DELAYEDDELIVERY_H__ diff --git a/libs/libgloox/disco.cpp b/libs/libgloox/disco.cpp new file mode 100644 index 0000000..bfd0cf5 --- /dev/null +++ b/libs/libgloox/disco.cpp @@ -0,0 +1,535 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "disco.h" +#include "discohandler.h" +#include "dataform.h" +#include "error.h" +#include "clientbase.h" +#include "disconodehandler.h" +#include "softwareversion.h" +#include "util.h" + + +namespace gloox +{ + + // ---- Disco::Identity ---- + Disco::Identity::Identity( const std::string& category, + const std::string& type, + const std::string& name ) + : m_category( category ), m_type( type ), m_name( name ) + { + } + + Disco::Identity::Identity( const Tag* tag ) + { + if( !tag || tag->name() != "identity" ) + return; + + m_category = tag->findAttribute( "category" ); + m_type = tag->findAttribute( "type" ); + m_name = tag->findAttribute( "name" ); + } + + Disco::Identity::Identity( const Identity& id ) + : m_category( id.m_category ), m_type( id.m_type ), m_name( id.m_name ) + { + } + + Disco::Identity::~Identity() + { + } + + Tag* Disco::Identity::tag() const + { + if( m_category.empty() || m_type.empty() ) + return 0; + + Tag* i = new Tag( "identity" ); + i->addAttribute( "category", m_category ); + i->addAttribute( "type", m_type ); + + if( !m_name.empty() ) + i->addAttribute( "name", m_name ); + + return i; + } + // ---- ~Disco::Identity ---- + + // ---- Disco::Info ---- + Disco::Info::Info( const std::string& node, bool defaultFeatures ) + : StanzaExtension( ExtDiscoInfo ), m_node( node ), m_form( 0 ) + { + if( defaultFeatures ) + { + m_features.push_back( XMLNS_DISCO_INFO ); + m_features.push_back( XMLNS_DISCO_ITEMS ); + } + } + + Disco::Info::Info( const Tag* tag ) + : StanzaExtension( ExtDiscoInfo ), m_form( 0 ) + { + if( !tag || tag->name() != "query" || tag->xmlns() != XMLNS_DISCO_INFO ) + return; + + m_node = tag->findAttribute( "node" ); + + const TagList& l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + const std::string& name = (*it)->name(); + if( name == "identity" ) + m_identities.push_back( new Identity( (*it) ) ); + else if( name == "feature" && (*it)->hasAttribute( "var" ) ) + m_features.push_back( (*it)->findAttribute( "var" ) ); + else if( !m_form && name == "x" && (*it)->xmlns() == XMLNS_X_DATA ) + m_form = new DataForm( (*it) ); + } + } + + Disco::Info::Info( const Info& info ) + : StanzaExtension( ExtDiscoInfo ), m_node( info.m_node ), m_features( info.m_features ), + m_identities( info.m_identities ), m_form( info.m_form ? new DataForm( *(info.m_form) ) : 0 ) + { + } + + Disco::Info::~Info() + { + delete m_form; + util::clearList( m_identities ); + } + + void Disco::Info::setForm( DataForm* form ) + { + delete m_form; + m_form = form; + } + + bool Disco::Info::hasFeature( const std::string& feature ) const + { + StringList::const_iterator it = m_features.begin(); + for( ; it != m_features.end() && (*it) != feature; ++it ) + ; + return it != m_features.end(); + } + + const std::string& Disco::Info::filterString() const + { + static const std::string filter = "/iq/query[@xmlns='" + XMLNS_DISCO_INFO + "']"; + return filter; + } + + Tag* Disco::Info::tag() const + { + Tag* t = new Tag( "query", XMLNS, XMLNS_DISCO_INFO ); + + if( !m_node.empty() ) + t->addAttribute( "node", m_node ); + + IdentityList::const_iterator it_i = m_identities.begin(); + for( ; it_i != m_identities.end(); ++it_i ) + t->addChild( (*it_i)->tag() ); + + StringList::const_iterator it_f = m_features.begin(); + for( ; it_f != m_features.end(); ++it_f ) + new Tag( t, "feature", "var", (*it_f) ); + + if( m_form ) + t->addChild( m_form->tag() ); + + return t; + } + // ---- ~Disco::Info ---- + + // ---- Disco::Item ---- + Disco::Item::Item( const Tag* tag ) + { + if( !tag || tag->name() != "item" ) + return; + + m_jid = tag->findAttribute( "jid" ); + m_node = tag->findAttribute( "node" ); + m_name = tag->findAttribute( "name" ); + } + + Tag* Disco::Item::tag() const + { + if( !m_jid ) + return 0; + + Tag* i = new Tag( "item" ); + i->addAttribute( "jid", m_jid.full() ); + + if( !m_node.empty() ) + i->addAttribute( "node", m_node ); + if( !m_name.empty() ) + i->addAttribute( "name", m_name ); + + return i; + } + // ---- ~Disco::Item ---- + + // ---- Disco::Items ---- + Disco::Items::Items( const std::string& node ) + : StanzaExtension( ExtDiscoItems ), m_node( node ) + { + } + + Disco::Items::Items( const Tag* tag ) + : StanzaExtension( ExtDiscoItems ) + { + if( !tag || tag->name() != "query" || tag->xmlns() != XMLNS_DISCO_ITEMS ) + return; + + m_node = tag->findAttribute( "node" ); + + const TagList& l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + const std::string& name = (*it)->name(); + if( name == "item" ) + m_items.push_back( new Item( (*it) ) ); + } + } + + Disco::Items::~Items() + { + util::clearList( m_items ); + } + + void Disco::Items::setItems( const ItemList& items ) + { + util::clearList( m_items ); + m_items = items; + } + + + const std::string& Disco::Items::filterString() const + { + static const std::string filter = "/iq/query[@xmlns='" + XMLNS_DISCO_ITEMS + "']"; + return filter; + } + + Tag* Disco::Items::tag() const + { + Tag* t = new Tag( "query", XMLNS, XMLNS_DISCO_ITEMS ); + + if( !m_node.empty() ) + t->addAttribute( "node", m_node ); + + ItemList::const_iterator it_i = m_items.begin(); + for( ; it_i != m_items.end(); ++it_i ) + t->addChild( (*it_i)->tag() ); + + return t; + } + // ---- ~Disco::Items ---- + + // ---- Disco ---- + Disco::Disco( ClientBase* parent ) + : m_parent( parent ), m_form( 0 ) + { + addFeature( XMLNS_VERSION ); +// addFeature( XMLNS_DISCO_INFO ); //handled by Disco::Info now +// addFeature( XMLNS_DISCO_ITEMS ); //handled by Disco::Info now + if( m_parent ) + { + m_parent->registerIqHandler( this, ExtDiscoInfo ); + m_parent->registerIqHandler( this, ExtDiscoItems ); + m_parent->registerIqHandler( this, ExtVersion ); + m_parent->registerStanzaExtension( new Disco::Info() ); + m_parent->registerStanzaExtension( new Disco::Items() ); + m_parent->registerStanzaExtension( new SoftwareVersion() ); + } + } + + Disco::~Disco() + { + util::clearList( m_identities ); + delete m_form; + + if( m_parent ) + { + m_parent->removeIqHandler( this, ExtDiscoInfo ); + m_parent->removeIqHandler( this, ExtDiscoItems ); + m_parent->removeIqHandler( this, ExtVersion ); + m_parent->removeStanzaExtension( ExtDiscoInfo ); + m_parent->removeStanzaExtension( ExtDiscoItems ); + m_parent->removeStanzaExtension( ExtVersion ); + m_parent->removeIDHandler( this ); + } + } + + void Disco::setForm( DataForm* form ) + { + delete m_form; + m_form = form; + } + + bool Disco::handleIq( const IQ& iq ) + { + switch( iq.subtype() ) + { + case IQ::Get: + { + IQ re( IQ::Result, iq.from(), iq.id() ); + re.setFrom( iq.to() ); + + const SoftwareVersion* sv = iq.findExtension( ExtVersion ); + if( sv ) + { + re.addExtension( new SoftwareVersion( m_versionName, m_versionVersion, m_versionOs ) ); + m_parent->send( re ); + return true; + } + + const Info *info = iq.findExtension( ExtDiscoInfo ); + if( info ) + { + Info *i = new Info( EmptyString, true ); + if( !info->node().empty() ) + { + i->setNode( info->node() ); + IdentityList identities; + StringList features; + DiscoNodeHandlerMap::const_iterator it = m_nodeHandlers.find( info->node() ); + if( it == m_nodeHandlers.end() ) + { + delete i; + IQ re( IQ::Error, iq.from(), iq.id() ); + re.addExtension( new Error( StanzaErrorTypeCancel, StanzaErrorItemNotFound ) ); + m_parent->send( re ); + return true; + } + else + { + DiscoNodeHandlerList::const_iterator in = (*it).second.begin(); + for( ; in != (*it).second.end(); ++in ) + { + IdentityList il = (*in)->handleDiscoNodeIdentities( iq.from(), info->node() ); + il.sort(); // needed on win32 + identities.merge( il ); + StringList fl = (*in)->handleDiscoNodeFeatures( iq.from(), info->node() ); + fl.sort(); // needed on win32 + features.merge( fl ); + } + } + i->setIdentities( identities ); + i->setFeatures( features ); + } + else + { + IdentityList il; + IdentityList::const_iterator it = m_identities.begin(); + for( ; it != m_identities.end(); ++it ) + { + il.push_back( new Identity( *(*it) ) ); + } + i->setIdentities( il ); + i->setFeatures( m_features ); + if( m_form ) + i->setForm( new DataForm( *m_form ) ); + } + + re.addExtension( i ); + m_parent->send( re ); + return true; + } + + const Items *items = iq.findExtension( ExtDiscoItems ); + if( items ) + { + Items *i = new Items( items->node() ); + if( !items->node().empty() ) + { + DiscoNodeHandlerMap::const_iterator it = m_nodeHandlers.find( items->node() ); + if( it == m_nodeHandlers.end() ) + { + delete i; + IQ re( IQ::Error, iq.from(), iq.id() ); + re.addExtension( new Error( StanzaErrorTypeCancel, StanzaErrorItemNotFound ) ); + m_parent->send( re ); + return true; + } + else + { + ItemList itemlist; + DiscoNodeHandlerList::const_iterator in = (*it).second.begin(); + for( ; in != (*it).second.end(); ++in ) + { + ItemList il = (*in)->handleDiscoNodeItems( iq.from(), iq.to(), items->node() ); + il.sort(); // needed on win32 + itemlist.merge( il ); + } + i->setItems( itemlist ); + } + } + + re.addExtension( i ); + m_parent->send( re ); + return true; + } + break; + } + + case IQ::Set: + { + bool res = false; + DiscoHandlerList::const_iterator it = m_discoHandlers.begin(); + for( ; it != m_discoHandlers.end(); ++it ) + { + if( (*it)->handleDiscoSet( iq ) ) + res = true; + } + return res; + break; + } + + default: + break; + } + return false; + } + + void Disco::handleIqID( const IQ& iq, int context ) + { + DiscoHandlerMap::iterator it = m_track.find( iq.id() ); + if( it != m_track.end() && (*it).second.dh ) + { + switch( iq.subtype() ) + { + case IQ::Result: + switch( context ) + { + case GetDiscoInfo: + { + const Info* di = iq.findExtension( ExtDiscoInfo ); + if( di ) + (*it).second.dh->handleDiscoInfo( iq.from(), *di, (*it).second.context ); + break; + } + case GetDiscoItems: + { + const Items* di = iq.findExtension( ExtDiscoItems ); + if( di ) + (*it).second.dh->handleDiscoItems( iq.from(), *di, (*it).second.context ); + break; + } + } + break; + + case IQ::Error: + { + (*it).second.dh->handleDiscoError( iq.from(), iq.error(), (*it).second.context ); + break; + } + + default: + break; + } + + m_track.erase( it ); + } + } + + void Disco::getDisco( const JID& to, const std::string& node, DiscoHandler* dh, int context, + IdType idType, const std::string& tid ) + { + const std::string& id = tid.empty() ? m_parent->getID() : tid; + + IQ iq( IQ::Get, to, id ); + if( idType == GetDiscoInfo ) + iq.addExtension( new Info( node ) ); + else + iq.addExtension( new Items( node ) ); + + DiscoHandlerContext ct; + ct.dh = dh; + ct.context = context; + m_track[id] = ct; + m_parent->send( iq, this, idType ); + } + + void Disco::setVersion( const std::string& name, const std::string& version, const std::string& os ) + { + m_versionName = name; + m_versionVersion = version; + m_versionOs = os; + } + + void Disco::setIdentity( const std::string& category, const std::string& type, + const std::string& name ) + { + util::clearList( m_identities ); + addIdentity( category, type, name ); + } + + void Disco::removeDiscoHandler( DiscoHandler* dh ) + { + m_discoHandlers.remove( dh ); + DiscoHandlerMap::iterator t; + DiscoHandlerMap::iterator it = m_track.begin(); + while( it != m_track.end() ) + { + t = it; + ++it; + if( dh == (*t).second.dh ) + { + m_track.erase( t ); + } + } + } + + void Disco::registerNodeHandler( DiscoNodeHandler* nh, const std::string& node ) + { + m_nodeHandlers[node].push_back( nh ); + } + + void Disco::removeNodeHandler( DiscoNodeHandler* nh, const std::string& node ) + { + DiscoNodeHandlerMap::iterator it = m_nodeHandlers.find( node ); + if( it != m_nodeHandlers.end() ) + { + (*it).second.remove( nh ); + if( (*it).second.empty() ) + m_nodeHandlers.erase( it ); + } + } + + void Disco::removeNodeHandlers( DiscoNodeHandler* nh ) + { + DiscoNodeHandlerMap::iterator it = m_nodeHandlers.begin(); + DiscoNodeHandlerMap::iterator it2; + while( it != m_nodeHandlers.end() ) + { + it2 = it++; + removeNodeHandler( nh, (*it2).first ); + } + } + + const StringList Disco::features( bool defaultFeatures ) const + { + StringList f = m_features; + if( defaultFeatures ) + { + f.push_back( XMLNS_DISCO_INFO ); + f.push_back( XMLNS_DISCO_ITEMS ); + } + return f; + } + +} diff --git a/libs/libgloox/disco.h b/libs/libgloox/disco.h new file mode 100644 index 0000000..fa180a6 --- /dev/null +++ b/libs/libgloox/disco.h @@ -0,0 +1,636 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef DISCO_H__ +#define DISCO_H__ + +#include "gloox.h" + +#include "iqhandler.h" +#include "jid.h" + +#include +#include +#include + +namespace gloox +{ + + class ClientBase; + class DataForm; + class DiscoHandler; + class DiscoNodeHandler; + class IQ; + + /** + * @brief This class implements XEP-0030 (Service Discovery) and XEP-0092 (Software Version). + * + * ClientBase will automatically instantiate a Disco object. It can be used to + * announce special features of your client, or its version, or... + * + * XEP version: 2.2 + * @author Jakob Schroeter + */ + class GLOOX_API Disco : public IqHandler + { + friend class ClientBase; + + public: + + class Identity; // declared below class Info + + /** + * A list of pointers to Identity objects. Used with Disco::Info. + */ + typedef std::list IdentityList; + + /** + * @brief An abstraction of a Disco Info element (from Service Discovery, XEP-0030) + * as a StanzaExtension. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Info : public StanzaExtension + { + friend class Disco; + + public: + /** + * Returns the queried node identifier, if any. + * @return The node identifier. May be empty. + */ + const std::string& node() const { return m_node; } + + /** + * Returns the entity's supported features. + * @return A list of supported features/namespaces. + */ + const StringList& features() const { return m_features; } + + /** + * Use this function to check if the entity the Info came from supports agiven feature. + * @param feature The feature to check for. + * @return @b True if the entity announces support for the feature, @b false otherwise. + */ + bool hasFeature( const std::string& feature ) const; + + /** + * Returns the entity's identities. + * @return A list of pointers to Identity objects. + */ + const IdentityList& identities() const { return m_identities; } + + /** + * Returns an optionally included data form. This is used by e.g. MUC (XEP-0045). + * @return An optional data form included in the disco#info. May be 0. + */ + const DataForm* form() const { return m_form; } + + /** + * Adds an optional DataForm, e.g. for XEP-0232. Only one form can be added + * at this point. + * @param form An optional DataForm to include in the Info reply. + * The form will be owned by and deleted on destruction of the Info object. + * @note If called more than once the previously set form will be deleted. + */ + void setForm( DataForm* form ); + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Info( tag ); + } + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new Info( *this ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + private: +#ifdef DISCO_INFO_TEST + public: +#endif + /** + * Creates a empty Info object, suitable for making disco#info requests. + * @param node The node identifier to query (optional). + * @param defaultFeatures Indicates whether or not the default features should be + * included in the Info. Should be @b false for requests, @b true for replies. + * Defaults to @b false. + */ + Info( const std::string& node = EmptyString, bool defaultFeatures = false ); + + /** + * Creates an Info object from the given Tag. + * @param tag A <query> tag in the disco#info namespace, (possibly) containing + * a disco#info reply. + */ + Info( const Tag* tag ); + + /** + * Copy constructor. + * @param info An Info object to copy. + */ + Info( const Info& info ); + + /** + * Virtual destructor. + */ + virtual ~Info(); + + /** + * Sets the current info node. + * @param node The info node. + */ + void setNode( const std::string& node ) { m_node = node; } + + /** + * This function can be used to set the entity's features. + * @param features A list of supported features/namespaces. + */ + void setFeatures( const StringList& features ) + { + StringList fl( features ); + fl.sort(); // needed on win32 + m_features.merge( fl ); + } + + /** + * This function can be used to set the entity's identities. + * @param identities A list of pointers to the entity's identities. + * @note The Identity objects pointed to will be owned by the Info object. The + * list should neither be used again nor should the Identity objects be deleted. + */ + void setIdentities( const IdentityList& identities ) { m_identities = identities; } + + std::string m_node; + StringList m_features; + IdentityList m_identities; + DataForm* m_form; + }; + + /** + * @brief An abstraction of a Disco identity (Service Discovery, XEP-0030). + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Identity + { + friend class Info; + friend class Disco; + + public: + /** + * Constructs a Disco Identity from a category, type and name. + * See http://www.xmpp.org/registrar/disco-categories.html for more info. + * @param category The identity's category. + * @param type The identity's type. + * @param name The identity's name. + */ + Identity( const std::string& category, + const std::string& type, + const std::string& name ); + + /** + * Copy Contructor. + * @param id An Identity to create a new Identity object from. + */ + Identity( const Identity& id ); + + /** + * Destructor. + */ + ~Identity(); + + /** + * Returns the identity's category. + * @return The identity's category. + */ + const std::string& category() const { return m_category; } + + /** + * Returns the identity's type. + * @return The identity's type. + */ + const std::string& type() const { return m_type; } + + /** + * Returns the identity's name. + * @return The identity's name. + */ + const std::string& name() const { return m_name; } + + /** + * Creates and returns a Tag representation of this identity. + * @return A Tag, or 0. + */ + Tag* tag() const; + + private: + /** + * Creates a Disco Identity from the given Tag. + * @param tag A Tag representation of a disco identity. + */ + Identity( const Tag* tag ); + + std::string m_category; /**< The identity's category. */ + std::string m_type; /**< The identity's type. */ + std::string m_name; /**< The identity's name. */ + + }; + + class Item; // declared below class Items + + /** + * A list of pointers to Item objects. Used with Disco::Items. + */ + typedef std::list ItemList; + + /** + * @brief An abstraction of a Disco query element (from Service Discovery, XEP-0030) + * in the disco#items namespace, implemented as a StanzaExtension. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Items : public StanzaExtension + { + friend class Disco; + + public: + // This needs to be public so one can proactively send a list of adhoc commands + // see XEP-0050 + /** + * Creates an empty Items object, suitable for making disco#items requests. + * @param node The node identifier to query (optional). + */ + Items( const std::string& node = EmptyString ); + + /** + * Virtual destructor. + */ + virtual ~Items(); + + /** + * This function can be used to set the entity's/node's items. + * @param items A list of pointers to the entity's/node's items. + * @note The Item objects pointed to will be owned by the Items object. The + * list should neither be used again nor should the Item objects be deleted. + */ + void setItems( const ItemList& items ); + + /** + * Returns the queried node identifier, if any. + * @return The node identifier. May be empty. + */ + const std::string& node() const { return m_node; } + + /** + * Returns the entity's/node's items. + * @return A list of pointers to Item objects. + */ + const ItemList& items() const { return m_items; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Items( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new Items( *this ); + } + + private: +#ifdef DISCO_ITEMS_TEST + public: +#endif + /** + * Creates an Items object from the given Tag. + * @param tag A <query> tag in the disco#items namespace, (possibly) containing + * a disco#items reply. + */ + Items( const Tag* tag ); + + std::string m_node; + ItemList m_items; + }; + + /** + * @brief An abstraction of a Disco item (Service Discovery, XEP-0030). + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Item + { + friend class Items; + + public: + /** + * Constructs a Disco Item from a JID, node and name. + * @param jid The item's JID. + * @param node The item's type. + * @param name The item's name. + */ + Item( const JID& jid, + const std::string& node, + const std::string& name ) + : m_jid( jid ), m_node( node ), m_name( name ) {} + + /** + * Destructor. + */ + ~Item() {} + + /** + * Returns the item's JID. + * @return The item's JID. + */ + const JID& jid() const { return m_jid; } + + /** + * Returns the item's node. + * @return The item's node. + */ + const std::string& node() const { return m_node; } + + /** + * Returns the item's name. + * @return The item's name. + */ + const std::string& name() const { return m_name; } + + /** + * Creates and returns a Tag representation of this item. + * @return A Tag, or 0. + */ + Tag* tag() const; + + private: + /** + * Creates a Disco Item from the given Tag. + * @param tag A Tag representation of a Disco item. + */ + Item( const Tag* tag ); + + JID m_jid; /**< The item's jid. */ + std::string m_node; /**< The item's type. */ + std::string m_name; /**< The item's name. */ + + }; + + /** + * Adds a feature to the list of supported Jabber features. + * The list will be posted as an answer to IQ queries in the + * "http://jabber.org/protocol/disco#info" namespace. + * These IQ packets will also be forwarded to the + * application's IqHandler, if it listens to the @c disco\#info namespace. + * By default, disco(very) queries are handled by the library. + * By default, all supported, not disabled features are announced. + * @param feature A feature (namespace) the host app supports. + * @note Use this function for non-queryable features. For nodes that shall + * answer to @c disco\#info queries, use registerNodeHandler(). + */ + void addFeature( const std::string& feature ) + { m_features.push_back( feature ); } + + /** + * Removes the given feature from the list of advertised client features. + * @param feature The feature to remove. + * @since 0.9 + */ + void removeFeature( const std::string& feature ) + { m_features.remove( feature ); } + + /** + * Lets you retrieve the features this Disco instance supports. + * @param defaultFeatures Include default features. Defaults to @b false. + * @return A list of supported features/namespaces. + */ + const StringList features( bool defaultFeatures = false ) const; + + /** + * Queries the given JID for general infomation according to + * XEP-0030 (Service Discovery). + * To receive the results inherit from DiscoHandler and register with the Disco object. + * @param to The destination-JID of the query. + * @param node An optional node to query. Not inserted if empty. + * @param dh The DiscoHandler to notify about results. + * @param context A context identifier. + * @param tid An optional id that is going to be used as the IQ request's id. Only + * necessary if you need to know the request's id. + */ + void getDiscoInfo( const JID& to, const std::string& node, DiscoHandler* dh, int context, + const std::string& tid = EmptyString ) + { getDisco( to, node, dh, context, GetDiscoInfo, tid ); } + + /** + * Queries the given JID for its items according to + * XEP-0030 (Service Discovery). + * To receive the results inherit from DiscoHandler and register with the Disco object. + * @param to The destination-JID of the query. + * @param node An optional node to query. Not inserted if empty. + * @param dh The DiscoHandler to notify about results. + * @param context A context identifier. + * @param tid An optional id that is going to be used as the IQ request's id. Only + * necessary if you need to know the request's id. + */ + void getDiscoItems( const JID& to, const std::string& node, DiscoHandler* dh, int context, + const std::string& tid = EmptyString ) + { getDisco( to, node, dh, context, GetDiscoItems, tid ); } + + /** + * Sets the version of the host application using this library. + * The library takes care of jabber:iq:version requests. These + * IQ packets will not be forwarded to the IqHandlers. + * @param name The name to be returned to inquireing clients. + * @param version The version to be returned to inquireing clients. + * @param os The operating system to announce. Default: don't include. + */ + void setVersion( const std::string& name, const std::string& version, + const std::string& os = EmptyString ); + + /** + * Returns the application's advertised name. + * @return The application's advertised name. + */ + const std::string& name() const { return m_versionName; } + + /** + * Returns the application's advertised version. + * @return The application's advertised version. + */ + const std::string& version() const { return m_versionVersion; } + + /** + * Returns the application's advertised operating system. + * @return The application's advertised operating system. + */ + const std::string& os() const { return m_versionOs; } + + /** + * Sets the identity of this entity. + * The library uses this information to answer disco#info requests + * with a correct identity. + * XEP-0030 requires an entity to have at least one identity. See XEP-0030 + * for more information on categories and types. + * @param category The entity category of this client. Default: client. + * @param type The type of this entity. Default: bot. + * @param name The name of the entity. Default: empty. + * @note An entity can have more than one identity. You cann add more identities + * using addIdentity(). A call to setIdentity() will clear the list of identities + * and, after that, add the new identity given by the arguments to setIdentity(). + */ + void setIdentity( const std::string& category, const std::string& type, + const std::string& name = EmptyString ); + + /** + * Adds another identity to the list of identities. + * @param category The entity category of this client. Default: client. + * @param type The type of this entity. Default: bot. + * @param name The name of the entity. Default: empty. + */ + void addIdentity( const std::string& category, const std::string& type, + const std::string& name = EmptyString ) + { m_identities.push_back( new Identity( category, type, name ) ); } + + /** + * Returns the entity's identities. + * @return The entity's identities. + */ + const IdentityList& identities() const { return m_identities; } + + /** + * Adds an optional DataForm to Disco:Info replies, e.g. for XEP-0232. + * Only one form can be added at this point. + * @param form An optional DataForm to include in the Info reply. + * The form will be owned by and deleted on destruction of the Disco object. + * @note If called more than once the previously set form will be deleted. + */ + void setForm( DataForm* form ); + + /** + * Returns the DataForm set by setForm(). Used by Capabilities. + * @return The DataForm, or 0. + */ + const DataForm* form() const { return m_form; } + + /** + * Use this function to register an @ref DiscoHandler with the Disco + * object. This is only necessary if you want to receive Disco-set requests. Else + * a one-time registration happens when calling getDiscoInfo() and getDiscoItems(), respectively. + * @param dh The DiscoHandler-derived object to register. + */ + void registerDiscoHandler( DiscoHandler* dh ) + { m_discoHandlers.push_back( dh ); } + + /** + * Unregisters the given DiscoHandler. + * @param dh The DiscoHandler to unregister. + */ + void removeDiscoHandler( DiscoHandler* dh ); + + /** + * Use this function to register a @ref DiscoNodeHandler with the Disco + * object. The DiscoNodeHandler will receive disco#items queries which are + * directed to the corresponding node registered for the handler. + * @param nh The NodeHandler-derived object to register. + * @param node The node name to associate with this handler. Use an empty string to + * register for the root node. + */ + void registerNodeHandler( DiscoNodeHandler* nh, const std::string& node ); + + /** + * Removes the node handler for the given node. + * @param nh The NodeHandler to unregister. + * @param node The node for which the handler shall be removed. Use an empty string to + * remove the root node's handler. + */ + void removeNodeHandler( DiscoNodeHandler* nh, const std::string& node ); + + /** + * Removes all registered nodes of the given node handler. + * @param nh The NodeHandler to unregister. + */ + void removeNodeHandlers( DiscoNodeHandler* nh ); + + // reimplemented from IqHandler. + virtual bool handleIq( const IQ& iq ); + + // reimplemented from IqHandler. + virtual void handleIqID( const IQ& iq, int context ); + + private: +#ifdef DISCO_TEST + public: +#endif + Disco( ClientBase* parent ); + virtual ~Disco(); + + enum IdType + { + GetDiscoInfo, + GetDiscoItems + }; + + void getDisco( const JID& to, const std::string& node, DiscoHandler* dh, + int context, IdType idType, const std::string& tid ); + + struct DiscoHandlerContext + { + DiscoHandler* dh; + int context; + }; + + ClientBase* m_parent; + + typedef std::list DiscoHandlerList; + typedef std::list DiscoNodeHandlerList; + typedef std::map DiscoNodeHandlerMap; + typedef std::map DiscoHandlerMap; + + DiscoHandlerList m_discoHandlers; + DiscoNodeHandlerMap m_nodeHandlers; + DiscoHandlerMap m_track; + IdentityList m_identities; + StringList m_features; + StringMap m_queryIDs; + DataForm* m_form; + + std::string m_versionName; + std::string m_versionVersion; + std::string m_versionOs; + + }; + +} + +#endif // DISCO_H__ diff --git a/libs/libgloox/discohandler.h b/libs/libgloox/discohandler.h new file mode 100644 index 0000000..1a0941d --- /dev/null +++ b/libs/libgloox/discohandler.h @@ -0,0 +1,83 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef DISCOHANDLER_H__ +#define DISCOHANDLER_H__ + +#include "macros.h" +#include "disco.h" + +#include + +namespace gloox +{ + + class IQ; + + /** + * @brief A virtual interface that enables objects to receive Service Discovery (XEP-0030) events. + * + * A class implementing this interface can receive the results of sent disco queries. + * + * @author Jakob Schroeter + */ + class GLOOX_API DiscoHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~DiscoHandler() {} + + /** + * Reimplement this function if you want to be notified about the result + * of an disco#info query. + * @param from The sender of the disco#info result. + * @param info The Info. + * @param context A context identifier. + * @since 1.0 + */ + virtual void handleDiscoInfo( const JID& from, const Disco::Info& info, int context ) = 0; + + /** + * Reimplement this function if you want to be notified about the result + * of a disco#items query. + * @param from The sender of the disco#items result. + * @param items The Items. + * @param context A context identifier. + * @since 1.0 + */ + virtual void handleDiscoItems( const JID& from, const Disco::Items& items, int context ) = 0; + + /** + * Reimplement this function to receive disco error notifications. + * @param from The sender of the error result. + * @param error The Error. May be 0. + * @param context A context identifier. + * @since 1.0 + */ + virtual void handleDiscoError( const JID& from, const Error* error, int context ) = 0; + + /** + * Reimplement this function to receive notifications about incoming IQ + * stanzas of type 'set' in the disco namespace. + * @param iq The full IQ. + * @return Returns @b true if the stanza was handled and answered, @b false otherwise. + */ + virtual bool handleDiscoSet( const IQ& iq ) { (void)iq; return false; } + + }; + +} + +#endif // DISCOHANDLER_H__ diff --git a/libs/libgloox/disconodehandler.h b/libs/libgloox/disconodehandler.h new file mode 100644 index 0000000..8cdde36 --- /dev/null +++ b/libs/libgloox/disconodehandler.h @@ -0,0 +1,81 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef DISCONODEHANDLER_H__ +#define DISCONODEHANDLER_H__ + +#include "gloox.h" +#include "disco.h" + +#include +#include +#include + +namespace gloox +{ + + /** + * @brief Derived classes can be registered as NodeHandlers for certain nodes with the Disco object. + * + * Incoming disco#info and disco#items queries are delegated to their respective handlers. + * + * @author Jakob Schroeter + */ + class GLOOX_API DiscoNodeHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~DiscoNodeHandler() {} + + /** + * In addition to @c handleDiscoNodeIdentities, this function is used to gather + * more information on a specific node. It is called when a disco#info query + * arrives with a node attribute that matches the one registered for this handler. + * @param from The sender of the request. + * @param node The node this handler is supposed to handle. + * @return A list of features supported by this node. + */ + virtual StringList handleDiscoNodeFeatures( const JID& from, const std::string& node ) = 0; + + /** + * In addition to @c handleDiscoNodeFeatures, this function is used to gather + * more information on a specific node. It is called when a disco#info query + * arrives with a node attribute that matches the one registered for this handler. + * @param from The sender of the request. + * @param node The node this handler is supposed to handle. + * @return A list of identities for the given node. The caller will own the identities. + */ + virtual Disco::IdentityList handleDiscoNodeIdentities( const JID& from, + const std::string& node ) = 0; + + /** + * This function is used to gather more information on a specific node. + * It is called when a disco#items query arrives with a node attribute that + * matches the one registered for this handler. If node is empty, items for the + * root node (no node) shall be returned. + * @param from The sender of the request. + * @param to The receiving JID (useful for transports). + * @param node The node this handler is supposed to handle. + * @return A list of items supported by this node. + */ + virtual Disco::ItemList handleDiscoNodeItems( const JID& from, const JID& to, + const std::string& node = EmptyString ) = 0; + + }; + +} + +#endif // DISCONODEHANDLER_H__ diff --git a/libs/libgloox/dns.cpp b/libs/libgloox/dns.cpp new file mode 100644 index 0000000..0ddc081 --- /dev/null +++ b/libs/libgloox/dns.cpp @@ -0,0 +1,488 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "config.h" + +#include "gloox.h" +#include "dns.h" +#include "util.h" + +#ifndef _WIN32_WCE +# include +#endif + +#include +#include + +#if ( !defined( _WIN32 ) && !defined( _WIN32_WCE ) ) || defined( __SYMBIAN32__ ) +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) +# include +#elif defined( _WIN32_WCE ) +# include +#endif + +#ifdef HAVE_WINDNS_H +# include +#endif + +#define SRV_COST (RRFIXEDSZ+0) +#define SRV_WEIGHT (RRFIXEDSZ+2) +#define SRV_PORT (RRFIXEDSZ+4) +#define SRV_SERVER (RRFIXEDSZ+6) +#define SRV_FIXEDSZ (RRFIXEDSZ+6) + +#ifndef T_SRV +# define T_SRV 33 +#endif + +// mingw +#ifndef DNS_TYPE_SRV +# define DNS_TYPE_SRV 33 +#endif + +#ifndef NS_CMPRSFLGS +# define NS_CMPRSFLGS 0xc0 +#endif + +#ifndef C_IN +# define C_IN 1 +#endif + +#ifndef INVALID_SOCKET +# define INVALID_SOCKET -1 +#endif + +#define XMPP_PORT 5222 + +namespace gloox +{ + +#if defined( HAVE_RES_QUERYDOMAIN ) && defined( HAVE_DN_SKIPNAME ) && defined( HAVE_RES_QUERY ) + DNS::HostMap DNS::resolve( const std::string& service, const std::string& proto, + const std::string& domain, const LogSink& logInstance ) + { + buffer srvbuf; + bool error = false; + + const std::string dname = "_" + service + "._" + proto; + + if( !domain.empty() ) + srvbuf.len = res_querydomain( dname.c_str(), const_cast( domain.c_str() ), + C_IN, T_SRV, srvbuf.buf, NS_PACKETSZ ); + else + srvbuf.len = res_query( dname.c_str(), C_IN, T_SRV, srvbuf.buf, NS_PACKETSZ ); + + if( srvbuf.len < 0 ) + return defaultHostMap( domain, logInstance ); + + HEADER* hdr = (HEADER*)srvbuf.buf; + unsigned char* here = srvbuf.buf + NS_HFIXEDSZ; + + if( ( hdr->tc ) || ( srvbuf.len < NS_HFIXEDSZ ) ) + error = true; + + if( hdr->rcode >= 1 && hdr->rcode <= 5 ) + error = true; + + if( ntohs( hdr->ancount ) == 0 ) + error = true; + + if( ntohs( hdr->ancount ) > NS_PACKETSZ ) + error = true; + + int cnt; + for( cnt = ntohs( hdr->qdcount ); cnt > 0; --cnt ) + { + int strlen = dn_skipname( here, srvbuf.buf + srvbuf.len ); + here += strlen + NS_QFIXEDSZ; + } + + unsigned char* srv[NS_PACKETSZ]; + int srvnum = 0; + for( cnt = ntohs( hdr->ancount ); cnt > 0; --cnt ) + { + int strlen = dn_skipname( here, srvbuf.buf + srvbuf.len ); + here += strlen; + srv[srvnum++] = here; + here += SRV_FIXEDSZ; + here += dn_skipname( here, srvbuf.buf + srvbuf.len ); + } + + if( error ) + { + return defaultHostMap( domain, logInstance ); + } + + // (q)sort here + + HostMap servers; + for( cnt = 0; cnt < srvnum; ++cnt ) + { + char srvname[NS_MAXDNAME]; + srvname[0] = '\0'; + + if( dn_expand( srvbuf.buf, srvbuf.buf + NS_PACKETSZ, + srv[cnt] + SRV_SERVER, srvname, NS_MAXDNAME ) < 0 + || !(*srvname) ) + continue; + + unsigned char* c = srv[cnt] + SRV_PORT; + servers.insert( std::make_pair( (char*)srvname, ntohs( c[1] << 8 | c[0] ) ) ); + } + + if( !servers.size() ) + return defaultHostMap( domain, logInstance ); + + return servers; + } + +#elif defined( _WIN32 ) && defined( HAVE_WINDNS_H ) + DNS::HostMap DNS::resolve( const std::string& service, const std::string& proto, + const std::string& domain, const LogSink& logInstance ) + { + const std::string dname = "_" + service + "._" + proto + "." + domain; + bool error = false; + + DNS::HostMap servers; + DNS_RECORD* pRecord = NULL; + DNS_STATUS status = DnsQuery_UTF8( dname.c_str(), DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &pRecord, NULL ); + if( status == ERROR_SUCCESS ) + { + DNS_RECORD* pRec = pRecord; + do + { + if( pRec->wType == DNS_TYPE_SRV ) + { + servers[pRec->Data.SRV.pNameTarget] = pRec->Data.SRV.wPort; + } + pRec = pRec->pNext; + } + while( pRec != NULL ); + DnsRecordListFree( pRecord, DnsFreeRecordList ); + } + else + { + logInstance.warn( LogAreaClassDns, "DnsQuery_UTF8() failed: " + util::int2string( status ) ); + error = true; + } + + if( error || !servers.size() ) + { + servers = defaultHostMap( domain, logInstance ); + } + + return servers; + } + +#else + DNS::HostMap DNS::resolve( const std::string& /*service*/, const std::string& /*proto*/, + const std::string& domain, const LogSink& logInstance ) + { + logInstance.warn( LogAreaClassDns, "Notice: gloox does not support SRV " + "records on this platform. Using A records instead." ); + return defaultHostMap( domain, logInstance ); + } +#endif + + DNS::HostMap DNS::defaultHostMap( const std::string& domain, const LogSink& logInstance ) + { + HostMap server; + + logInstance.warn( LogAreaClassDns, "Notice: no SRV record found for " + + domain + ", using default port." ); + + if( !domain.empty() ) + server[domain] = XMPP_PORT; + + return server; + } + +#ifdef HAVE_GETADDRINFO + void DNS::resolve( struct addrinfo** res, const std::string& service, const std::string& proto, + const std::string& domain, const LogSink& logInstance ) + { + logInstance.dbg( LogAreaClassDns, "Resolving: _" + service + "._" + proto + "." + domain ); + struct addrinfo hints; + if( proto == "tcp" ) + hints.ai_socktype = SOCK_STREAM; + else if( proto == "udp" ) + hints.ai_socktype = SOCK_DGRAM; + else + { + logInstance.err( LogAreaClassDns, "Unknown/Invalid protocol: " + proto ); + } + memset( &hints, '\0', sizeof( hints ) ); + hints.ai_flags = AI_ADDRCONFIG | AI_CANONNAME; + hints.ai_socktype = SOCK_STREAM; + int e = getaddrinfo( domain.c_str(), service.c_str(), &hints, res ); + if( e ) + logInstance.err( LogAreaClassDns, "getaddrinfo() failed" ); + } + + int DNS::connect( const std::string& host, const LogSink& logInstance ) + { + struct addrinfo* results = 0; + + resolve( &results, host, logInstance ); + if( !results ) + { + logInstance.err( LogAreaClassDns, "host not found: " + host ); + return -ConnDnsError; + } + + struct addrinfo* runp = results; + while( runp ) + { + int fd = DNS::connect( runp, logInstance ); + if( fd >= 0 ) + return fd; + + runp = runp->ai_next; + } + + freeaddrinfo( results ); + + return -ConnConnectionRefused; + } + + int DNS::connect( struct addrinfo* res, const LogSink& logInstance ) + { + if( !res ) + return -1; + + int fd = getSocket( res->ai_family, res->ai_socktype, res->ai_protocol, logInstance ); + if( fd < 0 ) + return fd; + + if( ::connect( fd, res->ai_addr, res->ai_addrlen ) == 0 ) + { + char ip[NI_MAXHOST]; + char port[NI_MAXSERV]; + + if( getnameinfo( res->ai_addr, sizeof( sockaddr ), + ip, sizeof( ip ), + port, sizeof( port ), + NI_NUMERICHOST | NI_NUMERICSERV ) ) + { + //FIXME do we need to handle this? How? Can it actually happen at all? +// printf( "could not get numeric hostname"); + } + + if( res->ai_canonname ) + logInstance.dbg( LogAreaClassDns, "Connecting to " + std::string( res->ai_canonname ) + + " (" + ip + "), port " + port ); + else + logInstance.dbg( LogAreaClassDns, "Connecting to " + ip + ":" + port ); + + return fd; + } + + std::string message = "connect() failed. " +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + "WSAGetLastError: " + util::int2string( ::WSAGetLastError() ); +#else + "errno: " + util::int2string( errno ); +#endif + logInstance.dbg( LogAreaClassDns, message ); + + closeSocket( fd, logInstance ); + return -ConnConnectionRefused; + } + +#else + + int DNS::connect( const std::string& host, const LogSink& logInstance ) + { + HostMap hosts = resolve( host, logInstance ); + if( hosts.size() == 0 ) + return -ConnDnsError; + + HostMap::const_iterator it = hosts.begin(); + for( ; it != hosts.end(); ++it ) + { + int fd = DNS::connect( (*it).first, (*it).second, logInstance ); + if( fd >= 0 ) + return fd; + } + + return -ConnConnectionRefused; + } +#endif + + int DNS::getSocket( const LogSink& logInstance ) + { +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + WSADATA wsaData; + if( WSAStartup( MAKEWORD( 1, 1 ), &wsaData ) != 0 ) + { + logInstance.dbg( LogAreaClassDns, "WSAStartup() failed. WSAGetLastError: " + + util::int2string( ::WSAGetLastError() ) ); + return -ConnDnsError; + } +#endif + + int protocol = IPPROTO_TCP; + struct protoent* prot; + if( ( prot = getprotobyname( "tcp" ) ) != 0 ) + { + protocol = prot->p_proto; + } + else + { + std::string message = "getprotobyname( \"tcp\" ) failed. " +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + "WSAGetLastError: " + util::int2string( ::WSAGetLastError() ) +#else + "errno: " + util::int2string( errno ); +#endif + + ". Falling back to IPPROTO_TCP: " + util::int2string( IPPROTO_TCP ); + logInstance.dbg( LogAreaClassDns, message ); + + // Do not return an error. We'll fall back to IPPROTO_TCP. + } + + return getSocket( PF_INET, SOCK_STREAM, protocol, logInstance ); + } + + int DNS::getSocket( int af, int socktype, int proto, const LogSink& logInstance ) + { +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + SOCKET fd; +#else + int fd; +#endif + if( ( fd = socket( af, socktype, proto ) ) == INVALID_SOCKET ) + { + std::string message = "getSocket( " + + util::int2string( af ) + ", " + + util::int2string( socktype ) + ", " + + util::int2string( proto ) + + " ) failed. " +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + "WSAGetLastError: " + util::int2string( ::WSAGetLastError() ); +#else + "errno: " + util::int2string( errno ); +#endif + logInstance.dbg( LogAreaClassDns, message ); + + cleanup( logInstance ); + return -ConnConnectionRefused; + } + +#ifdef HAVE_SETSOCKOPT + int timeout = 5000; + setsockopt( fd, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof( timeout ) ); + setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, (char*)&timeout, sizeof( timeout ) ); +#endif + + return (int)fd; + } + + int DNS::connect( const std::string& host, int port, const LogSink& logInstance ) + { + int fd = getSocket( logInstance ); + if( fd < 0 ) + return fd; + + struct hostent* h; + if( ( h = gethostbyname( host.c_str() ) ) == 0 ) + { + logInstance.dbg( LogAreaClassDns, "gethostbyname() failed for " + host + "." ); + cleanup( logInstance ); + return -ConnDnsError; + } + + struct sockaddr_in target; + target.sin_family = AF_INET; + target.sin_port = htons( static_cast( port ) ); + + if( h->h_length != sizeof( struct in_addr ) ) + { + logInstance.dbg( LogAreaClassDns, "gethostbyname() returned unexpected structure." ); + cleanup( logInstance ); + return -ConnDnsError; + } + else + { + memcpy( &target.sin_addr, h->h_addr, sizeof( struct in_addr ) ); + } + + logInstance.dbg( LogAreaClassDns, "Connecting to " + host + + " (" + inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ")" ); + + memset( target.sin_zero, '\0', 8 ); + if( ::connect( fd, (struct sockaddr *)&target, sizeof( struct sockaddr ) ) == 0 ) + { + logInstance.dbg( LogAreaClassDns, "Connected to " + host + " (" + + inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ")" ); + return fd; + } + + std::string message = "Connection to " + host + " (" + + inet_ntoa( target.sin_addr ) + ":" + util::int2string( port ) + ") failed. " +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + "WSAGetLastError: " + util::int2string( ::WSAGetLastError() ); +#else + "errno: " + util::int2string( errno ); +#endif + logInstance.dbg( LogAreaClassDns, message ); + + closeSocket( fd, logInstance ); + return -ConnConnectionRefused; + } + + void DNS::closeSocket( int fd, const LogSink& logInstance ) + { +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + int result = closesocket( fd ); +#else + int result = close( fd ); +#endif + + if( result != 0 ) + { + std::string message = "closeSocket() failed. " +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + "WSAGetLastError: " + util::int2string( ::WSAGetLastError() ); +#else + "errno: " + util::int2string( errno ); +#endif + logInstance.dbg( LogAreaClassDns, message ); + } + } + + void DNS::cleanup( const LogSink& logInstance ) + { +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + if( WSACleanup() != 0 ) + { + logInstance.dbg( LogAreaClassDns, "WSACleanup() failed. WSAGetLastError: " + + util::int2string( ::WSAGetLastError() ) ); + } +#else + (void)logInstance; +#endif + } + +} diff --git a/libs/libgloox/dns.h b/libs/libgloox/dns.h new file mode 100644 index 0000000..5be2a6f --- /dev/null +++ b/libs/libgloox/dns.h @@ -0,0 +1,179 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef DNS_H__ +#define DNS_H__ + +#include "macros.h" +#include "logsink.h" + +#ifdef __MINGW32__ +# include +# include +#endif + +#ifdef HAVE_ARPA_NAMESER_H +# include +#endif + +#ifdef __APPLE__ +# include +#endif + +#ifndef NS_MAXDNAME +# define NS_MAXDNAME 1025 +#endif + +#ifndef NS_PACKETSZ +# define NS_PACKETSZ 512 +#endif + +#ifdef HAVE_GETADDRINFO +# include +# include +# include +#endif + +#include +#include + +namespace gloox +{ + + /** + * @brief This class holds a number of static functions used for DNS related stuff. + * + * You should not need to use these functions directly. + * + * @author Jakob Schroeter + * @since 0.3 + */ + class GLOOX_API DNS + { + public: + + /** + * A list of strings (used for server addresses) and ints (used for port numbers). + */ + typedef std::map HostMap; + + /** + * This function resolves a service/protocol/domain tuple. + * @param service The SRV service type. + * @param proto The SRV protocol. + * @param domain The domain to search for SRV records. + * @param logInstance A LogSink to use for logging. + * @return A list of weighted hostname/port pairs from SRV records, or A records if no SRV + * records where found. + */ + static HostMap resolve( const std::string& service, const std::string& proto, + const std::string& domain, const LogSink& logInstance ); + + /** + * This is a convenience funtion which uses @ref resolve() to resolve SRV records + * for a given domain, using a service of @b xmpp-client and a proto of @b tcp. + * @param domain The domain to resolve SRV records for. + * @param logInstance A LogSink to use for logging. + * @return A list of weighted hostname/port pairs from SRV records, or A records if no SRV + * records where found. + */ + static HostMap resolve( const std::string& domain, const LogSink& logInstance ) + { return resolve( "xmpp-client", "tcp", domain, logInstance ); } + + /** + * This is a convenience function which uses @ref resolve() to get a list of hosts + * and connects to one of them. + * @param host The host to resolve SRV records for. + * @param logInstance A LogSink to use for logging. + * @return A file descriptor for the established connection. + */ + static int connect( const std::string& host, const LogSink& logInstance ); + + /** + * This is a convenience function which connects to the given host and port. No SRV + * records are resolved. Use this function for special setups. + * @param host The host/IP address to connect to. + * @param port A custom port to connect to. + * @param logInstance A LogSink to use for logging. + * @return A file descriptor for the established connection. + */ + static int connect( const std::string& host, int port, const LogSink& logInstance ); + + /** + * A convenience function that prepares and returnes a simple, unconnected TCP socket. + * @param logInstance A LogSink to use for logging. + * @return A TCP socket. + */ + static int getSocket( const LogSink& logInstance ); + + /** + * Closes the given socket. + * @param fd The socket to close. + * @param logInstance A LogSink to use for logging. + */ + static void closeSocket( int fd, const LogSink& logInstance ); + + private: +#ifdef HAVE_GETADDRINFO + /** + * Resolves the given service for the given domain and protocol, using the IPv6-ready + * getaddrinfo(). The result is put into the first parameter. + * @param res A pointer to a pointer holding the query results. + * @param service A service string to query for, e.g. xmpp-client. + * @param proto A protocol name. + * @param domain The domain to query for. + * @param logInstance A LogSink to use for logging. + */ + static void resolve( struct addrinfo** res, const std::string& service, const std::string& proto, + const std::string& domain, const LogSink& logInstance ); + + /** + * This is a convenience funtion which uses @ref resolve() to resolve SRV records + * for a given domain, using a service of @b xmpp-client and a proto of @b tcp. + * @param res A pointer to a pointer holding the query results. + * @param domain The domain to resolve SRV records for. + * @param logInstance A LogSink to use for logging. + */ + static void resolve( struct addrinfo** res, const std::string& domain, const LogSink& logInstance ) + { resolve( res, "xmpp-client", "tcp", domain, logInstance ); } + + /** + * Tries to connect to the host/address contained in the addrinfo structure. + * @param res The connection parameters. + * @param logInstance A LogSink to use for logging. + * @return A file descriptor for the established connection. + */ + static int connect( struct addrinfo* res, const LogSink& logInstance ); +#endif + + /** + * This function prepares and returns a socket with the given parameters. + * @param af The address family. E.g. PF_INET. + * @param socktype The socket type. E.g. SOCK_STREAM. + * @param proto The protocol number. E.g. 6 (TCP). + */ + static int getSocket( int af, int socktype, int proto, const LogSink& logInstance ); + + static HostMap defaultHostMap( const std::string& domain, const LogSink& logInstance ); + static void cleanup( const LogSink& logInstance ); + + struct buffer + { + unsigned char buf[NS_PACKETSZ]; + int len; + }; + }; + +} + +#endif // DNS_H__ diff --git a/libs/libgloox/error.cpp b/libs/libgloox/error.cpp new file mode 100644 index 0000000..d0c23cf --- /dev/null +++ b/libs/libgloox/error.cpp @@ -0,0 +1,138 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "error.h" +#include "tag.h" +#include "util.h" + +namespace gloox +{ + + /* Error type values */ + static const char* errValues [] = { + "auth", + "cancel", + "continue", + "modify", + "wait" + }; + + /* Stanza error values */ + static const char* stanzaErrValues [] = { + "bad-request", + "conflict", + "feature-not-implemented", + "forbidden", + "gone", + "internal-server-error", + "item-not-found", + "jid-malformed", + "not-acceptable", + "not-allowed", + "not-authorized", + "not-modified", + "payment-required", + "recipient-unavailable", + "redirect", + "registration-required", + "remote-server-not-found", + "remote-server-timeout", + "resource-constraint", + "service-unavailable", + "subscription-required", + "undefined-condition", + "unexpected-request", + "unknown-sender" + }; + + static inline StanzaErrorType stanzaErrorType( const std::string& type ) + { + return (StanzaErrorType)util::lookup( type, errValues ); + } + + static inline StanzaError stanzaError( const std::string& type ) + { + return (StanzaError)util::lookup( type, stanzaErrValues ); + } + + Error::Error( const Tag* tag ) + : StanzaExtension( ExtError ), + m_error( StanzaErrorUndefined ), m_appError( 0 ) + { + if( !tag || tag->name() != "error" ) + return; + + m_type = stanzaErrorType( tag->findAttribute( TYPE ) ); + + TagList::const_iterator it = tag->children().begin(); + for( ; it != tag->children().end(); ++it ) + { + StanzaError srt = gloox::stanzaError( (*it)->name() ); + if( srt != StanzaErrorUndefined ) + m_error = srt; + else if( (*it)->name() == "text" ) + m_text[(*it)->findAttribute("xml:lang")] = (*it)->cdata(); + else + m_appError = (*it)->clone(); + } + } + + Error::Error( const Error& error ) + : StanzaExtension( ExtError ), m_type( error.m_type ), + m_error( error.m_error ), m_appError( error.m_appError ? m_appError->clone() : 0 ) + {} + + Error::~Error() + { + delete m_appError; + } + + const std::string& Error::filterString() const + { + static const std::string filter = "/iq/error" + "|/message/error" + "|/presence/error" + "|/subscription/error"; + return filter; + } + + + Tag* Error::tag() const + { + if( m_type == StanzaErrorTypeUndefined || m_error == StanzaErrorUndefined ) + return 0; + + Tag* error = new Tag( "error", TYPE, util::lookup( m_type, errValues ) ); + new Tag( error, util::lookup( m_error, stanzaErrValues ), XMLNS, XMLNS_XMPP_STANZAS ); + + StringMap::const_iterator it = m_text.begin(); + for( ; it != m_text.end(); ++it ) + { + Tag* txt = new Tag( error, "text" ); + txt->setXmlns( XMLNS_XMPP_STANZAS ); + txt->addAttribute( "xml:lang", (*it).first ); + txt->setCData( (*it).second ); + } + + if( m_appError ) + error->addChild( m_appError->clone() ); + + return error; + } + + const std::string& Error::text( const std::string& lang ) const + { + StringMap::const_iterator it = m_text.find( lang ); + return it != m_text.end() ? (*it).second : EmptyString; + } + +} diff --git a/libs/libgloox/error.h b/libs/libgloox/error.h new file mode 100644 index 0000000..24eccce --- /dev/null +++ b/libs/libgloox/error.h @@ -0,0 +1,139 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef ERROR_H__ +#define ERROR_H__ + +#include "gloox.h" +#include "stanzaextension.h" + +#include +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief A stanza error abstraction implemented as a StanzaExtension. + * + * @author Vincent Thomasset + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Error : public StanzaExtension + { + public: + +// Error() +// : StanzaExtension( ExtError ), m_type( StanzaErrorTypeUndefined ), +// m_error( StanzaErrorUndefined ), m_appError( 0 ) +// {} + + /** + * Creates a new Error object from the given Tag. + * @param tag The Tag to parse. + */ + Error( const Tag* tag = 0 ); + + /** + * Creates a new Error object. + * @param type The error type. + * @param error The actual stanza error. + * @param appError An optional application-specific error. + */ + Error( StanzaErrorType type, StanzaError error, Tag* appError = 0 ) + : StanzaExtension( ExtError ), m_type( type ), + m_error( error ), m_appError( appError ) + {} + + /** + * Virtual destructor. + */ + virtual ~Error(); + + /** + * Returns the error type. + * @return The error type. + */ + StanzaErrorType type() const { return m_type; } + + /** + * Return the stanza error. + * @return The actual error. + */ + StanzaError error() const { return m_error; } + + /** + * This function can be used to retrieve the application-specific error + * condition of a stanza error. + * @return The application-specific error element of a stanza error. + * 0 if no respective element was found or no error occured. + */ + const Tag* appError() const { return m_appError; } + + /** + * Returns the text of a error stanza for the given language if available. + * If the requested language is not available, the default text (without + * a xml:lang attribute) will be returned. + * @param lang The language identifier for the desired language. It must + * conform to section 2.12 of the XML specification and RFC 3066. If + * empty, the default text will be returned, if any. + * @return The text of an error stanza. + */ + const std::string& text( const std::string& lang = EmptyString ) const; + + /** + * Sets the text of a error stanza for the given language. + * @param text The error text to set. + * @param lang The language identifier for the desired language. It must + * conform to section 2.12 of the XML specification and RFC 3066. If + * empty, the default text will be set. + */ + void setText( const std::string& text, const std::string& lang = EmptyString ) + { + m_text[lang] = text; + } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Error( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new Error( *this ); + } + + private: + Error( const Error& error ); + + void setValues( const Tag* tag ); + + StanzaErrorType m_type; + StanzaError m_error; + Tag* m_appError; + StringMap m_text; + }; + +} + +#endif /* ERROR_H__ */ diff --git a/libs/libgloox/event.h b/libs/libgloox/event.h new file mode 100644 index 0000000..3a2e3f2 --- /dev/null +++ b/libs/libgloox/event.h @@ -0,0 +1,81 @@ +/* + Copyright (c) 2008-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef EVENT_H__ +#define EVENT_H__ + +namespace gloox +{ + + class Stanza; + + /** + * @brief A base class for events. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class Event + { + + public: + /** + * Event types. + */ + enum EventType + { + PingPing, /**< Incoming Ping (XEP-0199). */ + PingPong, /**< Incoming Pong (XEP-0199). */ + PingError /**< Incoming Error Pong (XEP-0199). */ + }; + + /** + * Creates a new Event of the given type. + * @param type The Event type. + */ + Event( EventType type ) : m_eventType( type ), m_stanza( 0 ) {} + + /** + * Creates a new Event of the given type, referencing the given Stanza. + * @param type The Event type. + * @param stanza A Stanza to point at. No copy of the Stanza is taken, just its address. + */ + Event( EventType type, const Stanza& stanza ) : m_eventType( type ), m_stanza( &stanza ) {} + + /** + * Virtual Destructor. + */ + virtual ~Event() {} + + /** + * Returns the Event's type. + * @return The Event's type. + */ + EventType eventType() const { return m_eventType; } + + /** + * Returns a pointer to a Stanza-derived object. + * @return A pointer to a Stanza that caused the event. May be 0. + * @note You should @b not delete the Stanza object. + */ + const Stanza* stanza() const { return m_stanza; } + + protected: + EventType m_eventType; + const Stanza* m_stanza; + + }; + +} + +#endif // EVENT_H__ diff --git a/libs/libgloox/eventdispatcher.cpp b/libs/libgloox/eventdispatcher.cpp new file mode 100644 index 0000000..7db22a9 --- /dev/null +++ b/libs/libgloox/eventdispatcher.cpp @@ -0,0 +1,73 @@ +/* + Copyright (c) 2008-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "eventdispatcher.h" +#include "eventhandler.h" + +namespace gloox +{ + + EventDispatcher::EventDispatcher() + { + } + + EventDispatcher::~EventDispatcher() + { + } + + void EventDispatcher::dispatch( const Event& event, const std::string& context, bool remove ) + { + typedef ContextHandlerMap::iterator Ei; + std::pair g = m_contextHandlers.equal_range( context ); + Ei it = g.first; + Ei it2; + while( it != g.second ) + { + it2 = it++; + (*it2).second->handleEvent( event ); + if( remove ) + m_contextHandlers.erase( it2 ); + } + } + + void EventDispatcher::dispatch( const Event& event ) + { + TypeHandlerMap::iterator it = m_typeHandlers.begin(); + for( ; it != m_typeHandlers.end(); ++it ) + { + if( (*it).first == event.eventType() ) + (*it).second->handleEvent( event ); + } + } + + void EventDispatcher::registerEventHandler( EventHandler* eh, const std::string& context ) + { + if( !eh || context.empty() ) + return; + + m_contextHandlers.insert( std::make_pair( context, eh ) ); + } + + void EventDispatcher::removeEventHandler( EventHandler* eh ) + { + ContextHandlerMap::iterator it = m_contextHandlers.begin(); + ContextHandlerMap::iterator it2; + while( it != m_contextHandlers.end() ) + { + it2 = it++; + if( (*it2).second == eh ) + m_contextHandlers.erase( it2 ); + } + } + +} diff --git a/libs/libgloox/eventdispatcher.h b/libs/libgloox/eventdispatcher.h new file mode 100644 index 0000000..5c530fc --- /dev/null +++ b/libs/libgloox/eventdispatcher.h @@ -0,0 +1,88 @@ +/* + Copyright (c) 2008-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef EVENTDISPATCHER_H__ +#define EVENTDISPATCHER_H__ + +#include "event.h" + +#include +#include + +namespace gloox +{ + + class EventHandler; + + /** + * @brief An Event dispatcher. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class EventDispatcher + { + + public: + /** + * Creates a new EventDispatcher object. You should not need to use this class directly. + */ + EventDispatcher(); + + /** + * Virtual Destructor. + */ + virtual ~EventDispatcher(); + + /** + * Looks for handlers for the given Event, and removes the handlers if requested. + * @param event The Event to dispatch. + * @param context An identifier that limits the EventHandlers that will get notified to + * those that are specifically interested in this context. + * @param remove Whether or not to remove the context from the list of known contexts. Useful for + * IQ IDs. + */ + void dispatch( const Event& event, const std::string& context, bool remove ); + + /** + * Looks for handlers for the given Event, identified by its type. + * @param event The event to dispatch. + */ + void dispatch( const Event& event ); + + /** + * Registers the given EventHandler to be notified about Events with the given context. + * The context will usually be an IQ ID. + * @param eh The EventHandler to register. + * @param context The context to register the EventHandler for. + */ + void registerEventHandler( EventHandler* eh, const std::string& context ); + + /** + * Removes the given EventHandler. + * @param eh The EventHandler to remove. + */ + void removeEventHandler( EventHandler* eh ); + + private: + typedef std::multimap ContextHandlerMap; + typedef std::multimap TypeHandlerMap; + + ContextHandlerMap m_contextHandlers; + TypeHandlerMap m_typeHandlers; + + }; + +} + +#endif // EVENTDISPATCHER_H__ diff --git a/libs/libgloox/eventhandler.h b/libs/libgloox/eventhandler.h new file mode 100644 index 0000000..d841cd0 --- /dev/null +++ b/libs/libgloox/eventhandler.h @@ -0,0 +1,47 @@ +/* + Copyright (c) 2008-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef EVENTHANDLER_H__ +#define EVENTHANDLER_H__ + +namespace gloox +{ + + class Event; + + /** + * @brief An base class for event handlers. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class EventHandler + { + + public: + /** + * Virtual Destructor. + */ + virtual ~EventHandler() {} + + /** + * This function gets called for Events this handler was registered for. + * @param event The Event. + */ + virtual void handleEvent( const Event& event ) = 0; + + }; + +} + +#endif // EVENTHANDLER_H__ diff --git a/libs/libgloox/featureneg.cpp b/libs/libgloox/featureneg.cpp new file mode 100644 index 0000000..628dd97 --- /dev/null +++ b/libs/libgloox/featureneg.cpp @@ -0,0 +1,60 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "featureneg.h" +#include "dataform.h" +#include "tag.h" + +namespace gloox +{ + + FeatureNeg::FeatureNeg( DataForm* form ) + : StanzaExtension( ExtFeatureNeg ), m_form( form ) + { + } + + FeatureNeg::FeatureNeg( const Tag* tag ) + : StanzaExtension( ExtFeatureNeg ), m_form( 0 ) + { + if( !tag || tag->name() != "feature" || tag->xmlns() != XMLNS_FEATURE_NEG ) + return; + + const Tag* f = tag->findTag( "feature/x[@xmlns='" + XMLNS_X_DATA + "']" ); + if( f ) + m_form = new DataForm( f ); + } + + FeatureNeg::~FeatureNeg() + { + delete m_form; + } + + const std::string& FeatureNeg::filterString() const + { + static const std::string filter = "/message/feature[@xmlns='" + XMLNS_FEATURE_NEG + "']" + "|/iq/feature[@xmlns='" + XMLNS_FEATURE_NEG + "']" ; + return filter; + } + + Tag* FeatureNeg::tag() const + { + if( !m_form ) + return 0; + + Tag* t = new Tag( "feature" ); + t->setXmlns( XMLNS_FEATURE_NEG ); + t->addChild( m_form->tag() ); + return t; + } + +} diff --git a/libs/libgloox/featureneg.h b/libs/libgloox/featureneg.h new file mode 100644 index 0000000..5dca1a5 --- /dev/null +++ b/libs/libgloox/featureneg.h @@ -0,0 +1,86 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef FEATURENEG_H__ +#define FEATURENEG_H__ + +#include "stanzaextension.h" + +#include + +namespace gloox +{ + + class DataForm; + class Tag; + + /** + * @brief An abstraction of Feature Negotiation (XEP-0020), implemented + * as a StanzaExtension. + * + * XEP Version: 1.5 + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API FeatureNeg : public StanzaExtension + { + public: + /** + * Creates a new wrapper object using the given DataForm. + * @param form The DataForm to embed. The FeatureNeg object will own the DataForm. + */ + FeatureNeg( DataForm* form ); + + /** + * Creates a new wrapper object from the given Tag. + * @param tag The Tag to parse. + */ + FeatureNeg( const Tag* tag = 0 ); + + /** + * Virtual destructor. + */ + virtual ~FeatureNeg(); + + /** + * Returns the wrapped DataForm. + * @return The wrapped DataForm. May be 0. + */ + const DataForm* form() const { return m_form; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new FeatureNeg( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new FeatureNeg( m_form ); + } + + private: + DataForm* m_form; + + }; + +} + +#endif // FEATURENEG_H__ diff --git a/libs/libgloox/flexoff.cpp b/libs/libgloox/flexoff.cpp new file mode 100644 index 0000000..9716718 --- /dev/null +++ b/libs/libgloox/flexoff.cpp @@ -0,0 +1,207 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "flexoff.h" +#include "dataform.h" +#include "disco.h" +#include "error.h" + +#include + +namespace gloox +{ + + // ---- FlexibleOffline::Offline ---- + FlexibleOffline::Offline::Offline( const Tag* /*tag*/ ) + : StanzaExtension( ExtFlexOffline ) + { + // FIXME what to do here? + } + + FlexibleOffline::Offline::Offline( int context, const StringList& msgs ) + : StanzaExtension( ExtFlexOffline ), m_context( context ), m_msgs( msgs ) + { + } + + FlexibleOffline::Offline::~Offline() + { + } + + const std::string& FlexibleOffline::Offline::filterString() const + { + static const std::string filter = "/iq/offline[@xmlns='" + XMLNS_OFFLINE + "']"; + return filter; + } + + Tag* FlexibleOffline::Offline::tag() const + { + Tag* t = new Tag( "offline" ); + t->setXmlns( XMLNS_OFFLINE ); + + if( m_msgs.empty() ) + new Tag( t, m_context == FORequestMsgs ? "fetch" : "purge" ); + else + { + const std::string action = m_context == FORequestMsgs ? "view" : "remove"; + StringList::const_iterator it = m_msgs.begin(); + for( ; it != m_msgs.end(); ++it ) + { + Tag* i = new Tag( t, "item", "action", action ); + i->addAttribute( "node", (*it) ); + } + } + return t; + } + // ---- ~FlexibleOffline::Offline ---- + + // ---- FlexibleOffline ---- + FlexibleOffline::FlexibleOffline( ClientBase* parent ) + : m_parent( parent ), m_flexibleOfflineHandler( 0 ) + { + if( m_parent ) + m_parent->registerStanzaExtension( new Offline() ); + } + + FlexibleOffline::~FlexibleOffline() + { + if( m_parent ) + m_parent->removeIDHandler( this ); + } + + void FlexibleOffline::checkSupport() + { + m_parent->disco()->getDiscoInfo( m_parent->jid().server(), EmptyString, this, FOCheckSupport ); + } + + void FlexibleOffline::getMsgCount() + { + m_parent->disco()->getDiscoInfo( m_parent->jid().server(), XMLNS_OFFLINE, this, FORequestNum ); + } + + void FlexibleOffline::fetchHeaders() + { + m_parent->disco()->getDiscoItems( m_parent->jid().server(), XMLNS_OFFLINE, this, FORequestHeaders ); + } + + void FlexibleOffline::messageOperation( int context, const StringList& msgs ) + { + const std::string& id = m_parent->getID(); + IQ::IqType iqType = context == FORequestMsgs ? IQ::Get : IQ::Set; + IQ iq( iqType, JID(), id ); + iq.addExtension( new Offline( context, msgs ) ); + m_parent->send( iq, this, context ); + } + + void FlexibleOffline::registerFlexibleOfflineHandler( FlexibleOfflineHandler* foh ) + { + m_flexibleOfflineHandler = foh; + } + + void FlexibleOffline::removeFlexibleOfflineHandler() + { + m_flexibleOfflineHandler = 0; + } + + void FlexibleOffline::handleDiscoInfo( const JID& /*from*/, const Disco::Info& info, int context ) + { + if( !m_flexibleOfflineHandler ) + return; + + switch( context ) + { + case FOCheckSupport: + m_flexibleOfflineHandler->handleFlexibleOfflineSupport( info.hasFeature( XMLNS_OFFLINE ) ); + break; + + case FORequestNum: + int num = -1; + if( info.form() && info.form()->hasField( "number_of_messages" ) ) + num = atoi( info.form()->field( "number_of_messages" )->value().c_str() ); + + m_flexibleOfflineHandler->handleFlexibleOfflineMsgNum( num ); + break; + } + } + + void FlexibleOffline::handleDiscoItems( const JID& /*from*/, const Disco::Items& items, int context ) + { + if( context == FORequestHeaders && m_flexibleOfflineHandler ) + { + if( items.node() == XMLNS_OFFLINE ) + m_flexibleOfflineHandler->handleFlexibleOfflineMessageHeaders( items.items() ); + } + } + + void FlexibleOffline::handleDiscoError( const JID& /*from*/, const Error* /*error*/, int /*context*/ ) + { + } + + void FlexibleOffline::handleIqID( const IQ& iq, int context ) + { + if( !m_flexibleOfflineHandler ) + return; + + switch( context ) + { + case FORequestMsgs: + switch( iq.subtype() ) + { + case IQ::Result: + m_flexibleOfflineHandler->handleFlexibleOfflineResult( FomrRequestSuccess ); + break; + case IQ::Error: + switch( iq.error()->error() ) + { + case StanzaErrorForbidden: + m_flexibleOfflineHandler->handleFlexibleOfflineResult( FomrForbidden ); + break; + case StanzaErrorItemNotFound: + m_flexibleOfflineHandler->handleFlexibleOfflineResult( FomrItemNotFound ); + break; + default: + m_flexibleOfflineHandler->handleFlexibleOfflineResult( FomrUnknownError ); + break; + } + break; + default: + break; + } + break; + case FORemoveMsgs: + switch( iq.subtype() ) + { + case IQ::Result: + m_flexibleOfflineHandler->handleFlexibleOfflineResult( FomrRemoveSuccess ); + break; + case IQ::Error: + switch( iq.error()->error() ) + { + case StanzaErrorForbidden: + m_flexibleOfflineHandler->handleFlexibleOfflineResult( FomrForbidden ); + break; + case StanzaErrorItemNotFound: + m_flexibleOfflineHandler->handleFlexibleOfflineResult( FomrItemNotFound ); + break; + default: + m_flexibleOfflineHandler->handleFlexibleOfflineResult( FomrUnknownError ); + break; + } + break; + default: + break; + } + break; + } + } + +} diff --git a/libs/libgloox/flexoff.h b/libs/libgloox/flexoff.h new file mode 100644 index 0000000..92db971 --- /dev/null +++ b/libs/libgloox/flexoff.h @@ -0,0 +1,178 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef FLEXOFF_H__ +#define FLEXOFF_H__ + +#include "clientbase.h" +#include "discohandler.h" +#include "flexoffhandler.h" +#include "iqhandler.h" +#include "stanzaextension.h" + +namespace gloox +{ + + /** + * @brief An implementation of XEP-0013 (Flexible Offline Message Retrieval). + * + * Use the FlexibleOfflineHandler to receive results. + * + * @author Jakob Schroeter + * @since 0.7 + */ + class GLOOX_API FlexibleOffline : public DiscoHandler, public IqHandler + { + public: + /** + * Creates a new FlexibleOffline object that manages retrieval of offline messages. + * @param parent The ClientBase to use for communication. + */ + FlexibleOffline( ClientBase* parent ); + + /** + * Virtual Destructor. + */ + virtual ~FlexibleOffline(); + + /** + * Initiates querying the server for Flexible Offline Message Retrieval-support. + * The result is announced through the FlexibleOfflineHandler. + * An application could cache the result on a per-server basis to eliminate the associated delay. + */ + void checkSupport(); + + /** + * Asks the server for the number of stored offline messages. + * The result is announced through the FlexibleOfflineHandler. + */ + void getMsgCount(); + + /** + * Initiates fetching the offline message headers. + * The result is announced through the FlexibleOfflineHandler. + */ + void fetchHeaders(); + + /** + * Initiates fetching of one or more specific messages, or all messages. + * The result is announced through the FlexibleOfflineHandler. + * If the list of message nodes contains one or more nodes, the corresponding messages are + * fetched. If the list is empty all messages are fetched (<fetch>). + * @param msgs A list of message nodes to fetch. + */ + void fetchMessages( const StringList& msgs ) + { messageOperation( FORequestMsgs, msgs ); } + + /** + * Initiates removing of one or more specific messages, or all messages. + * The result is announced through the FlexibleOfflineHandler. + * If the list of message nodes contains one or more nodes, the corresponding messages are + * removed. If the list is empty all messages are removed (<purge>). + */ + void removeMessages( const StringList& msgs ) + { messageOperation( FORemoveMsgs, msgs ); } + + /** + * Registers a FlexibleOfflineHandler as object that receives results of XEP-0013 queries. + * Only one Handler at a time is possible. + * @param foh The Handler object to register. + */ + void registerFlexibleOfflineHandler( FlexibleOfflineHandler* foh ); + + /** + * Removes the registered handler. + */ + void removeFlexibleOfflineHandler(); + + // reimplemented from DiscoHandler + virtual void handleDiscoInfo( const JID& from, const Disco::Info& info, int context ); + + // reimplemented from DiscoHandler + virtual void handleDiscoItems( const JID& from, const Disco::Items& items, int context ); + + // reimplemented from DiscoHandler + virtual void handleDiscoError( const JID& from, const Error* error, int context ); + + // reimplemented from IqHandler. + virtual bool handleIq( const IQ& iq ) { (void)iq; return false; } + + // reimplemented from IqHandler. + virtual void handleIqID( const IQ& iq, int context ); + + private: +#ifdef FLEXOFF_TEST + public: +#endif + class Offline : public StanzaExtension + { + public: + /** + * Constructs a new Offline object from the given Tag. + * @param tag The Tag to parse. + */ + Offline( const Tag* tag = 0 ); + + /** + * Constructs a new Offline object for the given context and messages. + * @param context The context. + * @param msgs The messages. + */ + Offline( int context, const StringList& msgs ); + + /** + * Virtual destructor. + */ + virtual ~Offline(); + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Offline( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new Offline( *this ); + } + + private: + int m_context; + StringList m_msgs; + }; + + void messageOperation( int context, const StringList& msgs ); + + enum FOContext + { + FOCheckSupport, + FORequestNum, + FORequestHeaders, + FORequestMsgs, + FORemoveMsgs + }; + + ClientBase* m_parent; + FlexibleOfflineHandler* m_flexibleOfflineHandler; + }; + +} + +#endif // FLEXOFF_H__ diff --git a/libs/libgloox/flexoffhandler.h b/libs/libgloox/flexoffhandler.h new file mode 100644 index 0000000..803654d --- /dev/null +++ b/libs/libgloox/flexoffhandler.h @@ -0,0 +1,82 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef FLEXOFFHANDLER_H__ +#define FLEXOFFHANDLER_H__ + +#include "disco.h" +#include "gloox.h" + +namespace gloox +{ + + /** + * Describes the possible results of a message retrieval or deletion request. + */ + enum FlexibleOfflineResult + { + FomrRemoveSuccess, /**< Message(s) were removed successfully. */ + FomrRequestSuccess, /**< Message(s) were fetched successfully. */ + FomrForbidden, /**< The requester is a JID other than an authorized resource of the + * user. Something wnet serieously wrong */ + FomrItemNotFound, /**< The requested node (message ID) does not exist. */ + FomrUnknownError /**< An error occurred which is not specified in XEP-0013. */ + }; + + /** + * @brief Implementation of this virtual interface allows for retrieval of offline messages following + * XEP-0030. + * + * @author Jakob Schroeter + * @since 0.7 + */ + class GLOOX_API FlexibleOfflineHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~FlexibleOfflineHandler() {} + + /** + * This function is called to indicate whether the server supports XEP-0013 or not. + * Call @ref FlexibleOffline::checkSupport() to trigger the check. + * @param support Whether the server support XEP-0013 or not. + */ + virtual void handleFlexibleOfflineSupport( bool support ) = 0; + + /** + * This function is called to announce the number of available offline messages. + * Call @ref FlexibleOffline::getMsgCount() to trigger the check. + * @param num The number of stored offline messages. + */ + virtual void handleFlexibleOfflineMsgNum( int num ) = 0; + + /** + * This function is called when the offline message headers arrive. + * Call @ref FlexibleOffline::fetchHeaders() to trigger the check. + * @param headers A map of ID/sender pairs describing the offline messages. + */ + virtual void handleFlexibleOfflineMessageHeaders( const Disco::ItemList& headers ) = 0; + + /** + * This function is called to indicate the result of a fetch or delete instruction. + * @param foResult The result of the operation. + */ + virtual void handleFlexibleOfflineResult( FlexibleOfflineResult foResult ) = 0; + + }; + +} + +#endif // FLEXOFFHANDLER_H__ diff --git a/libs/libgloox/gloox.cpp b/libs/libgloox/gloox.cpp new file mode 100644 index 0000000..eb3eaa6 --- /dev/null +++ b/libs/libgloox/gloox.cpp @@ -0,0 +1,124 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "gloox.h" + +namespace gloox +{ + + const std::string XMLNS_CLIENT = "jabber:client"; + const std::string XMLNS_COMPONENT_ACCEPT = "jabber:component:accept"; + const std::string XMLNS_COMPONENT_CONNECT = "jabber:component:connect"; + + const std::string XMLNS_DISCO_INFO = "http://jabber.org/protocol/disco#info"; + const std::string XMLNS_DISCO_ITEMS = "http://jabber.org/protocol/disco#items"; + const std::string XMLNS_DISCO_PUBLISH = "http://jabber.org/protocol/disco#publish"; + const std::string XMLNS_ADHOC_COMMANDS = "http://jabber.org/protocol/commands"; + const std::string XMLNS_COMPRESSION = "http://jabber.org/protocol/compress"; + const std::string XMLNS_OFFLINE = "http://jabber.org/protocol/offline"; + + const std::string XMLNS_CHAT_STATES = "http://jabber.org/protocol/chatstates"; + const std::string XMLNS_AMP = "http://jabber.org/protocol/amp"; + const std::string XMLNS_IBB = "http://jabber.org/protocol/ibb"; + const std::string XMLNS_FEATURE_NEG = "http://jabber.org/protocol/feature-neg"; + const std::string XMLNS_CHATNEG = "http://jabber.org/protocol/chatneg"; + + const std::string XMLNS_XHTML_IM = "http://jabber.org/protocol/xhtml-im"; + const std::string XMLNS_DELAY = "urn:xmpp:delay"; + const std::string XMLNS_ROSTER = "jabber:iq:roster"; + const std::string XMLNS_VERSION = "jabber:iq:version"; + const std::string XMLNS_REGISTER = "jabber:iq:register"; + + const std::string XMLNS_PRIVACY = "jabber:iq:privacy"; + const std::string XMLNS_AUTH = "jabber:iq:auth"; + const std::string XMLNS_PRIVATE_XML = "jabber:iq:private"; + const std::string XMLNS_LAST = "jabber:iq:last"; + const std::string XMLNS_SEARCH = "jabber:iq:search"; + + const std::string XMLNS_IQ_OOB = "jabber:iq:oob"; + const std::string XMLNS_X_DATA = "jabber:x:data"; + const std::string XMLNS_X_EVENT = "jabber:x:event"; + const std::string XMLNS_X_OOB = "jabber:x:oob"; + const std::string XMLNS_X_DELAY = "jabber:x:delay"; + + const std::string XMLNS_X_GPGSIGNED = "jabber:x:signed"; + const std::string XMLNS_X_GPGENCRYPTED = "jabber:x:encrypted"; + const std::string XMLNS_VCARD_TEMP = "vcard-temp"; + const std::string XMLNS_X_VCARD_UPDATE = "vcard-temp:x:update"; + const std::string XMLNS_BOOKMARKS = "storage:bookmarks"; + + const std::string XMLNS_ANNOTATIONS = "storage:rosternotes"; + const std::string XMLNS_ROSTER_DELIMITER = "roster:delimiter"; + const std::string XMLNS_XMPP_PING = "urn:xmpp:ping"; + const std::string XMLNS_SI = "http://jabber.org/protocol/si"; + const std::string XMLNS_SI_FT = "http://jabber.org/protocol/si/profile/file-transfer"; + + const std::string XMLNS_BYTESTREAMS = "http://jabber.org/protocol/bytestreams"; + const std::string XMLNS_MUC = "http://jabber.org/protocol/muc"; + const std::string XMLNS_MUC_USER = "http://jabber.org/protocol/muc#user"; + const std::string XMLNS_MUC_ADMIN = "http://jabber.org/protocol/muc#admin"; + const std::string XMLNS_MUC_UNIQUE = "http://jabber.org/protocol/muc#unique"; + + const std::string XMLNS_MUC_OWNER = "http://jabber.org/protocol/muc#owner"; + const std::string XMLNS_MUC_ROOMINFO = "http://jabber.org/protocol/muc#roominfo"; + const std::string XMLNS_MUC_ROOMS = "http://jabber.org/protocol/muc#rooms"; + const std::string XMLNS_MUC_REQUEST = "http://jabber.org/protocol/muc#request"; + + const std::string XMLNS_PUBSUB = "http://jabber.org/protocol/pubsub"; + const std::string XMLNS_PUBSUB_ERRORS = "http://jabber.org/protocol/pubsub#errors"; + const std::string XMLNS_PUBSUB_EVENT = "http://jabber.org/protocol/pubsub#event"; + const std::string XMLNS_PUBSUB_OWNER = "http://jabber.org/protocol/pubsub#owner"; + + const std::string XMLNS_CAPS = "http://jabber.org/protocol/caps"; + const std::string XMLNS_FT_FASTMODE = "http://affinix.com/jabber/stream"; + + const std::string XMLNS_STREAM = "http://etherx.jabber.org/streams"; + const std::string XMLNS_XMPP_STREAM = "urn:ietf:params:xml:ns:xmpp-streams"; + const std::string XMLNS_XMPP_STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas"; + const std::string XMLNS_STREAM_TLS = "urn:ietf:params:xml:ns:xmpp-tls"; + const std::string XMLNS_STREAM_SASL = "urn:ietf:params:xml:ns:xmpp-sasl"; + + const std::string XMLNS_STREAM_BIND = "urn:ietf:params:xml:ns:xmpp-bind"; + const std::string XMLNS_STREAM_SESSION = "urn:ietf:params:xml:ns:xmpp-session"; + const std::string XMLNS_STREAM_IQAUTH = "http://jabber.org/features/iq-auth"; + const std::string XMLNS_STREAM_IQREGISTER = "http://jabber.org/features/iq-register"; + const std::string XMLNS_STREAM_COMPRESS = "http://jabber.org/features/compress"; + + const std::string XMLNS_HTTPBIND = "http://jabber.org/protocol/httpbind"; + const std::string XMLNS_XMPP_BOSH = "urn:xmpp:xbosh"; + const std::string XMLNS_RECEIPTS = "urn:xmpp:receipts"; + const std::string XMLNS_NICKNAME = "http://jabber.org/protocol/nick"; + + const std::string XMLNS_JINGLE = "urn:xmpp:tmp:jingle"; + const std::string XMLNS_JINGLE_AUDIO_RTP = "urn:xmpp:tmp:jingle:apps:audio-rtp"; + const std::string XMLNS_JINGLE_ICE_UDP = "urn:xmpp:tmp:jingle:transports:ice-udp"; + const std::string XMLNS_JINGLE_RAW_UDP = "urn:xmpp:tmp:jingle:transports:raw-udp"; + const std::string XMLNS_JINGLE_VIDEO_RTP = "urn:xmpp:tmp:jingle:apps:video-rtp"; + + const std::string XMLNS_SHIM = "http://jabber.org/protocol/shim"; + const std::string XMLNS_ATTENTION = "urn:xmpp:attention:0"; + + const std::string XMPP_STREAM_VERSION_MAJOR = "1"; + const std::string XMPP_STREAM_VERSION_MINOR = "0"; + const std::string GLOOX_VERSION = "1.0"; + const std::string GLOOX_CAPS_NODE = "http://camaya.net/gloox"; + + const std::string XMLNS = "xmlns"; + const std::string TYPE = "type"; + const std::string EmptyString = ""; +} + +const char* gloox_version() +{ + return gloox::GLOOX_VERSION.c_str(); +} diff --git a/libs/libgloox/gloox.h b/libs/libgloox/gloox.h new file mode 100644 index 0000000..b86462e --- /dev/null +++ b/libs/libgloox/gloox.h @@ -0,0 +1,1231 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +/*! @mainpage gloox API Documentation + * + * @section contents Contents + * @ref intro_sec
+ * @ref handlers_sec
+ * @ref comp_sec
+ * @ref client_sec
+ * @ref block_conn_sec
+ * @ref roster_sec
+ * @ref privacy_sec
+ * @ref auth_sec
+ * @ref msg_sec
+ * @ref xeps_sec
+ * @ref filetransfer_sec
+ * @ref proxy_sec
+ * @ref upgrading_sec
+ *
+ * + * @section intro_sec Introduction + * + * The design of gloox follows the so-called observer pattern, which basically means that everything is + * event-driven. There are two ways you can connect to the Jabber/XMPP network using gloox, either as + * client or as component. For a C++ XMPP server library see . + * + * @note Section 11.5 of the XMPP specification (RFC 3290) requires that only UTF-8 is used as encoding + * for any traffic sent over the wire. Since gloox cannot know which encoding is used in any given input, + * it is a requirement that any input to gloox is valid UTF-8. + * + * @section handlers_sec Event Handlers + * + * The most important tools of gloox are the event handlers. Currently, there exist 4 handlers for + * the basic protocol as defined in the RFCs, as well as numerous handlers for events generated by + * the included XEP-implementations and for additional functionality. Additionally, a log handler, + * a generic tag handler and a handler for connection events are available. + * + * Basically these handlers are virtual interfaces from which you derive a class and implement a few + * virtual functions. Then you register such an object with the respective protocol implementation. A + * short example: + * @code + * class MyClass : public PresenceHandler + * { + * public: + * // reimplemented from PresenceHandler + * virtual void handlePresence( const Presence& presence ); + * + * [...] + * }; + * + * void MyClass::handlePresence( const Presence& presence ) + * { + * // extract further information from the Presence object + * } + * @endcode + * + * Somewhere else you do something like this: + * @code + * OtherClass::doSomething() + * { + * Client* client = new Client( ... ); + * [...] + * MyClass* handler = new MyClass( ... ); + * client->registerPresenceHandler( handler ); + * } + * @endcode + * + * Now, every time a presence stanza (not subscription stanza) is received, handlePresence() is called + * with the current stanza as argument. You can then use the extensive getters of the Stanza class to + * extract stanza data. + * + * This works similar for all the other event handlers. + * Another example, this time using the connection event handler (class @link gloox::ConnectionListener + * ConnectionListener @endlink): + * @code + * class MyClass : public ConnectionListener + * { + * public: + * virtual void onConnect(); + * + * virtual bool onTLSConnect( ... ); + * }; + * + * void MyClass::onConnect() + * { + * // do something when the connection is established + * } + * + * bool MyClass::onTLSConnect( const CertInfo& info ) + * { + * // decide whether you trust the certificate, examine the CertInfo structure + * return true; // if you trust it, otherwise return false + * } + * @endcode + * + * @note The ConnectionListener interface is a peculiarity. You MUST re-implement + * @link gloox::ConnectionListener::onTLSConnect() ConnectionListener::onTLSConnect() @endlink if + * you want to be able to connect successfully to TLS/SSL enabled servers. Even though gloox tries + * to verify the server's certificate it does not automatically trust a server. The client's programmer + * and/or user have to decide whether to trust a server or not. This trust is expressed by the return + * value of onTLSConnect(). @b False means you don't trust the server/certificate and as a consequence + * the connection is dropped immediately. + * + * @section comp_sec Components + * + * A component in the Jabber/XMPP network is an add-on to a server which runs externally + * to the actual server software, but can have similar privileges. Components use a protocol described in + * XEP-0114 to connect and authenticate to a server. + * + * The @link gloox::Component Component @endlink class supports this protocol and can be used to create + * a new Jabber component. It's as simple as: + * @code + * Component* comp = new Component( ... ); + * comp->connect(); + * @endcode + * + * @section client_sec Clients + * + * A client can be an end-user's chat client, a bot, or a similar entity not tied to a particular + * server. The @link gloox::Client Client @endlink class implements the necessary functionality to + * connect to an XMPP server. Usage is, again, pretty simple: + * @code + * class MyClass : public ConnectionListener, PresenceHandler + * { + * public: + * void doSomething(); + * + * virtual void handlePresence( ... ); + * + * virtual void onConnect(); + * + * virtual bool onTLSConnect( const CertInfo& info ); + * }; + * + * void MyClass::doSomething() + * { + * JID jid( "jid@server/resource" ); + * Client* client = new Client( jid, "password" ); + * client->registerConnectionListener( this ); + * client->registerPresenceHandler( this ); + * client->connect(); + * } + * + * void MyClass::onConnect() + * { + * // connection established, auth done (see API docs for exceptions) + * } + * + * bool MyClass::onTLSConnect( const CertInfo& info ) + * { + * // examine certificate info + * } + * + * void MyClass::handlePresence( Presence* presence ) + * { + * // presence info + * } + * @endcode + * + * @note gloox does not officially support the style of connection which is usually used on port + * 5223, i.e. SSL encryption before any XML is sent, because it's a legacy method and not standard XMPP. + * However, gloox includes a ConnectionTLS class that, as a side-effect, allows you to establish such + * connections. + * + * @note @link gloox::Client::connect() Client::connect() @endlink by default blocks until the + * connection ends (either @link gloox::Client::disconnect() Client::disconnect() @endlink is called + * or the server closes the connection). + * + * @section block_conn_sec Blocking vs. Non-blocking Connections + * + * For some kind of bots a blocking connection (the default behaviour) is ideal. All the bot does is + * react to events coming from the server. However, for end user clients or anything with a GUI this + * is far from perfect. + * + * In these cases non-blocking connections can be used. If + * @link gloox::ClientBase::connect() ClientBase::connect( false ) @endlink is + * called, the function returnes immediately after the connection has been established. It is then + * the resposibility of the programmer to initiate receiving of data from the socket. + * + * The easiest way is to call @link gloox::ClientBase::recv() ClientBase::recv() @endlink + * periodically with the desired timeout (in microseconds) as parameter. The default value of -1 + * means the call blocks until any data was received, which is then parsed automatically. + * + * As an alternative to periodic polling you can get a hold of the raw file descriptor used for the + * connection. You can then use select() on it and use + * @link gloox::ClientBase::recv() ClientBase::recv() @endlink when select indicates that data is + * available. You should @b not recv() any data from the file descriptor directly as there is no + * way to feed that back into the parser. + * + * To get the file descriptor you'll need to set a connection class (e.g. an instance of + * @link gloox::ConnectionTCPClient ConnectionTCPClient @endlink) manually, like so: + * + * @code + * Client* client = new Client( ... ); + * ConnectionTCPClient* conn = new ConnectionTCPClient( client, client->logInstance(), server, port ); + * client->setConnectionImpl( conn ); + * + * client->connect( false ); + * int sock = conn->socket(); + * + * [...] + * @endcode + * + * It would also be possible to fetch the fd like this: + * + * @code + * Client* client = new Client( ... ); + * client->connect( false ); + * int sock = static_cast( client->connectionImpl() )->socket(); + * + * [...] + * @endcode + * Obviously this will only work as long as you haven't set a different type of connection using setConnectionImpl(). + * + * @note This has changed in 0.9. ClientBase::fileDescriptor() is no longer available. + * + * @section roster_sec Roster Management + * + * Among others, RFC 3921 defines the protocol to manage one's contact list (roster). In gloox, the + * @link gloox::RosterManager RosterManager @endlink class implements this functionality. A few + * easy-to-use functions are available to subscribe to or unsubscribe from the presence of remote + * entities. It is also possible to add a contact to a roster without actually subscribing to the + * contacts presence. Additionally, the interface @link gloox::RosterListener RosterListener @endlink + * offers many callbacks for various roster-related events. + * + * If you create a Client object as shown above, you also get a RosterManager for free. + * @link gloox::Client::rosterManager() Client::rosterManager() @endlink returns a pointer to the + * object. + * + * @section privacy_sec Privacy Lists + * + * Also defined in RFC 3921: Privacy Lists. A Privacy List can be used to explicitely block or allow + * sending of stanzas from and to contacts, respectively. You can define rules based on JID, stanza type, + * etc. The @link gloox::PrivacyManager PrivacyManager @endlink class and the + * @link gloox::PrivacyListHandler PrivacyListHandler @endlink virtual interface allow for full + * flexibility in Privacy List handling. + * + * @code + * PrivacyManager* p = new PrivacyManager( ... ); + * [...] + * PrivacyListHandler::PrivacyList list; + * PrivacyItem item( PrivacyItem::TypeJid, PrivacyItem::ActionDeny, + * PrivacyItem::PacketMessage, "me@there.com" ); + * list.push_back( item ); + * + * PrivacyItem item2( PrivacyItem::TypeJid, PrivacyItem::ActionAllow, + * PrivacyItem::PacketIq, "me@example.org" ); + * list.push_back( item2 ); + * + * p->store( "myList", list ); + * @endcode + * + * @section auth_sec Authentication + * + * gloox supports old-style IQ-based authentication defined in XEP-0078 as well as several SASL mechanisms. + * See the documentation of the @link gloox::Client Client @endlink class for more information. + * + * @section msg_sec Sending and Receiving of Chat Messages + * + * For Messaging it is recommended to use the MessageSession interface. It handles sending and receiving + * of messages as well as message events and chat states (such as typing notifications, etc.). See + * @link gloox::MessageSession MessageSession @endlink for more details. + * + * @section xeps_sec Protocol Extensions (XEPs) + * + * The XMPP Standards Foundation has published a number of extensions to the core protocols, called + * XMPP Extension Protocols (XEPs). A couple of these XEPs are implemented in gloox: + * + * @li XEP-0004 @link gloox::DataForm Data Forms @endlink + * @li XEP-0012 @link gloox::LastActivity Last Activity @endlink + * @li XEP-0013 @link gloox::FlexibleOffline Flexible Offline Message Retrieval @endlink + * @li XEP-0022 Message Events (see @link gloox::MessageSession MessageSession @endlink for examples) + * @li XEP-0027 Current Jabber OpenPGP Usage (see @link gloox::GPGSigned GPGSigned @endlink + * and @link gloox::GPGEncrypted GPGEncrypted @endlink) + * @li XEP-0030 @link gloox::Disco Service Discovery @endlink + * @li XEP-0045 @link gloox::MUCRoom Multi-User Chat @endlink + * @li XEP-0047 Used with @ref filetransfer_sec + * @li XEP-0048 @link gloox::BookmarkStorage Bookmark Storage @endlink + * @li XEP-0049 @link gloox::PrivateXML Private XML Storage @endlink + * @li XEP-0050 @link gloox::Adhoc Ad-hoc Commands @endlink + * @li XEP-0054 @link gloox::VCardManager vcard-temp @endlink + * @li XEP-0060 @link gloox::PubSub::Manager Publish-Subscribe @endlink + * @li XEP-0065 @link gloox::SOCKS5BytestreamManager SOCKS5 Bytestreams @endlink, used with + * @ref filetransfer_sec and @ref proxy_sec + * @li XEP-0066 @link gloox::OOB Out of Band Data @endlink, also used with @ref filetransfer_sec + * @li XEP-0077 @link gloox::Registration In-Band Registration @endlink + * @li XEP-0078 Non-SASL Authentication (automatically used if the server does not support SASL) + * @li XEP-0079 @link gloox::AMP Advanced Message Processing @endlink + * @li XEP-0083 Nested Roster Groups (automatically used if supported by the server. see + * @link gloox::RosterManager::delimiter() RosterManager @endlink) + * @li XEP-0085 Chat State Notifications (see @link gloox::MessageSession MessageSession @endlink for + * examples) + * @li XEP-0091 @link gloox::DelayedDelivery Delayed Delivery @endlink (old spec) + * @li XEP-0092 Software Version (integrated into @link gloox::Disco Service Discovery @endlink) + * @li XEP-0095 @link gloox::SIManager Stream Initiation @endlink, used with @ref filetransfer_sec + * @li XEP-0096 @ref filetransfer_sec + * @li XEP-0106 @link gloox::JID::escapeNode() JID Escaping @endlink + * @li XEP-0114 @link gloox::Component Jabber Component Protocol @endlink + * @li XEP-0115 @link gloox::Capabilities Entity Capabilities @endlink (used automatically internally) + * @li XEP-0124 @link gloox::ConnectionBOSH Bidirectional-streams Over Synchronous HTTP (BOSH) @endlink + * @li XEP-0131 @link gloox::SHIM Stanza Headers and Internet Metadata @endlink + * @li XEP-0138 Stream Compression (used automatically if gloox is compiled with zlib and if the server + * supports it) + * @li XEP-0145 @link gloox::Annotations Annotations @endlink + * @li XEP-0153 @link gloox::VCardUpdate vCard-based Avatars @endlink + * @li XEP-0172 @link gloox::Nickname User Nickname @endlink + * @li XEP-0184 @link gloox::Receipt Message Receipts @endlink + * @li XEP-0199 @link gloox::ClientBase::xmppPing() XMPP Ping @endlink + * @li XEP-0203 @link gloox::DelayedDelivery Delayed Delivery @endlink (new spec) + * @li XEP-0206 @link gloox::ConnectionBOSH see BOSH @endlink + * @li XEP-0224 @link gloox::Attention Attention @endlink + * @li XEP-0256 @link gloox::LastActivity::Query Last Activity in Presence @endlink + * + * Further extensions can easily be implemented using + * @link gloox::StanzaExtension StanzaExtensions @endlink. + * + * @section filetransfer_sec File Transfer + * + * For file transfer, gloox implements XEP-0095 (Stream Initiation) as well XEP-0096 (File Transfer) + * for the signalling, and XEP-0065 (SOCKS5 Bytestreams) as well as XEP-0047 (In-Band Bytestreams) + * for the transport. See @link gloox::SIProfileFT SIProfileFT @endlink. + * + * @section proxy_sec HTTP and SOCKS5 Proxy support + * + * gloox is capable of traversing HTTP as well as SOCKS5 proxies, even chained. See + * @link gloox::ConnectionHTTPProxy ConnectionHTTPProxy @endlink and + * @link gloox::ConnectionSOCKS5Proxy ConnectionSOCKS5Proxy @endlink. + * + * @section upgrading_sec Upgrading from earlier versions + * + * See Upgrading. + * + */ + +#ifndef GLOOX_H__ +#define GLOOX_H__ + +#include "macros.h" + +#include +#include +#include + +/** + * @brief The namespace for the gloox library. + * + * @author Jakob Schroeter + * @since 0.3 + */ +namespace gloox +{ + /** Client namespace (RFC 3920)*/ + GLOOX_API extern const std::string XMLNS_CLIENT; + + /** Component Accept namespace (XEP-0114) */ + GLOOX_API extern const std::string XMLNS_COMPONENT_ACCEPT; + + /** Component Connect namespace (XEP-0114) */ + GLOOX_API extern const std::string XMLNS_COMPONENT_CONNECT; + + /** Service Discovery Info namespace (XEP-0030) */ + GLOOX_API extern const std::string XMLNS_DISCO_INFO; + + /** Service Discovery Items namespace (XEP-0030) */ + GLOOX_API extern const std::string XMLNS_DISCO_ITEMS; + + /** Service Discovery Publish namespace (XEP-0030) */ + GLOOX_API extern const std::string XMLNS_DISCO_PUBLISH; + + /** Adhoc Commands namespace (XEP-0050) */ + GLOOX_API extern const std::string XMLNS_ADHOC_COMMANDS; + + /** Stream Compression namespace (XEP-0138) */ + GLOOX_API extern const std::string XMLNS_COMPRESSION; + + /** Flexible Offline Message Retrieval (XEP-0013) */ + GLOOX_API extern const std::string XMLNS_OFFLINE; + + /** Chat State Notifications namespace (XEP-0085) */ + GLOOX_API extern const std::string XMLNS_CHAT_STATES; + + /** Advanced Message Processing (XEP-0079) */ + GLOOX_API extern const std::string XMLNS_AMP; + + /** In-Band Bytestreams namespace (XEP-0047) */ + GLOOX_API extern const std::string XMLNS_IBB; + + /** Feature Negotiation namespace (XEP-0020) */ + GLOOX_API extern const std::string XMLNS_FEATURE_NEG; + + /** Chat Session Negotiation namespace (XEP-0155) */ + GLOOX_API extern const std::string XMLNS_CHATNEG; + + /** XHTML-IM namespace (XEP-0071) */ + GLOOX_API extern const std::string XMLNS_XHTML_IM; + + /** Delayed Delivery namespace (XEP-0203) */ + GLOOX_API extern const std::string XMLNS_DELAY; + + /** Roster namespace (RFC 3921) */ + GLOOX_API extern const std::string XMLNS_ROSTER; + + /** Software Version namespace (XEP-0092) */ + GLOOX_API extern const std::string XMLNS_VERSION; + + /** In-Band Registration namespace (XEP-0077) */ + GLOOX_API extern const std::string XMLNS_REGISTER; + + /** Privacy lists namespace (RFC 3921) */ + GLOOX_API extern const std::string XMLNS_PRIVACY; + + /** Non-SASL Authentication namespace (XEP-0078) */ + GLOOX_API extern const std::string XMLNS_AUTH; + + /** Private XML Storage namespace (XEP-0049) */ + GLOOX_API extern const std::string XMLNS_PRIVATE_XML; + + /** Last Activity namespace (XEP-0012) */ + GLOOX_API extern const std::string XMLNS_LAST; + + /** Jabber Search namespace (XEP-0055) */ + GLOOX_API extern const std::string XMLNS_SEARCH; + + /** Out of Band Data (IQ) namespace (XEP-0066) */ + GLOOX_API extern const std::string XMLNS_IQ_OOB; + + /** Data Forms namespace (XEP-0004) */ + GLOOX_API extern const std::string XMLNS_X_DATA; + + /** Message Events (XEP-0022) */ + GLOOX_API extern const std::string XMLNS_X_EVENT; + + /** Out of Band Data (X) namespace (XEP-0066) */ + GLOOX_API extern const std::string XMLNS_X_OOB; + + /** Delayed Delivery namespace (XEP-0091) */ + GLOOX_API extern const std::string XMLNS_X_DELAY; + + /** Current Jabber OpenPGP Usage (Sign.) (XEP-0027) */ + GLOOX_API extern const std::string XMLNS_X_GPGSIGNED; + + /** Current Jabber OpenPGP Usage (Enc.) (XEP-0027) */ + GLOOX_API extern const std::string XMLNS_X_GPGENCRYPTED; + + /** vcard-temp namespace (XEP-0054) */ + GLOOX_API extern const std::string XMLNS_VCARD_TEMP; + + /** vCard-Based Avatars namespace (XEP-0153) */ + GLOOX_API extern const std::string XMLNS_X_VCARD_UPDATE; + + /** Bookmark Storage namespace (XEP-0048) */ + GLOOX_API extern const std::string XMLNS_BOOKMARKS; + + /** Annotations namespace (XEP-0145) */ + GLOOX_API extern const std::string XMLNS_ANNOTATIONS; + + /** Nested Roster Groups namespace (XEP-0083) */ + GLOOX_API extern const std::string XMLNS_ROSTER_DELIMITER; + + /** XMPP Ping namespace (XEP-0199) */ + GLOOX_API extern const std::string XMLNS_XMPP_PING; + + /** Stream Initiation namespace (XEP-0095) */ + GLOOX_API extern const std::string XMLNS_SI; + + /** File transfer profile of Stream Initiation (XEP-0096) */ + GLOOX_API extern const std::string XMLNS_SI_FT; + + /** SOCKS5 Bytestreams namespace (XEP-0065) */ + GLOOX_API extern const std::string XMLNS_BYTESTREAMS; + + /** Multi-User Chat namespace (XEP-0045) */ + GLOOX_API extern const std::string XMLNS_MUC; + + /** Multi-User Chat namespace (user) (XEP-0045) */ + GLOOX_API extern const std::string XMLNS_MUC_USER; + + /** Multi-User Chat namespace (admin) (XEP-0045) */ + GLOOX_API extern const std::string XMLNS_MUC_ADMIN; + + /** Multi-User Chat namespace (unique) (XEP-0045) */ + GLOOX_API extern const std::string XMLNS_MUC_UNIQUE; + + /** Multi-User Chat namespace (owner) (XEP-0045) */ + GLOOX_API extern const std::string XMLNS_MUC_OWNER; + + /** Multi-User Chat namespace (roominfo) (XEP-0045) */ + GLOOX_API extern const std::string XMLNS_MUC_ROOMINFO; + + /** Multi-User Chat namespace (rooms) (XEP-0045) */ + GLOOX_API extern const std::string XMLNS_MUC_ROOMS; + + /** Multi-User Chat namespace (request) (XEP-0045) */ + GLOOX_API extern const std::string XMLNS_MUC_REQUEST; + + /** PubSub namespace (XEP-0060) */ + GLOOX_API extern const std::string XMLNS_PUBSUB; + + /** PubSub namespace (errors) (XEP-0060) */ + GLOOX_API extern const std::string XMLNS_PUBSUB_ERRORS; + + /** PubSub namespace (event) (XEP-0060) */ + GLOOX_API extern const std::string XMLNS_PUBSUB_EVENT; + + /** PubSub namespace (owner) (XEP-0060) */ + GLOOX_API extern const std::string XMLNS_PUBSUB_OWNER; + + /** Entity Capabilities namespace (XEP-0115) */ + GLOOX_API extern const std::string XMLNS_CAPS; + + /** SOCKS5 Fast Mode namespace */ + GLOOX_API extern const std::string XMLNS_FT_FASTMODE; + + /** XMPP stream namespace (RFC 3920) */ + GLOOX_API extern const std::string XMLNS_STREAM; + + /** XMPP stream namespace (RFC 3920) */ + GLOOX_API extern const std::string XMLNS_XMPP_STREAM; + + /** XMPP stanzas namespace (RFC 3920) */ + GLOOX_API extern const std::string XMLNS_XMPP_STANZAS; + + /** TLS Stream Feature namespace (RFC 3920) */ + GLOOX_API extern const std::string XMLNS_STREAM_TLS; + + /** SASL Stream Feature namespace (RFC 3920) */ + GLOOX_API extern const std::string XMLNS_STREAM_SASL; + + /** Resource Bind Stream Feature (RFC 3921) */ + GLOOX_API extern const std::string XMLNS_STREAM_BIND; + + /** Session Create Stream Feature (RFC 3921) */ + GLOOX_API extern const std::string XMLNS_STREAM_SESSION; + + /** Non-SASL Auth. Stream Feature (XEP-0078) */ + GLOOX_API extern const std::string XMLNS_STREAM_IQAUTH; + + /** In-Band Registration namespace (XEP-0077) */ + GLOOX_API extern const std::string XMLNS_STREAM_IQREGISTER; + + /** Stream Compression Feature namespace (XEP-0138) */ + GLOOX_API extern const std::string XMLNS_STREAM_COMPRESS; + + /** General HTTP binding (BOSH) namespace (XEP-0124) */ + GLOOX_API extern const std::string XMLNS_HTTPBIND; + + /** XMPP-over-BOSH extensions (XEP-0206) */ + GLOOX_API extern const std::string XMLNS_XMPP_BOSH; + + /** Message Receipt namespace (XEP-0184) */ + GLOOX_API extern const std::string XMLNS_RECEIPTS; + + /** Message Receipt namespace (XEP-0172) */ + GLOOX_API extern const std::string XMLNS_NICKNAME; + + /** Jingle namespace (XEP-0166) */ + GLOOX_API extern const std::string XMLNS_JINGLE; + + /** Jingle Audio via RTP namespace (XEP-0167) */ + GLOOX_API extern const std::string XMLNS_JINGLE_AUDIO_RTP; + + /** Jingle ICE-UDP Transport namespace (XEP-0176) */ + GLOOX_API extern const std::string XMLNS_JINGLE_ICE_UDP; + + /** Jingle Raw UDP Transport namespace (XEP-0177) */ + GLOOX_API extern const std::string XMLNS_JINGLE_RAW_UDP; + + /** Jingle Video via RTP namespace (XEP-0180) */ + GLOOX_API extern const std::string XMLNS_JINGLE_VIDEO_RTP; + + /** Stanza Headers and Internet Metadata (SHIM) namespace (XEP-0131) */ + GLOOX_API extern const std::string XMLNS_SHIM; + + /** Attention namespace (XEP-0224) */ + GLOOX_API extern const std::string XMLNS_ATTENTION; + + + /** Supported stream version (major). */ + GLOOX_API extern const std::string XMPP_STREAM_VERSION_MAJOR; + + /** Supported stream version (minor). */ + GLOOX_API extern const std::string XMPP_STREAM_VERSION_MINOR; + + /** gloox version */ + GLOOX_API extern const std::string GLOOX_VERSION; + + /** gloox caps node */ + GLOOX_API extern const std::string GLOOX_CAPS_NODE; + + /** A string containing "xmlns". */ + GLOOX_API extern const std::string XMLNS; + + /** A string containing "type". */ + GLOOX_API extern const std::string TYPE; + + /** An empty string. */ + GLOOX_API extern const std::string EmptyString; + + /** + * This describes the possible states of a stream. + */ + enum ConnectionState + { + StateDisconnected, /**< The client is in disconnected state. */ + StateConnecting, /**< The client is currently trying to establish a connection. */ + StateConnected /**< The client is connected to the server but authentication is not + * (yet) done. */ + }; + + /** + * Describes stream events that get emitted by means of ConnectionListener::onStreamEvent(). + * @since 0.9 + */ + enum StreamEvent + { + StreamEventConnecting, /**< The Client is about to initaite the connection. */ + StreamEventEncryption, /**< The Client is about to negotiate encryption. */ + StreamEventCompression, /**< The Client is about to negotiate compression. */ + StreamEventAuthentication, /**< The Client is about to authenticate. */ + StreamEventSessionInit, /**< The Client is about to create a session. */ + StreamEventResourceBinding, /**< The Client is about to bind a resource to the stream. */ + StreamEventSessionCreation, /**< The Client is about to create a session. + * @since 0.9.1 */ + StreamEventRoster, /**< The Client is about to request the roster. */ + StreamEventFinished /**< The log-in phase is completed. */ + }; + + /** + * This describes connection error conditions. + */ + enum ConnectionError + { + ConnNoError, /**< Not really an error. Everything went just fine. */ + ConnStreamError, /**< A stream error occured. The stream has been closed. + * Use ClientBase::streamError() to find the reason. */ + ConnStreamVersionError, /**< The incoming stream's version is not supported */ + ConnStreamClosed, /**< The stream has been closed (by the server). */ + ConnProxyAuthRequired, /**< The HTTP/SOCKS5 proxy requires authentication. + * @since 0.9 */ + ConnProxyAuthFailed, /**< HTTP/SOCKS5 proxy authentication failed. + * @since 0.9 */ + ConnProxyNoSupportedAuth, /**< The HTTP/SOCKS5 proxy requires an unsupported auth mechanism. + * @since 0.9 */ + ConnIoError, /**< An I/O error occured. */ + ConnParseError, /**< An XML parse error occurred. */ + ConnConnectionRefused, /**< The connection was refused by the server (on the socket level). + * @since 0.9 */ + ConnDnsError, /**< Resolving the server's hostname failed. + * @since 0.9 */ + ConnOutOfMemory, /**< Out of memory. Uhoh. */ + ConnNoSupportedAuth, /**< The auth mechanisms the server offers are not supported + * or the server offered no auth mechanisms at all. */ + ConnTlsFailed, /**< The server's certificate could not be verified or the TLS + * handshake did not complete successfully. */ + ConnTlsNotAvailable, /**< The server didn't offer TLS while it was set to be required, + * or TLS was not compiled in. + * @since 0.9.4 */ + ConnCompressionFailed, /**< Negotiating/initializing compression failed. + * @since 0.9 */ + ConnAuthenticationFailed, /**< Authentication failed. Username/password wrong or account does + * not exist. Use ClientBase::authError() to find the reason. */ + ConnUserDisconnected, /**< The user (or higher-level protocol) requested a disconnect. */ + ConnNotConnected /**< There is no active connection. */ + }; + + /** + * ClientBase's policy regarding TLS usage. Use with ClientBase::setTls(). + */ + enum TLSPolicy + { + TLSDisabled, /**< Don't use TLS. */ + TLSOptional, /**< Use TLS if compiled in and offered by the server. */ + TLSRequired /**< Don't attempt to log in if the server didn't offer TLS + * or if TLS was not compiled in. Disconnect error will be + * ConnTlsNotAvailable. */ + }; + + /** + * Supported Stream Features. + */ + enum StreamFeature + { + StreamFeatureBind = 1, /**< The server supports resource binding. */ + StreamFeatureUnbind = 2, /**< The server supports binding multiple resources. */ + StreamFeatureSession = 4, /**< The server supports sessions. */ + StreamFeatureStartTls = 8, /**< The server supports <starttls>. */ + StreamFeatureIqRegister = 16, /**< The server supports XEP-0077 (In-Band + * Registration). */ + StreamFeatureIqAuth = 32, /**< The server supports XEP-0078 (Non-SASL + * Authentication). */ + StreamFeatureCompressZlib = 64, /**< The server supports XEP-0138 (Stream + * Compression) (Zlib). */ + StreamFeatureCompressDclz = 128 /**< The server supports XEP-0138 (Stream + * Compression) (LZW/DCLZ). */ + // SASLMechanism below must be adjusted accordingly. + }; + + /** + * Supported SASL mechanisms. + */ + // must be adjusted with changes to StreamFeature enum above + enum SaslMechanism + { + SaslMechNone = 0, /**< Invalid SASL Mechanism. */ + SaslMechDigestMd5 = 256, /**< SASL Digest-MD5 according to RFC 2831. */ + SaslMechPlain = 512, /**< SASL PLAIN according to RFC 2595 Section 6. */ + SaslMechAnonymous = 1024, /**< SASL ANONYMOUS according to draft-ietf-sasl-anon-05.txt/ + * RFC 2245 Section 6. */ + SaslMechExternal = 2048, /**< SASL EXTERNAL according to RFC 2222 Section 7.4. */ + SaslMechGssapi = 4096, /**< SASL GSSAPI (Win32 only). */ + SaslMechNTLM = 8192, /**< SASL NTLM (Win32 only). */ + SaslMechAll = 65535 /**< Includes all supported SASL mechanisms. */ + }; + + /** + * This decribes stream error conditions as defined in RFC 3920 Sec. 4.7.3. + */ + enum StreamError + { + StreamErrorBadFormat, /**< The entity has sent XML that cannot be processed; + * this error MAY be used instead of the more specific XML-related + * errors, such as <bad-namespace-prefix/>, <invalid-xml/>, + * <restricted-xml/>, <unsupported-encoding/>, and + * <xml-not-well-formed/>, although the more specific errors are + * preferred. */ + StreamErrorBadNamespacePrefix, /**< The entity has sent a namespace prefix that is unsupported, or has + * sent no namespace prefix on an element that requires such a prefix + * (see XML Namespace Names and Prefixes (Section 11.2)). */ + StreamErrorConflict, /**< The server is closing the active stream for this entity because a + * new stream has been initiated that conflicts with the existing + * stream. */ + StreamErrorConnectionTimeout, /**< The entity has not generated any traffic over the stream for some + * period of time (configurable according to a local service policy).*/ + StreamErrorHostGone, /**< the value of the 'to' attribute provided by the initiating entity + * in the stream header corresponds to a hostname that is no longer + * hosted by the server.*/ + StreamErrorHostUnknown, /**< The value of the 'to' attribute provided by the initiating entity + * in the stream header does not correspond to a hostname that is hosted + * by the server. */ + StreamErrorImproperAddressing, /**< A stanza sent between two servers lacks a 'to' or 'from' attribute + * (or the attribute has no value). */ + StreamErrorInternalServerError, /**< the server has experienced a misconfiguration or an + * otherwise-undefined internal error that prevents it from servicing the + * stream. */ + StreamErrorInvalidFrom, /**< The JID or hostname provided in a 'from' address does not match an + * authorized JID or validated domain negotiated between servers via SASL + * or dialback, or between a client and a server via authentication and + * resource binding.*/ + StreamErrorInvalidId, /**< The stream ID or dialback ID is invalid or does not match an ID + * previously provided. */ + StreamErrorInvalidNamespace, /**< The streams namespace name is something other than + * "http://etherx.jabber.org/streams" or the dialback namespace name is + * something other than "jabber:server:dialback" (see XML Namespace Names + * and Prefixes (Section 11.2)). */ + StreamErrorInvalidXml, /**< The entity has sent invalid XML over the stream to a server that + * performs validation (see Validation (Section 11.3)). */ + StreamErrorNotAuthorized, /**< The entity has attempted to send data before the stream has been + * authenticated, or otherwise is not authorized to perform an action + * related to stream negotiation; the receiving entity MUST NOT process + * the offending stanza before sending the stream error. */ + StreamErrorPolicyViolation, /**< The entity has violated some local service policy; the server MAY + * choose to specify the policy in the <text/> element or an + * application-specific condition element. */ + StreamErrorRemoteConnectionFailed,/**< The server is unable to properly connect to a remote entity that + * is required for authentication or authorization. */ + StreamErrorResourceConstraint, /**< the server lacks the system resources necessary to service the + * stream. */ + StreamErrorRestrictedXml, /**< The entity has attempted to send restricted XML features such as a + * comment, processing instruction, DTD, entity reference, or unescaped + * character (see Restrictions (Section 11.1)). */ + StreamErrorSeeOtherHost, /**< The server will not provide service to the initiating entity but is + * redirecting traffic to another host; the server SHOULD specify the + * alternate hostname or IP address (which MUST be a valid domain + * identifier) as the XML character data of the <see-other-host/> + * element. */ + StreamErrorSystemShutdown, /**< The server is being shut down and all active streams are being + * closed. */ + StreamErrorUndefinedCondition, /**< The error condition is not one of those defined by the other + * conditions in this list; this error condition SHOULD be used only in + * conjunction with an application-specific condition. */ + StreamErrorUnsupportedEncoding, /**< The initiating entity has encoded the stream in an encoding that is + * not supported by the server (see Character Encoding (Section 11.5)). + */ + StreamErrorUnsupportedStanzaType,/**< The initiating entity has sent a first-level child of the stream + * that is not supported by the server. */ + StreamErrorUnsupportedVersion, /**< The value of the 'version' attribute provided by the initiating + * entity in the stream header specifies a version of XMPP that is not + * supported by the server; the server MAY specify the version(s) it + * supports in the <text/> element. */ + StreamErrorXmlNotWellFormed, /**< The initiating entity has sent XML that is not well-formed as + * defined by [XML]. */ + StreamErrorUndefined /**< An undefined/unknown error occured. Also used if a diconnect was + * user-initiated. Also set before and during a established connection + * (where obviously no error occured). */ + }; + + /** + * Describes types of stanza errors. + */ + enum StanzaErrorType + { + StanzaErrorTypeAuth, /**< Retry after providing credentials. */ + StanzaErrorTypeCancel, /**< Do not retry (the error is unrecoverable). */ + StanzaErrorTypeContinue, /**< Proceed (the condition was only a warning). */ + StanzaErrorTypeModify, /**< Retry after changing the data sent. */ + + StanzaErrorTypeWait, /**< Retry after waiting (the error is temporary). */ + StanzaErrorTypeUndefined /**< No error. */ + }; + + /** + * Describes the defined stanza error conditions of RFC 3920. + * Used by, eg., Stanza::error(). + */ + enum StanzaError + { + + StanzaErrorBadRequest, /**< The sender has sent XML that is malformed or that cannot be + * processed (e.g., an IQ stanza that includes an unrecognized value + * of the 'type' attribute); the associated error type SHOULD be + * "modify". */ + StanzaErrorConflict, /**< Access cannot be granted because an existing resource or session + * exists with the same name or address; the associated error type + * SHOULD be "cancel". */ + StanzaErrorFeatureNotImplemented,/**< The feature requested is not implemented by the recipient or + * server and therefore cannot be processed; the associated error + * type SHOULD be "cancel". */ + StanzaErrorForbidden, /**< The requesting entity does not possess the required permissions to + * perform the action; the associated error type SHOULD be "auth". */ + StanzaErrorGone, /**< The recipient or server can no longer be contacted at this address + * (the error stanza MAY contain a new address in the XML character data + * of the <gone/> element); the associated error type SHOULD be + * "modify". */ + StanzaErrorInternalServerError, /**< The server could not process the stanza because of a + * misconfiguration or an otherwise-undefined internal server error; the + * associated error type SHOULD be "wait". */ + StanzaErrorItemNotFound, /**< The addressed JID or item requested cannot be found; the associated + * error type SHOULD be "cancel". */ + StanzaErrorJidMalformed, /**< The sending entity has provided or communicated an XMPP address + * (e.g., a value of the 'to' attribute) or aspect thereof (e.g., a + * resource identifier) that does not adhere to the syntax defined in + * Addressing Scheme (Section 3); the associated error type SHOULD be + * "modify". */ + StanzaErrorNotAcceptable, /**< The recipient or server understands the request but is refusing to + * process it because it does not meet criteria defined by the recipient + * or server (e.g., a local policy regarding acceptable words in + * messages); the associated error type SHOULD be "modify". */ + StanzaErrorNotAllowed, /**< The recipient or server does not allow any entity to perform the + * action; the associated error type SHOULD be "cancel". */ + StanzaErrorNotAuthorized, /**< The sender must provide proper credentials before being allowed to + * perform the action, or has provided impreoper credentials; the + * associated error type should be "auth". */ + StanzaErrorNotModified, /**< The item requested has not changed since it was last requested; + * the associated error type SHOULD be "continue". */ + StanzaErrorPaymentRequired, /**< The requesting entity is not authorized to access the requested + * service because payment is required; the associated error type SHOULD + * be "auth". */ + StanzaErrorRecipientUnavailable,/**< The intended recipient is temporarily unavailable; the associated + * error type SHOULD be "wait" (note: an application MUST NOT return + * this error if doing so would provide information about the intended + * recipient's network availability to an entity that is not authorized + * to know such information). */ + StanzaErrorRedirect, /**< The recipient or server is redirecting requests for this + * information to another entity, usually temporarily (the error + * stanza SHOULD contain the alternate address, which MUST be a valid + * JID, in the XML character data of the <redirect/> element); + * the associated error type SHOULD be "modify". */ + StanzaErrorRegistrationRequired,/**< The requesting entity is not authorized to access the requested + * service because registration is required; the associated error type + * SHOULD be "auth". */ + StanzaErrorRemoteServerNotFound,/**< A remote server or service specified as part or all of the JID of + * the intended recipient does not exist; the associated error type + * SHOULD be "cancel". */ + StanzaErrorRemoteServerTimeout, /**< A remote server or service specified as part or all of the JID of + * the intended recipient (or required to fulfill a request) could not + * be contacted within a reasonable amount of time; the associated error + * type SHOULD be "wait". */ + StanzaErrorResourceConstraint, /**< The server or recipient lacks the system resources necessary to + * service the request; the associated error type SHOULD be "wait". */ + StanzaErrorServiceUnavailable, /**< The server or recipient does not currently provide the requested + * service; the associated error type SHOULD be "cancel". */ + StanzaErrorSubscribtionRequired,/**< The requesting entity is not authorized to access the requested + * service because a subscription is required; the associated error type + * SHOULD be "auth". */ + StanzaErrorUndefinedCondition, /**< The error condition is not one of those defined by the other + * conditions in this list; any error type may be associated with this + * condition, and it SHOULD be used only in conjunction with an + * application-specific condition. */ + StanzaErrorUnexpectedRequest, /**< The recipient or server understood the request but was not + * expecting it at this time (e.g., the request was out of order); + * the associated error type SHOULD be "wait". */ + StanzaErrorUnknownSender, /**< The stanza 'from' address specified by a connected client is not + * valid for the stream (e.g., the stanza does not include a 'from' + * address when multiple resources are bound to the stream); the + * associated error type SHOULD be "modify".*/ + StanzaErrorUndefined /**< No stanza error occured. */ + }; + + /** + * Describes the possible 'available presence' types. + */ +// enum Presence +// { +// PresenceUnknown, /**< Unknown status. */ +// PresenceAvailable, /**< The entity or resource is online and available. */ +// PresenceChat, /**< The entity or resource is actively interested in chatting. */ +// PresenceAway, /**< The entity or resource is temporarily away. */ +// PresenceDnd, /**< The entity or resource is busy (dnd = "Do Not Disturb"). */ +// PresenceXa, /**< The entity or resource is away for an extended period (xa = +// * "eXtended Away"). */ +// PresenceUnavailable /**< The entity or resource is offline. */ +// }; + + /** + * Describes the verification results of a certificate. + */ + enum CertStatus + { + CertOk = 0, /**< The certificate is valid and trusted. */ + CertInvalid = 1, /**< The certificate is not trusted. */ + CertSignerUnknown = 2, /**< The certificate hasn't got a known issuer. */ + CertRevoked = 4, /**< The certificate has been revoked. */ + CertExpired = 8, /**< The certificate has expired. */ + CertNotActive = 16, /**< The certifiacte is not yet active. */ + CertWrongPeer = 32, /**< The certificate has not been issued for the + * peer we're connected to. */ + CertSignerNotCa = 64 /**< The signer is not a CA. */ + }; + + /** + * Describes the certificate presented by the peer. + */ + struct CertInfo + { + int status; /**< Bitwise or'ed CertStatus or CertOK. */ + bool chain; /**< Determines whether the cert chain verified ok. */ + std::string issuer; /**< The name of the issuing entity.*/ + std::string server; /**< The server the certificate has been issued for. */ + int date_from; /**< The date from which onwards the certificate is valid + * (UNIX timestamp; UTC; not set when using OpenSSL). */ + int date_to; /**< The date up to which the certificate is valid + * (UNIX timestamp; UTC; not set when using OpenSSL). */ + std::string protocol; /**< The encryption protocol used for the connection. */ + std::string cipher; /**< The cipher used for the connection. */ + std::string mac; /**< The MAC used for the connection. */ + std::string compression; /**< The compression used for the connection. */ + }; + + /** + * Describes the defined SASL (and non-SASL) error conditions. + */ + enum AuthenticationError + { + AuthErrorUndefined, /**< No error occurred, or error condition is unknown. */ + SaslAborted, /**< The receiving entity acknowledges an <abort/> element sent + * by the initiating entity; sent in reply to the <abort/> + * element. */ + SaslIncorrectEncoding, /**< The data provided by the initiating entity could not be processed + * because the [BASE64] encoding is incorrect (e.g., because the encoding + * does not adhere to the definition in Section 3 of [BASE64]); sent in + * reply to a <response/> element or an <auth/> element with + * initial response data. */ + SaslInvalidAuthzid, /**< The authzid provided by the initiating entity is invalid, either + * because it is incorrectly formatted or because the initiating entity + * does not have permissions to authorize that ID; sent in reply to a + * <response/> element or an <auth/> element with initial + * response data.*/ + SaslInvalidMechanism, /**< The initiating entity did not provide a mechanism or requested a + * mechanism that is not supported by the receiving entity; sent in reply + * to an <auth/> element. */ + SaslMalformedRequest, /**< The request is malformed (e.g., the <auth/> element includes + * an initial response but the mechanism does not allow that); sent in + * reply to an <abort/>, <auth/>, <challenge/>, or + * <response/> element. */ + SaslMechanismTooWeak, /**< The mechanism requested by the initiating entity is weaker than + * server policy permits for that initiating entity; sent in reply to a + * <response/> element or an <auth/> element with initial + * response data. */ + SaslNotAuthorized, /**< The authentication failed because the initiating entity did not + * provide valid credentials (this includes but is not limited to the + * case of an unknown username); sent in reply to a <response/> + * element or an <auth/> element with initial response data. */ + SaslTemporaryAuthFailure, /**< The authentication failed because of a temporary error condition + * within the receiving entity; sent in reply to an <auth/> element + * or <response/> element. */ + NonSaslConflict, /**< XEP-0078: Resource Conflict */ + NonSaslNotAcceptable, /**< XEP-0078: Required Information Not Provided */ + NonSaslNotAuthorized /**< XEP-0078: Incorrect Credentials */ + }; + + /** + * Identifies log sources. + */ + enum LogArea + { + LogAreaClassParser = 0x000001, /**< Log messages from Parser. */ + LogAreaClassConnectionTCPBase = 0x000002, /**< Log messages from ConnectionTCPBase. */ + LogAreaClassClient = 0x000004, /**< Log messages from Client. */ + LogAreaClassClientbase = 0x000008, /**< Log messages from ClientBase. */ + LogAreaClassComponent = 0x000010, /**< Log messages from Component. */ + LogAreaClassDns = 0x000020, /**< Log messages from DNS. */ + LogAreaClassConnectionHTTPProxy = 0x000040, /**< Log messages from ConnectionHTTPProxy */ + LogAreaClassConnectionSOCKS5Proxy = 0x000080, /**< Log messages from ConnectionSOCKS5Proxy */ + LogAreaClassConnectionTCPClient = 0x000100, /**< Log messages from ConnectionTCPClient. */ + LogAreaClassConnectionTCPServer = 0x000200, /**< Log messages from ConnectionTCPServer. */ + LogAreaClassS5BManager = 0x000400, /**< Log messages from SOCKS5BytestreamManager. */ + LogAreaClassSOCKS5Bytestream = 0x000800, /**< Log messages from SOCKS5Bytestream. */ + LogAreaClassConnectionBOSH = 0x001000, /**< Log messages from ConnectionBOSH */ + LogAreaClassConnectionTLS = 0x002000, /**< Log messages from ConnectionTLS */ + LogAreaAllClasses = 0x01FFFF, /**< All log messages from all the classes. */ + LogAreaXmlIncoming = 0x020000, /**< Incoming XML. */ + LogAreaXmlOutgoing = 0x040000, /**< Outgoing XML. */ + LogAreaUser = 0x800000, /**< User-defined sources. */ + LogAreaAll = 0xFFFFFF /**< All log sources. */ + }; + + /** + * Describes a log message's severity. + */ + enum LogLevel + { + LogLevelDebug, /**< Debug messages. */ + LogLevelWarning, /**< Non-crititcal warning messages. */ + LogLevelError /**< Critical, unrecoverable errors. */ + }; + + /** + * The possible Message Events according to XEP-0022. + */ + enum MessageEventType + { + MessageEventOffline = 1, /**< Indicates that the message has been stored offline by the + * intended recipient's server. */ + MessageEventDelivered = 2, /**< Indicates that the message has been delivered to the + * recipient. */ + MessageEventDisplayed = 4, /**< Indicates that the message has been displayed */ + MessageEventComposing = 8, /**< Indicates that a reply is being composed. */ + MessageEventInvalid = 16, /**< Invalid type. */ + MessageEventCancel = 32 /**< Cancels the 'Composing' event. */ + }; + + /** + * The possible Chat States according to XEP-0085. + */ + enum ChatStateType + { + ChatStateActive = 1, /**< User is actively participating in the chat session. */ + ChatStateComposing = 2, /**< User is composing a message. */ + ChatStatePaused = 4, /**< User had been composing but now has stopped. */ + ChatStateInactive = 8, /**< User has not been actively participating in the chat session. */ + ChatStateGone = 16, /**< User has effectively ended their participation in the chat + * session. */ + ChatStateInvalid = 32 /**< Invalid type. */ + }; + + /** + * Describes the possible error conditions for resource binding. + */ + enum ResourceBindError + { + RbErrorUnknownError, /**< An unknown error occured. */ + RbErrorBadRequest, /**< Resource identifier cannot be processed. */ + RbErrorNotAllowed, /**< Client is not allowed to bind a resource. */ + RbErrorConflict /**< Resource identifier is in use. */ + }; + + /** + * Describes the possible error conditions for session establishemnt. + */ + enum SessionCreateError + { + ScErrorUnknownError, /**< An unknown error occured. */ + ScErrorInternalServerError, /**< Internal server error. */ + ScErrorForbidden, /**< Username or resource not allowed to create session. */ + ScErrorConflict /**< Server informs newly-requested session of resource + * conflict. */ + }; + + /** + * Currently implemented message session filters. + */ + enum MessageSessionFilter + { + FilterMessageEvents = 1, /**< Message Events (XEP-0022) */ + FilterChatStates = 2 /**< Chat State Notifications (XEP-0085) */ + }; + + /** + * Defined MUC room affiliations. See XEP-0045 for default privileges. + */ + enum MUCRoomAffiliation + { + AffiliationNone, /**< No affiliation with the room. */ + AffiliationOutcast, /**< The user has been banned from the room. */ + AffiliationMember, /**< The user is a member of the room. */ + AffiliationOwner, /**< The user is a room owner. */ + AffiliationAdmin, /**< The user is a room admin. */ + AffiliationInvalid /**< Invalid affiliation. */ + }; + + /** + * Defined MUC room roles. See XEP-0045 for default privileges. + */ + enum MUCRoomRole + { + RoleNone, /**< Not present in room. */ + RoleVisitor, /**< The user visits a room. */ + RoleParticipant, /**< The user has voice in a moderatd room. */ + RoleModerator, /**< The user is a room moderator. */ + RoleInvalid /**< Invalid role. */ + }; + + /** + * Configuration flags for a room. + */ + enum MUCRoomFlag + { + FlagPasswordProtected = 1<< 1, /**< Password-protected room. */ + FlagPublicLogging = 1<< 2, /**< Room conversation is logged. Code: 170 */ + FlagPublicLoggingOff = 1<< 3, /**< Room conversation is not logged. Code: 171 */ + FlagHidden = 1<< 4, /**< Hidden room. */ + FlagMembersOnly = 1<< 5, /**< Members-only room. */ + FlagModerated = 1<< 6, /**< Moderated room. */ + FlagNonAnonymous = 1<< 7, /**< Non-anonymous room. Code: 100, 172 */ + FlagOpen = 1<< 8, /**< Open room. */ + FlagPersistent = 1<< 9, /**< Persistent room .*/ + FlagPublic = 1<<10, /**< Public room. */ + FlagSemiAnonymous = 1<<11, /**< Semi-anonymous room. Code: 173 */ + FlagTemporary = 1<<12, /**< Temporary room. */ + FlagUnmoderated = 1<<13, /**< Unmoderated room. */ + FlagUnsecured = 1<<14, /**< Unsecured room. */ + FlagFullyAnonymous = 1<<15 /**< Fully anonymous room. Code: 174 */ + // keep in sync with MUCUserFlag below + }; + + /** + * Configuration flags for a user. + */ + // keep in sync with MUCRoomFlag above + enum MUCUserFlag + { + UserSelf = 1<<16, /**< Other flags relate to the current user him/herself. Code: 110 */ + UserNickChanged = 1<<17, /**< The user changed his/her nickname. Code: 303 */ + UserKicked = 1<<18, /**< The user has been kicked. Code: 307 */ + UserBanned = 1<<19, /**< The user has been banned. Code: 301 */ + UserAffiliationChanged = 1<<20, /**< The user's affiliation with the room changed and as a result + * he/she has been removed from the room. Code: 321 */ + UserRoomDestroyed = 1<<21, /**< The room has been destroyed. */ + UserNickAssigned = 1<<22, /**< Service has assigned or modified occupant's roomnick. + * Code: 210*/ + UserNewRoom = 1<<23, /**< The room has been newly created. Code: 201*/ + UserMembershipRequired = 1<<24, /**< User is being removed from the room because the room has + * been changed to members-only and the user is not a member. + * Code: 322 */ + UserRoomShutdown = 1<<25, /**< User is being removed from the room because of a system + * shutdown. Code: 332 */ + UserAffiliationChangedWNR = 1<<26 /**< The user's affiliation changed While Not in the Room. + * Code: 101 */ + }; + + /** + * Describes possible subscription types according to RFC 3921, Section 9. + */ + enum SubscriptionType + { + S10nNone, /**< Contact and user are not subscribed to each other, and + * neither has requested a subscription from the other. */ + S10nNoneOut, /**< Contact and user are not subscribed to each other, and + * user has sent contact a subscription request but contact + * has not replied yet. */ + S10nNoneIn, /**< Contact and user are not subscribed to each other, and + * contact has sent user a subscription request but user has + * not replied yet (note: contact's server SHOULD NOT push or + * deliver roster items in this state, but instead SHOULD wait + * until contact has approved subscription request from user). */ + S10nNoneOutIn, /**< Contact and user are not subscribed to each other, contact + * has sent user a subscription request but user has not replied + * yet, and user has sent contact a subscription request but + * contact has not replied yet. */ + S10nTo, /**< User is subscribed to contact (one-way). */ + S10nToIn, /**< User is subscribed to contact, and contact has sent user a + * subscription request but user has not replied yet. */ + S10nFrom, /**< Contact is subscribed to user (one-way). */ + S10nFromOut, /**< Contact is subscribed to user, and user has sent contact a + * subscription request but contact has not replied yet. */ + S10nBoth /**< User and contact are subscribed to each other (two-way). */ + }; + + /** + * A list of strings. + */ + typedef std::list StringList; + + /** + * A list of pointers to strings. + */ + typedef std::list StringPList; + + /** + * A map of strings. + */ + typedef std::map StringMap; + + /** + * A multimap of strings. + */ + typedef std::multimap StringMultiMap; + + class StanzaExtension; + /** + * A list of StanzaExtensions. + */ + typedef std::list StanzaExtensionList; +} + +extern "C" +{ + GLOOX_API const char* gloox_version(); +} + +#endif // GLOOX_H__ diff --git a/libs/libgloox/glooxversion.h b/libs/libgloox/glooxversion.h new file mode 100644 index 0000000..9b4b282 --- /dev/null +++ b/libs/libgloox/glooxversion.h @@ -0,0 +1,13 @@ +/* + Copyright (c) 2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#define GLOOXVERSION 0x010000 diff --git a/libs/libgloox/gpgencrypted.cpp b/libs/libgloox/gpgencrypted.cpp new file mode 100644 index 0000000..95e1ab0 --- /dev/null +++ b/libs/libgloox/gpgencrypted.cpp @@ -0,0 +1,60 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "gpgencrypted.h" +#include "tag.h" + +namespace gloox +{ + + GPGEncrypted::GPGEncrypted( const std::string& encrypted ) + : StanzaExtension( ExtGPGEncrypted ), + m_encrypted( encrypted ), m_valid( true ) + { + if( m_encrypted.empty() ) + m_valid = false; + } + + GPGEncrypted::GPGEncrypted( const Tag* tag ) + : StanzaExtension( ExtGPGEncrypted ), + m_valid( false ) + { + if( tag && tag->name() == "x" && tag->hasAttribute( XMLNS, XMLNS_X_GPGENCRYPTED ) ) + { + m_valid = true; + m_encrypted = tag->cdata(); + } + } + + GPGEncrypted::~GPGEncrypted() + { + } + + const std::string& GPGEncrypted::filterString() const + { + static const std::string filter = "/message/x[@xmlns='" + XMLNS_X_GPGENCRYPTED + "']"; + return filter; + } + + Tag* GPGEncrypted::tag() const + { + if( !m_valid ) + return 0; + + Tag* x = new Tag( "x", m_encrypted ); + x->addAttribute( XMLNS, XMLNS_X_GPGENCRYPTED ); + + return x; + } + +} diff --git a/libs/libgloox/gpgencrypted.h b/libs/libgloox/gpgencrypted.h new file mode 100644 index 0000000..f6e16df --- /dev/null +++ b/libs/libgloox/gpgencrypted.h @@ -0,0 +1,91 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef GPGENCRYPTED_H__ +#define GPGENCRYPTED_H__ + +#include "gloox.h" +#include "stanzaextension.h" + +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief This is an abstraction of a jabber:x:encrypted namespace element, as used in XEP-0027 + * (Current Jabber OpenPGP Usage). + * + * This class does not encrypt or decrypt any stanza content. It's meant to be an abstraction + * of the XML representation only. + * + * XEP version: 1.3 + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API GPGEncrypted : public StanzaExtension + { + public: + /** + * Constructs a new object with the given encrypted message. + * @param encrypted The encrypted message. + */ + GPGEncrypted( const std::string& encrypted ); + + /** + * Constructs an GPGEncrypted object from the given Tag. To be recognized properly, the Tag should + * have a name of 'x' in the @c jabber:x:encrypted namespace. + * @param tag The Tag to parse. + */ + GPGEncrypted( const Tag* tag ); + + /** + * Virtual destructor. + */ + virtual ~GPGEncrypted(); + + /** + * Returns the encrypted message. + * @return The encrypted message. + */ + const std::string& encrypted() const { return m_encrypted; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new GPGEncrypted( tag ); + } + + // reimplemented from StanzaExtension + Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new GPGEncrypted( *this ); + } + + private: + std::string m_encrypted; + bool m_valid; + + }; + +} + +#endif // GPGENCRYPTED_H__ diff --git a/libs/libgloox/gpgsigned.cpp b/libs/libgloox/gpgsigned.cpp new file mode 100644 index 0000000..bcc40ce --- /dev/null +++ b/libs/libgloox/gpgsigned.cpp @@ -0,0 +1,62 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "gpgsigned.h" +#include "tag.h" + +namespace gloox +{ + + GPGSigned::GPGSigned( const std::string& signature ) + : StanzaExtension( ExtGPGSigned ), + m_signature( signature ), m_valid( true ) + { + if( m_signature.empty() ) + m_valid = false; + } + + GPGSigned::GPGSigned( const Tag* tag ) + : StanzaExtension( ExtGPGSigned ), + m_valid( false ) + { + if( tag && tag->name() == "x" && tag->hasAttribute( XMLNS, XMLNS_X_GPGSIGNED ) ) + { + m_valid = true; + m_signature = tag->cdata(); + } + } + + GPGSigned::~GPGSigned() + { + } + + const std::string& GPGSigned::filterString() const + { + static const std::string filter = + "/presence/x[@xmlns='" + XMLNS_X_GPGSIGNED + "']" + "|/message/x[@xmlns='" + XMLNS_X_GPGSIGNED + "']"; + return filter; + } + + Tag* GPGSigned::tag() const + { + if( !m_valid ) + return 0; + + Tag* x = new Tag( "x", m_signature ); + x->addAttribute( XMLNS, XMLNS_X_GPGSIGNED ); + + return x; + } + +} diff --git a/libs/libgloox/gpgsigned.h b/libs/libgloox/gpgsigned.h new file mode 100644 index 0000000..93eb7eb --- /dev/null +++ b/libs/libgloox/gpgsigned.h @@ -0,0 +1,91 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef GPGSIGNED_H__ +#define GPGSIGNED_H__ + +#include "gloox.h" +#include "stanzaextension.h" + +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief This is an abstraction of a jabber:x:signed namespace element, as used in XEP-0027 + * (Current Jabber OpenPGP Usage). + * + * This class does not sign or verify any stanza content. It's meant to be an abstraction + * of the XML representation only. + * + * XEP version: 1.3 + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API GPGSigned : public StanzaExtension + { + public: + /** + * Constructs a new object with the given signature. + * @param signature The signature. + */ + GPGSigned( const std::string& signature ); + + /** + * Constructs an GPGSigned object from the given Tag. To be recognized properly, the Tag should + * have a name of 'x' in the @c jabber:x:signed namespace. + * @param tag The Tag to parse. + */ + GPGSigned( const Tag* tag ); + + /** + * Virtual destructor. + */ + virtual ~GPGSigned(); + + /** + * Returns the signature. + * @return The signature. + */ + const std::string& signature() const { return m_signature; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new GPGSigned( tag ); + } + + // reimplemented from StanzaExtension + Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new GPGSigned( *this ); + } + + private: + std::string m_signature; + bool m_valid; + + }; + +} + +#endif // GPGSIGNED_H__ diff --git a/libs/libgloox/inbandbytestream.cpp b/libs/libgloox/inbandbytestream.cpp new file mode 100644 index 0000000..cf6254d --- /dev/null +++ b/libs/libgloox/inbandbytestream.cpp @@ -0,0 +1,297 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "inbandbytestream.h" +#include "base64.h" +#include "bytestreamdatahandler.h" +#include "disco.h" +#include "clientbase.h" +#include "error.h" +#include "message.h" +#include "util.h" + +#include + +namespace gloox +{ + + // ---- InBandBytestream::IBB ---- + static const char* typeValues[] = + { + "open", "data", "close" + }; + + InBandBytestream::IBB::IBB( const std::string& sid, int blocksize ) + : StanzaExtension( ExtIBB ), m_sid ( sid ), m_seq( 0 ), m_blockSize( blocksize ), + m_type( IBBOpen ) + { + } + + InBandBytestream::IBB::IBB( const std::string& sid, int seq, const std::string& data ) + : StanzaExtension( ExtIBB ), m_sid ( sid ), m_seq( seq ), m_blockSize( 0 ), + m_data( data ), m_type( IBBData ) + { + } + + InBandBytestream::IBB::IBB( const std::string& sid ) + : StanzaExtension( ExtIBB ), m_sid ( sid ), m_seq( 0 ), m_blockSize( 0 ), + m_type( IBBClose ) + { + } + + InBandBytestream::IBB::IBB( const Tag* tag ) + : StanzaExtension( ExtIBB ), m_type( IBBInvalid ) + { + if( !tag || tag->xmlns() != XMLNS_IBB ) + return; + + m_type = (IBBType)util::lookup( tag->name(), typeValues ); + m_blockSize = atoi( tag->findAttribute( "block-size" ).c_str() ); + m_seq = atoi( tag->findAttribute( "seq" ).c_str() ); + m_sid = tag->findAttribute( "sid" ); + m_data = Base64::decode64( tag->cdata() ); + } + + InBandBytestream::IBB::~IBB() + { + } + + const std::string& InBandBytestream::IBB::filterString() const + { + static const std::string filter = "/iq/open[@xmlns='" + XMLNS_IBB + "']" + "|/iq/data[@xmlns='" + XMLNS_IBB + "']" + "|/message/data[@xmlns='" + XMLNS_IBB + "']" + "|/iq/close[@xmlns='" + XMLNS_IBB + "']"; + return filter; + } + + Tag* InBandBytestream::IBB::tag() const + { + if( m_type == IBBInvalid ) + return 0; + + Tag* t = new Tag( util::lookup( m_type, typeValues ) ); + t->setXmlns( XMLNS_IBB ); + t->addAttribute( "sid", m_sid ); + if( m_type == IBBData ) + { + t->setCData( Base64::encode64( m_data ) ); + t->addAttribute( "seq", m_seq ); + } + else if( m_type == IBBOpen ) + t->addAttribute( "block-size", m_blockSize ); + + return t; + } + // ---- ~InBandBytestream::IBB ---- + + // ---- InBandBytestream ---- + InBandBytestream::InBandBytestream( ClientBase* clientbase, LogSink& logInstance, const JID& initiator, + const JID& target, const std::string& sid ) + : Bytestream( Bytestream::IBB, logInstance, initiator, target, sid ), + m_clientbase( clientbase ), m_blockSize( 4096 ), m_sequence( -1 ), m_lastChunkReceived( -1 ) + { + if( m_clientbase ) + { + m_clientbase->registerStanzaExtension( new IBB() ); + m_clientbase->registerIqHandler( this, ExtIBB ); + m_clientbase->registerMessageHandler( this ); + } + + m_open = false; + } + + InBandBytestream::~InBandBytestream() + { + if( m_open ) + close(); + + if( m_clientbase ) + { + m_clientbase->removeMessageHandler( this ); + m_clientbase->removeIqHandler( this, ExtIBB ); + m_clientbase->removeIDHandler( this ); + } + } + + bool InBandBytestream::connect() + { + if( !m_clientbase ) + return false; + + if( m_target == m_clientbase->jid() ) + return true; + + const std::string& id = m_clientbase->getID(); + IQ iq( IQ::Set, m_target, id ); + iq.addExtension( new IBB( m_sid, m_blockSize ) ); + m_clientbase->send( iq, this, IBBOpen ); + return true; + } + + void InBandBytestream::handleIqID( const IQ& iq, int context ) + { + switch( iq.subtype() ) + { + case IQ::Result: + if( context == IBBOpen && m_handler ) + { + m_handler->handleBytestreamOpen( this ); + m_open = true; + } + break; + case IQ::Error: + closed(); + break; + default: + break; + } + } + + bool InBandBytestream::handleIq( const IQ& iq ) // data or open request, always 'set' + { + const IBB* i = iq.findExtension( ExtIBB ); + if( !i || !m_handler || iq.subtype() != IQ::Set ) + return false; + + if( !m_open ) + { + if( i->type() == IBBOpen ) + { + returnResult( iq.from(), iq.id() ); + m_open = true; + m_handler->handleBytestreamOpen( this ); + return true; + } + return false; + } + + if( i->type() == IBBClose ) + { + returnResult( iq.from(), iq.id() ); + closed(); + return true; + } + + if( ( m_lastChunkReceived + 1 ) != i->seq() ) + { + m_open = false; + returnError( iq.from(), iq.id(), StanzaErrorTypeModify, StanzaErrorItemNotFound ); + return false; + } + + if( i->data().empty() ) + { + m_open = false; + returnError( iq.from(), iq.id(), StanzaErrorTypeModify, StanzaErrorBadRequest ); + return false; + } + + returnResult( iq.from(), iq.id() ); + m_handler->handleBytestreamData( this, i->data() ); + m_lastChunkReceived++; + return true; + } + + void InBandBytestream::handleMessage( const Message& msg, MessageSession* /*session*/ ) + { + if( msg.from() != m_target || !m_handler ) + return; + + const IBB* i = msg.findExtension( ExtIBB ); + if( !i ) + return; + + if( !m_open ) + return; + + if( m_lastChunkReceived != i->seq() ) + { + m_open = false; + return; + } + + if( i->data().empty() ) + { + m_open = false; + return; + } + + m_handler->handleBytestreamData( this, i->data() ); + m_lastChunkReceived++; + } + + void InBandBytestream::returnResult( const JID& to, const std::string& id ) + { + IQ iq( IQ::Result, to, id ); + m_clientbase->send( iq ); + } + + void InBandBytestream::returnError( const JID& to, const std::string& id, StanzaErrorType type, StanzaError error ) + { + IQ iq( IQ::Error, to, id ); + iq.addExtension( new Error( type, error ) ); + m_clientbase->send( iq ); + } + + bool InBandBytestream::send( const std::string& data ) + { + if( !m_open || !m_clientbase ) + return false; + + size_t pos = 0; + size_t len = data.length(); + do + { + const std::string& id = m_clientbase->getID(); + IQ iq( IQ::Set, m_target, id ); + iq.addExtension( new IBB( m_sid, ++m_sequence, data.substr( pos, m_blockSize ) ) ); + m_clientbase->send( iq, this, IBBData ); + + pos += m_blockSize; + if( m_sequence == 65535 ) + m_sequence = -1; + } + while( pos < len ); + + return true; + } + + void InBandBytestream::closed() + { + if( !m_open ) + return; + + m_open = false; + + if( m_handler ) + m_handler->handleBytestreamClose( this ); + } + + void InBandBytestream::close() + { + m_open = false; + + if( !m_clientbase ) + return; + + const std::string& id = m_clientbase->getID(); + IQ iq( IQ::Set, m_target, id ); + iq.addExtension( new IBB( m_sid ) ); + m_clientbase->send( iq, this, IBBClose ); + + if( m_handler ) + m_handler->handleBytestreamClose( this ); + } + +} diff --git a/libs/libgloox/inbandbytestream.h b/libs/libgloox/inbandbytestream.h new file mode 100644 index 0000000..9a6267a --- /dev/null +++ b/libs/libgloox/inbandbytestream.h @@ -0,0 +1,214 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef INBANDBYTESTREAM_H__ +#define INBANDBYTESTREAM_H__ + +#include "bytestream.h" +#include "iqhandler.h" +#include "messagehandler.h" +#include "gloox.h" + +namespace gloox +{ + + class BytestreamDataHandler; + class ClientBase; + class Message; + + /** + * @brief An implementation of a single In-Band Bytestream (XEP-0047). + * + * One instance of this class handles a single byte stream. + * + * See SIProfileFT for a detailed description on how to implement file transfer. + * + * @note This class can @b receive data wrapped in Message stanzas. This will only work if you + * are not using MessageSessions. However, it will always send + * data using IQ stanzas (which will always work). + * + * @author Jakob Schroeter + * @since 0.8 + */ + class GLOOX_API InBandBytestream : public Bytestream, public IqHandler, public MessageHandler + { + + friend class SIProfileFT; + + public: + /** + * Virtual destructor. + */ + virtual ~InBandBytestream(); + + /** + * Lets you retrieve this bytestream's block-size. + * @return The bytestream's block-size. + */ + int blockSize() const { return m_blockSize; } + + /** + * Sets the stream's block-size. Default: 4096 + * @param blockSize The new block size. + * @note You should not change the block size once connect() has been called. + */ + void setBlockSize( int blockSize ) { m_blockSize = blockSize; } + + // reimplemented from Bytestream + virtual ConnectionError recv( int timeout = -1 ) { (void)timeout; return ConnNoError; } + + // reimplemented from Bytestream + bool send( const std::string& data ); + + // reimplemented from Bytestream + virtual bool connect(); + + // reimplemented from Bytestream + virtual void close(); + + // reimplemented from IqHandler + virtual bool handleIq( const IQ& iq ); + + // reimplemented from IqHandler + virtual void handleIqID( const IQ& iq, int context ); + + // reimplemented from MessageHandler + virtual void handleMessage( const Message& msg, MessageSession* session = 0 ); + + private: +#ifdef INBANDBYTESTREAM_TEST + public: +#endif + enum IBBType + { + IBBOpen, + IBBData, + IBBClose, + IBBInvalid + }; + + /** + * @brief An abstraction of IBB elements, implemented as as StanzaExtension. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class IBB : public StanzaExtension + { + public: + /** + * Constructs a new IBB object that opens an IBB, using the given SID and block size. + * @param sid The SID of the IBB to open. + * @param blocksize The streams block size. + */ + IBB( const std::string& sid, int blocksize ); + + /** + * Constructs a new IBB object that can be used to send a single block of data, + * using the given SID and sequence number. + * @param sid The SID of the IBB. + * @param seq The block's sequence number. + * @param data The block data, not base64 encoded. + */ + IBB( const std::string& sid, int seq, const std::string& data ); + + /** + * Constructs a new IBB object that closes an IBB, using the given SID. + * @param sid The SID of the IBB to close. + */ + IBB( const std::string& sid ); + + /** + * Constructs a new IBB object from the given Tag. + * @param tag The Tag to parse. + */ + IBB( const Tag* tag = 0 ); + + /** + * Virtual destructor. + */ + virtual ~IBB(); + + /** + * Returns the IBB's type. + * @return The IBB's type. + */ + IBBType type() const { return m_type; } + + /** + * Returns the IBB's block size. Only meaningful if the IBB is of type() IBBOpen. + * @return The IBB's block size. + */ + int blocksize() const { return m_blockSize; } + + /** + * Returns the current block's sequence number. + * @return The current block's sequence number. + */ + int seq() const { return m_seq; } + + /** + * Returns the current block's SID. + * @return The current block's SID. + */ + const std::string sid() const { return m_sid; } + + /** + * Returns the current block's data (not base64 encoded). + * @return The current block's data. + */ + const std::string& data() const { return m_data; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new IBB( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new IBB( *this ); + } + + private: + std::string m_sid; + int m_seq; + int m_blockSize; + std::string m_data; + IBBType m_type; + }; + + InBandBytestream( ClientBase* clientbase, LogSink& logInstance, const JID& initiator, + const JID& target, const std::string& sid ); + InBandBytestream& operator=( const InBandBytestream& ); + void closed(); // by remote entity + void returnResult( const JID& to, const std::string& id ); + void returnError( const JID& to, const std::string& id, StanzaErrorType type, StanzaError error ); + + ClientBase* m_clientbase; + int m_blockSize; + int m_sequence; + int m_lastChunkReceived; + + }; + +} + +#endif // INBANDBYTESTREAM_H__ diff --git a/libs/libgloox/instantmucroom.cpp b/libs/libgloox/instantmucroom.cpp new file mode 100644 index 0000000..3303a2b --- /dev/null +++ b/libs/libgloox/instantmucroom.cpp @@ -0,0 +1,31 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "instantmucroom.h" +#include "clientbase.h" +#include "jid.h" + +namespace gloox +{ + + InstantMUCRoom::InstantMUCRoom( ClientBase* parent, const JID& nick, MUCRoomHandler* mrh ) + : MUCRoom( parent, nick, mrh, 0 ) + { + } + + InstantMUCRoom::~InstantMUCRoom() + { + } + +} diff --git a/libs/libgloox/instantmucroom.h b/libs/libgloox/instantmucroom.h new file mode 100644 index 0000000..88462ba --- /dev/null +++ b/libs/libgloox/instantmucroom.h @@ -0,0 +1,60 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef INSTANTMUCROOM_H__ +#define INSTANTMUCROOM_H__ + +#include "mucroom.h" + +namespace gloox +{ + + /** + * @brief This class implements an instant MUC room. + * + * XEP version: 1.21 + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API InstantMUCRoom : public MUCRoom + { + public: + /** + * Creates a new abstraction of a @b unique Multi-User Chat room. The room is not joined + * automatically. Use join() to join the room, use leave() to leave it. See MUCRoom for + * detailed info. + * @param parent The ClientBase object to use for the communication. + * @param nick The room's name and service plus the desired nickname in the form + * room\@service/nick. + * @param mrh The MUCRoomHandler that will listen to room events. May be 0 and may be specified + * later using registerMUCRoomHandler(). However, without one, MUC is no joy. + * @note To subsequently configure the room, use MUCRoom::registerMUCRoomConfigHandler(). + */ + InstantMUCRoom( ClientBase* parent, const JID& nick, MUCRoomHandler* mrh ); + + /** + * Virtual Destructor. + */ + virtual ~InstantMUCRoom(); + + protected: + // reimplemented from MUCRoom (acknowledges instant room creation w/o a + // call to the MUCRoomConfigHandler) + virtual bool instantRoomHook() const { return true; } + + }; + +} + +#endif // INSTANTMUCROOM_H__ diff --git a/libs/libgloox/iq.cpp b/libs/libgloox/iq.cpp new file mode 100644 index 0000000..e33e971 --- /dev/null +++ b/libs/libgloox/iq.cpp @@ -0,0 +1,69 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "iq.h" +#include "util.h" + +namespace gloox +{ + + static const char * iqTypeStringValues[] = + { + "get", "set", "result", "error" + }; + + static inline const char* typeString( IQ::IqType type ) + { + return iqTypeStringValues[type]; + } + + IQ::IQ( Tag* tag ) + : Stanza( tag ), m_subtype( Invalid ) + { + if( !tag || tag->name() != "iq" ) + return; + + m_subtype = (IQ::IqType)util::lookup( tag->findAttribute( TYPE ), iqTypeStringValues ); + } + + IQ::IQ( IqType type, const JID& to, const std::string& id ) + : Stanza( to ), m_subtype( type ) + { + m_id = id; + } + + IQ::~IQ() + { + } + + Tag* IQ::tag() const + { + if( m_subtype == Invalid ) + return 0; + + Tag* t = new Tag( "iq" ); + if( m_to ) + t->addAttribute( "to", m_to.full() ); + if( m_from ) + t->addAttribute( "from", m_from.full() ); + if( !m_id.empty() ) + t->addAttribute( "id", m_id ); + t->addAttribute( TYPE, typeString( m_subtype ) ); + + StanzaExtensionList::const_iterator it = m_extensionList.begin(); + for( ; it != m_extensionList.end(); ++it ) + t->addChild( (*it)->tag() ); + + return t; + } + +} diff --git a/libs/libgloox/iq.h b/libs/libgloox/iq.h new file mode 100644 index 0000000..7270531 --- /dev/null +++ b/libs/libgloox/iq.h @@ -0,0 +1,96 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef IQ_H__ +#define IQ_H__ + +#include "stanza.h" +#include "gloox.h" + +#include + +namespace gloox +{ + + class JID; + + /** + * @brief An abstraction of an IQ stanza. + * + * @author Vincent Thomasset + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API IQ : public Stanza + { + + friend class ClientBase; + + public: + + /** + * Describes the different valid IQ types. + */ + enum IqType + { + Get = 0, /**< The stanza is a request for information or requirements. */ + Set, /**< The stanza provides required data, sets new values, or + * replaces existing values. */ + Result, /**< The stanza is a response to a successful get or set request. */ + Error, /**< An error has occurred regarding processing or delivery of a + * previously-sent get or set (see Stanza Errors (Section 9.3)). */ + Invalid /**< The stanza is invalid */ + }; + + /** + * Creates an IQ Query. + * @param type The desired IqType. + * @param to The intended receiver. + * @param id The request's ID. Usually obtained from ClientBase::getID(). Optional, + * will be added by ClientBase if the IQ is sent by means of + * @link gloox::ClientBase::send( IQ&, IqHandler*, int, bool ) send( IQ&, IqHandler*, int, bool ) @endlink. + * You should only need to pass this when creating a reply (i.e. an IQ of type Result or Error). + */ + IQ( IqType type, const JID& to, const std::string& id = EmptyString ); + + /** + * Virtual destructor. + */ + virtual ~IQ(); + + /** + * Returns the IQ's type. + * @return The IQ's type. + */ + IqType subtype() const { return m_subtype; } + + // reimplemented from Stanza + virtual Tag* tag() const; + + private: +#ifdef IQ_TEST + public: +#endif + /** + * Creates an IQ from a tag. The original Tag will be ripped off. + * @param tag The Tag to parse. + */ + IQ( Tag* tag ); + + void setID( const std::string& id ) { m_id = id; } + + IqType m_subtype; + }; + +} + +#endif // IQ_H__ diff --git a/libs/libgloox/iqhandler.h b/libs/libgloox/iqhandler.h new file mode 100644 index 0000000..809e36f --- /dev/null +++ b/libs/libgloox/iqhandler.h @@ -0,0 +1,66 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef IQHANDLER_H__ +#define IQHANDLER_H__ + +#include "iq.h" + +namespace gloox +{ + + /** + * @brief A virtual interface which can be reimplemented to receive IQ stanzas. + * + * Derived classes can be registered as IqHandlers with the Client. + * Upon an incoming IQ packet @ref handleIq() will be called. + * + * @author Jakob Schroeter + */ + class GLOOX_API IqHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~IqHandler() {} + + /** + * Reimplement this function if you want to be notified about incoming IQs. + * @param iq The complete IQ stanza. + * @return Indicates whether a request of type 'get' or 'set' has been handled. This includes + * the obligatory 'result' answer. If you return @b false, a 'error' will be sent. + * @since 1.0 + */ + virtual bool handleIq( const IQ& iq ) = 0; + + /** + * Reimplement this function if you want to be notified about + * incoming IQs with a specific value of the @c id attribute. You + * have to enable tracking of those IDs using Client::trackID(). + * This is usually useful for IDs that generate a positive reply, i.e. + * <iq type='result' id='reg'/> where a namespace filter wouldn't + * work. + * @param iq The complete IQ stanza. + * @param context A value to restore context, stored with ClientBase::trackID(). + * @note Only IQ stanzas of type 'result' or 'error' can arrive here. + * @since 1.0 + */ + virtual void handleIqID( const IQ& iq, int context ) = 0; + + }; + +} + +#endif // IQHANDLER_H__ diff --git a/libs/libgloox/jid.cpp b/libs/libgloox/jid.cpp new file mode 100644 index 0000000..d91396e --- /dev/null +++ b/libs/libgloox/jid.cpp @@ -0,0 +1,123 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "jid.h" +#include "prep.h" +#include "gloox.h" +#include "util.h" + +namespace gloox +{ + + bool JID::setJID( const std::string& jid ) + { + if ( jid.empty() ) + { + m_bare = m_full = m_server = m_username = m_serverRaw = m_resource = EmptyString; + m_valid = false; + return false; + } + + const std::string::size_type at = jid.find( '@' ); + const std::string::size_type slash = jid.find( '/', at == std::string::npos ? 0 : at ); + + if( at != std::string::npos && !( m_valid = prep::nodeprep( jid.substr( 0, at ), m_username ) ) ) + return false; + + m_serverRaw = jid.substr( at == std::string::npos ? 0 : at + 1, slash - at - 1 ); + if( !( m_valid = prep::nameprep( m_serverRaw, m_server ) ) ) + return false; + + if( slash != std::string::npos + && !( m_valid = prep::resourceprep( jid.substr( slash + 1 ), m_resource ) ) ) + return false; + + setStrings(); + + return m_valid; + } + + bool JID::setUsername( const std::string& uname ) + { + m_valid = prep::nodeprep( uname, m_username ); + setStrings(); + return m_valid; + } + + bool JID::setServer( const std::string& serv ) + { + m_serverRaw = serv; + m_valid = prep::nameprep( m_serverRaw, m_server ); + setStrings(); + return m_valid; + } + + bool JID::setResource( const std::string& res ) + { + m_valid = prep::resourceprep( res, m_resource ); + setFull(); + return m_valid; + } + + void JID::setFull() + { + m_full = bare(); + if( !m_resource.empty() ) + m_full += '/' + m_resource; + } + + void JID::setBare() + { + if( !m_username.empty() ) + m_bare = m_username + '@'; + else + m_bare = EmptyString; + m_bare += m_server; + } + + std::string JID::escapeNode( const std::string& node ) + { + std::string escaped = node; + + util::replaceAll( escaped, "\\", "\\5c" ); + util::replaceAll( escaped, " ", "\\20" ); + util::replaceAll( escaped, "\"", "\\22" ); + util::replaceAll( escaped, "&", "\\26" ); + util::replaceAll( escaped, "'", "\\27" ); + util::replaceAll( escaped, "/", "\\2f" ); + util::replaceAll( escaped, ":", "\\3a" ); + util::replaceAll( escaped, "<", "\\3c" ); + util::replaceAll( escaped, ">", "\\3e" ); + util::replaceAll( escaped, "@", "\\40" ); + + return escaped; + } + + std::string JID::unescapeNode( const std::string& node ) + { + std::string unescaped = node; + + util::replaceAll( unescaped, "\\20", " " ); + util::replaceAll( unescaped, "\\22", "\"" ); + util::replaceAll( unescaped, "\\26", "&" ); + util::replaceAll( unescaped, "\\27", "'" ); + util::replaceAll( unescaped, "\\2f", "/" ); + util::replaceAll( unescaped, "\\3a", ":" ); + util::replaceAll( unescaped, "\\3c", "<" ); + util::replaceAll( unescaped, "\\3e", ">" ); + util::replaceAll( unescaped, "\\40", "@" ); + util::replaceAll( unescaped, "\\5c", "\\" ); + + return unescaped; + } + +} diff --git a/libs/libgloox/jid.h b/libs/libgloox/jid.h new file mode 100644 index 0000000..841f6c6 --- /dev/null +++ b/libs/libgloox/jid.h @@ -0,0 +1,190 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef JID_H__ +#define JID_H__ + +#include "macros.h" + +#include + +namespace gloox +{ + /** + * @brief An abstraction of a JID. + * + * @author Jakob Schroeter + * @since 0.4 + */ + class GLOOX_API JID + { + public: + + /** + * Constructs an empty JID. + */ + JID() : m_valid( false ) {} + + /** + * Constructs a new JID from a string. + * @param jid The string containing the JID. + */ + JID( const std::string& jid ) : m_valid( true ) { setJID( jid ); } + + /** + * Destructor. + */ + ~JID() {} + + /** + * Sets the JID from a string. + * @param jid The string containing the JID. + * @return @b True if the given JID was valid, @b false otherwise. + */ + bool setJID( const std::string& jid ); + + /** + * Returns the full (prepped) JID (user\@host/resource). + * @return The full JID. + */ + const std::string& full() const { return m_full; } + + /** + * Returns the bare (prepped) JID (user\@host). + * @return The bare JID. + */ + const std::string& bare() const { return m_bare; } + + /** + * Creates and returns a JID from this JID's node and server parts. + * @return The bare JID. + * @since 0.9 + */ + JID bareJID() const { return JID( bare() ); } + + /** + * Sets the username. + * @param username The new username. + */ + bool setUsername( const std::string& username ); + + /** + * Sets the server. + * @param server The new server. + */ + bool setServer( const std::string& server ); + + /** + * Sets the resource. + * @param resource The new resource. + */ + bool setResource( const std::string& resource ); + + /** + * Returns the prepped username. + * @return The current username. + */ + const std::string& username() const { return m_username; } + + /** + * Returns the prepped server name. + * @return The current server. + */ + const std::string& server() const { return m_server; } + + /** + * Returns the raw (unprepped) server name. + * @return The raw server name. + */ + const std::string& serverRaw() const { return m_serverRaw; } + + /** + * Returns the prepped resource. + * @return The current resource. + */ + const std::string& resource() const { return m_resource; } + + /** + * Compares a JID with a string. + * @param right The second JID in string representation. + */ + bool operator==( const std::string& right ) const { return full() == right; } + + /** + * Compares a JID with a string. + * @param right The second JID in string representation. + */ + bool operator!=( const std::string& right ) const { return full() != right; } + + /** + * Compares two JIDs. + * @param right The second JID. + */ + bool operator==( const JID& right ) const { return full() == right.full(); } + + /** + * Compares two JIDs. + * @param right The second JID. + */ + bool operator!=( const JID& right ) const { return full() != right.full(); } + + /** + * Converts to @b true if the JID is valid, @b false otherwise. + */ + operator bool() const { return m_valid; } + + /** + * XEP-0106: JID Escaping + * @param node The node to escape. + * @return The escaped node. + */ + static std::string escapeNode( const std::string& node ); + + /** + * XEP-0106: JID Escaping + * @param node The node to unescape. + * @return The unescaped node. + */ + static std::string unescapeNode( const std::string& node ); + + private: + /** + * Utility function to rebuild both the bare and full jid. + */ + void setStrings() { setBare(); setFull(); } + + /** + * Utility function rebuilding the bare jid. + * @note Do not use this function directly, instead use setStrings. + */ + void setBare(); + + /** + * Utility function rebuilding the full jid. + */ + void setFull(); + + std::string m_resource; + std::string m_username; + std::string m_server; + std::string m_serverRaw; + std::string m_bare; + std::string m_full; + bool m_valid; + + }; + +} + +#endif // JID_H__ diff --git a/libs/libgloox/lastactivity.cpp b/libs/libgloox/lastactivity.cpp new file mode 100644 index 0000000..3760dfb --- /dev/null +++ b/libs/libgloox/lastactivity.cpp @@ -0,0 +1,132 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "lastactivity.h" +#include "disco.h" +#include "discohandler.h" +#include "clientbase.h" +#include "error.h" +#include "lastactivityhandler.h" + +#include + +namespace gloox +{ + + // ---- LastActivity::Query ---- + LastActivity::Query::Query( const Tag* tag ) + : StanzaExtension( ExtLastActivity ), m_seconds( -1 ) + { + if( !tag || tag->name() != "query" || tag->xmlns() != XMLNS_LAST ) + return; + + if( tag->hasAttribute( "seconds" ) ) + m_seconds = atoi( tag->findAttribute( "seconds" ).c_str() ); + + m_status = tag->cdata(); + } + + LastActivity::Query::Query( const std::string& _status, long _seconds ) + : StanzaExtension( ExtLastActivity ), m_seconds( _seconds ), + m_status( _status ) + { + } + + LastActivity::Query::~Query() + { + } + + const std::string& LastActivity::Query::filterString() const + { + static const std::string filter = "/iq/query[@xmlns='" + XMLNS_LAST + "']" + "|/presence/query[@xmlns='" + XMLNS_LAST + "']"; + return filter; + } + + Tag* LastActivity::Query::tag() const + { + Tag* t = new Tag( "query" ); + t->setXmlns( XMLNS_LAST ); + t->addAttribute( "seconds", m_seconds ); + t->setCData( m_status ); + return t; + } + // ---- ~LastActivity::Query ---- + + // ---- LastActivity ---- + LastActivity::LastActivity( ClientBase* parent ) + : m_lastActivityHandler( 0 ), m_parent( parent ), + m_active( time ( 0 ) ) + { + if( m_parent ) + { + m_parent->registerStanzaExtension( new Query() ); + m_parent->registerIqHandler( this, ExtLastActivity ); + m_parent->disco()->addFeature( XMLNS_LAST ); + } + } + + LastActivity::~LastActivity() + { + if( m_parent ) + { + m_parent->disco()->removeFeature( XMLNS_LAST ); + m_parent->removeIqHandler( this, ExtLastActivity ); + m_parent->removeIDHandler( this ); + } + } + + void LastActivity::query( const JID& jid ) + { + IQ iq( IQ::Get, jid, m_parent->getID() ); + iq.addExtension( new Query() ); + m_parent->send( iq, this, 0 ); + } + + bool LastActivity::handleIq( const IQ& iq ) + { + const Query* q = iq.findExtension( ExtLastActivity ); + if( !q || iq.subtype() != IQ::Get ) + return false; + + IQ re( IQ::Result, iq.from(), iq.id() ); + re.addExtension( new Query( EmptyString, (long)( time( 0 ) - m_active ) ) ); + m_parent->send( re ); + + return true; + } + + void LastActivity::handleIqID( const IQ& iq, int /*context*/ ) + { + if( !m_lastActivityHandler ) + return; + + if( iq.subtype() == IQ::Result ) + { + const Query* q = iq.findExtension( ExtLastActivity ); + if( !q || q->seconds() < 0 ) + return; + + m_lastActivityHandler->handleLastActivityResult( iq.from(), q->seconds(), q->status() ); + } + else if( iq.subtype() == IQ::Error && iq.error() ) + m_lastActivityHandler->handleLastActivityError( iq.from(), iq.error()->error() ); + } + + void LastActivity::resetIdleTimer() + { + m_active = time( 0 ); + } + +} diff --git a/libs/libgloox/lastactivity.h b/libs/libgloox/lastactivity.h new file mode 100644 index 0000000..f0c6eb1 --- /dev/null +++ b/libs/libgloox/lastactivity.h @@ -0,0 +1,166 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef LASTACTIVITY_H__ +#define LASTACTIVITY_H__ + +#include "iqhandler.h" + +#include + +namespace gloox +{ + + class JID; + class ClientBase; + class LastActivityHandler; + + /** + * @brief This is an implementation of XEP-0012 (Last Activity) for both clients and components. + * + * LastActivity can be used to query remote entities about their last activity time as well + * as answer incoming last-activity-queries. + * + * XEP Version: 2.0 + * + * @author Jakob Schroeter + * @since 0.6 + */ + class GLOOX_API LastActivity : public IqHandler + { + public: + /** + * @brief This is an abstraction of a LastActivity Query that + * can be used in XEP-0012 as well as XEP-0256. + * + * XEP-Version: 2.0 (XEP-0012) + * XEP-Version: 0.1 (XEP-0256) + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Query : public StanzaExtension + { + public: + /** + * Constructs a new Query object from the given Tag. + * @param tag The Tag to parse. + */ + Query( const Tag* tag = 0 ); + + /** + * Constructs a new Query object from the given long. + * @param status A status message. + * @param seconds The number of seconds since last activity. + */ + Query( const std::string& status, long seconds ); + + /** + * Virtual destructor. + */ + virtual ~Query(); + + /** + * Returns the number of seconds since last activity. + * @return The number of seconds since last activity. + * -1 if last activity is unknown. + */ + long seconds() const { return m_seconds; } + + /** + * Returns the last status message if the user is offline + * and specified a status message when logging off. + * @return The last status message, if any. + */ + const std::string& status() const { return m_status; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Query( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new Query( *this ); + } + + private: + long m_seconds; + std::string m_status; + + }; + + /** + * Constructs a new LastActivity object. + * @param parent The ClientBase object to use for communication. + */ + LastActivity( ClientBase* parent ); + + /** + * Virtual destructor. + */ + virtual ~LastActivity(); + + /** + * Queries the given JID for their last activity. The result can be received by reimplementing + * @ref LastActivityHandler::handleLastActivityResult() and + * @ref LastActivityHandler::handleLastActivityError(). + */ + void query( const JID& jid ); + + /** + * Use this function to register an object as handler for incoming results of Last-Activity queries. + * Only one handler is possible at a time. + * @param lah The object to register as handler. + */ + void registerLastActivityHandler( LastActivityHandler* lah ) { m_lastActivityHandler = lah; } + + /** + * Use this function to un-register the LastActivityHandler set earlier. + */ + void removeLastActivityHandler() { m_lastActivityHandler = 0; } + + /** + * Use this function to reset the idle timer. By default the number of seconds since the + * instantiation will be used. + */ + void resetIdleTimer(); + + // reimplemented from IqHandler + virtual bool handleIq( const IQ& iq ); + + // reimplemented from IqHandler + virtual void handleIqID( const IQ& iq, int context ); + + private: +#ifdef LASTACTIVITY_TEST + public: +#endif + LastActivityHandler* m_lastActivityHandler; + ClientBase* m_parent; + + time_t m_active; + + }; + +} + +#endif // LASTACTIVITY_H__ diff --git a/libs/libgloox/lastactivityhandler.h b/libs/libgloox/lastactivityhandler.h new file mode 100644 index 0000000..fb3217d --- /dev/null +++ b/libs/libgloox/lastactivityhandler.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef LASTACTIVITYHANDLER_H__ +#define LASTACTIVITYHANDLER_H__ + +#include "gloox.h" +#include "jid.h" + +namespace gloox +{ + + /** + * @brief This is an virtual interface that, once reimplemented, allows to receive the + * results of Last-Activity-queries to other entities. + * + * @author Jakob Schroeter + * @since 0.6 + */ + class GLOOX_API LastActivityHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~LastActivityHandler() {} + + /** + * This function is called when a positive result of a query arrives. + * @param jid The JID of the queried contact. + * @param seconds The idle time or time of last presence of the contact. (Depends + * on the JID, check the spec.) + * @param status If the contact is offline, this is the last presence status message. May be empty. + */ + virtual void handleLastActivityResult( const JID& jid, long seconds, const std::string& status ) = 0; + + /** + * This function is called when an error is returned by the queried antity. + * @param jid The queried entity's address. + * @param error The reported error. + */ + virtual void handleLastActivityError( const JID& jid, StanzaError error ) = 0; + + }; + +} + +#endif // LASTACTIVITYHANDLER_H__ diff --git a/libs/libgloox/loghandler.h b/libs/libgloox/loghandler.h new file mode 100644 index 0000000..fa45a1b --- /dev/null +++ b/libs/libgloox/loghandler.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef LOGHANDLER_H__ +#define LOGHANDLER_H__ + +#include "gloox.h" + +#include + +namespace gloox +{ + + /** + * @brief A virtual interface which can be reimplemented to receive debug and log messages. + * + * @ref handleLog() is called for log messages. + * + * @author Jakob Schroeter + * @since 0.5 + */ + class GLOOX_API LogHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~LogHandler() {} + + /** + * Reimplement this function if you want to receive the chunks of the conversation + * between gloox and server or other debug info from gloox. + * @param level The log message's severity. + * @param area The log message's origin. + * @param message The log message. + */ + virtual void handleLog( LogLevel level, LogArea area, const std::string& message ) = 0; + }; + +} + +#endif // LOGHANDLER_H__ diff --git a/libs/libgloox/logsink.cpp b/libs/libgloox/logsink.cpp new file mode 100644 index 0000000..30719de --- /dev/null +++ b/libs/libgloox/logsink.cpp @@ -0,0 +1,49 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "logsink.h" + +namespace gloox +{ + + LogSink::LogSink() + { + } + + LogSink::~LogSink() + { + } + + void LogSink::log( LogLevel level, LogArea area, const std::string& message ) const + { + LogHandlerMap::const_iterator it = m_logHandlers.begin(); + for( ; it != m_logHandlers.end(); ++it ) + { + if( (*it).first && ( (*it).second.level <= level ) && ( (*it).second.areas & area ) ) + (*it).first->handleLog( level, area, message ); + } + } + + void LogSink::registerLogHandler( LogLevel level, int areas, LogHandler* lh ) + { + LogInfo info = { level, areas }; + m_logHandlers[lh] = info; + } + + void LogSink::removeLogHandler( LogHandler* lh ) + { + m_logHandlers.erase( lh ); + } + +} diff --git a/libs/libgloox/logsink.h b/libs/libgloox/logsink.h new file mode 100644 index 0000000..6b92280 --- /dev/null +++ b/libs/libgloox/logsink.h @@ -0,0 +1,118 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef LOGSINK_H__ +#define LOGSINK_H__ + +#include "gloox.h" +#include "loghandler.h" + +#include +// #include + +namespace gloox +{ + + /** + * @brief An implementation of log sink and source. + * + * To log the output of your Client or Component, use ClientBase's + * @link ClientBase::logInstance() logInstance() @endlink to get hold of the LogSink + * object for that ClientBase. Register your LogHandler with that instance. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 0.8 + */ + class GLOOX_API LogSink + { + public: + /** + * Constructor. + */ + LogSink(); + + /** + * Virtual destructor. + */ + virtual ~LogSink(); + + /** + * Use this function to log a message with given LogLevel and LogIdentifier. + * dbg(), warn(), and err() are alternative shortcuts. + * @param level The severity of the logged event. + * @param area The part of the program/library the message comes from. + * @param message The actual log message. + */ + void log( LogLevel level, LogArea area, const std::string& message ) const; + + /** + * Use this function to log a debug message with given LogIdentifier. + * This is a convenience wrapper around log(). + * @param area The part of the program/library the message comes from. + * @param message The actual log message. + */ + void dbg( LogArea area, const std::string& message ) const + { log( LogLevelDebug, area, message ); } + + /** + * Use this function to log a warning message with given LogIdentifier. + * This is a convenience wrapper around log(). + * @param area The part of the program/library the message comes from. + * @param message The actual log message. + */ + void warn( LogArea area, const std::string& message ) const + { log( LogLevelWarning, area, message ); } + + /** + * Use this function to log a error message with given LogIdentifier. + * This is a convenience wrapper around log(). + * @param area The part of the program/library the message comes from. + * @param message The actual log message. + */ + void err( LogArea area, const std::string& message ) const + { log( LogLevelError, area, message ); } + + /** + * Registers @c lh as object that receives all debug messages of the specified type. + * Suitable for logging to a file, etc. + * @param level The LogLevel for this handler. + * @param areas Bit-wise ORed LogAreas the LogHandler wants to be informed about. + * @param lh The object to receive exchanged data. + */ + void registerLogHandler( LogLevel level, int areas, LogHandler* lh ); + + /** + * Removes the given object from the list of log handlers. + * @param lh The object to remove from the list. + */ + void removeLogHandler( LogHandler* lh ); + + private: + struct LogInfo + { + LogLevel level; + int areas; + }; + + LogSink( const LogSink& /*copy*/ ); + + typedef std::map LogHandlerMap; + LogHandlerMap m_logHandlers; + + }; + +} + +#endif // LOGSINK_H__ diff --git a/libs/libgloox/macros.h b/libs/libgloox/macros.h new file mode 100644 index 0000000..c8f622f --- /dev/null +++ b/libs/libgloox/macros.h @@ -0,0 +1,49 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef GLOOX_MACROS_H__ +#define GLOOX_MACROS_H__ + +#if defined( _MSC_VER ) || defined( _WIN32_WCE ) +# pragma warning( disable:4251 ) +# pragma warning( disable:4786 ) +#endif + +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) +# if defined( GLOOX_EXPORTS ) || defined( DLL_EXPORT ) +# define GLOOX_API __declspec( dllexport ) +# else +# if defined( GLOOX_IMPORTS ) || defined( DLL_IMPORT ) +# define GLOOX_API __declspec( dllimport ) +# endif +# endif +#endif + +#ifndef GLOOX_API +# define GLOOX_API +#endif + + +#if defined( __GNUC__ ) && ( __GNUC__ - 0 > 3 || ( __GNUC__ - 0 == 3 && __GNUC_MINOR__ - 0 >= 2 ) ) +# define GLOOX_DEPRECATED __attribute__ ( (__deprecated__) ) +# define GLOOX_DEPRECATED_CTOR explicit GLOOX_DEPRECATED +#elif defined( _MSC_VER ) && ( _MSC_VER >= 1300 ) +# define GLOOX_DEPRECATED __declspec( deprecated ) +# define GLOOX_DEPRECATED_CTOR explicit GLOOX_DEPRECATED +#else +# define GLOOX_DEPRECATED +# define GLOOX_DEPRECATED_CTOR +#endif + + +#endif // GLOOX_MACROS_H__ diff --git a/libs/libgloox/md5.cpp b/libs/libgloox/md5.cpp new file mode 100644 index 0000000..3b41b62 --- /dev/null +++ b/libs/libgloox/md5.cpp @@ -0,0 +1,466 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +/* + This class is based on a C implementation of the MD5 algorithm written by + L. Peter Deutsch. + The full notice as shipped with the original verson is included below. +*/ + +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +#include "config.h" + +#include "md5.h" + +#include +#include + +#include // [s]print[f] + +namespace gloox +{ +// #undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +// #ifdef ARCH_IS_BIG_ENDIAN +// # define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) +// #else +// # define BYTE_ORDER 0 +// #endif + +#undef BYTE_ORDER +#define BYTE_ORDER 0 + +#define T_MASK ((unsigned int)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + + const unsigned char MD5::pad[64] = + { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + MD5::MD5() + : m_finished( false ) + { + init(); + } + + MD5::~MD5() + { + } + + void MD5::process( const unsigned char* data /*[64]*/ ) + { + unsigned int a = m_state.abcd[0]; + unsigned int b = m_state.abcd[1]; + unsigned int c = m_state.abcd[2]; + unsigned int d = m_state.abcd[3]; + unsigned int t; +#if BYTE_ORDER > 0 + /* Define storage only for big-endian CPUs. */ + unsigned int X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + unsigned int xbuf[16]; + const unsigned int *X; +#endif + + { +#if BYTE_ORDER == 0 + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if( *((const unsigned char *)&w) ) /* dynamic little-endian */ +#endif +#if BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if( !((data - (const unsigned char*)0) & 3) ) + { + /* data are properly aligned */ + X = (const unsigned int*)data; + } + else + { + /* not aligned */ + memcpy( xbuf, data, 64 ); + X = xbuf; + } + } +#endif +#if BYTE_ORDER == 0 + else // dynamic big-endian +#endif +#if BYTE_ORDER >= 0 // big-endian + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const unsigned char* xp = data; + int i; + +# if BYTE_ORDER == 0 + X = xbuf; // (dynamic only) +# else +# define xbuf X /* (static only) */ +# endif + for( i = 0; i < 16; ++i, xp += 4 ) + xbuf[i] = xp[0] + ( xp[1] << 8 ) + ( xp[2] << 16 ) + ( xp[3] << 24 ); + } +#endif + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + m_state.abcd[0] += a; + m_state.abcd[1] += b; + m_state.abcd[2] += c; + m_state.abcd[3] += d; + } + + void MD5::init() + { + m_finished = false; + m_state.count[0] = 0; + m_state.count[1] = 0; + m_state.abcd[0] = 0x67452301; + m_state.abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + m_state.abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + m_state.abcd[3] = 0x10325476; + } + + void MD5::feed( const std::string& data ) + { + feed( (const unsigned char*)data.c_str(), (int)data.length() ); + } + + void MD5::feed( const unsigned char* data, int bytes ) + { + const unsigned char* p = data; + int left = bytes; + int offset = ( m_state.count[0] >> 3 ) & 63; + unsigned int nbits = (unsigned int)( bytes << 3 ); + + if( bytes <= 0 ) + return; + + /* Update the message length. */ + m_state.count[1] += bytes >> 29; + m_state.count[0] += nbits; + if( m_state.count[0] < nbits ) + m_state.count[1]++; + + /* Process an initial partial block. */ + if( offset ) + { + int copy = ( offset + bytes > 64 ? 64 - offset : bytes ); + + memcpy( m_state.buf + offset, p, copy ); + if( offset + copy < 64 ) + return; + p += copy; + left -= copy; + process( m_state.buf ); + } + + /* Process full blocks. */ + for( ; left >= 64; p += 64, left -= 64 ) + process( p ); + + /* Process a final partial block. */ + if( left ) + memcpy( m_state.buf, p, left ); + } + + void MD5::finalize() + { + if( m_finished ) + return; + + unsigned char data[8]; + + /* Save the length before padding. */ + for( int i = 0; i < 8; ++i ) + data[i] = (unsigned char)( m_state.count[i >> 2] >> ( ( i & 3 ) << 3 ) ); + + /* Pad to 56 bytes mod 64. */ + feed( pad, ( ( 55 - ( m_state.count[0] >> 3 ) ) & 63 ) + 1 ); + + /* Append the length. */ + feed( data, 8 ); + + m_finished = true; + } + + const std::string MD5::hex() + { + if( !m_finished ) + finalize(); + + char buf[33]; + + for( int i = 0; i < 16; ++i ) + sprintf( buf + i * 2, "%02x", (unsigned char)( m_state.abcd[i >> 2] >> ( ( i & 3 ) << 3 ) ) ); + + return std::string( buf, 32 ); + } + + const std::string MD5::binary() + { + if( !m_finished ) + finalize(); + + unsigned char digest[16]; + for( int i = 0; i < 16; ++i ) + digest[i] = (unsigned char)( m_state.abcd[i >> 2] >> ( ( i & 3 ) << 3 ) ); + + return std::string( (char*)digest, 16 ); + } + + void MD5::reset() + { + init(); + } + +} diff --git a/libs/libgloox/md5.h b/libs/libgloox/md5.h new file mode 100644 index 0000000..47dbdfc --- /dev/null +++ b/libs/libgloox/md5.h @@ -0,0 +1,137 @@ +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.h is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Removed support for non-ANSI compilers; removed + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); + added conditionalization for C++ compilation from Martin + Purschke . + 1999-05-03 lpd Original version. + */ + +#ifndef MD5_H__ +#define MD5_H__ + +#include "macros.h" + +#include + +namespace gloox +{ + + /** + * @brief An MD% implementation. + * + * This is an implementation of the Message Digest Algorithm as decribed in RFC 1321. + * The original code has been taken from an implementation by L. Peter Deutsch. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API MD5 + { + public: + /** + * Constructs a new MD5 object. + */ + MD5(); + + /** + * Virtual Destructor. + */ + virtual ~MD5(); + + /** + * Use this function to feed the hash. + * @param data The data to hash. + * @param bytes The size of @c data in bytes. + */ + void feed( const unsigned char* data, int bytes ); + + /** + * Use this function to feed the hash. + * @param data The data to hash. + */ + void feed( const std::string& data ); + + /** + * This function is used to finalize the hash operation. Use it after the last feed() and + * before calling hex(). + */ + void finalize(); + + /** + * Use this function to retrieve the hash value in hex. + * @return The hash in hex notation. + */ + const std::string hex(); + + /** + * Use this function to retrieve the raw binary hash. + * @return The raw binary hash. + */ + const std::string binary(); + + /** + * Use this function to reset the hash. + */ + void reset(); + + private: + struct MD5State + { + unsigned int count[2]; /* message length in bits, lsw first */ + unsigned int abcd[4]; /* digest buffer */ + unsigned char buf[64]; /* accumulate block */ + } m_state; + + void init(); + void process( const unsigned char* data ); + + static const unsigned char pad[64]; + + bool m_finished; + + }; + +} + +#endif // MD5_H__ diff --git a/libs/libgloox/message.cpp b/libs/libgloox/message.cpp new file mode 100644 index 0000000..66290d7 --- /dev/null +++ b/libs/libgloox/message.cpp @@ -0,0 +1,96 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "util.h" +#include "message.h" + +namespace gloox +{ + + static const char* msgTypeStringValues[] = + { + "chat", "error", "groupchat", "headline", "normal" + }; + + static inline const std::string typeString( Message::MessageType type ) + { + return util::lookup2( type, msgTypeStringValues ); + } + + Message::Message( Tag* tag ) + : Stanza( tag ), m_subtype( Invalid ), m_bodies( 0 ), m_subjects( 0 ) + { + if( !tag || tag->name() != "message" ) + return; + + const std::string& typestring = tag->findAttribute( TYPE ); + if( typestring.empty() ) + m_subtype = Normal; + else + m_subtype = (MessageType)util::lookup2( typestring, msgTypeStringValues ); + + const TagList& c = tag->children(); + TagList::const_iterator it = c.begin(); + for( ; it != c.end(); ++it ) + { + if( (*it)->name() == "body" ) + setLang( &m_bodies, m_body, (*it) ); + else if( (*it)->name() == "subject" ) + setLang( &m_subjects, m_subject, (*it) ); + else if( (*it)->name() == "thread" ) + m_thread = (*it)->cdata(); + } + } + + Message::Message( MessageType type, const JID& to, + const std::string& body, const std::string& subject, + const std::string& thread, const std::string& xmllang ) + : Stanza( to ), m_subtype( type ), m_bodies( 0 ), m_subjects( 0 ), m_thread( thread ) + { + setLang( &m_bodies, m_body, body, xmllang ); + setLang( &m_subjects, m_subject, subject, xmllang ); + } + + Message::~Message() + { + delete m_bodies; + delete m_subjects; + } + + Tag* Message::tag() const + { + if( m_subtype == Invalid ) + return 0; + + Tag* t = new Tag( "message" ); + if( m_to ) + t->addAttribute( "to", m_to.full() ); + if( m_from ) + t->addAttribute( "from", m_from.full() ); + if( !m_id.empty() ) + t->addAttribute( "id", m_id ); + t->addAttribute( TYPE, typeString( m_subtype ) ); + + getLangs( m_bodies, m_body, "body", t ); + getLangs( m_subjects, m_subject, "subject", t ); + + if( !m_thread.empty() ) + new Tag( t, "thread", m_thread ); + + StanzaExtensionList::const_iterator it = m_extensionList.begin(); + for( ; it != m_extensionList.end(); ++it ) + t->addChild( (*it)->tag() ); + + return t; + } + +} diff --git a/libs/libgloox/message.h b/libs/libgloox/message.h new file mode 100644 index 0000000..4eb21c4 --- /dev/null +++ b/libs/libgloox/message.h @@ -0,0 +1,156 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef MESSAGE_H__ +#define MESSAGE_H__ + +#include "delayeddelivery.h" +#include "stanza.h" + +#include + +namespace gloox +{ + + class JID; + + /** + * @brief An abstraction of a message stanza. + * + * @author Vincent Thomasset + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Message : public Stanza + { + + friend class ClientBase; + + public: + + /** + * Describes the different valid message types. + */ + enum MessageType + { + Chat = 1, /**< A chat message. */ + Error = 2, /**< An error message. */ + Groupchat = 4, /**< A groupchat message. */ + Headline = 8, /**< A headline message. */ + Normal = 16, /**< A normal message. */ + Invalid = 32 /**< The message stanza is invalid. */ + }; + + /** + * Creates a Message. + * @param type The message type. + * @param to The intended receiver. + * @param body The message's body text. + * @param subject The message's optional subject. + * @param thread The message's optional thread ID. + * @param xmllang An optional xml:lang for the message body. + */ + Message( MessageType type, const JID& to, + const std::string& body = EmptyString, const std::string& subject = EmptyString, + const std::string& thread = EmptyString, const std::string& xmllang = EmptyString ); + + /** + * Destructor. + */ + virtual ~Message(); + + /** + * Returns the message's type. + * @return The message's type. + */ + MessageType subtype() const { return m_subtype; } + + /** + * Returns the message body for the given language if available. + * If the requested language is not available, the default body (without a xml:lang + * attribute) will be returned. + * @param lang The language identifier for the desired language. It must conform to + * section 2.12 of the XML specification and RFC 3066. If empty, the default body + * will be returned, if any. + * @return The message body. + */ + const std::string body( const std::string& lang = "default" ) const + { + return findLang( m_bodies, m_body, lang ); + } + + /** + * Returns the message subject for the given language if available. + * If the requested language is not available, the default subject (without a xml:lang + * attribute) will be returned. + * @param lang The language identifier for the desired language. It must conform to + * section 2.12 of the XML specification and RFC 3066. If empty, the default subject + * will be returned, if any. + * @return The message subject. + */ + const std::string subject( const std::string& lang = "default" ) const + { + return findLang( m_subjects, m_subject, lang ); + } + + /** + * Returns the thread ID of a message stanza. + * @return The thread ID of a message stanza. Empty for non-message stanzas. + */ + const std::string& thread() const { return m_thread; } + + /** + * Sets the thread ID. + * @param thread The thread ID. + */ + void setThread( const std::string& thread ) { m_thread = thread; } + + /** + * Sets the message's ID. Optional. + * @param id The ID. + */ + void setID( const std::string& id ) { m_id = id; } + + /** + * Convenience function that returns a pointer to a DelayedDelivery StanzaExtension, if the + * message contains one. + * @return A pointer to a DelayedDelivery object, or 0. + */ + const DelayedDelivery* when() const + { + return static_cast( findExtension( ExtDelay ) ); + } + + // reimplemented from Stanza + virtual Tag* tag() const; + + private: +#ifdef MESSAGE_TEST + public: +#endif + /** + * Creates a message Stanza from the given Tag. The original Tag will be ripped off. + * @param tag The Tag to parse. + */ + Message( Tag* tag ); + + MessageType m_subtype; + std::string m_body; + std::string m_subject; + StringMap* m_bodies; + StringMap* m_subjects; + std::string m_thread; + }; + +} + +#endif // MESSAGE_H__ diff --git a/libs/libgloox/messageevent.cpp b/libs/libgloox/messageevent.cpp new file mode 100644 index 0000000..3e5c8b6 --- /dev/null +++ b/libs/libgloox/messageevent.cpp @@ -0,0 +1,65 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "messageevent.h" +#include "tag.h" +#include "util.h" + +namespace gloox +{ + + /* chat state type values */ + static const char* eventValues [] = { + "offline", + "delivered", + "displayed", + "composing" + }; + + MessageEvent::MessageEvent( const Tag* tag ) + : StanzaExtension( ExtMessageEvent ), m_event( MessageEventCancel ) + { + const TagList& l = tag->children(); + TagList::const_iterator it = l.begin(); + int event = 0; + for( ; it != l.end(); ++it ) + event |= util::lookup2( (*it)->name(), eventValues ); + if( event ) + m_event = event; + } + + const std::string& MessageEvent::filterString() const + { + static const std::string filter = "/message/x[@xmlns='" + XMLNS_X_EVENT + "']"; + return filter; + } + + Tag* MessageEvent::tag() const + { + Tag* x = new Tag( "x", XMLNS, XMLNS_X_EVENT ); + + if( m_event & MessageEventOffline ) + new Tag( x, "offline" ); + if( m_event & MessageEventDelivered ) + new Tag( x, "delivered" ); + if( m_event & MessageEventDisplayed ) + new Tag( x, "displayed" ); + if( m_event & MessageEventComposing ) + new Tag( x, "composing" ); + + if( !m_id.empty() ) + new Tag( x, "id", m_id ); + + return x; + } + +} diff --git a/libs/libgloox/messageevent.h b/libs/libgloox/messageevent.h new file mode 100644 index 0000000..dc60b5b --- /dev/null +++ b/libs/libgloox/messageevent.h @@ -0,0 +1,88 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef MESSAGEEVENT_H__ +#define MESSAGEEVENT_H__ + +#include "gloox.h" +#include "stanzaextension.h" + +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief An implementation of Message Events (XEP-0022) as a StanzaExtension. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API MessageEvent : public StanzaExtension + { + public: + + /** + * Constructs a new object from the given Tag. + * @param tag A Tag to parse. + */ + MessageEvent( const Tag* tag ); + + /** + * Constructs a new object of the given type, with an optional message ID. + * @param type One or more @link gloox::MessageEventType MessageEventType @endlink. + * @param id An optional message ID. Links this Event to the message it is generated for. + */ + MessageEvent( int type, const std::string& id = EmptyString ) + : StanzaExtension( ExtMessageEvent ), m_id( id ), m_event( type ) + {} + + /** + * Virtual destructor. + */ + virtual ~MessageEvent() {} + + /** + * Returns the object's event or events. + * @return The object's event or events. + */ + int event() const { return m_event; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new MessageEvent( tag ); + } + + // reimplemented from StanzaExtension + Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new MessageEvent( *this ); + } + + private: + std::string m_id; + int m_event; + + }; + +} + +#endif // MESSAGEEVENT_H__ diff --git a/libs/libgloox/messageeventfilter.cpp b/libs/libgloox/messageeventfilter.cpp new file mode 100644 index 0000000..12f71e1 --- /dev/null +++ b/libs/libgloox/messageeventfilter.cpp @@ -0,0 +1,112 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "messageeventfilter.h" +#include "messageeventhandler.h" +#include "messagesession.h" +#include "message.h" +#include "messageevent.h" +#include "error.h" + +namespace gloox +{ + + MessageEventFilter::MessageEventFilter( MessageSession* parent ) + : MessageFilter( parent ), m_messageEventHandler( 0 ), m_requestedEvents( 0 ), + m_lastSent( MessageEventCancel ), m_disable( false ) + { + } + + MessageEventFilter::~MessageEventFilter() + { + } + + void MessageEventFilter::filter( Message& msg ) + { + if( m_disable || !m_messageEventHandler ) + return; + + if( msg.subtype() == Message::Error ) + { + if( msg.error() && msg.error()->error() == StanzaErrorFeatureNotImplemented ) + m_disable = true; + + return; + } + + const MessageEvent* me = msg.findExtension( ExtMessageEvent ); + if( !me ) + { + m_requestedEvents = 0; + m_lastID = EmptyString; + return; + } + + if( msg.body().empty() ) + m_messageEventHandler->handleMessageEvent( msg.from(), (MessageEventType)me->event() ); + else + { + m_lastID = msg.id(); + m_requestedEvents = 0; + m_requestedEvents = me->event(); + } + } + + void MessageEventFilter::raiseMessageEvent( MessageEventType event ) + { + if( m_disable || ( !( m_requestedEvents & event ) && ( event != MessageEventCancel ) ) ) + return; + + switch( event ) + { + case MessageEventOffline: + case MessageEventDelivered: + case MessageEventDisplayed: + m_requestedEvents &= ~event; + break; + case MessageEventComposing: + if( m_lastSent == MessageEventComposing ) + return; + break; + case MessageEventCancel: + default: + break; + } + + m_lastSent = event; + Message m( Message::Normal, m_parent->target() ); + m.addExtension( new MessageEvent( event, m_lastID ) ); + send( m ); + } + + void MessageEventFilter::decorate( Message& msg ) + { + if( m_disable ) + return; + + msg.addExtension( new MessageEvent( MessageEventOffline | MessageEventDelivered | + MessageEventDisplayed | MessageEventComposing ) ); + m_lastSent = MessageEventCancel; + } + + void MessageEventFilter::registerMessageEventHandler( MessageEventHandler* meh ) + { + m_messageEventHandler = meh; + } + + void MessageEventFilter::removeMessageEventHandler() + { + m_messageEventHandler = 0; + } + +} diff --git a/libs/libgloox/messageeventfilter.h b/libs/libgloox/messageeventfilter.h new file mode 100644 index 0000000..b1c91a7 --- /dev/null +++ b/libs/libgloox/messageeventfilter.h @@ -0,0 +1,95 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef MESSAGEVENTFILTER_H__ +#define MESSAGEVENTFILTER_H__ + +#include "messagefilter.h" +#include "gloox.h" + +namespace gloox +{ + + class Tag; + class Message; + class MessageEventHandler; + class MessageSession; + + /** + * @brief This class adds Message Event (XEP-0022) support to a MessageSession. + * + * This implementation of Message Events is fully transparent to the user of the class. + * If the remote entity does not request message events, MessageEventFilter will not send + * any, even if the user requests it. (This is required by the protocol specification.) + * + * @author Jakob Schroeter + * @since 0.8 + */ + class GLOOX_API MessageEventFilter : public MessageFilter + { + public: + /** + * Contstructs a new Message Event filter for a MessageSession. + * @param parent The MessageSession to decorate. + */ + MessageEventFilter( MessageSession* parent ); + + /** + * Virtual destructor. + */ + virtual ~MessageEventFilter(); + + /** + * Use this function to raise an event as defined in XEP-0022. + * @note The Spec states that Message Events shall not be sent to an entity + * which did not request them. Reasonable effort is taken in this function to + * avoid spurious event sending. You should be safe to call this even if Message + * Events were not requested by the remote entity. However, + * calling raiseMessageEvent( MESSAGE_EVENT_COMPOSING ) for every keystroke still is + * discouraged. ;) + * @param event The event to raise. + */ + void raiseMessageEvent( MessageEventType event ); + + /** + * The MessageEventHandler registered here will receive Message Events according + * to XEP-0022. + * @param meh The MessageEventHandler to register. + */ + void registerMessageEventHandler( MessageEventHandler* meh ); + + /** + * This function clears the internal pointer to the MessageEventHandler. + * Message Events will not be delivered anymore after calling this function until another + * MessageEventHandler is registered. + */ + void removeMessageEventHandler(); + + // reimplemented from MessageFilter + virtual void decorate( Message& msg ); + + // reimplemented from MessageFilter + virtual void filter( Message& msg ); + + private: + MessageEventHandler* m_messageEventHandler; + std::string m_lastID; + int m_requestedEvents; + MessageEventType m_lastSent; + bool m_disable; + + }; + +} + +#endif // MESSAGEVENTFILTER_H__ diff --git a/libs/libgloox/messageeventhandler.h b/libs/libgloox/messageeventhandler.h new file mode 100644 index 0000000..6dbacb3 --- /dev/null +++ b/libs/libgloox/messageeventhandler.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef MESSAGEEVENTHANDLER_H__ +#define MESSAGEEVENTHANDLER_H__ + +#include "gloox.h" + +namespace gloox +{ + + class JID; + + /** + * @brief A virtual interface that enables an object to be notified about + * Message Events (XEP-0022). + * + * @author Jakob Schroeter + * @since 0.8 + */ + class GLOOX_API MessageEventHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~MessageEventHandler() {} + + /** + * Notifies the MessageEventHandler that an event has been raised by the remote + * contact. + * @param from The originator of the Event. + * @param event The Event which has been raised. + */ + virtual void handleMessageEvent( const JID& from, MessageEventType event ) = 0; + + }; + +} + +#endif // MESSAGEEVENTHANDLER_H__ diff --git a/libs/libgloox/messagefilter.cpp b/libs/libgloox/messagefilter.cpp new file mode 100644 index 0000000..2f80e6c --- /dev/null +++ b/libs/libgloox/messagefilter.cpp @@ -0,0 +1,42 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "messagefilter.h" + + +namespace gloox +{ + + MessageFilter::MessageFilter( MessageSession* parent ) + : m_parent( 0 ) + { + if( parent ) + attachTo( parent ); + } + + MessageFilter::~MessageFilter() + { + } + + void MessageFilter::attachTo( MessageSession* session ) + { + if( m_parent ) + m_parent->removeMessageFilter( this ); + + if( session ) + session->registerMessageFilter( this ); + + m_parent = session; + } + +} diff --git a/libs/libgloox/messagefilter.h b/libs/libgloox/messagefilter.h new file mode 100644 index 0000000..43e2cfa --- /dev/null +++ b/libs/libgloox/messagefilter.h @@ -0,0 +1,84 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef MESSAGEFILTER_H__ +#define MESSAGEFILTER_H__ + +#include "messagesession.h" + +namespace gloox +{ + + class Message; + + /** + * @brief Virtual base class for message filters. + * + * A message filter is fed with all messages passing through a MessageSession. It can + * modify the XML/XMPP structure and/or the message content at will. Messages arriving + * from the server as well as messages sent to the server can be altered. + * + * Messages to be sent out are presented to the filter via the decorate() function, incoming + * messages can be filtered in the -- filter() method. + * + * @author Jakob Schroeter + * @since 0.8 + */ + class GLOOX_API MessageFilter + { + + public: + /** + * Constructor. + * @param parent The MessageSession to attach to. + */ + MessageFilter( MessageSession* parent ); + + /** + * Virtual Destructor. + */ + virtual ~MessageFilter(); + + /** + * Attaches this MessageFilter to the given MessageSession and hooks it into + * the session's filter chain. + * If this filter was attached to a different MessageSession before, it is + * unregistered there prior to registering it with the new session. + * @param session The MessageSession to hook into. + */ + virtual void attachTo( MessageSession* session ); + + /** + * This function receives a message right before it is sent out (there may be other filters + * which get to see the message after this filter, though). + * @param msg The tag to decorate. It contains the message to be sent. + */ + virtual void decorate( Message& msg ) = 0; + + /** + * This function receives a message stanza right after it was received (there may be other filters + * which got to see the stanza before this filter, though). + * @param msg The complete message stanza. + */ + virtual void filter( Message& msg ) = 0; + + protected: + void send( Message& msg ) { if( m_parent ) m_parent->send( msg ); } + + MessageSession* m_parent; + + }; + +} + +#endif // MESSAGEFILTER_H__ diff --git a/libs/libgloox/messagehandler.h b/libs/libgloox/messagehandler.h new file mode 100644 index 0000000..47f302c --- /dev/null +++ b/libs/libgloox/messagehandler.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef MESSAGEHANDLER_H__ +#define MESSAGEHANDLER_H__ + +#include "macros.h" + +namespace gloox +{ + + class MessageSession; + class Message; + + /** + * @brief A virtual interface which can be reimplemented to receive incoming message stanzas. + * + * Derived classes can be registered as MessageHandlers with a ClientBase or MessageSession instance. + * Upon an incoming Message packet @ref handleMessage() will be called. If registered with a + * ClientBase this happens for every incoming message, regardless of the sender. With a MessageSession + * the registered handler will receive all messages originating from the Session's contact. See + * MessageSession for more details. + * + * @author Jakob Schroeter + */ + class GLOOX_API MessageHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~MessageHandler() {} + + /** + * Reimplement this function if you want to be notified about + * incoming messages. + * @param msg The complete Message. + * @param session If this MessageHandler is used with a MessageSession, this parameter + * holds a pointer to that MessageSession. + * @since 1.0 + */ + virtual void handleMessage( const Message& msg, MessageSession* session = 0 ) = 0; + + }; + +} + +#endif // MESSAGEHANDLER_H__ diff --git a/libs/libgloox/messagesession.cpp b/libs/libgloox/messagesession.cpp new file mode 100644 index 0000000..e8fd442 --- /dev/null +++ b/libs/libgloox/messagesession.cpp @@ -0,0 +1,111 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "messagesession.h" +#include "messagefilter.h" +#include "messagehandler.h" +#include "clientbase.h" +#include "disco.h" +#include "message.h" +#include "util.h" + +namespace gloox +{ + + MessageSession::MessageSession( ClientBase* parent, const JID& jid, bool wantUpgrade, int types, bool honorTID ) + : m_parent( parent ), m_target( jid ), m_messageHandler( 0 ), + m_types( types ), m_wantUpgrade( wantUpgrade ), m_hadMessages( false ), m_honorThreadID( honorTID ) + { + if( m_parent ) + m_parent->registerMessageSession( this ); + } + + MessageSession::~MessageSession() + { + util::clearList( m_messageFilterList ); + } + + void MessageSession::handleMessage( Message& msg ) + { + if( m_wantUpgrade && msg.from().bare() == m_target.full() ) + setResource( msg.from().resource() ); + + if( !m_hadMessages ) + { + m_hadMessages = true; + if( msg.thread().empty() ) + { + m_thread = "gloox" + m_parent->getID(); + msg.setThread( m_thread ); + } + else + m_thread = msg.thread(); + } + + MessageFilterList::const_iterator it = m_messageFilterList.begin(); + for( ; it != m_messageFilterList.end(); ++it ) + (*it)->filter( msg ); + + if( m_messageHandler && !msg.body().empty() ) + m_messageHandler->handleMessage( msg, this ); + } + + void MessageSession::send( const std::string& message, const std::string& subject, const StanzaExtensionList& sel ) + { + if( !m_hadMessages ) + { + m_thread = "gloox" + m_parent->getID(); + m_hadMessages = true; + } + + Message m( Message::Chat, m_target.full(), message, subject, m_thread ); + m.setID( m_parent->getID() ); + decorate( m ); + + if( sel.size() ) + { + StanzaExtensionList::const_iterator it = sel.begin(); + for( ; it != sel.end(); ++it ) + m.addExtension( (*it)); + } + + m_parent->send( m ); + } + + void MessageSession::send( const Message& msg ) + { + m_parent->send( msg ); + } + + void MessageSession::decorate( Message& msg ) + { + util::ForEach( m_messageFilterList, &MessageFilter::decorate, msg ); + } + + void MessageSession::resetResource() + { + m_wantUpgrade = true; + m_target.setResource( EmptyString ); + } + + void MessageSession::setResource( const std::string& resource ) + { + m_target.setResource( resource ); + } + + void MessageSession::disposeMessageFilter( MessageFilter* mf ) + { + removeMessageFilter( mf ); + delete mf; + } + +} diff --git a/libs/libgloox/messagesession.h b/libs/libgloox/messagesession.h new file mode 100644 index 0000000..d2232cd --- /dev/null +++ b/libs/libgloox/messagesession.h @@ -0,0 +1,309 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef MESSAGESESSION_H__ +#define MESSAGESESSION_H__ + +#include "jid.h" +#include "gloox.h" + +#include +#include + +namespace gloox +{ + + class ClientBase; + class MessageFilter; + class MessageHandler; + class Message; + + /** + * @brief An abstraction of a message session between any two entities. + * + * This is an alternative interface to unmanaged messaging. The original interface, using the simple + * MessageHandler-derived interface, is based on an all-or-nothing approach. Once registered with + * ClientBase, a handler receives all message stanzas sent to this client and has to do any filtering + * on its own. + * + * MessageSession adds an abstraction to a chat conversation. A MessageSession is responsible for + * communicating with exactly one (full) JID. It is extensible with so-called MessageFilters, which can + * provide additional features such as Message Events, or Chat State Notifications. + * + * You can still use the old MessageHandler in parallel, but messages will not be relayed to both + * the generic MessageHandler and a MessageSession established for the sender's JID. The MessageSession + * takes precedence. + * + * Using MessageSessions has the following advantages over the plain old MessageHandler: + * @li automatic creation of MessageSessions + * @li filtering by JID + * @li automatic handling of threading (i.e., XMPP message threads) + * @li simpler sending of messages + * @li support for MessageFilters. + * + * @b Usage:
+ * Derive an object from MessageSessionHandler and reimplement handleMessageSession() to store your + * shiny new sessions somewhere, or to create a new chat window, or whatever. Register your + * object with a ClientBase instance using registerMessageSessionHandler(). In code: + * @code + * void MyClass::myFunc() + * { + * JID jid( "abc@example.org/gloox" ); + * j = new Client( jid, "password" ); + * [...] + * j->registerMessageSessionHandler( this, 0 ); + * } + * @endcode + * MyClass is a MessageSessionHandler here. + * + * In this example, MyClass needs to be MessageHandler, MessageEventHandler and + * ChatStateHandler, too. The handlers are registered with the session to receive the + * respective events. + * @code + * virtual void MyClass::handleMessageSession( MessageSession* session ) + * { + * // for this example only, we delete any earlier session + * if( m_session ) + * j->disposeMessageSession( m_session ); + * m_session = session; + * m_session->registerMessageHandler( this ); + * + * // the following is optional + * m_messageEventFilter = new MessageEventFilter( m_session ); + * m_messageEventFilter->registerMessageEventHandler( this ); + * m_chatStateFilter = new ChatStateFilter( m_session ); + * m_chatStateFilter->registerChatStateHandler( this ); + * } + * @endcode + * + * MessageEventHandler::handleMessageEvent() and ChatStateHandler::handleChatState() are called + * for incoming Message Events and Chat States, respectively. + * @code + * virtual void MyClass::handleMessageEvent( const JID& from, MessageEventType event ) + * { + * // display contact's Message Event + * } + * + * virtual void MyClass::handleChatState( const JID& from, ChatStateType state ) + * { + * // display contact's Chat State + * } + * @endcode + * + * To let the chat partner now that the user is typing a message or has closed the chat window, use + * raiseMessageEvent() and setChatState(), respectively. For example: + * @code + * // user is typing a message + * m_messageEventFilter->raiseMessageEvent( MessageEventComposing ); + * + * // acknowledge receiving of a message + * m_messageEventFilter->raiseMessageEvent( MessageEventDelivered ); + * + * // user is not actively paying attention to the chat + * m_chatStateFilter->setChatState( ChatStateInactive ); + * + * // user has closed the chat window + * m_chatStateFilter->setChatState( ChatStateGone ); + * @endcode + * + * To send a message to the chat partner of the session, use + * @ref send( const std::string& message, const std::string& subject, const StanzaExtensionList& ). + * You don't have to care about + * receipient, thread id, etc., they are added automatically. + * + * @code + * m_session->send( "Hello World!", "No Subject" ); + * @endcode + * + * To initiate a new chat session, all you have to do is create a new MessageSession and register + * a MessageHandler with it: + * @code + * MessageSession* MyClass::newSession( const JID& to ) + * { + * MessageSession* session = new MessageSession( m_client, to ); + * session->registerMessageHandler( this ); + * return session; + * } + * @endcode + * + * @note You should never delete a MessageSession manually. Use ClientBase::disposeMessageSession() + * instead. + * + * @author Jakob Schroeter + * @since 0.8 + */ + class GLOOX_API MessageSession + { + + friend class MessageFilter; + + public: + /** + * Constructs a new MessageSession for the given JID. + * It is recommended to supply a full JID, in other words, it should have a resource set. + * No resource can lead to unexpected behavior. A thread ID is generated and sent along + * with every message sent through this session. + * @param parent The ClientBase to use for communication. + * @param jid The remote contact's full JID. If you don't know the full JID (this is probably the + * most common case) but still want replies from the full JID to be handled by this MessageSession, + * set the @b wantUpgrade parameter to true (or leave it untouched). + * @param wantUpgrade This flag indicates whether gloox should try to match an incoming message + * from a full JID to this MessageSession. If unsure, use the default. You probably only want to use + * a non-default value if this MessageSession is supposed to talk directly to a server or component + * JID that has no resource. This 'upgrade' will only happen once. + * @param types ORed list of Message::MessageType values this MessageSession shall receive. + * Defaults to 0 which means any type is received. + * @param honorTID Indicates whether thread IDs should be honored when matching incoming messages to MessageSessions. The default is usually fine. + */ + MessageSession( ClientBase* parent, const JID& jid, bool wantUpgrade = true, int types = 0, bool honorTID = true ); + + /** + * Virtual destructor. + * + * @note You should never delete a MessageSession manually. Use ClientBase::disposeMessageSession() + * instead. + */ + virtual ~MessageSession(); + + /** + * Use this function to find out where this session points at. + * @return The receipient's JID. + */ + const JID& target() const { return m_target; } + + /** + * By default, a thread ID is sent with every message to identify + * messages belonging together. + * @returns The thread ID for this session. + */ + const std::string& threadID() const { return m_thread; } + + /** + * Use this function to set the session's thread ID if e.g. a specific thread is + * continued. It should not normally be needed to set the thread ID manually. + * @param thread The new thread ID. + */ + void setThreadID( const std::string& thread ) { m_thread = thread; } + + /** + * Indicates whether thread IDs are honored when matching incoming + * messages to MessageSessions. + * @return Whether thread IDs are honored. + */ + bool honorThreadID() const { return m_honorThreadID; } + + /** + * Use this function to associate a MessageHandler with this MessageSession. + * The MessageHandler will receive all messages sent from this MessageSession's + * remote contact. + * @param mh The MessageHandler to register. + */ + void registerMessageHandler( MessageHandler* mh ) + { m_messageHandler = mh; } + + /** + * This function clears the internal pointer to the MessageHandler and therefore + * disables message delivery. + */ + void removeMessageHandler() + { m_messageHandler = 0; } + + /** + * A convenience function to quickly send a message (optionally with subject). This is + * the preferred way to send a message from a MessageSession. + * @param message The message to send. + * @param subject The optional subject to send. + * @param sel An optional list of StanzaExtensions. The extensions will be owned by the message-to-be-sent; + * do not attempt to re-use or delete them. + */ + virtual void send( const std::string& message, const std::string& subject = EmptyString, + const StanzaExtensionList& sel = StanzaExtensionList() ); + + /** + * Use this function to hook a new MessageFilter into a MessageSession. + * The filter will be able to read and/or modify a message stanza's content. + * @note The MessageSession will become the owner of the filter, it will be + * deleted by MessageSession's destructor. To get rid of the filter before that, + * use disposeMessageFilter(). + * @param mf The MessageFilter to add. + */ + void registerMessageFilter( MessageFilter* mf ) + { m_messageFilterList.push_back( mf ); } + + /** + * Use this function to remove a MessageFilter from the MessageSession. + * @param mf The MessageFilter to remove. + * @note To remove and delete the MessageFilter in one step use disposeMessageFilter(). + */ + void removeMessageFilter( MessageFilter* mf ) + { m_messageFilterList.remove( mf ); } + + /** + * Use this function to remove and delete a MessageFilter from the MessageSession. + * @param mf The MessageFilter to remove and delete. + * @note To just remove (and not delete) the MessageFilter use removeMessageFilter(). + */ + void disposeMessageFilter( MessageFilter* mf ); + + /** + * Returns the message type this MessageSession wants to receive. + * @return ORed list of Message::MessageType values this MessageSession wants to receive. + */ + int types() const { return m_types; } + + /** + * This function resets the session's target JID to its bare form such that + * subsequently sent messages will be sent to that bare JID. The server will + * determine the best resource to deliver to. Useful if the target + * resource changed presence to e.g. away or offline. + */ + void resetResource(); + + /** + * This function can be used to feed a message into the session. Ususally, only + * ClientBase should call this function. + * @param msg A Message to feed into the session. + */ + virtual void handleMessage( Message& msg ); + + protected: + /** + * A wrapper around ClientBase::send(). You should @b not use this function to send a + * chat message because the Tag is not prepared accordingly (neither a thread ID is added nor is + * the message ran through the message filters). + * @param msg A Message to send. + */ + virtual void send( const Message& msg ); + void decorate( Message& msg ); + + ClientBase* m_parent; + JID m_target; + MessageHandler* m_messageHandler; + + private: + void setResource( const std::string& resource ); + + typedef std::list MessageFilterList; + MessageFilterList m_messageFilterList; + + std::string m_thread; + int m_types; + bool m_wantUpgrade; + bool m_hadMessages; + bool m_honorThreadID; + + }; + +} + +#endif // MESSAGESESSION_H__ diff --git a/libs/libgloox/messagesessionhandler.h b/libs/libgloox/messagesessionhandler.h new file mode 100644 index 0000000..f6c4003 --- /dev/null +++ b/libs/libgloox/messagesessionhandler.h @@ -0,0 +1,67 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef MESSAGESESSIONHANDLER_H__ +#define MESSAGESESSIONHANDLER_H__ + +#include "stanza.h" +#include "messagesession.h" + +namespace gloox +{ + + /** + * @brief A virtual interface which can be reimplemented to receive incoming message sessions. + * + * Derived classes can be registered as MessageSessionHandlers with the Client. + * If you have enabled automatic MessageSession creation by calling Client::setAutoMessageSession(), + * handleMessageSession() will be called if a message stanza arrives for which there is no + * MessageSession yet. + * + * @author Jakob Schroeter + * @since 0.8 + */ + class GLOOX_API MessageSessionHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~MessageSessionHandler() {} + + /** + * Reimplement this function if you want to be notified about + * incoming messages by means of automatically created MessageSessions. + * You receive ownership of the supplied session (@b not the stanza) and + * are responsible for deleting it at the end of its life. + * + * @note Make sure to read the note in ClientBase::registerMessageSessionHandler() + * regarding the feeding of decorators. + * + * @note After receiving a MessageSession your object is the owner and is responsible + * for the destruction of the session. + * + * @note If you don't need the MessageSession, you should not delete it here. You will + * get an endless loop if you do. + * + * @note You should register your MessageHandler here, or else the first message + * (that caused the MessageSession to be created) may get lost. + * + * @param session The new MessageSession. + */ + virtual void handleMessageSession( MessageSession* session ) = 0; + }; + +} + +#endif // MESSAGESESSIONHANDLER_H__ diff --git a/libs/libgloox/mucinvitationhandler.h b/libs/libgloox/mucinvitationhandler.h new file mode 100644 index 0000000..fed2a06 --- /dev/null +++ b/libs/libgloox/mucinvitationhandler.h @@ -0,0 +1,72 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef MUCINVITATIONHANDLER_H__ +#define MUCINVITATIONHANDLER_H__ + +#include "clientbase.h" +#include "macros.h" +#include "jid.h" +#include "mucroom.h" + +#include + +namespace gloox +{ + + /** + * @brief A handler that can be used to receive invitations to MUC rooms. + * + * Register a derived class with ClientBase::registerMUCInvitationHandler(). + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API MUCInvitationHandler + { + public: + /** + * Constructor. Prepares the given ClientBase for receiving MUC invitations.. + * @param parent A ClientBase instance to prepare. + */ + MUCInvitationHandler( ClientBase* parent ) + { + if( parent ) + parent->registerStanzaExtension( new MUCRoom::MUCUser() ); + } + + /** + * Virtual Destructor. + */ + virtual ~MUCInvitationHandler() {} + + /** + * This function is called for incoming invitations to MUC rooms. + * @param room The JID of the room you're being invited to. + * @param from The JID of the inviter. + * @param reason A reason for the invitation. + * @param body The body of the message. May contain a MUC-service generated invitation message. + * @param password Optionally, a password for the room. + * @param cont Indicates whether or not the multi-user chat is a continuation of a private chat. + * @param thread An optional thread identifier in case this is a + * continued chat. + */ + virtual void handleMUCInvitation( const JID& room, const JID& from, const std::string& reason, + const std::string& body, const std::string& password, + bool cont, const std::string& thread ) = 0; + }; + +} + +#endif // MUCINVITATIONHANDLER_H__ diff --git a/libs/libgloox/mucmessagesession.cpp b/libs/libgloox/mucmessagesession.cpp new file mode 100644 index 0000000..36f97b6 --- /dev/null +++ b/libs/libgloox/mucmessagesession.cpp @@ -0,0 +1,54 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "mucmessagesession.h" +#include "clientbase.h" +#include "message.h" +#include "messagehandler.h" + +namespace gloox +{ + + MUCMessageSession::MUCMessageSession( ClientBase* parent, const JID& jid ) + : MessageSession( parent, jid, false, Message::Groupchat | Message::Chat + | Message::Normal | Message::Error, + false ) + { + } + + MUCMessageSession::~MUCMessageSession() + { + } + + void MUCMessageSession::handleMessage( Message& msg ) + { + if( m_messageHandler ) + m_messageHandler->handleMessage( msg ); + } + + void MUCMessageSession::send( const std::string& message ) + { + Message m( Message::Groupchat, m_target, message ); + +// decorate( m ); + + m_parent->send( m ); + } + + void MUCMessageSession::setSubject( const std::string& subject ) + { + Message m( Message::Groupchat, m_target.bareJID(), EmptyString, subject ); + m_parent->send( m ); + } + +} diff --git a/libs/libgloox/mucmessagesession.h b/libs/libgloox/mucmessagesession.h new file mode 100644 index 0000000..ece85c9 --- /dev/null +++ b/libs/libgloox/mucmessagesession.h @@ -0,0 +1,66 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef MUCMESSAGESESSION_H__ +#define MUCMESSAGESESSION_H__ + +#include "messagesession.h" + +namespace gloox +{ + + class ClientBase; + + /** + * @brief This is a MessageSession, adapted to be used in a MUC context. + * + * This class is used internally by MUCRoom. You should not need to use it directly. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API MUCMessageSession : public MessageSession + { + public: + /** + * Creates a new MUCMessageSession. + * @param parent The ClientBase to use for communication. + * @param jid The @b bare JID of the MUC room. + */ + MUCMessageSession( ClientBase* parent, const JID& jid ); + + /** + * Virtual Destructor. + */ + virtual ~MUCMessageSession(); + + /** + * Use this function to send a message to all room occupants. + * @param message The message to send. + */ + virtual void send( const std::string& message ); + + /** + * Use this function to set a new room subject. + * @param subject The new room subject. + */ + virtual void setSubject( const std::string& subject ); + + // reimplemented from MessageSession + virtual void handleMessage( Message& msg ); + + }; + +} + +#endif // MUCMESSAGESESSION_H__ diff --git a/libs/libgloox/mucroom.cpp b/libs/libgloox/mucroom.cpp new file mode 100644 index 0000000..d7074fb --- /dev/null +++ b/libs/libgloox/mucroom.cpp @@ -0,0 +1,1317 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "mucroom.h" +#include "clientbase.h" +#include "dataform.h" +#include "presence.h" +#include "disco.h" +#include "mucmessagesession.h" +#include "message.h" +#include "error.h" +#include "util.h" +#include "tag.h" + +namespace gloox +{ + + // ---- MUCRoom::MUCAdmin ---- + /* Error type values */ + static const char* affiliationValues [] = { + "none", + "outcast", + "member", + "owner", + "admin" + }; + + /* Stanza error values */ + static const char* roleValues [] = { + "none", + "visitor", + "participant", + "moderator", + }; + + /** Strings indicating the type of history to request. */ + const char* historyTypeValues[] = + { + "maxchars", "maxstanzas", "seconds", "since" + }; + + static inline MUCRoomAffiliation affiliationType( const std::string& type ) + { + return (MUCRoomAffiliation)util::lookup( type, affiliationValues ); + } + + static inline MUCRoomRole roleType( const std::string& type ) + { + return (MUCRoomRole)util::lookup( type, roleValues ); + } + + MUCRoom::MUCAdmin::MUCAdmin( MUCRoomRole role, const std::string& nick, + const std::string& reason ) + : StanzaExtension( ExtMUCAdmin ), m_affiliation( AffiliationInvalid ), m_role( role ) + { + m_list.push_back( MUCListItem( nick, role, reason ) ); + } + + MUCRoom::MUCAdmin::MUCAdmin( MUCRoomAffiliation affiliation, const std::string& nick, + const std::string& reason ) + : StanzaExtension( ExtMUCAdmin ), m_affiliation( affiliation ), m_role( RoleInvalid ) + { + m_list.push_back( MUCListItem( nick, affiliation, reason ) ); + } + + MUCRoom::MUCAdmin::MUCAdmin( MUCOperation operation, const MUCListItemList& jids ) + : StanzaExtension( ExtMUCAdmin ), m_list( jids ), m_affiliation( AffiliationInvalid ), + m_role( RoleInvalid ) + { + switch( operation ) + { + case StoreVoiceList: + case RequestVoiceList: + m_role = RoleParticipant; + break; + case StoreModeratorList: + case RequestModeratorList: + m_role = RoleModerator; + break; + case StoreBanList: + case RequestBanList: + m_affiliation = AffiliationOutcast; + break; + case StoreMemberList: + case RequestMemberList: + m_affiliation = AffiliationMember; + break; + case StoreOwnerList: + case RequestOwnerList: + m_affiliation = AffiliationOwner; + break; + case StoreAdminList: + case RequestAdminList: + m_affiliation = AffiliationAdmin; + break; + default: + return; + break; + } + + if( m_list.empty() ) + m_list.push_back( MUCListItem( JID() ) ); + } + + MUCRoom::MUCAdmin::MUCAdmin( const Tag* tag ) + : StanzaExtension( ExtMUCAdmin ), m_affiliation( AffiliationInvalid ), m_role( RoleInvalid ) + { + if( !tag || tag->name() != "query" || tag->xmlns() != XMLNS_MUC_ADMIN ) + return; + + const TagList& items = tag->findChildren( "item" ); + TagList::const_iterator it = items.begin(); + for( ; it != items.end(); ++it ) + { + m_list.push_back( MUCListItem( JID( (*it)->findAttribute( "jid" ) ), + roleType( (*it)->findAttribute( "role" ) ), + affiliationType( (*it)->findAttribute( "affiliation" ) ), + (*it)->findAttribute( "nick" ) ) ); + if( m_role == RoleInvalid ) + m_role = roleType( (*it)->findAttribute( "role" ) ); + if( m_affiliation == AffiliationInvalid ) + m_affiliation = affiliationType( (*it)->findAttribute( "affiliation" ) ); + } + } + + MUCRoom::MUCAdmin::~MUCAdmin() + { + } + + const std::string& MUCRoom::MUCAdmin::filterString() const + { + static const std::string filter = "/iq/query[@xmlns='" + XMLNS_MUC_ADMIN + "']"; + return filter; + } + + Tag* MUCRoom::MUCAdmin::tag() const + { + Tag* t = new Tag( "query" ); + t->setXmlns( XMLNS_MUC_ADMIN ); + + if( m_list.empty() || ( m_affiliation == AffiliationInvalid && m_role == RoleInvalid ) ) + return t; + + MUCListItemList::const_iterator it = m_list.begin(); + for( ; it != m_list.end(); ++it ) + { + Tag* i = new Tag( t, "item" ); + if( (*it).jid() ) + i->addAttribute( "jid", (*it).jid().bare() ); + if( !(*it).nick().empty() ) + i->addAttribute( "nick", (*it).nick() ); + + MUCRoomRole rol = RoleInvalid; + if( (*it).role() != RoleInvalid ) + rol = (*it).role(); + else if( m_role != RoleInvalid ) + rol = m_role; + if( rol != RoleInvalid ) + i->addAttribute( "role", util::lookup( rol, roleValues ) ); + + MUCRoomAffiliation aff = AffiliationInvalid; + if( (*it).affiliation() != AffiliationInvalid ) + aff = (*it).affiliation(); + else if( m_affiliation != AffiliationInvalid ) + aff = m_affiliation; + if( aff != AffiliationInvalid ) + i->addAttribute( "affiliation", util::lookup( aff, affiliationValues ) ); + if( !(*it).reason().empty() ) + new Tag( i, "reason", (*it).reason() ); + } + + return t; + } + // ---- ~MUCRoom::MUCAdmin ---- + + // ---- MUCRoom::MUCOwner ---- + MUCRoom::MUCOwner::MUCOwner( QueryType type, DataForm* form ) + : StanzaExtension( ExtMUCOwner ), m_type( type ), m_form( form ) + { + m_valid = true; + + if( m_form ) + return; + + switch( type ) + { + case TypeCancelConfig: + m_form = new DataForm( TypeCancel ); + break; + case TypeInstantRoom: + m_form = new DataForm( TypeSubmit ); + break; + default: + break; + } + } + + MUCRoom::MUCOwner::MUCOwner( const JID& alternate, const std::string& reason, + const std::string& password ) + : StanzaExtension( ExtMUCOwner ), m_type( TypeDestroy ), m_jid( alternate ), + m_reason( reason ), m_pwd( password ), m_form( 0 ) + { + m_valid = true; + } + + MUCRoom::MUCOwner::MUCOwner( const Tag* tag ) + : StanzaExtension( ExtMUCOwner ), m_type( TypeIncomingTag ), m_form( 0 ) + { + if( !tag || tag->name() != "query" || tag->xmlns() != XMLNS_MUC_OWNER ) + return; + + const TagList& l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + const std::string& name = (*it)->name(); + if( name == "x" && (*it)->xmlns() == XMLNS_X_DATA ) + { + m_form = new DataForm( (*it) ); + break; + } + else if( name == "destroy" ) + { + m_type = TypeDestroy; + m_jid = (*it)->findAttribute( "jid" ); + m_pwd = (*it)->findCData( "/query/destroy/password" ); + m_reason = (*it)->findCData( "/query/destroy/reason" ); + break; + } + } + m_valid = true; + } + + MUCRoom::MUCOwner::~MUCOwner() + { + delete m_form; + } + + const std::string& MUCRoom::MUCOwner::filterString() const + { + static const std::string filter = "/iq/query[@xmlns='" + XMLNS_MUC_OWNER + "']"; + return filter; + } + + Tag* MUCRoom::MUCOwner::tag() const + { + if( !m_valid ) + return 0; + + Tag* t = new Tag( "query" ); + t->setXmlns( XMLNS_MUC_OWNER ); + + switch( m_type ) + { + case TypeInstantRoom: + case TypeSendConfig: + case TypeCancelConfig: + case TypeIncomingTag: + if( m_form ) + t->addChild( m_form->tag() ); + break; + case TypeDestroy: + { + Tag* d = new Tag( t, "destroy" ); + if( m_jid ) + d->addAttribute( "jid", m_jid.bare() ); + + if( !m_reason.empty() ) + new Tag( d, "reason", m_reason ); + + if( !m_pwd.empty() ) + new Tag( d, "password", m_pwd ); + + break; + } + case TypeRequestConfig: + case TypeCreate: + default: + break; + } + + return t; + } + // ---- ~MUCRoom::MUCOwner ---- + + // ---- MUCRoom::MUCUser ---- + MUCRoom::MUCUser::MUCUser( MUCUserOperation operation, const std::string& to, + const std::string& reason, const std::string& thread ) + : StanzaExtension( ExtMUCUser ), m_affiliation( AffiliationInvalid ), m_role( RoleInvalid ), + m_jid( new std::string( to ) ), m_actor( 0 ), + m_thread( thread.empty() ? 0 : new std::string( thread ) ), + m_reason( new std::string( reason ) ), m_newNick( 0 ), m_password( 0 ), m_alternate( 0 ), + m_operation( operation ), + m_flags( 0 ), m_del( false ), m_continue( !thread.empty() ) + { + } + + MUCRoom::MUCUser::MUCUser( const Tag* tag ) + : StanzaExtension( ExtMUCUser ), m_affiliation( AffiliationInvalid ), m_role( RoleInvalid ), + m_jid( 0 ), m_actor( 0 ), m_thread( 0 ), m_reason( 0 ), m_newNick( 0 ), + m_password( 0 ), m_alternate( 0 ), m_operation( OpNone ), + m_flags( 0 ), m_del( false ), m_continue( false ) + { + if( !tag || tag->name() != "x" || tag->xmlns() != XMLNS_MUC_USER ) + return; + + const Tag* t = 0; + const TagList& l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + if( (*it)->name() == "item" ) + { + m_affiliation = getEnumAffiliation( (*it)->findAttribute( "affiliation" ) ); + m_role = getEnumRole( (*it)->findAttribute( "role" ) ); + + if( (*it)->hasAttribute( "jid" ) ) + m_jid = new std::string( (*it)->findAttribute( "jid" ) ); + + if( ( t = (*it)->findChild( "actor" ) ) ) + m_actor = new std::string( t->findAttribute( "jid" ) ); + + if( ( t = (*it)->findChild( "reason" ) ) ) + m_reason = new std::string( t->cdata() ); + + if( (*it)->hasAttribute( "nick" ) ) + m_newNick = new std::string( (*it)->findAttribute( "nick" ) ); + } + else if( (*it)->name() == "status" ) + { + const std::string& code = (*it)->findAttribute( "code" ); + if( code == "100" ) + m_flags |= FlagNonAnonymous; + else if( code == "101" ) + m_flags |= UserAffiliationChangedWNR; + else if( code == "110" ) + m_flags |= UserSelf; + else if( code == "170" ) + m_flags |= FlagPublicLogging; + else if( code == "201" ) + m_flags |= UserNewRoom; + else if( code == "210" ) + m_flags |= UserNickAssigned; + else if( code == "301" ) + m_flags |= UserBanned; + else if( code == "303" ) + m_flags |= UserNickChanged; + else if( code == "307" ) + m_flags |= UserKicked; + else if( code == "321" ) + m_flags |= UserAffiliationChanged; + else if( code == "322" ) + m_flags |= UserMembershipRequired; + else if( code == "332" ) + m_flags |= UserRoomShutdown; + } + else if( (*it)->name() == "destroy" ) + { + m_del = true; + if( (*it)->hasAttribute( "jid" ) ) + m_alternate = new std::string( (*it)->findAttribute( "jid" ) ); + + if( ( t = (*it)->findChild( "reason" ) ) ) + m_reason = new std::string( t->cdata() ); + + m_flags |= UserRoomDestroyed; + } + else if( (*it)->name() == "invite" ) + { + m_operation = OpInviteFrom; + m_jid = new std::string( (*it)->findAttribute( "from" ) ); + if( m_jid->empty() ) + { + m_operation = OpInviteTo; + m_jid->assign( (*it)->findAttribute( "to" ) ); + } + if( (*it)->hasChild( "reason" ) ) + m_reason = new std::string( (*it)->findChild( "reason" )->cdata() ); + if( (*it)->hasChild( "continue" ) ) + { + m_continue = true; + m_thread = new std::string( (*it)->findChild( "continue" )->findAttribute( "thread" ) ); + } + } + else if( (*it)->name() == "decline" ) + { + m_operation = OpDeclineFrom; + m_jid = new std::string( (*it)->findAttribute( "from" ) ); + if( m_jid->empty() ) + { + m_operation = OpDeclineTo; + m_jid->assign( (*it)->findAttribute( "from" ) ); + } + if( (*it)->hasChild( "reason" ) ) + m_reason = new std::string( (*it)->findChild( "reason" )->cdata() ); + } + else if( (*it)->name() == "password" ) + { + m_password = new std::string( (*it)->cdata() ); + } + } + } + + MUCRoom::MUCUser::~MUCUser() + { + delete m_jid; + delete m_actor; + delete m_thread; + delete m_reason; + delete m_newNick; + delete m_password; + delete m_alternate; + } + + MUCRoomRole MUCRoom::MUCUser::getEnumRole( const std::string& role ) + { + if( role == "moderator" ) + return RoleModerator; + if( role == "participant" ) + return RoleParticipant; + if( role == "visitor" ) + return RoleVisitor; + return RoleNone; + } + + MUCRoomAffiliation MUCRoom::MUCUser::getEnumAffiliation( const std::string& affiliation ) + { + if( affiliation == "owner" ) + return AffiliationOwner; + if( affiliation == "admin" ) + return AffiliationAdmin; + if( affiliation == "member" ) + return AffiliationMember; + if( affiliation == "outcast" ) + return AffiliationOutcast; + return AffiliationNone; + } + + const std::string& MUCRoom::MUCUser::filterString() const + { + static const std::string filter = "/presence/x[@xmlns='" + XMLNS_MUC_USER + "']" + "|/message/x[@xmlns='" + XMLNS_MUC_USER + "']"; + return filter; + } + + Tag* MUCRoom::MUCUser::tag() const + { + Tag* t = new Tag( "x" ); + t->setXmlns( XMLNS_MUC_USER ); + + if( m_affiliation != AffiliationInvalid || m_role != RoleInvalid ) + { + Tag* i = new Tag( t, "item" ); + if( m_jid ) + i->addAttribute( "jid", *m_jid ); + if( m_role != RoleInvalid ) + i->addAttribute( "role", util::lookup( m_role, roleValues ) ); + if( m_affiliation != AffiliationInvalid ) + i->addAttribute( "affiliation", util::lookup( m_affiliation, affiliationValues ) ); + + if( m_actor ) + new Tag( i, "actor", "jid", *m_actor ); + + if( m_flags & FlagNonAnonymous ) + new Tag( t, "status", "code", "100" ); + if( m_flags & UserAffiliationChangedWNR ) + new Tag( t, "status", "code", "101" ); + if( m_flags & UserSelf ) + new Tag( t, "status", "code", "110" ); + if( m_flags & FlagPublicLogging ) + new Tag( t, "status", "code", "170" ); + if( m_flags & UserNewRoom ) + new Tag( t, "status", "code", "201" ); + if( m_flags & UserNickAssigned ) + new Tag( t, "status", "code", "210" ); + if( m_flags & UserBanned ) + new Tag( t, "status", "code", "301" ); + if( m_flags & UserNickChanged ) + new Tag( t, "status", "code", "303" ); + if( m_flags & UserKicked ) + new Tag( t, "status", "code", "307" ); + if( m_flags & UserAffiliationChanged ) + new Tag( t, "status", "code", "321" ); + if( m_flags & UserMembershipRequired ) + new Tag( t, "status", "code", "322" ); + if( m_flags & UserRoomShutdown ) + new Tag( t, "status", "code", "332" ); + } + else if( m_del ) + { + Tag* d = new Tag( t, "destroy" ); + if( m_alternate ) + d->addAttribute( "jid", *m_alternate ); + if( m_reason ) + new Tag( d, "reason", *m_reason ); + } + else if( m_operation != OpNone && m_jid ) + { + Tag* d = 0; + if( m_operation == OpInviteTo ) + d = new Tag( t, "invite", "to", *m_jid ); + else if( m_operation == OpInviteFrom ) + d = new Tag( t, "invite", "from", *m_jid ); + else if( m_operation == OpDeclineTo ) + d = new Tag( t, "decline", "to", *m_jid ); + else if( m_operation == OpDeclineFrom ) + d = new Tag( t, "decline", "from", *m_jid ); + + if( m_reason ) + new Tag( d, "reason", *m_reason ); + + if( m_continue ) + { + Tag* c = new Tag( d, "continue" ); + if( m_thread ) + c->addAttribute( "thread", *m_thread ); + } + + if( m_password ) + new Tag( t, "password", *m_password ); + + } + + return t; + } + // ---- ~MUCRoom::MUCUser ---- + + // ---- MUCRoom::MUC ---- + MUCRoom::MUC::MUC( const std::string& password, + MUCRoom::HistoryRequestType historyType, + const std::string& historySince, + int historyValue ) + : StanzaExtension( ExtMUC ), + m_password( password.empty() ? 0 : new std::string( password ) ), + m_historySince( new std::string( historySince ) ), + m_historyType( historyType ), m_historyValue( historyValue ) + { + } + + MUCRoom::MUC::MUC( const Tag* tag ) + : StanzaExtension( ExtMUC ), + m_password( 0 ), m_historySince( 0 ), + m_historyType( HistoryUnknown ), m_historyValue( 0 ) + { + if( !tag || tag->name() != "x" || tag->xmlns() != XMLNS_MUC_USER ) + return; + + const TagList& l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + if( (*it)->name() == "history" ) + { + if( (*it)->hasAttribute( "seconds" ) ) + m_historyValue = atoi( (*it)->findAttribute( "seconds" ).c_str() ); + else if( (*it)->hasAttribute( "maxstanzas" ) ) + m_historyValue = atoi( (*it)->findAttribute( "maxstanzas" ).c_str() ); + else if( (*it)->hasAttribute( "maxchars" ) ) + m_historyValue = atoi( (*it)->findAttribute( "maxchars" ).c_str() ); + else if( (*it)->hasAttribute( "since" ) ) + m_historySince = new std::string( (*it)->findAttribute( "since" ) ); + } + else if( (*it)->name() == "password" ) + { + m_password = new std::string( (*it)->cdata() ); + } + } + } + + MUCRoom::MUC::~MUC() + { + delete m_password; + delete m_historySince; + } + + const std::string& MUCRoom::MUC::filterString() const + { + static const std::string filter = "/presence/x[@xmlns='" + XMLNS_MUC + "']"; + return filter; + } + + Tag* MUCRoom::MUC::tag() const + { + Tag* t = new Tag( "x" ); + t->setXmlns( XMLNS_MUC ); + + if( m_historyType != HistoryUnknown ) + { + const std::string& histStr = util::lookup( m_historyType, historyTypeValues ); + Tag* h = new Tag( t, "history" ); + if( m_historyType == HistorySince && m_historySince ) + h->addAttribute( histStr, *m_historySince ); + else + h->addAttribute( histStr, m_historyValue ); + } + + if( m_password ) + new Tag( t, "password", *m_password ); + + return t; + } + // ---- ~MUCRoom::MUC ---- + + // --- MUCRoom ---- + MUCRoom::MUCRoom( ClientBase* parent, const JID& nick, MUCRoomHandler* mrh, + MUCRoomConfigHandler* mrch ) + : m_parent( parent ), m_nick( nick ), m_joined( false ), m_roomHandler( mrh ), + m_roomConfigHandler( mrch ), m_affiliation( AffiliationNone ), m_role( RoleNone ), + m_historyType( HistoryUnknown ), m_historyValue( 0 ), m_flags( 0 ), + m_creationInProgress( false ), m_configChanged( false ), + m_publishNick( false ), m_publish( false ), m_unique( false ) + { + if( m_parent ) + { + m_parent->registerStanzaExtension( new MUCAdmin() ); + m_parent->registerStanzaExtension( new MUCOwner() ); + m_parent->registerStanzaExtension( new MUCUser() ); + m_parent->registerStanzaExtension( new MUC() ); + m_parent->registerStanzaExtension( new DelayedDelivery() ); + } + } + + MUCRoom::~MUCRoom() + { + if( m_joined ) + leave(); + + if( m_parent ) + { + if( m_publish ) + m_parent->disco()->removeNodeHandler( this, XMLNS_MUC_ROOMS ); + + m_parent->removeIDHandler( this ); +// m_parent->removeStanzaExtension( ExtMUCAdmin ); // don't remove, other rooms might need it +// m_parent->removeStanzaExtension( ExtMUCOwner ); + m_parent->removePresenceHandler( m_nick.bareJID(), this ); + m_parent->disco()->removeDiscoHandler( this ); + } + } + + void MUCRoom::join( Presence::PresenceType type, const std::string& status, int priority ) + { + if( m_joined || !m_parent ) + return; + + m_parent->registerPresenceHandler( m_nick.bareJID(), this ); + + m_session = new MUCMessageSession( m_parent, m_nick.bareJID() ); + m_session->registerMessageHandler( this ); + + Presence pres( type, m_nick.full(), status, priority ); + pres.addExtension( new MUC( m_password, m_historyType, m_historySince, m_historyValue ) ); + m_joined = true; + m_parent->send( pres ); + } + + void MUCRoom::leave( const std::string& msg ) + { + if( !m_joined ) + return; + + if( m_parent ) + { + Presence pres( Presence::Unavailable, m_nick.full(), msg ); + m_parent->send( pres ); + m_parent->removePresenceHandler( m_nick.bareJID(), this ); + m_parent->disposeMessageSession( m_session ); + } + + m_session = 0; + m_joined = false; + } + + void MUCRoom::destroy( const std::string& reason, const JID& alternate, const std::string& password ) + { + if( !m_parent ) + return; + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Set, m_nick.bareJID(), id ); + iq.addExtension( new MUCOwner( alternate, reason, password ) ); + m_parent->send( iq, this, DestroyRoom ); + } + + void MUCRoom::send( const std::string& message ) + { + if( m_session && m_joined ) + m_session->send( message ); + } + + void MUCRoom::setSubject( const std::string& subject ) + { + if( m_session && m_joined ) + m_session->setSubject( subject ); + } + + void MUCRoom::setNick( const std::string& nick ) + { + if( m_parent && m_joined ) + { + m_newNick = nick; + + Presence p( Presence::Available, m_nick.bare() + "/" + m_newNick ); + m_parent->send( p ); + } + else + m_nick.setResource( nick ); + } + + void MUCRoom::getRoomInfo() + { + if( m_parent ) + m_parent->disco()->getDiscoInfo( m_nick.bare(), EmptyString, this, GetRoomInfo ); + } + + void MUCRoom::getRoomItems() + { + if( m_parent ) + m_parent->disco()->getDiscoItems( m_nick.bare(), EmptyString, this, GetRoomItems ); + } + + void MUCRoom::setPresence( Presence::PresenceType presence, const std::string& msg ) + { + if( m_parent && presence != Presence::Unavailable && m_joined ) + { + Presence p( presence, m_nick.full(), msg ); + m_parent->send( p ); + } + } + + void MUCRoom::invite( const JID& invitee, const std::string& reason, const std::string& thread ) + { + if( !m_parent || !m_joined ) + return; + + Message msg( Message::Normal, m_nick.bareJID() ); + msg.addExtension( new MUCUser( OpInviteTo, invitee.bare(), reason, thread ) ); + m_parent->send( msg ); + } + + Message* MUCRoom::declineInvitation( const JID& room, const JID& invitor, const std::string& reason ) + { + Message* msg = new Message( Message::Normal, room.bare() ); + msg->addExtension( new MUCUser( OpDeclineTo, invitor.bare(), reason ) ); + return msg; + } + + void MUCRoom::setPublish( bool publish, bool publishNick ) + { + m_publish = publish; + m_publishNick = publishNick; + + if( !m_parent ) + return; + + if( m_publish ) + m_parent->disco()->registerNodeHandler( this, XMLNS_MUC_ROOMS ); + else + m_parent->disco()->removeNodeHandler( this, XMLNS_MUC_ROOMS ); + } + + void MUCRoom::addHistory( const std::string& message, const JID& from, const std::string& stamp ) + { + if( !m_joined || !m_parent ) + return; + + Message m( Message::Groupchat, m_nick.bareJID(), message ); + m.addExtension( new DelayedDelivery( from, stamp ) ); + m_parent->send( m ); + } + + void MUCRoom::setRequestHistory( int value, MUCRoom::HistoryRequestType type ) + { + m_historyType = type; + m_historySince = EmptyString; + m_historyValue = value; + } + + void MUCRoom::setRequestHistory( const std::string& since ) + { + m_historyType = HistorySince; + m_historySince = since; + m_historyValue = 0; + } + + Message* MUCRoom::createDataForm( const JID& room, const DataForm* df ) + { + Message* m = new Message( Message::Normal, room.bare() ); + m->addExtension( df ); + return m; + } + + void MUCRoom::requestVoice() + { + if( !m_parent || !m_joined ) + return; + + DataForm* df = new DataForm( TypeSubmit ); + df->addField( DataFormField::TypeNone, "FORM_TYPE", XMLNS_MUC_REQUEST ); + df->addField( DataFormField::TypeTextSingle, "muc#role", "participant", "Requested role" ); + + Message m( Message::Normal, m_nick.bare() ); + m.addExtension( df ); + + m_parent->send( m ); + } + + void MUCRoom::setRole( const std::string& nick, MUCRoomRole role, + const std::string& reason ) + { + if( !m_parent || !m_joined || nick.empty() || role == RoleInvalid ) + return; + + MUCOperation action = InvalidOperation; + switch( role ) + { + case RoleNone: + action = SetRNone; + break; + case RoleVisitor: + action = SetVisitor; + break; + case RoleParticipant: + action = SetParticipant; + break; + case RoleModerator: + action = SetModerator; + break; + default: + break; + } + + IQ iq( IQ::Set, m_nick.bareJID() ); + iq.addExtension( new MUCAdmin( role, nick, reason ) ); + + m_parent->send( iq, this, action ); + } + + void MUCRoom::setAffiliation( const std::string& nick, MUCRoomAffiliation affiliation, + const std::string& reason ) + { + if( !m_parent || !m_joined || nick.empty() || affiliation == AffiliationInvalid ) + return; + + MUCOperation action = InvalidOperation; + switch( affiliation ) + { + case AffiliationOutcast: + action = SetOutcast; + break; + case AffiliationNone: + action = SetANone; + break; + case AffiliationMember: + action = SetMember; + break; + case AffiliationAdmin: + action = SetAdmin; + break; + case AffiliationOwner: + action = SetOwner; + break; + default: + break; + } + + IQ iq( IQ::Set, m_nick.bareJID() ); + iq.addExtension( new MUCAdmin( affiliation, nick, reason ) ); + + m_parent->send( iq, this, action ); + } + + void MUCRoom::requestList( MUCOperation operation ) + { + if( !m_parent || !m_joined || !m_roomConfigHandler ) + return; + + IQ iq( IQ::Get, m_nick.bareJID() ); + iq.addExtension( new MUCAdmin( operation ) ); + m_parent->send( iq, this, operation ); + } + + void MUCRoom::storeList( const MUCListItemList items, MUCOperation operation ) + { + if( !m_parent || !m_joined ) + return; + + IQ iq( IQ::Set, m_nick.bareJID() ); + iq.addExtension( new MUCAdmin( operation , items ) ); + m_parent->send( iq, this, operation ); + } + + void MUCRoom::handlePresence( const Presence& presence ) + { + if( ( presence.from().bare() != m_nick.bare() ) || !m_roomHandler ) + return; + + if( presence.subtype() == Presence::Error ) + { + if( m_newNick.empty() ) + { + m_parent->removePresenceHandler( m_nick.bareJID(), this ); + m_parent->disposeMessageSession( m_session ); + m_joined = false; + m_session = 0; + } + else + m_newNick = ""; + + m_roomHandler->handleMUCError( this, presence.error() + ? presence.error()->error() + : StanzaErrorUndefined ); + } + else + { + const MUCUser* mu = presence.findExtension( ExtMUCUser ); + if( !mu ) + return; + + MUCRoomParticipant party; + party.nick = new JID( presence.from() ); + party.status = presence.status(); + party.affiliation = mu->affiliation(); + party.role = mu->role(); + party.jid = mu->jid() ? new JID( *(mu->jid()) ) : 0; + party.actor = mu->actor() ? new JID( *(mu->actor()) ) : 0; + party.reason = mu->reason() ? *(mu->reason()) : EmptyString; + party.newNick = mu->newNick() ? *(mu->newNick()) : EmptyString; + party.alternate = mu->alternate() ? new JID( *(mu->alternate()) ) : 0; + party.flags = mu->flags(); + + if( party.flags & FlagNonAnonymous ) + setNonAnonymous(); + + if( party.flags & UserSelf ) + { + m_role = party.role; + m_affiliation = party.affiliation; + } + if( party.flags & UserNewRoom ) + { + m_creationInProgress = true; + if( instantRoomHook() || m_roomHandler->handleMUCRoomCreation( this ) ) + acknowledgeInstantRoom(); + } + if( party.flags & UserNickAssigned ) + m_nick.setResource( presence.from().resource() ); + + if( party.flags & UserNickChanged && !party.newNick.empty() + && m_nick.resource() == presence.from().resource() + && party.newNick == m_newNick ) + party.flags |= UserSelf; + + if( party.flags & UserNickChanged && party.flags & UserSelf && !party.newNick.empty() ) + m_nick.setResource( party.newNick ); + + if( m_roomHandler ) + m_roomHandler->handleMUCParticipantPresence( this, party, presence ); + + delete party.nick; + } + } + + void MUCRoom::instantRoom( int context ) + { + if( !m_creationInProgress || !m_parent || !m_joined ) + return; + + IQ iq( IQ::Set, m_nick.bareJID() ); + iq.addExtension( new MUCOwner( context == CreateInstantRoom + ? MUCOwner::TypeInstantRoom : MUCOwner::TypeCancelConfig ) ); + + m_parent->send( iq, this, context ); + + m_creationInProgress = false; + } + + void MUCRoom::requestRoomConfig() + { + if( !m_parent || !m_joined ) + return; + + IQ iq( IQ::Get, m_nick.bareJID() ); + iq.addExtension( new MUCOwner( MUCOwner::TypeRequestConfig ) ); + + m_parent->send( iq, this, RequestRoomConfig ); + + if( m_creationInProgress ) + m_creationInProgress = false; + } + + void MUCRoom::setRoomConfig( DataForm* form ) + { + if( !m_parent || !m_joined ) + return; + + IQ iq( IQ::Set, m_nick.bareJID() ); + iq.addExtension( new MUCOwner( MUCOwner::TypeSendConfig, form ) ); + + m_parent->send( iq, this, SendRoomConfig ); + } + + void MUCRoom::setNonAnonymous() + { + m_flags |= FlagNonAnonymous; + m_flags &= ~( FlagSemiAnonymous | FlagFullyAnonymous ); + } + + void MUCRoom::setSemiAnonymous() + { + m_flags &= ~( FlagNonAnonymous | FlagFullyAnonymous ); + m_flags |= FlagSemiAnonymous; + } + + void MUCRoom::setFullyAnonymous() + { + m_flags &= ~( FlagNonAnonymous | FlagSemiAnonymous ); + m_flags |= FlagFullyAnonymous; + } + + void MUCRoom::handleMessage( const Message& msg, MessageSession* /*session*/ ) + { + if( !m_roomHandler ) + return; + + if( msg.subtype() == Message::Error ) + { + m_roomHandler->handleMUCError( this, msg.error() ? msg.error()->error() : StanzaErrorUndefined ); + } + else + { + const MUCUser* mu = msg.findExtension( ExtMUCUser ); + if( mu ) + { + const int flags = mu->flags(); + if( flags & FlagNonAnonymous ) + setNonAnonymous(); + if( flags & FlagPublicLogging ) + { + m_flags &= ~FlagPublicLoggingOff; + m_flags |= FlagPublicLogging; + } + if( flags & FlagPublicLoggingOff ) + { + m_flags &= ~FlagPublicLogging; + m_flags |= FlagPublicLoggingOff; + } + if( flags & FlagSemiAnonymous ) + setSemiAnonymous(); + if( flags & FlagFullyAnonymous ) + setFullyAnonymous(); + + if( mu->operation() == OpDeclineFrom && mu->jid() ) + m_roomHandler->handleMUCInviteDecline( this, JID( *(mu->jid()) ), + mu->reason() ? *(mu->reason()) : EmptyString ); + } + + const DataForm* df = msg.findExtension( ExtDataForm ); + if( m_roomConfigHandler && df ) + { + m_roomConfigHandler->handleMUCRequest( this, *df ); + return; + } + + if( !msg.subject().empty() ) + { + m_roomHandler->handleMUCSubject( this, msg.from().resource(), msg.subject() ); + } + else if( !msg.body().empty() ) + { + std::string when; + bool privMsg = false; + bool history = false; + if( msg.when() ) + { + when = msg.when()->stamp(); + history = true; + } + if( msg.subtype() & ( Message::Chat | Message::Normal ) ) + privMsg = true; + + m_roomHandler->handleMUCMessage( this, msg, privMsg ); + } + } + } + + void MUCRoom::handleIqID( const IQ& iq, int context ) + { + if( !m_roomConfigHandler ) + return; + + switch( iq.subtype() ) + { + case IQ::Result: + handleIqResult( iq, context ); + break; + case IQ::Error: + handleIqError( iq, context ); + break; + default: + break; + } + } + + void MUCRoom::handleIqResult( const IQ& iq, int context ) + { + switch( context ) + { + case SetRNone: + case SetVisitor: + case SetParticipant: + case SetModerator: + case SetANone: + case SetOutcast: + case SetMember: + case SetAdmin: + case SetOwner: + case CreateInstantRoom: + case CancelRoomCreation: + case DestroyRoom: + case StoreVoiceList: + case StoreBanList: + case StoreMemberList: + case StoreModeratorList: + case StoreAdminList: + m_roomConfigHandler->handleMUCConfigResult( this, true, (MUCOperation)context ); + break; + case RequestRoomConfig: + { + const MUCOwner* mo = iq.findExtension( ExtMUCOwner ); + if( !mo ) + break; + + if( mo->form() ) + m_roomConfigHandler->handleMUCConfigForm( this, *(mo->form()) ); + break; + } + case RequestVoiceList: + case RequestBanList: + case RequestMemberList: + case RequestModeratorList: + case RequestOwnerList: + case RequestAdminList: + { + const MUCAdmin* ma = iq.findExtension( ExtMUCAdmin ); + if( !ma ) + break; + + m_roomConfigHandler->handleMUCConfigList( this, ma->list(), (MUCOperation)context ); + break; + } + default: + break; + } + } + + void MUCRoom::handleIqError( const IQ& /*iq*/, int context ) + { + switch( context ) + { + case SetRNone: + case SetVisitor: + case SetParticipant: + case SetModerator: + case SetANone: + case SetOutcast: + case SetMember: + case SetAdmin: + case SetOwner: + case CreateInstantRoom: + case CancelRoomCreation: + case RequestRoomConfig: + case DestroyRoom: + case RequestVoiceList: + case StoreVoiceList: + case RequestBanList: + case StoreBanList: + case RequestMemberList: + case StoreMemberList: + case RequestModeratorList: + case StoreModeratorList: + case RequestOwnerList: + case StoreOwnerList: + case RequestAdminList: + case StoreAdminList: + m_roomConfigHandler->handleMUCConfigResult( this, false, (MUCOperation)context ); + break; + } + } + + void MUCRoom::handleDiscoInfo( const JID& /*from*/, const Disco::Info& info, int context ) + { + switch( context ) + { + case GetRoomInfo: + { + int oldflags = m_flags; + m_flags = 0; + if( oldflags & FlagPublicLogging ) + m_flags |= FlagPublicLogging; + + std::string name; + const StringList& l = info.features(); + StringList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + if( (*it) == "muc_hidden" ) + m_flags |= FlagHidden; + else if( (*it) == "muc_membersonly" ) + m_flags |= FlagMembersOnly; + else if( (*it) == "muc_moderated" ) + m_flags |= FlagModerated; + else if( (*it) == "muc_nonanonymous" ) + setNonAnonymous(); + else if( (*it) == "muc_open" ) + m_flags |= FlagOpen; + else if( (*it) == "muc_passwordprotected" ) + m_flags |= FlagPasswordProtected; + else if( (*it) == "muc_persistent" ) + m_flags |= FlagPersistent; + else if( (*it) == "muc_public" ) + m_flags |= FlagPublic; + else if( (*it) == "muc_semianonymous" ) + setSemiAnonymous(); + else if( (*it) == "muc_temporary" ) + m_flags |= FlagTemporary; + else if( (*it) == "muc_fullyanonymous" ) + setFullyAnonymous(); + else if( (*it) == "muc_unmoderated" ) + m_flags |= FlagUnmoderated; + else if( (*it) == "muc_unsecured" ) + m_flags |= FlagUnsecured; + } + + const Disco::IdentityList& il = info.identities(); + if( il.size() ) + name = il.front()->name(); + + if( m_roomHandler ) + m_roomHandler->handleMUCInfo( this, m_flags, name, info.form() ); + break; + } + default: + break; + } + } + + void MUCRoom::handleDiscoItems( const JID& /*from*/, const Disco::Items& items, int context ) + { + if( !m_roomHandler ) + return; + + switch( context ) + { + case GetRoomItems: + { + m_roomHandler->handleMUCItems( this, items.items() ); + break; + } + default: + break; + } + } + + void MUCRoom::handleDiscoError( const JID& /*from*/, const Error* /*error*/, int context ) + { + if( !m_roomHandler ) + return; + + switch( context ) + { + case GetRoomInfo: + m_roomHandler->handleMUCInfo( this, 0, EmptyString, 0 ); + break; + case GetRoomItems: + m_roomHandler->handleMUCItems( this, Disco::ItemList() ); + break; + default: + break; + } + } + + StringList MUCRoom::handleDiscoNodeFeatures( const JID& /*from*/, const std::string& /*node*/ ) + { + return StringList(); + } + + Disco::IdentityList MUCRoom::handleDiscoNodeIdentities( const JID& /*from*/, + const std::string& /*node*/ ) + { + return Disco::IdentityList(); + } + + Disco::ItemList MUCRoom::handleDiscoNodeItems( const JID& /*from*/, const JID& /*to*/, + const std::string& node ) + { + Disco::ItemList l; + if( node == XMLNS_MUC_ROOMS && m_publish ) + { + l.push_back( new Disco::Item( m_nick.bareJID(), EmptyString, + m_publishNick ? m_nick.resource() : EmptyString ) ); + } + return l; + } + +} diff --git a/libs/libgloox/mucroom.h b/libs/libgloox/mucroom.h new file mode 100644 index 0000000..444fff5 --- /dev/null +++ b/libs/libgloox/mucroom.h @@ -0,0 +1,994 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef MUCROOM_H__ +#define MUCROOM_H__ + +#include "discohandler.h" +#include "disconodehandler.h" +#include "dataform.h" +#include "presencehandler.h" +#include "iqhandler.h" +#include "messagehandler.h" +#include "mucroomhandler.h" +#include "mucroomconfighandler.h" +#include "jid.h" +#include "stanzaextension.h" + +#include + +namespace gloox +{ + + class ClientBase; + class MUCMessageSession; + class Message; + + /** + * @brief This is an implementation of XEP-0045 (Multi-User Chat). + * + * Usage is pretty simple: + * + * Derrive an object from MUCRoomHandler and implement its virtuals: + * @code + * class MyClass : public MUCRoomHandler + * { + * ... + * }; + * @endcode + * + * Then create a new MUCRoom object and pass it a valid ClientBase, the desired full room JID, + * your MUCRoomHandler-derived object, and an optional MUCRoomConfigHandler-derived object. + * @code + * void MyOtherClass::joinRoom( const std::string& room, const std::string& service, + * const std::string& nick ) + * { + * MyClass* myHandler = new MyClass(...); + * JID roomJID( room + "@" + service + "/" + nick ); + * m_room = new MUCRoom( m_clientbase, roomJID, myHandler, 0 ); + * m_room->join(); + * } + * @endcode + * + * When joining the room was successful, the various MUCRoomHandler functions will start to + * be called. If joining was not successful, MUCRoomHandler::handleMUCError() will be called, + * giving a hint at the reason for the failure. + * + * To set up your own room, or to configure an existing room, you should also derive a + * class from MUCRoomConfigHandler and register it with the MUCRoom (either by using it + * with MUCRoom's constructor, or by calling registerMUCRoomConfigHandler()). + * + * To quickly create an instant room, see InstantMUCRoom. + * + * To quickly create an instant room to turn a one-to-one chat into a multi-user chat, + * see UniqueMUCRoom. + * + * To send a private message to a room participant, use + * @link MessageSession gloox::MessageSession @endlink with the participant's full room JID + * (room\@service/nick). + * + * XEP version: 1.21 + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API MUCRoom : private DiscoHandler, private PresenceHandler, + public IqHandler, private MessageHandler, private DiscoNodeHandler + { + public: + /** + * Allowable history request types. To disable sending of history, use any value except + * HistoryUnknown and specify a zero-length time span (using setRequestHistory()). + */ + enum HistoryRequestType + { + HistoryMaxChars, /**< Limit the total number of characters in the history to "X" + * (where the character count is the characters of the complete + * XML stanzas, not only their XML character data). */ + HistoryMaxStanzas, /**< Limit the total number of messages in the history to "X". */ + HistorySeconds, /**< Send only the messages received in the last "X" seconds. */ + HistorySince, /**< Send only the messages received since the datetime specified + * (which MUST conform to the DateTime profile specified in Jabber + * Date and Time Profiles (XEP-0082)). */ + HistoryUnknown /**< It is up to the service to decide how much history to send. + * This is the default. */ + }; + + /** + * Available operations. + */ + enum MUCUserOperation + { + OpNone, /**< No operation. */ + OpInviteTo, /**< Invitation being sent to soemone. */ + OpInviteFrom, /**< Invitation received from someone. */ + OpDeclineTo, /**< Someone's invitation declined. */ + OpDeclineFrom /**< Someone declined an invitation. */ + }; + + /** + * @brief An abstraction of a MUC query. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class MUC : public StanzaExtension + { + public: + /** + * Creates a new MUC object. + * @param password An optional room password. + * @param historyType The type of room history to request. + * @param historySince A string describing the amount of room history. + * @param historyValue The amount of requested room history. + */ + MUC( const std::string& password, HistoryRequestType historyType = HistoryUnknown, + const std::string& historySince = EmptyString, int historyValue = 0 ); + + /** + * Constructs a new MUCUser object from the given Tag. + * @param tag The Tag to parse. + */ + MUC( const Tag* tag = 0 ); + + /** + * Virtual destructor. + */ + virtual ~MUC(); + + /** + * Returns a pointer to the current password, or 0. + * @return A pointer to the current password, or 0. + */ + const std::string* password() const { return m_password; } + + /** + * Returns a pointer to the description of the amount of room history requested. + * @return A pointer to the description of the amount of room history requested. + */ + const std::string* historySince() const { return m_historySince; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new MUC( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + MUC* m = new MUC(); + m->m_password = m_password ? new std::string( *m_password ) : 0; + m->m_historySince = m_historySince ? new std::string( *m_historySince ) : 0; + m->m_historyType = m_historyType; + m->m_historyValue = m_historyValue; + return m; + } + + private: + std::string* m_password; + std::string* m_historySince; + HistoryRequestType m_historyType; + int m_historyValue; + }; + + /** + * @brief An abstraction of a MUC user query. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class MUCUser : public StanzaExtension + { + public: + /** + * Constructor. + * @param operation An operation to perform. + * @param to The recipient. + * @param reason The reason for the operation. + * @param thread If this is an invitation, and if the invitation is part of + * a transformation of a one-to-one chat to a MUC, include the one-to-one chat's + * thread ID here. Defaults to the empty string (i.e. not a continuation). + */ + MUCUser( MUCUserOperation operation, const std::string& to, const std::string& reason, + const std::string& thread = EmptyString ); + + /** + * Constructs a new MUCUser object from the given Tag. + * @param tag The Tag to parse. + */ + MUCUser( const Tag* tag = 0 ); + + /** + * Virtual destructor. + */ + virtual ~MUCUser(); + + /** + * Returns the current room flags. + * @return The current room flags. + */ + int flags() const { return m_flags; } + + /** + * Returns the user's current room affiliation. + * @return The user's current room affiliation. + */ + MUCRoomAffiliation affiliation() const { return m_affiliation; } + + /** + * Returns the user's current room role. + * @return The user's current room role. + */ + MUCRoomRole role() const { return m_role; } + + /** + * + */ + const std::string* jid() const { return m_jid; } + + /** + * + */ + const std::string* actor() const { return m_actor; } + + /** + * + */ + const std::string* password() const { return m_password; } + + /** + * + */ + const std::string* thread() const { return m_thread; } + + /** + * + */ + const std::string* reason() const { return m_reason; } + + /** + * + */ + const std::string* newNick() const { return m_newNick; } + + /** + * Returns an alternate venue, if set. + * @return An alternate venue, if set. + */ + const std::string* alternate() const { return m_alternate; } + + /** + * Whether or not the 'continue' flag is set. + * @return Whether or not the 'continue' flag is set. + */ + bool continued() const { return m_continue; } + + /** + * Returns the current operation. + * @return The current operation. + */ + MUCUserOperation operation() const { return m_operation; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new MUCUser( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + MUCUser* m = new MUCUser(); + m->m_affiliation = m_affiliation; + m->m_role = m_role; + m->m_jid = m_jid ? new std::string( *m_jid ) : 0; + m->m_actor = m_actor ? new std::string( *m_actor ) : 0; + m->m_thread = m_thread ? new std::string( *m_thread ) : 0; + m->m_reason = m_reason ? new std::string( *m_reason ) : 0; + m->m_newNick = m_newNick ? new std::string( *m_newNick ) : 0; + m->m_password = m_password ? new std::string( *m_password ) : 0; + m->m_alternate = m_alternate ? new std::string( *m_alternate ) : 0; + m->m_operation = m_operation; + m->m_flags = m_flags; + m->m_del = m_del; + m->m_continue = m_continue; + return m; + } + + private: + static MUCRoomAffiliation getEnumAffiliation( const std::string& affiliation ); + static MUCRoomRole getEnumRole( const std::string& role ); + + + MUCRoomAffiliation m_affiliation; + MUCRoomRole m_role; + std::string* m_jid; + std::string* m_actor; + std::string* m_thread; + std::string* m_reason; + std::string* m_newNick; + std::string* m_password; + std::string* m_alternate; + MUCUserOperation m_operation; + int m_flags; + bool m_del; + bool m_continue; + }; + + /** + * Creates a new abstraction of a Multi-User Chat room. The room is not joined automatically. + * Use join() to join the room, use leave() to leave it. + * @param parent The ClientBase object to use for the communication. + * @param nick The room's name and service plus the desired nickname in the form + * room\@service/nick. + * @param mrh The MUCRoomHandler that will listen to room events. May be 0 and may be specified + * later using registerMUCRoomHandler(). However, without one, MUC is no joy. + * @param mrch The MUCRoomConfigHandler that will listen to room config result. Defaults to 0 + * initially. However, at the latest you need one when you create a new room which is not an + * instant room. You can set a MUCRoomConfigHandler using registerMUCRoomConfigHandler(). + */ + MUCRoom( ClientBase* parent, const JID& nick, MUCRoomHandler* mrh, MUCRoomConfigHandler* mrch = 0 ); + + /** + * Virtual Destructor. + */ + virtual ~MUCRoom(); + + /** + * Use this function to set a password to use when joining a (password protected) + * room. + * @param password The password to use for this room. + * @note This function does not password-protect a room. + */ + void setPassword( const std::string& password ) { m_password = password; } + + /** + * A convenience function that returns the room's name. + * @return The room's name. + */ + const std::string name() const { return m_nick.username(); } + + /** + * A convenience function that returns the name/address of the MUC service the room is running on + * (e.g., conference.jabber.org). + * @return The MUC service's name/address. + */ + const std::string service() const { return m_nick.server(); } + + /** + * A convenience function that returns the user's nickname in the room. + * @return The user's nickname. + */ + const std::string nick() const { return m_nick.resource(); } + + /** + * Join this room. + * @param type The presence to join with, defaults to Available. + * @param status The presence's optional status text. + * @param priority The presence's optional priority, defaults to 0. + * ClientBase will automatically include the default Presence extensions added using + * @link gloox::ClientBase::addPresenceExtension() ClientBase::addPresenceExtension() @endlink. + */ + virtual void join( Presence::PresenceType type = Presence::Available, + const std::string& status = EmptyString, + int priority = 0 ); + + /** + * Leave this room. + * @param msg An optional msg indicating the reason for leaving the room. Default: empty. + */ + void leave( const std::string& msg = EmptyString ); + + /** + * Sends a chat message to the room. + * @param message The message to send. + */ + void send( const std::string& message ); + + /** + * Sets the subject of the room to the given string. + * The MUC service may decline the request to set a new subject. You should + * not assume the subject was set successfully util it is acknowledged via the MUCRoomHandler. + * @param subject The new subject. + */ + void setSubject( const std::string& subject ); + + /** + * Returns the user's current affiliation with this room. + * @return The user's current affiliation. + */ + MUCRoomAffiliation affiliation() const { return m_affiliation; } + + /** + * Returns the user's current role in this room. + * @return The user's current role. + */ + MUCRoomRole role() const { return m_role; } + + /** + * Use this function to change the user's nickname in the room. + * The MUC service may decline the request to set a new nickname. You should not assume + * the nick change was successful until it is acknowledged via the MUCRoomHandler. + * @param nick The user's new nickname. + */ + void setNick( const std::string& nick ); + + /** + * Use this function to set the user's presence in this room. It is not possible to + * use Unavailable with this function. + * @param presence The user's new presence. + * @param msg An optional status message. Default: empty. + */ + void setPresence( Presence::PresenceType presence, const std::string& msg = EmptyString ); + + /** + * Use this function to invite another user to this room. + * @param invitee The (bare) JID of the user to invite. + * @param reason The user-supplied reason for the invitation. + * @param thread If this invitation is part of a transformation of a + * one-to-one chat to a MUC, include the one-to-one chat's thread ID here. Defaults + * to the empty string (i.e. not a continuation). + */ + void invite( const JID& invitee, const std::string& reason, const std::string& thread = EmptyString ); + + /** + * Use this function to request basic room info, possibly prior to joining it. + * Results are announced using the MUCRoomHandler. + */ + void getRoomInfo(); + + /** + * Use this function to request information about the current room occupants, + * possibly prior to joining it. The room ay be configured not to disclose such + * information. + * Results are announced using the MUCRoomHandler. + */ + void getRoomItems(); + + /** + * The MUC spec enables other entities to discover via Service Discovery which rooms + * an entity is in. By default, gloox does not publish such info for privacy reasons. + * This function can be used to enable publishing the info for @b this room. + * @param publish Whether to enable other entities to discover the user's presence in + * @b this room. + * @param publishNick Whether to publish the nickname used in the room. This parameter + * is ignored if @c publish is @b false. + */ + void setPublish( bool publish, bool publishNick ); + + /** + * Use this function to register a (new) MUCRoomHandler with this room. There can be only one + * MUCRoomHandler per room at any one time. + * @param mrl The MUCRoomHandler to register. + */ + void registerMUCRoomHandler( MUCRoomHandler* mrl ) { m_roomHandler = mrl; } + + /** + * Use this function to remove the registered MUCRoomHandler. + */ + void removeMUCRoomHandler() { m_roomHandler = 0; } + + /** + * Use this function to register a (new) MUCRoomConfigHandler with this room. There can + * be only one MUCRoomConfigHandler per room at any one time. + * @param mrch The MUCRoomConfigHandler to register. + */ + void registerMUCRoomConfigHandler( MUCRoomConfigHandler* mrch ) { m_roomConfigHandler = mrch; } + + /** + * Use this function to remove the registered MUCRoomConfigHandler. + */ + void removeMUCRoomConfigHandler() { m_roomConfigHandler = 0; } + + /** + * Use this function to add history to a (newly created) room. The use case from the MUC spec + * is to add history to a room that was created in the process of a transformation of a + * one-to-one chat to a multi-user chat. + * @param message A reason for declining the invitation. + * @param from The JID of the original author of this part of the history. + * @param stamp The datetime of the original message in the format: 20061224T12:15:23Z + * @note You should not attempt to use this function before + * MUCRoomHandler::handleMUCParticipantPresence() was called for the first time. + */ + void addHistory( const std::string& message, const JID& from, const std::string& stamp ); + + /** + * Use this function to request room history. Set @c value to zero to disable the room + * history request. You should not use HistorySince type with this function. + * History is sent only once after entering a room. You should use this function before joining. + * @param value Represents either the number of requested characters, the number of requested + * message stanzas, or the number seconds, depending on the value of @c type. + * @param type + * @note If this function is not used to request a specific amount of room history, it is up + * to the MUC service to decide how much history to send. + */ + void setRequestHistory( int value, HistoryRequestType type ); + + /** + * Use this function to request room history since specific datetime. + * History is sent only once after entering a room. You should use this function before joining. + * @param since A string representing a datetime conforming to the DateTime profile specified + * in Jabber Date and Time Profiles (XEP-0082). + * @note If this function is not used to request a specific amount of room history, it is up + * to the MUC service to decide how much history to send. + */ + void setRequestHistory( const std::string& since ); + + /** + * This static function allows to formally decline a MUC + * invitation received via the MUCInvitationListener. + * @param room The JID of the room the invitation came from. + * @param invitor The JID of the invitor. + * @param reason An optional reason for the decline. + * @return A pointer to a Message. You will have to send (and + * possibly delete) this Message manually. + */ + static Message* declineInvitation( const JID& room, const JID& invitor, + const std::string& reason = EmptyString); + + /** + * It is not possible for a visitor to speak in a moderated room. Use this function to request + * voice from the moderator. + */ + void requestVoice(); + + /** + * Use this function to kick a user from the room. + * Depending on service and/or room configuration and role/affiliation + * this may not always succeed. Usually, a role of 'moderator' is necessary. + * @note This is a convenience function. It directly uses setRole() with a MUCRoomRole of RoleNone. + * @param nick The nick of the user to be kicked. + * @param reason An optional reason for the kick. + */ + void kick( const std::string& nick, const std::string& reason = EmptyString ) + { setRole( nick, RoleNone, reason ); } + + /** + * Use this function to ban a user from the room. + * Depending on service and/or room configuration and role/affiliation + * this may not always succeed. Usually, an affiliation of admin is necessary. + * @note This is a convenience function. It directly uses setAffiliation() with a MUCRoomAffiliation + * of RoleOutcast. + * @param nick The nick of the user to be banned. + * @param reason An optional reason for the ban. + */ + void ban( const std::string& nick, const std::string& reason ) + { setAffiliation( nick, AffiliationOutcast, reason ); } + + /** + * Use this function to grant voice to a user in a moderated room. + * Depending on service and/or room configuration and role/affiliation + * this may not always succeed. Usually, a role of 'moderator' is necessary. + * @note This is a convenience function. It directly uses setRole() with a MUCRoomRole + * of RoleParticipant. + * @param nick The nick of the user to be granted voice. + * @param reason An optional reason for the grant. + */ + void grantVoice( const std::string& nick, const std::string& reason ) + { setRole( nick, RoleParticipant, reason ); } + + /** + * Use this function to create a Tag that approves a voice request or registration request + * delivered via MUCRoomConfigHandler::handleMUCVoiceRequest(). You will need to send this + * Tag off manually using Client/ClientBase. + * @param room The room's JID. This is needed because you can use this function outside of + * room context (e.g, if the admin is not in the room). + * @param df The filled-in DataForm from the voice/registration request. The form object + * will be owned by the returned Message. + */ + static Message* createDataForm( const JID& room, const DataForm* df ); + + /** + * Use this function to revoke voice from a user in a moderated room. + * Depending on service and/or room configuration and role/affiliation + * this may not always succeed. Usually, a role of 'moderator' is necessary. + * @note This is a convenience function. It directly uses setRole() with a MUCRoomRole + * of RoleVisitor. + * @param nick The nick of the user. + * @param reason An optional reason for the revoke. + */ + void revokeVoice( const std::string& nick, const std::string& reason ) + { setRole( nick, RoleVisitor, reason ); } + + /** + * Use this function to change the role of a user in the room. + * Usually, at least moderator privileges are required to succeed. + * @param nick The nick of the user who's role shall be modfified. + * @param role The user's new role in the room. + * @param reason An optional reason for the role change. + */ + void setRole( const std::string& nick, MUCRoomRole role, const std::string& reason = EmptyString ); + + /** + * Use this function to change the affiliation of a user in the room. + * Usually, at least admin privileges are required to succeed. + * @param nick The nick of the user who's affiliation shall be modfified. + * @param affiliation The user's new affiliation in the room. + * @param reason An optional reason for the affiliation change. + */ + void setAffiliation( const std::string& nick, MUCRoomAffiliation affiliation, + const std::string& reason ); + + /** + * Use this function to request the room's configuration form. + * It can be used either after MUCRoomHandler::handleMUCRoomCreation() was called, + * or at any later time. + * + * Usually owner privileges are required for this action to + * succeed. + * + * Use setRoomConfig() to send the modified room config back. + */ + void requestRoomConfig(); + + /** + * After requesting (using requestRoomConfig()) and + * editing/filling in the room's configuration, + * use this function to send it back to the server. + * @param form The form to send. The function will delete the + * object pointed to. + */ + void setRoomConfig( DataForm* form ); + + /** + * Use this function to accept the room's default configuration. This function is useful + * only after MUCRoomHandler::handleMUCRoomCreation() was called. This is a NOOP at + * any other time. + */ + void acknowledgeInstantRoom() + { instantRoom( CreateInstantRoom ); } + + /** + * Use this function to cancel the creation of a room. This function is useful only after + * MUCRoomHandler::handleMUCRoomCreation() was called. This is a NOOP at any other time. + */ + void cancelRoomCreation() + { instantRoom( CancelRoomCreation ); } + + /** + * Use this function to destroy the room. All the occupants will be removed from the room. + * @param reason An optional reason for the destruction. + * @param alternate A pointer to a JID of an alternate venue (e.g., another MUC room). + * May be 0. + * @param password An optional password for the alternate venue. + * + * Usually owner privileges are required for this action to succeed. + */ + void destroy( const std::string& reason = EmptyString, + const JID& alternate = JID(), const std::string& password = EmptyString ); + + /** + * Use this function to request a particluar list of room occupants. + * @note There must be a MUCRoomConfigHandler registered with this room for this + * function to be executed. + * @param operation The following types of lists are available: + * @li Voice List: List of people having voice in a moderated room. Use RequestVoiceList. + * @li Members List: List of members of a room. Use RequestMemberList. + * @li Ban List: List of people banned from the room. Use RequestBanList. + * @li Moderator List: List of room moderators. Use RequestModeratorList. + * @li Admin List: List of room admins. Use RequestAdminList. + * @li Owner List: List of room owners. Use RequestOwnerList. + * Any other value of @c operation will be ignored. + */ + void requestList( MUCOperation operation ); + + /** + * Use this function to store a (modified) list for the room. + * @param items The list of items. Example:
+ * You want to set the Voice List. The privilege of Voice refers to the role of Participant. + * Furthermore, you only store the delta of the original (Voice)List. (Optionally, you could + * probably store the whole list, however, remeber to include those items that were modified, + * too.) + * You want to, say, add one occupant to the Voice List, and remove another one. + * Therefore you store: + * @li GuyOne, role participant -- this guy gets voice granted, he/she is now a participant. + * @li GuyTwo, role visitor -- this guy gets voice revoked, he/she is now a mere visitor + * (Visitor is the Role "below" Participant in the privileges hierarchy). + * + * For operations modifying Roles, you should specifiy only the new Role in the MUCListItem + * structure, for those modifying Affiliations, you should only specify the new Affiliation, + * respectively. The nickname is mandatory in the MUCListItem structure. Items without nickname + * will be ignored. + * + * You may specify a reason for the role/affiliation change in the MUCListItem structure. + * You should not specify a JID in the MUCListItem structure, it will be ignored. + * + * @param operation See requestList() for a list of available list types. Any other value will + * be ignored. + */ + void storeList( const MUCListItemList items, MUCOperation operation ); + + /** + * Returns the currently known room flags. + * @return ORed MUCRoomFlag's describing the current room configuration. + */ + int flags() const { return m_flags; } + + // reimplemented from DiscoHandler + virtual void handleDiscoInfo( const JID& from, const Disco::Info& info, int context ); + + // reimplemented from DiscoHandler + // reimplemented from DiscoHandler + virtual void handleDiscoItems( const JID& from, const Disco::Items& items, int context ); + + // reimplemented from DiscoHandler + virtual void handleDiscoError( const JID& from, const Error* error, int context ); + + // reimplemented from PresenceHandler + virtual void handlePresence( const Presence& presence ); + + // reimplemented from MessageHandler + virtual void handleMessage( const Message& msg, MessageSession* session = 0 ); + + // reimplemented from IqHandler + virtual bool handleIq( const IQ& iq ) { (void)iq; return false; } + + // reimplemented from IqHandler + virtual void handleIqID( const IQ& iq, int context ); + + // reimplemented from DiscoNodeHandler + virtual StringList handleDiscoNodeFeatures( const JID& from, const std::string& node ); + + // reimplemented from DiscoNodeHandler + virtual Disco::IdentityList handleDiscoNodeIdentities( const JID& from, + const std::string& node ); + + // reimplemented from DiscoNodeHandler + virtual Disco::ItemList handleDiscoNodeItems( const JID& from, const JID& to, + const std::string& node = EmptyString ); + + protected: + /** + * Sets the room's name. + * @param name The room's name. + */ + void setName( const std::string& name ) { m_nick.setUsername( name ); } + + /** + * Acknowledges instant room creation w/o a call to the MUCRoomConfigHandler. + * @return Whether an instant room is being created. + */ + virtual bool instantRoomHook() const { return false; } + + ClientBase* m_parent; + JID m_nick; + + bool m_joined; + + private: +#ifdef MUCROOM_TEST + public: +#endif + /** + * @brief An abstraction of a MUC owner query. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class MUCOwner : public StanzaExtension + { + public: + + /** + * Describes available query types for the muc#owner namespace. + */ + enum QueryType + { + TypeCreate, /**< Create a room. */ + TypeRequestConfig, /**< Request room config. */ + TypeSendConfig, /**< Submit configuration form to MUC service. */ + TypeCancelConfig, /**< Cancel room configuration. */ + TypeInstantRoom, /**< Request an instant room */ + TypeDestroy, /**< Destroy the room. */ + TypeIncomingTag /**< The Query has been created from an incoming Tag. */ + }; + + /** + * Creates a new MUCOwner object for the given query, possibly including + * the given DataForm. + * @param type The intended query type. + * @param form An optional pointer to a DataForm. Necessity depends on the query type. + */ + MUCOwner( QueryType type, DataForm* form = 0 ); + + /** + * Creates a new query that destroys the current room. + * @param alternate An optional alternate discussion venue. + * @param reason An optional reason for the room destruction. + * @param password An optional password for the new room. + */ + MUCOwner( const JID& alternate = JID(), const std::string& reason = EmptyString, + const std::string& password = EmptyString); + + /** + * Creates a new MUCOwner object from the given Tag. + * @param tag A Tag to parse. + */ + MUCOwner( const Tag* tag ); + + /** + * Virtual destructor. + */ + virtual ~MUCOwner(); + + /** + * Returns a pointer to a DataForm, included in the MUCOwner object. May be 0. + * @return A pointer to a configuration form. + */ + const DataForm* form() const { return m_form; } + + // reimplemented from StanzaExtension + const std::string& filterString() const; + + // reimplemented from StanzaExtension + StanzaExtension* newInstance( const Tag* tag ) const + { + return new MUCOwner( tag ); + } + + // reimplemented from StanzaExtension + Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + MUCOwner* m = new MUCOwner(); + m->m_type = m_type; + m->m_jid = m_jid; + m->m_reason = m_reason; + m->m_pwd = m_pwd; + m->m_form = m_form ? new DataForm( *m_form ) : 0; + return m; + } + + private: + QueryType m_type; + JID m_jid; + std::string m_reason; + std::string m_pwd; + DataForm* m_form; + }; + + /** + * @brief An abstraction of a MUC admin query. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class MUCAdmin : public StanzaExtension + { + public: + /** + * Creates a new object that can be used to change the role of a room participant. + * @param role The participant's new role. + * @param nick The participant's nick. + * @param reason An optional reason for the role change. + */ + MUCAdmin( MUCRoomRole role, const std::string& nick, + const std::string& reason = EmptyString ); + + /** + * Creates a new object that can be used to change the affiliation of a room participant. + * @param affiliation The participant's new affiliation. + * @param nick The participant's nick. + * @param reason An optional reason for the role change. + */ + MUCAdmin( MUCRoomAffiliation affiliation, const std::string& nick, + const std::string& reason = EmptyString ); + + /** + * Creates a new object that can be used to request or store a role/affiliation + * list. + * @param operation The MUCOperation to carry out. Only the Request* and Store* + * operations are valid. Any other value will be ignored. + * @param jids A list of bare JIDs. Only the JID member of the MUCListItem + * structure should be set. The type of the list will be determined from the + * @c operation parameter. + */ + MUCAdmin( MUCOperation operation, const MUCListItemList& jids = MUCListItemList() ); + + /** + * Constructs a new MUCAdmin object from the given Tag. + * @param tag The Tag to parse. + */ + MUCAdmin( const Tag* tag = 0 ); + + /** + * Virtual destructor. + */ + virtual ~MUCAdmin(); + + /** + * Returns the contained list of MUC items. + * @return The contained list of MUC items. + */ + const MUCListItemList& list() const { return m_list; } + + // reimplemented from StanzaExtension + const std::string& filterString() const; + + // reimplemented from StanzaExtension + StanzaExtension* newInstance( const Tag* tag ) const + { + return new MUCAdmin( tag ); + } + + // reimplemented from StanzaExtension + Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new MUCAdmin( *this ); + } + + private: + MUCListItemList m_list; + MUCRoomAffiliation m_affiliation; + MUCRoomRole m_role; + }; + + void handleIqResult( const IQ& iq, int context ); + void handleIqError( const IQ& iq, int context ); + void setNonAnonymous(); + void setSemiAnonymous(); + void setFullyAnonymous(); + void acknowledgeRoomCreation(); + void instantRoom( int context ); + + MUCRoomHandler* m_roomHandler; + MUCRoomConfigHandler* m_roomConfigHandler; + MUCMessageSession* m_session; + + typedef std::list ParticipantList; + ParticipantList m_participants; + + std::string m_password; + std::string m_newNick; + + MUCRoomAffiliation m_affiliation; + MUCRoomRole m_role; + + HistoryRequestType m_historyType; + + std::string m_historySince; + int m_historyValue; + int m_flags; + bool m_creationInProgress; + bool m_configChanged; + bool m_publishNick; + bool m_publish; + bool m_unique; + + }; + +} + +#endif // MUCROOM_H__ diff --git a/libs/libgloox/mucroomconfighandler.h b/libs/libgloox/mucroomconfighandler.h new file mode 100644 index 0000000..67223d8 --- /dev/null +++ b/libs/libgloox/mucroomconfighandler.h @@ -0,0 +1,226 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef MUCROOMCONFIGHANDLER_H__ +#define MUCROOMCONFIGHANDLER_H__ + +#include "gloox.h" +#include "jid.h" + +#include +#include + +namespace gloox +{ + + class MUCRoom; + class DataForm; + + /** + * An item in a list of MUC room users. Lists of these items are + * used when manipulating the lists of members, admins, owners, etc. + * of a room. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class MUCListItem + { + public: + /** + * Constructs a new object using the given JID. + * @param jid The item's JID. + */ + MUCListItem( const JID& jid ) + : m_jid( jid ), m_affiliation( AffiliationInvalid ), m_role( RoleInvalid ) + {} + + /** + * Creates a new object, setting JID, affiliation, role, and nick. + * @param jid The item's JID. + * @param role The item's role. + * @param affiliation The item's affiliation. + * @param nick The item's nick. + */ + MUCListItem( const JID& jid, MUCRoomRole role, MUCRoomAffiliation affiliation, + const std::string& nick ) + : m_jid( jid ), m_nick( nick ), m_affiliation( affiliation ), m_role( role ) + {} + + /** + * Creates a new object, using nick, affiliation and a reason. + * @param nick The item's nick. + * @param affiliation The item's affiliation. + * @param reason A reason. + */ + MUCListItem( const std::string& nick, MUCRoomAffiliation affiliation, const std::string& reason ) + : m_nick( nick ), m_affiliation( affiliation ), m_role( RoleInvalid ), + m_reason( reason ) + {} + + /** + * Creates a new object, using nick, role and a reason. + * @param nick The item's nick. + * @param role The item's role. + * @param reason A reason. + */ + MUCListItem( const std::string& nick, MUCRoomRole role, const std::string& reason ) + : m_nick( nick ), m_affiliation( AffiliationInvalid ), m_role( role ), + m_reason( reason ) + {} + + /** + * Destructor. Deletes the @c jid member. + */ + ~MUCListItem() {} + + /** + * Returns the item's JID. + * @return The item's JID. + */ + const JID& jid() const { return m_jid; } + + /** + * Returns the item's nick. + * @return The item's nick. + */ + const std::string& nick() const { return m_nick; } + + /** + * Returns the item's affiliation. + * @return The item's affiliation. + */ + MUCRoomAffiliation affiliation() const { return m_affiliation; } + + /** + * Returns the item's role. + * @return The item's role. + */ + MUCRoomRole role() const { return m_role; } + + /** + * Returns the reason. + * @return The reason. + */ + const std::string& reason() const { return m_reason; } + + private: + JID m_jid; /**< Pointer to the occupant's JID if available, 0 otherwise. */ + std::string m_nick; /**< The occupant's nick in the room. */ + MUCRoomAffiliation m_affiliation; /**< The occupant's affiliation. */ + MUCRoomRole m_role; /**< The occupant's role. */ + std::string m_reason; /**< Use this only when **setting** the item's role/affiliation to + * specify a reason for the role/affiliation change. This field is + * empty in items fetched from the MUC service. */ + }; + + /** + * A list of MUCListItems. + */ + typedef std::list MUCListItemList; + + /** + * Available operations on a room. + */ + enum MUCOperation + { + RequestUniqueName, /**< Request a unique room name. */ + CreateInstantRoom, /**< Create an instant room. */ + CancelRoomCreation, /**< Cancel room creation process. */ + RequestRoomConfig, /**< Request room configuration form. */ + SendRoomConfig, /**< Send room configuration */ + DestroyRoom, /**< Destroy room. */ + GetRoomInfo, /**< Fetch room info. */ + GetRoomItems, /**< Fetch room items (e.g., current occupants). */ + SetRNone, /**< Set a user's role to None. */ + SetVisitor, /**< Set a user's role to Visitor. */ + SetParticipant, /**< Set a user's role to Participant. */ + SetModerator, /**< Set a user's role to Moderator. */ + SetANone, /**< Set a user's affiliation to None. */ + SetOutcast, /**< Set a user's affiliation to Outcast. */ + SetMember, /**< Set a user's affiliation to Member. */ + SetAdmin, /**< Set a user's affiliation to Admin. */ + SetOwner, /**< Set a user's affiliation to Owner. */ + RequestVoiceList, /**< Request the room's Voice List. */ + StoreVoiceList, /**< Store the room's Voice List. */ + RequestBanList, /**< Request the room's Ban List. */ + StoreBanList, /**< Store the room's Ban List. */ + RequestMemberList, /**< Request the room's Member List. */ + StoreMemberList, /**< Store the room's Member List. */ + RequestModeratorList, /**< Request the room's Moderator List. */ + StoreModeratorList, /**< Store the room's Moderator List. */ + RequestOwnerList, /**< Request the room's Owner List. */ + StoreOwnerList, /**< Store the room's Owner List. */ + RequestAdminList, /**< Request the room's Admin List. */ + StoreAdminList, /**< Store the room's Admin List. */ + InvalidOperation /**< Invalid operation. */ + }; + + /** + * @brief An abstract interface that can be implemented for MUC room configuration. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API MUCRoomConfigHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~MUCRoomConfigHandler() {} + + /** + * This function is called in response to MUCRoom::requestList() if the list was + * fetched successfully. + * @param room The room for which the list arrived. + * @param items The requestd list's items. + * @param operation The type of the list. + */ + virtual void handleMUCConfigList( MUCRoom* room, const MUCListItemList& items, + MUCOperation operation ) = 0; + + /** + * This function is called when the room's configuration form arrives. This usually happens + * after a call to MUCRoom::requestRoomConfig(). Use + * MUCRoom::setRoomConfig() to send the configuration back to the + * room. + * @param room The room for which the config form arrived. + * @param form The configuration form. + */ + virtual void handleMUCConfigForm( MUCRoom* room, const DataForm& form ) = 0; + + /** + * This function is called in response to MUCRoom::kick(), MUCRoom::storeList(), + * MUCRoom::ban(), and others, to indcate the end of the operation. + * @param room The room for which the operation ended. + * @param success Whether or not the operation was successful. + * @param operation The finished operation. + */ + virtual void handleMUCConfigResult( MUCRoom* room, bool success, MUCOperation operation ) = 0; + + /** + * This function is called when a Voice request or a Registration request arrive through + * the room that need to be approved/rejected by the room admin. Use MUCRoom::createDataForm() + * to have a Tag created that answers the request. + * @param room The room the request arrived from. + * @param form A DataForm containing the request. + */ + virtual void handleMUCRequest( MUCRoom* room, const DataForm& form ) = 0; + + }; + +} + +#endif // MUCROOMCONFIGHANDLER_H__ diff --git a/libs/libgloox/mucroomhandler.h b/libs/libgloox/mucroomhandler.h new file mode 100644 index 0000000..d1e47eb --- /dev/null +++ b/libs/libgloox/mucroomhandler.h @@ -0,0 +1,216 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef MUCROOMHANDLER_H__ +#define MUCROOMHANDLER_H__ + +#include "gloox.h" +#include "presence.h" +#include "disco.h" + +#include + +namespace gloox +{ + + class JID; + class MUCRoom; + class Message; + class DataForm; + + /** + * Describes a participant in a MUC room. + */ + struct MUCRoomParticipant + { + JID* nick; /**< Pointer to a JID holding the participant's full JID + * in the form @c room\@service/nick.
+ * @note The MUC server @b may change the chosen nickname. + * If the @b self member of this struct is true, one should + * check the resource of this member if the actual nickname + * is important. */ + MUCRoomAffiliation affiliation; /**< The participant's affiliation with the room. */ + MUCRoomRole role; /**< The participant's role with the room. */ + JID* jid; /**< Pointer to the occupant's full JID in a non-anonymous room or + * in a semi-anonymous room if the user (of gloox) has a role of + * moderator. + * 0 if the MUC service doesn't provide the JID. */ + int flags; /**< ORed MUCUserFlag values. Indicate conditions like: user has + * been kicked or banned from the room. Also may indicate that + * this struct refers to this instance's user. + * (MUC servers send presence to all room occupants, including + * the originator of the presence.) */ + std::string reason; /**< If the presence change is the result of an action where the + * actor can provide a reason for the action, this reason is stored + * here. Examples: Kicking, banning, leaving the room. */ + JID* actor; /**< If the presence change is the result of an action of a room + * member, a pointer to the actor's JID is stored here, if the + * actor chose to disclose his or her identity. Examples: Kicking + * and banning. + * 0 if the identity is not disclosed. */ + std::string newNick; /**< In case of a nickname change, this holds the new nick, while the + * nick member holds the old room nick (in JID form). @c newNick is only + * set if @c flags contains @b UserNickChanged. If @c flags contains + * @b UserSelf as well, a foregoing nick change request (using + * MUCRoom::setNick()) can be considered acknowledged. In any case + * the user's presence sent with the nick change acknowledgement + * is of type @c unavailable. Another presence of type @c available + * (or whatever the user's presence was at the time of the nick change + * request) will follow (not necessarily immediately) coming from the + * user's new nickname. Empty if there is no nick change in progress. */ + std::string status; /**< If the presence packet contained a status message, it is stored + * here. */ + JID* alternate; /**< If @c flags contains UserRoomDestroyed, and if the user who + * destroyed the room specified an alternate room, this member holds + * a pointer to the alternate room's JID, else it is 0. */ + }; + + /** + * @brief This interface enables inheriting classes to be notified about certain events in a MUC room. + * + * See MUCRoom for examples how to use this interface. + * + * @note This interface does not notify about room configuration related events. Use + * MUCRoomConfigHandler for that puprose. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API MUCRoomHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~MUCRoomHandler() {} + + /** + * This function is called whenever a room occupant enters the room, changes presence + * inside the room, or leaves the room. + * @note The MUCRoomParticipant struct, including pointers to JIDs, will be cleaned up after + * this function returned. + * @param room The room. + * @param participant A struct describing the occupant's status and/or action. + * @param presence The occupant's full presence. + */ + virtual void handleMUCParticipantPresence( MUCRoom* room, const MUCRoomParticipant participant, + const Presence& presence ) = 0; + + /** + * This function is called when a message arrives through the room. + * @note This may be a private message! If the message is private, and you want to answer + * it privately, you should create a new MessageSession to the user's full room nick and use + * that for any further private communication with the user. + * @param room The room the message came from. + * @param msg The entire Message. + * @param priv Indicates whether this is a private message. + * @note The sender's nick name can be obtained with this call: + * @code + * const std::string nick = msg.from().resource(); + * @endcode + * @note The message may contain an extension of type DelayedDelivery describing the + * date/time when the message was originally sent. The presence of such an extension + * usually indicates that the message is sent as part of the room history. This extension + * can be obtained with this call: + * @code + * const DelayedDelivery* dd = msg.when(); // may be 0 if no such extension exists + * @endcode + */ + virtual void handleMUCMessage( MUCRoom* room, const Message& msg, bool priv ) = 0; + + /** + * This function is called if the room that was just joined didn't exist prior to the attempted + * join. Therfore the room was created by MUC service. To accept the default configuration of + * the room assigned by the MUC service, return @b true from this function. The room will be opened + * by the MUC service and available for other users to join. If you don't want to accept the default + * room configuration, return @b false from this function. The room will stay locked until it is + * either fully configured, created as an instant room, or creation is canceled. + * + * If you returned false from this function you should use one of the following options: + * @li use MUCRoom::cancelRoomCreation() to abort creation and delete the room, + * @li use MUCRoom::acknowledgeInstantRoom() to accept the room's default configuration, or + * @li use MUCRoom::requestRoomConfig() to request the room's configuration form. + * + * @param room The room. + * @return @b True to accept the default room configuration, @b false to keep the room locked + * until configured manually by the room owner. + */ + virtual bool handleMUCRoomCreation( MUCRoom* room ) = 0; + + /** + * This function is called when the room subject has been changed. + * @param room The room. + * @param nick The nick of the occupant that changed the room subject. + * @note With some MUC services the nick may be empty when a room is first entered. + * @param subject The new room subject. + */ + virtual void handleMUCSubject( MUCRoom* room, const std::string& nick, + const std::string& subject ) = 0; + + /** + * This function is called when the user invited somebody (e.g., by using MUCRoom::invite()) + * to the room, but the invitation was declined by that person. + * @param room The room. + * @param invitee The JID if the person that declined the invitation. + * @param reason An optional reason for declining the invitation. + */ + virtual void handleMUCInviteDecline( MUCRoom* room, const JID& invitee, + const std::string& reason ) = 0; + + /** + * This function is called when an error occurs in the room or when entering the room. + * @note The following error conditions are specified for MUC: + * @li @b Not @b Authorized: Password required. + * @li @b Forbidden: Access denied, user is banned. + * @li @b Item @b Not @b Found: The room does not exist. + * @li @b Not @b Allowed: Room creation is restricted. + * @li @b Not @b Acceptable: Room nicks are locked down. + * @li @b Registration @b Required: User is not on the member list. + * @li @b Conflict: Desired room nickname is in use or registered by another user. + * @li @b Service @b Unavailable: Maximum number of users has been reached. + * + * Other errors might appear, depending on the service implementation. + * @param room The room. + * @param error The error. + */ + virtual void handleMUCError( MUCRoom* room, StanzaError error ) = 0; + + /** + * This function usually (see below) is called in response to a call to MUCRoom::getRoomInfo(). + * @param room The room. + * @param features ORed MUCRoomFlag's. + * @param name The room's name as returned by Service Discovery. + * @param infoForm A DataForm containing extended room information. May be 0 if the service + * doesn't support extended room information. See Section 15.5 of XEP-0045 for defined + * field types. You should not delete the form. + * + * @note This function may be called without a prior call to MUCRoom::getRoomInfo(). This + * happens if the room config is changed, e.g. by a room admin. + */ + virtual void handleMUCInfo( MUCRoom* room, int features, const std::string& name, + const DataForm* infoForm ) = 0; + + /** + * This function is called in response to a call to MUCRoom::getRoomItems(). + * @param room The room. + * @param items A map of room participants. The key is the name, the value is the occupant's + * room JID. The map may be empty if such info is private. + */ + virtual void handleMUCItems( MUCRoom* room, const Disco::ItemList& items ) = 0; + + }; + +} + +#endif// MUCROOMHANDLER_H__ diff --git a/libs/libgloox/mutex.cpp b/libs/libgloox/mutex.cpp new file mode 100644 index 0000000..0f9898c --- /dev/null +++ b/libs/libgloox/mutex.cpp @@ -0,0 +1,130 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "mutex.h" + +#include "config.h" + +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) +# include +#endif + +#ifdef _WIN32_WCE +# include +#endif + +#ifdef HAVE_PTHREAD +# include +#endif + +namespace gloox +{ + + namespace util + { + + class Mutex::MutexImpl + { + public: + MutexImpl(); + ~MutexImpl(); + void lock(); + bool trylock(); + void unlock(); + private: + MutexImpl( const MutexImpl& ); + MutexImpl& operator=( const MutexImpl& ); + +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + CRITICAL_SECTION m_cs; +#elif defined( HAVE_PTHREAD ) + pthread_mutex_t m_mutex; +#endif + + }; + + Mutex::MutexImpl::MutexImpl() + { +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + InitializeCriticalSection( &m_cs ); +#elif defined( HAVE_PTHREAD ) + pthread_mutex_init( &m_mutex, 0 ); +#endif + } + + Mutex::MutexImpl::~MutexImpl() + { +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + DeleteCriticalSection( &m_cs ); +#elif defined( HAVE_PTHREAD ) + pthread_mutex_destroy( &m_mutex ); +#endif + } + + void Mutex::MutexImpl::lock() + { +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + EnterCriticalSection( &m_cs ); +#elif defined( HAVE_PTHREAD ) + pthread_mutex_lock( &m_mutex ); +#endif + } + + bool Mutex::MutexImpl::trylock() + { +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + return TryEnterCriticalSection( &m_cs ) ? true : false; +#elif defined( HAVE_PTHREAD ) + return !( pthread_mutex_trylock( &m_mutex ) ); +#else + return true; +#endif + } + + void Mutex::MutexImpl::unlock() + { +#if defined( _WIN32 ) && !defined( __SYMBIAN32__ ) + LeaveCriticalSection( &m_cs ); +#elif defined( HAVE_PTHREAD ) + pthread_mutex_unlock( &m_mutex ); +#endif + } + + Mutex::Mutex() + : m_mutex( new MutexImpl() ) + { + } + + Mutex::~Mutex() + { + delete m_mutex; + } + + void Mutex::lock() + { + m_mutex->lock(); + } + + bool Mutex::trylock() + { + return m_mutex->trylock(); + } + + void Mutex::unlock() + { + m_mutex->unlock(); + } + + } + +} diff --git a/libs/libgloox/mutex.h b/libs/libgloox/mutex.h new file mode 100644 index 0000000..dab121c --- /dev/null +++ b/libs/libgloox/mutex.h @@ -0,0 +1,77 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef MUTEX_H__ +#define MUTEX_H__ + +#include "macros.h" + +namespace gloox +{ + + namespace util + { + /** + * @brief A simple implementation of mutex as a wrapper around a pthread mutex + * or a win32 critical section. + * + * If you locked a mutex you MUST unlock it within the same thread. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API Mutex + { + public: + /** + * Contructs a new simple mutex. + */ + Mutex(); + + /** + * Destructor + */ + ~Mutex(); + + /** + * Locks the mutex. + */ + void lock(); + + /** + * Tries to lock the mutex. + * @return @b True if the attempt was successful, @b false otherwise. + * @note This function also returns @b true if mutex support is not available, ie. if gloox + * is compiled without pthreads on non-Windows platforms. Make sure threads/mutexes are available + * if your code relies on trylock(). + */ + bool trylock(); + + /** + * Releases the mutex. + */ + void unlock(); + + private: + class MutexImpl; + + Mutex& operator=( const Mutex& ); + MutexImpl* m_mutex; + + }; + + } + +} + +#endif // MUTEX_H__ diff --git a/libs/libgloox/mutexguard.h b/libs/libgloox/mutexguard.h new file mode 100644 index 0000000..09ade92 --- /dev/null +++ b/libs/libgloox/mutexguard.h @@ -0,0 +1,61 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef MUTEXGUARD_H__ +#define MUTEXGUARD_H__ + +#include "mutex.h" + +namespace gloox +{ + + namespace util + { + + /** + * @brief A simple implementation of a mutex guard. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API MutexGuard + { + public: + /** + * Contructs a new simple mutex guard and locks the supplied Mutex. + * @param mutex The Mutex to guard. + */ + MutexGuard( Mutex* mutex ) : m_mutex( *mutex ) { if( mutex ) m_mutex.lock(); } + + /** + * Contructs a new simple mutex guard and locks the supplied Mutex. + * @param mutex The Mutex to guard. + */ + MutexGuard( Mutex& mutex ) : m_mutex( mutex ) { m_mutex.lock(); } + + /** + * Destructor. Releases the guarded Mutex. + */ + ~MutexGuard() { m_mutex.unlock(); } + + private: + MutexGuard& operator=( const MutexGuard& ); + Mutex& m_mutex; + + }; + + } + +} + +#endif // MUTEXGUARD_H__ diff --git a/libs/libgloox/nickname.cpp b/libs/libgloox/nickname.cpp new file mode 100644 index 0000000..0b1ab6b --- /dev/null +++ b/libs/libgloox/nickname.cpp @@ -0,0 +1,44 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "nickname.h" +#include "tag.h" + +namespace gloox +{ + + Nickname::Nickname( const Tag* tag ) + : StanzaExtension( ExtNickname ) + { + if( tag ) + m_nick = tag->cdata(); + } + + const std::string& Nickname::filterString() const + { + static const std::string filter = + "/presence/nick[@xmlns='" + XMLNS_NICKNAME + "']" + "|/message/nick[@xmlns='" + XMLNS_NICKNAME + "']"; + return filter; + } + + Tag* Nickname::tag() const + { + if( m_nick.empty() ) + return 0; + + Tag* n = new Tag( "nick", XMLNS, XMLNS_NICKNAME ); + n->setCData( m_nick ); + return n; + } + +} diff --git a/libs/libgloox/nickname.h b/libs/libgloox/nickname.h new file mode 100644 index 0000000..8b21003 --- /dev/null +++ b/libs/libgloox/nickname.h @@ -0,0 +1,87 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef NICKNAME_H__ +#define NICKNAME_H__ + +#include "gloox.h" +#include "stanzaextension.h" + +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief An implementation of User Nickname (XEP-0172) as a StanzaExtension. + * + * XEP version: 1.0 + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Nickname : public StanzaExtension + { + public: + + /** + * Constructs a new object from the given Tag. + * @param tag A Tag to parse. + */ + Nickname( const Tag* tag ); + + /** + * Constructs a new Nickname object. + * @param nick The nickname to include. + */ + Nickname( const std::string& nick ) + : StanzaExtension( ExtNickname ), m_nick( nick ) + {} + + /** + * Virtual destructor. + */ + virtual ~Nickname() {} + + /** + * Returns the extension's saved nickname. + * @return The nickname. + */ + const std::string nick() const { return m_nick; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Nickname( tag ); + } + + // reimplemented from StanzaExtension + Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new Nickname( *this ); + } + + private: + std::string m_nick; + + }; + +} + +#endif // NICKNAME_H__ diff --git a/libs/libgloox/nonsaslauth.cpp b/libs/libgloox/nonsaslauth.cpp new file mode 100644 index 0000000..a0cf734 --- /dev/null +++ b/libs/libgloox/nonsaslauth.cpp @@ -0,0 +1,174 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "nonsaslauth.h" +#include "client.h" +#include "error.h" +#include "sha.h" + +#include + +namespace gloox +{ + + // ---- NonSaslAuth::Query ---- + NonSaslAuth::Query::Query( const std::string& user ) + : StanzaExtension( ExtNonSaslAuth ), m_user( user ), m_digest( true ) + { + } + + NonSaslAuth::Query::Query( const Tag* tag ) + : StanzaExtension( ExtNonSaslAuth ) + { + if( !tag || tag->name() != "query" || tag->xmlns() != XMLNS_AUTH ) + return; + + m_digest = tag->hasChild( "digest" ); + } + + NonSaslAuth::Query* NonSaslAuth::Query::newInstance( const std::string& user, + const std::string& sid, + const std::string& pwd, + const std::string& resource ) const + { + Query* q = new Query( user ); + if( m_digest && !sid.empty() ) + { + SHA sha; + sha.feed( sid ); + sha.feed( pwd ); + q->m_pwd = sha.hex(); + } + else + q->m_pwd = pwd; + + q->m_resource = resource; + q->m_digest = m_digest; + return q; + } + + const std::string& NonSaslAuth::Query::filterString() const + { + static const std::string filter = "/iq/query[@xmlns='" + XMLNS_AUTH + "']"; + return filter; + } + + Tag* NonSaslAuth::Query::tag() const + { + if( m_user.empty() ) + return 0; + + Tag* t = new Tag( "query" ); + t->setXmlns( XMLNS_AUTH ); + new Tag( t, "username", m_user ); + + if( !m_pwd.empty() && !m_resource.empty() ) + { + new Tag( t, m_digest ? "digest" : "password", m_pwd ); + new Tag( t, "resource", m_resource ); + } + + return t; + } + // ---- ~NonSaslAuth::Query ---- + + // ---- NonSaslAuth ---- + NonSaslAuth::NonSaslAuth( Client* parent ) + : m_parent( parent ) + { + if( m_parent ) + { + m_parent->registerStanzaExtension( new Query() ); + m_parent->registerIqHandler( this, ExtNonSaslAuth ); + } + } + + NonSaslAuth::~NonSaslAuth() + { + if( m_parent ) + { + m_parent->removeStanzaExtension( ExtNonSaslAuth ); + m_parent->removeIqHandler( this, ExtNonSaslAuth ); + m_parent->removeIDHandler( this ); + } + } + + void NonSaslAuth::doAuth( const std::string& sid ) + { + m_sid = sid; + const std::string& id = m_parent->getID(); + + IQ iq( IQ::Get, m_parent->jid().server(), id ); + iq.addExtension( new Query( m_parent->username() ) ); + m_parent->send( iq, this, TrackRequestAuthFields ); + } + + void NonSaslAuth::handleIqID( const IQ& iq, int context ) + { + switch( iq.subtype() ) + { + case IQ::Error: + { + const Error* e = iq.error(); + if( e ) + { + switch( e->error() ) + { + case StanzaErrorConflict: + m_parent->setAuthFailure( NonSaslConflict ); + break; + case StanzaErrorNotAcceptable: + m_parent->setAuthFailure( NonSaslNotAcceptable ); + break; + case StanzaErrorNotAuthorized: + m_parent->setAuthFailure( NonSaslNotAuthorized ); + break; + default: + break; + } + } + m_parent->setAuthed( false ); + m_parent->disconnect( ConnAuthenticationFailed ); + break; + } + case IQ::Result: + switch( context ) + { + case TrackRequestAuthFields: + { + const Query* q = iq.findExtension( ExtNonSaslAuth ); + if( !q ) + return; + + const std::string& id = m_parent->getID(); + + IQ re( IQ::Set, JID(), id ); + re.addExtension( q->newInstance( m_parent->username(), m_sid, + m_parent->password(), + m_parent->jid().resource() ) ); + m_parent->send( re, this, TrackSendAuth ); + break; + } + case TrackSendAuth: + m_parent->setAuthed( true ); + m_parent->connected(); + break; + } + break; + + default: + break; + } + } + +} diff --git a/libs/libgloox/nonsaslauth.h b/libs/libgloox/nonsaslauth.h new file mode 100644 index 0000000..cfc1eae --- /dev/null +++ b/libs/libgloox/nonsaslauth.h @@ -0,0 +1,147 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef NONSASLAUTH_H__ +#define NONSASLAUTH_H__ + +#include "iqhandler.h" + +#include + +namespace gloox +{ + + class Client; + class Stanza; + class Tag; + + /** + * @brief This class is an implementation of XEP-0078 (Non-SASL Authentication). + * + * It is invoked by @ref Client automatically if supported by the server and if SASL authentication + * is not supported. + * You should not need to use this class manually. + * + * XEP Version: 2.3 + * @author Jakob Schroeter + * @since 0.3 + */ + class GLOOX_API NonSaslAuth : public IqHandler + { + public: + /** + * Constructor. + * @param parent The @ref ClientBase which is used to authenticate. + */ + NonSaslAuth( Client* parent ); + + /** + * Virtual Destructor. + */ + virtual ~NonSaslAuth(); + + /** + * Starts authentication by querying the server for the required authentication fields. + * Digest authentication is preferred over plain text passwords. + * @param sid The session ID given by the server with the stream opening tag. + */ + void doAuth( const std::string& sid ); + + // reimplemented from IqHandler + virtual bool handleIq( const IQ& iq ) { (void)iq; return false; } + + // reimplemented from IqHandler + virtual void handleIqID( const IQ& iq, int context ); + + private: +#ifdef NONSASLAUTH_TEST + public: +#endif + /** + * @brief An abstraction of an IQ extension used for Non-SASL authentication (XEP-0078). + * + * @author Jakob Schroeter + * @since 1.0 + */ + class Query : public StanzaExtension + { + public: + /** + * Creates a new object that can be used to query the server for + * authentication filds for the given user. + * @param user The user name to fetch authentication fields for. + */ + Query( const std::string& user ); + + /** + * Creates a now object from the given Tag. + * @param tag The Tag to parse. + */ + Query( const Tag* tag = 0 ); + + /** + * Creates a new object on the heap that can be used to + * authenticate, based on the current reply. + * @param user The uset o authenticate as. + * @param sid The stream's ID. + * @param pwd The password to use. + * @param resource The desired resource identifier. + */ + Query* newInstance( const std::string& user, const std::string& sid, + const std::string& pwd, const std::string& resource ) const; + + /** + * Virtual destructor. + */ + virtual ~Query() {} + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Query( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new Query( *this ); + } + + private: + std::string m_user; + std::string m_pwd; + std::string m_resource; + bool m_digest; + + }; + + enum NonSaslAuthTrack + { + TrackRequestAuthFields, + TrackSendAuth + }; + + Client* m_parent; + std::string m_sid; + + }; + +} + +#endif // NONSASLAUTH_H__ diff --git a/libs/libgloox/oob.cpp b/libs/libgloox/oob.cpp new file mode 100644 index 0000000..c59244f --- /dev/null +++ b/libs/libgloox/oob.cpp @@ -0,0 +1,81 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "oob.h" +#include "tag.h" + +namespace gloox +{ + + OOB::OOB( const std::string& url, const std::string& description, bool iqext ) + : StanzaExtension( ExtOOB ), m_url( url ), m_desc( description ), m_iqext( iqext ), + m_valid( true ) + { + if( m_url.empty() ) + m_valid = false; + } + + OOB::OOB( const Tag* tag ) + : StanzaExtension( ExtOOB ), m_iqext( false ), m_valid( false ) + { + if( tag && ( ( tag->name() == "x" && tag->hasAttribute( XMLNS, XMLNS_X_OOB ) ) || + ( tag && tag->name() == "query" && tag->hasAttribute( XMLNS, XMLNS_IQ_OOB ) ) ) ) + { + if( tag->name() == "query" ) + m_iqext = true; + } + else + return; + + if( tag->hasChild( "url" ) ) + { + m_valid = true; + m_url = tag->findChild( "url" )->cdata(); + } + if( tag->hasChild( "desc" ) ) + m_desc = tag->findChild( "desc" )->cdata(); + } + + OOB::~OOB() + { + } + + const std::string& OOB::filterString() const + { + static const std::string filter = + "/presence/x[@xmlns='" + XMLNS_X_OOB + "']" + "|/message/x[@xmlns='" + XMLNS_X_OOB + "']" + "|/iq/query[@xmlns='" + XMLNS_IQ_OOB + "']"; + return filter; + } + + Tag* OOB::tag() const + { + if( !m_valid ) + return 0; + + Tag* t = 0; + + if( m_iqext ) + t = new Tag( "query", XMLNS, XMLNS_IQ_OOB ); + else + t = new Tag( "x", XMLNS, XMLNS_X_OOB ); + + new Tag( t, "url", m_url ); + if( !m_desc.empty() ) + new Tag( t, "desc", m_desc ); + + return t; + } + +} diff --git a/libs/libgloox/oob.h b/libs/libgloox/oob.h new file mode 100644 index 0000000..c3095b2 --- /dev/null +++ b/libs/libgloox/oob.h @@ -0,0 +1,101 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef OOB_H__ +#define OOB_H__ + +#include "gloox.h" +#include "stanzaextension.h" + +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief This is an abstraction of a jabber:x:oob namespace element or a jabber:iq:oob namespace element + * as specified in XEP-0066. + * + * XEP version: 1.5 + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API OOB : public StanzaExtension + { + public: + /** + * Constructs an OOB StanzaExtension from teh given URL and description. + * @param url The out-of-band URL. + * @param description The URL's optional description. + * @param iqext Whether this object extends an IQ or a Presence/Message stanza (results in + * either jabber:iq:oob or jabber:x:oob namespaced element). + */ + OOB( const std::string& url, const std::string& description, bool iqext ); + + /** + * Constructs an OOB object from the given Tag. To be recognized properly, the Tag must + * have either a name of 'x' in the jabber:x:oob namespace, or a name of 'query' in the + * jabber:iq:oob namespace. + * @param tag The Tag to parse. + */ + OOB( const Tag* tag ); + + /** + * Virtual destructor. + */ + virtual ~OOB(); + + /** + * Returns the out-of-band URL. + * @return The out-of-band URL. + */ + const std::string& url() const { return m_url; } + + /** + * Returns the URL's description. + * @return The URL's description. + */ + const std::string& desc() const { return m_desc; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new OOB( tag ); + } + + // reimplemented from StanzaExtension + Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new OOB( *this ); + } + + private: + std::string m_xmlns; + std::string m_url; + std::string m_desc; + bool m_iqext; + bool m_valid; + + }; + +} + +#endif // OOB_H__ diff --git a/libs/libgloox/parser.cpp b/libs/libgloox/parser.cpp new file mode 100644 index 0000000..a72fbdc --- /dev/null +++ b/libs/libgloox/parser.cpp @@ -0,0 +1,800 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "gloox.h" +#include "util.h" +#include "parser.h" + +#include + +namespace gloox +{ + + Parser::Parser( TagHandler* ph, bool deleteRoot ) + : m_tagHandler( ph ), m_current( 0 ), m_root( 0 ), m_xmlnss( 0 ), m_state( Initial ), + m_preamble( 0 ), m_quote( false ), m_haveTagPrefix( false ), m_haveAttribPrefix( false ), + m_attribIsXmlns( false ), m_deleteRoot( deleteRoot ) + { + } + + Parser::~Parser() + { + delete m_root; + delete m_xmlnss; + } + + Parser::DecodeState Parser::decode( std::string::size_type& pos, const std::string& data ) + { + std::string::size_type p = data.find( ';', pos ); + std::string::size_type diff = p - pos; + + if( p == std::string::npos ) + { + m_backBuffer = data.substr( pos ); + return DecodeInsufficient; + } + + if( diff < 3 || diff > 9 ) + return DecodeInvalid; + + std::string rep; + switch( data[pos + 1] ) + { + case '#': + { + int base = 10; + int idx = 2; + + if( data[pos + 2] == 'x' || data[pos + 2] == 'X' ) + { + base = 16; + idx = 3; + } + + char* end; + const long int val = std::strtol( data.data() + pos + idx, &end, base ); + if( *end != ';' || val < 0 ) + return DecodeInvalid; + + if( val == 0x9 || val == 0xA || val == 0xD || ( val >= 0x20 && val <= 0x7F ) ) + { + rep += char( val ); + } + else if( val >= 0x80 && val <= 0x7FF ) + { + rep += char( 192 + ( val >> 6 ) ); + rep += char( 128 + ( val % 64 ) ); + } + else if( ( val >= 0x800 && val <= 0xD7FF ) || ( val >= 0xE000 && val <= 0xFFFD ) ) + { + rep += char( 224 + ( val >> 12 ) ); + rep += char( 128 + ( ( val >> 6 ) % 64 ) ); + rep += char( 128 + ( val % 64 ) ); + } + else if( val >= 0x100000 && val < 0x10FFFF ) + { + rep += char( 240 + ( val >> 18 ) ); + rep += char( 128 + ( ( val >> 12 ) % 64 ) ); + rep += char( 128 + ( ( val >> 6 ) % 64 ) ); + rep += char( 128 + ( val % 64 ) ); + } + else + return DecodeInvalid; + } + break; + case 'l': + if( diff == 3 && data[pos + 2] == 't' ) + rep += '<'; + else + return DecodeInvalid; + break; + case 'g': + if( diff == 3 && data[pos + 2] == 't' ) + rep += '>'; + else + return DecodeInvalid; + break; + case 'a': + if( diff == 5 && !data.compare( pos + 1, 5, "apos;" ) ) + rep += '\''; + else if( diff == 4 && !data.compare( pos + 1, 4, "amp;" ) ) + rep += '&'; + else + return DecodeInvalid; + break; + case 'q': + if( diff == 5 && !data.compare( pos + 1, 5, "quot;" ) ) + rep += '"'; + else + return DecodeInvalid; + break; + default: + return DecodeInvalid; + } + + switch( m_state ) + { + case TagInside: + m_cdata += rep; + break; + case TagAttributeValue: + m_value += rep; + break; + default: + break; + } + pos += diff; + return DecodeValid; + } + + Parser::ForwardScanState Parser::forwardScan( std::string::size_type& pos, const std::string& data, + const std::string& needle ) + { + if( pos + needle.length() <= data.length() ) + { + if( !data.compare( pos, needle.length(), needle ) ) + { + pos += needle.length() - 1; + return ForwardFound; + } + else + { + return ForwardNotFound; + } + } + else + { + m_backBuffer = data.substr( pos ); + return ForwardInsufficientSize; + } + } + + int Parser::feed( std::string& data ) + { + if( !m_backBuffer.empty() ) + { + data.insert( 0, m_backBuffer ); + m_backBuffer = EmptyString; + } + + std::string::size_type count = data.length(); + for( std::string::size_type i = 0; i < count; ++i ) + { + const unsigned char c = data[i]; +// printf( "found char: %c, ", c ); + + if( !isValid( c ) ) + { + cleanup(); + return static_cast( i ); + } + + switch( m_state ) + { + case Initial: +// printf( "Initial: %c\n", c ); + if( isWhitespace( c ) ) + break; + + switch( c ) + { + case '<': + m_state = TagOpening; + break; + default: + cleanup(); + return static_cast( i ); + break; + } + break; + case InterTag: +// printf( "InterTag: %c\n", c ); + m_tag = EmptyString; + if( isWhitespace( c ) ) + break; + + switch( c ) + { + case '<': + m_state = TagOpening; + break; + case '>': + default: + if( m_current ) + { + m_cdata += c; + m_state = TagInside; + } + break; + } + break; + case TagOpening: // opening '<' has been found before +// printf( "TagOpening: %c\n", c ); + if( isWhitespace( c ) ) + break; + + switch( c ) + { + case '<': + case '>': + case '&': + cleanup(); + return static_cast( i ); + break; + case '/': + m_state = TagClosingSlash; + break; + case '?': + m_state = TagNameCollect; + m_preamble = 1; + break; + case '!': + switch( forwardScan( i, data, "![CDATA[" ) ) + { + case ForwardFound: + m_state = TagCDATASection; + break; + case ForwardNotFound: + cleanup(); + return static_cast( i ); + case ForwardInsufficientSize: + return -1; + } + break; + default: + m_tag += c; + m_state = TagNameCollect; + break; + } + break; + case TagCDATASection: + switch( c ) + { + case ']': + switch( forwardScan( i, data, "]]>" ) ) + { + case ForwardFound: + m_state = TagInside; + break; + case ForwardNotFound: + m_cdata += c; + break; + case ForwardInsufficientSize: + return -1; + } + break; + default: + m_cdata += c; + break; + } + break; + case TagNameCollect: // we're collecting the tag's name, we have at least one octet already +// printf( "TagNameCollect: %c\n", c ); + if( isWhitespace( c ) ) + { + m_state = TagNameComplete; + break; + } + + switch( c ) + { + case '<': + case '?': + case '!': + case '&': + cleanup(); + return static_cast( i ); + break; + case '/': + m_state = TagOpeningSlash; + break; + case '>': + addTag(); + m_state = TagInside; + break; + case ':': + if( !m_haveTagPrefix ) + { + m_haveTagPrefix = true; + m_tagPrefix = m_tag; + m_tag = EmptyString; + } + else + { + cleanup(); + return static_cast( i ); + } + break; + default: + m_tag += c; + break; + } + break; + case TagInside: // we're inside a tag, expecting a child tag or cdata +// printf( "TagInside: %c\n", c ); + m_tag = EmptyString; + switch( c ) + { + case '<': + addCData(); + m_state = TagOpening; + break; + case '&': +// printf( "TagInside, calling decode\n" ); + switch( decode( i, data ) ) + { + case DecodeValid: + break; + case DecodeInvalid: + cleanup(); + return static_cast( i ); + case DecodeInsufficient: + return -1; + } + break; + default: + m_cdata += c; + break; + } + break; + case TagOpeningSlash: // a slash in an opening tag has been found, initing close of the tag +// printf( "TagOpeningSlash: %c\n", c ); + if( isWhitespace( c ) ) + break; + + if( c == '>' ) + { + addTag(); + if( !closeTag() ) + { +// printf( "noipe, here\n" ); + cleanup(); + return static_cast( i ); + } + + m_state = InterTag; + } + else + { + cleanup(); + return static_cast( i ); + } + break; + case TagClosingSlash: // we have found the '/' of a closing tag +// printf( "TagClosingSlash: %c\n", c ); + if( isWhitespace( c ) ) + break; + + switch( c ) + { + case '>': + case '<': + case '/': + cleanup(); + return static_cast( i ); + break; + default: + m_tag += c; + m_state = TagClosing; + break; + } + break; + case TagClosing: // we're collecting the name of a closing tag +// printf( "TagClosing: %c\n", c ); + switch( c ) + { + case '<': + case '/': + case '!': + case '?': + case '&': + cleanup(); + return static_cast( i ); + break; + case ':': + if( !m_haveTagPrefix ) + { + m_haveTagPrefix = true; + m_tagPrefix = m_tag; + m_tag = EmptyString; + } + else + { + cleanup(); + return static_cast( i ); + } + break; + case '>': + if( !closeTag() ) + { +// printf( "here\n" ); + cleanup(); + return static_cast( i ); + } + m_state = InterTag; + break; + default: + m_tag += c; + break; + } + break; + case TagNameComplete: // a tag name is complete, expect tag close or attribs +// printf( "TagNameComplete: %c\n", c ); + if( isWhitespace( c ) ) + break; + + switch( c ) + { + case '<': + case '!': + case '&': + cleanup(); + return static_cast( i ); + break; + case '/': + m_state = TagOpeningSlash; + break; + case '>': + if( m_preamble == 1 ) + { + cleanup(); + return static_cast( i ); + } + m_state = TagInside; + addTag(); + break; + case '?': + if( m_preamble == 1 ) + m_preamble = 2; + else + { + cleanup(); + return static_cast( i ); + } + break; + default: + m_attrib += c; + m_state = TagAttribute; + break; + } + break; + case TagAttribute: // we're collecting the name of an attribute, we have at least 1 octet +// printf( "TagAttribute: %c\n", c ); + if( isWhitespace( c ) ) + { + m_state = TagAttributeComplete; + break; + } + + switch( c ) + { + case '<': + case '/': + case '>': + case '?': + case '!': + case '&': + cleanup(); + return static_cast( i ); + break; + case '=': + m_state = TagAttributeEqual; + break; + case ':': + if( !m_haveAttribPrefix && m_attrib != XMLNS ) + { + m_haveAttribPrefix = true; + m_attribPrefix = m_attrib; + m_attrib = EmptyString; + } + else if( m_attrib == XMLNS ) + { + m_attribIsXmlns = true; + m_attrib = EmptyString; + } + else + { + cleanup(); + return static_cast( i ); + } + break; + default: + m_attrib += c; + } + break; + case TagAttributeComplete: // we're expecting an equals sign or ws +// printf( "TagAttributeComplete: %c\n", c ); + if( isWhitespace( c ) ) + break; + + switch( c ) + { + case '=': + m_state = TagAttributeEqual; + break; + default: + cleanup(); + return static_cast( i ); + break; + } + break; + case TagAttributeEqual: // we have found an equals sign +// printf( "TagAttributeEqual: %c\n", c ); + if( isWhitespace( c ) ) + break; + + switch( c ) + { + case '"': + m_quote = true; + case '\'': + m_state = TagAttributeValue; + break; + default: + cleanup(); + return static_cast( i ); + break; + } + break; + case TagAttributeValue: // we're expecting value data +// printf( "TagValue: %c\n", c ); + switch( c ) + { + case '<': + cleanup(); + return static_cast( i ); + break; + case '\'': + if( m_quote ) + { + m_value += c; + break; + } + case '"': + addAttribute(); + m_state = TagNameAlmostComplete; + m_quote = false; + break; + case '&': +// printf( "TagAttributeValue, calling decode\n" ); + switch( decode( i, data ) ) + { + case DecodeValid: + break; + case DecodeInvalid: + cleanup(); + return static_cast( i ); + case DecodeInsufficient: + return -1; + } + break; + case '>': + default: + m_value += c; + } + break; + case TagNameAlmostComplete: +// printf( "TagAttributeEqual: %c\n", c ); + if( isWhitespace( c ) ) + { + m_state = TagNameComplete; + break; + } + + switch( c ) + { + case '/': + m_state = TagOpeningSlash; + break; + case '>': + if( m_preamble == 1 ) + { + cleanup(); + return static_cast( i ); + } + m_state = TagInside; + addTag(); + break; + case '?': + if( m_preamble == 1 ) + m_preamble = 2; + else + { + cleanup(); + return static_cast( i ); + } + break; + default: + cleanup(); + return static_cast( i ); + break; + } + break; + default: +// printf( "default action!?\n" ); + break; + } +// printf( "parser state: %d\n", m_state ); + } + + return -1; + } + + void Parser::addTag() + { + if( !m_root ) + { +// printf( "created Tag named %s, ", m_tag.c_str() ); + m_root = new Tag( m_tag ); + m_current = m_root; + } + else + { +// printf( "created Tag named %s, ", m_tag.c_str() ); + m_current = new Tag( m_current, m_tag ); + } + + if( m_haveTagPrefix ) + { +// printf( "setting tag prefix: %s\n", m_tagPrefix.c_str() ); + m_current->setPrefix( m_tagPrefix ); + m_haveTagPrefix = false; + } + + if( m_attribs.size() ) + { + m_current->setAttributes( m_attribs ); +// printf( "added %d attributes, ", m_attribs.size() ); + m_attribs.clear(); + } + + if( m_xmlnss ) + { +// printf( "have ns decls\n" ); +// StringMap::const_iterator it = m_xmlnss->begin(); +// for( ; it != m_xmlnss->end(); ++it ) +// printf( "%s='%s'\n", (*it).first.c_str(), (*it).second.c_str() ); + m_current->setXmlns( m_xmlnss ); + m_xmlnss = 0; + } + + m_current->setXmlns( m_xmlns ); + m_xmlns = EmptyString; + + if( m_tag == "stream" && m_root->xmlns() == XMLNS_STREAM ) + { + streamEvent( m_root ); + cleanup( m_deleteRoot ); + return; + } +// else +// printf( "%s, ", m_root->xml().c_str() ); + + if( m_root && m_root == m_current && m_tagPrefix == "stream" ) + m_root->setXmlns( XMLNS_STREAM, m_tagPrefix ); + + if( m_tag == "xml" && m_preamble == 2 ) + cleanup(); + } + + void Parser::addAttribute() + { + Tag::Attribute* attr = new Tag::Attribute( m_attrib, m_value );; + if( m_attribIsXmlns ) + { + if( !m_xmlnss ) + m_xmlnss = new StringMap(); + + (*m_xmlnss)[m_attrib] = m_value; + attr->setPrefix( XMLNS ); + } + else + { +// printf( "adding attribute: %s:%s='%s'\n", m_attribPrefix.c_str(), m_attrib.c_str(), m_value.c_str() ); + if( !m_attribPrefix.empty() ) + attr->setPrefix( m_attribPrefix ); + if( m_attrib == XMLNS ) + m_xmlns = m_value; + } + m_attribs.push_back( attr ); + m_attrib = EmptyString; + m_value = EmptyString; + m_attribPrefix = EmptyString; + m_haveAttribPrefix = false; + m_attribIsXmlns = false; + } + + void Parser::addCData() + { + if( m_current && !m_cdata.empty() ) + { + m_current->addCData( m_cdata ); +// printf( "added cdata %s to %s: %s\n", +// m_cdata.c_str(), m_current->name().c_str(), m_current->xml().c_str() ); + m_cdata = EmptyString; + } + } + + bool Parser::closeTag() + { +// printf( "about to close, " ); + + if( m_tag == "stream" && m_tagPrefix == "stream" ) + return true; + + if( !m_current || m_current->name() != m_tag + || ( !m_current->prefix().empty() && m_current->prefix() != m_tagPrefix ) ) + { +// printf( "current xml: %s\n", m_current->xml().c_str() ); +// printf( "current name: %s, m_tag: %s\n", m_current->name().c_str(), m_tag.c_str() ); +// printf( "current prefix: %s, m_tagPrefix: %s\n", m_current->prefix().c_str(), m_tagPrefix.c_str() ); + return false; + } + +// printf( "m_current: %s, ", m_current->name().c_str() ); +// printf( "m_tag: %s, ", m_tag.c_str() ); + + m_tagPrefix = EmptyString; + m_haveTagPrefix = false; + + if( m_current->parent() ) + m_current = m_current->parent(); + else + { +// printf( "pushing upstream\n" ); + streamEvent( m_root ); + cleanup( m_deleteRoot ); + } + + return true; + } + + void Parser::cleanup( bool deleteRoot ) + { + if( deleteRoot ) + delete m_root; + m_root = 0; + m_current = 0; + delete m_xmlnss; + m_xmlnss = 0; + m_cdata = EmptyString; + m_tag = EmptyString; + m_attrib = EmptyString; + m_attribPrefix = EmptyString; + m_tagPrefix = EmptyString; + m_haveAttribPrefix = false; + m_haveTagPrefix = false; + m_value = EmptyString; + m_xmlns = EmptyString; + util::clearList( m_attribs ); + m_attribs.clear(); + m_state = Initial; + m_preamble = 0; + } + + bool Parser::isValid( unsigned char c ) + { + return ( c != 0xc0 || c != 0xc1 || c < 0xf5 ); + } + + bool Parser::isWhitespace( unsigned char c ) + { + return ( c == 0x09 || c == 0x0a || c == 0x0d || c == 0x20 ); + } + + void Parser::streamEvent( Tag* tag ) + { + if( m_tagHandler ) + m_tagHandler->handleTag( tag ); + } + +} diff --git a/libs/libgloox/parser.h b/libs/libgloox/parser.h new file mode 100644 index 0000000..c8e333c --- /dev/null +++ b/libs/libgloox/parser.h @@ -0,0 +1,139 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef PARSER_H__ +#define PARSER_H__ + +#include "gloox.h" +#include "taghandler.h" +#include "tag.h" + +#include + +namespace gloox +{ + + + /** + * @brief This class implements an XML parser. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API Parser + { + public: + /** + * Constructs a new Parser object. + * @param ph The object to send incoming Tags to. + * @param deleteRoot Indicates whether a parsed Tag should be + * deleted after pushing it upstream. Defaults to @p true. + */ + Parser( TagHandler* ph, bool deleteRoot = true ); + + /** + * Virtual destructor. + */ + virtual ~Parser(); + + /** + * Use this function to feed the parser with more XML. + * @param data Raw xml to parse. It may be modified if backbuffering is necessary. + * @return Returns @b -1 if parsing was successful. If a parse error occured, the + * character position where the error was occured is returned. + */ + int feed( std::string& data ); + + /** + * Resets internal state. + * @param deleteRoot Whether to delete the m_root member. For + * internal use only. + */ + void cleanup( bool deleteRoot = true ); + + private: + enum ParserInternalState + { + Initial, + InterTag, + TagOpening, + TagOpeningSlash, + TagOpeningLt, + TagInside, + TagNameCollect, + TagNameComplete, + TagNameAlmostComplete, + TagAttribute, + TagAttributeComplete, + TagAttributeEqual, + TagClosing, + TagClosingSlash, + TagValueApos, + TagAttributeValue, + TagPreamble, + TagCDATASection + }; + + enum ForwardScanState + { + ForwardFound, + ForwardNotFound, + ForwardInsufficientSize + }; + + enum DecodeState + { + DecodeValid, + DecodeInvalid, + DecodeInsufficient + }; + + void addTag(); + void addAttribute(); + void addCData(); + bool closeTag(); + bool isWhitespace( unsigned char c ); + bool isValid( unsigned char c ); + void streamEvent( Tag* tag ); + ForwardScanState forwardScan( std::string::size_type& pos, const std::string& data, + const std::string& needle ); + DecodeState decode( std::string::size_type& pos, const std::string& data ); + + TagHandler* m_tagHandler; + Tag* m_current; + Tag* m_root; + StringMap* m_xmlnss; + + ParserInternalState m_state; + Tag::AttributeList m_attribs; + std::string m_tag; + std::string m_cdata; + std::string m_attrib; + std::string m_value; + std::string m_xmlns; + std::string m_tagPrefix; + std::string m_attribPrefix; + std::string m_backBuffer; + int m_preamble; + bool m_quote; + bool m_haveTagPrefix; + bool m_haveAttribPrefix; + bool m_attribIsXmlns; + bool m_deleteRoot; + + }; + +} + +#endif // PARSER_H__ diff --git a/libs/libgloox/prep.cpp b/libs/libgloox/prep.cpp new file mode 100644 index 0000000..64dcc8d --- /dev/null +++ b/libs/libgloox/prep.cpp @@ -0,0 +1,121 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "prep.h" + +#include "config.h" + +#ifdef HAVE_LIBIDN +# include +# include +#endif + +#include +#include + +#include + +#define JID_PORTION_SIZE 1023 + +namespace gloox +{ + + namespace prep + { + +#ifdef HAVE_LIBIDN + /** + * Applies a Stringprep profile to a string. This function does the actual + * work behind nodeprep, nameprep and resourceprep. + * @param s The string to apply the profile to. + * @param out Contains the prepped string if prepping was successful, else untouched. + * @param profile The Stringprep profile to apply. + * @return Returns @b true if prepping was successful, @b false otherwise. + */ + static bool prepare( const std::string& s, std::string& out, const Stringprep_profile* profile ) + { + if( s.empty() || s.length() > JID_PORTION_SIZE ) + return false; + + char* p = static_cast( calloc( JID_PORTION_SIZE, sizeof( char ) ) ); + strncpy( p, s.c_str(), s.length() ); + int rc = stringprep( p, JID_PORTION_SIZE, (Stringprep_profile_flags)0, profile ); + if( rc == STRINGPREP_OK ) + out = p; + free( p ); + return rc == STRINGPREP_OK; + } +#endif + + bool nodeprep( const std::string& node, std::string& out ) + { +#ifdef HAVE_LIBIDN + return prepare( node, out, stringprep_xmpp_nodeprep ); +#else + if( node.length() > JID_PORTION_SIZE ) + return false; + out = node; + return true; +#endif + } + + bool nameprep( const std::string& domain, std::string& out ) + { +#ifdef HAVE_LIBIDN + return prepare( domain, out, stringprep_nameprep ); +#else + if( domain.length() > JID_PORTION_SIZE ) + return false; + out = domain; + return true; +#endif + } + + bool resourceprep( const std::string& resource, std::string& out ) + { +#ifdef HAVE_LIBIDN + return prepare( resource, out, stringprep_xmpp_resourceprep ); +#else + if( resource.length() > JID_PORTION_SIZE ) + return false; + out = resource; + return true; +#endif + } + + bool idna( const std::string& domain, std::string& out ) + { +#ifdef HAVE_LIBIDN + if( domain.empty() || domain.length() > JID_PORTION_SIZE ) + return false; + + char* prepped; + int rc = idna_to_ascii_8z( domain.c_str(), &prepped, (Idna_flags)IDNA_USE_STD3_ASCII_RULES ); + if( rc == IDNA_SUCCESS ) + { + out = prepped; + return true; + } + if( rc != IDNA_MALLOC_ERROR ) + free( prepped ); + return false; +#else + if( domain.length() > JID_PORTION_SIZE ) + return false; + out = domain; + return true; +#endif + } + + } + +} diff --git a/libs/libgloox/prep.h b/libs/libgloox/prep.h new file mode 100644 index 0000000..d8c5d37 --- /dev/null +++ b/libs/libgloox/prep.h @@ -0,0 +1,85 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef PREP_H__ +#define PREP_H__ + +#include "macros.h" + +#include + +namespace gloox +{ + + /** + * @brief This namespace offers functions to stringprep the individual parts of a JID. + * + * You should not need to use these functions directly. All the + * necessary prepping is done for you if you stick to the interfaces provided. + * If you write your own enhancements, check with the spec. + * + * @note These functions depend on an installed LibIDN at compile time of gloox. If + * LibIDN is not installed these functions return the string they are given + * without any modification. + * + * @author Jakob Schroeter + * @since 0.2 + */ + namespace prep + { + /** + * This function applies the Nodeprep profile of Stringprep to a string. + * @param node The string to apply the profile to. + * @param out The prepped string. In case of an error this string is not touched. + * If LibIDN is not available the string is returned unchanged. + * @return @b True if prepping was successful, @b false otherwise or of LibIDN + * is not available. + */ + bool nodeprep( const std::string& node, std::string& out ); + + /** + * This function applies the Nameprep profile of Stringprep to a string. + * @param domain The string to apply the profile to. + * @param out The prepped string. In case of an error this string is not touched. + * If LibIDN is not available the string is returned unchanged. + * @return @b True if prepping was successful, @b false otherwise or of LibIDN + * is not available. + */ + bool nameprep( const std::string& domain, std::string& out ); + + /** + * This function applies the Resourceprep profile of Stringprep to a std::string. + * @param resource The string to apply the profile to. + * @param out The prepped string. In case of an error this string is not touched. + * If LibIDN is not available the string is returned unchanged. + * @return @b True if prepping was successful, @b false otherwise or of LibIDN + * is not available. + */ + bool resourceprep( const std::string& resource, std::string& out ); + + /** + * This function applies the idna() function to a string. I.e. it transforms + * internationalized domain names into plain ASCII. + * @param domain The string to convert. + * @param out The converted string. In case of an error this string is not touched. + * If LibIDN is not available the string is returned unchanged. + * @return @b True if prepping was successful, @b false otherwise or of LibIDN + * is not available. + */ + bool idna( const std::string& domain, std::string& out ); + + } + +} + +#endif // PREP_H__ diff --git a/libs/libgloox/presence.cpp b/libs/libgloox/presence.cpp new file mode 100644 index 0000000..67c54a8 --- /dev/null +++ b/libs/libgloox/presence.cpp @@ -0,0 +1,143 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "presence.h" +#include "capabilities.h" +#include "util.h" + +#include + +namespace gloox +{ + + static const char* msgTypeStringValues[] = + { + "available", "", "", "", "", "unavailable", "probe", "error" + }; + + static inline const std::string typeString( Presence::PresenceType type ) + { + return util::lookup( type, msgTypeStringValues ); + } + + static const char* msgShowStringValues[] = + { + "", "chat", "away", "dnd", "xa", "", "", "" + }; + + static inline const std::string showString( Presence::PresenceType type ) + { + return util::lookup( type, msgShowStringValues ); + } + + Presence::Presence( Tag* tag ) + : Stanza( tag ), m_subtype( Invalid ), m_stati( 0 ), m_priority( 0 ) + { + if( !tag || tag->name() != "presence" ) + return; + + const std::string& type = tag->findAttribute( TYPE ); + if( type.empty() ) + m_subtype = Available; + else + m_subtype = (PresenceType)util::lookup( type, msgTypeStringValues ); + + if( m_subtype == Available ) + { + Tag* t = tag->findChild( "show" ); + if( t ) + m_subtype = (PresenceType)util::lookup( t->cdata(), msgShowStringValues ); + } + + const TagList& c = tag->children(); + TagList::const_iterator it = c.begin(); + for( ; it != c.end(); ++it ) + { + if( (*it)->name() == "status" ) + setLang( &m_stati, m_status, (*it) ); + else if( (*it)->name() == "priority" ) + m_priority = atoi( (*it)->cdata().c_str() ); + } + } + + Presence::Presence( PresenceType type, const JID& to, const std::string& status, + int priority, const std::string& xmllang ) + : Stanza( to ), m_subtype( type ), m_stati( 0 ) + { + setLang( &m_stati, m_status, status, xmllang ); + + setPriority( priority ); + } + + Presence::~Presence() + { + delete m_stati; + } + + void Presence::resetStatus() + { + delete m_stati; + m_stati = 0; + m_status = ""; + } + + void Presence::setPriority( int priority ) + { + if( priority < -128 ) + m_priority = -128; + else if( priority > 127 ) + m_priority = 127; + else + m_priority = priority; + } + + const Capabilities* Presence::capabilities() const + { + return findExtension( ExtCaps ); + } + + Tag* Presence::tag() const + { + if( m_subtype == Invalid ) + return 0; + + Tag* t = new Tag( "presence" ); + if( m_to ) + t->addAttribute( "to", m_to.full() ); + if( m_from ) + t->addAttribute( "from", m_from.full() ); + + const std::string& type = typeString( m_subtype ); + if( !type.empty() ) + { + if( type != "available" ) + t->addAttribute( "type", type ); + } + else + { + const std::string& show = showString( m_subtype ); + if( !show.empty() ) + new Tag( t, "show", show ); + } + + new Tag( t, "priority", util::int2string( m_priority ) ); + + getLangs( m_stati, m_status, "status", t ); + + StanzaExtensionList::const_iterator it = m_extensionList.begin(); + for( ; it != m_extensionList.end(); ++it ) + t->addChild( (*it)->tag() ); + + return t; + } + +} diff --git a/libs/libgloox/presence.h b/libs/libgloox/presence.h new file mode 100644 index 0000000..1611181 --- /dev/null +++ b/libs/libgloox/presence.h @@ -0,0 +1,161 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef PRESENCE_H__ +#define PRESENCE_H__ + +#include "stanza.h" + +#include + +namespace gloox +{ + + class Capabilities; + class JID; + + /** + * @brief An abstraction of a presence stanza. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Presence : public Stanza + { + + friend class ClientBase; + + public: + + /** + * Describes the different valid presence types. + */ + enum PresenceType + { + Available, /**< The entity is online. */ + Chat, /**< The entity is 'available for chat'. */ + Away, /**< The entity is away. */ + DND, /**< The entity is DND (Do Not Disturb). */ + XA, /**< The entity is XA (eXtended Away). */ + Unavailable, /**< The entity is offline. */ + Probe, /**< This is a presence probe. */ + Error, /**< This is a presence error. */ + Invalid /**< The stanza is invalid. */ + }; + + /** + * Creates a Presence request. + * @param type The presence type. + * @param to The intended receiver. Use an empty JID to create a broadcast packet. + * @param status An optional status message (e.g. "gone fishing"). + * @param priority An optional presence priority. Legal range is between -128 and +127. + * Defaults to 0. + * @param xmllang An optional xml:lang for the status message. + */ + Presence( PresenceType type, const JID& to, const std::string& status = EmptyString, + int priority = 0, const std::string& xmllang = EmptyString ); + + /** + * Destructor. + */ + virtual ~Presence(); + + /** + * Returns the presence's type. + * @return The presence's type. + */ + PresenceType subtype() const { return m_subtype; } + + /** + * A convenience function returning the stanza's Capabilities, if any. May be 0. + * @return A pointer to a Capabilities object, or 0. + */ + const Capabilities* capabilities() const; + + /** + * Returns the presence's type. + * @return The presence's type. + */ +//#warning FIXME return something useful (only 'show' values?) or kill this func + PresenceType presence() const { return m_subtype; } + + /** + * Sets the presence type. + * @param type The presence type. + */ + void setPresence( PresenceType type ) { m_subtype = type; } + + /** + * Returns the status text of a presence stanza for the given language if available. + * If the requested language is not available, the default status text (without a xml:lang + * attribute) will be returned. + * @param lang The language identifier for the desired language. It must conform to + * section 2.12 of the XML specification and RFC 3066. If empty, the default body + * will be returned, if any. + * @return The status text set by the sender. + */ + const std::string status( const std::string& lang = "default" ) const + { + return findLang( m_stati, m_status, lang ); + } + + /** + * Adds a (possibly translated) status message. + * @param status The status message. + * @param lang The language identifier for the desired language. It must conform to + * section 2.12 of the XML specification and RFC 3066. + */ + void addStatus( const std::string& status, const std::string& lang = EmptyString ) + { + setLang( &m_stati, m_status, status, lang ); + } + + /** + * Resets the default status message as well as all language-specific ones. + */ + void resetStatus(); + + /** + * Returns the presence priority in the legal range: -128 to +127. + * @return The priority information contained in the stanza, defaults to 0. + */ + int priority() const { return m_priority; } + + /** + * Sets the priority. Legal range: -128 to +127. + * @param priority The priority to set. + */ + void setPriority( int priority ); + + // reimplemented from Stanza + virtual Tag* tag() const; + + private: +#ifdef PRESENCE_TEST + public: +#endif + /** + * Creates a Presence request from the given Tag. The original Tag will be ripped off. + * @param tag The Tag to parse. + */ + Presence( Tag* tag ); + + PresenceType m_subtype; + StringMap* m_stati; + std::string m_status; + int m_priority; + + }; + +} + +#endif // PRESENCE_H__ diff --git a/libs/libgloox/presencehandler.h b/libs/libgloox/presencehandler.h new file mode 100644 index 0000000..8e727e3 --- /dev/null +++ b/libs/libgloox/presencehandler.h @@ -0,0 +1,50 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef PRESENCEHANDLER_H__ +#define PRESENCEHANDLER_H__ + +#include "presence.h" + +namespace gloox +{ + + /** + * @brief A virtual interface which can be reimplemented to receive presence stanzas. + * + * Derived classes can be registered as PresenceHandlers with the Client. + * Upon an incoming Presence packet @ref handlePresence() will be called. + * @author Jakob Schroeter + */ + class GLOOX_API PresenceHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~PresenceHandler() {} + + /** + * Reimplement this function if you want to be updated on + * incoming presence notifications. + * @param presence The complete stanza. + * @since 1.0 + */ + virtual void handlePresence( const Presence& presence ) = 0; + + }; + +} + +#endif // PRESENCEHANDLER_H__ diff --git a/libs/libgloox/privacyitem.cpp b/libs/libgloox/privacyitem.cpp new file mode 100644 index 0000000..e2f6f02 --- /dev/null +++ b/libs/libgloox/privacyitem.cpp @@ -0,0 +1,42 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "privacyitem.h" + +namespace gloox +{ + + PrivacyItem::PrivacyItem( const ItemType type, const ItemAction action, + const int packetType, const std::string& value ) + : m_type( type ), m_action( action ), m_packetType( packetType ), + m_value( value ) + { + } + + PrivacyItem::~PrivacyItem() + { + } + + bool PrivacyItem::operator==( const PrivacyItem& item ) const + { + if( m_type == item.type() + && m_action == item.action() + && m_packetType == item.packetType() + && m_value == item.value() ) + return true; + else + return false; + } + +} diff --git a/libs/libgloox/privacyitem.h b/libs/libgloox/privacyitem.h new file mode 100644 index 0000000..727b982 --- /dev/null +++ b/libs/libgloox/privacyitem.h @@ -0,0 +1,126 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef PRIVACYITEM_H__ +#define PRIVACYITEM_H__ + +#include "macros.h" +#include "gloox.h" + +#include + +namespace gloox +{ + + /** + * @brief This is an abstraction of a single item of a privacy list, describing an allowed or + * forbidden action. + * + * @author Jakob Schroeter + * @since 0.3 + */ + class GLOOX_API PrivacyItem + { + public: + + /** + * Three possible types of an item. Only one is allowed at a time. + */ + enum ItemType + { + TypeUndefined, /**< None of the types below is explicitely selected, + * "fall-through" case. */ + TypeJid, /**< The item affects the JID which is given in the value attribute. */ + TypeGroup, /**< The item affects the group which is given in the value attribute and + * which must exist at least once in the users roster. */ + TypeSubscription /**< The item affects the subscription type which is given in the value + * attribute. */ + }; + + /** + * Two possible actions. Only one is allowed at a time. + */ + enum ItemAction + { + ActionAllow, /**< The item explicitely allows the described packets. */ + ActionDeny /**< The item forbids the described packets. */ + }; + + /** + * The packet type a privacy item affects (blocks). Combinations are allowed. + */ + enum ItemPacketType + { + PacketMessage = 1, /**< The item blocks message stanzas. */ + PacketPresenceIn = 2, /**< The item blocks incoming presence stanzas. */ + PacketPresenceOut = 4, /**< The item blocks outgoing presence stanzas. */ + PacketIq = 8, /**< The item blocks IQ stanzas. */ + PacketAll = 15 /**< The item blocks all of these stanza types. */ + }; + + /** + * Constructs a new privacy item. + * @param type Action is based on matching JID, Group or Subscription. + * @param action The action to carry out. (Deny or allow.) + * @param packetType Affected packet types. Bit-wise OR'ed ItemPacketType. + * @param value The value to check for and match. + */ + PrivacyItem( const ItemType type = TypeUndefined, const ItemAction action = ActionAllow, + const int packetType = 0, const std::string& value = EmptyString ); + + /** + * Virtual destructor. + */ + virtual ~PrivacyItem(); + + /** + * Returns the item type. + * @return The type of the item. + */ + ItemType type() const { return m_type; } + + /** + * Returns the item's action. + * @return The action of the item. + */ + ItemAction action() const { return m_action; } + + /** + * Returns the packet type the item affects. + * @return An OR'ed list of affected packet types. + */ + int packetType() const { return m_packetType; } + + /** + * Returns the value of the item's 'value' attribute. + * @return value The 'value' attribute's value. + */ + const std::string value() const { return m_value; } + + /** + * Compares the current PrivacyItem with another one. + * @param item The item which shall be compared. + * @return @b True if both items are equal, @b false otherwise. + */ + bool operator==( const PrivacyItem& item ) const; + + private: + ItemType m_type; + ItemAction m_action; + int m_packetType; + std::string m_value; + }; + +} + +#endif // PRIVACYITEM_H__ diff --git a/libs/libgloox/privacylisthandler.h b/libs/libgloox/privacylisthandler.h new file mode 100644 index 0000000..7a5c167 --- /dev/null +++ b/libs/libgloox/privacylisthandler.h @@ -0,0 +1,99 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef PRIVACYLISTHANDLER_H__ +#define PRIVACYLISTHANDLER_H__ + +#include "privacyitem.h" +#include "gloox.h" + +#include +#include + +namespace gloox +{ + + /** + * The possible results of an operation on a privacy list. + */ + enum PrivacyListResult + { + ResultStoreSuccess, /**< Storing was successful. */ + ResultActivateSuccess, /**< Activation was successful. */ + ResultDefaultSuccess, /**< Setting the default list was successful. */ + ResultRemoveSuccess, /**< Removing a list was successful. */ + ResultRequestNamesSuccess, /**< Requesting the list names was successful. */ + ResultRequestListSuccess, /**< The list was requested successfully. */ + ResultConflict, /**< A conflict occurred when activating a list or setting the default + * list. */ + ResultItemNotFound, /**< The requested list does not exist. */ + ResultBadRequest, /**< Bad request. */ + ResultUnknownError /**< An unknown error occured. */ + }; + + /** + * @brief A virtual interface that allows to retrieve Privacy Lists. + * + * @author Jakob Schroeter + * @since 0.3 + */ + class GLOOX_API PrivacyListHandler + { + public: + + /** + * A list of PrivacyItems. + */ + typedef std::list PrivacyList; + + /** + * Virtual Destructor. + */ + virtual ~PrivacyListHandler() {} + + /** + * Reimplement this function to retrieve the list of privacy list names after requesting it using + * PrivacyManager::requestListNames(). + * @param active The name of the active list. + * @param def The name of the default list. + * @param lists All the lists. + */ + virtual void handlePrivacyListNames( const std::string& active, const std::string& def, + const StringList& lists ) = 0; + + /** + * Reimplement this function to retrieve the content of a privacy list after requesting it using + * PrivacyManager::requestList(). + * @param name The name of the list. + * @param items A list of PrivacyItem's. + */ + virtual void handlePrivacyList( const std::string& name, const PrivacyList& items ) = 0; + + /** + * Reimplement this function to be notified about new or changed lists. + * @param name The name of the new or changed list. + */ + virtual void handlePrivacyListChanged( const std::string& name ) = 0; + + /** + * Reimplement this function to receive results of stores etc. + * @param id The ID of the request, as returned by the initiating function. + * @param plResult The result of an operation. + */ + virtual void handlePrivacyListResult( const std::string& id, PrivacyListResult plResult ) = 0; + + }; + +} + +#endif // PRIVACYLISTHANDLER_H__ diff --git a/libs/libgloox/privacymanager.cpp b/libs/libgloox/privacymanager.cpp new file mode 100644 index 0000000..f7e8fc8 --- /dev/null +++ b/libs/libgloox/privacymanager.cpp @@ -0,0 +1,316 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "privacymanager.h" +#include "clientbase.h" +#include "error.h" + +namespace gloox +{ + + // ---- PrivacyManager::Query ---- + PrivacyManager::Query::Query( const Tag* tag ) + : StanzaExtension( ExtPrivacy ) + { + if( !tag ) + return; + + const TagList& l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + const std::string& name = (*it)->findAttribute( "name" ); + if( (*it)->name() == "default" ) + m_default = name; + else if( (*it)->name() == "active" ) + m_active = name; + else if( (*it)->name() == "list" ) + { + m_names.push_back( name ); + + const TagList& l = (*it)->children(); + TagList::const_iterator it_l = l.begin(); + for( ; it_l != l.end(); ++it_l ) + { + PrivacyItem::ItemType type; + PrivacyItem::ItemAction action; + int packetType = 0; + + const std::string& t = (*it_l)->findAttribute( TYPE ); + if( t == "jid" ) + type = PrivacyItem::TypeJid; + else if( t == "group" ) + type = PrivacyItem::TypeGroup; + else if( t == "subscription" ) + type = PrivacyItem::TypeSubscription; + else + type = PrivacyItem::TypeUndefined; + + const std::string& a = (*it_l)->findAttribute( "action" ); + if( a == "allow" ) + action = PrivacyItem::ActionAllow; + else if( a == "deny" ) + action = PrivacyItem::ActionDeny; + else + action = PrivacyItem::ActionAllow; + + const std::string& value = (*it_l)->findAttribute( "value" ); + + const TagList& c = (*it_l)->children(); + TagList::const_iterator it_c = c.begin(); + for( ; it_c != c.end(); ++it_c ) + { + if( (*it_c)->name() == "iq" ) + packetType |= PrivacyItem::PacketIq; + else if( (*it_c)->name() == "presence-out" ) + packetType |= PrivacyItem::PacketPresenceOut; + else if( (*it_c)->name() == "presence-in" ) + packetType |= PrivacyItem::PacketPresenceIn; + else if( (*it_c)->name() == "message" ) + packetType |= PrivacyItem::PacketMessage; + } + + PrivacyItem item( type, action, packetType, value ); + m_items.push_back( item ); + } + } + } + } + + PrivacyManager::Query::Query( IdType context, const std::string& name, + const PrivacyListHandler::PrivacyList& list ) + : StanzaExtension( ExtPrivacy ), m_context( context ), m_items( list ) + { + m_names.push_back( name ); + } + + PrivacyManager::Query::~Query() + { + } + + const std::string& PrivacyManager::Query::filterString() const + { + static const std::string filter = "/iq/query[@xmlns='" + XMLNS_PRIVACY + "']"; + return filter; + } + + Tag* PrivacyManager::Query::tag() const + { + Tag* t = new Tag( "query" ); + t->setXmlns( XMLNS_PRIVACY ); + + std::string child; + switch( m_context ) + { + case PLRequestList: + case PLRemove: + case PLStore: + child = "list"; + break; + case PLDefault: + case PLUnsetDefault: + child = "default"; + break; + case PLActivate: + case PLUnsetActivate: + child = "active"; + break; + default: + case PLRequestNames: + return t; + break; + } + Tag* c = new Tag( t, child ); + + if( !m_names.empty() ) + c->addAttribute( "name", (*m_names.begin()) ); + + int count = 0; + PrivacyListHandler::PrivacyList::const_iterator it = m_items.begin(); + for( ; it != m_items.end(); ++it ) + { + Tag* i = new Tag( c, "item" ); + + switch( (*it).type() ) + { + case PrivacyItem::TypeJid: + i->addAttribute( TYPE, "jid" ); + break; + case PrivacyItem::TypeGroup: + i->addAttribute( TYPE, "group" ); + break; + case PrivacyItem::TypeSubscription: + i->addAttribute( TYPE, "subscription" ); + break; + default: + break; + } + + switch( (*it).action() ) + { + case PrivacyItem::ActionAllow: + i->addAttribute( "action", "allow" ); + break; + case PrivacyItem::ActionDeny: + i->addAttribute( "action", "deny" ); + break; + } + + int pType = (*it).packetType(); + if( pType != 15 ) + { + if( pType & PrivacyItem::PacketMessage ) + new Tag( i, "message" ); + if( pType & PrivacyItem::PacketPresenceIn ) + new Tag( i, "presence-in" ); + if( pType & PrivacyItem::PacketPresenceOut ) + new Tag( i, "presence-out" ); + if( pType & PrivacyItem::PacketIq ) + new Tag( i, "iq" ); + } + + i->addAttribute( "value", (*it).value() ); + i->addAttribute( "order", ++count ); + } + + return t; + } + // ---- ~PrivacyManager::Query ---- + + // ---- PrivacyManager ---- + PrivacyManager::PrivacyManager( ClientBase* parent ) + : m_parent( parent ), m_privacyListHandler( 0 ) + { + if( m_parent ) + { + m_parent->registerStanzaExtension( new Query() ); + m_parent->registerIqHandler( this, ExtPrivacy ); + } + } + + PrivacyManager::~PrivacyManager() + { + if( m_parent ) + { + m_parent->removeIqHandler( this, ExtPrivacy ); + m_parent->removeIDHandler( this ); + } + } + + std::string PrivacyManager::operation( IdType context, const std::string& name ) + { + const std::string& id = m_parent->getID(); + IQ::IqType iqType = IQ::Set; + if( context == PLRequestNames || context == PLRequestList ) + iqType = IQ::Get; + IQ iq( iqType, JID(), id ); + iq.addExtension( new Query( context, name ) ); + m_parent->send( iq, this, context ); + return id; + } + + std::string PrivacyManager::store( const std::string& name, const PrivacyListHandler::PrivacyList& list ) + { + if( list.empty() ) + return EmptyString; + + const std::string& id = m_parent->getID(); + + IQ iq( IQ::Set, JID(), id ); + iq.addExtension( new Query( PLStore, name, list ) ); + m_parent->send( iq, this, PLStore ); + return id; + } + + bool PrivacyManager::handleIq( const IQ& iq ) + { + const Query* q = iq.findExtension( ExtPrivacy ); + if( iq.subtype() != IQ::Set || !m_privacyListHandler + || !q || q->name().empty() ) + return false; + + m_privacyListHandler->handlePrivacyListChanged( q->name() ); + IQ re( IQ::Result, JID(), iq.id() ); + m_parent->send( re ); + return true; + } + + void PrivacyManager::handleIqID( const IQ& iq, int context ) + { + if( !m_privacyListHandler ) + return; + + switch( iq.subtype() ) + { + case IQ::Result: + switch( context ) + { + case PLStore: + m_privacyListHandler->handlePrivacyListResult( iq.id(), ResultStoreSuccess ); + break; + case PLActivate: + m_privacyListHandler->handlePrivacyListResult( iq.id(), ResultActivateSuccess ); + break; + case PLDefault: + m_privacyListHandler->handlePrivacyListResult( iq.id(), ResultDefaultSuccess ); + break; + case PLRemove: + m_privacyListHandler->handlePrivacyListResult( iq.id(), ResultRemoveSuccess ); + break; + case PLRequestNames: + { + const Query* q = iq.findExtension( ExtPrivacy ); + if( !q ) + return; + m_privacyListHandler->handlePrivacyListNames( q->def(), q->active(), + q->names() ); + break; + } + case PLRequestList: + { + const Query* q = iq.findExtension( ExtPrivacy ); + if( !q ) + return; + m_privacyListHandler->handlePrivacyList( q->name(), q->items() ); + break; + } + } + break; + + case IQ::Error: + { + switch( iq.error()->error() ) + { + case StanzaErrorConflict: + m_privacyListHandler->handlePrivacyListResult( iq.id(), ResultConflict ); + break; + case StanzaErrorItemNotFound: + m_privacyListHandler->handlePrivacyListResult( iq.id(), ResultItemNotFound ); + break; + case StanzaErrorBadRequest: + m_privacyListHandler->handlePrivacyListResult( iq.id(), ResultBadRequest ); + break; + default: + m_privacyListHandler->handlePrivacyListResult( iq.id(), ResultUnknownError ); + break; + } + break; + } + + default: + break; + } + } + +} diff --git a/libs/libgloox/privacymanager.h b/libs/libgloox/privacymanager.h new file mode 100644 index 0000000..84d8f69 --- /dev/null +++ b/libs/libgloox/privacymanager.h @@ -0,0 +1,231 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef PRIVACYMANAGER_H__ +#define PRIVACYMANAGER_H__ + +#include "iqhandler.h" +#include "privacylisthandler.h" +#include "stanzaextension.h" + +#include + +namespace gloox +{ + + class ClientBase; + + /** + * @brief This class implements a manager for privacy lists as defined in section 10 of RFC 3921. + * + * @author Jakob Schroeter + * @since 0.3 + */ + class GLOOX_API PrivacyManager : public IqHandler + { + public: + /** + * Constructs a new PrivacyManager. + * @param parent The ClientBase to use for communication. + */ + PrivacyManager( ClientBase* parent ); + + /** + * Virtual destructor. + */ + virtual ~PrivacyManager(); + + /** + * Stores the given list on the server. If a list with the given name exists, the existing + * list is overwritten. + * @param name The list's name. + * @param list A non empty list of privacy items which describe the list. + */ + std::string store( const std::string& name, const PrivacyListHandler::PrivacyList& list ); + + /** + * Triggers the request of the privacy lists currently stored on the server. + */ + std::string requestListNames() + { return operation( PLRequestNames, EmptyString ); } + + /** + * Triggers the retrieval of the named privacy lists. + * @param name The name of the list to retrieve. + */ + std::string requestList( const std::string& name ) + { return operation( PLRequestList, name ); } + + /** + * Removes a list by its name. + * @param name The name of the list to remove. + */ + std::string removeList( const std::string& name ) + { return operation( PLRemove, name ); } + + /** + * Sets the named list as the default list, i.e. active by default after login. + * @param name The name of the list to set as default. + */ + std::string setDefault( const std::string& name ) + { return operation( PLDefault, name ); } + + /** + * This function declines the use of any default list. + */ + std::string unsetDefault() + { return operation( PLUnsetDefault, EmptyString ); } + + /** + * Sets the named list as active, i.e. active for this session + * @param name The name of the list to set active. + */ + std::string setActive( const std::string& name ) + { return operation( PLActivate, name ); } + + /** + * This function declines the use of any active list. + */ + std::string unsetActive() + { return operation( PLUnsetActivate, EmptyString ); } + + /** + * Use this function to register an object as PrivacyListHandler. + * Only one PrivacyListHandler at a time is possible. + * @param plh The object to register as handler for privacy list related events. + */ + void registerPrivacyListHandler( PrivacyListHandler* plh ) + { m_privacyListHandler = plh; } + + /** + * Use this function to clear the registered PrivacyListHandler. + */ + void removePrivacyListHandler() + { m_privacyListHandler = 0; } + + // reimplemented from IqHandler. + virtual bool handleIq( const IQ& iq ); + + // reimplemented from IqHandler. + virtual void handleIqID( const IQ& iq, int context ); + + private: + enum IdType + { + PLRequestNames, + PLRequestList, + PLActivate, + PLDefault, + PLUnsetActivate, + PLUnsetDefault, + PLRemove, + PLStore + }; + + class Query : public StanzaExtension + { + public: + /** + * Creates a new query for storing or requesting a + * privacy list. + * @param context The context of the list. + * @param name The list's name. + * @param list The list's (optional) content. + */ + Query( IdType context, const std::string& name, + const PrivacyListHandler::PrivacyList& list = PrivacyListHandler::PrivacyList() ); + + /** + * Creates a new query from the given Tag. + * @param tag The Tag to parse. + */ + Query( const Tag* tag = 0 ); + + /** + * Virtual destructor. + */ + virtual ~Query(); + + /** + * Returns the name of the active list, if given. + * @return The active list's name. + */ + const std::string& active() const { return m_active; } + + /** + * Returns the name of the default list, if given. + * @return The default list's name. + */ + const std::string& def() const { return m_default; } + + /** + * Returns a list of privacy items, if given. + * @return A list of PrivacyItems. + */ + const PrivacyListHandler::PrivacyList& items() const + { return m_items; } + + /** + * Returns a list of list names. + * @return A list of list names. + */ + const StringList& names() const { return m_names; } + + /** + * A convenience function that returns the first name of the list that + * names() would return, or an empty string. + * @return A list name. + */ + const std::string& name() const + { + if( m_names.empty()) + return EmptyString; + else + return (*m_names.begin()); + } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Query( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new Query( *this ); + } + + private: + IdType m_context; + StringList m_names; + std::string m_default; + std::string m_active; + PrivacyListHandler::PrivacyList m_items; + }; + + std::string operation( IdType context, const std::string& name ); + + ClientBase* m_parent; + PrivacyListHandler* m_privacyListHandler; + }; + +} + +#endif // PRIVACYMANAGER_H__ diff --git a/libs/libgloox/privatexml.cpp b/libs/libgloox/privatexml.cpp new file mode 100644 index 0000000..ada2a2a --- /dev/null +++ b/libs/libgloox/privatexml.cpp @@ -0,0 +1,129 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "privatexml.h" +#include "clientbase.h" +#include "stanza.h" + +namespace gloox +{ + + // ---- PrivateXML::Query ---- + PrivateXML::Query::Query( const Tag* tag ) + : StanzaExtension( ExtPrivateXML ), m_privateXML( 0 ) + { + if( !tag ) + return; + + if( tag->name() == "query" && tag->xmlns() == XMLNS_PRIVATE_XML ) + { + if( tag->children().size() ) + m_privateXML = tag->children().front()->clone(); + } + else + m_privateXML = tag; + } + + const std::string& PrivateXML::Query::filterString() const + { + static const std::string filter = "/iq/query[@xmlns='" + XMLNS_PRIVATE_XML + "']"; + return filter; + } + + Tag* PrivateXML::Query::tag() const + { + Tag* t = new Tag( "query" ); + t->setXmlns( XMLNS_PRIVATE_XML ); + if( m_privateXML ) + t->addChild( m_privateXML->clone() ); + return t; + } + // ---- ~PrivateXML::Query ---- + + // ---- PrivateXML ---- + PrivateXML::PrivateXML( ClientBase* parent ) + : m_parent( parent ) + { + if( !m_parent ) + return; + + m_parent->registerIqHandler( this, ExtPrivateXML ); + m_parent->registerStanzaExtension( new Query() ); + } + + PrivateXML::~PrivateXML() + { + if( !m_parent ) + return; + + m_parent->removeIqHandler( this, ExtPrivateXML ); + m_parent->removeIDHandler( this ); + m_parent->removeStanzaExtension( ExtPrivateXML ); + } + + std::string PrivateXML::requestXML( const std::string& tag, const std::string& xmlns, + PrivateXMLHandler* pxh ) + { + const std::string& id = m_parent->getID(); + + IQ iq( IQ::Get, JID(), id ); + iq.addExtension( new Query( tag, xmlns ) ); + + m_track[id] = pxh; + m_parent->send( iq, this, RequestXml ); + + return id; + } + + std::string PrivateXML::storeXML( const Tag* tag, PrivateXMLHandler* pxh ) + { + const std::string& id = m_parent->getID(); + + IQ iq( IQ::Set, JID(), id ); + iq.addExtension( new Query( tag ) ); + + m_track[id] = pxh; + m_parent->send( iq, this, StoreXml ); + + return id; + } + + void PrivateXML::handleIqID( const IQ& iq, int context ) + { + TrackMap::iterator t = m_track.find( iq.id() ); + if( t == m_track.end() ) + return; + + if( iq.subtype() == IQ::Result ) + { + if( context == RequestXml ) + { + const Query* q = iq.findExtension( ExtPrivateXML ); + if( q ) + (*t).second->handlePrivateXML( q->privateXML() ); + } + else if( context == StoreXml ) + (*t).second->handlePrivateXMLResult( iq.id(), PrivateXMLHandler::PxmlStoreOk ); + } + else if( iq.subtype() == IQ::Error ) + { + if( context == RequestXml ) + (*t).second->handlePrivateXMLResult( iq.id(), PrivateXMLHandler::PxmlRequestError ); + else if( context == StoreXml ) + (*t).second->handlePrivateXMLResult( iq.id(), PrivateXMLHandler::PxmlStoreError ); + } + + m_track.erase( t ); + } + +} diff --git a/libs/libgloox/privatexml.h b/libs/libgloox/privatexml.h new file mode 100644 index 0000000..b0e00f3 --- /dev/null +++ b/libs/libgloox/privatexml.h @@ -0,0 +1,160 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef PRIVATEXML_H__ +#define PRIVATEXML_H__ + +#include "iqhandler.h" +#include "privatexmlhandler.h" + +#include +#include +#include + +namespace gloox +{ + + class ClientBase; + class Tag; + class Stanza; + + /** + * @brief This class implements XEP-0049 (Private XML Storage). + * + * @author Jakob Schroeter + */ + class GLOOX_API PrivateXML : public IqHandler + { + public: + /** + * Constructor. + * Creates a new PrivateXML client that registers as IqHandler + * with @c ClientBase. + * @param parent The ClientBase used for XMPP communication + */ + PrivateXML( ClientBase* parent ); + + /** + * Virtual destructor. + */ + virtual ~PrivateXML(); + + /** + * Use this function to request the private XML stored in the given namespace. + * @param tag Child element of the query element used to identify the requested XML fragment. + * @param xmlns The namespace which qualifies the tag. + * @param pxh The handler to receive the result. + * @return The ID of the sent query. + */ + std::string requestXML( const std::string& tag, const std::string& xmlns, PrivateXMLHandler* pxh ); + + /** + * Use this function to store private XML stored in the given namespace. + * @param tag The XML to store. This is the complete tag including the unique namespace. + * It is deleted automatically after sending it. + * @param pxh The handler to receive the result. + * @return The ID of the sent query. + */ + std::string storeXML( const Tag* tag, PrivateXMLHandler* pxh ); + + // reimplemented from IqHandler. + virtual bool handleIq( const IQ& iq ) { (void)iq; return false; } + + // reimplemented from IqHandler. + virtual void handleIqID( const IQ& iq, int context ); + + protected: + ClientBase* m_parent; + + private: +#ifdef PRIVATEXML_TEST + public: +#endif + /** + * @brief An implementation of the Private XML Storage protocol as StanzaExtension. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class Query : public StanzaExtension + { + public: + /** + * Constructs a new Query object suitable for use with Private XML Storage. + * @param tag The private XML's element name. + * @param xmlns The private XML's namespace. + */ + Query( const std::string& tag, const std::string& xmlns ) + : StanzaExtension( ExtPrivateXML ) + { + m_privateXML = new Tag( tag, XMLNS, xmlns ); + } + + /** + * Constructs a new Query object suitable for storing an XML fragment in + * Private XML Storage. + * @param tag The private XML element to store. The Query object will own the Tag. + */ + Query( const Tag* tag = 0 ); + + /** + * Destructor. + */ + ~Query() { delete m_privateXML; } + + /** + * Returns the private XML fragment. The Tag is owned by the Query object. + * @return The stored private XML fragment. + */ + const Tag* privateXML() const { return m_privateXML; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Query( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + Query* q = new Query(); + q->m_privateXML = m_privateXML ? m_privateXML->clone() : 0; + return q; + } + + private: + const Tag* m_privateXML; + + }; + + enum IdType + { + RequestXml, + StoreXml + }; + + typedef std::map TrackMap; + + TrackMap m_track; + }; + +} + +#endif // PRIVATEXML_H__ diff --git a/libs/libgloox/privatexmlhandler.h b/libs/libgloox/privatexmlhandler.h new file mode 100644 index 0000000..1a315d3 --- /dev/null +++ b/libs/libgloox/privatexmlhandler.h @@ -0,0 +1,73 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef PRIVATEXMLHANDLER_H__ +#define PRIVATEXMLHANDLER_H__ + +#include "macros.h" + +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief A virtual interface which can be reimplemented to store and receive private XML data. + * + * Derived classes can be registered as PrivateXMLHandlers with the PrivateXML object. + * Upon an incoming PrivateXML packet @ref handlePrivateXML() will be called. + * + * @author Jakob Schroeter + */ + class GLOOX_API PrivateXMLHandler + { + public: + /** + * Describes the possible results of a 'store' or 'request' operation. + */ + enum PrivateXMLResult + { + PxmlStoreOk, /**< Storing was successful. */ + PxmlStoreError, /**< An error occurred while storing data in Private XML. */ + PxmlRequestError /**< An error occurred while requesting Private XML. */ + }; + + /** + * Virtual Destructor. + */ + virtual ~PrivateXMLHandler() {} + + /** + * Reimplement this function to receive the private XML that was requested earlier using + * @c PrivateXML::requestXML(). + * @param xml The private xml, i.e. the first child of the <query> tag. + * May be 0. You should not delete the object. + */ + virtual void handlePrivateXML( const Tag* xml ) = 0; + + /** + * This function is called to notify about the result of a 'store' or 'request' operation + * (successful requests are announced by means of handlePrivateXML()). + * @param uid The ID of the query. + * @param pxResult The result of the operation. + * @since 0.7 + */ + virtual void handlePrivateXMLResult( const std::string& uid, PrivateXMLResult pxResult ) = 0; + }; + +} + +#endif // PRIVATEXMLHANDLER_H__ diff --git a/libs/libgloox/pubsub.h b/libs/libgloox/pubsub.h new file mode 100644 index 0000000..dd06a8d --- /dev/null +++ b/libs/libgloox/pubsub.h @@ -0,0 +1,249 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef PUBSUB_H__ +#define PUBSUB_H__ + +#include +#include + +#include "gloox.h" +#include "jid.h" + +namespace gloox +{ + /** + * @brief Namespace holding all the Pubsub-related structures and definitions. + */ + namespace PubSub + { + + class Item; + + /** + * Describes the different node types. + */ + enum NodeType + { + NodeLeaf, /**< A node that contains published items only. It is NOT + * a container for other nodes. */ + NodeCollection, /**< A node that contains nodes and/or other collections but + * no published items. Collections make it possible to represent + * hierarchial node structures. */ + NodeInvalid /**< Invalid node type */ + }; + + /** + * Describes the different node affiliation types. + */ + enum AffiliationType + { + AffiliationNone, /**< No particular affiliation type. */ + AffiliationPublisher, /**< Entity is allowed to publish items. */ + AffiliationOwner, /**< Manager for the node. */ + AffiliationOutcast, /**< Entity is disallowed from subscribing or publishing. */ + AffiliationInvalid /**< Invalid Affiliation type. */ + }; + + /** + * Describes the different subscription types. + */ + enum SubscriptionType + { + SubscriptionNone, /**< The node MUST NOT send event notifications or payloads to the + * Entity. */ + SubscriptionSubscribed, /**< An entity is subscribed to a node. The node MUST send all event + * notifications (and, if configured, payloads) to the entity while it + * is in this state. */ + SubscriptionPending, /**< An entity has requested to subscribe to a node and the request + * has not yet been approved by a node owner. The node MUST NOT send + * event notifications or payloads to the entity while it is in this + * state. */ + SubscriptionUnconfigured, /**< An entity has subscribed but its subscription options have not yet + * been configured. The node MAY send event notifications or payloads + * to the entity while it is in this state. The service MAY timeout + * unconfigured subscriptions. */ + SubscriptionInvalid /**< Invalid subscription type. */ + }; + + /** + * Event types. + */ + enum EventType + { + EventCollection, /**< A Collection node has been created. */ + EventConfigure, /**< A node's configuration has changed. */ + EventDelete, /**< A node has been deleted. */ + EventItems, /**< An item has been created or modified. */ + EventItemsRetract, /**< An item has been deleted. */ + EventPurge, /**< A Leaf node has been purged. */ + EventSubscription, /**< A user's subscription has been processed. */ + EventUnknown /**< Unknown event. */ + }; + + /** + * Describes the different subscription types. + */ + enum SubscriptionObject + { + SubscriptionNodes, /**< Receive notification of new nodes only. */ + SubscriptionItems /**< Receive notification of new items only. */ + }; + + /** + * Describes the access types. + */ + enum AccessModel + { + AccessOpen, /**< Any entity may subscribe to the node (i.e., without the necessity + * for subscription approval) and any entity may retrieve items from the + * node (i.e., without being subscribed); this SHOULD be the default + * access model for generic pubsub services. */ + AccessPresence, /**< Any entity with a subscription of type "from" or "both" may subscribe + * to the node and retrieve items from the node; this access model applies + * mainly to instant messaging systems (see RFC 3921). */ + AccessRoster, /**< Any entity in the specified roster group(s) may subscribe to the node + * and retrieve items from the node; this access model applies mainly to + * instant messaging systems (see RFC 3921). */ + AccessAuthorize, /**< The node owner must approve all subscription requests, and only + * subscribers may retrieve items from the node. */ + AccessWhitelist, /**< An entity may be subscribed only through being added to a whitelist + * by the node owner (unsolicited subscription requests are rejected), and + * only subscribers may retrieve items from the node. In effect, the + * default affiliation is outcast. The node owner MUST automatically be + * on the whitelist. In order to add entities to the whitelist, the + * node owner SHOULD use the protocol specified in the Manage Affiliated + * Entities section of this document. */ + AccessDefault /**< Unspecified (default) Access Model (does not represent a real access + * type by itself). */ + }; + + /** + * Describes the different PubSub features (XEP-0060 Sect. 10). + */ + enum PubSubFeature + { + FeatureCollections = 1, /**< Collection nodes are supported. RECOMMENDED */ + FeatureConfigNode = 1<<1, /**< Configuration of node options is supported. RECOMMENDED */ + FeatureCreateAndConfig = 1<<2, /**< Simultaneous creation and configuration of nodes is + * supported. RECOMMENDED */ + FeatureCreateNodes = 1<<3, /**< Creation of nodes is supported. RECOMMENDED */ + FeatureDeleteAny = 1<<4, /**< Any publisher may delete an item (not only the originating + * publisher). OPTIONAL */ + FeatureDeleteNodes = 1<<5, /**< Deletion of nodes is supported. RECOMMENDED */ + FeatureGetPending = 1<<6, /**< Retrieval of pending subscription approvals is supported. + * OPTIONAL */ + FeatureInstantNodes = 1<<7, /**< Creation of instant nodes is supported. RECOMMENDED */ + FeatureItemIDs = 1<<8, /**< Publishers may specify item identifiers. RECOMMENDED */ + FeatureLeasedSubscription = 1<<9, /**< Time-based subscriptions are supported. OPTIONAL */ + FeatureManageSubscriptions = 1<<10, /**< Node owners may manage subscriptions. OPTIONAL */ + FeatureMetaData = 1<<11, /**< Node meta-data is supported. RECOMMENDED */ + FeatureModifyAffiliations = 1<<12, /**< Node owners may modify affiliations. OPTIONAL */ + FeatureMultiCollection = 1<<13, /**< A single leaf node may be associated with multiple + * collections. OPTIONAL */ + FeatureMultiSubscribe = 1<<14, /**< A single entity may subscribe to a node multiple times. + * OPTIONAL */ + FeaturePutcastAffiliation = 1<<15, /**< The outcast affiliation is supported. RECOMMENDED */ + FeaturePersistentItems = 1<<16, /**< Persistent items are supported. RECOMMENDED */ + FeaturePresenceNotifications = 1<<17, /**< Presence-based delivery of event notifications is supported. + * OPTIONAL */ + FeaturePublish = 1<<18, /**< Publishing items is supported (note: not valid for collection + * nodes). REQUIRED */ + FeaturePublisherAffiliation = 1<<19, /**< The publisher affiliation is supported. OPTIONAL */ + FeaturePurgeNodes = 1<<20, /**< Purging of nodes is supported. OPTIONAL */ + FeatureRetractItems = 1<<21, /**< Item retraction is supported. OPTIONAL */ + FeatureRetrieveAffiliations = 1<<22, /**< Retrieval of current affiliations is supported. + * RECOMMENDED */ + FeatureRetrieveDefault = 1<<23, /**< Retrieval of default node configuration is supported. + * RECOMMENDED */ + FeatureRetrieveItems = 1<<24, /**< Item retrieval is supported. RECOMMENDED */ + FeatureRetrieveSubscriptions = 1<<25, /**< Retrieval of current subscriptions is supported. + * RECOMMENDED */ + FeatureSubscribe = 1<<26, /**< Subscribing and unsubscribing are supported. REQUIRED */ + FeatureSubscriptionOptions = 1<<27, /**< Configuration of subscription options is supported. + * OPTIONAL */ + FeatureSubscriptionNotifs = 1<<28, /**< Notification of subscription state changes is supported. */ + FeatureUnknown = 1<<29 /**< Unrecognized feature */ + }; + +// [Persistent - Notification] +/* Publisher MUST include an <item/> element, which MAY be empty or contain a payload; if item ID is not provided by publisher, it MUST be generated by pubsub service */ + +// [Persistent - Payload] +/* Publisher MUST include an <item/> element that contains the payload; if item ID is not provided by publisher, it MUST be generated by pubsub service */ + +// [Transient - Notification] +/* Publisher MUST NOT include an <item/> element (therefore item ID is neither provided nor generated) but the notification will include an empty <items/> element */ + +// [Transient - Payload] +/* Publisher MUST include an <item/> element that contains the payload, but the item ID is OPTIONAL */ + + /** + * Describes a subscribed entity. + */ + struct Subscriber + { + Subscriber( const JID& _jid, + SubscriptionType _type, + const std::string& _subid = EmptyString) + : jid( _jid ), type( _type ), subid( _subid ) {} + JID jid; + SubscriptionType type; + std::string subid; + }; + + /** + * Describes an Affiliate. + */ + struct Affiliate + { + Affiliate( const JID& _jid, AffiliationType _type ) + : jid( _jid ), type( _type ) {} + JID jid; + AffiliationType type; + }; + + typedef std::list SubscriberList; + typedef std::list AffiliateList; + + /** + * Struct used to track info between requests. + * + */ + struct TrackedInfo + { + JID service; + std::string node; + std::string item; + std::string sid; + }; + + /** + * Struct used for subscription info. + */ + struct SubscriptionInfo + { + SubscriptionType type; + JID jid; + std::string subid; + }; + + typedef std::list SubscriptionList; + typedef std::map SubscriptionMap; + typedef std::map AffiliationMap; + typedef std::list ItemList; + + } + +} + +#endif // PUBSUB_H__ diff --git a/libs/libgloox/pubsubevent.cpp b/libs/libgloox/pubsubevent.cpp new file mode 100644 index 0000000..2ae86b8 --- /dev/null +++ b/libs/libgloox/pubsubevent.cpp @@ -0,0 +1,278 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "pubsubevent.h" +#include "tag.h" +#include "util.h" + +namespace gloox +{ + + namespace PubSub + { + + static const char* eventTypeValues[] = { + "collection", + "configuration", + "delete", + "items", + "items", + "purge", + "subscription" + }; + + Event::ItemOperation::ItemOperation( const ItemOperation& right ) + : retract( right.retract ), item( right.item ), + payload( right.payload ? right.payload->clone() : 0 ) + { + } + + Event::Event( const Tag* event ) + : StanzaExtension( ExtPubSubEvent ), m_type( PubSub::EventUnknown ), + m_subscriptionIDs( 0 ), m_config( 0 ), m_itemOperations( 0 ), m_subscription( false ) + { + if( !event || event->name() != "event" ) + return; + + const TagList& events = event->children(); + TagList::const_iterator it = events.begin(); + const Tag* tag = 0; + for( ; it != events.end(); ++it ) + { + tag = (*it); + PubSub::EventType type = (PubSub::EventType)util::lookup( tag->name(), eventTypeValues ); + + switch( type ) + { + case PubSub::EventCollection: + tag = tag->findChild( "node" ); + if( tag ) + { + m_node = tag->findAttribute( "id" ); + if( ( m_config = tag->findChild( "x" ) ) ) + m_config = m_config->clone(); + } + break; + + case PubSub::EventConfigure: + case PubSub::EventDelete: + case PubSub::EventPurge: + m_node = tag->findAttribute( "node" ); + if( type == PubSub::EventConfigure + && ( m_config = tag->findChild( "x" ) ) ) + m_config = m_config->clone(); + break; + + case PubSub::EventItems: + case PubSub::EventItemsRetract: + { + if( !m_itemOperations ) + m_itemOperations = new ItemOperationList(); + + m_node = tag->findAttribute( "node" ); + const TagList& items = tag->children(); + TagList::const_iterator itt = items.begin(); + for( ; itt != items.end(); ++itt ) + { + tag = (*itt); + bool retract = false; + if( tag->name() == "retract" ) + { + retract = true; + type = PubSub::EventItemsRetract; + } + ItemOperation* op = new ItemOperation( retract, + tag->findAttribute( "id" ), + tag->clone() ); + m_itemOperations->push_back( op ); + } + break; + } + + case EventSubscription: + { + m_node = tag->findAttribute( "node" ); + m_jid.setJID( tag->findAttribute( "jid" ) ); + m_subscription = tag->hasAttribute( "subscription", "subscribed" ); + break; + } + + case PubSub::EventUnknown: + if( type == PubSub::EventUnknown ) + { + if( tag->name() != "headers" || m_subscriptionIDs != 0 ) + { + m_valid = false; + return; + } + + m_subscriptionIDs = new StringList(); + + const TagList& headers = tag->children(); + TagList::const_iterator ith = headers.begin(); + for( ; ith != headers.end(); ++ith ) + { + const std::string& name = (*ith)->findAttribute( "name" ); + if( name == "pubsub#subid" ) + m_subscriptionIDs->push_back( (*ith)->cdata() ); + else if( name == "pubsub#collection" ) + m_collection = (*ith)->cdata(); + } + } + + default: + continue; + } + m_type = type; + } + + m_valid = true; + } + + Event::Event( const std::string& node, PubSub::EventType type ) + : StanzaExtension( ExtPubSubEvent ), m_type( type ), + m_node( node ), m_subscriptionIDs( 0 ), m_config( 0 ), + m_itemOperations( 0 ) + { + if( type != PubSub::EventUnknown ) + m_valid = true; + } + + Event::~Event() + { + delete m_subscriptionIDs; + delete m_config; + if( m_itemOperations ) + { + ItemOperationList::iterator it = m_itemOperations->begin(); + for( ; it != m_itemOperations->end(); ++it ) + { + delete (*it)->payload; + delete (*it); + } + delete m_itemOperations; + } + } + + void Event::addItem( ItemOperation* op ) + { + if( !m_itemOperations ) + m_itemOperations = new ItemOperationList(); + + m_itemOperations->push_back( op ); + } + + const std::string& Event::filterString() const + { + static const std::string filter = "/message/event[@xmlns='" + XMLNS_PUBSUB_EVENT + "']"; + return filter; + } + + Tag* Event::tag() const + { + if( !m_valid ) + return 0; + + Tag* event = new Tag( "event", XMLNS, XMLNS_PUBSUB_EVENT ); + Tag* child = new Tag( event, util::lookup( m_type, eventTypeValues ) ); + + Tag* item = 0; + + switch( m_type ) + { + case PubSub::EventCollection: + { + item = new Tag( child, "node", "id", m_node ); + item->addChildCopy( m_config ); + break; + } + + case PubSub::EventPurge: + case PubSub::EventDelete: + case PubSub::EventConfigure: + child->addAttribute( "node", m_node ); + if( m_type == PubSub::EventConfigure ) + child->addChildCopy( m_config ); + break; + + case PubSub::EventItems: + case PubSub::EventItemsRetract: + { + child->addAttribute( "node", m_node ); + if( m_itemOperations ) + { +// Tag* item; + ItemOperation* op; + ItemOperationList::const_iterator itt = m_itemOperations->begin(); + for( ; itt != m_itemOperations->end(); ++itt ) + { + op = (*itt); +// item = new Tag( child, op->retract ? "retract" : "item", "id", op->item ); + if( op->payload ) + child->addChildCopy( op->payload ); + } + } + break; + } + + case EventSubscription: + { + child->addAttribute( "node", m_node ); + child->addAttribute( "jid", m_jid.full() ); + child->addAttribute( "subscription", m_subscription ? "subscribed" : "none" ); + break; + } + + default: + delete event; + return 0; + } + + if( m_subscriptionIDs || !m_collection.empty() ) + { + Tag* headers = new Tag( event, "headers", XMLNS, "http://jabber.org/protocol/shim" ); + StringList::const_iterator it = m_subscriptionIDs->begin(); + for( ; it != m_subscriptionIDs->end(); ++it ) + { + (new Tag( headers, "header", "name", "pubsub#subid" ))->setCData( (*it) ); + } + + if( !m_collection.empty() ) + (new Tag( headers, "header", "name", "pubsub#collection" ) ) + ->setCData( m_collection ); + } + + return event; + } + + StanzaExtension* Event::clone() const + { + Event* e = new Event( m_node, m_type ); + e->m_subscriptionIDs = m_subscriptionIDs ? new StringList( *m_subscriptionIDs ) : 0; + e->m_config = m_config ? m_config->clone() : 0; + if( m_itemOperations ) + { + e->m_itemOperations = new ItemOperationList(); + ItemOperationList::const_iterator it = m_itemOperations->begin(); + for( ; it != m_itemOperations->end(); ++it ) + e->m_itemOperations->push_back( new ItemOperation( *(*it) ) ); + } + else + e->m_itemOperations = 0; + + e->m_collection = m_collection; + return e; + } + + } + +} diff --git a/libs/libgloox/pubsubevent.h b/libs/libgloox/pubsubevent.h new file mode 100644 index 0000000..10227bd --- /dev/null +++ b/libs/libgloox/pubsubevent.h @@ -0,0 +1,173 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef PUBSUBEVENT_H__ +#define PUBSUBEVENT_H__ + +#include "stanzaextension.h" +#include "pubsub.h" +#include "gloox.h" + +namespace gloox +{ + + class Tag; + + namespace PubSub + { + + /** + * @brief This is an implementation of a PubSub Notification as a StanzaExtension. + * + * @author Vincent Thomasset + * @since 1.0 + */ + class GLOOX_API Event : public StanzaExtension + { + public: + + /** + * Stores a retract or item notification. + */ + struct ItemOperation + { + /** + * Constructor. + * + * @param remove Whether this is a retract operation or not (ie item). + * @param itemid Item ID of this item. + * @param pld Payload for this object (in the case of a non transient + * item notification). + */ + ItemOperation( bool remove, const std::string& itemid, const Tag* pld = 0 ) + : retract( remove ), item( itemid ), payload( pld ) + {} + + /** + * Copy constructor. + * @param right The ItemOperation to copy from. + */ + ItemOperation( const ItemOperation& right ); + + bool retract; + std::string item; + const Tag* payload; + }; + + /** + * A list of ItemOperations. + */ + typedef std::list ItemOperationList; + + /** + * PubSub event notification Stanza Extension. + * @param event A tag to parse. + */ + Event( const Tag* event ); + + /** + * PubSub event notification Stanza Extension. + * @param node The node's ID for which the notification is sent. + * @param type The event's type. + */ + Event( const std::string& node, PubSub::EventType type ); + + /** + * Virtual destructor. + */ + virtual ~Event(); + + /** + * Returns the event's type. + * @return The event's type. + */ + PubSub::EventType type() const { return m_type; } + + /** + * Returns the list of subscription IDs for which this notification + * is valid. + * @return The list of subscription IDs. + */ + const StringList& subscriptions() const + { return m_subscriptionIDs ? *m_subscriptionIDs : m_emptyStringList; } + + /** + * Returns the list of ItemOperations for EventItems(Retract) notification. + * @return The list of ItemOperations. + */ + const ItemOperationList& items() const + { return m_itemOperations ? *m_itemOperations : m_emptyOperationList; } + + /** + * Add an item to the list of ItemOperations for EventItems(Retract) notification. + * After calling, the PubSub::Event object owns the ItemOperation and will free it. + * @param op An ItemOperation to add. + */ + void addItem( ItemOperation* op ); + + /** + * Returns the node's ID for which the notification is sent. + * @return The node's ID. + */ + const std::string& node() const { return m_node; } + + /** + * Returns the subscribe/unsubscribed JID. Only set for subscription notifications + * (type() == EventSubscription). + * @return The affected JID. + */ + const JID& jid() { return m_jid; } + + /** + * Returns the subscription state. Only set for subscription notifications + * (type() == EventSubscription). + * @return @b True if the subscription request was approved, @b false otherwise. + */ + bool subscription() { return m_subscription; } + + // reimplemented from StanzaExtension + const std::string& filterString() const; + + // reimplemented from StanzaExtension + StanzaExtension* newInstance( const Tag* tag ) const + { + return new Event( tag ); + } + + // reimplemented from StanzaExtension + Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const; + + private: + Event& operator=( const Event& ); + + PubSub::EventType m_type; + std::string m_node; + StringList* m_subscriptionIDs; + JID m_jid; + Tag* m_config; + ItemOperationList* m_itemOperations; + std::string m_collection; + bool m_subscription; + + const ItemOperationList m_emptyOperationList; + const StringList m_emptyStringList; + + }; + + } + +} + +#endif // PUBSUBEVENT_H__ diff --git a/libs/libgloox/pubsubitem.cpp b/libs/libgloox/pubsubitem.cpp new file mode 100644 index 0000000..a8e995a --- /dev/null +++ b/libs/libgloox/pubsubitem.cpp @@ -0,0 +1,63 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "pubsubitem.h" +#include "tag.h" + +namespace gloox +{ + + namespace PubSub + { + + Item::Item() + : m_payload( 0 ) + { + } + + Item::Item( const Tag* tag ) + : m_payload( 0 ) + { + if( !tag || tag->name() != "item" ) + return; + + m_id = tag->findAttribute( "id" ); + + if( tag->children().size() ) + m_payload = tag->children().front()->clone(); + } + + Item::Item( const Item& item ) + : m_payload( item.m_payload ? item.m_payload->clone() : 0 ) + { + m_id = item.m_id; + } + + Item::~Item() + { + delete m_payload; + } + + Tag* Item::tag() const + { + Tag* t = new Tag( "item" ); + t->addAttribute( "id", m_id ); + if( m_payload ) + t->addChild( m_payload->clone() ); + + return t; + } + + } + +} diff --git a/libs/libgloox/pubsubitem.h b/libs/libgloox/pubsubitem.h new file mode 100644 index 0000000..00406f3 --- /dev/null +++ b/libs/libgloox/pubsubitem.h @@ -0,0 +1,90 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef PUBSUBITEM_H__ +#define PUBSUBITEM_H__ + +#include "gloox.h" + +#include + +namespace gloox +{ + + class Tag; + + namespace PubSub + { + + /** + * @brief Abstracts a PubSub Item (XEP-0060). + * + * XEP Version: 1.12 + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Item + { + public: + /** + * Constructs a new empty Item. + */ + Item(); + + /** + * Constructs a new Item from the given Tag. + * @param tag The Tag to parse. + */ + Item( const Tag* tag ); + + /** + * Copy constructor. + * @param item The Item to be copied. + */ + Item( const Item& item ); + + /** + * Destructor. + */ + ~Item(); + + /** + * Returns the Item's payload. + * @return The layload. + */ + const Tag* payload() const { return m_payload; } + + /** + * Returns the item ID. + * @return The item ID. + */ + const std::string& id() const { return m_id; } + + /** + * Creates and returns a Tag representation of the Item. + * @return An XML representation of the Item. + */ + Tag* tag() const; + + private: + Tag* m_payload; + std::string m_id; + + }; + + } + +} + +#endif // PUBSUBITEM_H__ diff --git a/libs/libgloox/pubsubmanager.cpp b/libs/libgloox/pubsubmanager.cpp new file mode 100644 index 0000000..9742ea1 --- /dev/null +++ b/libs/libgloox/pubsubmanager.cpp @@ -0,0 +1,1199 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "pubsubmanager.h" +#include "clientbase.h" +#include "dataform.h" +#include "iq.h" +#include "pubsub.h" +#include "pubsubresulthandler.h" +#include "pubsubitem.h" +#include "shim.h" +#include "util.h" +#include "error.h" + +namespace gloox +{ + + namespace PubSub + { + + static const std::string + XMLNS_PUBSUB_NODE_CONFIG = "http://jabber.org/protocol/pubsub#node_config", + XMLNS_PUBSUB_SUBSCRIBE_OPTIONS = "http://jabber.org/protocol/pubsub#subscribe_options"; + + /** + * Finds the associated PubSubFeature for a feature tag 'type' attribute, + * as received from a disco info query on a pubsub service (XEP-0060 sect. 10). + * @param feat Feature string to search for. + * @return the associated PubSubFeature. + */ +/* static PubSubFeature featureType( const std::string& str ) + { + static const char* values [] = { + "collections", + "config-node", + "create-and-configure", + "create-nodes", + "delete-any", + "delete-nodes", + "get-pending", + "instant-nodes", + "item-ids", + "leased-subscription", + "manage-subscriptions", + "meta-data", + "modify-affiliations", + "multi-collection", + "multi-subscribe", + "outcast-affiliation", + "persistent-items", + "presence-notifications", + "publish", + "publisher-affiliation", + "purge-nodes", + "retract-items", + "retrieve-affiliations", + "retrieve-default", + "retrieve-items", + "retrieve-subscriptions", + "subscribe", + "subscription-options", + "subscription-notifications", + "owner", + "event", + }; + return static_cast< PubSubFeature >( util::lookup2( str, values ) ); + } +*/ + + static const char* subscriptionValues[] = { + "none", "subscribed", "pending", "unconfigured" + }; + + static inline SubscriptionType subscriptionType( const std::string& subscription ) + { + return (SubscriptionType)util::lookup( subscription, subscriptionValues ); + } + + static inline const std::string subscriptionValue( SubscriptionType subscription ) + { + return util::lookup( subscription, subscriptionValues ); + } + + static const char* affiliationValues[] = { + "none", "publisher", "owner", "outcast" + }; + + static inline AffiliationType affiliationType( const std::string& affiliation ) + { + return (AffiliationType)util::lookup( affiliation, affiliationValues ); + } + + static inline const std::string affiliationValue( AffiliationType affiliation ) + { + return util::lookup( affiliation, affiliationValues ); + } + + // ---- Manager::PubSubOwner ---- + Manager::PubSubOwner::PubSubOwner( TrackContext context ) + : StanzaExtension( ExtPubSubOwner ), m_ctx( context ), m_form( 0 ) + { + } + + Manager::PubSubOwner::PubSubOwner( const Tag* tag ) + : StanzaExtension( ExtPubSubOwner ), m_ctx( InvalidContext ), m_form( 0 ) + { + const Tag* d = tag->findTag( "pubsub/delete" ); + if( d ) + { + m_ctx = DeleteNode; + m_node = d->findAttribute( "node" ); + return; + } + const Tag* p = tag->findTag( "pubsub/purge" ); + if( p ) + { + m_ctx = PurgeNodeItems; + m_node = p->findAttribute( "node" ); + return; + } + const Tag* c = tag->findTag( "pubsub/configure" ); + if( c ) + { + m_ctx = SetNodeConfig; + m_node = c->findAttribute( "node" ); + if( c->hasChild( "x", "xmlns", XMLNS_X_DATA ) ) + { + m_ctx = GetNodeConfig; + m_form = new DataForm( c->findChild( "x", "xmlns", XMLNS_X_DATA ) ); + } + return; + } + const Tag* de = tag->findTag( "pubsub/default" ); + if( de ) + { + m_ctx = DefaultNodeConfig; + return; + } + const Tag* s = tag->findTag( "pubsub/subscriptions" ); + if( s ) + { + m_ctx = GetSubscriberList; + m_node = s->findAttribute( "node" ); + const TagList& l = s->children(); + TagList::const_iterator it =l.begin(); + for( ; it != l.end(); ++it ) + { + if( (*it)->name() == "subscription" ) + { + Subscriber sub( (*it)->findAttribute( "jid" ), + subscriptionType( (*it)->findAttribute( "subscription" ) ), + (*it)->findAttribute( "subid" ) ); + m_subList.push_back( sub ); + } + } + return; + } + const Tag* a = tag->findTag( "pubsub/affiliations" ); + if( a ) + { + m_ctx = GetAffiliateList; + m_node = a->findAttribute( "node" ); + const TagList& l = a->children(); + TagList::const_iterator it =l.begin(); + for( ; it != l.end(); ++it ) + { + if( (*it)->name() == "affiliation" ) + { + Affiliate aff( (*it)->findAttribute( "jid" ), + affiliationType( (*it)->findAttribute( "affiliation" ) ) ); + m_affList.push_back( aff ); + } + } + return; + } + } + + Manager::PubSubOwner::~PubSubOwner() + { + delete m_form; + } + + const std::string& Manager::PubSubOwner::filterString() const + { + static const std::string filter = "/iq/pubsub[@xmlns='" + XMLNS_PUBSUB_OWNER + "']"; + return filter; + } + + Tag* Manager::PubSubOwner::tag() const + { + if( m_ctx == InvalidContext ) + return 0; + + Tag* t = new Tag( "pubsub" ); + t->setXmlns( XMLNS_PUBSUB_OWNER ); + Tag* c = 0; + + switch( m_ctx ) + { + case DeleteNode: + { + c = new Tag( t, "delete", "node", m_node ); + break; + } + case PurgeNodeItems: + { + c = new Tag( t, "purge", "node", m_node ); + break; + } + case GetNodeConfig: + case SetNodeConfig: + { + c = new Tag( t, "configure" ); + c->addAttribute( "node", m_node ); + if( m_form ) + c->addChild( m_form->tag() ); + break; + } + case GetSubscriberList: + case SetSubscriberList: + + { + c = new Tag( t, "subscriptions" ); + c->addAttribute( "node", m_node ); + if( m_subList.size() ) + { + Tag* s; + SubscriberList::const_iterator it = m_subList.begin(); + for( ; it != m_subList.end(); ++it ) + { + s = new Tag( c, "subscription" ); + s->addAttribute( "jid", (*it).jid.full() ); + s->addAttribute( "subscription", util::lookup( (*it).type, subscriptionValues ) ); + if( !(*it).subid.empty() ) + s->addAttribute( "subid", (*it).subid ); + } + } + break; + } + case GetAffiliateList: + case SetAffiliateList: + { + c = new Tag( t, "affiliations" ); + c->addAttribute( "node", m_node ); + if( m_affList.size() ) + { + Tag* a; + AffiliateList::const_iterator it = m_affList.begin(); + for( ; it != m_affList.end(); ++it ) + { + a = new Tag( c, "affiliation", "jid", (*it).jid.full() ); + a->addAttribute( "affiliation", util::lookup( (*it).type, affiliationValues ) ); + } + } + break; + } + case DefaultNodeConfig: + { + c = new Tag( t, "default" ); + break; + } + default: + break; + } + + return t; + } + // ---- ~Manager::PubSubOwner ---- + + // ---- Manager::PubSub ---- + Manager::PubSub::PubSub( TrackContext context ) + : StanzaExtension( ExtPubSub ), m_ctx( context ), m_maxItems( 0 ), + m_notify( false ) + { + m_options.df = 0; + } + + Manager::PubSub::PubSub( const Tag* tag ) + : StanzaExtension( ExtPubSub ), m_ctx( InvalidContext ), + m_maxItems( 0 ), m_notify( false ) + { + m_options.df = 0; + if( !tag ) + return; + + ConstTagList l = tag->findTagList( "pubsub/subscriptions/subscription" ); + if( l.size() ) + { + m_ctx = GetSubscriptionList; + ConstTagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + const std::string& node = (*it)->findAttribute( "node" ); + const std::string& sub = (*it)->findAttribute( "subscription" ); + const std::string& subid = (*it)->findAttribute( "subid" ); + SubscriptionInfo si; + si.jid.setJID( (*it)->findAttribute( "jid" ) ); + si.type = subscriptionType( sub ); + si.subid = subid; + SubscriptionList& lst = m_subscriptionMap[node]; + lst.push_back( si ); + } + return; + } + l = tag->findTagList( "pubsub/affiliations/affiliation" ); + if( l.size() ) + { + m_ctx = GetAffiliationList; + ConstTagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + const std::string& node = (*it)->findAttribute( "node" ); + const std::string& aff = (*it)->findAttribute( "affiliation" ); + m_affiliationMap[node] = affiliationType( aff ); + } + return; + } + const Tag* s = tag->findTag( "pubsub/subscribe" ); + if( s ) + { + m_ctx = Subscription; + m_node = s->findAttribute( "node" ); + m_jid = s->findAttribute( "jid" ); + } + const Tag* u = tag->findTag( "pubsub/unsubscribe" ); + if( u ) + { + m_ctx = Unsubscription; + m_node = u->findAttribute( "node" ); + m_jid = u->findAttribute( "jid" ); + m_subid = u->findAttribute( "subid" ); + } + const Tag* o = tag->findTag( "pubsub/options" ); + if( o ) + { + if( m_ctx == InvalidContext ) + m_ctx = GetSubscriptionOptions; + m_jid.setJID( o->findAttribute( "jid" ) ); + m_options.node = o->findAttribute( "node" ); + m_options.df = new DataForm( o->findChild( "x", "xmlns", XMLNS_X_DATA ) ); + } + const Tag* su = tag->findTag( "pubsub/subscription" ); + if( su ) + { + SubscriptionInfo si; + si.jid.setJID( su->findAttribute( "jid" ) ); + si.subid = su->findAttribute( "subid" ); + si.type = subscriptionType( su->findAttribute( "type" ) ); + SubscriptionList& lst = m_subscriptionMap[su->findAttribute( "node" )]; + lst.push_back( si ); + return; + } + const Tag* i = tag->findTag( "pubsub/items" ); + if( i ) + { + m_ctx = RequestItems; + m_node = i->findAttribute( "node" ); + m_subid = i->findAttribute( "subid" ); + m_maxItems = atoi( i->findAttribute( "max_items" ).c_str() ); + const TagList& l = i->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + m_items.push_back( new Item( (*it) ) ); + return; + } + const Tag* p = tag->findTag( "pubsub/publish" ); + if( p ) + { + m_ctx = PublishItem; + m_node = p->findAttribute( "node" ); + const TagList& l = p->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + m_items.push_back( new Item( (*it) ) ); + return; + } + const Tag* r = tag->findTag( "pubsub/retract" ); + if( r ) + { + m_ctx = DeleteItem; + m_node = r->findAttribute( "node" ); + m_notify = r->hasAttribute( "notify", "1" ) || r->hasAttribute( "notify", "true" ); + const TagList& l = p->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + m_items.push_back( new Item( (*it) ) ); + return; + } + const Tag* c = tag->findTag( "pubsub/create" ); + if( c ) + { + m_ctx = CreateNode; + m_node = c->findAttribute( "node" ); + const Tag* config = tag->findTag( "pubsub/configure" ); + if( config && config->hasChild( "x", XMLNS_X_DATA ) ) + m_options.df = new DataForm( config->findChild( "x", XMLNS_X_DATA ) ); + } + } + + Manager::PubSub::~PubSub() + { + delete m_options.df; + util::clearList( m_items ); + } + + const std::string& Manager::PubSub::filterString() const + { + static const std::string filter = "/iq/pubsub[@xmlns='" + XMLNS_PUBSUB + "']"; + return filter; + } + + Tag* Manager::PubSub::tag() const + { + if( m_ctx == InvalidContext ) + return 0; + + Tag* t = new Tag( "pubsub" ); + t->setXmlns( XMLNS_PUBSUB ); + + if( m_ctx == GetSubscriptionList ) + { + Tag* sub = new Tag( t, "subscriptions" ); + SubscriptionMap::const_iterator it = m_subscriptionMap.begin(); + for( ; it != m_subscriptionMap.end(); ++it ) + { + const SubscriptionList& lst = (*it).second; + SubscriptionList::const_iterator it2 = lst.begin(); + for( ; it2 != lst.end(); ++it2 ) + { + Tag* s = new Tag( sub, "subscription" ); + s->addAttribute( "node", (*it).first ); + s->addAttribute( "jid", (*it2).jid ); + s->addAttribute( "subscription", subscriptionValue( (*it2).type ) ); + s->addAttribute( "sid", (*it2).subid ); + } + } + } + else if( m_ctx == GetAffiliationList ) + { + + Tag* aff = new Tag( t, "affiliations" ); + AffiliationMap::const_iterator it = m_affiliationMap.begin(); + for( ; it != m_affiliationMap.end(); ++it ) + { + Tag* a = new Tag( aff, "affiliation" ); + a->addAttribute( "node", (*it).first ); + a->addAttribute( "affiliation", affiliationValue( (*it).second ) ); + } + } + else if( m_ctx == Subscription ) + { + Tag* s = new Tag( t, "subscribe" ); + s->addAttribute( "node", m_node ); + s->addAttribute( "jid", m_jid.full() ); + if( m_options.df ) + { + Tag* o = new Tag( t, "options" ); + o->addChild( m_options.df->tag() ); + } + } + else if( m_ctx == Unsubscription ) + { + Tag* u = new Tag( t, "unsubscribe" ); + u->addAttribute( "node", m_node ); + u->addAttribute( "jid", m_jid.full() ); + u->addAttribute( "subid", m_subid ); + } + else if( m_ctx == GetSubscriptionOptions + || m_ctx == SetSubscriptionOptions + || ( m_ctx == Subscription && m_options.df ) ) + { + Tag* o = new Tag( t, "options" ); + o->addAttribute( "node", m_options.node ); + o->addAttribute( "jid", m_jid.full() ); + if( m_options.df ) + o->addChild( m_options.df->tag() ); + } + else if( m_ctx == RequestItems ) + { + Tag* i = new Tag( t, "items" ); + i->addAttribute( "node", m_node ); + if( m_maxItems ) + i->addAttribute( "max_items", m_maxItems ); + i->addAttribute( "subid", m_subid ); + ItemList::const_iterator it = m_items.begin(); + for( ; it != m_items.end(); ++it ) + i->addChild( (*it)->tag() ); + } + else if( m_ctx == PublishItem ) + { + Tag* p = new Tag( t, "publish" ); + p->addAttribute( "node", m_node ); + ItemList::const_iterator it = m_items.begin(); + for( ; it != m_items.end(); ++it ) + p->addChild( (*it)->tag() ); + if( m_options.df ) + { + Tag* po = new Tag( "publish-options" ); + po->addChild( m_options.df->tag() ); + } + } + else if( m_ctx == DeleteItem ) + { + Tag* r = new Tag( t, "retract" ); + r->addAttribute( "node", m_node ); + if( m_notify ) + r->addAttribute( "notify", "true" ); + ItemList::const_iterator it = m_items.begin(); + for( ; it != m_items.end(); ++it ) + r->addChild( (*it)->tag() ); + } + else if( m_ctx == CreateNode ) + { + Tag* c = new Tag( t, "create" ); + c->addAttribute( "node", m_node ); + Tag* config = new Tag( t, "configure" ); + if( m_options.df ) + config->addChild( m_options.df->tag() ); + } + return t; + } + + StanzaExtension* Manager::PubSub::clone() const + { + PubSub* p = new PubSub(); + p->m_affiliationMap = m_affiliationMap; + p->m_subscriptionMap = m_subscriptionMap; + p->m_ctx = m_ctx; + + p->m_options.node = m_options.node; + p->m_options.df = m_options.df ? new DataForm( *(m_options.df) ) : 0; + + p->m_jid = m_jid; + p->m_node = m_node; + p->m_subid = m_subid; + ItemList::const_iterator it = m_items.begin(); + for( ; it != m_items.end(); ++it ) + p->m_items.push_back( new Item( *(*it) ) ); + + p->m_maxItems = m_maxItems; + p->m_notify = m_notify; + return p; + } + // ---- ~Manager::PubSub ---- + + // ---- Manager ---- + Manager::Manager( ClientBase* parent ) + : m_parent( parent ) + { + if( m_parent ) + { + m_parent->registerStanzaExtension( new PubSub() ); + m_parent->registerStanzaExtension( new PubSubOwner() ); + m_parent->registerStanzaExtension( new SHIM() ); + } + } + + const std::string Manager::getSubscriptionsOrAffiliations( const JID& service, + ResultHandler* handler, + TrackContext context ) + { + if( !m_parent || !handler || !service || context == InvalidContext ) + return EmptyString; + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Get, service, id ); + iq.addExtension( new PubSub( context ) ); + + m_trackMapMutex.lock(); + m_resultHandlerTrackMap[id] = handler; + m_trackMapMutex.unlock(); + m_parent->send( iq, this, context ); + return id; + } + + const std::string Manager::subscribe( const JID& service, + const std::string& node, + ResultHandler* handler, + const JID& jid, + SubscriptionObject type, + int depth, + const std::string& expire + ) + { + if( !m_parent || !handler || !service || node.empty() ) + return EmptyString; + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Set, service, id ); + PubSub* ps = new PubSub( Subscription ); + ps->setJID( jid ? jid : m_parent->jid() ); + ps->setNode( node ); + if( type != SubscriptionNodes || depth != 1 ) + { + DataForm* df = new DataForm( TypeSubmit ); + df->addField( DataFormField::TypeHidden, "FORM_TYPE", XMLNS_PUBSUB_SUBSCRIBE_OPTIONS ); + + if( type == SubscriptionItems ) + df->addField( DataFormField::TypeNone, "pubsub#subscription_type", "items" ); + + if( depth != 1 ) + { + DataFormField* field = df->addField( DataFormField::TypeNone, "pubsub#subscription_depth" ); + if( depth == 0 ) + field->setValue( "all" ); + else + field->setValue( util::int2string( depth ) ); + } + + if( !expire.empty() ) + { + DataFormField* field = df->addField( DataFormField::TypeNone, "pubsub#expire" ); + field->setValue( expire ); + } + + ps->setOptions( node, df ); + } + iq.addExtension( ps ); + + m_trackMapMutex.lock(); + m_resultHandlerTrackMap[id] = handler; + m_nopTrackMap[id] = node; + m_trackMapMutex.unlock(); + m_parent->send( iq, this, Subscription ); + return id; + } + + const std::string Manager::unsubscribe( const JID& service, + const std::string& node, + const std::string& subid, + ResultHandler* handler, + const JID& jid ) + { + if( !m_parent || !handler || !service ) + return EmptyString; + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Set, service, id ); + PubSub* ps = new PubSub( Unsubscription ); + ps->setNode( node ); + ps->setJID( jid ? jid : m_parent->jid() ); + ps->setSubscriptionID( subid ); + iq.addExtension( ps ); + + m_trackMapMutex.lock(); + m_resultHandlerTrackMap[id] = handler; + m_trackMapMutex.unlock(); + // FIXME? need to track info for handler + m_parent->send( iq, this, Unsubscription ); + return id; + } + + const std::string Manager::subscriptionOptions( TrackContext context, + const JID& service, + const JID& jid, + const std::string& node, + ResultHandler* handler, + DataForm* df ) + { + if( !m_parent || !handler || !service ) + return EmptyString; + + const std::string& id = m_parent->getID(); + IQ iq( df ? IQ::Set : IQ::Get, service, id ); + PubSub* ps = new PubSub( context ); + ps->setJID( jid ? jid : m_parent->jid() ); + ps->setOptions( node, df ); + iq.addExtension( ps ); + + m_trackMapMutex.lock(); + m_resultHandlerTrackMap[id] = handler; + m_trackMapMutex.unlock(); + m_parent->send( iq, this, context ); + return id; + } + + const std::string Manager::requestItems( const JID& service, + const std::string& node, + const std::string& subid, + int maxItems, + ResultHandler* handler ) + { + if( !m_parent || !service || !handler ) + return EmptyString; + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Get, service, id ); + PubSub* ps = new PubSub( RequestItems ); + ps->setNode( node ); + ps->setSubscriptionID( subid ); + ps->setMaxItems( maxItems ); + iq.addExtension( ps ); + + m_trackMapMutex.lock(); + m_resultHandlerTrackMap[id] = handler; + m_trackMapMutex.unlock(); + m_parent->send( iq, this, RequestItems ); + return id; + } + + const std::string Manager::requestItems( const JID& service, + const std::string& node, + const std::string& subid, + const ItemList& items, + ResultHandler* handler ) + { + if( !m_parent || !service || !handler ) + return EmptyString; + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Get, service, id ); + PubSub* ps = new PubSub( RequestItems ); + ps->setNode( node ); + ps->setSubscriptionID( subid ); + ps->setItems( items ); + iq.addExtension( ps ); + + m_trackMapMutex.lock(); + m_resultHandlerTrackMap[id] = handler; + m_trackMapMutex.unlock(); + m_parent->send( iq, this, RequestItems ); + return id; + } + + const std::string Manager::publishItem( const JID& service, + const std::string& node, + ItemList& items, + DataForm* options, + ResultHandler* handler ) + { + if( !m_parent || !handler ) + { + util::clearList( items ); + return EmptyString; + } + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Set, service, id ); + PubSub* ps = new PubSub( PublishItem ); + ps->setNode( node ); + ps->setItems( items ); + ps->setOptions( EmptyString, options ); + iq.addExtension( ps ); + + m_trackMapMutex.lock(); + m_resultHandlerTrackMap[id] = handler; + m_trackMapMutex.unlock(); + m_parent->send( iq, this, PublishItem ); + return id; + } + + const std::string Manager::deleteItem( const JID& service, + const std::string& node, + const ItemList& items, + bool notify, + ResultHandler* handler ) + { + if( !m_parent || !handler || !service ) + return EmptyString; + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Set, service, id ); + PubSub* ps = new PubSub( DeleteItem ); + ps->setNode( node ); + ps->setItems( items ); + ps->setNotify( notify ); + iq.addExtension( ps ); + + m_trackMapMutex.lock(); + m_resultHandlerTrackMap[id] = handler; + m_trackMapMutex.unlock(); + m_parent->send( iq, this, DeleteItem ); + return id; + } + + const std::string Manager::createNode( const JID& service, + const std::string& node, + DataForm* config, + ResultHandler* handler ) + { + if( !m_parent || !handler || !service || node.empty() ) + return EmptyString; + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Set, service, id ); + PubSub* ps = new PubSub( CreateNode ); + ps->setNode( node ); + ps->setOptions( EmptyString, config ); + iq.addExtension( ps ); + + m_trackMapMutex.lock(); + m_nopTrackMap[id] = node; + m_resultHandlerTrackMap[id] = handler; + m_trackMapMutex.unlock(); + m_parent->send( iq, this, CreateNode ); + return id; + } + + const std::string Manager::deleteNode( const JID& service, + const std::string& node, + ResultHandler* handler ) + { + if( !m_parent || !handler || !service || node.empty() ) + return EmptyString; + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Set, service, id ); + PubSubOwner* pso = new PubSubOwner( DeleteNode ); + pso->setNode( node ); + iq.addExtension( pso ); + + m_trackMapMutex.lock(); + m_nopTrackMap[id] = node; + m_resultHandlerTrackMap[id] = handler; + m_trackMapMutex.unlock(); + m_parent->send( iq, this, DeleteNode ); + return id; + } + + const std::string Manager::getDefaultNodeConfig( const JID& service, + NodeType type, + ResultHandler* handler ) + { + if( !m_parent || !handler || !service ) + return EmptyString; + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Get, service, id ); + PubSubOwner* pso = new PubSubOwner( DefaultNodeConfig ); + if( type == NodeCollection ) + { + DataForm* df = new DataForm( TypeSubmit ); + df->addField( DataFormField::TypeHidden, "FORM_TYPE", XMLNS_PUBSUB_NODE_CONFIG ); + df->addField( DataFormField::TypeNone, "pubsub#node_type", "collection" ); + pso->setConfig( df ); + } + iq.addExtension( pso ); + + m_trackMapMutex.lock(); + m_resultHandlerTrackMap[id] = handler; + m_trackMapMutex.unlock(); + m_parent->send( iq, this, DefaultNodeConfig ); + return id; + } + + const std::string Manager::nodeConfig( const JID& service, + const std::string& node, + DataForm* config, + ResultHandler* handler ) + { + if( !m_parent || !handler || !service || node.empty() ) + return EmptyString; + + const std::string& id = m_parent->getID(); + IQ iq( config ? IQ::Set : IQ::Get, service, id ); + PubSubOwner* pso = new PubSubOwner( config ? SetNodeConfig : GetNodeConfig ); + pso->setNode( node ); + if( config ) + pso->setConfig( config ); + iq.addExtension( pso ); + + m_trackMapMutex.lock(); + m_resultHandlerTrackMap[id] = handler; + m_trackMapMutex.unlock(); + m_parent->send( iq, this, config ? SetNodeConfig : GetNodeConfig ); + return id; + } + + const std::string Manager::subscriberList( TrackContext ctx, + const JID& service, + const std::string& node, + const SubscriberList& subList, + ResultHandler* handler ) + { + if( !m_parent || !handler || !service || node.empty() ) + return EmptyString; + + const std::string& id = m_parent->getID(); + IQ iq( ctx == SetSubscriberList ? IQ::Set : IQ::Get, service, id ); + PubSubOwner* pso = new PubSubOwner( ctx ); + pso->setNode( node ); + pso->setSubscriberList( subList ); + iq.addExtension( pso ); + + m_trackMapMutex.lock(); + m_nopTrackMap[id] = node; + m_resultHandlerTrackMap[id] = handler; + m_trackMapMutex.unlock(); + m_parent->send( iq, this, ctx ); + return id; + } + + const std::string Manager::affiliateList( TrackContext ctx, + const JID& service, + const std::string& node, + const AffiliateList& affList, + ResultHandler* handler ) + { + if( !m_parent || !handler || !service || node.empty() ) + return EmptyString; + + const std::string& id = m_parent->getID(); + IQ iq( ctx == SetAffiliateList ? IQ::Set : IQ::Get, service, id ); + PubSubOwner* pso = new PubSubOwner( ctx ); + pso->setNode( node ); + pso->setAffiliateList( affList ); + iq.addExtension( pso ); + + m_trackMapMutex.lock(); + m_nopTrackMap[id] = node; + m_resultHandlerTrackMap[id] = handler; + m_trackMapMutex.unlock(); + m_parent->send( iq, this, ctx ); + return id; + } + + const std::string Manager::purgeNode( const JID& service, + const std::string& node, + ResultHandler* handler ) + { + if( !m_parent || !handler || !service || node.empty() ) + return EmptyString; + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Set, service, id ); + PubSubOwner* pso = new PubSubOwner( PurgeNodeItems ); + pso->setNode( node ); + iq.addExtension( pso ); + + m_trackMapMutex.lock(); + m_nopTrackMap[id] = node; + m_resultHandlerTrackMap[id] = handler; + m_trackMapMutex.unlock(); + m_parent->send( iq, this, PurgeNodeItems ); + return id; + } + + bool Manager::removeID( const std::string& id ) + { + m_trackMapMutex.lock(); + ResultHandlerTrackMap::iterator ith = m_resultHandlerTrackMap.find( id ); + if( ith == m_resultHandlerTrackMap.end() ) + { + m_trackMapMutex.unlock(); + return false; + } + m_resultHandlerTrackMap.erase( ith ); + m_trackMapMutex.unlock(); + return true; + } + + void Manager::handleIqID( const IQ& iq, int context ) + { + const JID& service = iq.from(); + const std::string& id = iq.id(); + + m_trackMapMutex.lock(); + ResultHandlerTrackMap::iterator ith = m_resultHandlerTrackMap.find( id ); + if( ith == m_resultHandlerTrackMap.end() ) + { + m_trackMapMutex.unlock(); + return; + } + ResultHandler* rh = (*ith).second; + m_resultHandlerTrackMap.erase( ith ); + m_trackMapMutex.unlock(); + + switch( iq.subtype() ) + { + case IQ::Error: + case IQ::Result: + { + const Error* error = iq.error(); + switch( context ) + { + case Subscription: + { + const PubSub* ps = iq.findExtension( ExtPubSub ); + if( !ps ) + return; + SubscriptionMap sm = ps->subscriptions(); + if( !sm.empty() ) + { + SubscriptionMap::const_iterator it = sm.begin(); + const SubscriptionList& lst = (*it).second; + if( lst.size() == 1 ) + { + SubscriptionList::const_iterator it2 = lst.begin(); + rh->handleSubscriptionResult( id, service, (*it).first, (*it2).subid, (*it2).jid, + (*it2).type, error ); + } + } + break; + } + case Unsubscription: + { + rh->handleUnsubscriptionResult( iq.id(), service, error ); + break; + } + case GetSubscriptionList: + { + const PubSub* ps = iq.findExtension( ExtPubSub ); + if( !ps ) + return; + + rh->handleSubscriptions( id, service, + ps->subscriptions(), + error ); + break; + } + case GetAffiliationList: + { + const PubSub* ps = iq.findExtension( ExtPubSub ); + if( !ps ) + return; + + rh->handleAffiliations( id, service, + ps->affiliations(), + error ); + break; + } + case RequestItems: + { + const PubSub* ps = iq.findExtension( ExtPubSub ); + if( !ps ) + return; + + rh->handleItems( id, service, ps->node(), + ps->items(), error ); + break; + } + case PublishItem: + { + const PubSub* ps = iq.findExtension( ExtPubSub ); + if( ps && ps->items().size()) + { + const ItemList il = ps->items(); + rh->handleItemPublication( id, service, "", + il, error ); + } + break; + } + case DeleteItem: + { + const PubSub* ps = iq.findExtension( ExtPubSub ); + if( ps ) + { + rh->handleItemDeletion( id, service, + ps->node(), + ps->items(), + error ); + } + break; + } + case DefaultNodeConfig: + { + const PubSubOwner* pso = iq.findExtension( ExtPubSubOwner ); + if( pso ) + { + rh->handleDefaultNodeConfig( id, service, + pso->config(), + error ); + } + break; + } + case GetSubscriptionOptions: + case GetSubscriberList: + case SetSubscriberList: + case GetAffiliateList: + case SetAffiliateList: + case GetNodeConfig: + case SetNodeConfig: + case CreateNode: + case DeleteNode: + case PurgeNodeItems: + { + switch( context ) + { + case GetSubscriptionOptions: + { + const PubSub* ps = iq.findExtension( ExtPubSub ); + if( ps ) + { + rh->handleSubscriptionOptions( id, service, + ps->jid(), + ps->node(), + ps->options(), + error ); + } + break; + } +// case GetSubscriberList: +// { +// const PubSub* ps = iq.findExtension( ExtPubSub ); +// if( ps ) +// { +// rh->handleSubscribers( service, ps->node(), ps->subscriptions() ); +// } +// break; +// } + case SetSubscriptionOptions: + case SetSubscriberList: + case SetAffiliateList: + case SetNodeConfig: + case CreateNode: + case DeleteNode: + case PurgeNodeItems: + { + m_trackMapMutex.lock(); + NodeOperationTrackMap::iterator it = m_nopTrackMap.find( id ); + if( it != m_nopTrackMap.end() ) + { + const std::string& node = (*it).second; + switch( context ) + { + case SetSubscriptionOptions: + rh->handleSubscriptionOptionsResult( id, service, JID( /* FIXME */ ), node, error ); + break; + case SetSubscriberList: + rh->handleSubscribersResult( id, service, node, 0, error ); + break; + case SetAffiliateList: + rh->handleAffiliatesResult( id, service, node, 0, error ); + break; + case SetNodeConfig: + rh->handleNodeConfigResult( id, service, node, error ); + break; + case CreateNode: + rh->handleNodeCreation( id, service, node, error ); + break; + case DeleteNode: + rh->handleNodeDeletion( id, service, node, error ); + break; + case PurgeNodeItems: + rh->handleNodePurge( id, service, node, error ); + break; + } + m_nopTrackMap.erase( it ); + } + m_trackMapMutex.unlock(); + break; + } + case GetAffiliateList: + { +// const PubSub + + /* const TagList& affiliates = query->children(); + AffiliateList affList; + TagList::const_iterator it = affiliates.begin(); + for( ; it != affiliates.end(); ++it ) + { + Affiliate aff( (*it)->findAttribute( "jid" ), + affiliationType( (*it)->findAttribute( "affiliation" ) ) ); + affList.push_back( aff ); + } + rh->handleAffiliates( service, query->findAttribute( "node" ), &affList ); + */ + break; + } + case GetNodeConfig: + { + const PubSubOwner* pso = iq.findExtension( ExtPubSubOwner ); + if( pso ) + { + rh->handleNodeConfig( id, service, + pso->node(), + pso->config(), + error ); + } + break; + } + default: + break; + } + + break; + } + } + break; + } + default: + break; + } + + } + + } + +} + diff --git a/libs/libgloox/pubsubmanager.h b/libs/libgloox/pubsubmanager.h new file mode 100644 index 0000000..4c448b1 --- /dev/null +++ b/libs/libgloox/pubsubmanager.h @@ -0,0 +1,828 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef PUBSUBMANAGER_H__ +#define PUBSUBMANAGER_H__ + +#include "pubsub.h" +#include "dataform.h" +#include "iqhandler.h" +#include "mutex.h" + +#include +#include + +namespace gloox +{ + + class ClientBase; + + namespace PubSub + { + + class ResultHandler; + + /** + * @brief This manager is used to interact with PubSub services (XEP-0060). + * + * @note PubSub support in gloox is still relatively young and you are most + * welcome to ask questions, criticize the API and so on. + * + * A ResultHandler is used to receive a request's result. Depending on the + * context, this can be a notification that an item has been succesfully + * deleted (or not), or the default node configuration for a service. + * + * To receive PubSub events: + * @li Tell ClientBase that you are interested in PubSub events by registering + * an empty PubSub::Event StanzaExtension + * @code + * m_client->registerStanzaExtension( new PubSub::Event() ); + * @endcode + * @li Implement a MessageHandler and register it with ClientBase, or use the MessageSession interface, at your choice, + * @li When receiving a Message, check it for a PubSub::Event + * @code + * const PubSub::Event* pse = msg.findExtension( ExtPubSubEvent ); + * if( pse ) + * { + * // use the Event + * } + * else + * { + * // no Event + * } + * @endcode + * + * To interact with PubSub services, you will need to + * instantiate a PubSub::Manager and + * implement the ResultHandler virtual interfaces to be notified of the + * result of requests. + * + * @note A null ResultHandler to a query is not allowed and is a no-op. + * + * XEP Version: 1.12 + * + * @author Jakob Schroeter + * @author Vincent Thomasset + * + * @since 1.0 + */ + class GLOOX_API Manager : public IqHandler + { + public: + + /** + * Initialize the manager. + * @param parent Client to which this manager belongs. + */ + Manager( ClientBase* parent ); + + /** + * Default virtual destructor. + */ + virtual ~Manager() {} + + /** + * Subscribe to a node. + * + * @param service Service hosting the node. + * @param node ID of the node to subscribe to. + * @param handler The ResultHandler. + * @param jid JID to subscribe. If empty, the client's JID will be used + * (ie. self subscription). + * @param type SubscriptionType of the subscription (Collections only). + * @param depth Subscription depth. For 'all', use 0 (Collections only). + * @param expire Subscription expiry. Defaults to the empty string. + * @return The IQ ID used in the request. + * + * @see ResultHandler::handleSubscriptionResult + */ + const std::string subscribe( const JID& service, const std::string& node, + ResultHandler* handler, const JID& jid = JID(), + SubscriptionObject type = SubscriptionNodes, + int depth = 1, const std::string& expire = EmptyString ); + + /** + * Unsubscribe from a node. + * + * @param service Service hosting the node. + * @param node ID of the node to unsubscribe from. + * @param subid An optional, additional subscription ID. + * @param handler ResultHandler receiving the result notification. + * @param jid JID to unsubscribe. If empty, the client's JID will be + * used (ie self unsubscription). + * @return The IQ ID used in the request. + * + * @see ResultHandler::handleUnsubscriptionResult + */ + const std::string unsubscribe( const JID& service, + const std::string& node, + const std::string& subid, + ResultHandler* handler, + const JID& jid = JID() ); + + /** + * Requests the subscription list from a service. + * + * @param service Service to query. + * @param handler The ResultHandler to handle the result. + * @return The IQ ID used in the request. + * + * @see ResultHandler::handleSubscriptions + */ + const std::string getSubscriptions( const JID& service, + ResultHandler* handler ) + { + return getSubscriptionsOrAffiliations( service, + handler, + GetSubscriptionList ); + } + + /** + * Requests the affiliation list from a service. + * + * @param service Service to query. + * @param handler The ResultHandler to handle the result. + * @return The IQ ID used in the request. + * + * @see ResultHandler::handleAffiliations + */ + const std::string getAffiliations( const JID& service, + ResultHandler* handler ) + { + return getSubscriptionsOrAffiliations( service, + handler, + GetAffiliationList ); + } + + /** + * Requests subscription options. + * + * @param service Service to query. + * @param jid Subscribed entity. + * @param node Node ID of the node. + * @param handler The SubscriptionListHandler to handle the result. + * @return The IQ ID used in the request. + * + * @see ResultHandler::handleSubscriptionOptions + */ + const std::string getSubscriptionOptions( const JID& service, + const JID& jid, + const std::string& node, + ResultHandler* handler) + { return subscriptionOptions( GetSubscriptionOptions, service, jid, node, handler, 0 ); } + + /** + * Modifies subscription options. + * + * @param service Service to query. + * @param jid Subscribed entity. + * @param node Node ID of the node. + * @param df New configuration. The DataForm will be owned and deleted by the Manager. + * @param handler The handler to handle the result. + * @return The IQ ID used in the request. + * + * @see ResultHandler::handleSubscriptionOptionsResult + */ + const std::string setSubscriptionOptions( const JID& service, + const JID& jid, + const std::string& node, + DataForm* df, + ResultHandler* handler ) + { return subscriptionOptions( SetSubscriptionOptions, service, jid, node, handler, df ); } + + /** + * Requests the affiliation list for a node. + * + * @param service Service to query. + * @param node Node ID of the node. + * @param handler The AffiliationListHandler to handle the result. + * + * @see ResultHandler::handleAffiliations + */ + void getAffiliations( const JID& service, + const std::string& node, + ResultHandler* handler ); + + /** + * Requests items from a node. + * @param service Service to query. + * @param node Node ID of the node. + * @param subid An optional subscription ID. + * @param maxItems The optional maximum number of items to return. + * @param handler The handler to handle the result. + * @return The ID used in the request. + */ + const std::string requestItems( const JID& service, + const std::string& node, + const std::string& subid, + int maxItems, + ResultHandler* handler); + + /** + * Requests specific items from a node. + * @param service Service to query. + * @param node Node ID of the node. + * @param subid An optional subscription ID. + * @param items The list of item IDs to request. + * @param handler The handler to handle the result. + * @return The ID used in the request. + */ + const std::string requestItems( const JID& service, + const std::string& node, + const std::string& subid, + const ItemList& items, + ResultHandler* handler); + + /** + * Publish an item to a node. The Tag to publish is destroyed + * by the function before returning. + * + * @param service Service hosting the node. + * @param node ID of the node to delete the item from. + * @param items One or more items to publish. The items will be owned and deleted by the Manager, + * even in the error case (empty string returned). + * @param options An optional DataForm containing publish options. The DataForm will be owned and deleted by the Manager. + * @param handler The handler to handle the result. + * @return The ID used in the request. + * + * @see ResultHandler::handleItemPublication + */ + const std::string publishItem( const JID& service, + const std::string& node, + ItemList& items, + DataForm* options, + ResultHandler* handler ); + + /** + * Delete an item from a node. + * + * @param service Service hosting the node. + * @param node ID of the node to delete the item from. + * @param items A list of items to delete (only ID filled in). + * @param notify Whether or not to notify subscribers about the deletion. + * @param handler The handler to handle the result. + * @return The ID used in the request. + * + * @see ResultHandler::handleItemDeletation + */ + const std::string deleteItem( const JID& service, + const std::string& node, + const ItemList& items, + bool notify, + ResultHandler* handler ); + + /** + * Creates a new node. + * + * @param service Service where to create the new node. + * @param node The ID of the new node. + * @param config An optional DataForm that holds the node configuration. + * The DataForm will be owned and deleted by the Manager. + * @param handler The handler to handle the result. + * @return The ID used in the request. + * + * @see ResultHandler::handleNodeCreation + */ + const std::string createNode( const JID& service, + const std::string& node, + DataForm* config, + ResultHandler* handler ); + + /** + * Deletes a node. + * + * @param service Service where to create the new node. + * @param node Node ID of the new node. + * @param handler The handler to handle the result. + * @return The ID used in the request. + * + * @see ResultHandler::handleNodeDeletion + */ + const std::string deleteNode( const JID& service, + const std::string& node, + ResultHandler* handler ); + + /** + * Retrieves the default configuration for a specific NodeType. + * + * @param service The queried service. + * @param type NodeType to get default configuration for. + * @param handler ResultHandler. + * @return The ID used in the request. + * + * @see ResultHandler::handleDefaultNodeConfig + */ + const std::string getDefaultNodeConfig( const JID& service, + NodeType type, + ResultHandler* handler ); + + /** + * Removes all the items from a node. + * + * @param service Service to query. + * @param node Node ID of the node. + * @param handler ResultHandler. + * @return The ID used in the request. + * + * @see ResultHandler::handleNodePurge + */ + const std::string purgeNode( const JID& service, + const std::string& node, + ResultHandler* handler ); + + /** + * Requests the subscriber list for a node. + * + * @param service Service to query. + * @param node Node ID of the node. + * @param handler ResultHandler. + * @return The ID used in the request. + * + * @see ResultHandler::handleSubscribers + */ + const std::string getSubscribers( const JID& service, + const std::string& node, + ResultHandler* handler ) + { return subscriberList( GetSubscriberList, service, + node, SubscriberList(), + handler ); } + + /** + * Modifies the subscriber list for a node. This function SHOULD only set the + * subscriber list to those which needs modification. + * + * @param service Service to query. + * @param node Node ID of the node. + * @param list The subscriber list. + * @param handler The ResultHandler. + * @return The ID used in the request. + * + * @see ResultHandler::handleSubscribers + */ + const std::string setSubscribers( const JID& service, + const std::string& node, + const SubscriberList& list, + ResultHandler* handler ) + { return subscriberList( SetSubscriberList, service, + node, list, handler ); } + + /** + * Requests the affiliate list for a node. + * + * @param service Service to query. + * @param node Node ID of the node. + * @param handler ResultHandler. + * @return The ID used in the request. + * + * @see ResultHandler::handleAffiliates + */ + const std::string getAffiliates( const JID& service, + const std::string& node, + ResultHandler* handler ) + { return affiliateList( GetAffiliateList, service, + node, AffiliateList(), + handler ); } + + /** + * Modifies the affiliate list for a node. + * + * @param service Service to query. + * @param node Node ID of the node. + * @param list ResultHandler. + * @param handler ResultHandler. + * @return The ID used in the request. + * + * @see ResultHandler::handleAffiliatesResult + */ + const std::string setAffiliates( const JID& service, + const std::string& node, + const AffiliateList& list, + ResultHandler* handler ) + { return affiliateList( SetAffiliateList, service, + node, list, handler ); } + + /** + * Retrieve the configuration (options) of a node. + * + * @param service Service hosting the node. + * @param node ID of the node. + * @param handler ResultHandler responsible to handle the request result. + * @return The ID used in the request. + * + * @see ResultHandler::handleNodeConfig + */ + const std::string getNodeConfig( const JID& service, + const std::string& node, + ResultHandler* handler ) + { return nodeConfig( service, node, 0, handler ); } + + /** + * Changes a node's configuration (options). + * + * @param service Service to query. + * @param node Node ID of the node. + * @param config The node's configuration DataForm. + * @param handler ResultHandler responsible to handle the request result. + * @return The ID used in the request. + * + * @see ResultHandler::handleNodeConfigResult + */ + const std::string setNodeConfig( const JID& service, + const std::string& node, + DataForm* config, + ResultHandler* handler ) + { return nodeConfig( service, node, config, handler ); } + + /** + * Removes an ID from our tracking lists. + * @param id The ID to remove. + * @return @b True if the ID was found and removed, @b false otherwise. + */ + bool removeID( const std::string& id ); + + // reimplemented from DiscoHandler + void handleDiscoInfoResult( IQ* iq, int context ); + + // reimplemented from DiscoHandler + void handleDiscoItemsResult( IQ* iq, int context ); + + // reimplemented from DiscoHandler + void handleDiscoError( IQ* iq, int context ); + + // reimplemented from DiscoHandler + bool handleDiscoSet( IQ* ) { return 0; } + + // reimplemented from IqHandler. + virtual bool handleIq( const IQ& iq ) { (void)iq; return false; } + + // reimplemented from IqHandler. + virtual void handleIqID( const IQ& iq, int context ); + + private: +#ifdef PUBSUBMANAGER_TEST + public: +#endif + + enum TrackContext + { + Subscription, + Unsubscription, + GetSubscriptionOptions, + SetSubscriptionOptions, + GetSubscriptionList, + GetSubscriberList, + SetSubscriberList, + GetAffiliationList, + GetAffiliateList, + SetAffiliateList, + GetNodeConfig, + SetNodeConfig, + DefaultNodeConfig, + GetItemList, + PublishItem, + DeleteItem, + CreateNode, + DeleteNode, + PurgeNodeItems, + NodeAssociation, + NodeDisassociation, + GetFeatureList, + DiscoServiceInfos, + DiscoNodeInfos, + DiscoNodeItems, + RequestItems, + InvalidContext + }; + + class PubSubOwner : public StanzaExtension + { + public: + /** + * Creates a new PubSubOwner object that can be used to request the given type. + * @param context The requets type. + */ + PubSubOwner( TrackContext context = InvalidContext ); + + /** + * Creates a new PubSubOwner object by parsing the given Tag. + * @param tag The Tag to parse. + */ + PubSubOwner( const Tag* tag ); + + /** + * Virtual destructor. + */ + virtual ~PubSubOwner(); + + /** + * Sets the node to use in e.g. subscription requests. + * @param node The node to use. + */ + void setNode( const std::string& node ) { m_node = node; } + + /** + * Returns the pubsub node. + * @return The pubsub node. + */ + const std::string& node() const { return m_node; } + + /** + * Sets an options DataForm. + * @param options The DataForm. + */ + void setConfig( DataForm* config ) + { m_form = config; } + + /** + * Returns the config DataForm. + * @return The config DataForm. + */ + const DataForm* config() const { return m_form; } + + /** + * Sets the subscriber list. + * @param subList The subscriber list. + */ + void setSubscriberList( const SubscriberList& subList ) + { m_subList = subList; } + + /** + * Sets the affiliate list. + * @param affList The affiliate list. + */ + void setAffiliateList( const AffiliateList& affList ) + { m_affList = affList; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new PubSubOwner( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + PubSubOwner* p = new PubSubOwner(); + p->m_node = m_node; + p->m_ctx = m_ctx; + p->m_form = m_form ? new DataForm( *m_form ) : 0; + p->m_subList = m_subList; + p->m_affList = m_affList; + return p; + } + + private: + std::string m_node; + TrackContext m_ctx; + DataForm* m_form; + SubscriberList m_subList; + AffiliateList m_affList; + }; + + class PubSub : public StanzaExtension + { + public: + /** + * Creates a new PubSub object that can be used to request the given type. + * @param context The requets type. + */ + PubSub( TrackContext context = InvalidContext ); + + /** + * Creates a new PubSub object by parsing the given Tag. + * @param tag The Tag to parse. + */ + PubSub( const Tag* tag ); + + /** + * Virtual destructor. + */ + virtual ~PubSub(); + + /** + * Sets the JID to use in e.g. subscription requests. + * @param jid The JID to use. + */ + void setJID( const JID& jid ) { m_jid = jid; } + + /** + * Returns the pubsub JID (not the service JID). + * @return The pubsub JID. + */ + const JID& jid() const { return m_jid; } + + /** + * Sets the node to use in e.g. subscription requests. + * @param node The node to use. + */ + void setNode( const std::string& node ) { m_node = node; } + + /** + * Returns the pubsub node. + * @return The pubsub node. + */ + const std::string& node() const { return m_node; } + + /** + * Sets the Subscription ID to use. + * @param subid The Subscription ID to use. + */ + void setSubscriptionID( const std::string& subid ) + { m_subid = subid; } + + /** + * Gets the Subscription ID to use. + * @return The Subscription ID to use. + */ + const std::string& subscriptionID() const { return m_subid; } + + /** + * Sets the subscription options. + * @param node The node to set the options for. + * @param df The DataForm holding the subscription options. + * Will be owned and deleted by the PubSub object + */ + void setOptions( const std::string& node, DataForm* df ) + { + m_options.node = node; + m_options.df = df; + } + + /** + * Returns the subscription options. + * @return The subscription options. + */ + const DataForm* options() const + { return m_options.df; } + + /** + * Returns the current Items. + * @return The current items. + */ + const ItemList& items() const { return m_items; } + + /** + * Sets the subscription IDs. + * @param ids Subscription IDs. + */ + void setItems( const ItemList& items ) + { m_items = items; } + + /** + * Sets the maximum number of items to request. + * @param maxItems The maximum number of items to request. + */ + void setMaxItems( int maxItems ) + { m_maxItems = maxItems; } + + /** + * Returns the subscriptions. + * @param The subscriptions. + */ + const SubscriptionMap& subscriptions() const + { return m_subscriptionMap; } + + /** + * Returns the affiliations. + * @param The affiliations. + */ + const AffiliationMap& affiliations() const + { return m_affiliationMap; } + + /** + * Sets whether or not a notify element should be included in a 'retract'. + * @param notify Indicates whether a notify attribute is included. + */ + void setNotify( bool notify ) { m_notify = notify; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new PubSub( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const; + + private: + AffiliationMap m_affiliationMap; + SubscriptionMap m_subscriptionMap; + TrackContext m_ctx; + + struct Options + { + std::string node; + DataForm* df; + }; + Options m_options; + JID m_jid; + std::string m_node; + std::string m_subid; + ItemList m_items; + int m_maxItems; + bool m_notify; + }; + + /** + * This function sets or requests a node's configuration form + * (depending on arguments). Does the actual work for requestNodeConfig + * and setNodeConfig. + * Requests or changes a node's configuration. + * @param service Service to query. + * @param node Node ID of the node. + * @param config If not NULL, the function will request the node config. + * Otherwise, it will set the config based on the form. + * @param handler ResultHandler responsible to handle the request result. + */ + const std::string nodeConfig( const JID& service, + const std::string& node, + DataForm* config, + ResultHandler* handler ); + + /** + * This function sets or requests a node's subscribers list form + * (depending on arguments). Does the actual work for + * requestSubscriberList and setSubscriberList. + * Requests or changes a node's configuration. + * @param ctx The operation to be performed. + * @param service Service to query. + * @param node Node ID of the node. + * @param config If not NULL, the function will request the node config. + * Otherwise, it will set the config based on the form. + * @param handler ResultHandler responsible to handle the request result. + * @return The ID used in the request. + */ + const std::string subscriberList( TrackContext ctx, + const JID& service, + const std::string& node, + const SubscriberList& config, + ResultHandler* handler ); + + /** + * This function sets or requests a node's affiliates list + * (depending on arguments). Does the actual work for + * requestAffiliateList and setAffiliateList. + * Requests or changes a node's configuration. + * @param ctx The operation to be performed. + * @param service Service to query. + * @param node Node ID of the node. + * @param config If not NULL, the function will request the node config. + * Otherwise, it will set the config based on the form. + * @param handler ResultHandler responsible to handle the request result. + * @return The ID used in the request. + */ + const std::string affiliateList( TrackContext ctx, + const JID& service, + const std::string& node, + const AffiliateList& config, + ResultHandler* handler ); + + const std::string subscriptionOptions( TrackContext context, + const JID& service, + const JID& jid, + const std::string& node, + ResultHandler* handler, + DataForm* df ); + + const std::string getSubscriptionsOrAffiliations( const JID& service, + ResultHandler* handler, + TrackContext context ); + + typedef std::map < std::string, std::string > NodeOperationTrackMap; + typedef std::map < std::string, ResultHandler* > ResultHandlerTrackMap; + + ClientBase* m_parent; + + NodeOperationTrackMap m_nopTrackMap; + ResultHandlerTrackMap m_resultHandlerTrackMap; + + util::Mutex m_trackMapMutex; + + }; + + } + +} + +#endif // PUBSUBMANAGER_H__ diff --git a/libs/libgloox/pubsubresulthandler.h b/libs/libgloox/pubsubresulthandler.h new file mode 100644 index 0000000..174febc --- /dev/null +++ b/libs/libgloox/pubsubresulthandler.h @@ -0,0 +1,391 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef PUBSUBRESULTHANDLER_H__ +#define PUBSUBRESULTHANDLER_H__ + +#include "error.h" +#include "jid.h" +#include "macros.h" +#include "pubsub.h" +#include "tag.h" + +#include +#include +#include + +namespace gloox +{ + + class Tag; + class Error; + class DataForm; + + namespace PubSub + { + /** + * @brief A virtual interface to receive item related requests results. + * + * Derive from this interface and pass it to item related requests. + * + * As a general rule, methods receive an Error pointer which will be null + * (when the request was successful) or describe the problem. Requests + * asking for information will have their "pointer to information" set to + * null when an error occured (that is they're mutually exclusive). In both + * cases, gloox takes care of deleting these objects. + * + * @author Vincent Thomasset + * @since 1.0 + */ + class GLOOX_API ResultHandler + { + public: + /** + * Virtual destructor. + */ + virtual ~ResultHandler() {} + + /** + * Receives the payload for an item. + * + * @param service Service hosting the queried node. + * @param node ID of the parent node. + * @param entry The complete item Tag (do not delete). + */ + virtual void handleItem( const JID& service, + const std::string& node, + const Tag* entry ) = 0; + + /** + * Receives the list of Items for a node. + * + * @param id The reply IQ's id. + * @param service Service hosting the queried node. + * @param node ID of the queried node (empty for the root node). + * @param itemList List of contained items. + * @param error Describes the error case if the request failed. + * + * @see Manager::requestItems() + */ + virtual void handleItems( const std::string& id, + const JID& service, + const std::string& node, + const ItemList& itemList, + const Error* error = 0 ) = 0; + + /** + * Receives the result for an item publication. + * + * @param id The reply IQ's id. + * @param service Service hosting the queried node. + * @param node ID of the queried node. If empty, the root node has been queried. + * @param itemList List of contained items. + * @param error Describes the error case if the request failed. + * + * @see Manager::publishItem + */ + virtual void handleItemPublication( const std::string& id, + const JID& service, + const std::string& node, + const ItemList& itemList, + const Error* error = 0 ) = 0; + + /** + * Receives the result of an item removal. + * + * @param id The reply IQ's id. + * @param service Service hosting the queried node. + * @param node ID of the queried node. If empty, the root node has been queried. + * @param itemList List of contained items. + * @param error Describes the error case if the request failed. + * + * @see Manager::deleteItem + */ + virtual void handleItemDeletion( const std::string& id, + const JID& service, + const std::string& node, + const ItemList& itemList, + const Error* error = 0 ) = 0; + + /** + * Receives the subscription results. In case a problem occured, the + * Subscription ID and SubscriptionType becomes irrelevant. + * + * @param id The reply IQ's id. + * @param service PubSub service asked for subscription. + * @param node Node asked for subscription. + * @param sid Subscription ID. + * @param jid Subscribed entity. + * @param subType Type of the subscription. + * @param error Subscription Error. + * + * @see Manager::subscribe + */ + virtual void handleSubscriptionResult( const std::string& id, + const JID& service, + const std::string& node, + const std::string& sid, + const JID& jid, + const SubscriptionType subType, + const Error* error = 0 ) = 0; + + /** + * Receives the unsubscription results. In case a problem occured, the + * subscription ID becomes irrelevant. + * + * @param id The reply IQ's id. + * @param service PubSub service. + * @param error Unsubscription Error. + * + * @see Manager::unsubscribe + */ + virtual void handleUnsubscriptionResult( const std::string& id, + const JID& service, + const Error* error = 0 ) = 0; + + /** + * Receives the subscription options for a node. + * + * @param id The reply IQ's id. + * @param service Service hosting the queried node. + * @param jid Subscribed entity. + * @param node ID of the node. + * @param options Options DataForm. + * @param error Subscription options retrieval Error. + * + * @see Manager::getSubscriptionOptions + */ + virtual void handleSubscriptionOptions( const std::string& id, + const JID& service, + const JID& jid, + const std::string& node, + const DataForm* options, + const Error* error = 0 ) = 0; + + /** + * Receives the result for a subscription options modification. + * + * @param id The reply IQ's id. + * @param service Service hosting the queried node. + * @param jid Subscribed entity. + * @param node ID of the queried node. + * @param error Subscription options modification Error. + * + * @see Manager::setSubscriptionOptions + */ + virtual void handleSubscriptionOptionsResult( const std::string& id, + const JID& service, + const JID& jid, + const std::string& node, + const Error* error = 0 ) = 0; + + + /** + * Receives the list of subscribers to a node. + * + * @param id The reply IQ's id. + * @param service Service hosting the node. + * @param node ID of the queried node. + * @param list Subscriber list. + * @param error Subscription options modification Error. + * + * @see Manager::getSubscribers + */ + virtual void handleSubscribers( const std::string& id, + const JID& service, + const std::string& node, + const SubscriberList* list, + const Error* error = 0 ) = 0; + + /** + * Receives the result of a subscriber list modification. + * + * @param id The reply IQ's id. + * @param service Service hosting the node. + * @param node ID of the queried node. + * @param list Subscriber list. + * @param error Subscriber list modification Error. + * + * @see Manager::setSubscribers + */ + virtual void handleSubscribersResult( const std::string& id, + const JID& service, + const std::string& node, + const SubscriberList* list, + const Error* error = 0 ) = 0; + + /** + * Receives the affiliate list for a node. + * + * @param id The reply IQ's id. + * @param service Service hosting the node. + * @param node ID of the queried node. + * @param list Affiliation list. + * @param error Affiliation list retrieval Error. + * + * @see Manager::getAffiliations + */ + virtual void handleAffiliates( const std::string& id, + const JID& service, + const std::string& node, + const AffiliateList* list, + const Error* error = 0 ) = 0; + + /** + * Handle the affiliate list for a specific node. + * + * @param id The reply IQ's id. + * @param service Service hosting the node. + * @param node ID of the node. + * @param list The Affiliate list. + * @param error Affiliation list modification Error. + * + * @see Manager::setAffiliations + */ + virtual void handleAffiliatesResult( const std::string& id, + const JID& service, + const std::string& node, + const AffiliateList* list, + const Error* error = 0 ) = 0; + + + /** + * Receives the configuration for a specific node. + * + * @param id The reply IQ's id. + * @param service Service hosting the node. + * @param node ID of the node. + * @param config Configuration DataForm. + * @param error Configuration retrieval Error. + * + * @see Manager::getNodeConfig + */ + virtual void handleNodeConfig( const std::string& id, + const JID& service, + const std::string& node, + const DataForm* config, + const Error* error = 0 ) = 0; + + /** + * Receives the result of a node's configuration modification. + * + * @param id The reply IQ's id. + * @param service Service hosting the node. + * @param node ID of the node. + * @param error Configuration modification Error. + * + * @see Manager::setNodeConfig + */ + virtual void handleNodeConfigResult( const std::string& id, + const JID& service, + const std::string& node, + const Error* error = 0 ) = 0; + + /** + * Receives the result of a node creation. + * + * @param id The reply IQ's id. + * @param service Service hosting the node. + * @param node ID of the node. + * @param error Node creation Error. + * + * @see Manager::setNodeConfig + */ + virtual void handleNodeCreation( const std::string& id, + const JID& service, + const std::string& node, + const Error* error = 0 ) = 0; + + /** + * Receives the result for a node removal. + * + * @param id The reply IQ's id. + * @param service Service hosting the node. + * @param node ID of the node. + * @param error Node removal Error. + * + * @see Manager::deleteNode + */ + virtual void handleNodeDeletion( const std::string& id, + const JID& service, + const std::string& node, + const Error* error = 0 ) = 0; + + + /** + * Receives the result of a node purge request. + * + * @param id The reply IQ's id. + * @param service Service hosting the node. + * @param node ID of the node. + * @param error Node purge Error. + * + * @see Manager::purgeNode + */ + virtual void handleNodePurge( const std::string& id, + const JID& service, + const std::string& node, + const Error* error = 0 ) = 0; + + /** + * Receives the Subscription list for a specific service. + * + * @param id The reply IQ's id. + * @param service The queried service. + * @param subMap The map of node's subscription. + * @param error Subscription list retrieval Error. + * + * @see Manager::getSubscriptions + */ + virtual void handleSubscriptions( const std::string& id, + const JID& service, + const SubscriptionMap& subMap, + const Error* error = 0) = 0; + + /** + * Receives the Affiliation map for a specific service. + * + * @param id The reply IQ's id. + * @param service The queried service. + * @param affMap The map of node's affiliation. + * @param error Affiliation list retrieval Error. + * + * @see Manager::getAffiliations + */ + virtual void handleAffiliations( const std::string& id, + const JID& service, + const AffiliationMap& affMap, + const Error* error = 0 ) = 0; + + /** + * Receives the default configuration for a specific node type. + * + * @param id The reply IQ's id. + * @param service The queried service. + * @param config Configuration form for the node type. + * @param error Default node config retrieval Error. + * + * @see Manager::getDefaultNodeConfig + */ + virtual void handleDefaultNodeConfig( const std::string& id, + const JID& service, + const DataForm* config, + const Error* error = 0 ) = 0; + + }; + + } + +} + +#endif // PUBSUBRESULTHANDLER_H__ + diff --git a/libs/libgloox/receipt.cpp b/libs/libgloox/receipt.cpp new file mode 100644 index 0000000..99cfba7 --- /dev/null +++ b/libs/libgloox/receipt.cpp @@ -0,0 +1,53 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "receipt.h" +#include "tag.h" +#include "util.h" + +namespace gloox +{ + + /* chat state type values */ + static const char* receiptValues [] = { + "request", + "received" + }; + + static inline Receipt::ReceiptType receiptType( const std::string& type ) + { + return (Receipt::ReceiptType)util::lookup( type, receiptValues ); + } + + Receipt::Receipt( const Tag* tag ) + : StanzaExtension( ExtReceipt ), + m_rcpt( receiptType( tag->name() ) ) + { + } + + const std::string& Receipt::filterString() const + { + static const std::string filter = + "/message/request[@xmlns='" + XMLNS_RECEIPTS + "']" + "|/message/received[@xmlns='" + XMLNS_RECEIPTS + "']"; + return filter; + } + + Tag* Receipt::tag() const + { + if( m_rcpt == Invalid ) + return 0; + + return new Tag( util::lookup( m_rcpt, receiptValues ), XMLNS, XMLNS_RECEIPTS ); + } + +} diff --git a/libs/libgloox/receipt.h b/libs/libgloox/receipt.h new file mode 100644 index 0000000..21141d5 --- /dev/null +++ b/libs/libgloox/receipt.h @@ -0,0 +1,95 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef RECEIPT_H__ +#define RECEIPT_H__ + +#include "gloox.h" +#include "stanzaextension.h" + +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief An implementation of Message Receipts (XEP-0184) as a StanzaExtension. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Receipt : public StanzaExtension + { + public: + /** + * Contains valid receipt types (XEP-0184). + */ + enum ReceiptType + { + Request, /**< Requests a receipt. */ + Received, /**< The receipt. */ + Invalid /**< Invalid type. */ + }; + + /** + * Constructs a new object from the given Tag. + * @param tag A Tag to parse. + */ + Receipt( const Tag* tag ); + + /** + * Constructs a new object of the given type. + * @param rcpt The receipt type. + */ + Receipt( ReceiptType rcpt ) + : StanzaExtension( ExtReceipt ), m_rcpt( rcpt ) + {} + + /** + * Virtual destructor. + */ + virtual ~Receipt() {} + + /** + * Returns the object's state. + * @return The object's state. + */ + ReceiptType rcpt() const { return m_rcpt; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Receipt( tag ); + } + + // reimplemented from StanzaExtension + Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new Receipt( *this ); + } + + private: + ReceiptType m_rcpt; + + }; + +} + +#endif // RECEIPT_H__ diff --git a/libs/libgloox/registration.cpp b/libs/libgloox/registration.cpp new file mode 100644 index 0000000..c5f900a --- /dev/null +++ b/libs/libgloox/registration.cpp @@ -0,0 +1,391 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "registration.h" + +#include "clientbase.h" +#include "stanza.h" +#include "error.h" +#include "prep.h" +#include "oob.h" + +namespace gloox +{ + + // Registration::Query ---- + Registration::Query::Query( DataForm* form ) + : StanzaExtension( ExtRegistration ), m_form( form ), m_fields( 0 ), m_oob( 0 ), + m_del( false ), m_reg( false ) + { + } + + Registration::Query::Query( bool del ) + : StanzaExtension( ExtRegistration ), m_form( 0 ), m_fields( 0 ), m_oob( 0 ), m_del( del ), + m_reg( false ) + { + } + + Registration::Query::Query( int fields, const RegistrationFields& values ) + : StanzaExtension( ExtRegistration ), m_form( 0 ), m_fields( fields ), m_values( values ), + m_oob( 0 ), m_del( false ), m_reg( false ) + { + } + + Registration::Query::Query( const Tag* tag ) + : StanzaExtension( ExtRegistration ), m_form( 0 ), m_fields( 0 ), m_oob( 0 ), m_del( false ), + m_reg( false ) + { + if( !tag || tag->name() != "query" || tag->xmlns() != XMLNS_REGISTER ) + return; + + const TagList& l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + const std::string& name = (*it)->name(); + if( name == "instructions" ) + m_instructions = (*it)->cdata(); + else if( name == "remove" ) + m_del = true; + else if( name == "registered" ) + m_reg = true; + else if( name == "username" ) + { + m_fields |= FieldUsername; + m_values.username = (*it)->cdata(); + } + else if( name == "nick" ) + { + m_fields |= FieldNick; + m_values.nick = (*it)->cdata(); + } + else if( name == "password" ) + { + m_fields |= FieldPassword; + m_values.password = (*it)->cdata(); + } + else if( name == "name" ) + { + m_fields |= FieldName; + m_values.name = (*it)->cdata(); + } + else if( name == "first" ) + { + m_fields |= FieldFirst; + m_values.first = (*it)->cdata(); + } + else if( name == "last" ) + { + m_fields |= FieldLast; + m_values.last = (*it)->cdata(); + } + else if( name == "email" ) + { + m_fields |= FieldEmail; + m_values.email = (*it)->cdata(); + } + else if( name == "address" ) + { + m_fields |= FieldAddress; + m_values.address = (*it)->cdata(); + } + else if( name == "city" ) + { + m_fields |= FieldCity; + m_values.city = (*it)->cdata(); + } + else if( name == "state" ) + { + m_fields |= FieldState; + m_values.state = (*it)->cdata(); + } + else if( name == "zip" ) + { + m_fields |= FieldZip; + m_values.zip = (*it)->cdata(); + } + else if( name == "phone" ) + { + m_fields |= FieldPhone; + m_values.phone = (*it)->cdata(); + } + else if( name == "url" ) + { + m_fields |= FieldUrl; + m_values.url = (*it)->cdata(); + } + else if( name == "date" ) + { + m_fields |= FieldDate; + m_values.date = (*it)->cdata(); + } + else if( name == "misc" ) + { + m_fields |= FieldMisc; + m_values.misc = (*it)->cdata(); + } + else if( name == "text" ) + { + m_fields |= FieldText; + m_values.text = (*it)->cdata(); + } + else if( !m_form && name == "x" && (*it)->xmlns() == XMLNS_X_DATA ) + m_form = new DataForm( (*it) ); + else if( !m_oob && name == "x" && (*it)->xmlns() == XMLNS_X_OOB ) + m_oob = new OOB( (*it) ); + } + } + + Registration::Query::~Query() + { + delete m_form; + delete m_oob; + } + + const std::string& Registration::Query::filterString() const + { + static const std::string filter = "/iq/query[@xmlns='" + XMLNS_REGISTER + "']"; + return filter; + } + + Tag* Registration::Query::tag() const + { + Tag* t = new Tag( "query" ); + t->setXmlns( XMLNS_REGISTER ); + + if( !m_instructions.empty() ) + new Tag( t, "instructions", m_instructions ); + + if ( m_reg ) + new Tag( t, "registered" ); + + if( m_form ) + t->addChild( m_form->tag() ); + else if( m_oob ) + t->addChild( m_oob->tag() ); + else if( m_del ) + new Tag( t, "remove" ); + else if( m_fields ) + { + if( m_fields & FieldUsername ) + new Tag( t, "username", m_values.username ); + if( m_fields & FieldNick ) + new Tag( t, "nick", m_values.nick ); + if( m_fields & FieldPassword ) + new Tag( t, "password", m_values.password ); + if( m_fields & FieldName ) + new Tag( t, "name", m_values.name ); + if( m_fields & FieldFirst ) + new Tag( t, "first", m_values.first ); + if( m_fields & FieldLast ) + new Tag( t, "last", m_values.last ); + if( m_fields & FieldEmail ) + new Tag( t, "email", m_values.email ); + if( m_fields & FieldAddress ) + new Tag( t, "address", m_values.address ); + if( m_fields & FieldCity ) + new Tag( t, "city", m_values.city ); + if( m_fields & FieldState ) + new Tag( t, "state", m_values.state ); + if( m_fields & FieldZip ) + new Tag( t, "zip", m_values.zip ); + if( m_fields & FieldPhone ) + new Tag( t, "phone", m_values.phone ); + if( m_fields & FieldUrl ) + new Tag( t, "url", m_values.url ); + if( m_fields & FieldDate ) + new Tag( t, "date", m_values.date ); + if( m_fields & FieldMisc ) + new Tag( t, "misc", m_values.misc ); + if( m_fields & FieldText ) + new Tag( t, "text", m_values.text ); + } + + return t; + } + // ---- ~Registration::Query ---- + + // ---- Registration ---- + Registration::Registration( ClientBase* parent, const JID& to ) + : m_parent( parent ), m_to( to ), m_registrationHandler( 0 ) + { + init(); + } + + Registration::Registration( ClientBase* parent ) + : m_parent( parent ), m_registrationHandler( 0 ) + { + init(); + } + + void Registration::init() + { + if( m_parent ) + { + m_parent->registerIqHandler( this, ExtRegistration ); + m_parent->registerStanzaExtension( new Query() ); + } + } + + Registration::~Registration() + { + if( m_parent ) + { + m_parent->removeIqHandler( this, ExtRegistration ); + m_parent->removeIDHandler( this ); + m_parent->removeStanzaExtension( ExtRegistration ); + } + } + + void Registration::fetchRegistrationFields() + { + if( !m_parent || m_parent->state() != StateConnected ) + return; + + IQ iq( IQ::Get, m_to ); + iq.addExtension( new Query() ); + m_parent->send( iq, this, FetchRegistrationFields ); + } + + bool Registration::createAccount( int fields, const RegistrationFields& values ) + { + std::string username; + if( !m_parent || !prep::nodeprep( values.username, username ) ) + return false; + + IQ iq( IQ::Set, m_to ); + iq.addExtension( new Query( fields, values ) ); + m_parent->send( iq, this, CreateAccount ); + + return true; + } + + void Registration::createAccount( DataForm* form ) + { + if( !m_parent || !form ) + return; + + IQ iq( IQ::Set, m_to ); + iq.addExtension( new Query( form ) ); + m_parent->send( iq, this, CreateAccount ); + } + + void Registration::removeAccount() + { + if( !m_parent || !m_parent->authed() ) + return; + + IQ iq( IQ::Set, m_to ); + iq.addExtension( new Query( true ) ); + m_parent->send( iq, this, RemoveAccount ); + } + + void Registration::changePassword( const std::string& username, const std::string& password ) + { + if( !m_parent || !m_parent->authed() || username.empty() ) + return; + + int fields = FieldUsername | FieldPassword; + RegistrationFields rf; + rf.username = username; + rf.password = password; + createAccount( fields, rf ); + } + + void Registration::registerRegistrationHandler( RegistrationHandler* rh ) + { + m_registrationHandler = rh; + } + + void Registration::removeRegistrationHandler() + { + m_registrationHandler = 0; + } + + void Registration::handleIqID( const IQ& iq, int context ) + { + if( !m_registrationHandler ) + return; + + if( iq.subtype() == IQ::Result ) + { + switch( context ) + { + case FetchRegistrationFields: + { + const Query* q = iq.findExtension( ExtRegistration ); + if( !q ) + return; + + if( q->registered() ) + m_registrationHandler->handleAlreadyRegistered( iq.from() ); + + if( q->form() ) + m_registrationHandler->handleDataForm( iq.from(), *(q->form()) ); + + if( q->oob() ) + m_registrationHandler->handleOOB( iq.from(), *(q->oob()) ); + + m_registrationHandler->handleRegistrationFields( iq.from(), q->fields(), q->instructions() ); + break; + } + + case CreateAccount: + case ChangePassword: + case RemoveAccount: + m_registrationHandler->handleRegistrationResult( iq.from(), RegistrationSuccess ); + break; + } + } + else if( iq.subtype() == IQ::Error ) + { + const Error* e = iq.error(); + if( !e ) + return; + + switch( e->error() ) + { + case StanzaErrorConflict: + m_registrationHandler->handleRegistrationResult( iq.from(), RegistrationConflict ); + break; + case StanzaErrorNotAcceptable: + m_registrationHandler->handleRegistrationResult( iq.from(), RegistrationNotAcceptable ); + break; + case StanzaErrorBadRequest: + m_registrationHandler->handleRegistrationResult( iq.from(), RegistrationBadRequest ); + break; + case StanzaErrorForbidden: + m_registrationHandler->handleRegistrationResult( iq.from(), RegistrationForbidden ); + break; + case StanzaErrorRegistrationRequired: + m_registrationHandler->handleRegistrationResult( iq.from(), RegistrationRequired ); + break; + case StanzaErrorUnexpectedRequest: + m_registrationHandler->handleRegistrationResult( iq.from(), RegistrationUnexpectedRequest ); + break; + case StanzaErrorNotAuthorized: + m_registrationHandler->handleRegistrationResult( iq.from(), RegistrationNotAuthorized ); + break; + case StanzaErrorNotAllowed: + m_registrationHandler->handleRegistrationResult( iq.from(), RegistrationNotAllowed ); + break; + default: + m_registrationHandler->handleRegistrationResult( iq.from(), RegistrationUnknownError ); + break; + + } + } + + } + +} diff --git a/libs/libgloox/registration.h b/libs/libgloox/registration.h new file mode 100644 index 0000000..3fef509 --- /dev/null +++ b/libs/libgloox/registration.h @@ -0,0 +1,339 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef REGISTRATION_H__ +#define REGISTRATION_H__ + +#include "iqhandler.h" +#include "registrationhandler.h" +#include "dataform.h" +#include "jid.h" +#include "oob.h" + +#include +#include + +namespace gloox +{ + + class ClientBase; + class Stanza; + + /** + * Holds all the possible fields a server may require for registration according + * to Section 14.1, XEP-0077. + */ + struct RegistrationFields + { + std::string username; /**< Desired username. */ + std::string nick; /**< User's nickname. */ + std::string password; /**< User's password. */ + std::string name; /**< User's name. */ + std::string first; /**< User's first name.*/ + std::string last; /**< User's last name. */ + std::string email; /**< User's email address. */ + std::string address; /**< User's address. */ + std::string city; /**< User's city. */ + std::string state; /**< User's state. */ + std::string zip; /**< User's ZIP code. */ + std::string phone; /**< User's phone number. */ + std::string url; /**< User's homepage URL (or other URL). */ + std::string date; /**< Date (?) */ + std::string misc; /**< Misc (?) */ + std::string text; /**< Text (?)*/ + }; + + /** + * @brief This class is an implementation of XEP-0077 (In-Band Registration). + * + * Derive your object from @ref RegistrationHandler and implement the + * virtual functions offered by that interface. Then use it like this: + * @code + * void MyClass::myFunc() + * { + * m_client = new Client( "example.org" ); + * m_client->disableRoster(); // a roster is not necessary for registration + * m_client->registerConnectionListener( this ); + * + * m_reg = new Registration( c ); + * m_reg->registerRegistrationHandler( this ); + * + * m_client->connect(); + * } + * + * void MyClass::onConnect() + * { + * m_reg->fetchRegistrationFields(); + * } + * @endcode + * + * In RegistrationHandler::handleRegistrationFields() you should check which information the server + * requires to open a new account. You might not always get away with just username and password. + * Then call createAccount() with a filled-in RegistrationFields and an @c int representing the bit-wise + * ORed fields you want to have included in the registration attempt. For your convenience you can + * use the 'fields' argument of handleRegistrationFields(). ;) It's your responsibility to make + * sure at least those fields the server requested are filled in. + * + * Check @c tests/register_test.cpp for an example. + * + * @author Jakob Schroeter + * @since 0.2 + */ + class GLOOX_API Registration : public IqHandler + { + public: + + /** + * The possible fields of a XEP-0077 account registration. + */ + enum fieldEnum + { + FieldUsername = 1, + FieldNick = 2, + FieldPassword = 4, + FieldName = 8, + FieldFirst = 16, + FieldLast = 32, + FieldEmail = 64, + FieldAddress = 128, + FieldCity = 256, + FieldState = 512, + FieldZip = 1024, + FieldPhone = 2048, + FieldUrl = 4096, + FieldDate = 8192, + FieldMisc = 16384, + FieldText = 32768 + }; + + /** + * @brief A wrapping class for the XEP-0077 <query> element. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class Query : public StanzaExtension + { + public: + /** + * Creates a new object that can be used to carry out a registration. + * @param form A DataForm containing the registration terms. + */ + Query( DataForm* form ); + + /** + * Creates a new object that can be used to carry out a registration. + * @param del Whether or not to remove the account. + */ + Query( bool del = false ); + + /** + * Creates a new object that can be used to carry out a registration. + * @param fields Bit-wise ORed fieldEnum values describing the valid (i.e., set) + * fields in the @b values parameter. + * @param values Contains the registration fields. + */ + Query( int fields, const RegistrationFields& values ); + + /** + * Creates a new object from the given Tag. + * @param tag The Tag to parse. + */ + Query( const Tag* tag ); + + /** + * Virtual Destructor. + */ + virtual ~Query(); + + /** + * Returns the contained registration form, if any. + * @return The registration form. May be 0. + */ + const DataForm* form() const { return m_form; } + + /** + * Returns the registration instructions, if given + * @return The registration instructions. + */ + const std::string& instructions() const { return m_instructions; } + + /** + * Returns the registration fields, if set. + * @return The registration fields. + */ + int fields() const { return m_fields; } + + /** + * + */ + const RegistrationFields& values() const { return m_values; } + + /** + * Indicates whether the account is already registered. + * @return @b True if the <registered> element is present, @b false otherwise. + */ + bool registered() const { return m_reg; } + + /** + * Indicates whether the account shall be removed. + * @return @b True if the <remove> element is present, @b false otherwise. + */ + bool remove() const { return m_del; } + + /** + * Returns an optional OOB object. + * @return A pointer to an OOB object, if present, 0 otherwise. + */ + const OOB* oob() const { return m_oob; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Query( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + Query* q = new Query(); + q->m_form = m_form ? new DataForm( *m_form ) : 0; + q->m_fields = m_fields; + q->m_values = m_values; + q->m_instructions = m_instructions; + q->m_oob = new OOB( *m_oob ); + q->m_del = m_del; + q->m_reg = m_reg; + return q; + } + + private: + DataForm* m_form; + int m_fields; + RegistrationFields m_values; + std::string m_instructions; + OOB* m_oob; + bool m_del; + bool m_reg; + }; + + /** + * Constructor. + * @param parent The ClientBase which is used for establishing a connection. + * @param to The server or service to authenticate with. If empty the currently connected + * server will be used. + */ + Registration( ClientBase* parent, const JID& to ); + + /** + * Constructor. Registration will be attempted with the ClientBase's connected host. + * @param parent The ClientBase which is used for establishing a connection. + */ + Registration( ClientBase* parent ); + + /** + * Virtual destructor. + */ + virtual ~Registration(); + + /** + * Use this function to request the registration fields the server requires. + * The required fields are returned asynchronously to the object registered as + * @ref RegistrationHandler by calling @ref RegistrationHandler::handleRegistrationFields(). + */ + void fetchRegistrationFields(); + + /** + * Attempts to register an account with the given credentials. Only the fields OR'ed in + * @c fields will be sent. This can only be called with an unauthenticated parent (@ref Client). + * @note It is recommended to use @ref fetchRegistrationFields to find out which fields the + * server requires. + * @param fields The fields to use to generate the registration request. OR'ed + * @ref fieldEnum values. + * @param values The struct contains the values which shall be used for the registration. + * @return Returns @b true if the registration request was sent successfully, @b false + * otherwise. In that case either there's no connected ClientBase available, or + * prepping of the username failed (i.e. the username is not valid for use in XMPP). + */ + bool createAccount( int fields, const RegistrationFields& values ); + + /** + * Attempts to register an account with the given credentials. This can only be called with an + * unauthenticated parent (@ref Client). + * @note According to XEP-0077, if the server sends both old-style fields and data form, + * implementations SHOULD prefer data forms. + * @param form The DataForm containing the registration credentials. + */ + void createAccount( DataForm* form ); + + /** + * Tells the server to remove the currently authenticated account from the server. + */ + void removeAccount(); + + /** + * Tells the server to change the password for the current account. + * @param username The username to change the password for. You might want to use + * Client::username() to get the current prepped username. + * @param password The new password. + */ + void changePassword( const std::string& username, const std::string& password ); + + /** + * Registers the given @c rh as RegistrationHandler. Only one handler is possible at a time. + * @param rh The RegistrationHandler to register. + */ + void registerRegistrationHandler( RegistrationHandler* rh ); + + /** + * Un-registers the current RegistrationHandler. + */ + void removeRegistrationHandler(); + + // reimplemented from IqHandler. + virtual bool handleIq( const IQ& iq ) { (void)iq; return false; } + + // reimplemented from IqHandler. + virtual void handleIqID( const IQ& iq, int context ); + + private: +#ifdef REGISTRATION_TEST + public: +#endif + + enum IdType + { + FetchRegistrationFields, + CreateAccount, + RemoveAccount, + ChangePassword + }; + + Registration operator=( const Registration& ); + + void init(); + + ClientBase* m_parent; + const JID m_to; + RegistrationHandler* m_registrationHandler; + }; + +} + +#endif // REGISTRATION_H__ diff --git a/libs/libgloox/registrationhandler.h b/libs/libgloox/registrationhandler.h new file mode 100644 index 0000000..f7422ad --- /dev/null +++ b/libs/libgloox/registrationhandler.h @@ -0,0 +1,128 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef REGISTRATIONHANDLER_H__ +#define REGISTRATIONHANDLER_H__ + +#include "oob.h" + +#include + +namespace gloox +{ + + class OOB; + class JID; + class DataForm; + + /** + * Possible results of a XEP-0077 operation. + */ + enum RegistrationResult + { + RegistrationSuccess = 0, /**< The last operation (account registration, account + * deletion or password change) was successful. */ + RegistrationNotAcceptable, /**< 406: Not all necessary information provided */ + RegistrationConflict, /**< 409: Username alreday exists. */ + RegistrationNotAuthorized, /**< Account removal: Unregistered entity waits too long + * before authentication or performs tasks other than + * authentication after registration.
+ * Password change: The server or service does not consider + * the channel safe enough to enable a password change. */ + RegistrationBadRequest, /**< Account removal: The <remove/> element was not + * the only child element of the <query/> element. + * Should not happen when only gloox functions are being + * used.
+ * Password change: The password change request does not + * contain complete information (both <username/> and + * <password/> are required). */ + RegistrationForbidden, /**< Account removal: The sender does not have sufficient + * permissions to cancel the registration. */ + RegistrationRequired, /**< Account removal: The entity sending the remove + * request was not previously registered. */ + RegistrationUnexpectedRequest, /**< Account removal: The host is an instant messaging + * server and the IQ get does not contain a 'from' + * address because the entity is not registered with + * the server.
+ * Password change: The host is an instant messaging + * server and the IQ set does not contain a 'from' + * address because the entity is not registered with + * the server. */ + RegistrationNotAllowed, /**< Password change: The server or service does not allow + * password changes. */ + RegistrationUnknownError /**< An unknown error condition occured. */ + }; + + /** + * @brief A virtual interface that receives events from an Registration object. + * + * Derived classes can be registered as RegistrationHandlers with an + * Registration object. Incoming results for operations initiated through + * the Registration object are forwarded to this handler. + * + * @author Jakob Schroeter + * @since 0.2 + */ + class GLOOX_API RegistrationHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~RegistrationHandler() {} + + /** + * Reimplement this function to receive results of the + * @ref Registration::fetchRegistrationFields() function. + * @param from The server or service the registration fields came from. + * @param fields The OR'ed fields the server requires. From @ref Registration::fieldEnum. + * @param instructions Any additional information the server sends along. + */ + virtual void handleRegistrationFields( const JID& from, int fields, + std::string instructions ) = 0; + + /** + * This function is called if @ref Registration::createAccount() was called on an authenticated + * stream and the server lets us know about this. + */ + virtual void handleAlreadyRegistered( const JID& from ) = 0; + + /** + * This funtion is called to notify about the result of an operation. + * @param from The server or service the result came from. + * @param regResult The result of the last operation. + */ + virtual void handleRegistrationResult( const JID& from, RegistrationResult regResult ) = 0; + + /** + * This function is called additionally to @ref handleRegistrationFields() if the server + * supplied a data form together with legacy registration fields. + * @param from The server or service the data form came from. + * @param form The DataForm containing registration information. + */ + virtual void handleDataForm( const JID& from, const DataForm& form ) = 0; + + /** + * This function is called if the server does not offer in-band registration + * but wants to refer the user to an external URL. + * @param from The server or service the referal came from. + * @param oob The OOB object describing the external URL. + */ + virtual void handleOOB( const JID& from, const OOB& oob ) = 0; + + }; + +} + +#endif // REGISTRATIONHANDLER_H__ diff --git a/libs/libgloox/resource.h b/libs/libgloox/resource.h new file mode 100644 index 0000000..c209d25 --- /dev/null +++ b/libs/libgloox/resource.h @@ -0,0 +1,106 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef RESOURCE_H__ +#define RESOURCE_H__ + +#include "presence.h" +#include "util.h" + +#include + +namespace gloox +{ + + class Presence; + + /** + * @brief Holds resource attributes. + * + * This holds the information of a single resource of a contact that is online. + * + * @author Jakob Schroeter + * @since 0.8 + */ + class GLOOX_API Resource + { + + friend class RosterItem; + + public: + /** + * Constructor. + * @param priority The resource's priority. + * @param msg The resource's status message. + * @param presence The resource's presence status. + */ + Resource( int priority, const std::string& msg, Presence::PresenceType presence ) + : m_priority( priority ), m_message( msg ), m_presence( presence ) {} + + /** + * Virtual destrcutor. + */ + virtual ~Resource() + { + util::clearList( m_extensions ); + } + + /** + * Lets you fetch the resource's priority. + * @return The resource's priority. + */ + int priority() const { return m_priority; } + + /** + * Lets you fetch the resource's status message. + * @return The resource's status message. + */ + const std::string& message() const { return m_message; } + + /** + * Lets you fetch the resource's last presence. + * @return The resource's presence status. + */ + Presence::PresenceType presence() const { return m_presence; } + + /** + * Returns the StanzaExtensions that were sent with the last presence stanza + * by the resource. + * @return A list of stanza extensions. + */ + const StanzaExtensionList& extensions() const { return m_extensions; } + + private: + void setPriority( int priority ) { m_priority = priority; } + void setMessage( std::string message ) { m_message = message; } + void setStatus( Presence::PresenceType presence ) { m_presence = presence; } + void setExtensions( const StanzaExtensionList& exts ) + { + StanzaExtensionList::const_iterator it = exts.begin(); + for( ; it != exts.end(); ++it ) + { + m_extensions.push_back( (*it)->clone() ); + } + } + + int m_priority; + std::string m_message; + std::string m_name; + Presence::PresenceType m_presence; + StanzaExtensionList m_extensions; + + }; + +} + +#endif // RESOURCE_H__ diff --git a/libs/libgloox/rosteritem.cpp b/libs/libgloox/rosteritem.cpp new file mode 100644 index 0000000..bfd0e8a --- /dev/null +++ b/libs/libgloox/rosteritem.cpp @@ -0,0 +1,177 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "rosteritem.h" +#include "rosteritemdata.h" +#include "util.h" + +namespace gloox +{ + + RosterItem::RosterItem( const std::string& jid, const std::string& name ) + : m_data( new RosterItemData( jid, name, StringList() ) ) + { + } + + RosterItem::RosterItem( const RosterItemData& data ) + : m_data( new RosterItemData( data ) ) + { + } + + RosterItem::~RosterItem() + { + delete m_data; + util::clearMap( m_resources ); + } + + void RosterItem::setName( const std::string& name ) + { + if( m_data ) + m_data->setName( name ); + } + + const std::string& RosterItem::name() const + { + if( m_data ) + return m_data->name(); + else + return EmptyString; + } + + const std::string& RosterItem::jid() const + { + if( m_data ) + return m_data->jid(); + else + return EmptyString; + } + + void RosterItem::setSubscription( const std::string& subscription, const std::string& ask ) + { + if( m_data ) + m_data->setSubscription( subscription, ask ); + } + + SubscriptionType RosterItem::subscription() const + { + if( m_data ) + return m_data->subscription(); + else + return S10nNone; + } + + void RosterItem::setGroups( const StringList& groups ) + { + if( m_data ) + m_data->setGroups( groups ); + } + + const StringList RosterItem::groups() const + { + if( m_data ) + return m_data->groups(); + else + return StringList(); + } + + bool RosterItem::changed() const + { + if( m_data ) + return m_data->changed(); + else + return false; + } + + void RosterItem::setSynchronized() + { + if( m_data ) + m_data->setSynchronized(); + } + + void RosterItem::setPresence( const std::string& resource, Presence::PresenceType presence ) + { + if( m_resources.find( resource ) == m_resources.end() ) + m_resources[resource] = new Resource( 0, EmptyString, presence ); + else + m_resources[resource]->setStatus( presence ); + } + + void RosterItem::setStatus( const std::string& resource, const std::string& msg ) + { + if( m_resources.find( resource ) == m_resources.end() ) + m_resources[resource] = new Resource( 0, msg, Presence::Unavailable ); + else + m_resources[resource]->setMessage( msg ); + } + + void RosterItem::setPriority( const std::string& resource, int priority ) + { + if( m_resources.find( resource ) == m_resources.end() ) + m_resources[resource] = new Resource( priority, EmptyString, Presence::Unavailable ); + else + m_resources[resource]->setPriority( priority ); + } + + const Resource* RosterItem::highestResource() const + { + int highestPriority = -255; + Resource* highestResource = 0; + ResourceMap::const_iterator it = m_resources.begin(); + for( ; it != m_resources.end() ; ++it ) + { + if( (*it).second->priority() > highestPriority ) + { + highestPriority = (*it).second->priority(); + highestResource = (*it).second; + } + } + return highestResource; + } + + void RosterItem::setExtensions( const std::string& resource, const StanzaExtensionList& exts ) + { + if( m_resources.find( resource ) == m_resources.end() ) + m_resources[resource] = new Resource( 0, EmptyString, Presence::Unavailable ); + + m_resources[resource]->setExtensions( exts ); + } + + void RosterItem::removeResource( const std::string& resource ) + { + ResourceMap::iterator it = m_resources.find( resource ); + if( it != m_resources.end() ) + { + delete (*it).second; + m_resources.erase( it ); + } + } + + bool RosterItem::online() const + { + return !m_resources.empty(); + } + + const Resource* RosterItem::resource( const std::string& res ) const + { + ResourceMap::const_iterator it = m_resources.find( res ); + return it != m_resources.end() ? (*it).second : 0; + } + + void RosterItem::setData( const RosterItemData& rid ) + { + delete m_data; + m_data = new RosterItemData( rid ); + } + +} diff --git a/libs/libgloox/rosteritem.h b/libs/libgloox/rosteritem.h new file mode 100644 index 0000000..27597c3 --- /dev/null +++ b/libs/libgloox/rosteritem.h @@ -0,0 +1,196 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef ROSTERITEM_H__ +#define ROSTERITEM_H__ + +#include "gloox.h" +#include "resource.h" +#include "presence.h" + +#include +#include + + +namespace gloox +{ + + class RosterItemData; + + /** + * @brief An abstraction of a roster item. + * + * For each RosterItem all resources that are available (online in some way) are stored in + * a ResourceMap. This map is accessible using the resources() function. + * + * @author Jakob Schroeter + * @since 0.3 + */ + class GLOOX_API RosterItem + { + friend class RosterManager; + + public: + /** + * A list of resources for the given JID. + */ + typedef std::map ResourceMap; + + /** + * Constructs a new item of the roster. + * @param jid The JID of the contact. + * @param name The displayed name of the contact. + */ + RosterItem( const std::string& jid, const std::string& name = EmptyString ); + + /** + * Constructs a new RosterItem using the data holding class. + * @param data The RosterItemData to construct the item from. The new + * item will own the data object. + */ + RosterItem( const RosterItemData& data ); + + /** + * Virtual destructor. + */ + virtual ~RosterItem(); + + /** + * Sets the displayed name of a contact/roster item. + * @param name The contact's new name. + */ + void setName( const std::string& name ); + + /** + * Retrieves the displayed name of a contact/roster item. + * @return The contact's name. + */ + const std::string& name() const; + + /** + * Returns the contact's bare JID. + * @return The contact's bare JID. + */ + const std::string& jid() const; + + /** + * Sets the current subscription status of the contact. + * @param subscription The current subscription. + * @param ask Whether a subscription request is pending. + */ + void setSubscription( const std::string& subscription, const std::string& ask ); + + /** + * Returns the current subscription type between the remote and the local entity. + * @return The subscription type. + */ + SubscriptionType subscription() const; + + /** + * Sets the groups this RosterItem belongs to. + * @param groups The groups to set for this item. + */ + void setGroups( const StringList& groups ); + + /** + * Returns the groups this RosterItem belongs to. + * @return The groups this item belongs to. + */ + const StringList groups() const; + + /** + * Whether the item has unsynchronized changes. + * @return @b True if the item has unsynchronized changes, @b false otherwise. + */ + bool changed() const; + + /** + * Indicates whether this item has at least one resource online (in any state). + * @return @b True if at least one resource is online, @b false otherwise. + */ + bool online() const; + + /** + * Returns the contact's resources. + * @return The contact's resources. + */ + const ResourceMap& resources() const { return m_resources; } + + /** + * Returns the Resource for a specific resource string. + * @param res The resource string. + * @return The Resource if found, 0 otherwise. + */ + const Resource* resource( const std::string& res ) const; + + /** + * Returns the Resource with the highest priority. + * @return The Resource with the highest priority. + */ + const Resource* highestResource() const; + + protected: + /** + * Sets the current presence of the resource. + * @param resource The resource to set the presence for. + * @param presence The current presence. + */ + void setPresence( const std::string& resource, Presence::PresenceType presence ); + + /** + * Sets the current status message of the resource. + * @param resource The resource to set the status message for. + * @param msg The current status message, i.e. from the presence info. + */ + void setStatus( const std::string& resource, const std::string& msg ); + + /** + * Sets the current priority of the resource. + * @param resource The resource to set the status message for. + * @param priority The resource's priority, i.e. from the presence info. + */ + void setPriority( const std::string& resource, int priority ); + + /** + * Sets the resource's presence extensions. + * @param resource The resource to set the extensions for. + * @param exts The extensions to set. + */ + void setExtensions( const std::string& resource, const StanzaExtensionList& exts ); + + /** + * Removes the 'changed' flag from the item. + */ + void setSynchronized(); + + /** + * This function is called to remove subsequent resources from a RosterItem. + * @param resource The resource to remove. + */ + void removeResource( const std::string& resource ); + + /** + * This function deletes the internal RosterItemData and replaces it with the provided + * one. The RosterItem will own the RosterItemData instance. + */ + void setData( const RosterItemData& rid ); + + private: + RosterItemData* m_data; + ResourceMap m_resources; + + }; + +} + +#endif // ROSTERITEM_H__ diff --git a/libs/libgloox/rosteritemdata.h b/libs/libgloox/rosteritemdata.h new file mode 100644 index 0000000..cd5e9a6 --- /dev/null +++ b/libs/libgloox/rosteritemdata.h @@ -0,0 +1,190 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef ROSTERITEMBASE_H__ +#define ROSTERITEMBASE_H__ + +#include "gloox.h" +#include "jid.h" +#include "tag.h" + +#include +#include + + +namespace gloox +{ + + /** + * @brief A class holding roster item data. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API RosterItemData + { + + public: + /** + * Constructs a new item of the roster. + * @param jid The JID of the contact. + * @param name The displayed name of the contact. + * @param groups A list of groups the contact belongs to. + */ + RosterItemData( const std::string& jid, const std::string& name, + const StringList& groups ) + : m_jid( jid ), m_name( name ), m_groups( groups ), + m_subscription( S10nNone ), m_changed( false ), m_remove( false ) + {} + + /** + * Constructs a new item of the roster, scheduled for removal. + * @param jid The JID of the contact to remove. + */ + RosterItemData( const std::string& jid ) + : m_jid( jid ), m_subscription( S10nNone ), m_changed( false ), + m_remove( true ) + {} + + /** + * Virtual destructor. + */ + virtual ~RosterItemData() {} + + /** + * Returns the contact's bare JID. + * @return The contact's bare JID. + */ + const std::string& jid() const { return m_jid; } + + /** + * Sets the displayed name of a contact/roster item. + * @param name The contact's new name. + */ + void setName( const std::string& name ) + { + m_name = name; + m_changed = true; + } + + /** + * Retrieves the displayed name of a contact/roster item. + * @return The contact's name. + */ + const std::string& name() const { return m_name; } + + /** + * Sets the current subscription status of the contact. + * @param subscription The current subscription. + * @param ask Whether a subscription request is pending. + */ + void setSubscription( const std::string& subscription, const std::string& ask ) + { + m_sub = subscription; + m_ask = ask; + + if( subscription == "from" && ask.empty() ) + m_subscription = S10nFrom; + else if( subscription == "from" && !ask.empty() ) + m_subscription = S10nFromOut; + else if( subscription == "to" && ask.empty() ) + m_subscription = S10nTo; + else if( subscription == "to" && !ask.empty() ) + m_subscription = S10nToIn; + else if( subscription == "none" && ask.empty() ) + m_subscription = S10nNone; + else if( subscription == "none" && !ask.empty() ) + m_subscription = S10nNoneOut; + else if( subscription == "both" ) + m_subscription = S10nBoth; + } + + /** + * Returns the current subscription type between the remote and the local entity. + * @return The subscription type. + */ + SubscriptionType subscription() const { return m_subscription; } + + /** + * Sets the groups this RosterItem belongs to. + * @param groups The groups to set for this item. + */ + void setGroups( const StringList& groups ) + { + m_groups = groups; + m_changed = true; + } + + /** + * Returns the groups this RosterItem belongs to. + * @return The groups this item belongs to. + */ + const StringList& groups() const { return m_groups; } + + /** + * Whether the item has unsynchronized changes. + * @return @b True if the item has unsynchronized changes, @b false otherwise. + */ + bool changed() const { return m_changed; } + + /** + * Whether the item is scheduled for removal. + * @return @b True if the item is subject to a removal or scheduled for removal, @b false + * otherwise. + */ + bool remove() const { return m_remove; } + + /** + * Removes the 'changed' flag from the item. + */ + void setSynchronized() { m_changed = false; } + + /** + * Retruns a Tag representation of the roster item data. + * @return A Tag representation. + */ + Tag* tag() const + { + Tag* i = new Tag( "item" ); + i->addAttribute( "jid", m_jid ); + if( m_remove ) + i->addAttribute( "subscription", "remove" ); + else + { + i->addAttribute( "name", m_name ); + StringList::const_iterator it = m_groups.begin(); + for( ; it != m_groups.end(); ++it ) + new Tag( i, "group", (*it) ); + i->addAttribute( "subscription", m_sub ); + i->addAttribute( "ask", m_ask ); + } + return i; + } + + protected: + std::string m_jid; + std::string m_name; + StringList m_groups; + SubscriptionType m_subscription; + std::string m_sub; + std::string m_ask; + bool m_changed; + bool m_remove; + + }; + +} + +#endif // ROSTERITEMBASE_H__ diff --git a/libs/libgloox/rosterlistener.h b/libs/libgloox/rosterlistener.h new file mode 100644 index 0000000..ce93777 --- /dev/null +++ b/libs/libgloox/rosterlistener.h @@ -0,0 +1,173 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef ROSTERLISTENER_H__ +#define ROSTERLISTENER_H__ + +#include "rosteritem.h" + +#include +#include + +namespace gloox +{ + + class IQ; + class Presence; + + /** + * A map of JID/RosterItem pairs. + */ + typedef std::map Roster; + + /** + * @brief A virtual interface which can be reimplemented to receive roster updates. + * + * A class implementing this interface and being registered as RosterListener with the + * RosterManager object receives notifications about all the changes in the server-side + * roster. Only one RosterListener per Roster at a time is possible. + * + * @author Jakob Schroeter + * @since 0.3 + */ + class GLOOX_API RosterListener + { + public: + /** + * Virtual Destructor. + */ + virtual ~RosterListener() {} + + /** + * Reimplement this function if you want to be notified about new items + * on the server-side roster (items subject to a so-called Roster Push). + * This function will be called regardless who added the item, either this + * resource or another. However, it will not be called for JIDs for which + * presence is received without them being on the roster. + * @param jid The new item's full address. + */ + virtual void handleItemAdded( const JID& jid ) = 0; + + /** + * Reimplement this function if you want to be notified about items + * which authorised subscription. + * @param jid The authorising item's full address. + */ + virtual void handleItemSubscribed( const JID& jid ) = 0; + + /** + * Reimplement this function if you want to be notified about items that + * were removed from the server-side roster (items subject to a so-called Roster Push). + * This function will be called regardless who deleted the item, either this resource or + * another. + * @param jid The removed item's full address. + */ + virtual void handleItemRemoved( const JID& jid ) = 0; + + /** + * Reimplement this function if you want to be notified about items that + * were modified on the server-side roster (items subject to a so-called Roster Push). + * A roster push is initiated if a second resource of this JID modifies an item stored on the + * server-side contact list. This can include modifying the item's name, its groups, or the + * subscription status. These changes are pushed by the server to @b all connected resources. + * This is why this function will be called if you modify a roster item locally and synchronize + * it with the server. + * @param jid The modified item's full address. + */ + virtual void handleItemUpdated( const JID& jid ) = 0; + + /** + * Reimplement this function if you want to be notified about items which + * removed subscription authorization. + * @param jid The item's full address. + */ + virtual void handleItemUnsubscribed( const JID& jid ) = 0; + + /** + * Reimplement this function if you want to receive the whole server-side roster + * on the initial roster push. After successful authentication, RosterManager asks the + * server for the full server-side roster. Invocation of this method announces its arrival. + * Roster item status is set to 'unavailable' until incoming presence info updates it. A full + * roster push only happens once per connection. + * @param roster The full roster. + */ + virtual void handleRoster( const Roster& roster ) = 0; + + /** + * This function is called on every status change of an item in the roster. + * If the presence is of type Unavailable, then the resource has already been + * removed from the RosterItem. + * @param item The roster item. + * @param resource The resource that changed presence. + * @param presence The item's new presence. + * @param msg The status change message. + * @since 0.9 + */ + virtual void handleRosterPresence( const RosterItem& item, const std::string& resource, + Presence::PresenceType presence, const std::string& msg ) = 0; + + /** + * This function is called on every status change of a JID that matches the Client's + * own JID. + * If the presence is of type Unavailable, then the resource has already been + * removed from the RosterItem. + * @param item The self item. + * @param resource The resource that changed presence. + * @param presence The item's new presence. + * @param msg The status change message. + * @since 0.9 + */ + virtual void handleSelfPresence( const RosterItem& item, const std::string& resource, + Presence::PresenceType presence, const std::string& msg ) = 0; + + /** + * This function is called when an entity wishes to subscribe to this entity's presence. + * If the handler is registered as a asynchronous handler for subscription requests, + * the return value of this function is ignored. In this case you should use + * RosterManager::ackSubscriptionRequest() to answer the request. + * @param jid The requesting item's address. + * @param msg A message sent along with the request. + * @return Return @b true to allow subscription and subscribe to the remote entity's + * presence, @b false to ignore the request. + */ + virtual bool handleSubscriptionRequest( const JID& jid, const std::string& msg ) = 0; + + /** + * This function is called when an entity unsubscribes from this entity's presence. + * If the handler is registered as a asynchronous handler for subscription requests, + * the return value of this function is ignored. In this case you should use + * RosterManager::unsubscribe() if you want to unsubscribe yourself from the contct's + * presence and to remove the contact from the roster. + * @param jid The item's address. + * @param msg A message sent along with the request. + * @return Return @b true to unsubscribe from the remote entity, @b false to ignore. + */ + virtual bool handleUnsubscriptionRequest( const JID& jid, const std::string& msg ) = 0; + + /** + * This function is called whenever presence from an entity is received which is not in + * the roster. + * @param presence The full presence stanza. + */ + virtual void handleNonrosterPresence( const Presence& presence ) = 0; + + /** + * This function is called if the server returned an error. + * @param iq The error stanza. + */ + virtual void handleRosterError( const IQ& iq ) = 0; + }; + +} + +#endif // ROSTERLISTENER_H__ diff --git a/libs/libgloox/rostermanager.cpp b/libs/libgloox/rostermanager.cpp new file mode 100644 index 0000000..207d11e --- /dev/null +++ b/libs/libgloox/rostermanager.cpp @@ -0,0 +1,421 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "clientbase.h" +#include "rostermanager.h" +#include "disco.h" +#include "rosteritem.h" +#include "rosteritemdata.h" +#include "rosterlistener.h" +#include "privatexml.h" +#include "util.h" +#include "stanzaextension.h" +#include "capabilities.h" + + +namespace gloox +{ + + // ---- RosterManager::Query ---- + RosterManager::Query::Query( const JID& jid, const std::string& name, const StringList& groups ) + : StanzaExtension( ExtRoster ) + { + m_roster.push_back( new RosterItemData( jid.bare(), name, groups ) ); + } + + RosterManager::Query::Query( const JID& jid ) + : StanzaExtension( ExtRoster ) + { + m_roster.push_back( new RosterItemData( jid.bare() ) ); + } + + RosterManager::Query::Query( const Tag* tag ) + : StanzaExtension( ExtRoster ) + { + if( !tag || tag->name() != "query" || tag->xmlns() != XMLNS_ROSTER ) + return; + + const ConstTagList& l = tag->findTagList( "query/item" ); + ConstTagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + StringList groups; + const ConstTagList& g = (*it)->findTagList( "item/group" ); + ConstTagList::const_iterator it_g = g.begin(); + for( ; it_g != g.end(); ++it_g ) + groups.push_back( (*it_g)->cdata() ); + + const std::string sub = (*it)->findAttribute( "subscription" ); + if( sub == "remove" ) + m_roster.push_back( new RosterItemData( (*it)->findAttribute( "jid" ) ) ); + else + { + RosterItemData* rid = new RosterItemData( (*it)->findAttribute( "jid" ), + (*it)->findAttribute( "name" ), + groups ); + rid->setSubscription( sub, (*it)->findAttribute( "ask" ) ); + m_roster.push_back( rid ); + } + } + } + + RosterManager::Query::~Query() + { + util::clearList( m_roster ); + } + + const std::string& RosterManager::Query::filterString() const + { + static const std::string filter = "/iq/query[@xmlns='" + XMLNS_ROSTER + "']"; + return filter; + } + + Tag* RosterManager::Query::tag() const + { + Tag* t = new Tag( "query" ); + t->setXmlns( XMLNS_ROSTER ); + + RosterData::const_iterator it = m_roster.begin(); + for( ; it != m_roster.end(); ++it ) + t->addChild( (*it)->tag() ); + + return t; + } + + StanzaExtension* RosterManager::Query::clone() const + { + Query* q = new Query(); + RosterData::const_iterator it = m_roster.begin(); + for( ; it != m_roster.end(); ++it ) + { + q->m_roster.push_back( new RosterItemData( *(*it) ) ); + } + return q; + } + // ---- ~RosterManager::Query ---- + + // ---- RosterManager ---- + RosterManager::RosterManager( ClientBase* parent ) + : m_rosterListener( 0 ), m_parent( parent ), m_privateXML( 0 ), + m_syncSubscribeReq( false ) + { + if( m_parent ) + { + m_parent->registerIqHandler( this, ExtRoster ); + m_parent->registerPresenceHandler( this ); + m_parent->registerSubscriptionHandler( this ); + m_parent->registerStanzaExtension( new Query() ); + + m_self = new RosterItem( m_parent->jid().bare() ); + m_privateXML = new PrivateXML( m_parent ); + } + } + + RosterManager::~RosterManager() + { + if( m_parent ) + { + m_parent->removeIqHandler( this, ExtRoster ); + m_parent->removeIDHandler( this ); + m_parent->removePresenceHandler( this ); + m_parent->removeSubscriptionHandler( this ); + m_parent->removeStanzaExtension( ExtRoster ); + delete m_self; + delete m_privateXML; + } + + util::clearMap( m_roster ); + } + + Roster* RosterManager::roster() + { + return &m_roster; + } + + void RosterManager::fill() + { + if( !m_parent ) + return; + + util::clearMap( m_roster ); + m_privateXML->requestXML( "roster", XMLNS_ROSTER_DELIMITER, this ); + IQ iq( IQ::Get, JID(), m_parent->getID() ); + iq.addExtension( new Query() ); + m_parent->send( iq, this, RequestRoster ); + } + + bool RosterManager::handleIq( const IQ& iq ) + { + if( iq.subtype() != IQ::Set ) // FIXME add checks for 'from' attribute (empty or bare self jid?) + return false; + + // single roster item push + const Query* q = iq.findExtension( ExtRoster ); + if( q && q->roster().size() ) + mergePush( q->roster() ); + + IQ re( IQ::Result, JID(), iq.id() ); + m_parent->send( re ); + return true; + } + + void RosterManager::handleIqID( const IQ& iq, int context ) + { + if( iq.subtype() == IQ::Result ) // initial roster + { + const Query* q = iq.findExtension( ExtRoster ); + if( q ) + mergeRoster( q->roster() ); + + if( context == RequestRoster ) + { + if( m_parent ) + m_parent->rosterFilled(); + + if( m_rosterListener ) + m_rosterListener->handleRoster( m_roster ); + } + } + else if( iq.subtype() == IQ::Error ) + { + if( context == RequestRoster && m_parent ) + m_parent->rosterFilled(); + + if( m_rosterListener ) + m_rosterListener->handleRosterError( iq ); + } + } + + void RosterManager::handlePresence( const Presence& presence ) + { + if( presence.subtype() == Presence::Error ) + return; + + bool self = false; + Roster::iterator it = m_roster.find( presence.from().bare() ); + if( it != m_roster.end() || ( self = ( presence.from().bare() == m_self->jid() ) ) ) + { + RosterItem* ri = self ? m_self : (*it).second; + const std::string& resource = presence.from().resource(); + + if( presence.presence() == Presence::Unavailable ) + ri->removeResource( resource ); + else + { + ri->setPresence( resource, presence.presence() ); + ri->setStatus( resource, presence.status() ); + ri->setPriority( resource, presence.priority() ); + ri->setExtensions( resource, presence.extensions() ); + } + + if( m_rosterListener && !self ) + m_rosterListener->handleRosterPresence( *ri, resource, + presence.presence(), presence.status() ); + else if( m_rosterListener && self ) + m_rosterListener->handleSelfPresence( *ri, resource, + presence.presence(), presence.status() ); + } + else + { + if( m_rosterListener ) + m_rosterListener->handleNonrosterPresence( presence ); + } + } + + void RosterManager::subscribe( const JID& jid, const std::string& name, + const StringList& groups, const std::string& msg ) + { + if( !jid ) + return; + + add( jid, name, groups ); + + Subscription s( Subscription::Subscribe, jid.bareJID(), msg ); + m_parent->send( s ); + } + + + void RosterManager::add( const JID& jid, const std::string& name, const StringList& groups ) + { + if( !jid ) + return; + + IQ iq( IQ::Set, JID(), m_parent->getID() ); + iq.addExtension( new Query( jid, name, groups) ); + + m_parent->send( iq, this, AddRosterItem ); + } + + void RosterManager::unsubscribe( const JID& jid, const std::string& msg ) + { + Subscription p( Subscription::Unsubscribe, jid.bareJID(), msg ); + m_parent->send( p ); + } + + void RosterManager::cancel( const JID& jid, const std::string& msg ) + { + Subscription p( Subscription::Unsubscribed, jid.bareJID(), msg ); + m_parent->send( p ); + } + + void RosterManager::remove( const JID& jid ) + { + if( !jid ) + return; + + IQ iq( IQ::Set, JID(), m_parent->getID() ); + iq.addExtension( new Query( jid ) ); + + m_parent->send( iq, this, RemoveRosterItem ); + } + + void RosterManager::synchronize() + { + Roster::const_iterator it = m_roster.begin(); + for( ; it != m_roster.end(); ++it ) + { + if( !(*it).second->changed() ) + continue; + + IQ iq( IQ::Set, JID(), m_parent->getID() ); + iq.addExtension( new Query( (*it).second->jid(), (*it).second->name(), (*it).second->groups() ) ); + m_parent->send( iq, this, SynchronizeRoster ); + } + } + + void RosterManager::ackSubscriptionRequest( const JID& to, bool ack ) + { + Subscription p( ack ? Subscription::Subscribed + : Subscription::Unsubscribed, to.bareJID() ); + m_parent->send( p ); + } + + void RosterManager::handleSubscription( const Subscription& s10n ) + { + if( !m_rosterListener ) + return; + + switch( s10n.subtype() ) + { + case Subscription::Subscribe: + { + bool answer = m_rosterListener->handleSubscriptionRequest( s10n.from(), s10n.status() ); + if( m_syncSubscribeReq ) + { + ackSubscriptionRequest( s10n.from(), answer ); + } + break; + } + case Subscription::Subscribed: + { + m_rosterListener->handleItemSubscribed( s10n.from() ); + break; + } + + case Subscription::Unsubscribe: + { + Subscription p( Subscription::Unsubscribed, s10n.from().bareJID() ); + m_parent->send( p ); + + bool answer = m_rosterListener->handleUnsubscriptionRequest( s10n.from(), s10n.status() ); + if( m_syncSubscribeReq && answer ) + remove( s10n.from().bare() ); + break; + } + + case Subscription::Unsubscribed: + { + m_rosterListener->handleItemUnsubscribed( s10n.from() ); + break; + } + + default: + break; + } + } + + void RosterManager::registerRosterListener( RosterListener* rl, bool syncSubscribeReq ) + { + m_syncSubscribeReq = syncSubscribeReq; + m_rosterListener = rl; + } + + void RosterManager::removeRosterListener() + { + m_syncSubscribeReq = false; + m_rosterListener = 0; + } + + void RosterManager::setDelimiter( const std::string& delimiter ) + { + m_delimiter = delimiter; + Tag* t = new Tag( "roster", m_delimiter ); + t->addAttribute( XMLNS, XMLNS_ROSTER_DELIMITER ); + m_privateXML->storeXML( t, this ); + } + + void RosterManager::handlePrivateXML( const Tag* xml ) + { + if( xml ) + m_delimiter = xml->cdata(); + } + + void RosterManager::handlePrivateXMLResult( const std::string& /*uid*/, PrivateXMLResult /*result*/ ) + { + } + + RosterItem* RosterManager::getRosterItem( const JID& jid ) + { + Roster::const_iterator it = m_roster.find( jid.bare() ); + return it != m_roster.end() ? (*it).second : 0; + } + + void RosterManager::mergePush( const RosterData& data ) + { + RosterData::const_iterator it = data.begin(); + for( ; it != data.end(); ++it ) + { + Roster::iterator itr = m_roster.find( (*it)->jid() ); + if( itr != m_roster.end() ) + { + if( (*it)->remove() ) + { + if( m_rosterListener ) + m_rosterListener->handleItemRemoved( (*it)->jid() ); + delete (*itr).second; + m_roster.erase( itr ); + } + else + { + (*itr).second->setData( *(*it) ); + if( m_rosterListener ) + m_rosterListener->handleItemUpdated( (*it)->jid() ); + } + } + else if( !(*it)->remove() ) + { + m_roster.insert( std::make_pair( (*it)->jid(), new RosterItem( *(*it) ) ) ); + if( m_rosterListener ) + m_rosterListener->handleItemAdded( (*it)->jid() ); + } + } + } + + void RosterManager::mergeRoster( const RosterData& data ) + { + RosterData::const_iterator it = data.begin(); + for( ; it != data.end(); ++it ) + m_roster.insert( std::make_pair( (*it)->jid(), new RosterItem( *(*it) ) ) ); + } + +} diff --git a/libs/libgloox/rostermanager.h b/libs/libgloox/rostermanager.h new file mode 100644 index 0000000..0428873 --- /dev/null +++ b/libs/libgloox/rostermanager.h @@ -0,0 +1,283 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef ROSTERMANAGER_H__ +#define ROSTERMANAGER_H__ + +#include "subscriptionhandler.h" +#include "privatexmlhandler.h" +#include "iqhandler.h" +#include "presencehandler.h" +#include "rosterlistener.h" + +#include +#include +#include + +namespace gloox +{ + + class ClientBase; + class Stanza; + class PrivateXML; + class RosterItem; + + /** + * @brief This class implements Jabber/XMPP roster handling in the @b jabber:iq:roster namespace. + * + * It takes care of changing presence, subscriptions, etc. + * You can modify any number of RosterItems within the Roster at any time. These changes must be + * synchronized with the server by calling @ref synchronize(). Note that incoming Roster pushes + * initiated by other resources may overwrite changed values. + * Additionally, XEP-0083 (Nested Roster Groups) is implemented herein. + * + * @author Jakob Schroeter + * @since 0.3 + */ + class GLOOX_API RosterManager : public IqHandler, public PresenceHandler, public SubscriptionHandler, + public PrivateXMLHandler + { + public: + /** + * Creates a new RosterManager. + * @param parent The ClientBase which is used for communication. + */ + RosterManager( ClientBase* parent ); + + /** + * Virtual destructor. + */ + virtual ~RosterManager(); + + /** + * This function does the initial filling of the roster with + * the current server-side roster. + */ + void fill(); + + /** + * This function returns the roster. + * @return Returns a map of JIDs with their current presence. + */ + Roster* roster(); + + /** + * Use this function to subscribe to a new JID. The contact is added to the roster automatically + * (by compliant servers, as required by RFC 3921). + * @param jid The address to subscribe to. + * @param name The displayed name of the contact. + * @param groups A list of groups the contact belongs to. + * @param msg A message sent along with the request. + */ + void subscribe( const JID& jid, const std::string& name = EmptyString, + const StringList& groups = StringList(), + const std::string& msg = EmptyString ); + + /** + * Synchronizes locally modified RosterItems back to the server. + */ + void synchronize(); + + /** + * Use this function to add a contact to the roster. No subscription request is sent. + * @note Use @ref unsubscribe() to remove an item from the roster. + * @param jid The JID to add. + * @param name The displayed name of the contact. + * @param groups A list of groups the contact belongs to. + */ + void add( const JID& jid, const std::string& name, const StringList& groups ); + + /** + * Use this function to unsubscribe from a contact's presence. You will no longer + * receive presence from this contact. + * @param jid The address to unsubscribe from. + * @param msg A message to send along with the request. + * @since 0.9 + * @note Use remove() to remove a contact from the roster and to cancel its subscriptions. + */ + void unsubscribe( const JID& jid, const std::string& msg = EmptyString ); + + /** + * Use this function to cancel the contact's subscription to your presence. The contact will + * no longer receive presence from you. + * @param jid The contact's JID. + * @param msg A message to send along with the request. + * @since 0.9 + * @note Use remove() to remove a contact from the roster and to cancel its subscriptions. + */ + void cancel( const JID& jid, const std::string& msg = EmptyString ); + + /** + * Use this function to remove a contact from the roster. Subscription is implicitely + * cancelled. + * @param jid The contact's JID. + * @since 0.9 + */ + void remove( const JID& jid ); + + /** + * Use this function to acknowledge a subscription request if you requested asynchronous + * subscription request handling. + * @param to The JID to authorize/decline. + * @param ack Whether to authorize or decline the contact's request. + */ + void ackSubscriptionRequest( const JID& to, bool ack ); + + /** + * Use this function to retrieve the delimiter of Nested Roster Groups (XEP-0083). + * @return The group delimiter. + * @since 0.7 + */ + const std::string& delimiter() const { return m_delimiter; } + + /** + * Use this function to set the group delimiter (XEP-0083). + * @param delimiter The group delimiter. + * @since 0.7 + */ + void setDelimiter( const std::string& delimiter ); + + /** + * Lets you retrieve the RosterItem that belongs to the given JID. + * @param jid The JID to return the RosterItem for. + */ + RosterItem* getRosterItem( const JID& jid ); + + /** + * Register @c rl as object that receives updates on roster operations. + * For GUI applications it may be necessary to display a dialog or whatever to + * the user without blocking. If you want that, use asynchronous subscription + * requests. If you want to answer a request right away, make it synchronous. + * @param rl The object that receives roster updates. + * @param syncSubscribeReq Indicates whether (Un)SubscriptionRequests shall + * be handled synchronous (@b true) or asynchronous (@b false). Default: synchronous. + */ + void registerRosterListener( RosterListener* rl, bool syncSubscribeReq = true ); + + /** + * Complementary function to @ref registerRosterListener. Removes the current RosterListener. + * Roster events will not be delivered anywhere. + */ + void removeRosterListener(); + + // reimplemented from IqHandler. + virtual bool handleIq( const IQ& iq ); + + // reimplemented from IqHandler. + virtual void handleIqID( const IQ& iq, int context ); + + // reimplemented from PresenceHandler. + virtual void handlePresence( const Presence& presence ); + + // reimplemented from SubscriptionHandler. + virtual void handleSubscription( const Subscription& subscription ); + + // reimplemented from PrivateXMLHandler + virtual void handlePrivateXML( const Tag* xml ); + + // reimplemented from PrivateXMLHandler + virtual void handlePrivateXMLResult( const std::string& uid, PrivateXMLResult pxResult ); + + private: +#ifdef ROSTERMANAGER_TEST + public: +#endif + typedef std::list RosterData; + + /** + * @brief An implementation of StanzaExtension that helps in roster management. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class Query : public StanzaExtension + { + public: + /** + * Constructs a new object that can be used to add a contact to the roster. + * @param jid The contact's JID. + * @param name The contact's optional user-defined name. + * @param groups An optional list of groups the contact belongs to. + */ + Query( const JID& jid, const std::string& name, const StringList& groups ); + + /** + * Constructs a new object that can be used to remove a contact from the roster. + * @param jid The contact's JID. + */ + Query( const JID& jid ); + + /** + * Creates a new Query object from teh given Tag. + * @param tag The Tag to parse. + */ + Query( const Tag* tag = 0 ); + + /** + * Destructor. + */ + ~Query(); + + /** + * Retruns the internal roster that was created by the ctors (either from an + * incoming packet or passed arguments). + * This is not necessarily the full roster, but may be a single item. + * @return The (possibly partial) roster). + */ + const RosterData& roster() const { return m_roster; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Query( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const; + + private: + RosterData m_roster; + + }; + + void mergePush( const RosterData& data ); + void mergeRoster( const RosterData& data ); + + RosterListener* m_rosterListener; + Roster m_roster; + ClientBase* m_parent; + PrivateXML* m_privateXML; + RosterItem* m_self; + + std::string m_delimiter; + bool m_syncSubscribeReq; + + enum RosterContext + { + RequestRoster, + AddRosterItem, + RemoveRosterItem, + SynchronizeRoster + }; + + }; + +} + +#endif // ROSTER_H__ diff --git a/libs/libgloox/search.cpp b/libs/libgloox/search.cpp new file mode 100644 index 0000000..097b529 --- /dev/null +++ b/libs/libgloox/search.cpp @@ -0,0 +1,223 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "search.h" + +#include "clientbase.h" +#include "dataform.h" +#include "iq.h" + +namespace gloox +{ + + // Search::Query ---- + Search::Query::Query( DataForm* form ) + : StanzaExtension( ExtSearch ), m_form( form ), m_fields( 0 ) + { + } + + Search::Query::Query( int fields, const SearchFieldStruct& values ) + : StanzaExtension( ExtSearch ), m_form( 0 ), m_fields( fields ), m_values( values ) + { + } + + Search::Query::Query( const Tag* tag ) + : StanzaExtension( ExtSearch ), m_form( 0 ), m_fields( 0 ) + { + if( !tag || tag->name() != "query" || tag->xmlns() != XMLNS_SEARCH ) + return; + + const TagList& l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + if( (*it)->name() == "instructions" ) + { + m_instructions = (*it)->cdata(); + } + else if( (*it)->name() == "item" ) + { + m_srl.push_back( new SearchFieldStruct( (*it) ) ); + } + else if( (*it)->name() == "first" ) + m_fields |= SearchFieldFirst; + else if( (*it)->name() == "last" ) + m_fields |= SearchFieldLast; + else if( (*it)->name() == "email" ) + m_fields |= SearchFieldEmail; + else if( (*it)->name() == "nick" ) + m_fields |= SearchFieldNick; + else if( !m_form && (*it)->name() == "x" && (*it)->xmlns() == XMLNS_X_DATA ) + m_form = new DataForm( (*it) ); + } + } + + Search::Query::~Query() + { + delete m_form; + SearchResultList::iterator it = m_srl.begin(); + for( ; it != m_srl.end(); ++it ) + delete (*it); + } + + const std::string& Search::Query::filterString() const + { + static const std::string filter = "/iq/query[@xmlns='" + XMLNS_SEARCH + "']"; + return filter; + } + + Tag* Search::Query::tag() const + { + Tag* t = new Tag( "query" ); + t->setXmlns( XMLNS_SEARCH ); + if( m_form ) + t->addChild( m_form->tag() ); + else if( m_fields ) + { + if( !m_instructions.empty() ) + new Tag( t, "instructions", m_instructions ); + if( m_fields & SearchFieldFirst ) + new Tag( t, "first", m_values.first() ); + if( m_fields & SearchFieldLast ) + new Tag( t, "last", m_values.last() ); + if( m_fields & SearchFieldNick ) + new Tag( t, "nick", m_values.nick() ); + if( m_fields & SearchFieldEmail ) + new Tag( t, "email", m_values.email() ); + } + else if( !m_srl.empty() ) + { + SearchResultList::const_iterator it = m_srl.begin(); + for( ; it != m_srl.end(); ++it ) + { + t->addChild( (*it)->tag() ); + } + } + return t; + } + // ---- ~Search::Query ---- + + // ---- Search ---- + Search::Search( ClientBase* parent ) + : m_parent( parent ) + { + if( m_parent ) + m_parent->registerStanzaExtension( new Query() ); + } + + Search::~Search() + { + if( m_parent ) + { + m_parent->removeIDHandler( this ); + m_parent->removeStanzaExtension( ExtRoster ); + } + } + + void Search::fetchSearchFields( const JID& directory, SearchHandler* sh ) + { + if( !m_parent || !directory || !sh ) + return; + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Get, directory, id ); + iq.addExtension( new Query() ); + m_track[id] = sh; + m_parent->send( iq, this, FetchSearchFields ); + } + + void Search::search( const JID& directory, DataForm* form, SearchHandler* sh ) + { + if( !m_parent || !directory || !sh ) + return; + + const std::string& id = m_parent->getID(); + IQ iq( IQ::Set, directory, id ); + iq.addExtension( new Query( form ) ); + + m_track[id] = sh; + m_parent->send( iq, this, DoSearch ); + } + + void Search::search( const JID& directory, int fields, const SearchFieldStruct& values, SearchHandler* sh ) + { + if( !m_parent || !directory || !sh ) + return; + + const std::string& id = m_parent->getID(); + + IQ iq( IQ::Set, directory ); + iq.addExtension( new Query( fields, values ) ); + + m_track[id] = sh; + m_parent->send( iq, this, DoSearch ); + } + + void Search::handleIqID( const IQ& iq, int context ) + { + TrackMap::iterator it = m_track.find( iq.id() ); + if( it != m_track.end() ) + { + switch( iq.subtype() ) + { + case IQ::Result: + { + const Query* q = iq.findExtension( ExtSearch ); + if( !q ) + return; + + switch( context ) + { + case FetchSearchFields: + { + if( q->form() ) + { + (*it).second->handleSearchFields( iq.from(), q->form() ); + } + else + { + (*it).second->handleSearchFields( iq.from(), q->fields(), q->instructions() ); + } + break; + } + case DoSearch: + { + if( q->form() ) + { + (*it).second->handleSearchResult( iq.from(), q->form() ); + } + else + { + (*it).second->handleSearchResult( iq.from(), q->result() ); + } + break; + } + } + break; + } + case IQ::Error: + (*it).second->handleSearchError( iq.from(), iq.error() ); + break; + + default: + break; + } + + m_track.erase( it ); + } + + return; + } + +} diff --git a/libs/libgloox/search.h b/libs/libgloox/search.h new file mode 100644 index 0000000..677305d --- /dev/null +++ b/libs/libgloox/search.h @@ -0,0 +1,215 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef SEARCH_H__ +#define SEARCH_H__ + +#include "gloox.h" +#include "searchhandler.h" +#include "discohandler.h" +#include "iqhandler.h" +#include "stanzaextension.h" +#include "dataform.h" + +#include + +namespace gloox +{ + + class ClientBase; + class IQ; + class Disco; + + /** + * @brief An implementation of XEP-0055 (Jabber Search) + * + * To perform a search in a directory (e.g., a User Directory): + * + * @li Inherit from SearchHandler and implement the virtual functions. + * @li Create a new Search object. + * @li Ask the directory for the supported fields using fetchSearchFields(). Depending on the directory, + * the result can be either an integer (bit-wise ORed supported fields) or a DataForm. + * @li Search by either using a DataForm or the SearchFieldStruct. + * @li The results can be either a (empty) list of SearchFieldStructs or a DataForm. + * + * @author Jakob Schroeter + * @since 0.8.5 + */ + class GLOOX_API Search : public IqHandler + { + + public: + /** + * Creates a new Search object. + * @param parent The ClientBase to use. + */ + Search( ClientBase* parent ); + + /** + * Virtual Destructor. + */ + ~Search(); + + /** + * Use this function to check which fields the directory supports. + * @param directory The (user) directory to fetch the available/searchable fields from. + * @param sh The SearchHandler to notify about the results. + */ + void fetchSearchFields( const JID& directory, SearchHandler* sh ); + + /** + * Initiates a search on the given directory, with the given data form. The given SearchHandler + * is notified about the results. + * @param directory The (user) directory to search. + * @param form The DataForm contains the phrases the user wishes to search for. + * Search will delete the form eventually. + * @param sh The SearchHandler to notify about the results. + */ + void search( const JID& directory, DataForm* form, SearchHandler* sh ); + + /** + * Initiates a search on the given directory, with the given phrases. The given SearchHandler + * is notified about the results. + * @param directory The (user) directory to search. + * @param fields Bit-wise ORed FieldEnum values describing the valid (i.e., set) fields in + * the @b values parameter. + * @param values Contains the phrases to search for. + * @param sh The SearchHandler to notify about the results. + */ + void search( const JID& directory, int fields, const SearchFieldStruct& values, SearchHandler* sh ); + + // reimplemented from IqHandler. + virtual bool handleIq( const IQ& iq ) { (void)iq; return false; } + + // reimplemented from IqHandler. + virtual void handleIqID( const IQ& iq, int context ); + + protected: + enum IdType + { + FetchSearchFields, + DoSearch + }; + + typedef std::map TrackMap; + TrackMap m_track; + + ClientBase* m_parent; + Disco* m_disco; + + private: +#ifdef SEARCH_TEST + public: +#endif + /** + * @brief A wrapping class for the XEP-0055 <query> element. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class Query : public StanzaExtension + { + public: + /** + * Creates a new object that can be used to carry out a search. + * @param form A DataForm containing the search terms. + */ + Query( DataForm* form ); + + /** + * Creates a new object that can be used to carry out a search. + * @param fields Bit-wise ORed FieldEnum values describing the valid (i.e., set) + * fields in the @b values parameter. + * @param values Contains the phrases to search for. + */ + Query( int fields, const SearchFieldStruct& values ); + + /** + * Creates a new object that can be used to request search fields. + * Optionally, it can parse the given Tag. + * @param tag The Tag to parse. + */ + Query( const Tag* tag = 0 ); + + /** + * Virtual Destructor. + */ + virtual ~Query(); + + /** + * Returns the contained search form, if any. + * @return The search form. May be 0. + */ + const DataForm* form() const { return m_form; } + + /** + * Returns the search instructions, if given + * @return The search instructions. + */ + const std::string& instructions() const { return m_instructions; } + + /** + * Returns the search fields, if set. + * @return The search fields. + */ + int fields() const { return m_fields; } + + /** + * Returns the search's result, if available in legacy form. Use form() otherwise. + * @return The search's result. + */ + const SearchResultList& result() const { return m_srl; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Query( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + Query* q = new Query(); + q->m_form = m_form ? new DataForm( *m_form ) : 0; + q->m_fields = m_fields; + q->m_values = m_values; + q->m_instructions = m_instructions; + SearchResultList::const_iterator it = m_srl.begin(); + for( ; it != m_srl.end(); ++it ) + q->m_srl.push_back( new SearchFieldStruct( *(*it) ) ); + return q; + } + + private: +#ifdef SEARCH_TEST + public: +#endif + DataForm* m_form; + int m_fields; + SearchFieldStruct m_values; + std::string m_instructions; + SearchResultList m_srl; + }; + + }; + +} + +#endif // SEARCH_H__ diff --git a/libs/libgloox/searchhandler.h b/libs/libgloox/searchhandler.h new file mode 100644 index 0000000..1a790e4 --- /dev/null +++ b/libs/libgloox/searchhandler.h @@ -0,0 +1,195 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef SEARCHHANDLER_H__ +#define SEARCHHANDLER_H__ + +#include "stanza.h" + +#include + +namespace gloox +{ + + class DataForm; + + /** + * Holds all the possible fields a server may require for searching according + * to Section 7, XEP-0055. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class SearchFieldStruct + { + public: + /** + * + */ + SearchFieldStruct() {} + + /** + * + */ + SearchFieldStruct( const std::string& first, const std::string& last, const std::string& nick, + const std::string& email ) + : m_first( first ), m_last( last ), m_nick( nick ), m_email( email ) + {} + + /** + * + */ + SearchFieldStruct( const Tag* tag ) + { + if( !tag || tag->name() != "item" || !tag->hasAttribute( "jid" ) ) + return; + + m_jid.setJID( tag->findAttribute( "jid" ) ); + const TagList& l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + if( (*it)->name() == "first" ) + m_first = (*it)->cdata(); + else if( (*it)->name() == "last" ) + m_last = (*it)->cdata(); + else if( (*it)->name() == "email" ) + m_email = (*it)->cdata(); + else if( (*it)->name() == "nick" ) + m_nick = (*it)->cdata(); + } + } + + /** + * + */ + ~SearchFieldStruct() {} + + /** + * + */ + const std::string first() const { return m_first; } + + /** + * + */ + const std::string last() const { return m_last; } + + /** + * + */ + const std::string email() const { return m_email; } + + /** + * + */ + const std::string nick() const { return m_nick; } + + /** + * + */ + Tag* tag() const + { + Tag* t = new Tag( "item" ); + t->addAttribute( "jid", m_jid.bare() ); + new Tag( t, "first", m_first ); + new Tag( t, "last", m_last ); + new Tag( t, "nick", m_nick ); + new Tag( t, "email", m_email ); + return t; + } + + private: + std::string m_first; /**< User's first name. */ + std::string m_last; /**< User's last name. */ + std::string m_nick; /**< User's nickname. */ + std::string m_email; /**< User's email. */ + JID m_jid; /**< User's JID. */ + }; + + /** + * The possible fields of a XEP-0055 user search. + */ + enum SearchFieldEnum + { + SearchFieldFirst = 1, /**< Search in first names. */ + SearchFieldLast = 2, /**< Search in last names. */ + SearchFieldNick = 4, /**< Search in nicknames. */ + SearchFieldEmail = 8 /**< Search in email addresses. */ + }; + + /** + * A list of directory entries returned by a search. + */ + typedef std::list SearchResultList; + + /** + * @brief A virtual interface that enables objects to receive Jabber Search (XEP-0055) results. + * + * A class implementing this interface can receive the result of a Jabber Search. + * + * @author Jakob Schroeter + * @since 0.8.5 + */ + class GLOOX_API SearchHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~SearchHandler() {} + + /** + * This function is called to announce the searchable fields a directory supports. It is the result + * of a call to @link gloox::Search::fetchSearchFields Search::fetchSearchFields() @endlink. + * @param directory The directory that was queried. + * @param fields Bit-wise ORed SearchFieldEnum values. + * @param instructions Plain-text instructions for the end user. + */ + virtual void handleSearchFields( const JID& directory, int fields, + const std::string& instructions ) = 0; + + /** + * This function is called to announce the searchable fields a directory supports. It is the result + * of a call to @link gloox::Search::fetchSearchFields Search::fetchSearchFields() @endlink. + * @param directory The directory that was queried. + * @param form A DataForm describing the valid searchable fields. Do not delete the form. + */ + virtual void handleSearchFields( const JID& directory, const DataForm* form ) = 0; + + /** + * This function is called to let the SearchHandler know about the results of the search. + * @param directory The searched directory. + * @param resultList A list of SearchFieldStructs. May be empty. + */ + virtual void handleSearchResult( const JID& directory, const SearchResultList& resultList ) = 0; + + /** + * This function is called to let the SearchHandler know about the result of the search. + * @param directory The searched directory. + * @param form A DataForm containing the search results. Do not delete the form. + */ + virtual void handleSearchResult( const JID& directory, const DataForm* form ) = 0; + + /** + * This function is called if a error occured as a result to a search or search field request. + * @param directory The queried/searched directory. + * @param error The error. May be 0. + */ + virtual void handleSearchError( const JID& directory, const Error* error ) = 0; + + }; + +} + +#endif // SEARCHHANDLER_H__ diff --git a/libs/libgloox/sha.cpp b/libs/libgloox/sha.cpp new file mode 100644 index 0000000..6e0266e --- /dev/null +++ b/libs/libgloox/sha.cpp @@ -0,0 +1,250 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "config.h" + +#include "sha.h" +#include "gloox.h" + +#include + +namespace gloox +{ + + SHA::SHA() + { + init(); + } + + SHA::~SHA() + { + } + + void SHA::init() + { + Length_Low = 0; + Length_High = 0; + Message_Block_Index = 0; + + H[0] = 0x67452301; + H[1] = 0xEFCDAB89; + H[2] = 0x98BADCFE; + H[3] = 0x10325476; + H[4] = 0xC3D2E1F0; + + m_finished = false; + m_corrupted = false; + } + + void SHA::reset() + { + init(); + } + + const std::string SHA::hex() + { + if( m_corrupted ) + return EmptyString; + + finalize(); + + char buf[41]; + for( int i = 0; i < 20; ++i ) + sprintf( buf + i * 2, "%02x", (unsigned char)( H[i >> 2] >> ( ( 3 - ( i & 3 ) ) << 3 ) ) ); + + return std::string( buf, 40 ); + } + + const std::string SHA::binary() + { + if( !m_finished ) + finalize(); + + unsigned char digest[20]; + for( int i = 0; i < 20; ++i ) + digest[i] = (unsigned char)( H[i >> 2] >> ( ( 3 - ( i & 3 ) ) << 3 ) ); + + return std::string( (char*)digest, 20 ); + } + + void SHA::finalize() + { + if( !m_finished ) + { + pad(); + m_finished = true; + } + } + + void SHA::feed( const unsigned char* data, unsigned length ) + { + if( !length ) + return; + + if( m_finished || m_corrupted ) + { + m_corrupted = true; + return; + } + + while( length-- && !m_corrupted ) + { + Message_Block[Message_Block_Index++] = ( *data & 0xFF ); + + Length_Low += 8; + Length_Low &= 0xFFFFFFFF; + if( Length_Low == 0 ) + { + Length_High++; + Length_High &= 0xFFFFFFFF; + if( Length_High == 0 ) + { + m_corrupted = true; + } + } + + if( Message_Block_Index == 64 ) + { + process(); + } + + ++data; + } + } + + void SHA::feed( const std::string& data ) + { + feed( (const unsigned char*)data.c_str(), (int)data.length() ); + } + + void SHA::process() + { + const unsigned K[] = { 0x5A827999, + 0x6ED9EBA1, + 0x8F1BBCDC, + 0xCA62C1D6 + }; + int t; + unsigned temp; + unsigned W[80]; + unsigned A, B, C, D, E; + + for( t = 0; t < 16; t++ ) + { + W[t] = ((unsigned) Message_Block[t * 4]) << 24; + W[t] |= ((unsigned) Message_Block[t * 4 + 1]) << 16; + W[t] |= ((unsigned) Message_Block[t * 4 + 2]) << 8; + W[t] |= ((unsigned) Message_Block[t * 4 + 3]); + } + + for( t = 16; t < 80; ++t ) + { + W[t] = shift( 1, W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16] ); + } + + A = H[0]; + B = H[1]; + C = H[2]; + D = H[3]; + E = H[4]; + + for( t = 0; t < 20; ++t ) + { + temp = shift( 5, A ) + ( ( B & C ) | ( ( ~B ) & D ) ) + E + W[t] + K[0]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = shift( 30, B ); + B = A; + A = temp; + } + + for( t = 20; t < 40; ++t ) + { + temp = shift( 5, A ) + ( B ^ C ^ D ) + E + W[t] + K[1]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = shift( 30, B ); + B = A; + A = temp; + } + + for( t = 40; t < 60; ++t ) + { + temp = shift( 5, A ) + ( ( B & C ) | ( B & D ) | ( C & D ) ) + E + W[t] + K[2]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = shift( 30, B ); + B = A; + A = temp; + } + + for( t = 60; t < 80; ++t ) + { + temp = shift( 5, A ) + ( B ^ C ^ D ) + E + W[t] + K[3]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = shift( 30, B ); + B = A; + A = temp; + } + + H[0] = ( H[0] + A ) & 0xFFFFFFFF; + H[1] = ( H[1] + B ) & 0xFFFFFFFF; + H[2] = ( H[2] + C ) & 0xFFFFFFFF; + H[3] = ( H[3] + D ) & 0xFFFFFFFF; + H[4] = ( H[4] + E ) & 0xFFFFFFFF; + + Message_Block_Index = 0; + } + + void SHA::pad() + { + Message_Block[Message_Block_Index++] = 0x80; + + if( Message_Block_Index > 55 ) + { + while( Message_Block_Index < 64 ) + { + Message_Block[Message_Block_Index++] = 0; + } + + process(); + } + + while( Message_Block_Index < 56 ) + { + Message_Block[Message_Block_Index++] = 0; + } + + Message_Block[56] = static_cast( ( Length_High >> 24 ) & 0xFF ); + Message_Block[57] = static_cast( ( Length_High >> 16 ) & 0xFF ); + Message_Block[58] = static_cast( ( Length_High >> 8 ) & 0xFF ); + Message_Block[59] = static_cast( ( Length_High ) & 0xFF ); + Message_Block[60] = static_cast( ( Length_Low >> 24 ) & 0xFF ); + Message_Block[61] = static_cast( ( Length_Low >> 16 ) & 0xFF ); + Message_Block[62] = static_cast( ( Length_Low >> 8 ) & 0xFF ); + Message_Block[63] = static_cast( ( Length_Low ) & 0xFF ); + + process(); + } + + + unsigned SHA::shift( int bits, unsigned word ) + { + return ( ( word << bits ) & 0xFFFFFFFF) | ( ( word & 0xFFFFFFFF ) >> ( 32-bits ) ); + } + +} diff --git a/libs/libgloox/sha.h b/libs/libgloox/sha.h new file mode 100644 index 0000000..0eabd5e --- /dev/null +++ b/libs/libgloox/sha.h @@ -0,0 +1,98 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef SHA_H__ +#define SHA_H__ + +#include "macros.h" + +#include + +namespace gloox +{ + + /** + * @brief An implementation of SHA1. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API SHA + { + + public: + /** + * Constructs a new SHA object. + */ + SHA(); + + /** + * Virtual Destructor. + */ + virtual ~SHA(); + + /** + * Resets the internal state. + */ + void reset(); + + /** + * Finalizes the hash computation. + */ + void finalize(); + + /** + * Returns the message digest in hex notation. Finalizes the hash if finalize() + * has not been called before. + * @return The message digest. + */ + const std::string hex(); + + /** + * Returns the raw binary message digest. Finalizes the hash if finalize() + * has not been called before. + * @return The message raw binary digest. + */ + const std::string binary(); + + /** + * Provide input to SHA1. + * @param data The data to compute the digest of. + * @param length The size of the data in bytes. + */ + void feed( const unsigned char* data, unsigned length ); + + /** + * Provide input to SHA1. + * @param data The data to compute the digest of. + */ + void feed( const std::string& data ); + + private: + void process(); + void pad(); + inline unsigned shift( int bits, unsigned word ); + void init(); + + unsigned H[5]; + unsigned Length_Low; + unsigned Length_High; + unsigned char Message_Block[64]; + int Message_Block_Index; + bool m_finished; + bool m_corrupted; + + }; + +} + +#endif // SHA_H__ diff --git a/libs/libgloox/shim.cpp b/libs/libgloox/shim.cpp new file mode 100644 index 0000000..2aa1dc1 --- /dev/null +++ b/libs/libgloox/shim.cpp @@ -0,0 +1,71 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "shim.h" +#include "tag.h" + +namespace gloox +{ + + SHIM::SHIM( const HeaderList& hl ) + : StanzaExtension( ExtSHIM ), m_headers( hl ) + { + } + + SHIM::SHIM( const Tag* tag ) + : StanzaExtension( ExtSHIM ) + { + if( !tag || tag->name() != "headers" || tag->xmlns() != XMLNS_SHIM ) + return; + + const TagList& l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + if( (*it)->name() != "header" || !(*it)->hasAttribute( "name" ) ) + return; + + m_headers.insert( std::make_pair( (*it)->findAttribute( "name" ), (*it)->cdata() ) ); + } + } + + SHIM::~SHIM() + { + } + + const std::string& SHIM::filterString() const + { + static const std::string filter = "/presence/headers[@xmlns='" + XMLNS_SHIM + "']" + "|/message/headers[@xmlns='" + XMLNS_SHIM + "']" + "|/iq/*/headers[@xmlns='" + XMLNS_SHIM + "']"; + return filter; + } + + Tag* SHIM::tag() const + { + if( !m_headers.size() ) + return 0; + + Tag* t = new Tag( "headers" ); + t->setXmlns( XMLNS_SHIM ); + + HeaderList::const_iterator it = m_headers.begin(); + for( ; it != m_headers.end(); ++it ) + { + Tag* h = new Tag( t, "header" ); + h->addAttribute( "name", (*it).first ); + h->setCData( (*it).second ); + } + return t; + } + +} diff --git a/libs/libgloox/shim.h b/libs/libgloox/shim.h new file mode 100644 index 0000000..fbe0197 --- /dev/null +++ b/libs/libgloox/shim.h @@ -0,0 +1,91 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef SHIM_H__ +#define SHIM_H__ + +#include "stanzaextension.h" +#include "macros.h" + +#include +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief An implementation/abstraction of Stanza Headers and Internet Metadata (SHIM, XEP-0131). + * + * XEP Version: 1.2 + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API SHIM : public StanzaExtension + { + public: + /** + * A list of SHIM headers (name & value). + */ + typedef std::map HeaderList; + + /** + * Creates a new SHIM object containing the given headers. + * @param hl The list of headers. + */ + SHIM( const HeaderList& hl ); + + /** + * Creates a new SHIM object from the given Tag. + * @param tag The Tag to parse. + */ + SHIM( const Tag* tag = 0 ); + + /** + * Returns the headers. + * @return The headers. + */ + const HeaderList& headers() const { return m_headers; } + + /** + * Virtual destructor. + */ + virtual ~SHIM(); + + // re-implemented from StanzaExtension + virtual const std::string& filterString() const; + + // re-implemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new SHIM( tag ); + } + + // re-implemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new SHIM( *this ); + } + + private: + HeaderList m_headers; + + }; + +} + +#endif // SHIM_H__ diff --git a/libs/libgloox/sihandler.h b/libs/libgloox/sihandler.h new file mode 100644 index 0000000..c0c69dd --- /dev/null +++ b/libs/libgloox/sihandler.h @@ -0,0 +1,70 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef SIHANDLER_H__ +#define SIHANDLER_H__ + +#include "macros.h" +#include "simanager.h" + +#include + +namespace gloox +{ + + class IQ; + class Tag; + class JID; + + /** + * @brief An abstract base class to handle results of outgoing SI requests, i.e. you requested a stream + * (using SIManager::requestSI()) to send a file to a remote entity. + * + * You should usually not need to use this class directly, unless your profile is not supported + * by gloox. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API SIHandler + { + + public: + /** + * Virtual destructor. + */ + virtual ~SIHandler() {} + + /** + * This function is called to handle results of outgoing SI requests, i.e. you requested a stream + * (using SIManager::requestSI()) to send a file to a remote entity. + * @param from The remote SI receiver. + * @param to The SI requestor. Usually oneself. Used in component scenario. + * @param sid The stream ID. + * @param si The request's complete SI. + */ + virtual void handleSIRequestResult( const JID& from, const JID& to, const std::string& sid, + const SIManager::SI& si ) = 0; + + /** + * This function is called to handle a request error or decline. + * @param iq The complete error stanza. + * @param sid The request's SID. + */ + virtual void handleSIRequestError( const IQ& iq, const std::string& sid ) = 0; + + }; + +} + +#endif // SIHANDLER_H__ diff --git a/libs/libgloox/simanager.cpp b/libs/libgloox/simanager.cpp new file mode 100644 index 0000000..0162c6d --- /dev/null +++ b/libs/libgloox/simanager.cpp @@ -0,0 +1,259 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "simanager.h" + +#include "siprofilehandler.h" +#include "sihandler.h" +#include "clientbase.h" +#include "disco.h" +#include "error.h" + +namespace gloox +{ + + // ---- SIManager::SI ---- + SIManager::SI::SI( const Tag* tag ) + : StanzaExtension( ExtSI ), m_tag1( 0 ), m_tag2( 0 ) + { + if( !tag || tag->name() != "si" || tag->xmlns() != XMLNS_SI ) + return; + + m_valid = true; + + m_id = tag->findAttribute( "id" ); + m_mimetype = tag->findAttribute( "mime-type" ); + m_profile = tag->findAttribute( "profile" ); + + Tag* c = tag->findChild( "file", "xmlns", XMLNS_SI_FT ); + if ( c ) + m_tag1 = c->clone(); + c = tag->findChild( "feature", "xmlns", XMLNS_FEATURE_NEG ); + if( c ) + m_tag2 = c->clone(); + } + + SIManager::SI::SI( Tag* tag1, Tag* tag2, const std::string& id, + const std::string& mimetype, const std::string& profile ) + : StanzaExtension( ExtSI ), m_tag1( tag1 ), m_tag2( tag2 ), + m_id( id ), m_mimetype( mimetype ), m_profile( profile ) + { + m_valid = true; + } + + SIManager::SI::~SI() + { + delete m_tag1; + delete m_tag2; + } + + const std::string& SIManager::SI::filterString() const + { + static const std::string filter = "/iq/si[@xmlns='" + XMLNS_SI + "']"; + return filter; + } + + Tag* SIManager::SI::tag() const + { + if( !m_valid ) + return 0; + + Tag* t = new Tag( "si" ); + t->setXmlns( XMLNS_SI ); + if( !m_id.empty() ) + t->addAttribute( "id", m_id ); + if( !m_mimetype.empty() ) + t->addAttribute( "mime-type", m_mimetype.empty() ? "binary/octet-stream" : m_mimetype ); + if( !m_profile.empty() ) + t->addAttribute( "profile", m_profile ); + if( m_tag1 ) + t->addChildCopy( m_tag1 ); + if( m_tag2 ) + t->addChildCopy( m_tag2 ); + + return t; + } + // ---- ~SIManager::SI ---- + + // ---- SIManager ---- + SIManager::SIManager( ClientBase* parent, bool advertise ) + : m_parent( parent ), m_advertise( advertise ) + { + if( m_parent ) + { + m_parent->registerStanzaExtension( new SI() ); + m_parent->registerIqHandler( this, ExtSI ); + if( m_parent->disco() && m_advertise ) + m_parent->disco()->addFeature( XMLNS_SI ); + } + } + + SIManager::~SIManager() + { + if( m_parent ) + { + m_parent->removeIqHandler( this, ExtSI ); + m_parent->removeIDHandler( this ); + if( m_parent->disco() && m_advertise ) + m_parent->disco()->removeFeature( XMLNS_SI ); + } + } + + const std::string SIManager::requestSI( SIHandler* sih, const JID& to, const std::string& profile, + Tag* child1, Tag* child2, const std::string& mimetype, + const JID& from, const std::string& sid ) + { + if( !m_parent || !sih ) + return EmptyString; + + const std::string& id = m_parent->getID(); + const std::string& sidToUse = sid.empty() ? m_parent->getID() : sid; + + IQ iq( IQ::Set, to, id ); + iq.addExtension( new SI( child1, child2, sidToUse, mimetype, profile ) ); + if( from ) + iq.setFrom( from ); + + TrackStruct t; + t.sid = sidToUse; + t.profile = profile; + t.sih = sih; + m_track[id] = t; + m_parent->send( iq, this, OfferSI ); + + return sidToUse; + } + + void SIManager::acceptSI( const JID& to, const std::string& id, Tag* child1, Tag* child2, const JID& from ) + { + IQ iq( IQ::Result, to, id ); + iq.addExtension( new SI( child1, child2 ) ); + if( from ) + iq.setFrom( from ); + + m_parent->send( iq ); + } + + void SIManager::declineSI( const JID& to, const std::string& id, SIError reason, const std::string& text ) + { + IQ iq( IQ::Error, to, id ); + Error* error; + if( reason == NoValidStreams || reason == BadProfile ) + { + Tag* appError = 0; + if( reason == NoValidStreams ) + appError = new Tag( "no-valid-streams", XMLNS, XMLNS_SI ); + else if( reason == BadProfile ) + appError = new Tag( "bad-profile", XMLNS, XMLNS_SI ); + error = new Error( StanzaErrorTypeCancel, StanzaErrorBadRequest, appError ); + } + else + { + error = new Error( StanzaErrorTypeCancel, StanzaErrorForbidden ); + if( !text.empty() ) + error->text( text ); + } + + iq.addExtension( error ); + m_parent->send( iq ); + } + + void SIManager::registerProfile( const std::string& profile, SIProfileHandler* sih ) + { + if( !sih || profile.empty() ) + return; + + m_handlers[profile] = sih; + + if( m_parent && m_advertise && m_parent->disco() ) + m_parent->disco()->addFeature( profile ); + } + + void SIManager::removeProfile( const std::string& profile ) + { + if( profile.empty() ) + return; + + m_handlers.erase( profile ); + + if( m_parent && m_advertise && m_parent->disco() ) + m_parent->disco()->removeFeature( profile ); + } + + bool SIManager::handleIq( const IQ& iq ) + { + TrackMap::iterator itt = m_track.find( iq.id() ); + if( itt != m_track.end() ) + return false; + + const SI* si = iq.findExtension( ExtSI ); + if( !si || si->profile().empty() ) + return false; + + HandlerMap::const_iterator it = m_handlers.find( si->profile() ); + if( it != m_handlers.end() && (*it).second ) + { + (*it).second->handleSIRequest( iq.from(), iq.to(), iq.id(), *si ); + return true; + } + + return false; + } + + void SIManager::handleIqID( const IQ& iq, int context ) + { + switch( iq.subtype() ) + { + case IQ::Result: + if( context == OfferSI ) + { + TrackMap::iterator it = m_track.find( iq.id() ); + if( it != m_track.end() ) + { + const SI* si = iq.findExtension( ExtSI ); + if( !si /*|| si->profile().empty()*/ ) + return; + +// Tag* si = iq.query(); +// Tag* ptag = 0; +// Tag* fneg = 0; +// if( si && si->name() == "si" && si->xmlns() == XMLNS_SI ) +// { +// ptag = si->findChildWithAttrib( XMLNS, (*it).second.profile ); +// fneg = si->findChild( "feature", XMLNS, XMLNS_FEATURE_NEG ); +// } + + // FIXME: remove above commented code and + // check corectness of last 3 params! + (*it).second.sih->handleSIRequestResult( iq.from(), iq.to(), (*it).second.sid, *si ); + m_track.erase( it ); + } + } + break; + case IQ::Error: + if( context == OfferSI ) + { + TrackMap::iterator it = m_track.find( iq.id() ); + if( it != m_track.end() ) + { + (*it).second.sih->handleSIRequestError( iq, (*it).second.sid ); + m_track.erase( it ); + } + } + break; + default: + break; + } + } + +} diff --git a/libs/libgloox/simanager.h b/libs/libgloox/simanager.h new file mode 100644 index 0000000..ace715a --- /dev/null +++ b/libs/libgloox/simanager.h @@ -0,0 +1,245 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef SIMANAGER_H__ +#define SIMANAGER_H__ + +#include "iqhandler.h" + +namespace gloox +{ + + class ClientBase; + class SIProfileHandler; + class SIHandler; + + /** + * @brief This class manages streams initiated using XEP-0095. + * + * You need only one SIManager object per ClientBase instance. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API SIManager : public IqHandler + { + + public: + /** + * SI error conditions. + */ + enum SIError + { + NoValidStreams, /**< None of the stream types are acceptable */ + BadProfile, /**< Profile is not understood. */ + RequestRejected /**< SI request was rejected. */ + }; + + class SI : public StanzaExtension + { + public: + /** + * Constructs a new SI object from the given Tag. + * @param tag The Tag to parse. + */ + SI( const Tag* tag = 0 ); + + /** + * Constructs a new SI object, wrapping the given Tags. + * @param tag1 Tag 1. + * @param tag2 Tag 2. + */ + SI( Tag* tag1, Tag* tag2, const std::string& id = EmptyString, + const std::string& mimetype = EmptyString, + const std::string& profile = EmptyString ); + + /** + * Virtual destructor. + */ + virtual ~SI(); + + /** + * Returns the current profile namespace. + * @return The profile namespace. + */ + const std::string& profile() const { return m_profile; }; + + /** + * Returns the mime-type. + * @return The mime-type. + */ + const std::string& mimetype() const { return m_mimetype; }; + + /** + * Returns the SI's ID. + * @return The SI's id. + */ + const std::string& id() const { return m_id; }; + + /** + * Returns the first SI child tag. + * @return The first SI child tag. + * @todo Use real objects. + */ + const Tag* tag1() const { return m_tag1; }; + + /** + * Returns the second SI child tag. + * @return The second SI child tag. + * @todo Use real objects. + */ + const Tag* tag2() const { return m_tag2; }; + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new SI( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + SI* s = new SI(); + s->m_tag1 = m_tag1 ? m_tag1->clone() : 0; + s->m_tag2 = m_tag2 ? m_tag2->clone() : 0; + s->m_id = m_id; + s->m_mimetype = m_mimetype; + s->m_profile = m_profile; + return s; + } + + private: + Tag* m_tag1; + Tag* m_tag2; + std::string m_id; + std::string m_mimetype; + std::string m_profile; + }; + + /** + * Constructor. + * @param parent The ClientBase to use for communication. + * @param advertise Whether to advertise SI capabilities by disco. Defaults to true. + */ + SIManager( ClientBase* parent, bool advertise = true ); + + /** + * Virtual destructor. + */ + virtual ~SIManager(); + + /** + * Starts negotiating a stream with a remote entity. + * @param sih The SIHandler to handle the result of this request. + * @param to The entity to talk to. + * @param profile The SI profile to use. See XEP-0095 for more info. + * @param child1 The first of the two allowed children of the SI offer. See + * XEP-0095 for more info. + * @param child2 The second of the two allowed children of the SI offer. See + * XEP-0095 for more info. Defaults to 0. + * @param mimetype The stream's/file's mime-type. Defaults to 'binary/octet-stream'. + * @param from An optional 'from' address to stamp outgoing requests with. + * Used in component scenario only. Defaults to empty JID. + * @param sid Optionally specify a stream ID (SID). If empty, one will be generated. + * @return The requested stream's ID (SID). Empty if SIHandler or ClientBase are invalid. + * @note The SIManager claims ownership of the Tags supplied to this function, and will + * delete them after use. + */ + const std::string requestSI( SIHandler* sih, const JID& to, const std::string& profile, Tag* child1, + Tag* child2 = 0, const std::string& mimetype = "binary/octet-stream", + const JID& from = JID(), const std::string& sid = EmptyString ); + + /** + * Call this function to accept an SI request previously announced by means of + * SIProfileHandler::handleSIRequest(). + * @param to The requestor. + * @param id The request's id, as passed to SIProfileHandler::handleSIRequest(). + * @param child1 The <feature/> child of the SI request. See XEP-0095 for details. + * @param child2 The profile-specific child of the SI request. May be 0. See XEP-0095 + * for details. + * @param from An optional 'from' address to stamp outgoing stanzas with. + * Used in component scenario only. Defaults to empty JID. + * @note The SIManager claims ownership of the Tags supplied to this function, and will + * delete them after use. + */ + void acceptSI( const JID& to, const std::string& id, Tag* child1, Tag* child2 = 0, const JID& from = JID() ); + + /** + * Call this function to decline an SI request previously announced by means of + * SIProfileHandler::handleSIRequest(). + * @param to The requestor. + * @param id The request's id, as passed to SIProfileHandler::handleSIRequest(). + * @param reason The reason for the reject. + * @param text An optional human-readable text explaining the decline. + */ + void declineSI( const JID& to, const std::string& id, SIError reason, + const std::string& text = EmptyString ); + + /** + * Registers the given SIProfileHandler to handle requests for the + * given SI profile namespace. The profile will be advertised by disco (unless disabled in + * the ctor). + * @param profile The complete profile namespace, e.g. + * http://jabber.org/protocol/si/profile/file-transfer. + * @param sih The profile handler. + */ + void registerProfile( const std::string& profile, SIProfileHandler* sih ); + + /** + * Un-registers the given profile. + * @param profile The profile's namespace to un-register. + */ + void removeProfile( const std::string& profile ); + + // reimplemented from IqHandler. + virtual bool handleIq( const IQ& iq ); + + // reimplemented from IqHandler. + virtual void handleIqID( const IQ& iq, int context ); + + private: +#ifdef SIMANAGER_TEST + public: +#endif + enum TrackContext + { + OfferSI + }; + + struct TrackStruct + { + std::string sid; + std::string profile; + SIHandler* sih; + }; + typedef std::map TrackMap; + TrackMap m_track; + + ClientBase* m_parent; + + typedef std::map HandlerMap; + HandlerMap m_handlers; + + bool m_advertise; + + }; + +} + +#endif // SIMANAGER_H__ diff --git a/libs/libgloox/siprofileft.cpp b/libs/libgloox/siprofileft.cpp new file mode 100644 index 0000000..4836efd --- /dev/null +++ b/libs/libgloox/siprofileft.cpp @@ -0,0 +1,312 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "siprofileft.h" + +#include "clientbase.h" +#include "siprofilefthandler.h" +#include "simanager.h" +#include "dataform.h" +#include "inbandbytestream.h" +#include "oob.h" +#include "socks5bytestream.h" +#include "socks5bytestreammanager.h" + +#include +#include + +namespace gloox +{ + + SIProfileFT::SIProfileFT( ClientBase* parent, SIProfileFTHandler* sipfth, SIManager* manager, + SOCKS5BytestreamManager* s5Manager ) + : m_parent( parent ), m_manager( manager ), m_handler( sipfth ), + m_socks5Manager( s5Manager ), m_delManager( false ), + m_delS5Manager( false ) + { + if( !m_manager ) + { + m_delManager = true; + m_manager = new SIManager( m_parent ); + } + + m_manager->registerProfile( XMLNS_SI_FT, this ); + + if( !m_socks5Manager ) + { + m_socks5Manager = new SOCKS5BytestreamManager( m_parent, this ); + m_delS5Manager = true; + } + } + + SIProfileFT::~SIProfileFT() + { + m_manager->removeProfile( XMLNS_SI_FT ); + + if( m_delManager ) + delete m_manager; + + if( m_socks5Manager && m_delS5Manager ) + delete m_socks5Manager; + } + + const std::string SIProfileFT::requestFT( const JID& to, const std::string& name, long size, + const std::string& hash, const std::string& desc, + const std::string& date, const std::string& mimetype, + int streamTypes, const JID& from, + const std::string& sid ) + { + if( name.empty() || size <= 0 || !m_manager ) + return EmptyString; + + Tag* file = new Tag( "file", XMLNS, XMLNS_SI_FT ); + file->addAttribute( "name", name ); + file->addAttribute( "size", size ); + if( !hash.empty() ) + file->addAttribute( "hash", hash ); + if( !date.empty() ) + file->addAttribute( "date", date ); + if( !desc.empty() ) + new Tag( file, "desc", desc ); + + Tag* feature = new Tag( "feature", XMLNS, XMLNS_FEATURE_NEG ); + DataForm df( TypeForm ); + DataFormField* dff = df.addField( DataFormField::TypeListSingle, "stream-method" ); + StringMultiMap sm; + if( streamTypes & FTTypeS5B ) + sm.insert( std::make_pair( "s5b", XMLNS_BYTESTREAMS ) ); + if( streamTypes & FTTypeIBB ) + sm.insert( std::make_pair( "ibb", XMLNS_IBB ) ); + if( streamTypes & FTTypeOOB ) + sm.insert( std::make_pair( "oob", XMLNS_IQ_OOB ) ); + dff->setOptions( sm ); + feature->addChild( df.tag() ); + + return m_manager->requestSI( this, to, XMLNS_SI_FT, file, feature, mimetype, from, sid ); + } + + void SIProfileFT::acceptFT( const JID& to, const std::string& sid, StreamType type, const JID& from ) + { + if( !m_manager ) + return; + + if( m_id2sid.find( sid ) == m_id2sid.end() ) + return; + + const std::string& id = m_id2sid[sid]; + + Tag* feature = new Tag( "feature", XMLNS, XMLNS_FEATURE_NEG ); + DataFormField* dff = new DataFormField( "stream-method" ); + switch( type ) + { + case FTTypeAll: + case FTTypeS5B: + dff->setValue( XMLNS_BYTESTREAMS ); + break; + case FTTypeIBB: + dff->setValue( XMLNS_IBB ); + if( m_handler ) + { + InBandBytestream* ibb = new InBandBytestream( m_parent, m_parent->logInstance(), to, + from ? from : m_parent->jid(), sid ); + m_handler->handleFTBytestream( ibb ); + } + break; + case FTTypeOOB: + dff->setValue( XMLNS_IQ_OOB ); + break; + } + DataForm df( TypeSubmit ); + df.addField( dff ); + feature->addChild( df.tag() ); + + m_manager->acceptSI( to, id, 0, feature, from ); + } + + void SIProfileFT::declineFT( const JID& to, const std::string& sid, SIManager::SIError reason, + const std::string& text ) + { + if( m_id2sid.find( sid ) == m_id2sid.end() || !m_manager ) + return; + + m_manager->declineSI( to, m_id2sid[sid], reason, text ); + } + + void SIProfileFT::dispose( Bytestream* bs ) + { + if( bs ) + { + if( bs->type() == Bytestream::S5B && m_socks5Manager ) + m_socks5Manager->dispose( static_cast( bs ) ); + else + delete bs; + } + } + + void SIProfileFT::cancel( Bytestream* bs ) + { + if( !bs ) + return; + + if( m_id2sid.find( bs->sid() ) == m_id2sid.end() || !m_manager ) + return; + + if( bs->type() == Bytestream::S5B && m_socks5Manager ) + m_socks5Manager->rejectSOCKS5Bytestream( bs->sid(), StanzaErrorServiceUnavailable ); + + dispose( bs ); + } + + void SIProfileFT::setStreamHosts( StreamHostList hosts ) + { + if( m_socks5Manager ) + m_socks5Manager->setStreamHosts( hosts ); + } + + void SIProfileFT::addStreamHost( const JID& jid, const std::string& host, int port ) + { + if( m_socks5Manager ) + m_socks5Manager->addStreamHost( jid, host, port ); + } + + void SIProfileFT::handleSIRequest( const JID& from, const JID& to, const std::string& id, + const SIManager::SI& si ) + { + if( si.profile() != XMLNS_SI_FT || !si.tag1() ) + return; + + if( m_handler ) + { + const Tag* t = si.tag1()->findChild( "desc" ); + const std::string& desc = t ? t->cdata() : EmptyString; + + const std::string& mt = si.mimetype(); + int types = 0; + + if( si.tag2() ) + { + const DataForm df( si.tag2()->findChild( "x", XMLNS, XMLNS_X_DATA ) ); + const DataFormField* dff = df.field( "stream-method" ); + + if( dff ) + { + const StringMultiMap& options = dff->options(); + StringMultiMap::const_iterator it = options.begin(); + for( ; it != options.end(); ++it ) + { + if( (*it).second == XMLNS_BYTESTREAMS ) + types |= FTTypeS5B; + else if( (*it).second == XMLNS_IBB ) + types |= FTTypeIBB; + else if( (*it).second == XMLNS_IQ_OOB ) + types |= FTTypeOOB; + } + } + } + + const std::string& sid = si.id(); + m_id2sid[sid] = id; + m_handler->handleFTRequest( from, to, sid, si.tag1()->findAttribute( "name" ), + atol( si.tag1()->findAttribute( "size" ).c_str() ), + si.tag1()->findAttribute( "hash" ), + si.tag1()->findAttribute( "date" ), + mt.empty() ? "binary/octet-stream" : mt, + desc, types ); + } + } + + void SIProfileFT::handleSIRequestResult( const JID& from, const JID& to, const std::string& sid, + const SIManager::SI& si ) + { + if( si.tag2() ) + { + const DataForm df( si.tag2()->findChild( "x", XMLNS, XMLNS_X_DATA ) ); + const DataFormField* dff = df.field( "stream-method" ); + + if( dff ) + { + if( m_socks5Manager && dff->value() == XMLNS_BYTESTREAMS ) + { + // check return value: + m_socks5Manager->requestSOCKS5Bytestream( from, SOCKS5BytestreamManager::S5BTCP, sid, to ); + } + else if( m_handler ) + { + if( dff->value() == XMLNS_IBB ) + { + InBandBytestream* ibb = new InBandBytestream( m_parent, m_parent->logInstance(), + to ? to : m_parent->jid(), from, sid ); + + m_handler->handleFTBytestream( ibb ); + } + else if( dff->value() == XMLNS_IQ_OOB ) + { + const std::string& url = m_handler->handleOOBRequestResult( from, to, sid ); + if( !url.empty() ) + { + const std::string& id = m_parent->getID(); + IQ iq( IQ::Set, from, id ); + if( to ) + iq.setFrom( to ); + + iq.addExtension( new OOB( url, EmptyString, true ) ); + m_parent->send( iq, this, OOBSent ); + } + } + } + } + } + } + + void SIProfileFT::handleIqID( const IQ& /*iq*/, int context ) + { + switch( context ) + { + case OOBSent: +// if( iq->subtype() == IQ::Error ) +// m_handler->handleOOBError + break; + } + } + + void SIProfileFT::handleSIRequestError( const IQ& iq, const std::string& sid ) + { + if( m_handler ) + m_handler->handleFTRequestError( iq, sid ); + } + + void SIProfileFT::handleIncomingBytestreamRequest( const std::string& sid, const JID& /*from*/ ) + { +// TODO: check for valid sid/from tuple + m_socks5Manager->acceptSOCKS5Bytestream( sid ); + } + + void SIProfileFT::handleIncomingBytestream( Bytestream* bs ) + { + if( m_handler ) + m_handler->handleFTBytestream( bs ); + } + + void SIProfileFT::handleOutgoingBytestream( Bytestream* bs ) + { + if( m_handler ) + m_handler->handleFTBytestream( bs ); + } + + void SIProfileFT::handleBytestreamError( const IQ& iq, const std::string& sid ) + { + if( m_handler ) + m_handler->handleFTRequestError( iq, sid ); + } + +} diff --git a/libs/libgloox/siprofileft.h b/libs/libgloox/siprofileft.h new file mode 100644 index 0000000..b83c859 --- /dev/null +++ b/libs/libgloox/siprofileft.h @@ -0,0 +1,341 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef SIPROFILEFT_H__ +#define SIPROFILEFT_H__ + +#include "iqhandler.h" +#include "socks5bytestreammanager.h" +#include "siprofilehandler.h" +#include "sihandler.h" +#include "simanager.h" +#include "bytestreamhandler.h" + +#include +#include + +namespace gloox +{ + + class ClientBase; + class InBandBytestream; + class IQ; + class JID; + class SIProfileFTHandler; + class SOCKS5Bytestream; + + /** + * @brief An implementation of the file transfer SI profile (XEP-0096). + * + * An SIProfileFT object acts as a 'plugin' to the SIManager. SIProfileFT + * manages most of the file transfer functionality. The naming comes from the fact that + * File Transfer (FT) is a profile of Stream Initiation (SI). + * + * Usage: + * + * Create a new SIProfileFT object. It needs a ClientBase -derived object (e.g. Client) + * as well as a SIProfileFTHandler -derived object that will receive file transfer-related events. + * If you already use SI and the SIManager somewhere else, you should pass a pointer to that + * SIManager object as third parameter to SIProfileFT's constructor. + * @code + * class MyFileTransferHandler : public SIProfileFTHandler + * { + * // ... + * }; + * + * Client* client = new Client( ... ); + * // ... + * MyFileTransferHandler* mh = new MyFileTransferHandler( ... ); + * + * SIProfileFT* ft = new SIProfileFT( client, mh ); + * @endcode + * + * You are now, basically, ready to send and receive files. + * + * A couple of notes: + * @li There are two (actually two and a half) possible "techniques" to transfer files + * using SI. The first is using a peer-to-peer SOCKS5 bytestream, optionally via a + * (special) SOCKS5 proxy. + * The second techniques is using an in-band bytestream, i.e. the data is encapsulated in XMPP stanzas + * and sent through the server. + * + * @li To be able to send files using the former method (SOCKS5 bytestreams), you may need + * access to a SOCKS5 bytestream proxy (called StreamHost). This is especially true if either + * or both of sender and receiver are behind NATs or are otherwise blocked from establishing + * direct TCP connections. You should use Disco to query a potential SOCKS5 proxy + * for its host and port parameters and feed that information into SIProfileFT: + * @code + * ft->addStreamHost( JID( "proxy.server.dom" ), "101.102.103.104", 6677 ); + * @endcode + * You should @b not hard-code this information (esp. host/IP and port) into your app since + * the proxy may go down occasionally or vanish completely. + * + * @li In addition to (or even instead of) using external SOCKS5 proxies, you can use a + * SOCKS5BytestreamServer object that gloox provides: + * @code + * SOCKS5BytestreamServer* server = new SOCKS5BytestreamServer( client->logInstance(), 1234 ); + * if( server->listen() != ConnNoError ) + * printf( "port in use\n" ); + * + * ft->addStreamHost( client->jid(), my_ip, 1234 ); + * ft->registerSOCKS5BytestreamServer( server ); + * @endcode + * This listening server should then be integrated into your mainloop to have its + * @link gloox::SOCKS5BytestreamServer::recv() recv() @endlink method called from time to time. + * It is safe to put the server into its own thread. + * + * @li When you finally receive a Bytestream via the SIProfileFTHandler, you will need + * to integrate this bytestream with your mainloop, or put it into a separate thread (if + * occasional blocking is not acceptable). You will need to call + * @link gloox::Bytestream::connect() connect() @endlink on that Bytestream. For SOCKS5 bytestreams, + * this function will try to connect to each of the given StreamHosts and block until it has established + * a connection with one of them (or until all attempts failed). Further, if you want to receive + * a file via the bytestream, you will have to call recv() on the object from time to time. + * For in-band bytestreams, @link gloox::Bytestream::connect() connect() @endlink will send an "open the + * bytestream" request to the contact. + * + * @li For both stream types, + * @link gloox::BytestreamDataHandler::handleBytestreamOpen() BytestreamDataHandler::handleBytestreamOpen() @endlink + * will announce the established bytestream. The stream then is ready to send and receive data. + * + * @li In general, both types of streams can be handled equally, i.e. there's no need to know whether + * the underlying stream really is a SOCKS5Bytestream or an InBandBytestream. + * @link gloox::Bytestream::type() Bytestream::type() @endlink tells anyway. Note, however, that + * sending large amounts of data using in-band bytestreams may trigger rate limiting in some servers. + * + * @li If you e.g. told Client to connect through a @link gloox::ConnectionHTTPProxy HTTP proxy @endlink + * or a @link gloox::ConnectionSOCKS5Proxy SOCKS5 proxy @endlink, or any other ConnectionBase -derived + * method, or even chains thereof, SIProfileFT will use the same connection types with the same + * configuration to connect to the Stream Host/SOCKS5 proxy. If this is inappropriate because you have + * e.g. a local SOCKS5 proxy inside your local network, use SOCKS5Bytestream::setConnectionImpl() to + * override the above default connection(s). + * + * @li Do @b not delete Bytestream objects manually. Use dispose() instead. + * + * @li When using the Client's JID as the first argument to addStreamHost() as in the code snippet + * above, make sure the JID is actually a full JID. If you let the server pick a resource, the call + * to Client::jid() needs to be made @b after the connection has been established and authenticated, + * because only then Client knows its full JID. This is generally a good idea, since the server + * may choose to change the resource, even if you provided one at login. + * + * @li The interal SOCKS5BytestreamServer will obviously not work across NATs. + * + * @li Using addStreamHost(), you can add as many potential StreamHosts as you like. However, you + * should add the best options (e.g. the local SOCKS5BytestreamServer) first. + * + * When cleaning up, delete the objectes you created above in the opposite order of + * creation: + * + * @code + * delete server + * delete ft; + * delete client; + * @endcode + * + * For usage examples see src/examples/ft_send.cpp and src/examples/ft_recv.cpp. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API SIProfileFT : public SIProfileHandler, public SIHandler, + public BytestreamHandler, public IqHandler + { + public: + /** + * Supported stream types. + */ + enum StreamType + { + FTTypeS5B = 1, /**< SOCKS5 Bytestreams. */ + FTTypeIBB = 2, /**< In-Band Bytestreams. */ + FTTypeOOB = 4, /**< Out-of-Band Data. */ + FTTypeAll = 0xFF /**< All types. */ + }; + + /** + * Constructor. + * @param parent The ClientBase to use for signaling. + * @param sipfth The SIProfileFTHandler to receive events. + * @param manager An optional SIManager to register with. If this is zero, SIProfileFT + * will create its own SIManager. You should pass a valid SIManager here if you are + * already using one with the @c parent ClientBase above. + * @param s5Manager An optional SOCKS5BytestreamManager to use. If this is zero, SIProfileFT + * will create its own SOCKS5BytestreamManager. You should pass a valid SOCKS5BytestreamManager + * here if you are already using one with the @c parent ClientBase above. + * @note If you passed a SIManager and/or SOCKS5BytestreamManager and/or InBandBytestreamManager + * to SIProfileFT's constructor, these objects will @b not be deleted on desctruction of SIProfileFT. + */ + SIProfileFT( ClientBase* parent, SIProfileFTHandler* sipfth, SIManager* manager = 0, + SOCKS5BytestreamManager* s5Manager = 0 ); + + /** + * Virtual destructor. + */ + virtual ~SIProfileFT(); + + /** + * Starts negotiating a file transfer with a remote entity. + * @param to The entity to send the file to. Must be a full JID. + * @param name The file's name. Mandatory and must not be empty. + * @param size The file's size. Mandatory and must be > 0. + * @param hash The file content's MD5 hash. + * @param desc A description. + * @param date The file's last modification date/time. See XEP-0082 for details. + * @param mimetype The file's mime-type. Defaults to 'binary/octet-stream' if empty. + * @param streamTypes ORed StreamType that can be used for this transfer. + * @param from An optional 'from' address to stamp outgoing requests with. + * Used in component scenario only. Defaults to empty JID. + * @param sid Optionally specify a stream ID (SID). If empty, one will be generated. + * @return The requested stream's ID (SID). Empty if conditions above (file name, size) + * are not met. + */ + const std::string requestFT( const JID& to, const std::string& name, long size, + const std::string& hash = EmptyString, + const std::string& desc = EmptyString, + const std::string& date = EmptyString, + const std::string& mimetype = EmptyString, + int streamTypes = FTTypeAll, + const JID& from = JID(), + const std::string& sid = EmptyString ); + + /** + * Call this function to accept a file transfer request previously announced by means of + * @link gloox::SIProfileFTHandler::handleFTRequest() SIProfileFTHandler::handleFTRequest() @endlink. + * @param to The requestor. + * @param sid The request's sid, as passed to SIProfileHandler::handleFTRequest(). + * @param type The desired stream type to use for this file transfer. Defaults to + * SOCKS5 Bytestream. You should not use @c FTTypeAll here. + * @param from An optional 'from' address to stamp outgoing stanzas with. + * Used in component scenario only. Defaults to empty JID. + */ + void acceptFT( const JID& to, const std::string& sid, + StreamType type = FTTypeS5B, const JID& from = JID() ); + + /** + * Call this function to decline a FT request previously announced by means of + * @link gloox::SIProfileFTHandler::handleFTRequest() SIProfileFTHandler::handleFTRequest() @endlink. + * @param to The requestor. + * @param sid The request's sid, as passed to SIProfileFTHandler::handleFTRequest(). + * @param reason The reason for the reject. + * @param text An optional human-readable text explaining the decline. + */ + void declineFT( const JID& to, const std::string& sid, SIManager::SIError reason, + const std::string& text = EmptyString ); + + /** + * Cancels the given bytestream. Most useful for SOCKS5 bytestreams where no proxies could be found. + * The given Bytestream will be deleted. + * @param bs The Bytestream to cancel. + * @note Can also be used with IBB. + */ + void cancel( Bytestream* bs ); + + /** + * To get rid of a bytestream (i.e., close and delete it), call this function. + * The remote entity will be notified about the closing of the stream. + * @param bs The bytestream to dispose. It will be deleted here. + */ + void dispose( Bytestream* bs ); + + /** + * Registers a handler that will be informed about incoming file transfer + * requests, i.e. when a remote entity wishes to send a file. + * @param sipfth A SIProfileFTHandler to register. Only one handler can be registered + * at any one time. + */ + void registerSIProfileFTHandler( SIProfileFTHandler* sipfth ) { m_handler = sipfth; } + + /** + * Removes the previously registered file transfer request handler. + */ + void removeSIProfileFTHandler() { m_handler = 0; } + + /** + * Sets a list of StreamHosts that will be used for subsequent SOCKS5 bytestream requests. + * @note At least one StreamHost is required. + * @param hosts A list of StreamHosts. + */ + void setStreamHosts( StreamHostList hosts ); + + /** + * Adds one StreamHost to the list of SOCKS5 StreamHosts. + * @param jid The StreamHost's JID. + * @param host The StreamHost's hostname. + * @param port The StreamHost's port. + */ + void addStreamHost( const JID& jid, const std::string& host, int port ); + + /** + * Tells the interal SOCKS5BytestreamManager which SOCKS5BytestreamServer handles + * peer-2-peer SOCKS5 bytestreams. + * @param server The SOCKS5BytestreamServer to use. + */ + void registerSOCKS5BytestreamServer( SOCKS5BytestreamServer* server ) + { if( m_socks5Manager ) m_socks5Manager->registerSOCKS5BytestreamServer( server ); } + + /** + * Un-registers any local SOCKS5BytestreamServer. + */ + void removeSOCKS5BytestreamServer() + { if( m_socks5Manager ) m_socks5Manager->removeSOCKS5BytestreamServer(); } + + // reimplemented from SIProfileHandler + virtual void handleSIRequest( const JID& from, const JID& to, const std::string& id, + const SIManager::SI& si ); + + // reimplemented from SIHandler + virtual void handleSIRequestResult( const JID& from, const JID& to, const std::string& sid, + const SIManager::SI& si ); + + // reimplemented from SIHandler + virtual void handleSIRequestError( const IQ& iq, const std::string& sid ); + + // reimplemented from BytestreamHandler + virtual void handleIncomingBytestreamRequest( const std::string& sid, const JID& from ); + + // reimplemented from BytestreamHandler + virtual void handleIncomingBytestream( Bytestream* bs ); + + // reimplemented from BytestreamHandler + virtual void handleOutgoingBytestream( Bytestream* bs ); + + // reimplemented from BytestreamHandler + virtual void handleBytestreamError( const IQ& iq, const std::string& sid ); + + // reimplemented from IqHandler. + virtual bool handleIq( const IQ& iq ) { (void)iq; return false; } + + // reimplemented from IqHandler. + virtual void handleIqID( const IQ& iq, int context ); + + private: + + enum TrackEnum + { + OOBSent + }; + + ClientBase* m_parent; + SIManager* m_manager; + SIProfileFTHandler* m_handler; + SOCKS5BytestreamManager* m_socks5Manager; + StreamHostList m_hosts; + StringMap m_id2sid; + bool m_delManager; + bool m_delS5Manager; + + }; + +} + +#endif // SIPROFILEFT_H__ diff --git a/libs/libgloox/siprofilefthandler.h b/libs/libgloox/siprofilefthandler.h new file mode 100644 index 0000000..3a7c0f4 --- /dev/null +++ b/libs/libgloox/siprofilefthandler.h @@ -0,0 +1,104 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef SIPROFILEFTHANDLER_H__ +#define SIPROFILEFTHANDLER_H__ + +#include "jid.h" + +#include + +namespace gloox +{ + + class JID; + class IQ; + class Bytestream; + + /** + * @brief An abstract base class to handle file transfer (FT) requests. + * + * See SIProfileFT for more information regarding file transfer. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API SIProfileFTHandler + { + + public: + /** + * Virtual destructor. + */ + virtual ~SIProfileFTHandler() {} + + /** + * This function is called to handle incoming file transfer requests, i.e. a remote entity requested + * to send a file to you. You should use either SIProfileFT::acceptFT() or + * SIProfileFT::declineFT() to accept or reject the request, respectively. + * @param from The file transfer requestor. + * @param to The file transfer recipient. Usuall oneself. Used in component scenario. + * @param sid The requested stream's ID. This sid MUST be supplied to SIProfileFT::acceptFT() + * and SIProfileFT::declineFT(), respectively. + * @param name The file name. + * @param size The file size. + * @param hash The file content's MD5 sum. + * @param date The file's last modification time. + * @param mimetype The file's mime-type. + * @param desc The file's description. + * @param stypes An ORed list of @link gloox::SIProfileFT::StreamType SIProfileFT::StreamType @endlink + * indicating the StreamTypes the initiator supports. + */ + virtual void handleFTRequest( const JID& from, const JID& to, const std::string& sid, + const std::string& name, long size, const std::string& hash, + const std::string& date, const std::string& mimetype, + const std::string& desc, int stypes ) = 0; + + /** + * This function is called to handle a request error or decline. + * @param iq The complete error stanza. + * @param sid The request's SID. + */ + virtual void handleFTRequestError( const IQ& iq, const std::string& sid ) = 0; + + /** + * This function is called to pass a negotiated bytestream (SOCKS5 or IBB). + * The bytestream is not yet open and not ready to send/receive data. + * @note To initialize the bytestream and to prepare it for data transfer + * do the following, preferable in that order: + * @li register a BytestreamDataHandler with the Bytestream, + * @li set up a separate thread for the bytestream or integrate it into + * your main loop, + * @li call its connect() method and check the return value. + * To not block your application while the data transfer and/or the connection + * attempts last, you most likely want to put the bytestream into its own + * thread or process (before calling connect() on it). It is safe to do so + * without additional synchronization. + * @param bs The bytestream. + */ + virtual void handleFTBytestream( Bytestream* bs ) = 0; + + /** + * This function is called if the contact chose OOB as the mechanism. + * @param from The remote contact's JID. + * @param to The local recipient's JID. Usually oneself. Used in component scenario. + * @param sid The stream's ID. + * @return The file's URL. + */ + virtual const std::string handleOOBRequestResult( const JID& from, const JID& to, const std::string& sid ) = 0; + + }; + +} + +#endif // SIPROFILEFTHANDLER_H__ diff --git a/libs/libgloox/siprofilehandler.h b/libs/libgloox/siprofilehandler.h new file mode 100644 index 0000000..9af75b4 --- /dev/null +++ b/libs/libgloox/siprofilehandler.h @@ -0,0 +1,62 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef SIPROFILEHANDLER_H__ +#define SIPROFILEHANDLER_H__ + +#include "jid.h" +#include "simanager.h" + +#include + +namespace gloox +{ + + class Tag; + class JID; + + /** + * @brief An abstract base class to handle SI requests for a specific profile, e.g. file transfer. + * + * You should usually not need to use this class directly, unless your profile is not supported + * by gloox. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API SIProfileHandler + { + + public: + /** + * Virtual destructor. + */ + virtual ~SIProfileHandler() {} + + /** + * This function is called to handle incoming SI requests, i.e. a remote entity requested + * a stream to send a file to you. You should use either SIManager::acceptSI() or + * SIManager::declineSI() to accept or reject the request, respectively. + * @param from The SI requestor. + * @param to The SI recipient, usually oneself. Used in component scenario. + * @param id The request's id (@b not the stream's id). This id MUST be supplied to either + * SIManager::acceptSI() or SIManager::declineSI(). + * @param si The request's complete SI. + */ + virtual void handleSIRequest( const JID& from, const JID& to, const std::string& id, const SIManager::SI& si ) = 0; + + }; + +} + +#endif // SIPROFILEHANDLER_H__ diff --git a/libs/libgloox/socks5bytestream.cpp b/libs/libgloox/socks5bytestream.cpp new file mode 100644 index 0000000..e4d80b3 --- /dev/null +++ b/libs/libgloox/socks5bytestream.cpp @@ -0,0 +1,155 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "socks5bytestream.h" +#include "bytestreamdatahandler.h" +#include "clientbase.h" +#include "connectionbase.h" +#include "connectionsocks5proxy.h" +#include "sha.h" +#include "logsink.h" + +namespace gloox +{ + + SOCKS5Bytestream::SOCKS5Bytestream( SOCKS5BytestreamManager* manager, ConnectionBase* connection, + LogSink& logInstance, const JID& initiator, const JID& target, + const std::string& sid ) + : Bytestream( Bytestream::S5B, logInstance, initiator, target, sid ), + m_manager( manager ), m_connection( 0 ), m_socks5( 0 ), m_connected( false ) + { + if( connection && connection->state() == StateConnected ) + m_open = true; + + setConnectionImpl( connection ); + } + + SOCKS5Bytestream::~SOCKS5Bytestream() + { + if( m_open ) + close(); + + if( m_socks5 ) + delete m_socks5; + } + + void SOCKS5Bytestream::setConnectionImpl( ConnectionBase* connection ) + { + if( m_socks5 ) + delete m_socks5; // deletes m_connection as well + + m_connection = connection; + + SHA sha; + sha.feed( m_sid ); + sha.feed( m_initiator.full() ); + sha.feed( m_target.full() ); + m_socks5 = new ConnectionSOCKS5Proxy( this, connection, m_logInstance, sha.hex(), 0 ); + } + + bool SOCKS5Bytestream::connect() + { + if( !m_connection || !m_socks5 || !m_manager ) + return false; + + if( m_open ) + return true; + + StreamHostList::const_iterator it = m_hosts.begin(); + for( ; it != m_hosts.end(); ++it ) + { + if( ++it == m_hosts.end() ) + m_connected = true; + --it; // FIXME ++it followed by --it is kinda ugly + m_connection->setServer( (*it).host, (*it).port ); + if( m_socks5->connect() == ConnNoError ) + { + m_proxy = (*it).jid; + m_connected = true; + return true; + } + } + + m_manager->acknowledgeStreamHost( false, JID(), EmptyString ); + return false; + } + + bool SOCKS5Bytestream::send( const std::string& data ) + { + if( !m_open || !m_connection || !m_socks5 || !m_manager ) + return false; + + return m_socks5->send( data ); + } + + ConnectionError SOCKS5Bytestream::recv( int timeout ) + { + if( !m_connection || !m_socks5 || !m_manager ) + return ConnNotConnected; + + return m_socks5->recv( timeout ); + } + + void SOCKS5Bytestream::activate() + { + m_open = true; + if( m_handler ) + m_handler->handleBytestreamOpen( this ); + } + + void SOCKS5Bytestream::close() + { + if( m_open && m_handler ) + { + m_open = false; + m_connected = false; + m_socks5->disconnect(); + m_handler->handleBytestreamClose( this ); + } + } + + void SOCKS5Bytestream::handleReceivedData( const ConnectionBase* /*connection*/, const std::string& data ) + { + if( !m_handler ) + return; + + if( !m_open ) + { + m_open = true; + m_handler->handleBytestreamOpen( this ); + } + +// if( !m_open && data.length() == 2 && data[0] == 0x05 && data[1] == 0x00 ) +// { +// printf( "received acknowleding zero byte, stream is now open\n" ); +// m_open = true; +// m_handler->handleBytestream5Open( this ); +// return; +// } + + if( m_open ) + m_handler->handleBytestreamData( this, data ); + } + + void SOCKS5Bytestream::handleConnect( const ConnectionBase* /*connection*/ ) + { + m_manager->acknowledgeStreamHost( true, m_proxy, m_sid ); + } + + void SOCKS5Bytestream::handleDisconnect( const ConnectionBase* /*connection*/, ConnectionError /*reason*/ ) + { + if( m_handler && m_connected ) + m_handler->handleBytestreamClose( this ); + } + +} diff --git a/libs/libgloox/socks5bytestream.h b/libs/libgloox/socks5bytestream.h new file mode 100644 index 0000000..d2a6201 --- /dev/null +++ b/libs/libgloox/socks5bytestream.h @@ -0,0 +1,136 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef SOCKS5BYTESTREAM_H__ +#define SOCKS5BYTESTREAM_H__ + +#include "bytestream.h" +#include "gloox.h" +#include "socks5bytestreammanager.h" +#include "connectiondatahandler.h" + +#include + +namespace gloox +{ + + class SOCKS5BytestreamDataHandler; + class ConnectionBase; + class LogSink; + + /** + * @brief An implementation of a single SOCKS5 Bytestream (XEP-0065). + * + * One instance of this class handles one bytestream. + * + * See SOCKS5BytestreamManager for a detailed description on how to implement + * SOCKS5 Bytestreams in your application. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API SOCKS5Bytestream : public ConnectionDataHandler, public Bytestream + { + friend class SOCKS5BytestreamManager; + + public: + /** + * Virtual destructor. + */ + virtual ~SOCKS5Bytestream(); + + /** + * This function starts the connection process. That is, it attempts to connect + * to each of the available StreamHosts. Once a working StreamHosts is found, the + * SOCKS5BytestreamManager is notified and the function returns. + * @return @b True if a connection to a StreamHost could be established, @b false + * otherwise. + * @note If @b false is returned you should hand this SOCKS5Bytestream object + * to SOCKS5BytestreamManager::dispose() for deletion. + * @note Make sure you have a SOCKS5BytestreamDataHandler registered (using + * registerSOCKS5BytestreamDataHandler()) before calling this function. + */ + virtual bool connect(); + + /** + * Closes the bytestream. + */ + virtual void close(); + + /** + * Use this function to send a chunk of data over an open bytestream. There is + * no limit for the size of the chunk (other than your machine's memory). + * If the stream is not open or has been closed again + * (by the remote entity or locally), nothing is sent and @b false is returned. + * @param data The block of data to send. + * @return @b True if the data has been sent (no guarantee of receipt), @b false + * in case of an error. + */ + virtual bool send( const std::string& data ); + + /** + * Call this function repeatedly to receive data from the socket. You should even do this + * if you use the bytestream to merely @b send data. + * @param timeout The timeout to use for select in microseconds. Default of -1 means blocking. + * @return The state of the connection. + */ + virtual ConnectionError recv( int timeout = -1 ); + + /** + * Sets the connection to use. + * @param connection The connection. The bytestream will own the connection, any + * previously set connection gets deleted. + */ + void setConnectionImpl( ConnectionBase* connection ); + + /** + * This function returns the concrete connection implementation currently in use. + * @return The concrete connection implementation. + * @since 0.9.7 + */ + ConnectionBase* connectionImpl( ) { return m_connection; } + + /** + * Use this function to set the available StreamHosts. Usually you should not need to + * use this function directly. + */ + void setStreamHosts( const StreamHostList& hosts ) { m_hosts = hosts; } + + // reimplemented from ConnectionDataHandler + virtual void handleReceivedData( const ConnectionBase* connection, const std::string& data ); + + // reimplemented from ConnectionDataHandler + virtual void handleConnect( const ConnectionBase* connection ); + + // reimplemented from ConnectionDataHandler + virtual void handleDisconnect( const ConnectionBase* connection, ConnectionError reason ); + + private: + SOCKS5Bytestream( SOCKS5BytestreamManager* manager, ConnectionBase* connection, + LogSink& logInstance, const JID& initiator, const JID& target, + const std::string& sid ); + void activate(); + + SOCKS5BytestreamManager* m_manager; + ConnectionBase* m_connection; + ConnectionBase* m_socks5; + JID m_proxy; + bool m_connected; + + StreamHostList m_hosts; + + }; + +} + +#endif // SOCKS5BYTESTREAM_H__ diff --git a/libs/libgloox/socks5bytestreammanager.cpp b/libs/libgloox/socks5bytestreammanager.cpp new file mode 100644 index 0000000..e885688 --- /dev/null +++ b/libs/libgloox/socks5bytestreammanager.cpp @@ -0,0 +1,486 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "bytestreamhandler.h" +#include "socks5bytestreammanager.h" +#include "socks5bytestreamserver.h" +#include "socks5bytestream.h" +#include "clientbase.h" +#include "disco.h" +#include "error.h" +#include "connectionbase.h" +#include "sha.h" +#include "util.h" + +#include + +namespace gloox +{ + + // ---- SOCKS5BytestreamManager::Query ---- + static const char* s5bModeValues[] = + { + "tcp", "udp" + }; + + static inline const char* modeString( SOCKS5BytestreamManager::S5BMode mode ) + { + return s5bModeValues[mode]; + } + + SOCKS5BytestreamManager::Query::Query() + : StanzaExtension( ExtS5BQuery ), m_type( TypeInvalid ) + { + } + + SOCKS5BytestreamManager::Query::Query( const std::string& sid, S5BMode mode, + const StreamHostList& hosts ) + : StanzaExtension( ExtS5BQuery ), m_sid( sid ), m_mode( mode ), m_hosts( hosts ), m_type( TypeSH ) + { + } + + SOCKS5BytestreamManager::Query::Query( const JID& jid, const std::string& sid, bool activate ) + : StanzaExtension( ExtS5BQuery ), m_sid( sid ), m_jid( jid ), m_type( activate ? TypeA : TypeSHU ) + { + } + + SOCKS5BytestreamManager::Query::Query( const Tag* tag ) + : StanzaExtension( ExtS5BQuery ), m_type( TypeInvalid ) + { + if( !tag || tag->name() != "query" || tag->xmlns() != XMLNS_BYTESTREAMS + /*|| !tag->hasAttribute( "sid" )*/ ) + return; + + m_sid = tag->findAttribute( "sid" ); + m_mode = static_cast( util::deflookup( tag->findAttribute( "mode" ), s5bModeValues, S5BTCP ) ); + + const TagList& l = tag->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + if( (*it)->name() == "streamhost" && (*it)->hasAttribute( "jid" ) + && (*it)->hasAttribute( "host" ) && (*it)->hasAttribute( "port" ) ) + { + m_type = TypeSH; + StreamHost sh; + sh.jid = (*it)->findAttribute( "jid" ); + sh.host = (*it)->findAttribute( "host" ); + sh.port = atoi( (*it)->findAttribute( "port" ).c_str() ); + m_hosts.push_back( sh ); + } + else if( (*it)->name() == "streamhost-used" ) + { + m_type = TypeSHU; + m_jid = (*it)->findAttribute( "jid" ); + } + else if( (*it)->name() == "activate" ) + { + m_type = TypeA; + m_jid = (*it)->cdata(); + } + } + } + + SOCKS5BytestreamManager::Query::~Query() + { + } + + const std::string& SOCKS5BytestreamManager::Query::filterString() const + { + static const std::string filter = "/iq/query[@xmlns='" + XMLNS_BYTESTREAMS + "']"; + return filter; + } + + Tag* SOCKS5BytestreamManager::Query::tag() const + { + if( m_type == TypeInvalid /*|| m_sid.empty()*/ ) + return 0; + + Tag* t = new Tag( "query" ); + t->setXmlns( XMLNS_BYTESTREAMS ); + t->addAttribute( "sid", m_sid ); + switch( m_type ) + { + case TypeSH: + { + t->addAttribute( "mode", util::deflookup( m_mode, s5bModeValues, "tcp" ) ); + StreamHostList::const_iterator it = m_hosts.begin(); + for( ; it != m_hosts.end(); ++it ) + { + Tag* s = new Tag( t, "streamhost" ); + s->addAttribute( "jid", (*it).jid.full() ); + s->addAttribute( "host", (*it).host ); + s->addAttribute( "port", (*it).port ); + } + break; + } + case TypeSHU: + { + Tag* s = new Tag( t, "streamhost-used" ); + s->addAttribute( "jid", m_jid.full() ); + break; + } + case TypeA: + { + Tag* c = new Tag( t, "activate" ); + c->setCData( m_jid.full() ); + break; + } + default: + break; + } + + return t; + } + // ---- ~SOCKS5BytestreamManager::Query ---- + + // ---- SOCKS5BytestreamManager ---- + SOCKS5BytestreamManager::SOCKS5BytestreamManager( ClientBase* parent, BytestreamHandler* s5bh ) + : m_parent( parent ), m_socks5BytestreamHandler( s5bh ), m_server( 0 ) + { + if( m_parent ) + { + m_parent->registerStanzaExtension( new Query() ); + m_parent->registerIqHandler( this, ExtS5BQuery ); + } + } + + SOCKS5BytestreamManager::~SOCKS5BytestreamManager() + { + if( m_parent ) + { + m_parent->removeIqHandler( this, ExtS5BQuery ); + m_parent->removeIDHandler( this ); + } + + util::clearMap( m_s5bMap ); + } + + void SOCKS5BytestreamManager::addStreamHost( const JID& jid, const std::string& host, int port ) + { + StreamHost sh; + sh.jid = jid; + sh.host = host; + sh.port = port; + m_hosts.push_back( sh ); + } + + bool SOCKS5BytestreamManager::requestSOCKS5Bytestream( const JID& to, S5BMode mode, + const std::string& sid, + const JID& from ) + { + if( !m_parent ) + { + m_parent->logInstance().warn( LogAreaClassS5BManager, + "No parent (ClientBase) set, cannot request bytestream." ); + return false; + } + + if( m_hosts.empty() ) + { + m_parent->logInstance().warn( LogAreaClassS5BManager, + "No stream hosts set, cannot request bytestream." ); + return false; + } + + const std::string& msid = sid.empty() ? m_parent->getID() : sid; + const std::string& id = m_parent->getID(); + IQ iq( IQ::Set, to, id ); + iq.addExtension( new Query( msid, mode, m_hosts ) ); + if( from ) + iq.setFrom( from ); + + if( m_server ) + { + SHA sha; + sha.feed( msid ); + if( from ) + sha.feed( from.full() ); + else + sha.feed( m_parent->jid().full() ); + sha.feed( to.full() ); + m_server->registerHash( sha.hex() ); + } + + AsyncS5BItem asi; + asi.sHosts = m_hosts; + asi.id = id; + asi.from = to; + asi.to = from ? from : m_parent->jid(); + asi.incoming = false; + m_asyncTrackMap[msid] = asi; + + m_trackMap[id] = msid; + m_parent->send( iq, this, S5BOpenStream ); + + return true; + } + + void SOCKS5BytestreamManager::acknowledgeStreamHost( bool success, const JID& jid, + const std::string& sid ) + { + AsyncTrackMap::const_iterator it = m_asyncTrackMap.find( sid ); + if( it == m_asyncTrackMap.end() || !m_parent ) + return; + + const AsyncS5BItem& item = (*it).second; + + IQ* iq = 0; + + if( item.incoming ) + { + iq = new IQ( IQ::Result, item.from.full(), item.id ); + if( item.to ) + iq->setFrom( item.to ); + + if( success ) + iq->addExtension( new Query( jid, sid, false ) ); + else + iq->addExtension( new Error( StanzaErrorTypeCancel, StanzaErrorItemNotFound ) ); + + m_parent->send( *iq ); + } + else + { + if( success ) + { + const std::string& id = m_parent->getID(); + iq = new IQ( IQ::Set, jid.full(), id ); + iq->addExtension( new Query( item.from, sid, true ) ); + + m_trackMap[id] = sid; + m_parent->send( *iq, this, S5BActivateStream ); + } + } + + delete iq; + } + + bool SOCKS5BytestreamManager::handleIq( const IQ& iq ) + { + const Query* q = iq.findExtension( ExtS5BQuery ); + if( !q || !m_socks5BytestreamHandler + || m_trackMap.find( iq.id() ) != m_trackMap.end() ) + return false; + + switch( iq.subtype() ) + { + case IQ::Set: + { + const std::string& sid = q->sid(); +// FIXME What is haveStream() good for? + if( /*haveStream( iq.from() ) ||*/ sid.empty() || q->mode() == S5BUDP ) + { + rejectSOCKS5Bytestream( iq.from(), iq.id(), StanzaErrorNotAcceptable ); + return true; + } + AsyncS5BItem asi; + asi.sHosts = q->hosts(); + asi.id = iq.id(); + asi.from = iq.from(); + asi.to = iq.to(); + asi.incoming = true; + m_asyncTrackMap[sid] = asi; + m_socks5BytestreamHandler->handleIncomingBytestreamRequest( sid, iq.from() ); + break; + } + case IQ::Error: + m_socks5BytestreamHandler->handleBytestreamError( iq, EmptyString ); + break; + default: + break; + } + + return true; + } + + const StreamHost* SOCKS5BytestreamManager::findProxy( const JID& from, const std::string& hostjid, + const std::string& sid ) + { + AsyncTrackMap::const_iterator it = m_asyncTrackMap.find( sid ); + if( it == m_asyncTrackMap.end() ) + return 0; + + if( (*it).second.from == from ) + { + StreamHostList::const_iterator it2 = (*it).second.sHosts.begin(); + for( ; it2 != (*it).second.sHosts.end(); ++it2 ) + { + if( (*it2).jid == hostjid ) + { + return &(*it2); + } + } + } + + return 0; + } + + bool SOCKS5BytestreamManager::haveStream( const JID& from ) + { + S5BMap::const_iterator it = m_s5bMap.begin(); + for( ; it != m_s5bMap.end(); ++it ) + { + if( (*it).second && (*it).second->target() == from ) + return true; + } + return false; + } + + void SOCKS5BytestreamManager::acceptSOCKS5Bytestream( const std::string& sid ) + { + AsyncTrackMap::iterator it = m_asyncTrackMap.find( sid ); + if( it == m_asyncTrackMap.end() || !m_socks5BytestreamHandler ) + return; + + SOCKS5Bytestream* s5b = new SOCKS5Bytestream( this, m_parent->connectionImpl()->newInstance(), + m_parent->logInstance(), + (*it).second.from, (*it).second.to, sid ); + s5b->setStreamHosts( (*it).second.sHosts ); + m_s5bMap[sid] = s5b; + m_socks5BytestreamHandler->handleIncomingBytestream( s5b ); + } + + void SOCKS5BytestreamManager::rejectSOCKS5Bytestream( const std::string& sid, StanzaError reason ) + { + AsyncTrackMap::iterator it = m_asyncTrackMap.find( sid ); + if( it != m_asyncTrackMap.end() ) + { + rejectSOCKS5Bytestream( (*it).second.from, (*it).second.id, reason ); + m_asyncTrackMap.erase( it ); + } + } + + void SOCKS5BytestreamManager::rejectSOCKS5Bytestream( const JID& from, + const std::string& id, + StanzaError reason ) + { + IQ iq( IQ::Error, from, id ); + + switch( reason ) + { + case StanzaErrorForbidden: + case StanzaErrorNotAcceptable: + { + iq.addExtension( new Error( StanzaErrorTypeAuth, reason ) ); + break; + } + case StanzaErrorFeatureNotImplemented: + case StanzaErrorNotAllowed: + default: + { + iq.addExtension( new Error( StanzaErrorTypeCancel, reason ) ); + break; + } + } + + m_parent->send( iq ); + } + + void SOCKS5BytestreamManager::handleIqID( const IQ& iq, int context ) + { + StringMap::iterator it = m_trackMap.find( iq.id() ); + if( it == m_trackMap.end() ) + return; + + switch( context ) + { + case S5BOpenStream: + { + switch( iq.subtype() ) + { + case IQ::Result: + { + const Query* q = iq.findExtension( ExtS5BQuery ); + if( q && m_socks5BytestreamHandler ) + { + const std::string& proxy = q->jid().full(); + const StreamHost* sh = findProxy( iq.from(), proxy, (*it).second ); + if( sh ) + { + SOCKS5Bytestream* s5b = 0; + bool selfProxy = ( proxy == m_parent->jid().full() && m_server ); + if( selfProxy ) + { + SHA sha; + sha.feed( (*it).second ); + sha.feed( iq.to().full() ); + sha.feed( iq.from().full() ); + s5b = new SOCKS5Bytestream( this, m_server->getConnection( sha.hex() ), + m_parent->logInstance(), + iq.to(), iq.from(), + (*it).second ); + } + else + { + s5b = new SOCKS5Bytestream( this, m_parent->connectionImpl()->newInstance(), + m_parent->logInstance(), + iq.to(), iq.from(), + (*it).second ); + s5b->setStreamHosts( StreamHostList( 1, *sh ) ); + } + m_s5bMap[(*it).second] = s5b; + m_socks5BytestreamHandler->handleOutgoingBytestream( s5b ); + if( selfProxy ) + s5b->activate(); + } + } + break; + } + case IQ::Error: + m_socks5BytestreamHandler->handleBytestreamError( iq, (*it).second ); + break; + default: + break; + } + break; + } + case S5BActivateStream: + { + switch( iq.subtype() ) + { + case IQ::Result: + { + S5BMap::const_iterator it5 = m_s5bMap.find( (*it).second ); + if( it5 != m_s5bMap.end() ) + (*it5).second->activate(); + break; + } + case IQ::Error: + m_socks5BytestreamHandler->handleBytestreamError( iq, (*it).second ); + break; + default: + break; + } + break; + } + default: + break; + } + m_trackMap.erase( it ); + } + + bool SOCKS5BytestreamManager::dispose( SOCKS5Bytestream* s5b ) + { + S5BMap::iterator it = m_s5bMap.find( s5b->sid() ); + if( it != m_s5bMap.end() ) + { + delete s5b; + m_s5bMap.erase( it ); + return true; + } + + return false; + } + +} diff --git a/libs/libgloox/socks5bytestreammanager.h b/libs/libgloox/socks5bytestreammanager.h new file mode 100644 index 0000000..49fbebb --- /dev/null +++ b/libs/libgloox/socks5bytestreammanager.h @@ -0,0 +1,307 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef SOCKS5BYTESTREAMMANAGER_H__ +#define SOCKS5BYTESTREAMMANAGER_H__ + +#include "iqhandler.h" +#include "jid.h" +#include "stanzaextension.h" + +namespace gloox +{ + + class BytestreamHandler; + class SOCKS5BytestreamServer; + class SOCKS5Bytestream; + class ClientBase; + + /** + * Describes a single StreamHost. + */ + struct StreamHost + { + JID jid; /**< The StreamHost's JID. */ + std::string host; /**< The StreamHost's IP or host name. */ + int port; /**< The StreamHost's port. */ +// std::string zeroconf; /**< A zeroconf identifier. */ + }; + + /** + * A list of StreamHosts. + */ + typedef std::list StreamHostList; + + /** + * @brief An SOCKS5BytestreamManager dispatches SOCKS5 Bytestreams. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API SOCKS5BytestreamManager : public IqHandler + { + + friend class SOCKS5Bytestream; + + public: + + /** + * Supported transport layer protocols. + */ + enum S5BMode + { + S5BTCP, /**< Use TCP on the transport layer. */ + S5BUDP, /**< Use UDP on the transport layer. Not currently supported. */ + S5BInvalid /**< Invalid mode. */ + }; + + /** + * Constructs a new SOCKS5BytestreamManager. + * @param parent The ClientBase to use for sending data. + * @param s5bh A SOCKS5BytestreamManager -derived object that will receive + * incoming and outgoing SOCKS5Bytestreams. + */ + SOCKS5BytestreamManager( ClientBase* parent, BytestreamHandler* s5bh ); + + /** + * Virtual destructor. + */ + virtual ~SOCKS5BytestreamManager(); + + /** + * Sets a list of StreamHosts that will be used for subsequent bytestream requests. + * @note At least one StreamHost is required. + * @param hosts A list of StreamHosts. + */ + void setStreamHosts( StreamHostList hosts ) { m_hosts = hosts; } + + /** + * Adds one StreamHost to the list of StreamHosts. + * @param jid The StreamHost's JID. + * @param host The StreamHost's hostname. + * @param port The StreamHost's port. + */ + void addStreamHost( const JID& jid, const std::string& host, int port ); + + /** + * This function requests a bytestream with the remote entity. + * Data can only be sent over an open stream. Use isOpen() to find out what the stream's + * current state is. However, successful opening/initiation will be announced by means of the + * BytestreamHandler interface. Multiple bytestreams (even per JID) can be initiated + * without waiting for success. + * @param to The recipient of the requested bytestream. + * @param mode The desired transport layer protocol. + * @param sid The bytestream's stream ID, if previously negotiated e.g. using SI (XEP-0095). + * @param from An optional 'from' address to stamp outgoing + * requests with. Only useful in component scenarios. Defaults to empty JID. + * @return @b False in case of an error, @b true otherwise. A return value of @b true does + * @b not indicate that the bytestream has been opened. This is announced by means of the + * BytestreamHandler. + */ + bool requestSOCKS5Bytestream( const JID& to, S5BMode mode, const std::string& sid = EmptyString, + const JID& from = JID() ); + + /** + * To get rid of a bytestream (i.e., close and delete it), call this function. You + * should then not use the bytestream any more. + * The remote entity will be notified of the closing of the stream. + * @param s5b The bytestream to dispose. It will be deleted here. + */ + bool dispose( SOCKS5Bytestream* s5b ); + + /** + * Use this function to accept an incoming bytestream. + * @param sid The stream's id as passed to BytestreamHandler::handleIncomingSOCKS5Bytestream(). + */ + void acceptSOCKS5Bytestream( const std::string& sid ); + + /** + * Use this function to reject an incoming bytestream. + * @param sid The stream's id as passed to BytestreamHandler::handleIncomingSOCKS5Bytestream(). + * @param reason The reason for the reject. + */ + void rejectSOCKS5Bytestream( const std::string& sid, StanzaError reason = StanzaErrorNotAcceptable ); + + /** + * Use this function to register an object that will receive new @b incoming bytestream + * requests from the SOCKS5BytestreamManager. Only one BytestreamHandler can be + * registered at any one time. + * @param s5bh The BytestreamHandler derived object to receive notifications. + */ + void registerBytestreamHandler( BytestreamHandler* s5bh ) + { m_socks5BytestreamHandler = s5bh; } + + /** + * Removes the registered BytestreamHandler. + */ + void removeBytestreamHandler() + { m_socks5BytestreamHandler = 0; } + + /** + * Tells the SOCKS5BytestreamManager which SOCKS5BytestreamServer handles peer-2-peer SOCKS5 + * bytestreams. + * @param server The SOCKS5BytestreamServer to use. + */ + void registerSOCKS5BytestreamServer( SOCKS5BytestreamServer* server ) { m_server = server; } + + /** + * Un-registers any local SOCKS5BytestreamServer. + */ + void removeSOCKS5BytestreamServer() { m_server = 0; } + + // reimplemented from IqHandler. + virtual bool handleIq( const IQ& iq ); + + // reimplemented from IqHandler. + virtual void handleIqID( const IQ& iq, int context ); + + private: +#ifdef SOCKS5BYTESTREAMMANAGER_TEST + public: +#endif + + class Query : public StanzaExtension + { + public: + /** + * Constructs a new empty Query object. + */ + Query(); + + /** + * Constructs a new Query (streamhost) object from the given parameters. + * @param sid The stream ID. + * @param mode The stream mode (TCP or UDP). + * @param hosts A list of stream hosts. + */ + Query( const std::string& sid, S5BMode mode, + const StreamHostList& hosts ); + + /** + * Constructs a new Query (streamhost-used or activate) object, including the given JID. + * @param jid The JID. + * @param sid The stream ID. + * @param activate Determines whether the object will be an 'activate' (@b true) or + * 'streamhost-used' (@b false) one. + */ + Query( const JID& jid, const std::string& sid, bool activate ); + + /** + * Constructs a new Query object from the given Tag. + * @param tag The Tag to parse. + */ + Query( const Tag* tag ); + + /** + * Virtual destructor. + */ + virtual ~Query(); + + /** + * Returns the current stream ID. + * @return The current stream ID. + */ + const std::string& sid() const { return m_sid; } + + /** + * Returns the current JID. + * @return The current JID. + */ + const JID& jid() const { return m_jid; } + + /** + * Returns the current mode. + * @return The current mode. + */ + S5BMode mode() const { return m_mode; } + + /** + * Returns the current list of stream hosts. + * @return The current list of stream hosts. + */ + const StreamHostList& hosts() const { return m_hosts; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Query( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new Query( *this ); + } + + private: + enum QueryType + { + TypeSH, + TypeSHU, + TypeA, + TypeInvalid + }; + + std::string m_sid; + JID m_jid; + SOCKS5BytestreamManager::S5BMode m_mode; + StreamHostList m_hosts; + QueryType m_type; + + }; + + SOCKS5BytestreamManager& operator=( const SOCKS5BytestreamManager&); + void rejectSOCKS5Bytestream( const JID& from, const std::string& id, StanzaError reason = StanzaErrorNotAcceptable ); + bool haveStream( const JID& from ); + const StreamHost* findProxy( const JID& from, const std::string& hostjid, const std::string& sid ); + + void acknowledgeStreamHost( bool success, const JID& jid, const std::string& sid ); + + enum IBBActionType + { + S5BOpenStream, + S5BCloseStream, + S5BActivateStream + }; + + typedef std::map S5BMap; + S5BMap m_s5bMap; + + struct AsyncS5BItem + { + JID from; + JID to; + std::string id; + StreamHostList sHosts; + bool incoming; + }; + typedef std::map AsyncTrackMap; + AsyncTrackMap m_asyncTrackMap; + + ClientBase* m_parent; + BytestreamHandler* m_socks5BytestreamHandler; + SOCKS5BytestreamServer* m_server; + StreamHostList m_hosts; + StringMap m_trackMap; + + }; + +} + +#endif // SOCKS5BYTESTREAMMANAGER_H__ diff --git a/libs/libgloox/socks5bytestreamserver.cpp b/libs/libgloox/socks5bytestreamserver.cpp new file mode 100644 index 0000000..55f5fcc --- /dev/null +++ b/libs/libgloox/socks5bytestreamserver.cpp @@ -0,0 +1,220 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "socks5bytestreamserver.h" +#include "connectiontcpserver.h" +#include "mutexguard.h" +#include "util.h" + +namespace gloox +{ + + SOCKS5BytestreamServer::SOCKS5BytestreamServer( const LogSink& logInstance, int port, + const std::string& ip ) + : m_tcpServer( 0 ), m_logInstance( logInstance ), m_ip( ip ), m_port( port ) + { + m_tcpServer = new ConnectionTCPServer( this, m_logInstance, m_ip, m_port ); + } + + SOCKS5BytestreamServer::~SOCKS5BytestreamServer() + { + if( m_tcpServer ) + delete m_tcpServer; + + ConnectionMap::const_iterator it = m_connections.begin(); + for( ; it != m_connections.end(); ++it ) + delete (*it).first; + } + + ConnectionError SOCKS5BytestreamServer::listen() + { + if( m_tcpServer ) + return m_tcpServer->connect(); + + return ConnNotConnected; + } + + ConnectionError SOCKS5BytestreamServer::recv( int timeout ) + { + if( !m_tcpServer ) + return ConnNotConnected; + + ConnectionError ce = m_tcpServer->recv( timeout ); + if( ce != ConnNoError ) + return ce; + + ConnectionMap::const_iterator it = m_connections.begin(); + ConnectionMap::const_iterator it2; + while( it != m_connections.end() ) + { + it2 = it++; + (*it2).first->recv( timeout ); + } + + util::clearList( m_oldConnections ); + return ConnNoError; + } + + void SOCKS5BytestreamServer::stop() + { + if( m_tcpServer ) + { + m_tcpServer->disconnect(); + m_tcpServer->cleanup(); + } + } + + int SOCKS5BytestreamServer::localPort() const + { + if( m_tcpServer ) + return m_tcpServer->localPort(); + + return m_port; + } + + const std::string SOCKS5BytestreamServer::localInterface() const + { + if( m_tcpServer ) + return m_tcpServer->localInterface(); + + return m_ip; + } + + ConnectionBase* SOCKS5BytestreamServer::getConnection( const std::string& hash ) + { + util::MutexGuard mg( m_mutex ); + + ConnectionMap::iterator it = m_connections.begin(); + for( ; it != m_connections.end(); ++it ) + { + if( (*it).second.hash == hash ) + { + ConnectionBase* conn = (*it).first; + conn->registerConnectionDataHandler( 0 ); + m_connections.erase( it ); + return conn; + } + } + + return 0; + } + + void SOCKS5BytestreamServer::registerHash( const std::string& hash ) + { + util::MutexGuard mg( m_mutex ); + m_hashes.push_back( hash ); + } + + void SOCKS5BytestreamServer::removeHash( const std::string& hash ) + { + util::MutexGuard mg( m_mutex ); + m_hashes.remove( hash ); + } + + void SOCKS5BytestreamServer::handleIncomingConnection( ConnectionBase* /*server*/, ConnectionBase* connection ) + { + connection->registerConnectionDataHandler( this ); + ConnectionInfo ci; + ci.state = StateUnnegotiated; + m_connections[connection] = ci; + } + + void SOCKS5BytestreamServer::handleReceivedData( const ConnectionBase* connection, + const std::string& data ) + { + ConnectionMap::iterator it = m_connections.find( const_cast( connection ) ); + if( it == m_connections.end() ) + return; + + switch( (*it).second.state ) + { + case StateDisconnected: + (*it).first->disconnect(); + break; + case StateUnnegotiated: + { + char c[2]; + c[0] = 0x05; + c[1] = (char)(unsigned char)0xFF; + (*it).second.state = StateDisconnected; + + if( data.length() >= 3 && data[0] == 0x05 ) + { + unsigned int sz = ( data.length() - 2 < static_cast( data[1] ) ) + ? static_cast( data.length() - 2 ) + : static_cast( data[1] ); + for( unsigned int i = 2; i < sz + 2; ++i ) + { + if( data[i] == 0x00 ) + { + c[1] = 0x00; + (*it).second.state = StateAuthAccepted; + break; + } + } + } + (*it).first->send( std::string( c, 2 ) ); + break; + } + case StateAuthmethodAccepted: + // place to implement any future auth support + break; + case StateAuthAccepted: + { + std::string reply = data; + if( reply.length() < 2 ) + reply.resize( 2 ); + + reply[0] = 0x05; + reply[1] = 0x01; // general SOCKS server failure + (*it).second.state = StateDisconnected; + + if( data.length() == 47 && data[0] == 0x05 && data[1] == 0x01 && data[2] == 0x00 + && data[3] == 0x03 && data[4] == 0x28 && data[45] == 0x00 && data[46] == 0x00 ) + { + const std::string hash = data.substr( 5, 40 ); + + HashMap::const_iterator ith = m_hashes.begin(); + for( ; ith != m_hashes.end() && (*ith) != hash; ++ith ) + ; + + if( ith != m_hashes.end() ) + { + reply[1] = 0x00; + (*it).second.hash = hash; + (*it).second.state = StateDestinationAccepted; + } + } + (*it).first->send( reply ); + break; + } + case StateDestinationAccepted: + case StateActive: + // should not happen + break; + } + } + + void SOCKS5BytestreamServer::handleConnect( const ConnectionBase* /*connection*/ ) + { + // should never happen, TCP connection is already established + } + + void SOCKS5BytestreamServer::handleDisconnect( const ConnectionBase* connection, + ConnectionError /*reason*/ ) + { + m_connections.erase( const_cast( connection ) ); + m_oldConnections.push_back( connection ); + } + +} diff --git a/libs/libgloox/socks5bytestreamserver.h b/libs/libgloox/socks5bytestreamserver.h new file mode 100644 index 0000000..1910e8d --- /dev/null +++ b/libs/libgloox/socks5bytestreamserver.h @@ -0,0 +1,144 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef SOCKS5BYTESTREAMSERVER_H__ +#define SOCKS5BYTESTREAMSERVER_H__ + +#include "macros.h" +#include "connectionhandler.h" +#include "logsink.h" +#include "mutex.h" + +namespace gloox +{ + + class ConnectionTCPServer; + + /** + * @brief A server listening for SOCKS5 bytestreams. + * + * @note You can use a single SOCKS5BytestreamServer instance with multiple Client objects. + * + * @note It is safe to put a SOCKS5BytestreamServer instance into a separate thread. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API SOCKS5BytestreamServer : public ConnectionHandler, public ConnectionDataHandler + { + + friend class SOCKS5BytestreamManager; + + public: + /** + * Constructs a new SOCKS5BytestreamServer. + * @param logInstance A LogSink to use. + * @param port The local port to listen on. + * @param ip The local IP to bind to. If empty, the server will listen on all local interfaces. + */ + SOCKS5BytestreamServer( const LogSink& logInstance, int port, const std::string& ip = EmptyString ); + + /** + * Destructor. + */ + ~SOCKS5BytestreamServer(); + + /** + * Starts listening on the specified interface and port. + * @return Returns @c ConnNoError on success, @c ConnIoError on failure (e.g. if the port + * is already in use). + */ + ConnectionError listen(); + + /** + * Call this function repeatedly to check for incoming connections and to negotiate + * them. + * @param timeout The timeout to use for select in microseconds. + * @return The state of the listening socket. + */ + ConnectionError recv( int timeout ); + + /** + * Stops listening and unbinds from the interface and port. + */ + void stop(); + + /** + * Expose our TCP Connection localPort + * Returns the local port. + * @return The local port. + */ + int localPort() const; + + /** + * Expose our TCP Connection localInterface + * Returns the locally bound IP address. + * @return The locally bound IP address. + */ + const std::string localInterface() const; + + // reimplemented from ConnectionHandler + virtual void handleIncomingConnection( ConnectionBase* server, ConnectionBase* connection ); + + // reimplemented from ConnectionDataHandler + virtual void handleReceivedData( const ConnectionBase* connection, const std::string& data ); + + // reimplemented from ConnectionDataHandler + virtual void handleConnect( const ConnectionBase* connection ); + + // reimplemented from ConnectionDataHandler + virtual void handleDisconnect( const ConnectionBase* connection, ConnectionError reason ); + + private: + SOCKS5BytestreamServer& operator=( const SOCKS5BytestreamServer& ); + void registerHash( const std::string& hash ); + void removeHash( const std::string& hash ); + ConnectionBase* getConnection( const std::string& hash ); + + enum NegotiationState + { + StateDisconnected, + StateUnnegotiated, + StateAuthmethodAccepted, + StateAuthAccepted, + StateDestinationAccepted, + StateActive + }; + + struct ConnectionInfo + { + NegotiationState state; + std::string hash; + }; + + typedef std::map ConnectionMap; + ConnectionMap m_connections; + + typedef std::list ConnectionList; + ConnectionList m_oldConnections; + + typedef std::list HashMap; + HashMap m_hashes; + + ConnectionTCPServer* m_tcpServer; + + util::Mutex m_mutex; + const LogSink& m_logInstance; + std::string m_ip; + int m_port; + + }; + +} + +#endif // SOCKS5BYTESTREAMSERVER_H__ diff --git a/libs/libgloox/softwareversion.cpp b/libs/libgloox/softwareversion.cpp new file mode 100644 index 0000000..6705de1 --- /dev/null +++ b/libs/libgloox/softwareversion.cpp @@ -0,0 +1,74 @@ +/* + Copyright (c) 2008-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "softwareversion.h" +#include "tag.h" + +namespace gloox +{ + + SoftwareVersion::SoftwareVersion( const std::string& name, + const std::string& version, + const std::string& os ) + : StanzaExtension( ExtVersion ), m_name( name ), m_version( version ), m_os( os ) + { + } + + SoftwareVersion::SoftwareVersion( const Tag* tag ) + : StanzaExtension( ExtVersion ) + { + if( !tag ) + return; + + Tag* t = tag->findChild( "name" ); + if( t ) + m_name = t->cdata(); + + t = tag->findChild( "version" ); + if( t ) + m_version = t->cdata(); + + t = tag->findChild( "os" ); + if( t ) + m_os = t->cdata(); + } + + SoftwareVersion::~SoftwareVersion() + { + } + + const std::string& SoftwareVersion::filterString() const + { + static const std::string filter = "/iq/query[@xmlns='" + XMLNS_VERSION + "']"; + return filter; + } + + Tag* SoftwareVersion::tag() const + { + Tag* t = new Tag( "query" ); + t->setXmlns( XMLNS_VERSION ); + + if( !m_name.empty() ) + new Tag( t, "name", m_name ); + + if( !m_version.empty() ) + new Tag( t, "version", m_version ); + + if( !m_os.empty() ) + new Tag( t, "os", m_os ); + + return t; + } + +} diff --git a/libs/libgloox/softwareversion.h b/libs/libgloox/softwareversion.h new file mode 100644 index 0000000..2fe654d --- /dev/null +++ b/libs/libgloox/softwareversion.h @@ -0,0 +1,101 @@ +/* + Copyright (c) 2008-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef SOFTWAREVERSION_H__ +#define SOFTWAREVERSION_H__ + + +#include "stanzaextension.h" + +#include + +namespace gloox +{ + + class Tag; + + /** + * @brief This is an implementation of XEP-0092 as a StanzaExtension. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API SoftwareVersion : public StanzaExtension + { + + public: + /** + * Constructs a new object with the given resource string. + * @param name The software's name. + * @param version The software's version. + * @param os The software's operating system. + */ + SoftwareVersion( const std::string& name, const std::string& version, const std::string& os ); + + /** + * Constructs a new object from the given Tag. + * @param tag The Tag to parse. + */ + SoftwareVersion( const Tag* tag = 0 ); + + /** + * Virtual Destructor. + */ + virtual ~SoftwareVersion(); + + /** + * Returns the application's name. + * @return The application's name. + */ + const std::string& name() const { return m_name; } + + /** + * Returns the application's version. + * @return The application's version. + */ + const std::string& version() const { return m_version; } + + /** + * Returns the application's Operating System. + * @return The application's OS. + */ + const std::string& os() const { return m_os; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new SoftwareVersion( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new SoftwareVersion( *this ); + } + + private: + std::string m_name; + std::string m_version; + std::string m_os; + }; + +} + +#endif// SOFTWAREVERSION_H__ diff --git a/libs/libgloox/stanza.cpp b/libs/libgloox/stanza.cpp new file mode 100644 index 0000000..f0d979a --- /dev/null +++ b/libs/libgloox/stanza.cpp @@ -0,0 +1,127 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "stanza.h" +#include "error.h" +#include "jid.h" +#include "util.h" +#include "stanzaextension.h" +#include "stanzaextensionfactory.h" + +#include + +namespace gloox +{ + + Stanza::Stanza( const JID& to ) + : m_xmllang( "default" ), m_to( to ) + { + } + + Stanza::Stanza( Tag* tag ) + : m_xmllang( "default" ) + { + if( !tag ) + return; + + m_from.setJID( tag->findAttribute( "from" ) ); + m_to.setJID( tag->findAttribute( "to" ) ); + m_id = tag->findAttribute( "id" ); + } + + Stanza::~Stanza() + { + removeExtensions(); + } + + const Error* Stanza::error() const + { + return findExtension( ExtError ); + } + + void Stanza::addExtension( const StanzaExtension* se ) + { + m_extensionList.push_back( se ); + } + + const StanzaExtension* Stanza::findExtension( int type ) const + { + StanzaExtensionList::const_iterator it = m_extensionList.begin(); + for( ; it != m_extensionList.end() && (*it)->extensionType() != type; ++it ) ; + return it != m_extensionList.end() ? (*it) : 0; + } + + void Stanza::removeExtensions() + { + util::clearList( m_extensionList ); + } + + void Stanza::setLang( StringMap** map, + std::string& defaultLang, + const Tag* tag ) + { + const std::string& lang = tag ? tag->findAttribute( "xml:lang" ) : EmptyString; + setLang( map, defaultLang, tag ? tag->cdata() : EmptyString, lang ); + } + + void Stanza::setLang( StringMap** map, + std::string& defaultLang, + const std::string& data, + const std::string& xmllang ) + { + if( data.empty() ) + return; + + if( xmllang.empty() ) + defaultLang = data; + else + { + if( !*map ) + *map = new StringMap(); + (**map)[xmllang] = data; + } + } + + const std::string& Stanza::findLang( const StringMap* map, + const std::string& defaultData, + const std::string& lang ) + { + if( map && lang != "default" ) + { + StringMap::const_iterator it = map->find( lang ); + if( it != map->end() ) + return (*it).second; + } + return defaultData; + } + + void Stanza::getLangs( const StringMap* map, + const std::string& defaultData, + const std::string& name, + Tag* tag ) + { + if( !defaultData.empty() ) + new Tag( tag, name, defaultData ); + + if( !map ) + return; + + StringMap::const_iterator it = map->begin(); + for( ; it != map->end(); ++it ) + { + Tag* t = new Tag( tag, name, "xml:lang", (*it).first ); + t->setCData( (*it).second ); + } + } + +} diff --git a/libs/libgloox/stanza.h b/libs/libgloox/stanza.h new file mode 100644 index 0000000..45acdd6 --- /dev/null +++ b/libs/libgloox/stanza.h @@ -0,0 +1,174 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef STANZA_H__ +#define STANZA_H__ + +#include "gloox.h" +#include "tag.h" +#include "jid.h" +#include "stanzaextension.h" + +namespace gloox +{ + + class Error; + + /** + * @brief This is the base class for XMPP stanza abstractions. + * + * @author Jakob Schroeter + * @since 0.4 + */ + class GLOOX_API Stanza + { + public: + /** + * Virtual destructor. + */ + virtual ~Stanza(); + + /** + * Sets the 'from' address of the Stanza. This useful for @link gloox::Component Components @endlink. + * @param from The from address. + */ + void setFrom( const JID& from ) { m_from = from; } + + /** + * Returns the JID the stanza comes from. + * @return The origin of the stanza. + */ + const JID& from() const { return m_from; } + + /** + * Returns the receiver of the stanza. + * @return The stanza's destination. + */ + const JID& to() const { return m_to; } + + /** + * Returns the id of the stanza, if set. + * @return The ID of the stanza. + */ + const std::string& id() const { return m_id; } + + /** + * A convenience function that returns the stanza error condition, if any. + * @return The stanza error condition, may be 0. + */ + const Error* error() const; + + /** + * Retrieves the value of the xml:lang attribute of this stanza. + * Default is 'en'. + * @return The stanza's default language. + */ + const std::string& xmlLang() const { return m_xmllang; } + + /** + * Use this function to add a StanzaExtension to this Stanza. + * @param se The StanzaExtension to add. + * @note The Stanza will become the owner of the StanzaExtension and + * will take care of deletion. + * @since 0.9 + */ + void addExtension( const StanzaExtension* se ); + + /** + * Finds a StanzaExtension of a particular type. + * @param type StanzaExtensionType to search for. + * @return A pointer to the StanzaExtension, or 0 if none was found. + */ + const StanzaExtension* findExtension( int type ) const; + + /** + * Finds a StanzaExtension of a particular type. + * Example: + * @code + * const MyExtension* c = presence.findExtension( ExtMyExt ); + * @endcode + * @param type The extension type to look for. + * @return The static_cast' type, or 0 if none was found. + */ + template< class T > + inline const T* findExtension( int type ) const + { + return static_cast( findExtension( type ) ); + } + + /** + * Returns the list of the Stanza's extensions. + * @return The list of the Stanza's extensions. + */ + const StanzaExtensionList& extensions() const { return m_extensionList; } + + /** + * Removes (deletes) all the stanza's extensions. + */ + void removeExtensions(); + + /** + * Creates a Tag representation of the Stanza. The Tag is completely + * independent of the Stanza and will not be updated when the Stanza + * is modified. + * @return A pointer to a Tag representation. It is the job of the + * caller to delete the Tag. + */ + virtual Tag* tag() const = 0; + + protected: + /** + * Creates a new Stanza, taking from and to addresses from the given Tag. + * @param tag The Tag to create the Stanza from. + * @since 1.0 + */ + Stanza( Tag* tag ); + + /** + * Creates a new Stanza object and initializes the receiver's JID. + * @param to The receipient of the Stanza. + * @since 1.0 + */ + Stanza( const JID& to ); + + StanzaExtensionList m_extensionList; + std::string m_id; + std::string m_xmllang; + JID m_from; + JID m_to; + + static const std::string& findLang( const StringMap* map, + const std::string& defaultData, + const std::string& lang ); + + static void setLang( StringMap** map, + std::string& defaultLang, + const Tag* tag ); + + static void setLang( StringMap** map, + std::string& defaultLang, + const std::string& data, + const std::string& xmllang ); + + static void getLangs( const StringMap* map, + const std::string& defaultData, + const std::string& name, Tag* tag ); + + private: + Stanza( const Stanza& ); + + }; + +} + +#endif // STANZA_H__ diff --git a/libs/libgloox/stanzaextension.h b/libs/libgloox/stanzaextension.h new file mode 100644 index 0000000..89d71ee --- /dev/null +++ b/libs/libgloox/stanzaextension.h @@ -0,0 +1,241 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef STANZAEXTENSION_H__ +#define STANZAEXTENSION_H__ + +#include "macros.h" + +#include + +namespace gloox +{ + + class Tag; + + /** + * Supported Stanza extension types. + */ + enum StanzaExtensionType + { + ExtNone, /**< Invalid StanzaExtension. */ + ExtVCardUpdate, /**< Extension in the vcard-temp:x:update namespace, + * advertising a user avatar's SHA1 hash (XEP-0153). */ + ExtOOB, /**< An extension in the jabber:iq:oob or jabber:x:oob + * namespaces (XEP-0066). */ + ExtGPGSigned, /**< An extension containing a GPG/PGP signature + * (XEP-0027). */ + ExtGPGEncrypted, /**< An extension containing a GPG/PGP encrypted message + * (XEP-0027). */ + ExtReceipt, /**< An extension containing a Message Receipt/Request + * (XEP-0184). */ + ExtDelay, /**< An extension containing notice of delayed delivery + * (XEP-0203 & XEP-0091). */ + ExtAMP, /**< An extension containing advanced message processing + * rules (XEP-0079). */ + ExtError, /**< An extension containing an error. */ + ExtCaps, /**< An extension containing Entity Capabilities + * (XEP-0115). */ + ExtChatState, /**< An extension containing a chat state (XEP-0085). */ + ExtMessageEvent, /**< An extension containing a message event (XEP-0022). */ + ExtDataForm, /**< An extension containing a Data Form (XEP-0004). */ + ExtNickname, /**< An extension containing a User Nickname (XEP-0172). */ + ExtResourceBind, /**< A resource bind SE (RFC3921). */ + ExtSessionCreation, /**< A session establishing SE (RFC3921). */ + ExtVersion, /**< An extension containing a Version request/reply + * (XEP-0092). */ + ExtXHtmlIM, /**< An extension containing an XHTML message + * representation (XEP-0071) */ + ExtDiscoInfo, /**< An extension containing a disco#info element (XEP-0030). */ + ExtDiscoItems, /**< An extension containing a disco#items element (XEP-0030). */ + ExtAdhocCommand, /**< An extension containing a Adhoc Command (XEP-0050). */ + ExtPrivateXML, /**< An extension used for Private XML Storage (XEP-0048). */ + ExtRoster, /**< An extension dealing with the user's roster (RFC-3921). */ + ExtFeatureNeg, /**< An extension abstracting a Feature Negotiation element + * (XEP-0020). */ + ExtIBB, /**< An extension dealing with IBBs (XEP-0047). */ + ExtNonSaslAuth, /**< An extension for doing Non-SASL Authentication (XEP-0078). */ + ExtMUC, /**< An extension dealing with the muc namespace of XEP-0045. */ + ExtMUCOwner, /**< An extension dealing with the muc#owner namespace of XEP-0045. */ + ExtMUCAdmin, /**< An extension dealing with the muc#admin namespace of XEP-0045. */ + ExtMUCUser, /**< An extension dealing with the muc#user namespace of XEP-0045. */ + ExtMUCUnique, /**< An extension dealing with the muc#unique namespace of XEP-0045. */ + ExtPing, /**< An XMPP Ping (XEP-0199). */ + ExtSearch, /**< A XEP-0055 (Jabber Search) wrapper. */ + ExtRegistration, /**< A XEP-0077 (In-Band Registration) wrapper. */ + ExtJingle, /**< An extension dealing with Jingle (XEP-0166) */ + ExtVCard, /**< An extension dealing with vcard-temp (XEP-0054) */ + ExtPrivacy, /**< An extension dealing with Privacy Lists (XEP-0016) */ + ExtLastActivity, /**< An extension dealing with Last Activity (XEP-0012). */ + ExtFlexOffline, /**< An extension dealing with Flexible Offline Messages (XEP-0013). */ + ExtSI, /**< An extension dealing with Stream Initiation (XEP-0095). */ + ExtS5BQuery, /**< An extension dealing with stream host offers (XEP-0065) */ + ExtPubSub, /**< An extension dealing with PubSub requests (XEP-0060). */ + ExtPubSubOwner, /**< An extension dealing with PubSub owner requests (XEP-0060). */ + ExtPubSubEvent, /**< An extension for PubSub event notifications + * (XEP-0060) */ + ExtSHIM, /**< An extension dealing with Stanza Headers and Internet Metadata (XEP-0131). */ + ExtAttention, /**< An extension dealing with Attention (XEP-0224). */ + ExtUser /**< User-supplied extensions must use IDs above this. Do + * not hard-code ExtUser's value anywhere, it is subject + * to change. */ + }; + + /** + * @brief This class abstracts a stanza extension, which is usually + * an element in a specific namespace. + * + * This class is the base class for almost all protocol extensions in gloox. + * As such, it should be used whenever an add-on to the core XMPP spec + * needs to be made. For simple protocols it may suffice to create a sub-class + * of StanzaExtension. For protocols which require keeping of state, an additional + * persistent object acting like a manager may be needed. + * + * A Stanza can be extended by additional namespaced child elements. Obviously, + * it is not viable to include all the kinds of extensions possible. To avoid + * hard-coding of such extensions into gloox, StanzaExtension can be used to + * inform the core of gloox about additional supported extensions without it + * needing to know about the exact implementation. + * + * Note that a StanzaExtension can be used for both sending and receiving + * of custom protocols. When receiving, gloox requires an appropriate implementation + * of the pure virtuals filterString() and newInstance(). To be able to properly use + * the encapsulation, some getters may be necessary. Note that the object you will be + * dealing with usually is @em const. + * For sending StanzaExtensions, a custom constructor (as well as some setters, + * possibly) is needed. Additionally, an implementation of tag() is required. + * + * @li Sub-class StanzaExtension and re-implement filterString(). filterString() + * is supposed to return an XPath expression that matches the child element + * of a stanza that the protocol-to-implement uses. For example, consider this + * hypothetical XML format: The protocol is encapsulated inside a <stats> + * element in the 'ext:stats' namespace. It uses IQ stanzas for transmission. + * @code + * + * + * + * + * + * + * 10 + * + * + * @endcode + * The idea of filterString() and its XPath expression is to match the + * <stats> element such that it can be fed to your + * StanzaExtension-derived class' constructor when creating a new instance + * of it. For our @e stats protocol, filterString() would return something like: + * /iq/stats[\@xmlns='ext:stats'] + * + * @li When subclassing StanzaExtension, you have to initialize it with an int, the extension's + * type. You should choose a value that is not yet used in gloox, and unique to + * the given extension you implement. In general, you are free to use values + * above @link gloox::ExtUser ExtUser @endlink. See + * @link gloox::StanzaExtensionType StanzaExtensionType @endlink for existing values. + * + * @li The next step is to implement newInstance(). Whenever filterString()'s + * XPath expression matches a child element of an incoming stanza, newInstance() + * is called with the matched Tag. For our example above, this is the <stats> + * element (including its children): + * @code + * + * 10 + * + * @endcode + * The purpose of newInstance() is to return a new instance of your specialized + * StanzaExtension (implicitly cast to StanzaExtension). This way, gloox can deal + * entirely with the abstract base, StanzaExtension, and never ever needs to know + * which kind of extension it deals with. The most common implementation of + * newInstance() looks like this: + * @code + * StanzaExtension* StatsExtension::newInstance( const Tag* tag ) const + * { + * return new StatsExtension( tag ); + * } + * @endcode + * This of course implies that a constructor exists that takes a const Tag* as the + * only parameter. + * + * @li Finally, gloox must be able to serialize the StanzaExtension back + * into string'ified XML. This is done by means of the tag() function which + * must be reimplemented. The output Tag should -- like the input Tag -- be embeddable + * into the respective stanza. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API StanzaExtension + { + public: + /** + * Constructs an empty StanzaExtension. + * @param type Designates the extension's type. It should be one of StanzaExtensionType + * for built-in extensions, and it should be higher than ExtUser for custom types. + */ + StanzaExtension( int type ) : m_valid( false ), m_extensionType( type ) {} + + /** + * Virtual destructor. + */ + virtual ~StanzaExtension() {} + + /** + * Returns an XPath expression that describes a path to child elements of a + * stanza that an extension handles. + * + * @return The extension's filter string. + */ + virtual const std::string& filterString() const = 0; + + /** + * Returns a new Instance of the derived type. Usually, for a derived class FooExtension, + * the implementation of this function looks like: + * @code + * StanzaExtension* FooExtension::newInstance( const Tag* tag ) const + * { + * return new FooExtension( tag ); + * } + * @endcode + * @return The derived extension's new instance. + */ + virtual StanzaExtension* newInstance( const Tag* tag ) const = 0; + + /** + * Returns a Tag representation of the extension. + * @return A Tag representation of the extension. + */ + virtual Tag* tag() const = 0; + + /** + * Returns an identical copy of the current StanzaExtension. + * @return An identical copy of the current StanzaExtension. + */ + virtual StanzaExtension* clone() const = 0; + + /** + * Returns the extension's type. + * @return The extension's type. + */ + int extensionType() const { return m_extensionType; } + + protected: + bool m_valid; + + private: + int m_extensionType; + + }; + +} + +#endif // STANZAEXTENSION_H__ diff --git a/libs/libgloox/stanzaextensionfactory.cpp b/libs/libgloox/stanzaextensionfactory.cpp new file mode 100644 index 0000000..06d65e2 --- /dev/null +++ b/libs/libgloox/stanzaextensionfactory.cpp @@ -0,0 +1,85 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "stanzaextensionfactory.h" + +#include "gloox.h" +#include "util.h" +#include "stanza.h" +#include "stanzaextension.h" +#include "tag.h" + +namespace gloox +{ + + StanzaExtensionFactory::StanzaExtensionFactory() + { + } + + StanzaExtensionFactory::~StanzaExtensionFactory() + { + util::clearList( m_extensions ); + } + + void StanzaExtensionFactory::registerExtension( StanzaExtension* ext ) + { + if( !ext ) + return; + + SEList::iterator it = m_extensions.begin(); + SEList::iterator it2; + while( it != m_extensions.end() ) + { + it2 = it++; + if( ext->extensionType() == (*it2)->extensionType() ) + { + delete (*it2); + m_extensions.erase( it2 ); + } + } + m_extensions.push_back( ext ); + } + + bool StanzaExtensionFactory::removeExtension( int ext ) + { + SEList::iterator it = m_extensions.begin(); + for( ; it != m_extensions.end(); ++it ) + { + if( (*it)->extensionType() == ext ) + { + delete (*it); + m_extensions.erase( it ); + return true; + } + } + return false; + } + + void StanzaExtensionFactory::addExtensions( Stanza& stanza, Tag* tag ) + { + ConstTagList::const_iterator it; + SEList::const_iterator ite = m_extensions.begin(); + for( ; ite != m_extensions.end(); ++ite ) + { + const ConstTagList& match = tag->findTagList( (*ite)->filterString() ); + it = match.begin(); + for( ; it != match.end(); ++it ) + { + StanzaExtension* se = (*ite)->newInstance( (*it) ); + if( se ) + stanza.addExtension( se ); + } + } + } + +} diff --git a/libs/libgloox/stanzaextensionfactory.h b/libs/libgloox/stanzaextensionfactory.h new file mode 100644 index 0000000..ab53451 --- /dev/null +++ b/libs/libgloox/stanzaextensionfactory.h @@ -0,0 +1,90 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef STANZAEXTENSIONFACTORY_H__ +#define STANZAEXTENSIONFACTORY_H__ + +#include + +namespace gloox +{ + + class Tag; + class Stanza; + class StanzaExtension; + + /** + * @brief A Factory that creates StanzaExtensions from Tags. + * + * To supply a custom StanzaExtension, reimplement StanzaExtension's + * virtuals and pass an instance to registerExtension(). + * + * You should not need to use this class directly. Use ClientBase::registerStanzaExtension() + * instead. See StanzaExtension for more information about adding protocol implementations + * to gloox. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class StanzaExtensionFactory + { + + friend class ClientBase; + + public: + /** + * Use this function to inform StanzaExtensionFactory about available StanzaExtensions. + * By default, StanzaExtensionFactory does not know about any extensions. + * gloox-built-in extensions will usually be registered by their respective protocol + * implementations unless otherwise noted in the extension's API docs. + * @param ext An extension to register. + * @note The supplied StanzaExtension will be deleted in StanzaExtensionFactory's destructor. + * @note Only one instance per extension type can be registered. In case an extension is + * registered that is of the same type as an already registered extension, the new extension + * will replace the previously registered one. + */ + void registerExtension( StanzaExtension* ext ); + + /** + * Removes the given extension type. + * @param ext The extension type. + * @return @b True if the given type was found (and removed), @b false otherwise. + */ + bool removeExtension( int ext ); + + /** + * Creates a new StanzaExtensionFactory. + */ + StanzaExtensionFactory(); + + /** + * Non-virtual destructor. + */ + ~StanzaExtensionFactory(); + + /** + * This function creates StanzaExtensions from the given Tag and attaches them to the given Stanza. + * @param stanza The Stanza to attach the extensions to. + * @param tag The Tag to parse and create the StanzaExtension from. + */ + void addExtensions( Stanza& stanza, Tag* tag ); + + private: + typedef std::list SEList; + SEList m_extensions; + + }; + +} + +#endif // STANZAEXTENSIONFACTORY_H__ diff --git a/libs/libgloox/statisticshandler.h b/libs/libgloox/statisticshandler.h new file mode 100644 index 0000000..74ef964 --- /dev/null +++ b/libs/libgloox/statisticshandler.h @@ -0,0 +1,80 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef STATISTICSHANDLER_H__ +#define STATISTICSHANDLER_H__ + +#include "stanza.h" + +namespace gloox +{ + + /** + * A structure describing the current connection statistics. + */ + struct StatisticsStruct + { + long int totalBytesSent; /**< The total number of bytes sent over the wire. This does @b not + * include the TLS handshake nor any TLS-related overhead, but it does + * include anything sent before compression was switched on. */ + long int totalBytesReceived; /**< The total number of bytes received over the wire. This does @b not + * include the TLS handshake nor any TLS-related overhead, but it does + * include anything sent before compression was switched on. */ + long int compressedBytesSent; /**< Total number of bytes sent over the wire after compression was + * applied. */ + long int compressedBytesReceived; /**< Total number of bytes received over the wire before decompression + * was applied. */ + long int uncompressedBytesSent; /**< Total number of bytes sent over the wire before compression was + * applied. */ + long int uncompressedBytesReceived; /**< Total number of bytes received over the wire after decompression + * was applied. */ + long int totalStanzasSent; /**< The total number of Stanzas sent. */ + long int totalStanzasReceived; /**< The total number of Stanzas received. */ + long int iqStanzasSent; /**< The total number of IQ Stanzas sent. */ + long int iqStanzasReceived; /**< The total number of IQ Stanzas received. */ + long int messageStanzasSent; /**< The total number of Message Stanzas sent. */ + long int messageStanzasReceived; /**< The total number of Message Stanzas received. */ + long int s10nStanzasSent; /**< The total number of Subscription Stanzas sent. */ + long int s10nStanzasReceived; /**< The total number of Subscription Stanzas received. */ + long int presenceStanzasSent; /**< The total number of Presence Stanzas sent. */ + long int presenceStanzasReceived; /**< The total number of Presence Stanzas received. */ + bool encryption; /**< Whether or not the connection (to the server) is encrypted. */ + bool compression; /**< Whether or not the stream (to the server) gets compressed. */ + }; + + /** + * @brief A virtual interface which can be reimplemented to receive connection statistics. + * + * Derived classes can be registered as StatisticsHandlers with the ClientBase. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API StatisticsHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~StatisticsHandler() {} + + /** + * This function is called when a Stanza has been sent or received. + * @param stats The updated connection statistics. + */ + virtual void handleStatistics( const StatisticsStruct stats ) = 0; + }; + +} + +#endif // STATISTICSHANDLER_H__ diff --git a/libs/libgloox/subscription.cpp b/libs/libgloox/subscription.cpp new file mode 100644 index 0000000..bb310d6 --- /dev/null +++ b/libs/libgloox/subscription.cpp @@ -0,0 +1,77 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "subscription.h" +#include "util.h" + +namespace gloox +{ + + static const char* msgTypeStringValues[] = + { + "subscribe", "subscribed", "unsubscribe", "unsubscribed" + }; + + static inline const std::string typeString( Subscription::S10nType type ) + { + return util::lookup( type, msgTypeStringValues ); + } + + Subscription::Subscription( Tag* tag ) + : Stanza( tag ), m_subtype( Invalid ), m_stati( 0 ) + { + if( !tag || tag->name() != "presence" ) + return; + + m_subtype = (S10nType)util::lookup( tag->findAttribute( TYPE ), msgTypeStringValues ); + + const ConstTagList& c = tag->findTagList( "/presence/status" ); + ConstTagList::const_iterator it = c.begin(); + for( ; it != c.end(); ++it ) + setLang( &m_stati, m_status, (*it) ); + } + + Subscription::Subscription( S10nType type, const JID& to, const std::string& status, + const std::string& xmllang ) + : Stanza( to ), m_subtype( type ), m_stati( 0 ) + { + setLang( &m_stati, m_status, status, xmllang ); + } + + Subscription::~Subscription() + { + delete m_stati; + } + + Tag* Subscription::tag() const + { + if( m_subtype == Invalid ) + return 0; + + Tag* t = new Tag( "presence" ); + if( m_to ) + t->addAttribute( "to", m_to.full() ); + if( m_from ) + t->addAttribute( "from", m_from.full() ); + + t->addAttribute( "type", typeString( m_subtype ) ); + + getLangs( m_stati, m_status, "status", t ); + + StanzaExtensionList::const_iterator it = m_extensionList.begin(); + for( ; it != m_extensionList.end(); ++it ) + t->addChild( (*it)->tag() ); + + return t; + } + +} diff --git a/libs/libgloox/subscription.h b/libs/libgloox/subscription.h new file mode 100644 index 0000000..ae377d1 --- /dev/null +++ b/libs/libgloox/subscription.h @@ -0,0 +1,106 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef SUBSCRIPTION_H__ +#define SUBSCRIPTION_H__ + +#include "stanza.h" + +#include + +namespace gloox +{ + + class JID; + + /** + * @brief An abstraction of a subscription stanza. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Subscription : public Stanza + { + + public: + + friend class ClientBase; + + /** + * Describes the different valid message types. + */ + enum S10nType + { + Subscribe, /**> A subscription request. */ + Subscribed, /**< A subscription notification. */ + Unsubscribe, /**< An unsubscription request. */ + Unsubscribed, /**< An unsubscription notification. */ + Invalid /**< The stanza is invalid. */ + }; + + /** + * Creates a Subscription request. + * @param type The presence type. + * @param to The intended receiver. Use an empty JID to create a broadcast packet. + * @param status An optional status message (e.g. "please authorize me"). + * @param xmllang An optional xml:lang for the status message. + */ + Subscription( S10nType type, const JID& to, const std::string& status = EmptyString, + const std::string& xmllang = EmptyString ); + /** + * Destructor. + */ + virtual ~Subscription(); + + /** + * Returns the subscription stanza's type. + * @return The subscription stanza's type. + * + */ + S10nType subtype() const { return m_subtype; } + + /** + * Returns the status text of a presence stanza for the given language if available. + * If the requested language is not available, the default status text (without a xml:lang + * attribute) will be returned. + * @param lang The language identifier for the desired language. It must conform to + * section 2.12 of the XML specification and RFC 3066. If empty, the default body + * will be returned, if any. + * @return The status text set by the sender. + */ + const std::string status( const std::string& lang = "default" ) const + { + return findLang( m_stati, m_status, lang ); + } + + // reimplemented from Stanza + virtual Tag* tag() const; + + private: +#ifdef SUBSCRIPTION_TEST + public: +#endif + /** + * Creates a Subscription request from the given Tag. The original Tag will be ripped off. + * @param tag The Tag to parse. + */ + Subscription( Tag* tag ); + + S10nType m_subtype; + StringMap* m_stati; + std::string m_status; + + }; + +} + +#endif // SUBSCRIPTION_H__ diff --git a/libs/libgloox/subscriptionhandler.h b/libs/libgloox/subscriptionhandler.h new file mode 100644 index 0000000..d891a7f --- /dev/null +++ b/libs/libgloox/subscriptionhandler.h @@ -0,0 +1,49 @@ +/* + Copyright (c) 2004-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef SUBSCRIPTIONHANDLER_H__ +#define SUBSCRIPTIONHANDLER_H__ + +#include "subscription.h" + +namespace gloox +{ + + /** + * @brief A virtual interface which can be reimplemented to receive incoming subscription stanzas. + * + * Derived classes can be registered as SubscriptionHandlers with the Client. + * Upon an incoming Subscription packet @ref handleSubscription() will be called. + * @author Jakob Schroeter + */ + class GLOOX_API SubscriptionHandler + { + public: + /** + * Virtual destructor. + */ + virtual ~SubscriptionHandler() {} + + /** + * Reimplement this function if you want to be notified about incoming + * subscriptions/subscription requests. + * @param subscription The complete Subscription stanza. + * @since 1.0 + */ + virtual void handleSubscription( const Subscription& subscription ) = 0; + + }; + +} + +#endif // SUBSCRIPTIONHANDLER_H__ diff --git a/libs/libgloox/tag.cpp b/libs/libgloox/tag.cpp new file mode 100644 index 0000000..8416157 --- /dev/null +++ b/libs/libgloox/tag.cpp @@ -0,0 +1,1372 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "tag.h" +#include "util.h" + +#include + +#include + +namespace gloox +{ + + // ---- Tag::Attribute ---- + Tag::Attribute::Attribute( Tag* parent, const std::string& name, const std::string& value, + const std::string& xmlns ) + : m_parent( parent ) + { + if( m_parent ) + m_parent->addAttribute( this ); + + init( name, value, xmlns ); + } + + Tag::Attribute::Attribute( const std::string& name, const std::string& value, + const std::string& xmlns ) + : m_parent( 0 ) + { + init( name, value, xmlns ); + } + + Tag::Attribute::Attribute( const Attribute& attr ) + : m_parent( attr.m_parent ), m_name( attr.m_name ), m_value( attr.m_value ), + m_xmlns( attr.m_xmlns ), m_prefix( attr.m_prefix ) + { + } + + void Tag::Attribute::init( const std::string& name, const std::string& value, + const std::string& xmlns ) + { + if( util::checkValidXMLChars( xmlns ) ) + m_xmlns = xmlns; + else + return; + + if( util::checkValidXMLChars( value ) ) + m_value = value; + else + return; + + if( util::checkValidXMLChars( name ) ) + m_name = name; + else + return; + } + + bool Tag::Attribute::setValue( const std::string& value ) + { + if( !util::checkValidXMLChars( value ) ) + return false; + + m_value = value; + return true; + } + + bool Tag::Attribute::setXmlns( const std::string& xmlns ) + { + if( !util::checkValidXMLChars( xmlns ) ) + return false; + + m_xmlns = xmlns; + return true; + } + + bool Tag::Attribute::setPrefix( const std::string& prefix ) + { + if( !util::checkValidXMLChars( prefix ) ) + return false; + + m_prefix = prefix; + return true; + } + + const std::string& Tag::Attribute::xmlns() const + { + if( !m_xmlns.empty() ) + return m_xmlns; + + if( m_parent ) + return m_parent->xmlns( m_prefix ); + + return EmptyString; + } + + const std::string& Tag::Attribute::prefix() const + { + if( !m_prefix.empty() ) + return m_prefix; + + if( m_parent ) + return m_parent->prefix( m_xmlns ); + + return EmptyString; + } + + const std::string Tag::Attribute::xml() const + { + if( m_name.empty() ) + return EmptyString; + + std::string xml; + xml += ' '; + if( !m_prefix.empty() ) + { + xml += m_prefix; + xml += ':'; + } + xml += m_name; + xml += "='"; + xml += util::escape( m_value ); + xml += '\''; + + return xml; + } + // ---- ~Tag::Attribute ---- + + // ---- Tag ---- + Tag::Tag( const std::string& name, const std::string& cdata ) + : m_parent( 0 ), m_children( 0 ), m_cdata( 0 ), + m_attribs( 0 ), m_nodes( 0 ), + m_xmlnss( 0 ) + { + addCData( cdata ); // implicitly UTF-8 checked + + if( util::checkValidXMLChars( name ) ) + m_name = name; + } + + Tag::Tag( Tag* parent, const std::string& name, const std::string& cdata ) + : m_parent( parent ), m_children( 0 ), m_cdata( 0 ), + m_attribs( 0 ), m_nodes( 0 ), + m_xmlnss( 0 ) + { + if( m_parent ) + m_parent->addChild( this ); + + addCData( cdata ); // implicitly UTF-8 checked + + if( util::checkValidXMLChars( name ) ) + m_name = name; + } + + Tag::Tag( const std::string& name, + const std::string& attrib, + const std::string& value ) + : m_parent( 0 ), m_children( 0 ), m_cdata( 0 ), + m_attribs( 0 ), m_nodes( 0 ), + m_name( name ), m_xmlnss( 0 ) + { + addAttribute( attrib, value ); // implicitly UTF-8 checked + + if( util::checkValidXMLChars( name ) ) + m_name = name; + } + + Tag::Tag( Tag* parent, const std::string& name, + const std::string& attrib, + const std::string& value ) + : m_parent( parent ), m_children( 0 ), m_cdata( 0 ), + m_attribs( 0 ), m_nodes( 0 ), + m_name( name ), m_xmlnss( 0 ) + { + if( m_parent ) + m_parent->addChild( this ); + + addAttribute( attrib, value ); // implicitly UTF-8 checked + + if( util::checkValidXMLChars( name ) ) + m_name = name; + } + + Tag::Tag( Tag* tag ) + : m_parent( 0 ), m_children( 0 ), m_cdata( 0 ), m_attribs( 0 ), + m_nodes( 0 ), m_xmlnss( 0 ) + { + if( !tag ) + return; + + m_children = tag->m_children; + m_cdata = tag->m_cdata; + m_attribs = tag->m_attribs; + m_nodes = tag->m_nodes; + m_name = tag->m_name; + m_xmlns = tag->m_xmlns; + m_xmlnss = tag->m_xmlnss; + + tag->m_nodes = 0; + tag->m_cdata = 0; + tag->m_attribs = 0; + tag->m_children = 0; + tag->m_xmlnss = 0; + + if( m_attribs ) + { + AttributeList::iterator it = m_attribs->begin(); + while( it != m_attribs->end() ) + (*it++)->m_parent = this; + } + + if( m_children ) + { + TagList::iterator it = m_children->begin(); + while( it != m_children->end() ) + (*it++)->m_parent = this; + } + } + + Tag::~Tag() + { + if( m_cdata ) + util::clearList( *m_cdata ); + if( m_attribs ) + util::clearList( *m_attribs ); + if( m_children ) + util::clearList( *m_children ); + if( m_nodes ) + util::clearList( *m_nodes ); + + delete m_cdata; + delete m_attribs; + delete m_children; + delete m_nodes; + delete m_xmlnss; + + m_parent = 0; + } + + bool Tag::operator==( const Tag& right ) const + { + if( m_name != right.m_name || m_xmlns != right.m_xmlns ) + return false; + + if( m_cdata && right.m_cdata ) + { + StringPList::const_iterator ct = m_cdata->begin(); + StringPList::const_iterator ct_r = right.m_cdata->begin(); + while( ct != m_cdata->end() && ct_r != right.m_cdata->end() && *(*ct) == *(*ct_r) ) + { + ++ct; + ++ct_r; + } + if( ct != m_cdata->end() ) + return false; + } + else if( m_cdata || right.m_cdata ) + return false; + + if( m_children && right.m_children ) + { + TagList::const_iterator it = m_children->begin(); + TagList::const_iterator it_r = right.m_children->begin(); + while( it != m_children->end() && it_r != right.m_children->end() && *(*it) == *(*it_r) ) + { + ++it; + ++it_r; + } + if( it != m_children->end() ) + return false; + } + else if( m_children || right.m_children ) + return false; + + if( m_attribs && right.m_attribs ) + { + AttributeList::const_iterator at = m_attribs->begin(); + AttributeList::const_iterator at_r = right.m_attribs->begin(); + while( at != m_attribs->end() && at_r != right.m_attribs->end() && *(*at) == *(*at_r) ) + { + ++at; + ++at_r; + } + if( at != m_attribs->end() ) + return false; + } + else if( m_attribs || right.m_attribs ) + return false; + + return true; + } + + const std::string Tag::xml() const + { + if( m_name.empty() ) + return EmptyString; + + std::string xml = "<"; + if( !m_prefix.empty() ) + { + xml += m_prefix; + xml += ':'; + } + xml += m_name; + if( m_attribs && !m_attribs->empty() ) + { + AttributeList::const_iterator it_a = m_attribs->begin(); + for( ; it_a != m_attribs->end(); ++it_a ) + { + xml += (*it_a)->xml(); + } + } + + if( !m_nodes || m_nodes->empty() ) + xml += "/>"; + else + { + xml += '>'; + NodeList::const_iterator it_n = m_nodes->begin(); + for( ; it_n != m_nodes->end(); ++it_n ) + { + switch( (*it_n)->type ) + { + case TypeTag: + xml += (*it_n)->tag->xml(); + break; + case TypeString: + xml += util::escape( *((*it_n)->str) ); + break; + } + } + xml += "'; + } + + return xml; + } + + bool Tag::addAttribute( Attribute* attr ) + { + if( !attr ) + return false; + + if( !(*attr) ) + { + delete attr; + return false; + } + + if( !m_attribs ) + m_attribs = new AttributeList(); + + AttributeList::iterator it = m_attribs->begin(); + for( ; it != m_attribs->end(); ++it ) + { + if( (*it)->name() == attr->name() + && ( (*it)->xmlns() == attr->xmlns() || (*it)->prefix() == attr->prefix() ) ) + { + delete (*it); + (*it) = attr; + return true; + } + } + + m_attribs->push_back( attr ); + + return true; + } + + bool Tag::addAttribute( const std::string& name, const std::string& value ) + { + if( name.empty() || value.empty() ) + return false; + + return addAttribute( new Attribute( name, value ) ); + } + + bool Tag::addAttribute( const std::string& name, int value ) + { + if( name.empty() ) + return false; + + return addAttribute( name, util::int2string( value ) ); + } + + bool Tag::addAttribute( const std::string& name, long value ) + { + if( name.empty() ) + return false; + + return addAttribute( name, util::long2string( value ) ); + } + + void Tag::setAttributes( const AttributeList& attributes ) + { + if( !m_attribs ) + m_attribs = new AttributeList( attributes ); + else + { + util::clearList( *m_attribs ); + *m_attribs = attributes; + } + + AttributeList::iterator it = m_attribs->begin(); + for( ; it != m_attribs->end(); ++it ) + (*it)->m_parent = this; + } + + void Tag::addChild( Tag* child ) + { + if( !child ) + return; + + if( !m_nodes ) + m_nodes = new NodeList(); + if( !m_children ) + m_children = new TagList(); + + m_children->push_back( child ); + child->m_parent = this; + m_nodes->push_back( new Node( TypeTag, child ) ); + } + + void Tag::addChildCopy( const Tag* child ) + { + if( !child ) + return; + + addChild( child->clone() ); + } + + bool Tag::setCData( const std::string& cdata ) + { + if( cdata.empty() || !util::checkValidXMLChars( cdata ) ) + return false; + + if( !m_cdata ) + m_cdata = new StringPList(); + else + util::clearList( *m_cdata ); + + if( !m_nodes ) + m_nodes = new NodeList(); + else + { + NodeList::iterator it = m_nodes->begin(); + NodeList::iterator t; + while( it != m_nodes->end() ) + { + if( (*it)->type == TypeString ) + { + t = it++; + delete (*t); + m_nodes->erase( t ); + } + } + } + + return addCData( cdata ); + } + + bool Tag::addCData( const std::string& cdata ) + { + if( cdata.empty() || !util::checkValidXMLChars( cdata ) ) + return false; + + if( !m_cdata ) + m_cdata = new StringPList(); + if( !m_nodes ) + m_nodes = new NodeList(); + + std::string* str = new std::string( cdata ); + m_cdata->push_back( str ); + m_nodes->push_back( new Node( TypeString, str ) ); + return true; + } + + const std::string Tag::cdata() const + { + if( !m_cdata ) + return EmptyString; + + std::string str; + StringPList::const_iterator it = m_cdata->begin(); + for( ; it != m_cdata->end(); ++it ) + str += *(*it); + + return str; + } + + const TagList& Tag::children() const + { + static const TagList empty; + return m_children ? *m_children : empty; + } + + const Tag::AttributeList& Tag::attributes() const + { + static const AttributeList empty; + return m_attribs ? *m_attribs : empty; + } + + bool Tag::setXmlns( const std::string& xmlns, const std::string& prefix ) + { + if( !util::checkValidXMLChars( xmlns ) || !util::checkValidXMLChars( prefix ) ) + return false; + + if( prefix.empty() ) + { + m_xmlns = xmlns; + return addAttribute( XMLNS, m_xmlns ); + } + else + { + if( !m_xmlnss ) + m_xmlnss = new StringMap(); + + (*m_xmlnss)[prefix] = xmlns; + + return addAttribute( XMLNS + ":" + prefix, xmlns ); + } + } + + const std::string& Tag::xmlns() const + { + return xmlns( m_prefix ); + } + + const std::string& Tag::xmlns( const std::string& prefix ) const + { + if( prefix.empty() ) + { + return hasAttribute( XMLNS ) ? findAttribute( XMLNS ) : m_xmlns; + } + + if( m_xmlnss ) + { + StringMap::const_iterator it = m_xmlnss->find( prefix ); + if( it != m_xmlnss->end() ) + return (*it).second; + } + + return m_parent ? m_parent->xmlns( prefix ) : EmptyString; + } + + bool Tag::setPrefix( const std::string& prefix ) + { + if( !util::checkValidXMLChars( prefix ) ) + return false; + + m_prefix = prefix; + return true; + } + + const std::string& Tag::prefix( const std::string& xmlns ) const + { + if( xmlns.empty() || !m_xmlnss ) + return EmptyString; + + StringMap::const_iterator it = m_xmlnss->begin(); + for( ; it != m_xmlnss->end(); ++it ) + { + if( (*it).second == xmlns ) + return (*it).first; + } + + return EmptyString; + } + + const std::string& Tag::findAttribute( const std::string& name ) const + { + if( !m_attribs ) + return EmptyString; + + AttributeList::const_iterator it = m_attribs->begin(); + for( ; it != m_attribs->end(); ++it ) + if( (*it)->name() == name ) + return (*it)->value(); + + return EmptyString; + } + + bool Tag::hasAttribute( const std::string& name, const std::string& value ) const + { + if( name.empty() || !m_attribs ) + return false; + + AttributeList::const_iterator it = m_attribs->begin(); + for( ; it != m_attribs->end(); ++it ) + if( (*it)->name() == name ) + return value.empty() || (*it)->value() == value; + + return false; + } + + bool Tag::hasChild( const std::string& name, const std::string& attr, + const std::string& value ) const + { + if( attr.empty() ) + return findChild( name ) ? true : false; + else + return findChild( name, attr, value ) ? true : false; + } + + Tag* Tag::findChild( const std::string& name ) const + { + if( !m_children ) + return 0; + + TagList::const_iterator it = m_children->begin(); + while( it != m_children->end() && (*it)->name() != name ) + ++it; + return it != m_children->end() ? (*it) : 0; + } + + Tag* Tag::findChild( const std::string& name, const std::string& attr, + const std::string& value ) const + { + if( !m_children || name.empty() ) + return 0; + + TagList::const_iterator it = m_children->begin(); + while( it != m_children->end() && ( (*it)->name() != name || !(*it)->hasAttribute( attr, value ) ) ) + ++it; + return it != m_children->end() ? (*it) : 0; + } + + bool Tag::hasChildWithCData( const std::string& name, const std::string& cdata ) const + { + if( !m_children || name.empty() || cdata.empty() ) + return 0; + + TagList::const_iterator it = m_children->begin(); + while( it != m_children->end() && ( (*it)->name() != name + || ( !cdata.empty() && (*it)->cdata() != cdata ) ) ) + ++it; + return it != m_children->end(); + } + + Tag* Tag::findChildWithAttrib( const std::string& attr, const std::string& value ) const + { + if( !m_children || attr.empty() ) + return 0; + + TagList::const_iterator it = m_children->begin(); + while( it != m_children->end() && !(*it)->hasAttribute( attr, value ) ) + ++it; + return it != m_children->end() ? (*it) : 0; + } + + Tag* Tag::clone() const + { + Tag* t = new Tag( m_name ); + t->m_xmlns = m_xmlns; + t->m_prefix = m_prefix; + + if( m_attribs ) + { + t->m_attribs = new AttributeList(); + Tag::AttributeList::const_iterator at = m_attribs->begin(); + Attribute* attr; + for( ; at != m_attribs->end(); ++at ) + { + attr = new Attribute( *(*at) ); + attr->m_parent = t; + t->m_attribs->push_back( attr ); + } + } + + if( m_xmlnss ) + { + t->m_xmlnss = new StringMap( *m_xmlnss ); + } + + if( m_nodes ) + { + Tag::NodeList::const_iterator nt = m_nodes->begin(); + for( ; nt != m_nodes->end(); ++nt ) + { + switch( (*nt)->type ) + { + case TypeTag: + t->addChild( (*nt)->tag->clone() ); + break; + case TypeString: + t->addCData( *((*nt)->str) ); + break; + } + } + } + + return t; + } + + TagList Tag::findChildren( const std::string& name, + const std::string& xmlns ) const + { + return m_children ? findChildren( *m_children, name, xmlns ) : TagList(); + } + + TagList Tag::findChildren( const TagList& list, const std::string& name, + const std::string& xmlns ) const + { + TagList ret; + TagList::const_iterator it = list.begin(); + for( ; it != list.end(); ++it ) + { + if( (*it)->name() == name && ( xmlns.empty() || (*it)->xmlns() == xmlns ) ) + ret.push_back( (*it) ); + } + return ret; + } + + void Tag::removeChild( const std::string& name, const std::string& xmlns ) + { + if( name.empty() || !m_children || !m_nodes ) + return; + + TagList l = findChildren( name, xmlns ); + TagList::iterator it = l.begin(); + TagList::iterator it2; + while( it != l.end() ) + { + it2 = it++; + NodeList::iterator itn = m_nodes->begin(); + for( ; itn != m_nodes->end(); ++itn ) + { + if( (*itn)->type == TypeTag && (*itn)->tag == (*it2) ) + { + delete (*itn); + m_nodes->erase( itn ); + break; + } + } + m_children->remove( (*it2) ); + delete (*it2); + } + } + + void Tag::removeChild( Tag* tag ) + { + if( m_children ) + m_children->remove( tag ); + + if( !m_nodes ) + return; + + NodeList::iterator it = m_nodes->begin(); + for( ; it != m_nodes->end(); ++it ) + { + if( (*it)->type == TypeTag && (*it)->tag == tag ) + { + delete (*it); + m_nodes->erase( it ); + return; + } + } + } + + void Tag::removeAttribute( const std::string& attr, const std::string& value, + const std::string& xmlns ) + { + if( attr.empty() || !m_attribs ) + return; + + AttributeList::iterator it = m_attribs->begin(); + AttributeList::iterator it2; + while( it != m_attribs->end() ) + { + it2 = it++; + if( (*it2)->name() == attr && ( value.empty() || (*it2)->value() == value ) + && ( xmlns.empty() || (*it2)->xmlns() == xmlns ) ) + { + delete (*it2); + m_attribs->erase( it2 ); + } + } + } + + const std::string Tag::findCData( const std::string& expression ) const + { + const ConstTagList& l = findTagList( expression ); + return !l.empty() ? l.front()->cdata() : EmptyString; + } + + const Tag* Tag::findTag( const std::string& expression ) const + { + const ConstTagList& l = findTagList( expression ); + return !l.empty() ? l.front() : 0; + } + + ConstTagList Tag::findTagList( const std::string& expression ) const + { + ConstTagList l; + if( expression == "/" || expression == "//" ) + return l; + + if( m_parent && expression.length() >= 2 && expression[0] == '/' + && expression[1] != '/' ) + return m_parent->findTagList( expression ); + + unsigned len = 0; + Tag* p = parse( expression, len ); +// if( p ) +// printf( "parsed tree: %s\n", p->xml().c_str() ); + l = evaluateTagList( p ); + delete p; + return l; + } + + ConstTagList Tag::evaluateTagList( Tag* token ) const + { + ConstTagList result; + if( !token ) + return result; + +// printf( "evaluateTagList called in Tag %s and Token %s (type: %s)\n", name().c_str(), +// token->name().c_str(), token->findAttribute( TYPE ).c_str() ); + + TokenType tokenType = (TokenType)atoi( token->findAttribute( TYPE ).c_str() ); + switch( tokenType ) + { + case XTUnion: + add( result, evaluateUnion( token ) ); + break; + case XTElement: + { +// printf( "in XTElement, token: %s\n", token->name().c_str() ); + if( token->name() == name() || token->name() == "*" ) + { +// printf( "found %s\n", name().c_str() ); + const TagList& tokenChildren = token->children(); + if( tokenChildren.size() ) + { + bool predicatesSucceeded = true; + TagList::const_iterator cit = tokenChildren.begin(); + for( ; cit != tokenChildren.end(); ++cit ) + { + if( (*cit)->hasAttribute( "predicate", "true" ) ) + { + predicatesSucceeded = evaluatePredicate( (*cit) ); + if( !predicatesSucceeded ) + return result; + } + } + + bool hasElementChildren = false; + cit = tokenChildren.begin(); + for( ; cit != tokenChildren.end(); ++cit ) + { + if( (*cit)->hasAttribute( "predicate", "true" ) || + (*cit)->hasAttribute( "number", "true" ) ) + continue; + + hasElementChildren = true; + +// printf( "checking %d children of token %s\n", tokenChildren.size(), token->name().c_str() ); + if( m_children && !m_children->empty() ) + { + TagList::const_iterator it = m_children->begin(); + for( ; it != m_children->end(); ++it ) + { + add( result, (*it)->evaluateTagList( (*cit) ) ); + } + } + else if( atoi( (*cit)->findAttribute( TYPE ).c_str() ) == XTDoubleDot && m_parent ) + { + (*cit)->addAttribute( TYPE, XTDot ); + add( result, m_parent->evaluateTagList( (*cit) ) ); + } + } + + if( !hasElementChildren ) + result.push_back( this ); + } + else + { +// printf( "adding %s to result set\n", name().c_str() ); + result.push_back( this ); + } + } +// else +// printf( "found %s != %s\n", token->name().c_str(), name().c_str() ); + + break; + } + case XTDoubleSlash: + { +// printf( "in XTDoubleSlash\n" ); + Tag* t = token->clone(); +// printf( "original token: %s\ncloned token: %s\n", token->xml().c_str(), n->xml().c_str() ); + t->addAttribute( TYPE, XTElement ); + add( result, evaluateTagList( t ) ); + const ConstTagList& res2 = allDescendants(); + ConstTagList::const_iterator it = res2.begin(); + for( ; it != res2.end(); ++it ) + { + add( result, (*it)->evaluateTagList( t ) ); + } + delete t; + break; + } + case XTDot: + { + const TagList& tokenChildren = token->children(); + if( !tokenChildren.empty() ) + { + add( result, evaluateTagList( tokenChildren.front() ) ); + } + else + result.push_back( this ); + break; + } + case XTDoubleDot: + { +// printf( "in XTDoubleDot\n" ); + if( m_parent ) + { + const TagList& tokenChildren = token->children(); + if( tokenChildren.size() ) + { + Tag* testtoken = tokenChildren.front(); + if( testtoken->name() == "*" ) + { + add( result, m_parent->evaluateTagList( testtoken ) ); + } + else + { + Tag* t = token->clone(); + t->addAttribute( TYPE, XTElement ); + t->m_name = m_parent->m_name; + add( result, m_parent->evaluateTagList( t ) ); + delete t; + } + } + else + { + result.push_back( m_parent ); + } + } + } + case XTInteger: + { + const TagList& l = token->children(); + if( !l.size() ) + break; + + const ConstTagList& res = evaluateTagList( l.front() ); + + int pos = atoi( token->name().c_str() ); +// printf( "checking index %d\n", pos ); + if( pos > 0 && pos <= (int)res.size() ) + { + ConstTagList::const_iterator it = res.begin(); + while ( --pos ) + { + ++it; + } + result.push_back( *it ); + } + break; + } + default: + break; + } + return result; + } + + bool Tag::evaluateBoolean( Tag* token ) const + { + if( !token ) + return false; + + bool result = false; + TokenType tokenType = (TokenType)atoi( token->findAttribute( TYPE ).c_str() ); + switch( tokenType ) + { + case XTAttribute: + if( token->name() == "*" && m_attribs && m_attribs->size() ) + result = true; + else + result = hasAttribute( token->name() ); + break; + case XTOperatorEq: + result = evaluateEquals( token ); + break; + case XTOperatorLt: + break; + case XTOperatorLtEq: + break; + case XTOperatorGtEq: + break; + case XTOperatorGt: + break; + case XTUnion: + case XTElement: + { + Tag* t = new Tag( "." ); + t->addAttribute( TYPE, XTDot ); + t->addChild( token ); + result = !evaluateTagList( t ).empty(); + t->removeChild( token ); + delete t; + break; + } + default: + break; + } + + return result; + } + + bool Tag::evaluateEquals( Tag* token ) const + { + if( !token || token->children().size() != 2 ) + return false; + + bool result = false; + TagList::const_iterator it = token->children().begin(); + Tag* ch1 = (*it); + Tag* ch2 = (*++it); + + TokenType tt1 = (TokenType)atoi( ch1->findAttribute( TYPE ).c_str() ); + TokenType tt2 = (TokenType)atoi( ch2->findAttribute( TYPE ).c_str() ); + switch( tt1 ) + { + case XTAttribute: + switch( tt2 ) + { + case XTInteger: + case XTLiteral: + result = ( findAttribute( ch1->name() ) == ch2->name() ); + break; + case XTAttribute: + result = ( hasAttribute( ch1->name() ) && hasAttribute( ch2->name() ) && + findAttribute( ch1->name() ) == findAttribute( ch2->name() ) ); + break; + default: + break; + } + break; + case XTInteger: + case XTLiteral: + switch( tt2 ) + { + case XTAttribute: + result = ( ch1->name() == findAttribute( ch2->name() ) ); + break; + case XTLiteral: + case XTInteger: + result = ( ch1->name() == ch2->name() ); + break; + default: + break; + } + break; + default: + break; + } + + return result; + } + + ConstTagList Tag::allDescendants() const + { + ConstTagList result; + + if( !m_children ) + return result; + + TagList::const_iterator it = m_children->begin(); + for( ; it != m_children->end(); ++it ) + { + result.push_back( (*it) ); + add( result, (*it)->allDescendants() ); + } + return result; + } + + ConstTagList Tag::evaluateUnion( Tag* token ) const + { + ConstTagList result; + if( !token ) + return result; + + const TagList& l = token->children(); + TagList::const_iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + add( result, evaluateTagList( (*it) ) ); + } + return result; + } + + void Tag::closePreviousToken( Tag** root, Tag** current, Tag::TokenType& type, std::string& tok ) const + { + if( !tok.empty() ) + { + addToken( root, current, type, tok ); + type = XTElement; + tok = EmptyString; + } + } + + Tag* Tag::parse( const std::string& expression, unsigned& len, Tag::TokenType border ) const + { + Tag* root = 0; + Tag* current = root; + std::string token; + +// XPathError error = XPNoError; +// XPathState state = Init; +// int expected = 0; +// bool run = true; +// bool ws = false; + + Tag::TokenType type = XTElement; + + char c; + for( ; len < expression.length(); ++len ) + { + c = expression[len]; + if( type == XTLiteralInside && c != '\'' ) + { + token += c; + continue; + } + + switch( c ) + { + case '/': + closePreviousToken( &root, ¤t, type, token ); + + if( len < expression.length()-1 && expression[len+1] == '/' ) + { +// addToken( &root, ¤t, XTDoubleSlash, "//" ); + type = XTDoubleSlash; + ++len; + } +// else +// { +// if( !current ) +// addToken( &root, ¤t, XTSlash, "/" ); +// } + break; + case ']': + closePreviousToken( &root, ¤t, type, token ); + return root; + case '[': + { + closePreviousToken( &root, ¤t, type, token ); + Tag* t = parse( expression, ++len, XTRightBracket ); + if( !addPredicate( &root, ¤t, t ) ) + delete t; + break; + } + case '(': + { + closePreviousToken( &root, ¤t, type, token ); + Tag* t = parse( expression, ++len, XTRightParenthesis ); + if( current ) + { +// printf( "added %s to %s\n", t->xml().c_str(), current->xml().c_str() ); + t->addAttribute( "argument", "true" ); + current->addChild( t ); + } + else + { + root = t; +// printf( "made %s new root\n", t->xml().c_str() ); + } + break; + } + case ')': + closePreviousToken( &root, ¤t, type, token ); + ++len; + return root; + case '\'': + if( type == XTLiteralInside ) + if( expression[len - 2] == '\\' ) + token[token.length() - 2] = c; + else + type = XTLiteral; + else + type = XTLiteralInside; + break; + case '@': + type = XTAttribute; + break; + case '.': + token += c; + if( token.size() == 1 ) + { + if( len < expression.length()-1 && expression[len+1] == '.' ) + { + type = XTDoubleDot; + ++len; + token += c; + } + else + { + type = XTDot; + } + } + break; + case '*': +// if( !root || ( current && ( current->tokenType() == XTSlash +// || current->tokenType() == XTDoubleSlash ) ) ) +// { +// addToken( &root, ¤t, type, "*" ); +// break; +// } + addToken( &root, ¤t, type, "*" ); + type = XTElement; + break; + case '+': + case '>': + case '<': + case '=': + case '|': + { + closePreviousToken( &root, ¤t, type, token ); + std::string s( 1, c ); + Tag::TokenType ttype = getType( s ); + if( ttype <= border ) + return root; + Tag* t = parse( expression, ++len, ttype ); + addOperator( &root, ¤t, t, ttype, s ); + if( border == XTRightBracket ) + return root; + break; + } + default: + token += c; + } + } + + if( !token.empty() ) + addToken( &root, ¤t, type, token ); + +// if( error != XPNoError ) +// printf( "error: %d\n", error ); + return root; + } + + void Tag::addToken( Tag **root, Tag **current, Tag::TokenType type, + const std::string& token ) const + { + Tag* t = new Tag( token ); + if( t->isNumber() && !t->children().size() ) + type = XTInteger; + t->addAttribute( TYPE, type ); + + if( *root ) + { +// printf( "new current %s, type: %d\n", token.c_str(), type ); + (*current)->addChild( t ); + *current = t; + } + else + { +// printf( "new root %s, type: %d\n", token.c_str(), type ); + *current = *root = t; + } + } + + void Tag::addOperator( Tag** root, Tag** current, Tag* arg, + Tag::TokenType type, const std::string& token ) const + { + Tag* t = new Tag( token ); + t->addAttribute( TYPE, type ); +// printf( "new operator: %s (arg1: %s, arg2: %s)\n", t->name().c_str(), (*root)->xml().c_str(), +// arg->xml().c_str() ); + t->addAttribute( "operator", "true" ); + t->addChild( *root ); + t->addChild( arg ); + *current = *root = t; + } + + bool Tag::addPredicate( Tag **root, Tag **current, Tag* token ) const + { + if( !*root || !*current ) + return false; + + if( ( token->isNumber() && !token->children().size() ) || token->name() == "+" ) + { +// printf( "found Index %s, full: %s\n", token->name().c_str(), token->xml().c_str() ); + if( !token->hasAttribute( "operator", "true" ) ) + { + token->addAttribute( TYPE, XTInteger ); + } + if( *root == *current ) + { + *root = token; +// printf( "made Index new root\n" ); + } + else + { + (*root)->removeChild( *current ); + (*root)->addChild( token ); +// printf( "added Index somewhere between root and current\n" ); + } + token->addChild( *current ); +// printf( "added Index %s, full: %s\n", token->name().c_str(), token->xml().c_str() ); + } + else + { + token->addAttribute( "predicate", "true" ); + (*current)->addChild( token ); + } + + return true; + } + + Tag::TokenType Tag::getType( const std::string& c ) + { + if( c == "|" ) + return XTUnion; + if( c == "<" ) + return XTOperatorLt; + if( c == ">" ) + return XTOperatorGt; + if( c == "*" ) + return XTOperatorMul; + if( c == "+" ) + return XTOperatorPlus; + if( c == "=" ) + return XTOperatorEq; + + return XTNone; + } + + bool Tag::isWhitespace( const char c ) + { + return ( c == 0x09 || c == 0x0a || c == 0x0d || c == 0x20 ); + } + + bool Tag::isNumber() const + { + if( m_name.empty() ) + return false; + + std::string::size_type l = m_name.length(); + std::string::size_type i = 0; + while( i < l && isdigit( m_name[i] ) ) + ++i; + return i == l; + } + + void Tag::add( ConstTagList& one, const ConstTagList& two ) + { + ConstTagList::const_iterator it = two.begin(); + for( ; it != two.end(); ++it ) + if( std::find( one.begin(), one.end(), (*it) ) == one.end() ) + one.push_back( (*it) ); + } + +} diff --git a/libs/libgloox/tag.h b/libs/libgloox/tag.h new file mode 100644 index 0000000..3575bb7 --- /dev/null +++ b/libs/libgloox/tag.h @@ -0,0 +1,710 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef TAG_H__ +#define TAG_H__ + +#include "gloox.h" + +#include +#include +#include + +namespace gloox +{ + + class Tag; + + /** + * A list of Tags. + */ + typedef std::list TagList; + + /** + * A list of const Tags. + */ + typedef std::list ConstTagList; + + /** + * @brief This is an abstraction of an XML element. + * + * @note Use setXmlns() to set namespaces and namespace prefixes. + * + * @author Jakob Schroeter + * @since 0.4 + */ + class GLOOX_API Tag + { + + friend class Parser; + + public: + + /** + * An XML element's attribute. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class GLOOX_API Attribute + { + + friend class Tag; + + public: + /** + * Creates a new Attribute from @c name, @c value and optional @c xmlns and attaches + * it to the given Tag. + * + * In the future: If @c xmlns is not empty, and if it is different from the Tag's + * default namespace, an appropriate and unique namespace declaration (prefix) will + * be added to the Tag and the attribute will be prefixed accordingly. + * @param parent The Tag to attach the Attribute to. + * @param name The attribute's name. Invalid (non-UTF-8) input will be ignored. + * @param value The attribute's value. Invalid (non-UTF-8) input will be ignored. + * @param xmlns The attribute's namespace. Invalid (non-UTF-8) input will be ignored. + */ + Attribute( Tag* parent, const std::string& name, const std::string& value, + const std::string& xmlns = EmptyString ); + + /** + * Creates a new Attribute from @c name, @c value and optional @c xmlns. + * @param name The attribute's name. Invalid (non-UTF-8) input will be ignored. + * @param value The attribute's value. Invalid (non-UTF-8) input will be ignored. + * @param xmlns The attribute's namespace. Invalid (non-UTF-8) input will be ignored. + */ + Attribute( const std::string& name, const std::string& value, + const std::string& xmlns = EmptyString ); + + /** + * Copy constructor. + * @param attr The Attribute to copy. + */ + Attribute( const Attribute& attr ); + + /** + * Destructor. + */ + virtual ~Attribute() {} + + /** + * Returns the attribute's name. + * @return The attribute's name. + */ + const std::string& name() const { return m_name; } + + /** + * Returns the attribute's value. + * @return The attribute's value. + */ + const std::string& value() const { return m_value; } + + /** + * Sets the attribute's value. + * @param value The new value. + * @return @b True if the input is valid UTF-8, @b false otherwise. Invalid + * input will be ignored. + */ + bool setValue( const std::string& value ); + + /** + * Returns the attribute's namespace. + * @return The attribute's namespace. + */ + const std::string& xmlns() const; + + /** + * Sets the attribute's namespace. + * @param xmlns The new namespace. + * @return @b True if the input is valid UTF-8, @b false otherwise. Invalid + * input will be ignored. + */ + bool setXmlns( const std::string& xmlns ); + + /** + * Sets the attribute's namespace prefix. + * @param prefix The new namespace prefix. + * @return @b True if the input is valid UTF-8, @b false otherwise. Invalid + * input will be ignored. + */ + bool setPrefix( const std::string& prefix ); + + /** + * Returns the attribute's namespace prefix. + * @return The namespace prefix. + */ + const std::string& prefix() const; + + /** + * Returns a string representation of the attribute. + * @return A string representation. + */ + const std::string xml() const; + + /** + * Checks two Attributes for equality. + * @param right The Attribute to check against the current Attribute. + */ + bool operator==( const Attribute &right ) const + { return m_name == right.m_name && m_value == right.m_value && m_xmlns == right.m_xmlns; } + + /** + * Checks two Attributes for inequality. + * @param right The Attribute to check against the current Attribute. + */ + bool operator!=( const Attribute &right ) const + { return !( *this == right ); } + + /** + * Returns @b true if the Attribute is valid, @b false otherwise. + */ + operator bool() const { return !m_name.empty(); } + + private: + void init( const std::string& name, const std::string& value, + const std::string& xmlns ); + Tag* m_parent; + std::string m_name; + std::string m_value; + std::string m_xmlns; + std::string m_prefix; + + }; + + /** + * A list of XML element attributes. + */ + typedef std::list AttributeList; + + /** + * Creates a new tag with a given name (and XML character data, if given). + * @param name The name of the element. + * @param cdata The XML character data of the element. + */ + Tag( const std::string& name, const std::string& cdata = EmptyString ); + + /** + * Creates a new tag as a child tag of the given parent, with a given name (and + * XML character data, if given). + * @param parent The parent tag. + * @param name The name of the element. + * @param cdata The XML character data of the element. + */ + Tag( Tag* parent, const std::string& name, const std::string& cdata = EmptyString ); + + /** + * Creates a new tag with a given name and an attribute. + * @param name The name of the element. + * @param attrib The attribute name. + * @param value The attribute value. + */ + Tag( const std::string& name, const std::string& attrib, const std::string& value ); + + /** + * Creates a new tag as a child tag of the given parent, with a given name and + * an attribute. + * @param parent The parent tag. + * @param name The name of the element. + * @param attrib The attribute name. + * @param value The attribute value. + */ + Tag( Tag* parent, const std::string& name, const std::string& attrib, const std::string& value ); + + /** + * Virtual destructor. + */ + virtual ~Tag(); + + /** + * This function can be used to retrieve the complete XML of a tag as a string. + * It includes all the attributes, child nodes and character data. + * @return The complete XML. + */ + const std::string xml() const; + + /** + * Sets the Tag's namespace prefix. + * @param prefix The namespace prefix. + * @return @b True if the input is valid UTF-8, @b false otherwise. Invalid + * input will be ignored. + * @since 1.0 + */ + bool setPrefix( const std::string& prefix ); + + /** + * Returns the namespace prefix for this Tag, if any. + * @return The namespace prefix. + * @since 1.0 + */ + const std::string& prefix() const { return m_prefix; } + + /** + * Returns the namespace prefix for the given namespace. + * @return The namespace prefix for the given namespace. + * @since 1.0 + */ + const std::string& prefix( const std::string& xmlns ) const; + + /* * + * Adds an XML namespace declaration to the Tag. If @b def is false, a unique prefix will + * be created, else the default namespace is set (no prefix). + * @param xmlns The namespace value. + * @param def If @b true, this sets the default namespace; if @b false, a unique namespace + * prefix will be created (unless one already exists for the namespace) and used for + * all subsequent references to the same namespace. + * @since 1.0 + */ +// const std::string addXmlns( const std::string& xmlns, bool def ); + + /** + * Sets an XML namespace with a given prefix, or the default namespace if @c prefix + * is empty. + * @param xmlns The namespace value. + * @param prefix An optional namespace prefix. + * @return @b True if the input is valid UTF-8, @b false otherwise. Invalid + * input will be ignored. + * @since 1.0 + */ + bool setXmlns( const std::string& xmlns, const std::string& prefix = EmptyString ); + + /** + * Returns the namespace for this element. + * Namespace declarations in parent tags as well as prefixes will be taken into account. + * @return The namespace for this element. + * @since 1.0 + */ + const std::string& xmlns() const; + + /** + * Returns the namespace for the given prefix, or the default namespace if + * @c prefix is empty. Namespace declarations in parent tags will be taken into account. + * Consider the following XML: + * @code + * <foo:bar xmlns:foo='foobar'/> + * @endcode + * <bar/> is in the @c foobar namespace, having a prefix of @b foo. A call to prefix() + * will return 'foo'. A call to xmlns( "foo" ) or xmlns( prefix() ) will return 'foobar'. + * A call to xmlns() will also return 'foobar' (it is a shortcut to + * xmlns( prefix() ). + * @param prefix The namespace prefix to look up, or an empty string to fetch the + * default namespace. + * @return The namespace for the given prefix, or the empty string if no such prefix exists. + * The default namespace if an empty prefix is given. + * @since 1.0 + */ + const std::string& xmlns( const std::string& prefix ) const; + + /** + * Use this function to add a new attribute to the tag. The Tag will become the owner of the + * Attribute and take care of deletion. If an Attribute with the same name already exists, + * it will be replaced by the new one. + * @param attr A pointer to the attribute to add. + * @return @b True if the input is valid UTF-8, @b false otherwise. Invalid + * input will be ignored. + * @since 1.0 + * @note Do not use this function to set XML namespaces, use setXmlns() instead. + */ + bool addAttribute( Attribute* attr ); + + /** + * Use this function to add a new attribute to the tag. + * @param name The name of the attribute. + * @param value The value of the attribute. + * @note Do not use this function to set XML namespaces, use setXmlns() instead. + * @return @b True if the input is valid UTF-8, @b false otherwise. Invalid + * input will be ignored. + */ + bool addAttribute( const std::string& name, const std::string& value ); + + /** + * Use this function to add a new attribute to the tag. The value is an @c int here. + * @param name The name of the attribute. + * @param value The value of the attribute. + * @note Do not use this function to set XML namespaces, use setXmlns() instead. + * @return @b True if the input is valid UTF-8, @b false otherwise. Invalid + * input will be ignored. + * @since 0.8 + */ + bool addAttribute( const std::string& name, int value ); + + /** + * Use this function to add a new attribute to the tag. The value is a @c long here. + * @param name The name of the attribute. + * @param value The value of the attribute. + * @return @b True if the input is valid UTF-8, @b false otherwise. Invalid + * input will be ignored. + * @note Do not use this function to set XML namespaces, use setXmlns() instead. + * @since 0.9 + */ + bool addAttribute( const std::string& name, long value ); + + /** + * Sets the given attributes. Any existing attributes are lost. + * @param attributes The attributes to set. + * @return @b True if the input is valid UTF-8, @b false otherwise. Invalid + * input will be ignored. + * @note Do not use this function to set XML namespaces, use setXmlns() instead. + * @since 0.9 + */ + void setAttributes( const AttributeList& attributes ); + + /** + * Use this function to add a child node to the tag. The Tag will be owned by Tag. + * @param child The node to be inserted. + */ + void addChild( Tag* child ); + + /** + * Use this function to add a copy of the given element to the tag. + * @param child The node to be inserted. + * @since 0.9 + */ + void addChildCopy( const Tag* child ); + + /** + * Sets the XML character data for this Tag. + * @param cdata The new cdata. + * @return @b True if the input is valid UTF-8, @b false otherwise. Invalid + * input will be ignored. + */ + bool setCData( const std::string& cdata ); + + /** + * Adds the string to the existing XML character data for this Tag. + * @param cdata The additional cdata. + * @return @b True if the input is valid UTF-8, @b false otherwise. Invalid + * input will be ignored. + */ + bool addCData( const std::string& cdata ); + + /** + * Use this function to retrieve the name of an element. + * @return The name of the tag. + */ + const std::string& name() const { return m_name; } + + /** + * Use this function to retrieve the XML character data of an element. + * @return The cdata the element contains. + */ + const std::string cdata() const; + + /** + * Use this function to fetch a const list of attributes. + * @return A constant reference to the list of attributes. + */ + const AttributeList& attributes() const; + + /** + * Use this function to fetch a const list of child elements. + * @return A constant reference to the list of child elements. + */ + const TagList& children() const; + + /** + * This function can be used to retrieve the value of a Tag's attribute. + * @param name The name of the attribute to look for. + * @return The value of the attribute if found, an empty string otherwise. + */ + const std::string& findAttribute( const std::string& name ) const; + + /** + * Checks whether the tag has a attribute with given name and optional value. + * @param name The name of the attribute to check for. + * @param value The value of the attribute to check for. + * @return Whether the attribute exists (optionally with the given value). + */ + bool hasAttribute( const std::string& name, const std::string& value = EmptyString ) const; + + /** + * This function finds and returns the @b first element within the child elements of the current tag + * that has a matching tag name. + * @param name The name of the element to search for. + * @return The found Tag, or 0. + */ + Tag* findChild( const std::string& name ) const; + + /** + * This function finds and returns the @b first element within the child elements of the current tag, + * that has a certain name, and a certain attribute with a certain value. + * @param name The name of the element to search for. + * @param attr The name of the attribute of the child element. + * @param value The value of the attribute of the child element. + * @return The found Tag, or 0. + */ + Tag* findChild( const std::string& name, const std::string& attr, + const std::string& value = EmptyString ) const; + + /** + * This function checks whether the Tag has a child element with a given name, and optionally + * this child element is checked for having a given attribute with an optional value. + * @param name The name of the child element. + * @param attr The name of the attribute of the child element. + * @param value The value of the attribute of the child element. + * @return @b True if the given child element exists, @b false otherwise. + */ + bool hasChild( const std::string& name, const std::string& attr = EmptyString, + const std::string& value = EmptyString ) const; + + /** + * This function checks whether the Tag has a child element which posesses a given attribute + * with an optional value. The name of the child element does not matter. + * @param attr The name of the attribute of the child element. + * @param value The value of the attribute of the child element. + * @return The child if found, 0 otherwise. + */ + Tag* findChildWithAttrib( const std::string& attr, const std::string& value = EmptyString ) const; + + /** + * This function checks whether the Tag has a child element which posesses a given attribute + * with an optional value. The name of the child element does not matter. + * @param attr The name of the attribute of the child element. + * @param value The value of the attribute of the child element. + * @return @b True if any such child element exists, @b false otherwise. + */ + inline bool hasChildWithAttrib( const std::string& attr, + const std::string& value = EmptyString ) const + { return findChildWithAttrib( attr, value ) ? true : false; } + + /** + * Returns a list of child tags of the current tag with the given name. + * @param name The name of the tags to look for. + * @param xmlns An optional namespace to check for. + * @return A list of tags with the given name. + * @note The tags are still linked to the current Tag and should not be deleted. + * @since 0.9 + */ + TagList findChildren( const std::string& name, const std::string& xmlns = EmptyString ) const; + + /** + * Removes and deletes all child tags that have the given name and are, optionally, + * within the given namespace. + * @param name The name of the tag(s) to remove from the list of child tags. + * @param xmlns An optional namespace to check for. + */ + void removeChild( const std::string& name, const std::string& xmlns = EmptyString ); + + /** + * Removes the given Tag from the list of child Tags. + * @param tag The Tag to remove from the list of child Tags. + * @note The Tag @p tag is not deleted. + */ + void removeChild( Tag* tag ); + + /** + * Removes the attribute with the given name and optional value from this Tag. + * @param attr The attribute's name. + * @param value The attribute's optional value. + * @param xmlns An optional namespace to check for. + */ + void removeAttribute( const std::string& attr, const std::string& value = EmptyString, + const std::string& xmlns = EmptyString ); + + /** + * This function checks whether a child element with given name exists and has + * XML character data that equals the given cdata string. + * @param name The name of the child element. + * @param cdata The character data that has to exist in the child element. + * @return @b True if a child element with given cdata exists, @b false otherwise. + */ + bool hasChildWithCData( const std::string& name, const std::string& cdata ) const; + + /** + * Returns the tag's parent Tag. + * @return The Tag above the current Tag. May be @b 0. + */ + Tag* parent() const { return m_parent; } + + /** + * This function creates a deep copy of this Tag. + * @return An independent copy of the Tag. + * @since 0.7 + */ + Tag* clone() const; + + /** + * Evaluates the given XPath expression and returns the result Tag's character data, if any. + * If more than one Tag match, only the first one's character data is returned. + * @note Currently, XPath support is somewhat limited. However, it should be useable + * for basic expressions. For now, see src/tests/xpath/xpath_test.cpp for supported + * expressions. + * @param expression An XPath expression to evaluate. + * @return A matched Tag's character data, or the empty string. + * @since 1.0 + */ + const std::string findCData( const std::string& expression ) const; + + /** + * Evaluates the given XPath expression and returns the result Tag. If more than one + * Tag match, only the first one is returned. + * @note Currently, XPath support is somewhat limited. However, it should be useable + * for basic expressions. For now, see src/tests/xpath/xpath_test.cpp for supported + * expressions. + * @param expression An XPath expression to evaluate. + * @return A matched Tag, or 0. + * @since 0.9 + */ + const Tag* findTag( const std::string& expression ) const; + + /** + * Evaluates the given XPath expression and returns the matched Tags. + * @note Currently, XPath support is somewhat limited. However, it should be useable + * for basic expressions. For now, see src/tests/xpath/xpath_test.cpp for supported + * expressions. + * @param expression An XPath expression to evaluate. + * @return A list of matched Tags, or an empty TagList. + * @since 0.9 + */ + ConstTagList findTagList( const std::string& expression ) const; + + /** + * Checks two Tags for equality. Order of attributes and child tags does matter. + * @param right The Tag to check against the current Tag. + * @since 0.9 + */ + bool operator==( const Tag &right ) const; + + /** + * Checks two Tags for inequality. Order of attributes and child tags does matter. + * @param right The Tag to check against the current Tag. + * @since 0.9 + */ + bool operator!=( const Tag &right ) const { return !( *this == right ); } + + /** + * Returns @b true if the Tag is valid, @b false otherwise. + */ + operator bool() const { return !m_name.empty(); } + + private: + /** + * Creates a new Tag by stealing the original Tag's body (elements, attributes). The + * original Tag is pretty much useless afterwards. + * @param tag The Tag to rip off. + */ + Tag( Tag* tag ); + + /** + * XPath error conditions. + */ + enum XPathError + { + XPNoError, /**< No error occured. */ + XPExpectedLeftOperand, /**< Operator expected a left-hand operand. */ + XPUnexpectedToken + }; + + enum NodeType + { + TypeTag, /**< The Node is a Tag. */ + TypeString /**< The Node is a std::string. */ + }; + + struct Node + { + Node( NodeType _type, Tag* _tag ) : type( _type ), tag( _tag ) {} + Node( NodeType _type, std::string* _str ) : type( _type ), str( _str ) {} + ~Node() {} + + NodeType type; + union + { + Tag* tag; + std::string* str; + }; + }; + + typedef std::list NodeList; + + Tag* m_parent; + TagList* m_children; + StringPList* m_cdata; + AttributeList* m_attribs; + NodeList* m_nodes; + std::string m_name; + std::string m_xmlns; + StringMap* m_xmlnss; + std::string m_prefix; + + enum TokenType + { + XTNone, + XTLeftParenthesis, + XTRightParenthesis, + XTNodeSet, + XTInteger, + XTElement, + XTLeftBracket, + XTRightBracket, + XTFunction, + XTAsterisk, + XTAttribute, + XTLiteralInside, + XTLiteral, + XTDot, + XTDoubleDot, + XTOperatorOr, + XTOperatorAnd, + XTOperatorEq, + XTOperatorNe, + XTOperatorGt, + XTOperatorLt, + XTOperatorLtEq, + XTOperatorGtEq, + XTOperatorPlus, + XTOperatorMinus, + XTOperatorMul, + XTOperatorDiv, + XTOperatorMod, + XTUnion, + XTSlash, + XTDoubleSlash + }; + + /** + * Sets a list of namespaces. + * @param xmlnss The list of namespaces. + * @since 1.0 + */ + void setXmlns( StringMap* xmlns ) + { delete m_xmlnss; m_xmlnss = xmlns; } + + Tag* parse( const std::string& expression, unsigned& len, TokenType border = XTNone ) const; + + void closePreviousToken( Tag**, Tag**, TokenType&, std::string& ) const; + void addToken( Tag **root, Tag **current, TokenType type, const std::string& token ) const; + void addOperator( Tag **root, Tag **current, Tag* arg, TokenType type, + const std::string& token ) const; + bool addPredicate( Tag **root, Tag **current, Tag* token ) const; + + TagList findChildren( const TagList& list, const std::string& name, + const std::string& xmlns = EmptyString ) const; + ConstTagList evaluateTagList( Tag* token ) const; + ConstTagList evaluateUnion( Tag* token ) const; + ConstTagList allDescendants() const; + + static TokenType getType( const std::string& c ); + + static bool isWhitespace( const char c ); + bool isNumber() const; + + bool evaluateBoolean( Tag* token ) const; + bool evaluatePredicate( Tag* token ) const { return evaluateBoolean( token ); } + bool evaluateEquals( Tag* token ) const; + + static void add( ConstTagList& one, const ConstTagList& two ); + }; + +} + +#endif // TAG_H__ diff --git a/libs/libgloox/taghandler.h b/libs/libgloox/taghandler.h new file mode 100644 index 0000000..9766851 --- /dev/null +++ b/libs/libgloox/taghandler.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef TAGHANDLER_H__ +#define TAGHANDLER_H__ + +#include "tag.h" + +namespace gloox +{ + + /** + * @brief A virtual interface which can be reimplemented to receive non-XMPP Core stanzas. + * + * Derived classes can be registered as TagHandlers with the ClientBase. + * A TagHandler can handle arbitrary elements not defined by RFC 3920, XMPP Core. + * + * It can also be used to handle Tags emitted by Parser. + * + * @author Jakob Schroeter + */ + class GLOOX_API TagHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~TagHandler() {} + + /** + * This function is called when a registered XML element arrives. + * As with every handler in gloox, the Tag is going to be deleted after this function returned. + * If you need a copy afterwards, create it using Tag::clone(). + * @param tag The complete Tag. + */ + virtual void handleTag( Tag* tag ) = 0; + }; + +} + +#endif // TAGHANDLER_H__ diff --git a/libs/libgloox/tlsbase.h b/libs/libgloox/tlsbase.h new file mode 100644 index 0000000..101c493 --- /dev/null +++ b/libs/libgloox/tlsbase.h @@ -0,0 +1,149 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef TLSBASE_H__ +#define TLSBASE_H__ + +#include "gloox.h" +#include "mutex.h" +#include "tlshandler.h" + +namespace gloox +{ + + /** + * @brief An abstract base class for TLS implementations. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API TLSBase + { + public: + /** + * Constructor. + * @param th The TLSHandler to handle TLS-related events. + * @param server The server to use in certificate verification. + */ + TLSBase( TLSHandler* th, const std::string server ) + : m_handler( th ), m_server( server ), m_secure( false ), m_valid( false ), m_initLib( true ) + {} + + /** + * Virtual destructor. + */ + virtual ~TLSBase() {} + + /** + * Initializes the TLS module. This function must be called (and execute successfully) + * before the module can be used. + * @param clientKey The absolute path to the user's private key in PEM format. + * @param clientCerts A path to a certificate bundle in PEM format. + * @param cacerts A list of absolute paths to CA root certificate files in PEM format. + * @return @b False if initialization failed, @b true otherwise. + * @since 1.0 + */ + virtual bool init( const std::string& clientKey = EmptyString, + const std::string& clientCerts = EmptyString, + const StringList& cacerts = StringList() ) = 0; + + /** + * Enables/disables initialization of the underlying TLS library. By default, + * initialization is performed. You may want to switch it off if the TLS library + * is used elsewhere in your applicationas well and you have no control over the + * initialization. + * @param init Whether or not to intialize the underlying TLS library. + */ + void setInitLib( bool init ) { m_initLib = init; } + + /** + * Use this function to feed unencrypted data to the encryption implementation. + * The encrypted result will be pushed to the TLSHandler's handleEncryptedData() function. + * @param data The data to encrypt. + * @return Whether or not the data was used successfully. + */ + virtual bool encrypt( const std::string& data ) = 0; + + /** + * Use this function to feed encrypted data or received handshake data to the + * encryption implementation. Handshake data will be eaten, unencrypted data + * will be pushed to the TLSHandler's handleDecryptedData() function. + * @param data The data to decrypt. + * @return The number of bytes used from the input. + */ + virtual int decrypt( const std::string& data ) = 0; + + /** + * This function performs internal cleanup and will be called after a failed handshake attempt. + */ + virtual void cleanup() = 0; + + /** + * This functiopn performs the TLS handshake. Handshake data from the server side should be + * fed in using decrypt(). Handshake data that is to be sent to the other side is pushed through + * TLSBase's handleEncryptedData(). + * @return @b True if the handshake was successful or if more input is needed, @b false if the + * handshake failed. + */ + virtual bool handshake() = 0; + + /** + * Returns the state of the encryption. + * @return The state of the encryption. + */ + virtual bool isSecure() const { return m_secure; } + + /** + * Use this function to set a number of trusted root CA certificates which shall be + * used to verify a servers certificate. + * @param cacerts A list of absolute paths to CA root certificate files in PEM format. + */ + virtual void setCACerts( const StringList& cacerts ) = 0; + + /** + * This function is used to retrieve certificate and connection info of a encrypted connection. + * @return Certificate information. + */ + virtual const CertInfo& fetchTLSInfo() const { return m_certInfo; } + + /** + * Use this function to set the user's certificate and private key. The certificate will + * be presented to the server upon request and can be used for SASL EXTERNAL authentication. + * The user's certificate file should be a bundle of more than one certificate in PEM format. + * The first one in the file should be the user's certificate, each cert following that one + * should have signed the previous one. + * @note These certificates are not necessarily the same as those used to verify the server's + * certificate. + * @param clientKey The absolute path to the user's private key in PEM format. + * @param clientCerts A path to a certificate bundle in PEM format. + */ + virtual void setClientCert( const std::string& clientKey, const std::string& clientCerts ) = 0; + + protected: + TLSHandler* m_handler; + StringList m_cacerts; + std::string m_clientKey; + std::string m_clientCerts; + std::string m_server; + CertInfo m_certInfo; + util::Mutex m_mutex; + bool m_secure; + bool m_valid; + bool m_initLib; + + }; + +} + +#endif // TLSBASE_H__ diff --git a/libs/libgloox/tlsdefault.cpp b/libs/libgloox/tlsdefault.cpp new file mode 100644 index 0000000..2c09640 --- /dev/null +++ b/libs/libgloox/tlsdefault.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2007-2009 by Jakob Schroeter + * This file is part of the gloox library. http://camaya.net/gloox + * + * This software is distributed under a license. The full license + * agreement can be found in the file LICENSE in this distribution. + * This software may not be copied, modified, sold or distributed + * other than expressed in the named license agreement. + * + * This software is distributed without any warranty. + */ + +#include "tlsdefault.h" + +#include "tlshandler.h" + +#include "config.h" + +#if defined( HAVE_GNUTLS ) +# define HAVE_TLS +# include "tlsgnutlsclient.h" +# include "tlsgnutlsclientanon.h" +# include "tlsgnutlsserveranon.h" +#elif defined( HAVE_OPENSSL ) +# define HAVE_TLS +# include "tlsopensslclient.h" +#ifndef __SYMBIAN32__ +# include "tlsopensslserver.h" +#endif +#elif defined( HAVE_WINTLS ) +# define HAVE_TLS +# include "tlsschannel.h" +#endif + +namespace gloox +{ + + TLSDefault::TLSDefault( TLSHandler* th, const std::string server, Type type ) + : TLSBase( th, server ), m_impl( 0 ) + { + switch( type ) + { + case VerifyingClient: +#ifdef HAVE_GNUTLS + m_impl = new GnuTLSClient( th, server ); +#elif defined( HAVE_OPENSSL ) + m_impl = new OpenSSLClient( th, server ); +#elif defined( HAVE_WINTLS ) + m_impl = new SChannel( th, server ); +#endif + break; + case AnonymousClient: +#ifdef HAVE_GNUTLS + m_impl = new GnuTLSClientAnon( th ); +#endif + break; + case AnonymousServer: +#ifdef HAVE_GNUTLS + m_impl = new GnuTLSServerAnon( th ); +#endif + break; + case VerifyingServer: +#ifdef HAVE_OPENSSL +#ifndef __SYMBIAN32__ + m_impl = new OpenSSLServer( th ); +#endif +#endif + break; + default: + break; + } + } + + TLSDefault::~TLSDefault() + { + delete m_impl; + } + + bool TLSDefault::init( const std::string& clientKey, + const std::string& clientCerts, + const StringList& cacerts ) + { + return m_impl ? m_impl->init( clientKey, clientCerts, + cacerts ) : false; + } + + int TLSDefault::types() + { + int types = 0; +#ifdef HAVE_GNUTLS + types |= VerifyingClient; + types |= AnonymousClient; + types |= AnonymousServer; +#elif defined( HAVE_OPENSSL ) + types |= VerifyingClient; + types |= VerifyingServer; +#elif defined( HAVE_WINTLS ) + types |= VerifyingClient; +#endif + return types; + } + + bool TLSDefault::encrypt( const std::string& data ) + { + return m_impl ? m_impl->encrypt( data ) : false; + } + + int TLSDefault::decrypt( const std::string& data ) + { + return m_impl ? m_impl->decrypt( data ) : 0; + } + + void TLSDefault::cleanup() + { + if( m_impl ) + m_impl->cleanup(); + } + + bool TLSDefault::handshake() + { + return m_impl ? m_impl->handshake() : false; + } + + bool TLSDefault::isSecure() const + { + return m_impl ? m_impl->isSecure() : false; + } + + void TLSDefault::setCACerts( const StringList& cacerts ) + { + if( m_impl ) + m_impl->setCACerts( cacerts ); + } + + const CertInfo& TLSDefault::fetchTLSInfo() const + { + return m_impl ? m_impl->fetchTLSInfo() : m_certInfo; + } + + void TLSDefault::setClientCert( const std::string& clientKey, const std::string& clientCerts ) + { + if( m_impl ) + m_impl->setClientCert( clientKey, clientCerts ); + } + +} diff --git a/libs/libgloox/tlsdefault.h b/libs/libgloox/tlsdefault.h new file mode 100644 index 0000000..cee4940 --- /dev/null +++ b/libs/libgloox/tlsdefault.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2007-2009 by Jakob Schroeter + * This file is part of the gloox library. http://camaya.net/gloox + * + * This software is distributed under a license. The full license + * agreement can be found in the file LICENSE in this distribution. + * This software may not be copied, modified, sold or distributed + * other than expressed in the named license agreement. + * + * This software is distributed without any warranty. + */ + + +#ifndef TLSDEFAULT_H__ +#define TLSDEFAULT_H__ + +#include "tlsbase.h" + +namespace gloox +{ + + class TLSHandler; + + /** + * @brief This is an abstraction of the various TLS backends. + * + * You should use an instance of this class should you whish to use TLS encryption. + * TLS support for the main XMPP connection is managed by Client/ClientBase directly. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API TLSDefault : public TLSBase + { + public: + + /** + * Supported TLS types. + */ + enum Type + { + VerifyingClient = 1, /**< TLS client, verifying, available for all + * TLS implementations. */ + AnonymousClient = 2, /**< Anonymous TLS client (non-verifying), available with + * GnuTLS. */ + VerifyingServer = 4, /**< TLS server, verifying, currently not available. */ + AnonymousServer = 8 /**< Anonymous TLS server (non-verifying), available with + * GnuTLS. */ + }; + + /** + * Constructs a new TLS wrapper. + * @param th The TLSHandler to handle TLS-related events. + * @param server The server to use in certificate verification. + * @param type What you want to use this TLS object for. + */ + TLSDefault( TLSHandler* th, const std::string server, Type type = VerifyingClient ); + + /** + * Virtual Destructor. + */ + virtual ~TLSDefault(); + + // reimplemented from TLSBase + virtual bool init( const std::string& clientKey = EmptyString, + const std::string& clientCerts = EmptyString, + const StringList& cacerts = StringList() ); + + // reimplemented from TLSBase + virtual bool encrypt( const std::string& data ); + + // reimplemented from TLSBase + virtual int decrypt( const std::string& data ); + + // reimplemented from TLSBase + virtual void cleanup(); + + // reimplemented from TLSBase + virtual bool handshake(); + + // reimplemented from TLSBase + virtual bool isSecure() const; + + // reimplemented from TLSBase + virtual void setCACerts( const StringList& cacerts ); + + // reimplemented from TLSBase + virtual const CertInfo& fetchTLSInfo() const; + + // reimplemented from TLSBase + virtual void setClientCert( const std::string& clientKey, const std::string& clientCerts ); + + /** + * Returns an ORed list of supported TLS types. + * @return ORed TLSDefault::type members. + */ + static int types(); + + private: + TLSBase* m_impl; + + }; +} + +#endif // TLSDEFAULT_H__ diff --git a/libs/libgloox/tlsgnutlsbase.cpp b/libs/libgloox/tlsgnutlsbase.cpp new file mode 100644 index 0000000..d98c802 --- /dev/null +++ b/libs/libgloox/tlsgnutlsbase.cpp @@ -0,0 +1,178 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "tlsgnutlsbase.h" + +#ifdef HAVE_GNUTLS + +#include +#include +#include + +namespace gloox +{ + + GnuTLSBase::GnuTLSBase( TLSHandler* th, const std::string& server ) + : TLSBase( th, server ), m_session( new gnutls_session_t ), m_buf( 0 ), m_bufsize( 17000 ) + { + m_buf = (char*)calloc( m_bufsize + 1, sizeof( char ) ); + } + + GnuTLSBase::~GnuTLSBase() + { + free( m_buf ); + m_buf = 0; + cleanup(); + delete m_session; +// FIXME: It segfaults if more then one account uses +// encryption at same time, so we comment it for now. +// Do we need to deinit at all? +// gnutls_global_deinit(); + } + + bool GnuTLSBase::encrypt( const std::string& data ) + { + if( !m_secure ) + { + handshake(); + return true; + } + + ssize_t ret = 0; + std::string::size_type sum = 0; + do + { + ret = gnutls_record_send( *m_session, data.c_str() + sum, data.length() - sum ); + sum += ret; + } + while( ( ret == GNUTLS_E_AGAIN ) || ( ret == GNUTLS_E_INTERRUPTED ) || sum < data.length() ); + return true; + } + + int GnuTLSBase::decrypt( const std::string& data ) + { + m_recvBuffer += data; + + if( !m_secure ) + { + handshake(); + return static_cast( data.length() ); + } + + int sum = 0; + int ret = 0; + do + { + ret = static_cast( gnutls_record_recv( *m_session, m_buf, m_bufsize ) ); + + if( ret > 0 && m_handler ) + { + m_handler->handleDecryptedData( this, std::string( m_buf, ret ) ); + sum += ret; + } + } + while( ret > 0 ); + + return sum; + } + + void GnuTLSBase::cleanup() + { + if( !m_mutex.trylock() ) + return; + + TLSHandler* handler = m_handler; + m_handler = 0; + gnutls_bye( *m_session, GNUTLS_SHUT_RDWR ); + gnutls_db_remove_session( *m_session ); + gnutls_credentials_clear( *m_session ); + if( m_secure ) + gnutls_deinit( *m_session ); + + m_secure = false; + m_valid = false; + delete m_session; + m_session = 0; + m_session = new gnutls_session_t; + m_handler = handler; + + m_mutex.unlock(); + } + + bool GnuTLSBase::handshake() + { + if( !m_handler ) + return false; + + int ret = gnutls_handshake( *m_session ); + if( ret < 0 && gnutls_error_is_fatal( ret ) ) + { + gnutls_perror( ret ); + gnutls_db_remove_session( *m_session ); + gnutls_deinit( *m_session ); + m_valid = false; + + m_handler->handleHandshakeResult( this, false, m_certInfo ); + return false; + } + else if( ret == GNUTLS_E_AGAIN ) + { + return true; + } + + m_secure = true; + + getCertInfo(); + + m_handler->handleHandshakeResult( this, true, m_certInfo ); + return true; + } + + ssize_t GnuTLSBase::pullFunc( void* data, size_t len ) + { + ssize_t cpy = ( len > m_recvBuffer.length() ) ? ( m_recvBuffer.length() ) : ( len ); + if( cpy > 0 ) + { + memcpy( data, (const void*)m_recvBuffer.c_str(), cpy ); + m_recvBuffer.erase( 0, cpy ); + return cpy; + } + else + { + errno = EAGAIN; + return GNUTLS_E_AGAIN; + } + } + + ssize_t GnuTLSBase::pullFunc( gnutls_transport_ptr_t ptr, void* data, size_t len ) + { + return static_cast( ptr )->pullFunc( data, len ); + } + + ssize_t GnuTLSBase::pushFunc( const void* data, size_t len ) + { + if( m_handler ) + m_handler->handleEncryptedData( this, std::string( (const char*)data, len ) ); + + return len; + } + + ssize_t GnuTLSBase::pushFunc( gnutls_transport_ptr_t ptr, const void* data, size_t len ) + { + return static_cast( ptr )->pushFunc( data, len ); + } + +} + +#endif // HAVE_GNUTLS diff --git a/libs/libgloox/tlsgnutlsbase.h b/libs/libgloox/tlsgnutlsbase.h new file mode 100644 index 0000000..79ef968 --- /dev/null +++ b/libs/libgloox/tlsgnutlsbase.h @@ -0,0 +1,92 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef TLSGNUTLSBASE_H__ +#define TLSGNUTLSBASE_H__ + +#include "tlsbase.h" + +#include "config.h" + +#ifdef HAVE_GNUTLS + +#include +#include + +namespace gloox +{ + + /** + * @brief This is the common base class for (stream) encryption using GnuTLS. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GnuTLSBase : public TLSBase + { + public: + /** + * Constructor. + * @param th The TLSHandler to handle TLS-related events. + * @param server The server to use in certificate verification. + */ + GnuTLSBase( TLSHandler* th, const std::string& server = EmptyString ); + + /** + * Virtual destructor. + */ + virtual ~GnuTLSBase(); + + // reimplemented from TLSBase + virtual bool encrypt( const std::string& data ); + + // reimplemented from TLSBase + virtual int decrypt( const std::string& data ); + + // reimplemented from TLSBase + virtual void cleanup(); + + // reimplemented from TLSBase + virtual bool handshake(); + + // reimplemented from TLSBase + virtual void setCACerts( const StringList& /*cacerts*/ ) {} + + // reimplemented from TLSBase + virtual void setClientCert( const std::string& /*clientKey*/, const std::string& /*clientCerts*/ ) {} + + protected: + virtual void getCertInfo() {} + + gnutls_session_t* m_session; + + std::string m_recvBuffer; + char* m_buf; + const int m_bufsize; + + ssize_t pullFunc( void* data, size_t len ); + static ssize_t pullFunc( gnutls_transport_ptr_t ptr, void* data, size_t len ); + + ssize_t pushFunc( const void* data, size_t len ); + static ssize_t pushFunc( gnutls_transport_ptr_t ptr, const void* data, size_t len ); + + }; + +} + +#endif // HAVE_GNUTLS + +#endif // TLSGNUTLSBASE_H__ diff --git a/libs/libgloox/tlsgnutlsclient.cpp b/libs/libgloox/tlsgnutlsclient.cpp new file mode 100644 index 0000000..c1d24c2 --- /dev/null +++ b/libs/libgloox/tlsgnutlsclient.cpp @@ -0,0 +1,223 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "tlsgnutlsclient.h" + +#ifdef HAVE_GNUTLS + +#include + +namespace gloox +{ + + GnuTLSClient::GnuTLSClient( TLSHandler* th, const std::string& server ) + : GnuTLSBase( th, server ) + { + } + + GnuTLSClient::~GnuTLSClient() + { + } + + void GnuTLSClient::cleanup() + { + GnuTLSBase::cleanup(); + init(); + } + + bool GnuTLSClient::init( const std::string& clientKey, + const std::string& clientCerts, + const StringList& cacerts ) + { + const int protocolPriority[] = { +#ifdef GNUTLS_TLS1_2 + GNUTLS_TLS1_2, +#endif + GNUTLS_TLS1_1, GNUTLS_TLS1, 0 }; + const int kxPriority[] = { GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, GNUTLS_KX_DHE_DSS, 0 }; + const int cipherPriority[] = { GNUTLS_CIPHER_AES_256_CBC, GNUTLS_CIPHER_AES_128_CBC, + GNUTLS_CIPHER_3DES_CBC, GNUTLS_CIPHER_ARCFOUR, 0 }; + const int compPriority[] = { GNUTLS_COMP_ZLIB, GNUTLS_COMP_NULL, 0 }; + const int macPriority[] = { GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0 }; + + if( m_initLib && gnutls_global_init() != 0 ) + return false; + + if( gnutls_certificate_allocate_credentials( &m_credentials ) < 0 ) + return false; + + if( gnutls_init( m_session, GNUTLS_CLIENT ) != 0 ) + { + gnutls_certificate_free_credentials( m_credentials ); + return false; + } + + gnutls_protocol_set_priority( *m_session, protocolPriority ); + gnutls_cipher_set_priority( *m_session, cipherPriority ); + gnutls_compression_set_priority( *m_session, compPriority ); + gnutls_kx_set_priority( *m_session, kxPriority ); + gnutls_mac_set_priority( *m_session, macPriority ); + gnutls_credentials_set( *m_session, GNUTLS_CRD_CERTIFICATE, m_credentials ); + + gnutls_transport_set_ptr( *m_session, (gnutls_transport_ptr_t)this ); + gnutls_transport_set_push_function( *m_session, pushFunc ); + gnutls_transport_set_pull_function( *m_session, pullFunc ); + + m_valid = true; + return true; + } + + void GnuTLSClient::setCACerts( const StringList& cacerts ) + { + m_cacerts = cacerts; + + StringList::const_iterator it = m_cacerts.begin(); + for( ; it != m_cacerts.end(); ++it ) + gnutls_certificate_set_x509_trust_file( m_credentials, (*it).c_str(), GNUTLS_X509_FMT_PEM ); + } + + void GnuTLSClient::setClientCert( const std::string& clientKey, const std::string& clientCerts ) + { + m_clientKey = clientKey; + m_clientCerts = clientCerts; + + if( !m_clientKey.empty() && !m_clientCerts.empty() ) + { + gnutls_certificate_set_x509_key_file( m_credentials, m_clientCerts.c_str(), + m_clientKey.c_str(), GNUTLS_X509_FMT_PEM ); + } + } + + void GnuTLSClient::getCertInfo() + { + unsigned int status; + bool error = false; + + gnutls_certificate_free_ca_names( m_credentials ); + + if( gnutls_certificate_verify_peers2( *m_session, &status ) < 0 ) + error = true; + + m_certInfo.status = 0; + if( status & GNUTLS_CERT_INVALID ) + m_certInfo.status |= CertInvalid; + if( status & GNUTLS_CERT_SIGNER_NOT_FOUND ) + m_certInfo.status |= CertSignerUnknown; + if( status & GNUTLS_CERT_REVOKED ) + m_certInfo.status |= CertRevoked; + if( status & GNUTLS_CERT_SIGNER_NOT_CA ) + m_certInfo.status |= CertSignerNotCa; + const gnutls_datum_t* certList = 0; + unsigned int certListSize; + if( !error && ( ( certList = gnutls_certificate_get_peers( *m_session, &certListSize ) ) == 0 ) ) + error = true; + + gnutls_x509_crt_t* cert = new gnutls_x509_crt_t[certListSize+1]; + for( unsigned int i=0; !error && ( i 0 ) + && certListSize > 0 ) + certListSize--; + + bool chain = true; + for( unsigned int i=1; !error && ( i time( 0 ) ) + m_certInfo.status |= CertNotActive; + m_certInfo.date_from = t; + + t = (int)gnutls_x509_crt_get_expiration_time( cert[0] ); + if( t == -1 ) + error = true; + else if( t < time( 0 ) ) + m_certInfo.status |= CertExpired; + m_certInfo.date_to = t; + + char name[64]; + size_t nameSize = sizeof( name ); + gnutls_x509_crt_get_issuer_dn( cert[0], name, &nameSize ); + m_certInfo.issuer = name; + + nameSize = sizeof( name ); + gnutls_x509_crt_get_dn( cert[0], name, &nameSize ); + m_certInfo.server = name; + + const char* info; + info = gnutls_compression_get_name( gnutls_compression_get( *m_session ) ); + if( info ) + m_certInfo.compression = info; + + info = gnutls_mac_get_name( gnutls_mac_get( *m_session ) ); + if( info ) + m_certInfo.mac = info; + + info = gnutls_cipher_get_name( gnutls_cipher_get( *m_session ) ); + if( info ) + m_certInfo.cipher = info; + + info = gnutls_protocol_get_name( gnutls_protocol_get_version( *m_session ) ); + if( info ) + m_certInfo.protocol = info; + + if( !gnutls_x509_crt_check_hostname( cert[0], m_server.c_str() ) ) + m_certInfo.status |= CertWrongPeer; + + for( unsigned int i = 0; i < certListSize; ++i ) + gnutls_x509_crt_deinit( cert[i] ); + + delete[] cert; + + m_valid = true; + } + + static bool verifyCert( gnutls_x509_crt_t cert, unsigned result ) + { + return ! ( ( result & GNUTLS_CERT_INVALID ) + || gnutls_x509_crt_get_expiration_time( cert ) < time( 0 ) + || gnutls_x509_crt_get_activation_time( cert ) > time( 0 ) ); + } + + bool GnuTLSClient::verifyAgainst( gnutls_x509_crt_t cert, gnutls_x509_crt_t issuer ) + { + unsigned int result; + gnutls_x509_crt_verify( cert, &issuer, 1, 0, &result ); + return verifyCert( cert, result ); + } + + bool GnuTLSClient::verifyAgainstCAs( gnutls_x509_crt_t cert, gnutls_x509_crt_t* CAList, int CAListSize ) + { + unsigned int result; + gnutls_x509_crt_verify( cert, CAList, CAListSize, GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT, &result ); + return verifyCert( cert, result ); + } + +} + +#endif // HAVE_GNUTLS diff --git a/libs/libgloox/tlsgnutlsclient.h b/libs/libgloox/tlsgnutlsclient.h new file mode 100644 index 0000000..0d60e74 --- /dev/null +++ b/libs/libgloox/tlsgnutlsclient.h @@ -0,0 +1,81 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef TLSGNUTLSCLIENT_H__ +#define TLSGNUTLSCLIENT_H__ + +#include "tlsgnutlsbase.h" + +#include "config.h" + +#ifdef HAVE_GNUTLS + +#include +#include + +namespace gloox +{ + + /** + * @brief This class implements a TLS backend using GnuTLS. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GnuTLSClient : public GnuTLSBase + { + public: + /** + * Constructor. + * @param th The TLSHandler to handle TLS-related events. + * @param server The server to use in certificate verification. + */ + GnuTLSClient( TLSHandler* th, const std::string& server ); + + /** + * Virtual destructor. + */ + virtual ~GnuTLSClient(); + + // reimplemented from TLSBase + virtual bool init( const std::string& clientKey = EmptyString, + const std::string& clientCerts = EmptyString, + const StringList& cacerts = StringList() ); + + // reimplemented from TLSBase + virtual void setCACerts( const StringList& cacerts ); + + // reimplemented from TLSBase + virtual void setClientCert( const std::string& clientKey, const std::string& clientCerts ); + + // reimplemented from TLSBase + virtual void cleanup(); + + private: + virtual void getCertInfo(); + + bool verifyAgainst( gnutls_x509_crt_t cert, gnutls_x509_crt_t issuer ); + bool verifyAgainstCAs( gnutls_x509_crt_t cert, gnutls_x509_crt_t *CAList, int CAListSize ); + + gnutls_certificate_credentials m_credentials; + + }; + +} + +#endif // HAVE_GNUTLS + +#endif // TLSGNUTLSCLIENT_H__ diff --git a/libs/libgloox/tlsgnutlsclientanon.cpp b/libs/libgloox/tlsgnutlsclientanon.cpp new file mode 100644 index 0000000..d46464f --- /dev/null +++ b/libs/libgloox/tlsgnutlsclientanon.cpp @@ -0,0 +1,101 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "tlsgnutlsclientanon.h" + +#ifdef HAVE_GNUTLS + +#include + +namespace gloox +{ + + GnuTLSClientAnon::GnuTLSClientAnon( TLSHandler* th ) + : GnuTLSBase( th ) + { + } + + GnuTLSClientAnon::~GnuTLSClientAnon() + { + gnutls_anon_free_client_credentials( m_anoncred ); + } + + void GnuTLSClientAnon::cleanup() + { + GnuTLSBase::cleanup(); + init(); + } + + bool GnuTLSClientAnon::init( const std::string&, + const std::string&, + const StringList& ) + { + const int protocolPriority[] = { GNUTLS_TLS1, 0 }; + const int kxPriority[] = { GNUTLS_KX_ANON_DH, 0 }; + const int cipherPriority[] = { GNUTLS_CIPHER_AES_256_CBC, GNUTLS_CIPHER_AES_128_CBC, + GNUTLS_CIPHER_3DES_CBC, GNUTLS_CIPHER_ARCFOUR, 0 }; + const int compPriority[] = { GNUTLS_COMP_ZLIB, GNUTLS_COMP_NULL, 0 }; + const int macPriority[] = { GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0 }; + + if( m_initLib && gnutls_global_init() != 0 ) + return false; + + if( gnutls_anon_allocate_client_credentials( &m_anoncred ) < 0 ) + return false; + + if( gnutls_init( m_session, GNUTLS_CLIENT ) != 0 ) + return false; + + gnutls_protocol_set_priority( *m_session, protocolPriority ); + gnutls_cipher_set_priority( *m_session, cipherPriority ); + gnutls_compression_set_priority( *m_session, compPriority ); + gnutls_kx_set_priority( *m_session, kxPriority ); + gnutls_mac_set_priority( *m_session, macPriority ); + gnutls_credentials_set( *m_session, GNUTLS_CRD_ANON, m_anoncred ); + + gnutls_transport_set_ptr( *m_session, (gnutls_transport_ptr_t)this ); + gnutls_transport_set_push_function( *m_session, pushFunc ); + gnutls_transport_set_pull_function( *m_session, pullFunc ); + + m_valid = true; + return true; + } + + void GnuTLSClientAnon::getCertInfo() + { + m_certInfo.status = CertOk; + + const char* info; + info = gnutls_compression_get_name( gnutls_compression_get( *m_session ) ); + if( info ) + m_certInfo.compression = info; + + info = gnutls_mac_get_name( gnutls_mac_get( *m_session ) ); + if( info ) + m_certInfo.mac = info; + + info = gnutls_cipher_get_name( gnutls_cipher_get( *m_session ) ); + if( info ) + m_certInfo.cipher = info; + + info = gnutls_protocol_get_name( gnutls_protocol_get_version( *m_session ) ); + if( info ) + m_certInfo.protocol = info; + + m_valid = true; + } + +} + +#endif // HAVE_GNUTLS diff --git a/libs/libgloox/tlsgnutlsclientanon.h b/libs/libgloox/tlsgnutlsclientanon.h new file mode 100644 index 0000000..3faec75 --- /dev/null +++ b/libs/libgloox/tlsgnutlsclientanon.h @@ -0,0 +1,70 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef TLSGNUTLSCLIENTANON_H__ +#define TLSGNUTLSCLIENTANON_H__ + +#include "tlsgnutlsbase.h" + +#include "config.h" + +#ifdef HAVE_GNUTLS + +#include +#include + +namespace gloox +{ + + /** + * @brief This class implements an anonymous TLS backend using GnuTLS. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GnuTLSClientAnon : public GnuTLSBase + { + public: + /** + * Constructor. + * @param th The TLSHandler to handle TLS-related events. + */ + GnuTLSClientAnon( TLSHandler* th ); + + /** + * Virtual destructor. + */ + virtual ~GnuTLSClientAnon(); + + // reimplemented from TLSBase + virtual bool init( const std::string& clientKey = EmptyString, + const std::string& clientCerts = EmptyString, + const StringList& cacerts = StringList() ); + + // reimplemented from TLSBase + virtual void cleanup(); + + private: + virtual void getCertInfo(); + + gnutls_anon_client_credentials_t m_anoncred; + }; + +} + +#endif // HAVE_GNUTLS + +#endif // TLSGNUTLSCLIENTANON_H__ diff --git a/libs/libgloox/tlsgnutlsserveranon.cpp b/libs/libgloox/tlsgnutlsserveranon.cpp new file mode 100644 index 0000000..f851fdd --- /dev/null +++ b/libs/libgloox/tlsgnutlsserveranon.cpp @@ -0,0 +1,113 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "tlsgnutlsserveranon.h" + +#ifdef HAVE_GNUTLS + +#include + +namespace gloox +{ + + GnuTLSServerAnon::GnuTLSServerAnon( TLSHandler* th ) + : GnuTLSBase( th ), m_dhBits( 1024 ) + { + } + + GnuTLSServerAnon::~GnuTLSServerAnon() + { + gnutls_anon_free_server_credentials( m_anoncred ); + gnutls_dh_params_deinit( m_dhParams ); + } + + void GnuTLSServerAnon::cleanup() + { + GnuTLSBase::cleanup(); + init(); + } + + bool GnuTLSServerAnon::init( const std::string&, + const std::string&, + const StringList& ) + { + const int protocolPriority[] = { GNUTLS_TLS1, 0 }; + const int kxPriority[] = { GNUTLS_KX_ANON_DH, 0 }; + const int cipherPriority[] = { GNUTLS_CIPHER_AES_256_CBC, GNUTLS_CIPHER_AES_128_CBC, + GNUTLS_CIPHER_3DES_CBC, GNUTLS_CIPHER_ARCFOUR, 0 }; + const int compPriority[] = { GNUTLS_COMP_ZLIB, GNUTLS_COMP_NULL, 0 }; + const int macPriority[] = { GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0 }; + + if( m_initLib && gnutls_global_init() != 0 ) + return false; + + if( gnutls_anon_allocate_server_credentials( &m_anoncred ) < 0 ) + return false; + + generateDH(); + gnutls_anon_set_server_dh_params( m_anoncred, m_dhParams ); + + if( gnutls_init( m_session, GNUTLS_SERVER ) != 0 ) + return false; + + gnutls_protocol_set_priority( *m_session, protocolPriority ); + gnutls_cipher_set_priority( *m_session, cipherPriority ); + gnutls_compression_set_priority( *m_session, compPriority ); + gnutls_kx_set_priority( *m_session, kxPriority ); + gnutls_mac_set_priority( *m_session, macPriority ); + gnutls_credentials_set( *m_session, GNUTLS_CRD_ANON, m_anoncred ); + + gnutls_dh_set_prime_bits( *m_session, m_dhBits ); + + gnutls_transport_set_ptr( *m_session, (gnutls_transport_ptr_t)this ); + gnutls_transport_set_push_function( *m_session, pushFunc ); + gnutls_transport_set_pull_function( *m_session, pullFunc ); + + m_valid = true; + return true; + } + + void GnuTLSServerAnon::generateDH() + { + gnutls_dh_params_init( &m_dhParams ); + gnutls_dh_params_generate2( m_dhParams, m_dhBits ); + } + + void GnuTLSServerAnon::getCertInfo() + { + m_certInfo.status = CertOk; + + const char* info; + info = gnutls_compression_get_name( gnutls_compression_get( *m_session ) ); + if( info ) + m_certInfo.compression = info; + + info = gnutls_mac_get_name( gnutls_mac_get( *m_session ) ); + if( info ) + m_certInfo.mac = info; + + info = gnutls_cipher_get_name( gnutls_cipher_get( *m_session ) ); + if( info ) + m_certInfo.cipher = info; + + info = gnutls_protocol_get_name( gnutls_protocol_get_version( *m_session ) ); + if( info ) + m_certInfo.protocol = info; + + m_valid = true; + } + +} + +#endif // HAVE_GNUTLS diff --git a/libs/libgloox/tlsgnutlsserveranon.h b/libs/libgloox/tlsgnutlsserveranon.h new file mode 100644 index 0000000..f66ae28 --- /dev/null +++ b/libs/libgloox/tlsgnutlsserveranon.h @@ -0,0 +1,75 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef TLSGNUTLSSERVERANON_H__ +#define TLSGNUTLSSERVERANON_H__ + +#include "tlsgnutlsbase.h" + +#include "config.h" + +#ifdef HAVE_GNUTLS + +#include +#include + +namespace gloox +{ + + /** + * @brief This class implements (stream) encryption using GnuTLS server-side. + * + * You should not need to use this class directly. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GnuTLSServerAnon : public GnuTLSBase + { + public: + /** + * Constructor. + * @param th The TLSHandler to handle TLS-related events. + */ + GnuTLSServerAnon( TLSHandler* th ); + + /** + * Virtual destructor. + */ + virtual ~GnuTLSServerAnon(); + + // reimplemented from TLSBase + virtual bool init( const std::string& clientKey = EmptyString, + const std::string& clientCerts = EmptyString, + const StringList& cacerts = StringList() ); + + // reimplemented from TLSBase + virtual void cleanup(); + + private: + virtual void getCertInfo(); + void generateDH(); + + gnutls_anon_server_credentials_t m_anoncred; + gnutls_dh_params_t m_dhParams; + + const int m_dhBits; + + }; + +} + +#endif // HAVE_GNUTLS + +#endif // TLSGNUTLSSERVERANON_H__ diff --git a/libs/libgloox/tlshandler.h b/libs/libgloox/tlshandler.h new file mode 100644 index 0000000..aa42fe7 --- /dev/null +++ b/libs/libgloox/tlshandler.h @@ -0,0 +1,68 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef TLSHANDLER_H__ +#define TLSHANDLER_H__ + +#include "macros.h" + +#include + +namespace gloox +{ + + struct CertInfo; + class TLSBase; + + /** + * @brief An interface that allows for interacting with TLS implementations derived from TLSBase. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API TLSHandler + { + public: + /** + * Virtual Destructor. + */ + virtual ~TLSHandler() {} + + /** + * Reimplement this function to receive encrypted data from a TLSBase implementation. + * @param base The encryption implementation which called this function. + * @param data The encrypted data (e.g. to send over the wire). + */ + virtual void handleEncryptedData( const TLSBase* base, const std::string& data ) = 0; + + /** + * Reimplement this function to receive decrypted data from a TLSBase implementation. + * @param base The encryption implementation which called this function. + * @param data The decrypted data (e.g. to parse). + */ + virtual void handleDecryptedData( const TLSBase* base, const std::string& data ) = 0; + + /** + * Reimplement this function to receive the result of a TLS handshake. + * @param base The encryption implementation which called this function. + * @param success Whether or not the handshake was successful. + * @param certinfo Information about the server's certificate. + */ + virtual void handleHandshakeResult( const TLSBase* base, bool success, CertInfo &certinfo ) = 0; + + }; + +} + +#endif // TLSHANDLER_H__ diff --git a/libs/libgloox/tlsopensslbase.cpp b/libs/libgloox/tlsopensslbase.cpp new file mode 100644 index 0000000..2936bad --- /dev/null +++ b/libs/libgloox/tlsopensslbase.cpp @@ -0,0 +1,333 @@ +/* + Copyright (c) 2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "tlsopensslbase.h" + +#ifdef HAVE_OPENSSL + +#include +#include +#include +#include + +#include + +namespace gloox +{ + + OpenSSLBase::OpenSSLBase( TLSHandler* th, const std::string& server ) + : TLSBase( th, server ), m_ssl( 0 ), m_ctx( 0 ), m_buf( 0 ), m_bufsize( 17000 ) + { + m_buf = (char*)calloc( m_bufsize + 1, sizeof( char ) ); + } + + OpenSSLBase::~OpenSSLBase() + { + m_handler = 0; + free( m_buf ); + SSL_CTX_free( m_ctx ); + SSL_shutdown( m_ssl ); + SSL_free( m_ssl ); + BIO_free( m_nbio ); + cleanup(); + } + + bool OpenSSLBase::init( const std::string& clientKey, + const std::string& clientCerts, + const StringList& cacerts ) + { + if( m_initLib ) + SSL_library_init(); + + SSL_COMP_add_compression_method( 193, COMP_zlib() ); + + OpenSSL_add_all_algorithms(); + + if( !setType() ) //inits m_ctx + return false; + + setClientCert( clientKey, clientCerts ); + setCACerts( cacerts ); + + if( !SSL_CTX_set_cipher_list( m_ctx, "HIGH:MEDIUM:AES:@STRENGTH" ) ) + return false; + + m_ssl = SSL_new( m_ctx ); + if( !m_ssl ) + return false; + + if( !BIO_new_bio_pair( &m_ibio, 0, &m_nbio, 0 ) ) + return false; + + SSL_set_bio( m_ssl, m_ibio, m_ibio ); + SSL_set_mode( m_ssl, SSL_MODE_AUTO_RETRY | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE ); + + ERR_load_crypto_strings(); + SSL_load_error_strings(); + + if( !privateInit() ) + return false; + + m_valid = true; + return true; + } + + bool OpenSSLBase::encrypt( const std::string& data ) + { + m_sendBuffer += data; + + if( !m_secure ) + { + handshake(); + return 0; + } + + doTLSOperation( TLSWrite ); + return true; + } + + int OpenSSLBase::decrypt( const std::string& data ) + { + m_recvBuffer += data; + + if( !m_secure ) + { + handshake(); + return 0; + } + + doTLSOperation( TLSRead ); + return true; + } + + void OpenSSLBase::setCACerts( const StringList& cacerts ) + { + m_cacerts = cacerts; + + StringList::const_iterator it = m_cacerts.begin(); + for( ; it != m_cacerts.end(); ++it ) + SSL_CTX_load_verify_locations( m_ctx, (*it).c_str(), 0 ); + } + + void OpenSSLBase::setClientCert( const std::string& clientKey, const std::string& clientCerts ) + { + m_clientKey = clientKey; + m_clientCerts = clientCerts; + + if( !m_clientKey.empty() && !m_clientCerts.empty() ) + { + if( SSL_CTX_use_certificate_chain_file( m_ctx, m_clientCerts.c_str() ) != 1 ) + { + // FIXME + } + if( SSL_CTX_use_RSAPrivateKey_file( m_ctx, m_clientKey.c_str(), SSL_FILETYPE_PEM ) != 1 ) + { + // FIXME + } + } + + if ( SSL_CTX_check_private_key( m_ctx ) != 1 ) + { + // FIXME + } + } + + void OpenSSLBase::cleanup() + { + if( !m_mutex.trylock() ) + return; + + m_secure = false; + m_valid = false; + + m_mutex.unlock(); + } + + void OpenSSLBase::doTLSOperation( TLSOperation op ) + { + if( !m_handler ) + return; + + int ret = 0; + bool onceAgain = false; + + do + { + switch( op ) + { + case TLSHandshake: + ret = handshakeFunction(); + break; + case TLSWrite: + ret = SSL_write( m_ssl, m_sendBuffer.c_str(), m_sendBuffer.length() ); + break; + case TLSRead: + ret = SSL_read( m_ssl, m_buf, m_bufsize ); + break; + } + + switch( SSL_get_error( m_ssl, ret ) ) + { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + pushFunc(); + break; + case SSL_ERROR_NONE: + if( op == TLSHandshake ) + m_secure = true; + else if( op == TLSWrite ) + m_sendBuffer.erase( 0, ret ); + else if( op == TLSRead ) + m_handler->handleDecryptedData( this, std::string( m_buf, ret ) ); + pushFunc(); + break; + default: + if( !m_secure ) + m_handler->handleHandshakeResult( this, false, m_certInfo ); + return; + break; + } + if( !onceAgain && !m_recvBuffer.length() ) + onceAgain = true; + else if( onceAgain ) + onceAgain = false; + } + while( ( ( onceAgain || m_recvBuffer.length() ) && ( !m_secure || op == TLSRead ) ) + || ( ( op == TLSWrite ) && ( ret > 0 ) )); + } + + int OpenSSLBase::openSSLTime2UnixTime( const char* time_string ) + { + char tstring[19]; + + // making seperate c string out of time string + int m = 0; + for( int n = 0; n < 12; n += 2 ) + { + tstring[m] = time_string[n]; + tstring[m + 1] = time_string[n + 1]; + tstring[m + 2] = 0; + m += 3; + } + + // converting to struct tm + tm time_st; + time_st.tm_year = ( atoi( &tstring[3 * 0] ) >= 70 ) ? atoi( &tstring[3 * 0] ) + : atoi( &tstring[3 * 0] ) + 100; + time_st.tm_mon = atoi( &tstring[3 * 1] ) - 1; + time_st.tm_mday = atoi( &tstring[3 * 2] ); + time_st.tm_hour = atoi( &tstring[3 * 3] ); + time_st.tm_min = atoi( &tstring[3 * 4] ); + time_st.tm_sec = atoi( &tstring[3 * 5] ); + + time_t unixt = mktime( &time_st ); + return unixt; + } + + bool OpenSSLBase::handshake() + { + + doTLSOperation( TLSHandshake ); + + if( !m_secure ) + return true; + + int res = SSL_get_verify_result( m_ssl ); + if( res != X509_V_OK ) + m_certInfo.status = CertInvalid; + else + m_certInfo.status = CertOk; + + X509* peer = SSL_get_peer_certificate( m_ssl ); + if( peer ) + { + char peer_CN[256]; + X509_NAME_get_text_by_NID( X509_get_issuer_name( peer ), NID_commonName, peer_CN, sizeof( peer_CN ) ); + m_certInfo.issuer = peer_CN; + X509_NAME_get_text_by_NID( X509_get_subject_name( peer ), NID_commonName, peer_CN, sizeof( peer_CN ) ); + m_certInfo.server = peer_CN; + m_certInfo.date_from = openSSLTime2UnixTime( (char*) (peer->cert_info->validity->notBefore->data) ); + m_certInfo.date_to = openSSLTime2UnixTime( (char*) (peer->cert_info->validity->notAfter->data) ); + std::string p( peer_CN ); + std::transform( p.begin(), p.end(), p.begin(), tolower ); + if( p != m_server ) + m_certInfo.status |= CertWrongPeer; + + if( ASN1_UTCTIME_cmp_time_t( X509_get_notBefore( peer ), time( 0 ) ) != -1 ) + m_certInfo.status |= CertNotActive; + + if( ASN1_UTCTIME_cmp_time_t( X509_get_notAfter( peer ), time( 0 ) ) != 1 ) + m_certInfo.status |= CertExpired; + } + else + { + m_certInfo.status = CertInvalid; + } + + const char* tmp; + tmp = SSL_get_cipher_name( m_ssl ); + if( tmp ) + m_certInfo.cipher = tmp; + + tmp = SSL_get_cipher_version( m_ssl ); + if( tmp ) + m_certInfo.protocol = tmp; + + tmp = SSL_COMP_get_name( SSL_get_current_compression( m_ssl ) ); + if( tmp ) + m_certInfo.compression = tmp; + + m_valid = true; + + m_handler->handleHandshakeResult( this, true, m_certInfo ); + return true; + } + + void OpenSSLBase::pushFunc() + { + int wantwrite; + size_t wantread; + int frombio; + int tobio; + + while( ( wantwrite = BIO_ctrl_pending( m_nbio ) ) > 0 ) + { + if( wantwrite > m_bufsize ) + wantwrite = m_bufsize; + + if( !wantwrite ) + break; + + frombio = BIO_read( m_nbio, m_buf, wantwrite ); + + if( m_handler ) + m_handler->handleEncryptedData( this, std::string( m_buf, frombio ) ); + } + + while( ( wantread = BIO_ctrl_get_read_request( m_nbio ) ) > 0 ) + { + if( wantread > m_recvBuffer.length() ) + wantread = m_recvBuffer.length(); + + if( !wantread ) + break; + + tobio = BIO_write( m_nbio, m_recvBuffer.c_str(), wantread ); + m_recvBuffer.erase( 0, tobio ); + } + } + +} + +#endif // HAVE_OPENSSL diff --git a/libs/libgloox/tlsopensslbase.h b/libs/libgloox/tlsopensslbase.h new file mode 100644 index 0000000..8e35f67 --- /dev/null +++ b/libs/libgloox/tlsopensslbase.h @@ -0,0 +1,108 @@ +/* + Copyright (c) 2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef TLSOPENSSLBASE_H__ +#define TLSOPENSSLBASE_H__ + +#include "tlsbase.h" + +#include "config.h" + +#ifdef HAVE_OPENSSL + +#include + +namespace gloox +{ + + /** + * This is a common base class for client and server-side TLS + * stream encryption implementations using OpenSSL. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class OpenSSLBase : public TLSBase + { + public: + /** + * Constructor. + * @param th The TLSHandler to handle TLS-related events. + * @param server The server to use in certificate verification. + */ + OpenSSLBase( TLSHandler* th, const std::string& server = EmptyString ); + + /** + * Virtual destructor. + */ + virtual ~OpenSSLBase(); + + // reimplemented from TLSBase + virtual bool init( const std::string& clientKey = EmptyString, + const std::string& clientCerts = EmptyString, + const StringList& cacerts = StringList() ); + + // reimplemented from TLSBase + virtual bool encrypt( const std::string& data ); + + // reimplemented from TLSBase + virtual int decrypt( const std::string& data ); + + // reimplemented from TLSBase + virtual void cleanup(); + + // reimplemented from TLSBase + virtual bool handshake(); + + // reimplemented from TLSBase + virtual void setCACerts( const StringList& cacerts ); + + // reimplemented from TLSBase + virtual void setClientCert( const std::string& clientKey, const std::string& clientCerts ); + + protected: + virtual bool setType() = 0; + virtual int handshakeFunction() = 0; + + SSL* m_ssl; + SSL_CTX* m_ctx; + BIO* m_ibio; + BIO* m_nbio; + + private: + void pushFunc(); + virtual bool privateInit() { return true; } + + enum TLSOperation + { + TLSHandshake, + TLSWrite, + TLSRead + }; + + void doTLSOperation( TLSOperation op ); + int openSSLTime2UnixTime( const char* time_string ); + + std::string m_recvBuffer; + std::string m_sendBuffer; + char* m_buf; + const int m_bufsize; + + }; + +} + +#endif // HAVE_OPENSSL + +#endif // TLSOPENSSLBASE_H__ diff --git a/libs/libgloox/tlsopensslclient.cpp b/libs/libgloox/tlsopensslclient.cpp new file mode 100644 index 0000000..8642a47 --- /dev/null +++ b/libs/libgloox/tlsopensslclient.cpp @@ -0,0 +1,47 @@ +/* + Copyright (c) 2005-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "tlsopensslclient.h" + +#ifdef HAVE_OPENSSL + +namespace gloox +{ + + OpenSSLClient::OpenSSLClient( TLSHandler* th, const std::string& server ) + : OpenSSLBase( th, server ) + { + } + + OpenSSLClient::~OpenSSLClient() + { + } + + bool OpenSSLClient::setType() + { + m_ctx = SSL_CTX_new( SSLv23_client_method() ); // FIXME: use TLSv1_client_method() as soon as OpenSSL/gtalk combo is fixed! + if( !m_ctx ) + return false; + + return true; + } + + int OpenSSLClient::handshakeFunction() + { + return SSL_connect( m_ssl ); + } + +} + +#endif // HAVE_OPENSSL diff --git a/libs/libgloox/tlsopensslclient.h b/libs/libgloox/tlsopensslclient.h new file mode 100644 index 0000000..0ad5640 --- /dev/null +++ b/libs/libgloox/tlsopensslclient.h @@ -0,0 +1,63 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef TLSOPENSSLCLIENT_H__ +#define TLSOPENSSLCLIENT_H__ + +#include "tlsopensslbase.h" + +#include "config.h" + +#ifdef HAVE_OPENSSL + +#include + +namespace gloox +{ + + /** + * This class implements a TLS client backend using OpenSSL. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class OpenSSLClient : public OpenSSLBase + { + public: + /** + * Constructor. + * @param th The TLSHandler to handle TLS-related events. + * @param server The server to use in certificate verification. + */ + OpenSSLClient( TLSHandler* th, const std::string& server ); + + /** + * Virtual destructor. + */ + virtual ~OpenSSLClient(); + + private: + // reimplemented from OpenSSLBase + virtual bool setType(); + + // reimplemented from OpenSSLBase + virtual int handshakeFunction(); + + }; + +} + +#endif // HAVE_OPENSSL + +#endif // TLSOPENSSLCLIENT_H__ diff --git a/libs/libgloox/tlsopensslserver.cpp b/libs/libgloox/tlsopensslserver.cpp new file mode 100644 index 0000000..ae0fab3 --- /dev/null +++ b/libs/libgloox/tlsopensslserver.cpp @@ -0,0 +1,266 @@ +/* + Copyright (c) 2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "tlsopensslserver.h" + +#ifdef HAVE_OPENSSL + +#ifndef __SYMBIAN32__ + +#ifndef HEADER_DH_H +#include +#endif + +namespace gloox +{ + + OpenSSLServer::OpenSSLServer( TLSHandler* th ) + : OpenSSLBase( th ) + { + } + + OpenSSLServer::~OpenSSLServer() + { + } + + bool OpenSSLServer::setType() + { + m_ctx = SSL_CTX_new( SSLv23_server_method() ); + if( !m_ctx ) + return false; + + return true; + } + + int OpenSSLServer::handshakeFunction() + { + return SSL_accept( m_ssl ); + } + + DH* getDH512() + { + static unsigned char dh512_p[] = + { + 0xF5,0x2A,0xFF,0x3C,0xE1,0xB1,0x29,0x40,0x18,0x11,0x8D,0x7C, + 0x84,0xA7,0x0A,0x72,0xD6,0x86,0xC4,0x03,0x19,0xC8,0x07,0x29, + 0x7A,0xCA,0x95,0x0C,0xD9,0x96,0x9F,0xAB,0xD0,0x0A,0x50,0x9B, + 0x02,0x46,0xD3,0x08,0x3D,0x66,0xA4,0x5D,0x41,0x9F,0x9C,0x7C, + 0xBD,0x89,0x4B,0x22,0x19,0x26,0xBA,0xAB,0xA2,0x5E,0xC3,0x55, + 0xE9,0x2A,0x05,0x5F, + }; + static unsigned char dh512_g[] = + { + 0x02, + }; + DH* dh = DH_new(); + + if( !dh ) + return 0; + + dh->p = BN_bin2bn( dh512_p, sizeof( dh512_p ), 0 ); + dh->g = BN_bin2bn( dh512_g, sizeof( dh512_g ), 0 ); + if( ( dh->p == 0 ) || ( dh->g == 0 ) ) + { + DH_free( dh ); + return 0; + } + + return dh; + } + DH* getDH1024() + { + static unsigned char dh1024_p[]={ + 0xF4,0x88,0xFD,0x58,0x4E,0x49,0xDB,0xCD,0x20,0xB4,0x9D,0xE4, + 0x91,0x07,0x36,0x6B,0x33,0x6C,0x38,0x0D,0x45,0x1D,0x0F,0x7C, + 0x88,0xB3,0x1C,0x7C,0x5B,0x2D,0x8E,0xF6,0xF3,0xC9,0x23,0xC0, + 0x43,0xF0,0xA5,0x5B,0x18,0x8D,0x8E,0xBB,0x55,0x8C,0xB8,0x5D, + 0x38,0xD3,0x34,0xFD,0x7C,0x17,0x57,0x43,0xA3,0x1D,0x18,0x6C, + 0xDE,0x33,0x21,0x2C,0xB5,0x2A,0xFF,0x3C,0xE1,0xB1,0x29,0x40, + 0x18,0x11,0x8D,0x7C,0x84,0xA7,0x0A,0x72,0xD6,0x86,0xC4,0x03, + 0x19,0xC8,0x07,0x29,0x7A,0xCA,0x95,0x0C,0xD9,0x96,0x9F,0xAB, + 0xD0,0x0A,0x50,0x9B,0x02,0x46,0xD3,0x08,0x3D,0x66,0xA4,0x5D, + 0x41,0x9F,0x9C,0x7C,0xBD,0x89,0x4B,0x22,0x19,0x26,0xBA,0xAB, + 0xA2,0x5E,0xC3,0x55,0xE9,0x2F,0x78,0xC7, + }; + static unsigned char dh1024_g[]={ + 0x02, + }; + DH* dh = DH_new(); + + if( !dh ) + return 0; + + dh->p = BN_bin2bn( dh1024_p, sizeof( dh1024_p ), 0 ); + dh->g = BN_bin2bn( dh1024_g, sizeof( dh1024_g ), 0 ); + if( ( dh->p == 0 ) || ( dh->g == 0 ) ) + { + DH_free( dh ); + return 0; + } + + return dh; + } + DH* getDH2048() + { + static unsigned char dh2048_p[]={ + 0xF6,0x42,0x57,0xB7,0x08,0x7F,0x08,0x17,0x72,0xA2,0xBA,0xD6, + 0xA9,0x42,0xF3,0x05,0xE8,0xF9,0x53,0x11,0x39,0x4F,0xB6,0xF1, + 0x6E,0xB9,0x4B,0x38,0x20,0xDA,0x01,0xA7,0x56,0xA3,0x14,0xE9, + 0x8F,0x40,0x55,0xF3,0xD0,0x07,0xC6,0xCB,0x43,0xA9,0x94,0xAD, + 0xF7,0x4C,0x64,0x86,0x49,0xF8,0x0C,0x83,0xBD,0x65,0xE9,0x17, + 0xD4,0xA1,0xD3,0x50,0xF8,0xF5,0x59,0x5F,0xDC,0x76,0x52,0x4F, + 0x3D,0x3D,0x8D,0xDB,0xCE,0x99,0xE1,0x57,0x92,0x59,0xCD,0xFD, + 0xB8,0xAE,0x74,0x4F,0xC5,0xFC,0x76,0xBC,0x83,0xC5,0x47,0x30, + 0x61,0xCE,0x7C,0xC9,0x66,0xFF,0x15,0xF9,0xBB,0xFD,0x91,0x5E, + 0xC7,0x01,0xAA,0xD3,0x5B,0x9E,0x8D,0xA0,0xA5,0x72,0x3A,0xD4, + 0x1A,0xF0,0xBF,0x46,0x00,0x58,0x2B,0xE5,0xF4,0x88,0xFD,0x58, + 0x4E,0x49,0xDB,0xCD,0x20,0xB4,0x9D,0xE4,0x91,0x07,0x36,0x6B, + 0x33,0x6C,0x38,0x0D,0x45,0x1D,0x0F,0x7C,0x88,0xB3,0x1C,0x7C, + 0x5B,0x2D,0x8E,0xF6,0xF3,0xC9,0x23,0xC0,0x43,0xF0,0xA5,0x5B, + 0x18,0x8D,0x8E,0xBB,0x55,0x8C,0xB8,0x5D,0x38,0xD3,0x34,0xFD, + 0x7C,0x17,0x57,0x43,0xA3,0x1D,0x18,0x6C,0xDE,0x33,0x21,0x2C, + 0xB5,0x2A,0xFF,0x3C,0xE1,0xB1,0x29,0x40,0x18,0x11,0x8D,0x7C, + 0x84,0xA7,0x0A,0x72,0xD6,0x86,0xC4,0x03,0x19,0xC8,0x07,0x29, + 0x7A,0xCA,0x95,0x0C,0xD9,0x96,0x9F,0xAB,0xD0,0x0A,0x50,0x9B, + 0x02,0x46,0xD3,0x08,0x3D,0x66,0xA4,0x5D,0x41,0x9F,0x9C,0x7C, + 0xBD,0x89,0x4B,0x22,0x19,0x26,0xBA,0xAB,0xA2,0x5E,0xC3,0x55, + 0xE9,0x32,0x0B,0x3B, + }; + static unsigned char dh2048_g[]={ + 0x02, + }; + DH* dh = DH_new(); + + if( !dh ) + return 0; + + dh->p = BN_bin2bn( dh2048_p, sizeof( dh2048_p ), 0 ); + dh->g = BN_bin2bn( dh2048_g, sizeof( dh2048_g ), 0 ); + if( ( dh->p == 0 ) || ( dh->g == 0 ) ) + { + DH_free( dh ); + return 0; + } + + return dh; + } + + DH* getDH4096() + { + static unsigned char dh4096_p[]={ + 0xFA,0x14,0x72,0x52,0xC1,0x4D,0xE1,0x5A,0x49,0xD4,0xEF,0x09, + 0x2D,0xC0,0xA8,0xFD,0x55,0xAB,0xD7,0xD9,0x37,0x04,0x28,0x09, + 0xE2,0xE9,0x3E,0x77,0xE2,0xA1,0x7A,0x18,0xDD,0x46,0xA3,0x43, + 0x37,0x23,0x90,0x97,0xF3,0x0E,0xC9,0x03,0x50,0x7D,0x65,0xCF, + 0x78,0x62,0xA6,0x3A,0x62,0x22,0x83,0xA1,0x2F,0xFE,0x79,0xBA, + 0x35,0xFF,0x59,0xD8,0x1D,0x61,0xDD,0x1E,0x21,0x13,0x17,0xFE, + 0xCD,0x38,0x87,0x9E,0xF5,0x4F,0x79,0x10,0x61,0x8D,0xD4,0x22, + 0xF3,0x5A,0xED,0x5D,0xEA,0x21,0xE9,0x33,0x6B,0x48,0x12,0x0A, + 0x20,0x77,0xD4,0x25,0x60,0x61,0xDE,0xF6,0xB4,0x4F,0x1C,0x63, + 0x40,0x8B,0x3A,0x21,0x93,0x8B,0x79,0x53,0x51,0x2C,0xCA,0xB3, + 0x7B,0x29,0x56,0xA8,0xC7,0xF8,0xF4,0x7B,0x08,0x5E,0xA6,0xDC, + 0xA2,0x45,0x12,0x56,0xDD,0x41,0x92,0xF2,0xDD,0x5B,0x8F,0x23, + 0xF0,0xF3,0xEF,0xE4,0x3B,0x0A,0x44,0xDD,0xED,0x96,0x84,0xF1, + 0xA8,0x32,0x46,0xA3,0xDB,0x4A,0xBE,0x3D,0x45,0xBA,0x4E,0xF8, + 0x03,0xE5,0xDD,0x6B,0x59,0x0D,0x84,0x1E,0xCA,0x16,0x5A,0x8C, + 0xC8,0xDF,0x7C,0x54,0x44,0xC4,0x27,0xA7,0x3B,0x2A,0x97,0xCE, + 0xA3,0x7D,0x26,0x9C,0xAD,0xF4,0xC2,0xAC,0x37,0x4B,0xC3,0xAD, + 0x68,0x84,0x7F,0x99,0xA6,0x17,0xEF,0x6B,0x46,0x3A,0x7A,0x36, + 0x7A,0x11,0x43,0x92,0xAD,0xE9,0x9C,0xFB,0x44,0x6C,0x3D,0x82, + 0x49,0xCC,0x5C,0x6A,0x52,0x42,0xF8,0x42,0xFB,0x44,0xF9,0x39, + 0x73,0xFB,0x60,0x79,0x3B,0xC2,0x9E,0x0B,0xDC,0xD4,0xA6,0x67, + 0xF7,0x66,0x3F,0xFC,0x42,0x3B,0x1B,0xDB,0x4F,0x66,0xDC,0xA5, + 0x8F,0x66,0xF9,0xEA,0xC1,0xED,0x31,0xFB,0x48,0xA1,0x82,0x7D, + 0xF8,0xE0,0xCC,0xB1,0xC7,0x03,0xE4,0xF8,0xB3,0xFE,0xB7,0xA3, + 0x13,0x73,0xA6,0x7B,0xC1,0x0E,0x39,0xC7,0x94,0x48,0x26,0x00, + 0x85,0x79,0xFC,0x6F,0x7A,0xAF,0xC5,0x52,0x35,0x75,0xD7,0x75, + 0xA4,0x40,0xFA,0x14,0x74,0x61,0x16,0xF2,0xEB,0x67,0x11,0x6F, + 0x04,0x43,0x3D,0x11,0x14,0x4C,0xA7,0x94,0x2A,0x39,0xA1,0xC9, + 0x90,0xCF,0x83,0xC6,0xFF,0x02,0x8F,0xA3,0x2A,0xAC,0x26,0xDF, + 0x0B,0x8B,0xBE,0x64,0x4A,0xF1,0xA1,0xDC,0xEE,0xBA,0xC8,0x03, + 0x82,0xF6,0x62,0x2C,0x5D,0xB6,0xBB,0x13,0x19,0x6E,0x86,0xC5, + 0x5B,0x2B,0x5E,0x3A,0xF3,0xB3,0x28,0x6B,0x70,0x71,0x3A,0x8E, + 0xFF,0x5C,0x15,0xE6,0x02,0xA4,0xCE,0xED,0x59,0x56,0xCC,0x15, + 0x51,0x07,0x79,0x1A,0x0F,0x25,0x26,0x27,0x30,0xA9,0x15,0xB2, + 0xC8,0xD4,0x5C,0xCC,0x30,0xE8,0x1B,0xD8,0xD5,0x0F,0x19,0xA8, + 0x80,0xA4,0xC7,0x01,0xAA,0x8B,0xBA,0x53,0xBB,0x47,0xC2,0x1F, + 0x6B,0x54,0xB0,0x17,0x60,0xED,0x79,0x21,0x95,0xB6,0x05,0x84, + 0x37,0xC8,0x03,0xA4,0xDD,0xD1,0x06,0x69,0x8F,0x4C,0x39,0xE0, + 0xC8,0x5D,0x83,0x1D,0xBE,0x6A,0x9A,0x99,0xF3,0x9F,0x0B,0x45, + 0x29,0xD4,0xCB,0x29,0x66,0xEE,0x1E,0x7E,0x3D,0xD7,0x13,0x4E, + 0xDB,0x90,0x90,0x58,0xCB,0x5E,0x9B,0xCD,0x2E,0x2B,0x0F,0xA9, + 0x4E,0x78,0xAC,0x05,0x11,0x7F,0xE3,0x9E,0x27,0xD4,0x99,0xE1, + 0xB9,0xBD,0x78,0xE1,0x84,0x41,0xA0,0xDF, + }; + static unsigned char dh4096_g[]={ + 0x02, + }; + DH* dh = DH_new(); + + if( !dh ) + return 0; + + dh->p = BN_bin2bn( dh4096_p, sizeof( dh4096_p ), 0 ); + dh->g = BN_bin2bn( dh4096_g, sizeof( dh4096_g ), 0 ); + if( ( dh->p == 0 ) || ( dh->g == 0 ) ) + { + DH_free( dh ); + return 0; + } + + return dh; + } + + DH* tmp_dh_callback( SSL* /*s*/, int is_export, int keylength ) + { + switch( keylength ) + { + case 512: + return getDH512(); + break; + case 1024: + return getDH1024(); + break; + case 2048: + return getDH2048(); + break; + case 4096: + return getDH4096(); + break; + default: + // unsupported DH param length requested + return 0; + break; + } + } + + RSA* tmp_rsa_callback( SSL* /*s*/, int is_export, int keylength ) + { + return RSA_generate_key( keylength, RSA_F4, 0, 0 ); + } + + bool OpenSSLServer::privateInit() + { + SSL_CTX_set_tmp_rsa_callback( m_ctx, tmp_rsa_callback ); + SSL_CTX_set_tmp_dh_callback( m_ctx, tmp_dh_callback ); + SSL_CTX_set_tmp_ecdh( m_ctx, EC_KEY_new_by_curve_name( NID_sect163r2 ) ); + SSL_CTX_set_options( m_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE ); + return true; + } + +} + +#endif // __SYMBIAN32__ + +#endif // HAVE_OPENSSL diff --git a/libs/libgloox/tlsopensslserver.h b/libs/libgloox/tlsopensslserver.h new file mode 100644 index 0000000..5b5d8e5 --- /dev/null +++ b/libs/libgloox/tlsopensslserver.h @@ -0,0 +1,64 @@ +/* + Copyright (c) 2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef TLSOPENSSLSERVER_H__ +#define TLSOPENSSLSERVER_H__ + +#include "tlsopensslbase.h" + +#include "config.h" + +#ifdef HAVE_OPENSSL + +#include + +namespace gloox +{ + + /** + * This class implements a TLS server backend using OpenSSL. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class OpenSSLServer : public OpenSSLBase + { + public: + /** + * Constructor. + * @param th The TLSHandler to handle TLS-related events. + */ + OpenSSLServer( TLSHandler* th ); + + /** + * Virtual destructor. + */ + virtual ~OpenSSLServer(); + + private: + // reimplemented from OpenSSLBase + virtual bool privateInit(); + // reimplemented from OpenSSLBase + virtual bool setType(); + + // reimplemented from OpenSSLBase + virtual int handshakeFunction(); + + }; + +} + +#endif // HAVE_OPENSSL + +#endif // TLSOPENSSLSERVER_H__ diff --git a/libs/libgloox/tlsschannel.cpp b/libs/libgloox/tlsschannel.cpp new file mode 100644 index 0000000..d06eddb --- /dev/null +++ b/libs/libgloox/tlsschannel.cpp @@ -0,0 +1,803 @@ +/* + * Copyright (c) 2007-2009 by Jakob Schroeter + * This file is part of the gloox library. http://camaya.net/gloox + * + * This software is distributed under a license. The full license + * agreement can be found in the file LICENSE in this distribution. + * This software may not be copied, modified, sold or distributed + * other than expressed in the named license agreement. + * + * This software is distributed without any warranty. + */ + +#include "tlsschannel.h" + +#ifdef HAVE_WINTLS + +#include // just for debugging output + +namespace gloox +{ + SChannel::SChannel( TLSHandler* th, const std::string& server ) + : TLSBase( th, server ), m_cleanedup( true ) + { + //printf(">> SChannel::SChannel()\n"); + } + + SChannel::~SChannel() + { + m_handler = 0; + cleanup(); + //printf(">> SChannel::~SChannel()\n"); + } + + bool SChannel::encrypt( const std::string& data ) + { + if( !m_handler ) + return false; + + //printf(">> SChannel::encrypt()\n"); + std::string data_copy = data; + + SecBuffer buffer[4]; + SecBufferDesc buffer_desc; + DWORD cbIoBufferLength = m_sizes.cbHeader + m_sizes.cbMaximumMessage + m_sizes.cbTrailer; + + PBYTE e_iobuffer = static_cast( LocalAlloc( LMEM_FIXED, cbIoBufferLength ) ); + + if( e_iobuffer == NULL ) + { + //printf("**** Out of memory (2)\n"); + cleanup(); + if( !m_secure ) + m_handler->handleHandshakeResult( this, false, m_certInfo ); + return false; + } + PBYTE e_message = e_iobuffer + m_sizes.cbHeader; + do + { + const size_t size = ( data_copy.size() > m_sizes.cbMaximumMessage ) + ? m_sizes.cbMaximumMessage + : data_copy.size(); + memcpy( e_message, data_copy.data(), size ); + if( data_copy.size() > m_sizes.cbMaximumMessage ) + data_copy.erase( 0, m_sizes.cbMaximumMessage ); + else + data_copy = EmptyString; + + buffer[0].pvBuffer = e_iobuffer; + buffer[0].cbBuffer = m_sizes.cbHeader; + buffer[0].BufferType = SECBUFFER_STREAM_HEADER; + + buffer[1].pvBuffer = e_message; + buffer[1].cbBuffer = size; + buffer[1].BufferType = SECBUFFER_DATA; + + buffer[2].pvBuffer = static_cast(buffer[1].pvBuffer) + buffer[1].cbBuffer; + buffer[2].cbBuffer = m_sizes.cbTrailer; + buffer[2].BufferType = SECBUFFER_STREAM_TRAILER; + + buffer[3].BufferType = SECBUFFER_EMPTY; + + buffer_desc.ulVersion = SECBUFFER_VERSION; + buffer_desc.cBuffers = 4; + buffer_desc.pBuffers = buffer; + + SECURITY_STATUS e_status = EncryptMessage( &m_context, 0, &buffer_desc, 0 ); + if( SUCCEEDED( e_status ) ) + { + std::string encrypted( reinterpret_cast(e_iobuffer), + buffer[0].cbBuffer + buffer[1].cbBuffer + buffer[2].cbBuffer ); + m_handler->handleEncryptedData( this, encrypted ); + //if (data_copy.size() <= m_sizes.cbMaximumMessage) data_copy = EmptyString; + } + else + { + LocalFree( e_iobuffer ); + if( !m_secure ) + m_handler->handleHandshakeResult( this, false, m_certInfo ); + cleanup(); + return false; + } + } + while( data_copy.size() > 0 ); + LocalFree( e_iobuffer ); + return true; + } + + int SChannel::decrypt( const std::string& data ) + { + + if( !m_handler ) + return 0; + + //printf(">> SChannel::decrypt()\n"); + if( m_secure ) + { + m_buffer += data; + + SecBuffer buffer[4]; + SecBufferDesc buffer_desc; + DWORD cbIoBufferLength = m_sizes.cbHeader + m_sizes.cbMaximumMessage + m_sizes.cbTrailer; + bool wantNewBufferSize = false; + + PBYTE e_iobuffer = static_cast( LocalAlloc( LMEM_FIXED, cbIoBufferLength ) ); + if( e_iobuffer == NULL ) + { + //printf("**** Out of memory (2)\n"); + cleanup(); + if( !m_secure ) + m_handler->handleHandshakeResult( this, false, m_certInfo ); + return 0; + } + SECURITY_STATUS e_status; + + do + { + if( wantNewBufferSize ) + { + e_iobuffer = static_cast( LocalReAlloc( e_iobuffer, cbIoBufferLength, 0 ) ); + wantNewBufferSize = false; + } + + // copy data chunk from tmp string into encryption memory buffer + memcpy( e_iobuffer, m_buffer.data(), m_buffer.size() > + cbIoBufferLength ? cbIoBufferLength : m_buffer.size() ); + + buffer[0].pvBuffer = e_iobuffer; + buffer[0].cbBuffer = static_cast( m_buffer.size() > cbIoBufferLength + ? cbIoBufferLength + : m_buffer.size() ); + buffer[0].BufferType = SECBUFFER_DATA; + buffer[1].cbBuffer = buffer[2].cbBuffer = buffer[3].cbBuffer = 0; + buffer[1].BufferType = buffer[2].BufferType = buffer[3].BufferType = SECBUFFER_EMPTY; + + buffer_desc.ulVersion = SECBUFFER_VERSION; + buffer_desc.cBuffers = 4; + buffer_desc.pBuffers = buffer; + + unsigned long processed_data = buffer[0].cbBuffer; + e_status = DecryptMessage( &m_context, &buffer_desc, 0, 0 ); + + // print_error(e_status, "decrypt() ~ DecryptMessage()"); + // for (int n=0; n<4; n++) + // printf("buffer[%d].cbBuffer: %d \t%d\n", n, buffer[n].cbBuffer, buffer[n].BufferType); + + // Locate data and (optional) extra buffers. + SecBuffer* pDataBuffer = NULL; + SecBuffer* pExtraBuffer = NULL; + for( int i = 1; i < 4; i++ ) + { + if( pDataBuffer == NULL && buffer[i].BufferType == SECBUFFER_DATA ) + { + pDataBuffer = &buffer[i]; + //printf("buffer[%d].BufferType = SECBUFFER_DATA\n",i); + } + if( pExtraBuffer == NULL && buffer[i].BufferType == SECBUFFER_EXTRA ) + { + pExtraBuffer = &buffer[i]; + } + } + if( e_status == SEC_E_OK ) + { + std::string decrypted( reinterpret_cast( pDataBuffer->pvBuffer ), + pDataBuffer->cbBuffer ); + m_handler->handleDecryptedData( this, decrypted ); + if( pExtraBuffer == NULL ) + { + m_buffer.erase( 0, processed_data ); + } + else + { + //std::cout << "m_buffer.size() = " << pExtraBuffer->cbBuffer << std::endl; + m_buffer.erase( 0, processed_data - pExtraBuffer->cbBuffer ); + //std::cout << "m_buffer.size() = " << m_buffer.size() << std::endl; + + cbIoBufferLength = m_sizes.cbHeader + m_sizes.cbMaximumMessage + m_sizes.cbTrailer; + wantNewBufferSize = true; + } + } + else if( e_status == SEC_E_INCOMPLETE_MESSAGE ) + { + if( cbIoBufferLength < 200000 && m_buffer.size() > cbIoBufferLength ) + { + cbIoBufferLength += 1000; + wantNewBufferSize = true; + } + else + { + cbIoBufferLength = m_sizes.cbHeader + m_sizes.cbMaximumMessage + m_sizes.cbTrailer; + wantNewBufferSize = true; + break; + } + } + else + { + //std::cout << "decrypt !!!ERROR!!!\n"; + if( !m_secure ) + m_handler->handleHandshakeResult( this, false, m_certInfo ); + cleanup(); + break; + } + } + while( m_buffer.size() != 0 ); + LocalFree( e_iobuffer ); + } + else + { + handshakeStage( data ); + } + //printf("<< SChannel::decrypt()\n"); + return 0; + } + + void SChannel::cleanup() + { + if( !m_mutex.trylock() ) + return; + + m_buffer = ""; + if( !m_cleanedup ) + { + m_valid = false; + m_secure = false; + m_cleanedup = true; + DeleteSecurityContext( &m_context ); + FreeCredentialsHandle( &m_credHandle ); + } + + m_mutex.unlock(); + } + + bool SChannel::handshake() + { + if( !m_handler ) + return false; + + //printf(">> SChannel::handshake()\n"); + SECURITY_STATUS error; + ULONG return_flags; + TimeStamp t; + SecBuffer obuf[1]; + SecBufferDesc obufs; + SCHANNEL_CRED tlscred; + ULONG request = ISC_REQ_ALLOCATE_MEMORY + | ISC_REQ_CONFIDENTIALITY + | ISC_REQ_EXTENDED_ERROR + | ISC_REQ_INTEGRITY + | ISC_REQ_REPLAY_DETECT + | ISC_REQ_SEQUENCE_DETECT + | ISC_REQ_STREAM + | ISC_REQ_MANUAL_CRED_VALIDATION; + + /* initialize TLS credential */ + memset( &tlscred, 0, sizeof( SCHANNEL_CRED ) ); + tlscred.dwVersion = SCHANNEL_CRED_VERSION; + tlscred.grbitEnabledProtocols = SP_PROT_TLS1; + /* acquire credentials */ + error = AcquireCredentialsHandle( 0, + UNISP_NAME, + SECPKG_CRED_OUTBOUND, + 0, + &tlscred, + 0, + 0, + &m_credHandle, + &t ); + //print_error(error, "handshake() ~ AcquireCredentialsHandle()"); + if( error != SEC_E_OK ) + { + cleanup(); + m_handler->handleHandshakeResult( this, false, m_certInfo ); + return false; + } + else + { + /* initialize buffers */ + obuf[0].cbBuffer = 0; + obuf[0].pvBuffer = 0; + obuf[0].BufferType = SECBUFFER_TOKEN; + /* initialize buffer descriptors */ + obufs.ulVersion = SECBUFFER_VERSION; + obufs.cBuffers = 1; + obufs.pBuffers = obuf; + /* negotiate security */ + SEC_CHAR* hname = const_cast( m_server.c_str() ); + + error = InitializeSecurityContextA( &m_credHandle, + 0, + hname, + request, + 0, + SECURITY_NETWORK_DREP, + 0, + 0, + &m_context, + &obufs, + &return_flags, + NULL ); + //print_error(error, "handshake() ~ InitializeSecurityContext()"); + + if( error == SEC_I_CONTINUE_NEEDED ) + { + m_cleanedup = false; + //std::cout << "obuf[1].cbBuffer: " << obuf[0].cbBuffer << "\n"; + std::string senddata( static_cast(obuf[0].pvBuffer), obuf[0].cbBuffer ); + FreeContextBuffer( obuf[0].pvBuffer ); + m_handler->handleEncryptedData( this, senddata ); + return true; + } + else + { + cleanup(); + m_handler->handleHandshakeResult( this, false, m_certInfo ); + return false; + } + } + } + + void SChannel::handshakeStage( const std::string& data ) + { + //printf(" >> handshake_stage\n"); + m_buffer += data; + + SECURITY_STATUS error; + ULONG a; + TimeStamp t; + SecBuffer ibuf[2], obuf[1]; + SecBufferDesc ibufs, obufs; + ULONG request = ISC_REQ_ALLOCATE_MEMORY + | ISC_REQ_CONFIDENTIALITY + | ISC_REQ_EXTENDED_ERROR + | ISC_REQ_INTEGRITY + | ISC_REQ_REPLAY_DETECT + | ISC_REQ_SEQUENCE_DETECT + | ISC_REQ_STREAM + | ISC_REQ_MANUAL_CRED_VALIDATION; + + SEC_CHAR* hname = const_cast( m_server.c_str() ); + + do + { + /* initialize buffers */ + ibuf[0].cbBuffer = static_cast( m_buffer.size() ); + ibuf[0].pvBuffer = static_cast( const_cast( m_buffer.c_str() ) ); + //std::cout << "Size: " << m_buffer.size() << "\n"; + ibuf[1].cbBuffer = 0; + ibuf[1].pvBuffer = 0; + obuf[0].cbBuffer = 0; + obuf[0].pvBuffer = 0; + + ibuf[0].BufferType = SECBUFFER_TOKEN; + ibuf[1].BufferType = SECBUFFER_EMPTY; + obuf[0].BufferType = SECBUFFER_EMPTY; + /* initialize buffer descriptors */ + ibufs.ulVersion = obufs.ulVersion = SECBUFFER_VERSION; + ibufs.cBuffers = 2; + obufs.cBuffers = 1; + ibufs.pBuffers = ibuf; + obufs.pBuffers = obuf; + + /* + * std::cout << "obuf[0].cbBuffer: " << obuf[0].cbBuffer << "\t" << obuf[0].BufferType << "\n"; + * std::cout << "ibuf[0].cbBuffer: " << ibuf[0].cbBuffer << "\t" << ibuf[0].BufferType << "\n"; + * std::cout << "ibuf[1].cbBuffer: " << ibuf[1].cbBuffer << "\t" << ibuf[1].BufferType << "\n"; + */ + + /* negotiate security */ + error = InitializeSecurityContextA( &m_credHandle, + &m_context, + hname, + request, + 0, + 0, + &ibufs, + 0, + 0, + &obufs, + &a, + &t ); + //print_error(error, "handshake() ~ InitializeSecurityContext()"); + if( error == SEC_E_OK ) + { + // EXTRA STUFF?? + if( ibuf[1].BufferType == SECBUFFER_EXTRA ) + { + m_buffer.erase( 0, m_buffer.size() - ibuf[1].cbBuffer ); + } + else + { + m_buffer = EmptyString; + } + setSizes(); + setCertinfos(); + + m_secure = true; + m_handler->handleHandshakeResult( this, true, m_certInfo ); + break; + } + else if( error == SEC_I_CONTINUE_NEEDED ) + { + /* + * std::cout << "obuf[0].cbBuffer: " << obuf[0].cbBuffer << "\t" << obuf[0].BufferType << "\n"; + * std::cout << "ibuf[0].cbBuffer: " << ibuf[0].cbBuffer << "\t" << ibuf[0].BufferType << "\n"; + * std::cout << "ibuf[1].cbBuffer: " << ibuf[1].cbBuffer << "\t" << ibuf[1].BufferType << "\n"; + */ + + // STUFF TO SEND?? + if( obuf[0].cbBuffer != 0 && obuf[0].pvBuffer != NULL ) + { + std::string senddata( static_cast(obuf[0].pvBuffer), obuf[0].cbBuffer ); + FreeContextBuffer( obuf[0].pvBuffer ); + m_handler->handleEncryptedData( this, senddata ); + } + // EXTRA STUFF?? + if( ibuf[1].BufferType == SECBUFFER_EXTRA ) + { + m_buffer.erase( 0, m_buffer.size() - ibuf[1].cbBuffer ); + // Call again if we aren't sending anything (otherwise the server will not send anything back + // and this function won't get called again to finish the processing). This is needed for + // NT4.0 which does not seem to process the entire buffer the first time around + if( obuf[0].cbBuffer == 0 ) + handshakeStage( EmptyString ); + } + else + { + m_buffer = EmptyString; + } + return; + } + else if( error == SEC_I_INCOMPLETE_CREDENTIALS ) + { + handshakeStage( EmptyString ); + } + else if( error == SEC_E_INCOMPLETE_MESSAGE ) + { + break; + } + else + { + cleanup(); + m_handler->handleHandshakeResult( this, false, m_certInfo ); + break; + } + } + while( true ); + } + + void SChannel::setCACerts( const StringList& /*cacerts*/ ) {} + + void SChannel::setClientCert( const std::string& /*clientKey*/, const std::string& /*clientCerts*/ ) {} + + void SChannel::setSizes() + { + if( QueryContextAttributes( &m_context, SECPKG_ATTR_STREAM_SIZES, &m_sizes ) == SEC_E_OK ) + { + //std::cout << "set_sizes success\n"; + } + else + { + //std::cout << "set_sizes no success\n"; + cleanup(); + m_handler->handleHandshakeResult( this, false, m_certInfo ); + } + } + + int SChannel::filetime2int( FILETIME t ) + { + SYSTEMTIME stUTC; + FileTimeToSystemTime(&t, &stUTC); + std::tm ts; + ts.tm_year = stUTC.wYear - 1900; + ts.tm_mon = stUTC.wMonth - 1; + ts.tm_mday = stUTC.wDay; + ts.tm_hour = stUTC.wHour; + ts.tm_min = stUTC.wMinute; + ts.tm_sec = stUTC.wSecond; + + time_t unixtime; + if ( (unixtime = mktime(&ts)) == -1 ) + unixtime = 0; + return (int)unixtime; + } + + void SChannel::validateCert() + { + bool valid = false; + HTTPSPolicyCallbackData policyHTTPS; + CERT_CHAIN_POLICY_PARA policyParameter; + CERT_CHAIN_POLICY_STATUS policyStatus; + + PCCERT_CONTEXT remoteCertContext = NULL; + PCCERT_CHAIN_CONTEXT chainContext = NULL; + CERT_CHAIN_PARA chainParameter; + PSTR serverName = const_cast( m_server.c_str() ); + + PWSTR uServerName = NULL; + DWORD csizeServerName; + + LPSTR Usages[] = { + szOID_PKIX_KP_SERVER_AUTH, + szOID_SERVER_GATED_CRYPTO, + szOID_SGC_NETSCAPE + }; + DWORD cUsages = sizeof( Usages ) / sizeof( LPSTR ); + + do + { + // Get server's certificate. + if( QueryContextAttributes( &m_context, SECPKG_ATTR_REMOTE_CERT_CONTEXT, + (PVOID)&remoteCertContext ) != SEC_E_OK ) + { + //printf("Error querying remote certificate\n"); + // !!! THROW SOME ERROR + break; + } + + // unicode conversation + // calculating unicode server name size + csizeServerName = MultiByteToWideChar( CP_ACP, 0, serverName, -1, NULL, 0 ); + uServerName = reinterpret_cast( LocalAlloc( LMEM_FIXED, + csizeServerName * sizeof( WCHAR ) ) ); + if( uServerName == NULL ) + { + //printf("SEC_E_INSUFFICIENT_MEMORY ~ Not enough memory!!!\n"); + break; + } + + // convert into unicode + csizeServerName = MultiByteToWideChar( CP_ACP, 0, serverName, -1, uServerName, csizeServerName ); + if( csizeServerName == 0 ) + { + //printf("SEC_E_WRONG_PRINCIPAL\n"); + break; + } + + // create the chain + ZeroMemory( &chainParameter, sizeof( chainParameter ) ); + chainParameter.cbSize = sizeof( chainParameter ); + chainParameter.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; + chainParameter.RequestedUsage.Usage.cUsageIdentifier = cUsages; + chainParameter.RequestedUsage.Usage.rgpszUsageIdentifier = Usages; + + if( !CertGetCertificateChain( NULL, remoteCertContext, NULL, remoteCertContext->hCertStore, + &chainParameter, 0, NULL, &chainContext ) ) + { +// DWORD status = GetLastError(); +// printf("Error 0x%x returned by CertGetCertificateChain!!!\n", status); + break; + } + + // validate the chain + ZeroMemory( &policyHTTPS, sizeof( HTTPSPolicyCallbackData ) ); + policyHTTPS.cbStruct = sizeof( HTTPSPolicyCallbackData ); + policyHTTPS.dwAuthType = AUTHTYPE_SERVER; + policyHTTPS.fdwChecks = 0; + policyHTTPS.pwszServerName = uServerName; + + memset( &policyParameter, 0, sizeof( policyParameter ) ); + policyParameter.cbSize = sizeof( policyParameter ); + policyParameter.pvExtraPolicyPara = &policyHTTPS; + + memset( &policyStatus, 0, sizeof( policyStatus ) ); + policyStatus.cbSize = sizeof( policyStatus ); + + if( !CertVerifyCertificateChainPolicy( CERT_CHAIN_POLICY_SSL, chainContext, &policyParameter, + &policyStatus ) ) + { +// DWORD status = GetLastError(); +// printf("Error 0x%x returned by CertVerifyCertificateChainPolicy!!!\n", status); + break; + } + + if( policyStatus.dwError ) + { + //printf("Trust Error!!!}n"); + break; + } + valid = true; + } + while( false ); + // cleanup + if( chainContext ) CertFreeCertificateChain( chainContext ); + m_certInfo.chain = valid; + } + + void SChannel::connectionInfos() + { + SecPkgContext_ConnectionInfo conn_info; + + memset( &conn_info, 0, sizeof( conn_info ) ); + + if( QueryContextAttributes( &m_context, SECPKG_ATTR_CONNECTION_INFO, &conn_info ) == SEC_E_OK ) + { + switch( conn_info.dwProtocol ) + { + case SP_PROT_TLS1_CLIENT: + m_certInfo.protocol = "TLSv1"; + break; + case SP_PROT_SSL3_CLIENT: + m_certInfo.protocol = "SSLv3"; + break; + default: + m_certInfo.protocol = "unknown"; + } + + switch( conn_info.aiCipher ) + { + case CALG_3DES: + m_certInfo.cipher = "3DES"; + break; + case CALG_AES_128: + m_certInfo.cipher = "AES_128"; + break; + case CALG_AES_256: + m_certInfo.cipher = "AES_256"; + break; + case CALG_DES: + m_certInfo.cipher = "DES"; + break; + case CALG_RC2: + m_certInfo.cipher = "RC2"; + break; + case CALG_RC4: + m_certInfo.cipher = "RC4"; + break; + default: + m_certInfo.cipher = EmptyString; + } + + switch( conn_info.aiHash ) + { + case CALG_MD5: + m_certInfo.mac = "MD5"; + break; + case CALG_SHA: + m_certInfo.mac = "SHA"; + break; + default: + m_certInfo.mac = EmptyString; + } + } + } + + void SChannel::certData() + { + PCCERT_CONTEXT remoteCertContext = NULL; + CHAR certString[1000]; + + // getting server's certificate + if( QueryContextAttributes( &m_context, SECPKG_ATTR_REMOTE_CERT_CONTEXT, + (PVOID)&remoteCertContext ) != SEC_E_OK ) + { + return; + } + + // setting certificat's lifespan + m_certInfo.date_from = filetime2int( remoteCertContext->pCertInfo->NotBefore ); + m_certInfo.date_to = filetime2int( remoteCertContext->pCertInfo->NotAfter ); + + if( !CertNameToStrA( remoteCertContext->dwCertEncodingType, + &remoteCertContext->pCertInfo->Subject, + CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, + certString, sizeof( certString ) ) ) + { + return; + } + m_certInfo.server = certString; + + if( !CertNameToStrA( remoteCertContext->dwCertEncodingType, + &remoteCertContext->pCertInfo->Issuer, + CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, + certString, sizeof( certString ) ) ) + { + return; + } + m_certInfo.issuer = certString; + } + + void SChannel::setCertinfos() + { + validateCert(); + connectionInfos(); + certData(); + } + +#if 0 + void SChannel::print_error( int errorcode, const char* place ) + { + printf( "Win error at %s.\n", place ); + switch( errorcode ) + { + case SEC_E_OK: + printf( "\tValue:\tSEC_E_OK\n" ); + printf( "\tDesc:\tNot really an error. Everything is fine.\n" ); + break; + case SEC_E_INSUFFICIENT_MEMORY: + printf( "\tValue:\tSEC_E_INSUFFICIENT_MEMORY\n" ); + printf( "\tDesc:\tThere is not enough memory available to complete the requested action.\n" ); + break; + case SEC_E_INTERNAL_ERROR: + printf( "\tValue:\tSEC_E_INTERNAL_ERROR\n" ); + printf( "\tDesc:\tAn error occurred that did not map to an SSPI error code.\n" ); + break; + case SEC_E_NO_CREDENTIALS: + printf( "\tValue:\tSEC_E_NO_CREDENTIALS\n" ); + printf( "\tDesc:\tNo credentials are available in the security package.\n" ); + break; + case SEC_E_NOT_OWNER: + printf( "\tValue:\tSEC_E_NOT_OWNER\n" ); + printf( "\tDesc:\tThe caller of the function does not have the necessary credentials.\n" ); + break; + case SEC_E_SECPKG_NOT_FOUND: + printf( "\tValue:\tSEC_E_SECPKG_NOT_FOUND\n" ); + printf( "\tDesc:\tThe requested security package does not exist. \n" ); + break; + case SEC_E_UNKNOWN_CREDENTIALS: + printf( "\tValue:\tSEC_E_UNKNOWN_CREDENTIALS\n" ); + printf( "\tDesc:\tThe credentials supplied to the package were not recognized.\n" ); + break; + case SEC_E_INCOMPLETE_MESSAGE: + printf( "\tValue:\tSEC_E_INCOMPLETE_MESSAGE\n" ); + printf( "\tDesc:\tData for the whole message was not read from the wire.\n" ); + break; + case SEC_E_INVALID_HANDLE: + printf( "\tValue:\tSEC_E_INVALID_HANDLE\n" ); + printf( "\tDesc:\tThe handle passed to the function is invalid.\n" ); + break; + case SEC_E_INVALID_TOKEN: + printf( "\tValue:\tSEC_E_INVALID_TOKEN\n" ); + printf( "\tDesc:\tThe error is due to a malformed input token, such as a token " + "corrupted in transit...\n" ); + break; + case SEC_E_LOGON_DENIED: + printf( "\tValue:\tSEC_E_LOGON_DENIED\n" ); + printf( "\tDesc:\tThe logon failed.\n" ); + break; + case SEC_E_NO_AUTHENTICATING_AUTHORITY: + printf( "\tValue:\tSEC_E_NO_AUTHENTICATING_AUTHORITY\n" ); + printf( "\tDesc:\tNo authority could be contacted for authentication...\n" ); + break; + case SEC_E_TARGET_UNKNOWN: + printf( "\tValue:\tSEC_E_TARGET_UNKNOWN\n" ); + printf( "\tDesc:\tThe target was not recognized.\n" ); + break; + case SEC_E_UNSUPPORTED_FUNCTION: + printf( "\tValue:\tSEC_E_UNSUPPORTED_FUNCTION\n" ); + printf( "\tDesc:\tAn invalid context attribute flag (ISC_REQ_DELEGATE or " + "ISC_REQ_PROMPT_FOR_CREDS)...\n" ); + break; + case SEC_E_WRONG_PRINCIPAL: + printf( "\tValue:\tSEC_E_WRONG_PRINCIPAL\n" ); + printf( "\tDesc:\tThe principal that received the authentication request " + "is not the same as the...\n" ); + break; + case SEC_I_COMPLETE_AND_CONTINUE: + printf( "\tValue:\tSEC_I_COMPLETE_AND_CONTINUE\n" ); + printf( "\tDesc:\tThe client must call CompleteAuthToken and then pass the output...\n" ); + break; + case SEC_I_COMPLETE_NEEDED: + printf( "\tValue:\tSEC_I_COMPLETE_NEEDED\n" ); + printf( "\tDesc:\tThe client must finish building the message and then " + "call the CompleteAuthToken function.\n" ); + break; + case SEC_I_CONTINUE_NEEDED: + printf( "\tValue:\tSEC_I_CONTINUE_NEEDED\n" ); + printf( "\tDesc:\tThe client must send the output token to the server " + "and wait for a return token...\n" ); + break; + case SEC_I_INCOMPLETE_CREDENTIALS: + printf( "\tValue:\tSEC_I_INCOMPLETE_CREDENTIALS\n" ); + printf( "\tDesc:\tThe server has requested client authentication, " + "and the supplied credentials either...\n" ); + break; + default: + printf( "\tValue:\t%d\n", errorcode ); + printf( "\tDesc:\tUnknown error code.\n" ); + } + } +#endif + +} + +#endif // HAVE_WINTLS diff --git a/libs/libgloox/tlsschannel.h b/libs/libgloox/tlsschannel.h new file mode 100644 index 0000000..8286835 --- /dev/null +++ b/libs/libgloox/tlsschannel.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2007-2009 by Jakob Schroeter + * This file is part of the gloox library. http://camaya.net/gloox + * + * This software is distributed under a license. The full license + * agreement can be found in the file LICENSE in this distribution. + * This software may not be copied, modified, sold or distributed + * other than expressed in the named license agreement. + * + * This software is distributed without any warranty. + */ + +#ifndef TLSSCHANNEL_H__ +#define TLSSCHANNEL_H__ + +#include "tlsbase.h" + +#include "config.h" + +#ifdef HAVE_WINTLS + +#include + +#define SECURITY_WIN32 +#include +#include +#include + +namespace gloox +{ + + /** + * This class implements a TLS backend using SChannel. + * + * @author Jakob Schroeter + * @since 0.9 + */ + class SChannel : public TLSBase + { + public: + /** + * Constructor. + * @param th The TLSHandler to handle TLS-related events. + * @param server The server to use in certificate verification. + */ + SChannel( TLSHandler* th, const std::string& server ); + + /** + * Virtual destructor. + */ + virtual ~SChannel(); + + // reimplemented from TLSBase + virtual bool init( const std::string& /*clientKey*/ = EmptyString, + const std::string& /*clientCerts*/ = EmptyString, + const StringList& /*cacerts*/ = StringList() ) + { return true; } + + // reimplemented from TLSBase + virtual bool encrypt( const std::string& data ); + + // reimplemented from TLSBase + virtual int decrypt( const std::string& data ); + + // reimplemented from TLSBase + virtual void cleanup(); + + // reimplemented from TLSBase + virtual bool handshake(); + + // reimplemented from TLSBase + virtual void setCACerts( const StringList& cacerts ); + + // reimplemented from TLSBase + virtual void setClientCert( const std::string& clientKey, const std::string& clientCerts ); + + private: + void handshakeStage( const std::string& data ); + void setSizes(); + + int filetime2int( FILETIME t ); + + void validateCert(); + void connectionInfos(); + void certData(); + void setCertinfos(); + CredHandle m_credHandle; + CtxtHandle m_context; + + SecPkgContext_StreamSizes m_sizes; + + size_t m_header_max; + size_t m_message_max; + size_t m_trailer_max; + + std::string m_buffer; + + bool m_cleanedup; + + // windows error outputs +// void print_error( int errorcode, const char* place = 0 ); + + }; +} + +#endif // HAVE_WINTLS + +#endif // TLSSCHANNEL_H__ diff --git a/libs/libgloox/uniquemucroom.cpp b/libs/libgloox/uniquemucroom.cpp new file mode 100644 index 0000000..1ed1784 --- /dev/null +++ b/libs/libgloox/uniquemucroom.cpp @@ -0,0 +1,109 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#include "uniquemucroom.h" +#include "clientbase.h" +#include "jid.h" +#include "sha.h" + +namespace gloox +{ + + // ---- UniqueMUCRoom::Unique ---- + UniqueMUCRoom::Unique::Unique( const Tag* tag ) + : StanzaExtension( ExtMUCUnique ) + { + if( !tag || tag->name() != "unique" || tag->xmlns() != XMLNS_MUC_UNIQUE ) + return; + + m_name = tag->cdata(); + } + + const std::string& UniqueMUCRoom::Unique::filterString() const + { + static const std::string filter = "/iq/unique[@xmlns='" + XMLNS_MUC_UNIQUE + "']"; + return filter; + } + + Tag* UniqueMUCRoom::Unique::tag() const + { + Tag* t = new Tag( "unique" ); + t->setXmlns( XMLNS_MUC_UNIQUE ); + if( !m_name.empty() ) + t->setCData( m_name ); + return t; + } + // ---- ~UniqueMUCRoom::Unique ---- + + // ---- UniqueMUCRoom ---- + UniqueMUCRoom::UniqueMUCRoom( ClientBase* parent, const JID& nick, MUCRoomHandler* mrh ) + : InstantMUCRoom( parent, nick, mrh ) + { + if( m_parent ) + { + m_parent->registerStanzaExtension( new Unique() ); + } + } + + UniqueMUCRoom::~UniqueMUCRoom() + { + if( m_parent ) + { + m_parent->removeIDHandler( this ); +// m_parent->removeStanzaExtension( ExtMUCUnique ); // don't remove, other rooms might need it + } + } + + void UniqueMUCRoom::join() + { + if( !m_parent || m_joined ) + return; + + IQ iq( IQ::Get, m_nick.server() ); + iq.addExtension( new Unique() ); + m_parent->send( iq, this, RequestUniqueName ); + } + + void UniqueMUCRoom::handleIqID( const IQ& iq, int context ) + { + switch( iq.subtype() ) + { + case IQ::Result: + if( context == RequestUniqueName ) + { + const Unique* u = iq.findExtension( ExtMUCUnique ); + if( u ) + { + if( !u->name().empty() ) + setName( u->name() ); + } + } + break; + case IQ::Error: + if( context == RequestUniqueName ) + { + SHA s; + s.feed( m_parent->jid().full() ); + s.feed( m_parent->getID() ); + setName( s.hex() ); + } + break; + default: + break; + } + + MUCRoom::join(); + } + +} diff --git a/libs/libgloox/uniquemucroom.h b/libs/libgloox/uniquemucroom.h new file mode 100644 index 0000000..bfc39e2 --- /dev/null +++ b/libs/libgloox/uniquemucroom.h @@ -0,0 +1,117 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + + +#ifndef UNIQUEMUCROOM_H__ +#define UNIQUEMUCROOM_H__ + +#include "instantmucroom.h" +#include "stanzaextension.h" + +namespace gloox +{ + + /** + * @brief This class implements a unique MUC room. + * + * A unique MUC room is a room with a non-human-readable name. It is primarily intended + * to be used when converting one-to-one chats to multi-user chats. + * + * XEP version: 1.21 + * @author Jakob Schroeter + * @since 0.9 + */ + class GLOOX_API UniqueMUCRoom : public InstantMUCRoom + { + public: + /** + * Creates a new abstraction of a @b unique Multi-User Chat room. The room is not joined + * automatically. Use join() to join the room, use leave() to leave it. See MUCRoom for + * detailed info. + * @param parent The ClientBase object to use for the communication. + * @param nick The service to create the room on plus the desired nickname in the form + * @b service/nick. + * @param mrh The MUCRoomHandler that will listen to room events. May be 0 and may be specified + * later using registerMUCRoomHandler(). However, without one, MUC is no joy. + * @note To subsequently configure the room, use MUCRoom::registerMUCRoomConfigHandler(). + */ + UniqueMUCRoom( ClientBase* parent, const JID& nick, MUCRoomHandler* mrh ); + + /** + * Virtual Destructor. + */ + virtual ~UniqueMUCRoom(); + + // reimplemented from MUCRoom + virtual void join(); + + private: +#ifdef UNIQUEMUCROOM_TEST + public: +#endif + /** + * @brief A stanza extension wrapping MUC's <unique> element. + * + * @author Jakob Schroeter + * @since 1.0 + */ + class Unique : public StanzaExtension + { + public: + /** + * Creates a new object from the given Tag. + * @param tag The Tag to parse. + */ + Unique( const Tag* tag = 0 ); + + /** + *Virtual Destructor. + */ + virtual ~Unique() {} + + /** + * Returns the unique name created by the server. + * @return The server-created unique room name. + */ + const std::string& name() const { return m_name; } + + // reimplemented from StanzaExtension + virtual const std::string& filterString() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* newInstance( const Tag* tag ) const + { + return new Unique( tag ); + } + + // reimplemented from StanzaExtension + virtual Tag* tag() const; + + // reimplemented from StanzaExtension + virtual StanzaExtension* clone() const + { + return new Unique( *this ); + } + + private: + std::string m_name; + }; + + // reimplemented from MUCRoom (IqHandler) + void handleIqID( const IQ& iq, int context ); + + }; + +} + +#endif // UNIQUEMUCROOM_H__ diff --git a/libs/libgloox/util.cpp b/libs/libgloox/util.cpp new file mode 100644 index 0000000..f50026c --- /dev/null +++ b/libs/libgloox/util.cpp @@ -0,0 +1,121 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#include "util.h" +#include "gloox.h" + +namespace gloox +{ + + namespace util + { + + int internalLog2( unsigned int n ) + { + int pos = 0; + if ( n >= 1<<16 ) { n >>= 16; pos += 16; } + if ( n >= 1<< 8 ) { n >>= 8; pos += 8; } + if ( n >= 1<< 4 ) { n >>= 4; pos += 4; } + if ( n >= 1<< 2 ) { n >>= 2; pos += 2; } + if ( n >= 1<< 1 ) { pos += 1; } + return ( (n == 0) ? (-1) : pos ); + } + + unsigned _lookup( const std::string& str, const char* values[], unsigned size, int def ) + { + unsigned i = 0; + for( ; i < size && str != values[i]; ++i ) + ; + return ( i == size && def >= 0 ) ? (unsigned)def : i; + } + + const std::string _lookup( unsigned code, const char* values[], unsigned size, const std::string& def ) + { + return code < size ? std::string( values[code] ) : def; + } + + unsigned _lookup2( const std::string& str, const char* values[], + unsigned size, int def ) + { + return 1 << _lookup( str, values, size, def <= 0 ? def : (int)internalLog2( def ) ); + } + + const std::string _lookup2( unsigned code, const char* values[], unsigned size, const std::string& def ) + { + const unsigned i = (unsigned)internalLog2( code ); + return i < size ? std::string( values[i] ) : def; + } + + static const char escape_chars[] = { '&', '<', '>', '\'', '"' }; + + static const std::string escape_seqs[] = { "amp;", "lt;", "gt;", "apos;", "quot;" }; + + static const unsigned escape_size = 5; + + const std::string escape( std::string what ) + { + for( size_t val, i = 0; i < what.length(); ++i ) + { + for( val = 0; val < escape_size; ++val ) + { + if( what[i] == escape_chars[val] ) + { + what[i] = '&'; + what.insert( i+1, escape_seqs[val] ); + i += escape_seqs[val].length(); + break; + } + } + } + return what; + } + + bool checkValidXMLChars( const std::string& data ) + { + if( data.empty() ) + return true; + + std::string::const_iterator it = data.begin(); + for( ; it != data.end() + && ( (unsigned char)(*it) == 0x09 + || (unsigned char)(*it) == 0x0a + || (unsigned char)(*it) == 0x0d + || ( (unsigned char)(*it) >= 0x20 + && (unsigned char)(*it) != 0xc0 + && (unsigned char)(*it) != 0xc1 + && (unsigned char)(*it) < 0xf5 ) ); ++it ) + ; + + return ( it == data.end() ); + } + + void replaceAll( std::string& target, const std::string& find, const std::string& replace ) + { + std::string::size_type findSize = find.size(); + std::string::size_type replaceSize = replace.size(); + + if( findSize == 0 ) + return; + + std::string::size_type index = target.find( find, 0 ); + + while( index != std::string::npos ) + { + target.replace( index, findSize, replace ); + index = target.find( find, index+replaceSize ); + } + } + + } + +} + diff --git a/libs/libgloox/util.h b/libs/libgloox/util.h new file mode 100644 index 0000000..63b3feb --- /dev/null +++ b/libs/libgloox/util.h @@ -0,0 +1,269 @@ +/* + Copyright (c) 2007-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + +#ifndef UTIL_H__ +#define UTIL_H__ + +#include "gloox.h" + +#include +#include +#include +#include +#include + +namespace gloox +{ + + /** + * @brief A namespace holding a couple utility functions. + */ + namespace util + { + + #define lookup( a, b ) _lookup( a, b, sizeof(b)/sizeof(char*) ) + #define lookup2( a, b ) _lookup2( a, b, sizeof(b)/sizeof(char*) ) + #define deflookup( a, b, c ) _lookup( a, b, sizeof(b)/sizeof(char*), c ) + #define deflookup2( a, b, c ) _lookup2( a, b, sizeof(b)/sizeof(char*), c ) + + /** + * Finds the enumerated value associated with a string value. + * @param str String to search for. + * @param values Array of String/Code pairs to look into. + * @param size The array's size. + * @param def Default value returned in case the lookup failed. + * @return The associated enum code. + */ + GLOOX_API unsigned _lookup( const std::string& str, const char* values[], + unsigned size, int def = -1 ); + + /** + * Finds the string associated with an enumerated type. + * @param code Code of the string to search for. + * @param values Array of String/Code pairs to look into. + * @param size The array's size. + * @param def Default value returned in case the lookup failed. + * @return The associated string (empty in case there's no match). + */ + GLOOX_API const std::string _lookup( unsigned code, const char* values[], + unsigned size, const std::string& def = EmptyString ); + + /** + * Finds the ORable enumerated value associated with a string value. + * @param str String to search for. + * @param values Array of String/Code pairs to look into. + * @param size The array's size. + * @param def The default value to return if the lookup failed. + * @return The associated enum code. + */ + GLOOX_API unsigned _lookup2( const std::string& str, const char* values[], + unsigned size, int def = -1 ); + + /** + * Finds the string associated with an ORable enumerated type. + * @param code Code of the string to search for. + * @param values Array of String/Code pairs to look into. + * @param size The array's size. + * @param def The default value to return if the lookup failed. + * @return The associated string (empty in case there's no match). + */ + GLOOX_API const std::string _lookup2( unsigned code, const char* values[], + unsigned size, const std::string& def = EmptyString ); + + /** + * A convenience function that executes the given function on each object in a given list. + * @param t The object to execute the function on. + * @param f The function to execute. + */ + template< typename T, typename F > + inline void ForEach( T& t, F f ) + { + for( typename T::iterator it = t.begin(); it != t.end(); ++it ) + ( (*it)->*f )(); + } + + /** + * A convenience function that executes the given function on each object in a given list, + * passing the given argument. + * @param t The object to execute the function on. + * @param f The function to execute. + * @param d An argument to pass to the function. + */ + template< typename T, typename F, typename D > + inline void ForEach( T& t, F f, D& d ) + { + for( typename T::iterator it = t.begin(); it != t.end(); ++it ) + ( (*it)->*f )( d ); + } + + /** + * A convenience function that executes the given function on each object in a given list, + * passing the given arguments. + * @param t The object to execute the function on. + * @param f The function to execute. + * @param d1 An argument to pass to the function. + * @param d2 An argument to pass to the function. + */ + template< typename T, typename F, typename D1, typename D2 > + inline void ForEach( T& t, F f, D1& d1, D2& d2 ) + { + for( typename T::iterator it = t.begin(); it != t.end(); ++it ) + ( (*it)->*f )( d1, d2 ); + } + + /** + * A convenience function that executes the given function on each object in a given list, + * passing the given arguments. + * @param t The object to execute the function on. + * @param f The function to execute. + * @param d1 An argument to pass to the function. + * @param d2 An argument to pass to the function. + * @param d3 An argument to pass to the function. + */ + template< typename T, typename F, typename D1, typename D2, typename D3 > + inline void ForEach( T& t, F f, D1& d1, D2& d2, D3& d3 ) + { + for( typename T::iterator it = t.begin(); it != t.end(); ++it ) + ( (*it)->*f )( d1, d2, d3 ); + } + + /** + * Delete all elements from a list of pointers. + * @param L List of pointers to delete. + */ + template< typename T > + inline void clearList( std::list< T* >& L ) + { + typename std::list< T* >::iterator it = L.begin(); + typename std::list< T* >::iterator it2; + while( it != L.end() ) + { + it2 = it++; + delete (*it2); + L.erase( it2 ); + } + } + + /** + * Delete all associated values from a map (not the key elements). + * @param M Map of pointer values to delete. + */ + template< typename Key, typename T > + inline void clearMap( std::map< Key, T* >& M ) + { + typename std::map< Key, T* >::iterator it = M.begin(); + typename std::map< Key, T* >::iterator it2; + while( it != M.end() ) + { + it2 = it++; + delete (*it2).second; + M.erase( it2 ); + } + } + + /** + * Delete all associated values from a map (not the key elements). + * Const key type version. + * @param M Map of pointer values to delete. + */ + template< typename Key, typename T > + inline void clearMap( std::map< const Key, T* >& M ) + { + typename std::map< const Key, T* >::iterator it = M.begin(); + typename std::map< const Key, T* >::iterator it2; + while( it != M.end() ) + { + it2 = it++; + delete (*it2).second; + M.erase( it2 ); + } + } + + /** + * Does some fancy escaping. (& --> &amp;, etc). + * @param what A string to escape. + * @return The escaped string. + */ + GLOOX_API const std::string escape( std::string what ); + + /** + * Checks whether the given input is valid UTF-8. + * @param data The data to check for validity. + * @return @@b True if the input is valid UTF-8, @b false otherwise. + */ + GLOOX_API bool checkValidXMLChars( const std::string& data ); + + /** + * Custom log2() implementation. + * @param n Figure to take the logarithm from. + * @return The logarithm to the basis of 2. + */ + GLOOX_API int internalLog2( unsigned int n ); + + /** + * Replace all instances of one substring of arbitrary length + * with another substring of arbitrary length. Replacement happens + * in place (so make a copy first if you don't want the original modified). + * @param target The string to process. Changes are made "in place". + * @param find The sub-string to find within the target string + * @param replace The sub-string to substitute for the find string. + * @todo Look into merging with util::escape() and Parser::decode(). + */ + GLOOX_API void replaceAll( std::string& target, const std::string& find, const std::string& replace ); + + /** + * Converts a long int to its string representation. + * @param value The long integer value. + * @param base The integer's base. + * @return The long int's string represenation. + */ + static inline const std::string long2string( long int value, const int base = 10 ) + { + int add = 0; + if( base < 2 || base > 16 || value == 0 ) + return "0"; + else if( value < 0 ) + { + ++add; + value = -value; + } + int len = (int)( log( (double)( value ? value : 1 ) ) / log( (double)base ) ) + 1; + const char digits[] = "0123456789ABCDEF"; + char* num = (char*)calloc( len + 1 + add, sizeof( char ) ); + num[len--] = '\0'; + if( add ) + num[0] = '-'; + while( value && len > -1 ) + { + num[len-- + add] = digits[(int)( value % base )]; + value /= base; + } + const std::string result( num ); + free( num ); + return result; + } + + /** + * Converts an int to its string representation. + * @param value The integer value. + * @return The int's string represenation. + */ + static inline const std::string int2string( int value ) + { + return long2string( value ); + } + + } + +} + +#endif // UTIL_H__ diff --git a/libs/libgloox/vcard.cpp b/libs/libgloox/vcard.cpp new file mode 100644 index 0000000..740dcbe --- /dev/null +++ b/libs/libgloox/vcard.cpp @@ -0,0 +1,555 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#include "vcard.h" +#include "tag.h" +#include "base64.h" + +namespace gloox +{ + + void VCard::insertField( Tag* vcard, const char* field, const std::string& var ) + { + if( field && !var.empty() ) + new Tag( vcard, field, var ); + } + + void VCard::insertField( Tag* vcard, const char* field, bool var ) + { + if( field && var ) + new Tag( vcard, field ); + } + + void VCard::checkField( const Tag* vcard, const char* field, std::string& var ) + { + if( field ) + { + Tag* child = vcard->findChild( field ); + if( child ) + var = child->cdata(); + } + } + + VCard::VCard() + : StanzaExtension( ExtVCard ), m_class( ClassNone ), m_prodid( "gloox" + GLOOX_VERSION ), + m_N( false ), m_PHOTO( false ), m_LOGO( false ) + { + m_valid = true; + } + + VCard::VCard( const Tag* vcard ) + : StanzaExtension( ExtVCard ), m_class( ClassNone ), m_prodid( "gloox" + GLOOX_VERSION ), + m_N( false ), m_PHOTO( false ), m_LOGO( false ) + { + if( !vcard || vcard->name() != "vCard" || vcard->xmlns() != XMLNS_VCARD_TEMP ) + return; + + m_valid = true; + + checkField( vcard, "FN", m_formattedname ); + checkField( vcard, "NICKNAME", m_nickname ); + checkField( vcard, "URL", m_url ); + checkField( vcard, "BDAY", m_bday ); + checkField( vcard, "JABBERID", m_jabberid ); + checkField( vcard, "TITLE", m_title ); + checkField( vcard, "ROLE", m_role ); + checkField( vcard, "NOTE", m_note ); + checkField( vcard, "DESC", m_desc ); + checkField( vcard, "MAILER", m_mailer ); + checkField( vcard, "TZ", m_tz ); + checkField( vcard, "PRODID", m_prodid ); + checkField( vcard, "REV", m_rev ); + checkField( vcard, "SORT-STRING", m_sortstring ); + checkField( vcard, "UID", m_uid ); + + TagList::const_iterator it = vcard->children().begin(); + for( ; it != vcard->children().end(); ++it ) + { + const Tag& tag = *(*it); + if( tag.name() == "N" ) + { + m_N = true; + const Tag * child = tag.findChild( "FAMILY" ); + if( child ) + m_name.family = child->cdata(); + if( ( child = tag.findChild( "GIVEN" ) ) ) + m_name.given = child->cdata(); + if( ( child = tag.findChild( "MIDDLE" ) ) ) + m_name.middle = child->cdata(); + if( ( child = tag.findChild( "PREFIX" ) ) ) + m_name.prefix = child->cdata(); + if( ( child = tag.findChild( "SUFFIX" ) ) ) + m_name.suffix = child->cdata(); + } + else if( tag.name() == "PHOTO" ) + { + if( tag.hasChild( "EXTVAL" ) ) + { + m_photo.extval = tag.findChild( "EXTVAL" )->cdata(); + m_PHOTO = true; + } + else if( tag.hasChild( "TYPE" ) && tag.hasChild( "BINVAL" ) ) + { + std::string binval = tag.findChild( "BINVAL" )->cdata(); + std::string::size_type pos = 0; + while( ( pos = binval.find( '\n' ) ) != std::string::npos ) + binval.erase( pos, 1 ); + m_photo.type = tag.findChild( "TYPE" )->cdata(); + m_photo.binval = Base64::decode64( binval ); + m_PHOTO = true; + } + } + else if( tag.name() == "LOGO" ) + { + if( tag.hasChild( "EXTVAL" ) ) + { + m_logo.extval = tag.findChild( "EXTVAL" )->cdata(); + m_LOGO = true; + } + else if( tag.hasChild( "TYPE" ) && tag.hasChild( "BINVAL" ) ) + { + std::string binval = tag.findChild( "BINVAL" )->cdata(); + std::string::size_type pos = 0; + while( ( pos = binval.find( '\n' ) ) != std::string::npos ) + binval.erase( pos, 1 ); + m_logo.type = tag.findChild( "TYPE" )->cdata(); + m_logo.binval = Base64::decode64( binval ); + m_LOGO = true; + } + } + else if( tag.name() == "EMAIL" && tag.hasChild( "USERID" ) ) + { + Email item; + item.userid = tag.findChild( "USERID" )->cdata(); + item.internet = tag.hasChild( "INTERNET" ); + item.x400 = tag.hasChild( "X400" ); + item.work = tag.hasChild( "WORK" ); + item.home = tag.hasChild( "HOME" ); + item.pref = tag.hasChild( "PREF" ); + m_emailList.push_back( item ); + } + else if( tag.name() == "ADR" ) + { + Address item; + checkField( &tag, "POBOX", item.pobox ); + checkField( &tag, "EXTADD", item.extadd ); + checkField( &tag, "STREET", item.street ); + checkField( &tag, "LOCALITY", item.locality ); + checkField( &tag, "REGION", item.region ); + checkField( &tag, "PCODE", item.pcode ); + checkField( &tag, "CTRY", item.ctry ); + item.postal = tag.hasChild( "POSTAL" ); + item.parcel = tag.hasChild( "PARCEL" ); + item.work = tag.hasChild( "WORK" ); + item.home = tag.hasChild( "HOME" ); + item.pref = tag.hasChild( "PREF" ); + item.dom = tag.hasChild( "DOM" ); + item.intl = !item.dom && tag.hasChild( "INTL" ); + m_addressList.push_back( item ); + } + else if( tag.name() == "LABEL" ) + { + Label item; + TagList::const_iterator it2 = tag.children().begin(); + for( ; it2 != tag.children().end(); ++it2 ) + { + if( (*it2)->name() == "LINE" ) + item.lines.push_back( (*it)->cdata() ); + item.postal = (*it2)->name() == "POSTAL"; + item.parcel = (*it2)->name() == "PARCEL"; + item.work = (*it2)->name() == "WORK"; + item.home = (*it2)->name() == "HOME"; + item.pref = (*it2)->name() == "PREF"; + item.dom = (*it2)->name() == "DOM"; + item.intl = !item.dom && (*it2)->name() == "INTL"; + } + m_labelList.push_back( item ); + } + else if( tag.name() == "TEL" && tag.hasChild( "NUMBER" ) ) + { + Telephone item; + item.number = tag.findChild( "NUMBER" )->cdata(); + item.work = tag.hasChild( "WORK" ); + item.home = tag.hasChild( "HOME" ); + item.voice = tag.hasChild( "VOICE" ); + item.fax = tag.hasChild( "FAX" ); + item.pager = tag.hasChild( "PAGER" ); + item.msg = tag.hasChild( "MSG" ); + item.cell = tag.hasChild( "CELL" ); + item.video = tag.hasChild( "VIDEO" ); + item.bbs = tag.hasChild( "BBS" ); + item.modem = tag.hasChild( "MODEM" ); + item.isdn = tag.hasChild( "ISDN" ); + item.pcs = tag.hasChild( "PCS" ); + item.pref = tag.hasChild( "PREF" ); + m_telephoneList.push_back( item ); + } + else if( tag.name() == "ORG" ) + { + TagList::const_iterator ito = tag.children().begin(); + for( ; ito != tag.children().end(); ++ito ) + { + if( (*ito)->name() == "ORGNAME" ) + m_org.name = (*ito)->cdata(); + else if( (*ito)->name() == "ORGUNIT" ) + m_org.units.push_back( (*ito)->cdata() ); + } + } + else if( tag.name() == "GEO" ) + { + checkField( &tag, "LON", m_geo.longitude ); + checkField( &tag, "LAT", m_geo.latitude ); + } + else if( tag.name() == "CLASS" ) + { + if( tag.hasChild( "PRIVATE" ) ) + m_class = ClassPrivate; + else if( tag.hasChild( "PUBLIC" ) ) + m_class = ClassPublic; + else if( tag.hasChild( "CONFIDENTIAL" ) ) + m_class = ClassConfidential; + } + + } + + } + + void VCard::setName( const std::string& family, const std::string& given, + const std::string& middle, const std::string& prefix, + const std::string& suffix ) + { + m_name.family = family; + m_name.given = given; + m_name.middle = middle; + m_name.prefix = prefix; + m_name.suffix = suffix; + m_N = true; + } + + void VCard::setPhoto( const std::string& extval ) + { + if( !extval.empty() ) + { + m_photo.extval= extval; + m_PHOTO = true; + } + } + + void VCard::setPhoto( const std::string& type, const std::string& binval ) + { + if( !type.empty() && !binval.empty() ) + { + m_photo.type = type; + m_photo.binval = binval; + m_PHOTO = true; + } + else + { + m_photo.type = EmptyString; + m_photo.binval = EmptyString; + m_photo.extval = EmptyString; + m_PHOTO = false; + } + } + + void VCard::setLogo( const std::string& extval ) + { + if( !extval.empty() ) + { + m_logo.extval = extval; + m_LOGO = true; + } + } + + void VCard::setLogo( const std::string& type, const std::string& binval ) + { + if( !type.empty() && !binval.empty() ) + { + m_logo.type = type; + m_logo.binval = binval; + m_LOGO = true; + } + else + { + m_logo.type = EmptyString; + m_logo.binval = EmptyString; + m_logo.extval = EmptyString; + m_LOGO = false; + } + } + + void VCard::addEmail( const std::string& userid, int type ) + { + if( userid.empty() ) + return; + + Email item; + item.userid = userid; + item.internet = ((type & AddrTypeInet) == AddrTypeInet); + item.x400 = ((type & AddrTypeX400) == AddrTypeX400); + item.work = ((type & AddrTypeWork) == AddrTypeWork); + item.home = ((type & AddrTypeHome) == AddrTypeHome); + item.pref = ((type & AddrTypePref) == AddrTypePref); + + m_emailList.push_back( item ); + } + + void VCard::addAddress( const std::string& pobox, const std::string& extadd, + const std::string& street, const std::string& locality, + const std::string& region, const std::string& pcode, + const std::string& ctry, int type ) + { + if( pobox.empty() && extadd.empty() && street.empty() && + locality.empty() && region.empty() && pcode.empty() && ctry.empty() ) + return; + + Address item; + item.pobox = pobox; + item.extadd = extadd; + item.street = street; + item.locality = locality; + item.region = region; + item.pcode = pcode; + item.ctry = ctry; + item.home = ((type & AddrTypeHome) == AddrTypeHome); + item.work = ((type & AddrTypeWork) == AddrTypeWork); + item.parcel = ((type & AddrTypeParcel) == AddrTypeParcel); + item.postal = ((type & AddrTypePostal) == AddrTypePostal); + item.dom = ((type & AddrTypeDom) == AddrTypeDom); + item.intl = !item.dom && ((type & AddrTypeIntl) == AddrTypeIntl); + item.pref = ((type & AddrTypePref) == AddrTypePref); + + m_addressList.push_back( item ); + } + + void VCard::addLabel( const StringList& lines, int type ) + { + if( lines.empty() ) + return; + + Label item; + item.lines = lines; + item.work = ((type & AddrTypeWork) == AddrTypeWork); + item.home = ((type & AddrTypeHome) == AddrTypeHome); + item.postal = ((type & AddrTypePostal) == AddrTypePostal); + item.parcel = ((type & AddrTypeParcel) == AddrTypeParcel); + item.pref = ((type & AddrTypePref) == AddrTypePref); + item.dom = ((type & AddrTypeDom) == AddrTypeDom); + item.intl = !item.dom && ((type & AddrTypeIntl) == AddrTypeIntl); + + m_labelList.push_back( item ); + } + + void VCard::addTelephone( const std::string& number, int type ) + { + if( number.empty() ) + return; + + Telephone item; + item.number = number; + item.work = ((type & AddrTypeWork) == AddrTypeWork); + item.home = ((type & AddrTypeHome) == AddrTypeHome); + item.voice = ((type & AddrTypeVoice) == AddrTypeVoice); + item.fax = ((type & AddrTypeFax) == AddrTypeFax); + item.pager = ((type & AddrTypePager) == AddrTypePager); + item.msg = ((type & AddrTypeMsg) == AddrTypeMsg); + item.cell = ((type & AddrTypeCell) == AddrTypeCell); + item.video = ((type & AddrTypeVideo) == AddrTypeVideo); + item.bbs = ((type & AddrTypeBbs) == AddrTypeBbs); + item.modem = ((type & AddrTypeModem) == AddrTypeModem); + item.isdn = ((type & AddrTypeIsdn) == AddrTypeIsdn); + item.pcs = ((type & AddrTypePcs) == AddrTypePcs); + item.pref = ((type & AddrTypePref) == AddrTypePref); + + m_telephoneList.push_back( item ); + } + + void VCard::setGeo( const std::string& lat, const std::string& lon ) + { + if( !lat.empty() && !lon.empty() ) + { + m_geo.latitude = lat; + m_geo.longitude = lon; + } + } + + void VCard::setOrganization( const std::string& orgname, const StringList& orgunits ) + { + if( !orgname.empty() ) + { + m_org.name = orgname; + m_org.units = orgunits; + } + } + + const std::string& VCard::filterString() const + { + static const std::string filter = "/iq/vCard[@xmlns='" + XMLNS_VCARD_TEMP + "']"; + return filter; + } + + Tag* VCard::tag() const + { + Tag* v = new Tag( "vCard" ); + v->setXmlns( XMLNS_VCARD_TEMP ); + + if( !m_valid ) + return v; + + v->addAttribute( "version", "3.0" ); + + insertField( v, "FN", m_formattedname ); + insertField( v, "NICKNAME", m_nickname ); + insertField( v, "URL", m_url ); + insertField( v, "BDAY", m_bday ); + insertField( v, "JABBERID", m_jabberid ); + insertField( v, "TITLE", m_title ); + insertField( v, "ROLE", m_role ); + insertField( v, "NOTE", m_note ); + insertField( v, "DESC", m_desc ); + insertField( v, "MAILER", m_mailer ); + insertField( v, "TZ", m_tz ); + insertField( v, "REV", m_rev ); + insertField( v, "SORT_STRING", m_sortstring ); + insertField( v, "UID", m_uid ); + + if( m_N ) + { + Tag* n = new Tag( v, "N" ); + insertField( n, "FAMILY", m_name.family ); + insertField( n, "GIVEN", m_name.given ); + insertField( n, "MIDDLE", m_name.middle ); + insertField( n, "PREFIX", m_name.prefix ); + insertField( n, "SUFFIX", m_name.suffix ); + } + + if( m_PHOTO ) + { + Tag* p = new Tag( v, "PHOTO" ); + if( !m_photo.extval.empty() ) + { + new Tag( p, "EXTVAL", m_photo.extval ); + } + else if( !m_photo.type.empty() && !m_photo.binval.empty() ) + { + new Tag( p, "TYPE", m_photo.type ); + new Tag( p, "BINVAL", Base64::encode64( m_photo.binval ) ); + } + } + + if( m_LOGO ) + { + Tag* l = new Tag( v, "LOGO" ); + if( !m_logo.extval.empty() ) + { + new Tag( l, "EXTVAL", m_logo.extval ); + } + else if( !m_logo.type.empty() && !m_logo.binval.empty() ) + { + new Tag( l, "TYPE", m_logo.type ); + new Tag( l, "BINVAL", Base64::encode64( m_logo.binval ) ); + } + } + + EmailList::const_iterator ite = m_emailList.begin(); + for( ; ite != m_emailList.end(); ++ite ) + { + Tag* e = new Tag( v, "EMAIL" ); + insertField( e, "INTERNET", (*ite).internet ); + insertField( e, "WORK", (*ite).work ); + insertField( e, "HOME", (*ite).home ); + insertField( e, "X400", (*ite).x400 ); + insertField( e, "PREF", (*ite).pref ); + insertField( e, "USERID", (*ite).userid ); + } + + AddressList::const_iterator ita = m_addressList.begin(); + for( ; ita != m_addressList.end(); ++ita ) + { + Tag* a = new Tag( v, "ADR" ); + insertField( a, "POSTAL", (*ita).postal ); + insertField( a, "PARCEL", (*ita).parcel ); + insertField( a, "HOME", (*ita).home ); + insertField( a, "WORK", (*ita).work ); + insertField( a, "PREF", (*ita).pref ); + insertField( a, "DOM", (*ita).dom ); + if( !(*ita).dom ) + insertField( a, "INTL", (*ita).intl ); + + insertField( a, "POBOX", (*ita).pobox ); + insertField( a, "EXTADD", (*ita).extadd ); + insertField( a, "STREET", (*ita).street ); + insertField( a, "LOCALITY", (*ita).locality ); + insertField( a, "REGION", (*ita).region ); + insertField( a, "PCODE", (*ita).pcode ); + insertField( a, "CTRY", (*ita).ctry ); + } + + TelephoneList::const_iterator itt = m_telephoneList.begin(); + for( ; itt != m_telephoneList.end(); ++itt ) + { + Tag* t = new Tag( v, "TEL" ); + insertField( t, "NUMBER", (*itt).number ); + insertField( t, "HOME", (*itt).home ); + insertField( t, "WORK", (*itt).work ); + insertField( t, "VOICE", (*itt).voice ); + insertField( t, "FAX", (*itt).fax ); + insertField( t, "PAGER", (*itt).pager ); + insertField( t, "MSG", (*itt).msg ); + insertField( t, "CELL", (*itt).cell ); + insertField( t, "VIDEO", (*itt).video ); + insertField( t, "BBS", (*itt).bbs ); + insertField( t, "MODEM", (*itt).modem ); + insertField( t, "ISDN", (*itt).isdn ); + insertField( t, "PCS", (*itt).pcs ); + insertField( t, "PREF", (*itt).pref ); + } + + if( !m_geo.latitude.empty() && !m_geo.longitude.empty() ) + { + Tag* g = new Tag( v, "GEO" ); + new Tag( g, "LAT", m_geo.latitude ); + new Tag( g, "LON", m_geo.longitude ); + } + + if( !m_org.name.empty() ) + { + Tag* o = new Tag( v, "ORG" ); + new Tag( o, "ORGNAME", m_org.name ); + StringList::const_iterator ito = m_org.units.begin(); + for( ; ito != m_org.units.end(); ++ito ) + new Tag( o, "ORGUNIT", (*ito) ); + } + + if( m_class != ClassNone ) + { + Tag* c = new Tag( v, "CLASS" ); + switch( m_class ) + { + case ClassPublic: + new Tag( c, "PUBLIC" ); + break; + case ClassPrivate: + new Tag( c, "PRIVATE" ); + break; + case ClassConfidential: + new Tag( c, "CONFIDENTIAL" ); + break; + default: + break; + } + } + + return v; + } +} diff --git a/libs/libgloox/vcard.h b/libs/libgloox/vcard.h new file mode 100644 index 0000000..3d28f19 --- /dev/null +++ b/libs/libgloox/vcard.h @@ -0,0 +1,627 @@ +/* + Copyright (c) 2006-2009 by Jakob Schroeter + This file is part of the gloox library. http://camaya.net/gloox + + This software is distributed under a license. The full license + agreement can be found in the file LICENSE in this distribution. + This software may not be copied, modified, sold or distributed + other than expressed in the named license agreement. + + This software is distributed without any warranty. +*/ + + +#ifndef VCARD_H__ +#define VCARD_H__ + +#include "gloox.h" +#include "stanzaextension.h" + +namespace gloox +{ + + class Tag; + + /** + * @brief A VCard abstraction. + * + * See @link gloox::VCardManager VCardManager @endlink for info on how to + * fetch VCards. + * + * @author Jakob Schroeter + * @since 0.8 + */ + class GLOOX_API VCard : public StanzaExtension + { + public: + /** + * Addressing type indicators. + * @note @c AddrTypeDom and @c AddrTypeIntl are mutually exclusive. If both are present, + * @c AddrTypeDom takes precendence. + * @note Also note that not all adress types are applicable everywhere. For example, + * @c AddrTypeIsdn does not make sense for a postal address. Check XEP-0054 + * for details. + */ + enum AddressType + { + AddrTypeHome = 1, /**< Home address. */ + AddrTypeWork = 2, /**< Work address. */ + AddrTypePref = 4, /**< Preferred address. */ + AddrTypeX400 = 8, /**< X.400 address. */ + AddrTypeInet = 16, /**< Internet address. */ + AddrTypeParcel = 32, /**< Parcel address. */ + AddrTypePostal = 64, /**< Postal address. */ + AddrTypeDom = 128, /**< Domestic(?) address. */ + AddrTypeIntl = 256, /**< International(?) address. */ + AddrTypeVoice = 512, /**< Voice number. */ + AddrTypeFax = 1024, /**< Fax number. */ + AddrTypePager = 2048, /**< Pager. */ + AddrTypeMsg = 4096, /**< MSG(?) */ + AddrTypeCell = 8192, /**< Cell phone number. */ + AddrTypeVideo = 16384, /**< Video chat(?). */ + AddrTypeBbs = 32768, /**< BBS. */ + AddrTypeModem = 65536, /**< Modem. */ + AddrTypeIsdn = 131072, /**< ISDN. */ + AddrTypePcs = 262144 /**< PCS. */ + }; + + /** + * A person's full name. + */ + struct Name + { + std::string family; /**< Family name. */ + std::string given; /**< Given name. */ + std::string middle; /**< Middle name. */ + std::string prefix; /**< Name prefix. */ + std::string suffix; /**< Name suffix. */ + }; + + /** + * Classifies the VCard. + */ + enum VCardClassification + { + ClassNone = 0, /**< Not classified. */ + ClassPublic = 1, /**< Public. */ + ClassPrivate = 2, /**< Private. */ + ClassConfidential = 4 /**< Confidential. */ + }; + + /** + * Describes an email field. + */ + struct Email + { + std::string userid; /**< Email address. */ + bool home; /**< Whether this is a personal address. */ + bool work; /**< Whether this is a work address. */ + bool internet; /**< Whether this is an internet address(?). */ + bool pref; /**< Whether this is the preferred address. */ + bool x400; /**< Whether this is an X.400 address. */ + }; + + /** + * A list of email fields. + */ + typedef std::list EmailList; + + /** + * Describes a telephone number entry. + */ + struct Telephone + { + std::string number; /**< The phone number. */ + bool home; /**< Whether this is a personal number. */ + bool work; /**< Whether this is a work number. */ + bool voice; /**< Whether this is a voice number. */ + bool fax; /**< Whether this is a fax number. */ + bool pager; /**< Whether this is a pager. */ + bool msg; /**< MSG(?) */ + bool cell; /**< Whether this is a cell phone. */ + bool video; /**< Whether this is a video chat(?). */ + bool bbs; /**< Whether this is a BBS. */ + bool modem; /**< Whether this is a modem. */ + bool isdn; /**< Whether this is a ISDN line(?) */ + bool pcs; /**< PCS(?) */ + bool pref; /**< Whether this is the preferred number. */ + }; + + /** + * A list of telephone entries. + */ + typedef std::list TelephoneList; + + /** + * Describes an address entry. + */ + struct Address + { + std::string pobox; /**< Pobox. */ + std::string extadd; /**< Extended address. */ + std::string street; /**< Street. */ + std::string locality; /**< Locality. */ + std::string region; /**< Region. */ + std::string pcode; /**< Postal code. */ + std::string ctry; /**< Country. */ + bool home; /**< Whether this is a personal address. */ + bool work; /**< Whether this is a work address. */ + bool postal; /**< Whether this is a postal address(?). */ + bool parcel; /**< Whether this is a arcel address(?). */ + bool pref; /**< Whether this is the preferred address. */ + bool dom; /**< Whether this is a domestic(?) address. */ + bool intl; /**< Whether this is an international(?) address. */ + }; + + /** + * Describes an address label. + */ + struct Label + { + StringList lines; /**< A list of lines. */ + bool home; /**< Whether this is a personal address. */ + bool work; /**< Whether this is a work address. */ + bool postal; /**< Whether this is a postal address(?). */ + bool parcel; /**< Whether this is a arcel address(?). */ + bool pref; /**< Whether this is the preferred address. */ + bool dom; /**< Whether this is a domestic(?) address. */ + bool intl; /**< Whether this is an international(?) address. */ + }; + + /** + * Describes geo information. + */ + struct Geo + { + std::string latitude; /**< Longitude. */ + std::string longitude; /**< Latitude. */ + }; + + /** + * Describes organization information. + */ + struct Org + { + std::string name; /**< The organizations name. */ + StringList units; /**< A list of units in the organization + * (the VCard's owner belongs to?). */ + }; + + /** + * Describes photo/logo information. + */ + struct Photo + { + std::string extval; /**< The photo is not stored inside the VCard. This is a hint (URL?) + * where to look for it. */ + std::string binval; /**< This is the photo (binary). */ + std::string type; /**< This is a hint at the mime-type. May be forged! */ + }; + + /** + * A list of address entries. + */ + typedef std::list
AddressList; + + /** + * A list of address labels. + */ + typedef std::list