diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | bcb704366cb5e333a626c18c308c7e0448a8e69f (patch) | |
tree | f0d6ab7d78ecdd9207cf46536376b44b91a1ca71 /kopete/protocols/jabber/libiris/iris | |
download | tdenetwork-bcb704366cb5e333a626c18c308c7e0448a8e69f.tar.gz tdenetwork-bcb704366cb5e333a626c18c308c7e0448a8e69f.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdenetwork@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kopete/protocols/jabber/libiris/iris')
44 files changed, 23029 insertions, 0 deletions
diff --git a/kopete/protocols/jabber/libiris/iris/Makefile.am b/kopete/protocols/jabber/libiris/iris/Makefile.am new file mode 100644 index 00000000..03e5818f --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = include jabber xmpp-core xmpp-im diff --git a/kopete/protocols/jabber/libiris/iris/TODO b/kopete/protocols/jabber/libiris/iris/TODO new file mode 100644 index 00000000..e6cf74c6 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/TODO @@ -0,0 +1,16 @@ +- Stream::id(), Stream::lang() +- whitespace pings (but disable when using http poll) +- make stanza error conditions work for both 1.0 and old + +- xmpp-im (messages, roster, subscriptions, presence, privacy) +- document xmpp-core +- provide complete support for xmpp-core. this means all functionality from + the draft, and noting behavior issues (like IQ semantics) in the + library documentation. + +- SASL "EXTERNAL" w/ client certificate +- SASL "ANONYMOUS" ? + +credits: + MD5 algorithm by Peter Deutsch (Aladdin Enterprises) + diff --git a/kopete/protocols/jabber/libiris/iris/include/Makefile.am b/kopete/protocols/jabber/libiris/iris/include/Makefile.am new file mode 100644 index 00000000..6375392b --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/include/Makefile.am @@ -0,0 +1,7 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libiris.la +INCLUDES = -Ixmpp-core -Ixmpp-im -I../cutestuff/util -I../cutestuff/network -I../qca/src $(all_includes) + +libiris_la_SOURCES = \ + empty.cpp diff --git a/kopete/protocols/jabber/libiris/iris/include/empty.cpp b/kopete/protocols/jabber/libiris/iris/include/empty.cpp new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/include/empty.cpp diff --git a/kopete/protocols/jabber/libiris/iris/include/im.h b/kopete/protocols/jabber/libiris/iris/include/im.h new file mode 100644 index 00000000..832ec62a --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/include/im.h @@ -0,0 +1,721 @@ +/* + * im.h - XMPP "IM" library API + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMPP_IM_H +#define XMPP_IM_H + +#include<qdatetime.h> +#include<qvaluelist.h> +#include"xmpp.h" + +namespace XMPP +{ + class Url + { + public: + Url(const QString &url="", const QString &desc=""); + Url(const Url &); + Url & operator=(const Url &); + ~Url(); + + QString url() const; + QString desc() const; + + void setUrl(const QString &); + void setDesc(const QString &); + + private: + class Private; + Private *d; + }; + + typedef QValueList<Url> UrlList; + typedef QMap<QString, QString> StringMap; + typedef enum { OfflineEvent, DeliveredEvent, DisplayedEvent, + ComposingEvent, CancelEvent, InactiveEvent, GoneEvent } MsgEvent; + + class Message + { + public: + Message(const Jid &to=""); + Message(const Message &from); + Message & operator=(const Message &from); + ~Message(); + + Jid to() const; + Jid from() const; + QString id() const; + QString type() const; + QString lang() const; + QString subject(const QString &lang="") const; + QString body(const QString &lang="") const; + QString xHTMLBody(const QString &lang="") const; + QString thread() const; + Stanza::Error error() const; + + void setTo(const Jid &j); + void setFrom(const Jid &j); + void setId(const QString &s); + void setType(const QString &s); + void setLang(const QString &s); + void setSubject(const QString &s, const QString &lang=""); + void setBody(const QString &s, const QString &lang=""); + void setXHTMLBody(const QString &s, const QString &lang="", const QString &attr = ""); + void setThread(const QString &s); + void setError(const Stanza::Error &err); + + // JEP-0091 + QDateTime timeStamp() const; + void setTimeStamp(const QDateTime &ts); + + // JEP-0066 + UrlList urlList() const; + void urlAdd(const Url &u); + void urlsClear(); + void setUrlList(const UrlList &list); + + // JEP-0022 + QString eventId() const; + void setEventId(const QString& id); + bool containsEvents() const; + bool containsEvent(MsgEvent e) const; + void addEvent(MsgEvent e); + + // JEP-0027 + QString xencrypted() const; + void setXEncrypted(const QString &s); + + // Obsolete invitation + QString invite() const; + void setInvite(const QString &s); + + // for compatibility. delete me later + bool spooled() const; + void setSpooled(bool); + bool wasEncrypted() const; + void setWasEncrypted(bool); + + Stanza toStanza(Stream *stream) const; + bool fromStanza(const Stanza &s, int tzoffset); + + private: + class Private; + Private *d; + }; + + class Subscription + { + public: + enum SubType { None, To, From, Both, Remove }; + + Subscription(SubType type=None); + + int type() const; + + QString toString() const; + bool fromString(const QString &); + + private: + SubType value; + }; + + class Status + { + public: + Status(const QString &show="", const QString &status="", int priority=0, bool available=true); + ~Status(); + + int priority() const; + const QString & show() const; + const QString & status() const; + QDateTime timeStamp() const; + const QString & keyID() const; + bool isAvailable() const; + bool isAway() const; + bool isInvisible() const; + bool hasError() const; + int errorCode() const; + const QString & errorString() const; + + const QString & xsigned() const; + const QString & songTitle() const; + const QString & capsNode() const; + const QString & capsVersion() const; + const QString & capsExt() const; + + void setPriority(int); + void setShow(const QString &); + void setStatus(const QString &); + void setTimeStamp(const QDateTime &); + void setKeyID(const QString &); + void setIsAvailable(bool); + void setIsInvisible(bool); + void setError(int, const QString &); + void setCapsNode(const QString&); + void setCapsVersion(const QString&); + void setCapsExt(const QString&); + + void setXSigned(const QString &); + void setSongTitle(const QString &); + + private: + int v_priority; + QString v_show, v_status, v_key; + QDateTime v_timeStamp; + bool v_isAvailable; + bool v_isInvisible; + + QString v_xsigned; + // gabber song extension + QString v_songTitle; + QString v_capsNode, v_capsVersion, v_capsExt; + + int ecode; + QString estr; + + class Private; + Private *d; + }; + + class Resource + { + public: + Resource(const QString &name="", const Status &s=Status()); + ~Resource(); + + const QString & name() const; + int priority() const; + const Status & status() const; + + void setName(const QString &); + void setStatus(const Status &); + + private: + QString v_name; + Status v_status; + + class ResourcePrivate *d; + }; + + class ResourceList : public QValueList<Resource> + { + public: + ResourceList(); + ~ResourceList(); + + ResourceList::Iterator find(const QString &); + ResourceList::Iterator priority(); + + ResourceList::ConstIterator find(const QString &) const; + ResourceList::ConstIterator priority() const; + + private: + class ResourceListPrivate *d; + }; + + class RosterItem + { + public: + RosterItem(const Jid &jid=""); + virtual ~RosterItem(); + + const Jid & jid() const; + const QString & name() const; + const QStringList & groups() const; + const Subscription & subscription() const; + const QString & ask() const; + bool isPush() const; + bool inGroup(const QString &) const; + + virtual void setJid(const Jid &); + void setName(const QString &); + void setGroups(const QStringList &); + void setSubscription(const Subscription &); + void setAsk(const QString &); + void setIsPush(bool); + bool addGroup(const QString &); + bool removeGroup(const QString &); + + QDomElement toXml(QDomDocument *) const; + bool fromXml(const QDomElement &); + + private: + Jid v_jid; + QString v_name; + QStringList v_groups; + Subscription v_subscription; + QString v_ask; + bool v_push; + + class RosterItemPrivate *d; + }; + + class Roster : public QValueList<RosterItem> + { + public: + Roster(); + ~Roster(); + + Roster::Iterator find(const Jid &); + Roster::ConstIterator find(const Jid &) const; + + private: + class RosterPrivate *d; + }; + + class Features + { + public: + Features(); + Features(const QStringList &); + Features(const QString &); + ~Features(); + + QStringList list() const; // actual featurelist + void setList(const QStringList &); + + // features + bool canRegister() const; + bool canSearch() const; + bool canGroupchat() const; + bool canVoice() const; + bool canDisco() const; + bool canXHTML() const; + bool isGateway() const; + bool haveVCard() const; + + enum FeatureID { + FID_Invalid = -1, + FID_None, + FID_Register, + FID_Search, + FID_Groupchat, + FID_Disco, + FID_Gateway, + FID_VCard, + FID_Xhtml, + + // private Psi actions + FID_Add + }; + + // useful functions + bool test(const QStringList &) const; + + QString name() const; + static QString name(long id); + static QString name(const QString &feature); + + long id() const; + static long id(const QString &feature); + static QString feature(long id); + + class FeatureName; + private: + QStringList _list; + }; + + class AgentItem + { + public: + AgentItem() { } + + const Jid & jid() const { return v_jid; } + const QString & name() const { return v_name; } + const QString & category() const { return v_category; } + const QString & type() const { return v_type; } + const Features & features() const { return v_features; } + + void setJid(const Jid &j) { v_jid = j; } + void setName(const QString &n) { v_name = n; } + void setCategory(const QString &c) { v_category = c; } + void setType(const QString &t) { v_type = t; } + void setFeatures(const Features &f) { v_features = f; } + + private: + Jid v_jid; + QString v_name, v_category, v_type; + Features v_features; + }; + + typedef QValueList<AgentItem> AgentList; + + class DiscoItem + { + public: + DiscoItem(); + ~DiscoItem(); + + const Jid &jid() const; + const QString &node() const; + const QString &name() const; + + void setJid(const Jid &); + void setName(const QString &); + void setNode(const QString &); + + enum Action { + None = 0, + Remove, + Update + }; + + Action action() const; + void setAction(Action); + + const Features &features() const; + void setFeatures(const Features &); + + struct Identity + { + QString category; + QString name; + QString type; + }; + + typedef QValueList<Identity> Identities; + + const Identities &identities() const; + void setIdentities(const Identities &); + + // some useful helper functions + static Action string2action(QString s); + static QString action2string(Action a); + + DiscoItem & operator= (const DiscoItem &); + DiscoItem(const DiscoItem &); + + operator AgentItem() const { return toAgentItem(); } + AgentItem toAgentItem() const; + void fromAgentItem(const AgentItem &); + + private: + class Private; + Private *d; + }; + + typedef QValueList<DiscoItem> DiscoList; + + class FormField + { + public: + enum { username, nick, password, name, first, last, email, address, city, state, zip, phone, url, date, misc }; + FormField(const QString &type="", const QString &value=""); + ~FormField(); + + int type() const; + QString fieldName() const; + QString realName() const; + bool isSecret() const; + const QString & value() const; + void setType(int); + bool setType(const QString &); + void setValue(const QString &); + + private: + int tagNameToType(const QString &) const; + QString typeToTagName(int) const; + + int v_type; + QString v_value; + + class Private; + Private *d; + }; + + class Form : public QValueList<FormField> + { + public: + Form(const Jid &j=""); + ~Form(); + + Jid jid() const; + QString instructions() const; + QString key() const; + void setJid(const Jid &); + void setInstructions(const QString &); + void setKey(const QString &); + + private: + Jid v_jid; + QString v_instructions, v_key; + + class Private; + Private *d; + }; + + class SearchResult + { + public: + SearchResult(const Jid &jid=""); + ~SearchResult(); + + const Jid & jid() const; + const QString & nick() const; + const QString & first() const; + const QString & last() const; + const QString & email() const; + + void setJid(const Jid &); + void setNick(const QString &); + void setFirst(const QString &); + void setLast(const QString &); + void setEmail(const QString &); + + private: + Jid v_jid; + QString v_nick, v_first, v_last, v_email; + }; + + class Client; + class LiveRosterItem; + class LiveRoster; + class S5BManager; + class IBBManager; + class JidLinkManager; + class FileTransferManager; + + class Task : public QObject + { + Q_OBJECT + public: + enum { ErrDisc }; + Task(Task *parent); + Task(Client *, bool isRoot); + virtual ~Task(); + + Task *parent() const; + Client *client() const; + QDomDocument *doc() const; + QString id() const; + + bool success() const; + int statusCode() const; + const QString & statusString() const; + + void go(bool autoDelete=false); + virtual bool take(const QDomElement &); + void safeDelete(); + + signals: + void finished(); + + protected: + virtual void onGo(); + virtual void onDisconnect(); + void send(const QDomElement &); + void setSuccess(int code=0, const QString &str=""); + void setError(const QDomElement &); + void setError(int code=0, const QString &str=""); + void debug(const char *, ...); + void debug(const QString &); + bool iqVerify(const QDomElement &x, const Jid &to, const QString &id, const QString &xmlns=""); + + private slots: + void clientDisconnected(); + void done(); + + private: + void init(); + + class TaskPrivate; + TaskPrivate *d; + }; + + class Client : public QObject + { + Q_OBJECT + + public: + Client(QObject *parent=0); + ~Client(); + + bool isActive() const; + void connectToServer(ClientStream *s, const Jid &j, bool auth=true); + void start(const QString &host, const QString &user, const QString &pass, const QString &resource); + void close(bool fast=false); + + Stream & stream(); + const LiveRoster & roster() const; + const ResourceList & resourceList() const; + + void send(const QDomElement &); + void send(const QString &); + + QString host() const; + QString user() const; + QString pass() const; + QString resource() const; + Jid jid() const; + + void rosterRequest(); + void sendMessage(const Message &); + void sendSubscription(const Jid &, const QString &); + void setPresence(const Status &); + + void debug(const QString &); + QString genUniqueId(); + Task *rootTask(); + QDomDocument *doc() const; + + QString OSName() const; + QString timeZone() const; + int timeZoneOffset() const; + QString clientName() const; + QString clientVersion() const; + QString capsNode() const; + QString capsVersion() const; + QString capsExt() const; + + void setOSName(const QString &); + void setTimeZone(const QString &, int); + void setClientName(const QString &); + void setClientVersion(const QString &); + void setCapsNode(const QString &); + void setCapsVersion(const QString &); + + void setIdentity(DiscoItem::Identity); + DiscoItem::Identity identity(); + + void addExtension(const QString& ext, const Features& f); + void removeExtension(const QString& ext); + const Features& extension(const QString& ext) const; + QStringList extensions() const; + + S5BManager *s5bManager() const; + IBBManager *ibbManager() const; + JidLinkManager *jidLinkManager() const; + + void setFileTransferEnabled(bool b); + FileTransferManager *fileTransferManager() const; + + bool groupChatJoin(const QString &host, const QString &room, const QString &nick); + bool groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &password); + void groupChatSetStatus(const QString &host, const QString &room, const Status &); + void groupChatChangeNick(const QString &host, const QString &room, const QString &nick, const Status &); + void groupChatLeave(const QString &host, const QString &room); + + signals: + void activated(); + void disconnected(); + //void authFinished(bool, int, const QString &); + void rosterRequestFinished(bool, int, const QString &); + void rosterItemAdded(const RosterItem &); + void rosterItemUpdated(const RosterItem &); + void rosterItemRemoved(const RosterItem &); + void resourceAvailable(const Jid &, const Resource &); + void resourceUnavailable(const Jid &, const Resource &); + void presenceError(const Jid &, int, const QString &); + void subscription(const Jid &, const QString &); + void messageReceived(const Message &); + void debugText(const QString &); + void xmlIncoming(const QString &); + void xmlOutgoing(const QString &); + void groupChatJoined(const Jid &); + void groupChatLeft(const Jid &); + void groupChatPresence(const Jid &, const Status &); + void groupChatError(const Jid &, int, const QString &); + + void incomingJidLink(); + + private slots: + //void streamConnected(); + //void streamHandshaken(); + //void streamError(const StreamError &); + //void streamSSLCertificateReady(const QSSLCert &); + //void streamCloseFinished(); + void streamError(int); + void streamReadyRead(); + void streamIncomingXml(const QString &); + void streamOutgoingXml(const QString &); + + void slotRosterRequestFinished(); + + // basic daemons + void ppSubscription(const Jid &, const QString &); + void ppPresence(const Jid &, const Status &); + void pmMessage(const Message &); + void prRoster(const Roster &); + + void s5b_incomingReady(); + void ibb_incomingReady(); + + public: + class GroupChat; + private: + void cleanup(); + void distribute(const QDomElement &); + void importRoster(const Roster &); + void importRosterItem(const RosterItem &); + void updateSelfPresence(const Jid &, const Status &); + void updatePresence(LiveRosterItem *, const Jid &, const Status &); + + class ClientPrivate; + ClientPrivate *d; + }; + + class LiveRosterItem : public RosterItem + { + public: + LiveRosterItem(const Jid &j=""); + LiveRosterItem(const RosterItem &); + ~LiveRosterItem(); + + void setRosterItem(const RosterItem &); + + ResourceList & resourceList(); + ResourceList::Iterator priority(); + + const ResourceList & resourceList() const; + ResourceList::ConstIterator priority() const; + + bool isAvailable() const; + const Status & lastUnavailableStatus() const; + bool flagForDelete() const; + + void setLastUnavailableStatus(const Status &); + void setFlagForDelete(bool); + + private: + ResourceList v_resourceList; + Status v_lastUnavailableStatus; + bool v_flagForDelete; + + class LiveRosterItemPrivate; + LiveRosterItemPrivate *d; + }; + + class LiveRoster : public QValueList<LiveRosterItem> + { + public: + LiveRoster(); + ~LiveRoster(); + + void flagAllForDelete(); + LiveRoster::Iterator find(const Jid &, bool compareRes=true); + LiveRoster::ConstIterator find(const Jid &, bool compareRes=true) const; + + private: + class LiveRosterPrivate; + LiveRosterPrivate *d; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/include/xmpp.h b/kopete/protocols/jabber/libiris/iris/include/xmpp.h new file mode 100644 index 00000000..5636f963 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/include/xmpp.h @@ -0,0 +1,553 @@ +/* + * xmpp.h - XMPP "core" library API + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMPP_H +#define XMPP_H + +#include<qobject.h> +#include<qstring.h> +#include<qhostaddress.h> +#include<qstring.h> +#include<qcstring.h> +#include<qxml.h> +#include<qdom.h> + +namespace QCA +{ + class TLS; +} + +#ifndef CS_XMPP +class ByteStream; +#endif + +namespace XMPP +{ + // CS_IMPORT_BEGIN cutestuff/bytestream.h +#ifdef CS_XMPP + class ByteStream; +#endif + // CS_IMPORT_END + + class Debug + { + public: + virtual ~Debug(); + + virtual void msg(const QString &)=0; + virtual void outgoingTag(const QString &)=0; + virtual void incomingTag(const QString &)=0; + virtual void outgoingXml(const QDomElement &)=0; + virtual void incomingXml(const QDomElement &)=0; + }; + + void setDebug(Debug *); + + class Connector : public QObject + { + Q_OBJECT + public: + Connector(QObject *parent=0); + virtual ~Connector(); + + virtual void connectToServer(const QString &server)=0; + virtual ByteStream *stream() const=0; + virtual void done()=0; + + bool useSSL() const; + bool havePeerAddress() const; + QHostAddress peerAddress() const; + Q_UINT16 peerPort() const; + + signals: + void connected(); + void error(); + + protected: + void setUseSSL(bool b); + void setPeerAddressNone(); + void setPeerAddress(const QHostAddress &addr, Q_UINT16 port); + + private: + bool ssl; + bool haveaddr; + QHostAddress addr; + Q_UINT16 port; + }; + + class AdvancedConnector : public Connector + { + Q_OBJECT + public: + enum Error { ErrConnectionRefused, ErrHostNotFound, ErrProxyConnect, ErrProxyNeg, ErrProxyAuth, ErrStream }; + AdvancedConnector(QObject *parent=0); + virtual ~AdvancedConnector(); + + class Proxy + { + public: + enum { None, HttpConnect, HttpPoll, Socks }; + Proxy(); + ~Proxy(); + + int type() const; + QString host() const; + Q_UINT16 port() const; + QString url() const; + QString user() const; + QString pass() const; + int pollInterval() const; + + void setHttpConnect(const QString &host, Q_UINT16 port); + void setHttpPoll(const QString &host, Q_UINT16 port, const QString &url); + void setSocks(const QString &host, Q_UINT16 port); + void setUserPass(const QString &user, const QString &pass); + void setPollInterval(int secs); + + private: + int t; + QString v_host, v_url; + Q_UINT16 v_port; + QString v_user, v_pass; + int v_poll; + }; + + void setProxy(const Proxy &proxy); + void setOptHostPort(const QString &host, Q_UINT16 port); + void setOptProbe(bool); + void setOptSSL(bool); + + void changePollInterval(int secs); + + void connectToServer(const QString &server); + ByteStream *stream() const; + void done(); + + int errorCode() const; + + signals: + void srvLookup(const QString &server); + void srvResult(bool success); + void httpSyncStarted(); + void httpSyncFinished(); + + private slots: + void dns_done(); + void srv_done(); + void bs_connected(); + void bs_error(int); + void http_syncStarted(); + void http_syncFinished(); + + private: + class Private; + Private *d; + + void cleanup(); + void do_resolve(); + void do_connect(); + void tryNextSrv(); + }; + + class TLSHandler : public QObject + { + Q_OBJECT + public: + TLSHandler(QObject *parent=0); + virtual ~TLSHandler(); + + virtual void reset()=0; + virtual void startClient(const QString &host)=0; + virtual void write(const QByteArray &a)=0; + virtual void writeIncoming(const QByteArray &a)=0; + + signals: + void success(); + void fail(); + void closed(); + void readyRead(const QByteArray &a); + void readyReadOutgoing(const QByteArray &a, int plainBytes); + }; + + class QCATLSHandler : public TLSHandler + { + Q_OBJECT + public: + QCATLSHandler(QCA::TLS *parent); + ~QCATLSHandler(); + + QCA::TLS *tls() const; + int tlsError() const; + + void reset(); + void startClient(const QString &host); + void write(const QByteArray &a); + void writeIncoming(const QByteArray &a); + + signals: + void tlsHandshaken(); + + public slots: + void continueAfterHandshake(); + + private slots: + void tls_handshaken(); + void tls_readyRead(); + void tls_readyReadOutgoing(int); + void tls_closed(); + void tls_error(int); + + private: + class Private; + Private *d; + }; + + class Jid + { + public: + Jid(); + ~Jid(); + + Jid(const QString &s); + Jid(const char *s); + Jid & operator=(const QString &s); + Jid & operator=(const char *s); + + void set(const QString &s); + void set(const QString &domain, const QString &node, const QString &resource=""); + + void setDomain(const QString &s); + void setNode(const QString &s); + void setResource(const QString &s); + + const QString & domain() const { return d; } + const QString & node() const { return n; } + const QString & resource() const { return r; } + const QString & bare() const { return b; } + const QString & full() const { return f; } + + Jid withNode(const QString &s) const; + Jid withResource(const QString &s) const; + + bool isValid() const; + bool isEmpty() const; + bool compare(const Jid &a, bool compareRes=true) const; + + static bool validDomain(const QString &s, QString *norm=0); + static bool validNode(const QString &s, QString *norm=0); + static bool validResource(const QString &s, QString *norm=0); + + // TODO: kill these later + const QString & host() const { return d; } + const QString & user() const { return n; } + const QString & userHost() const { return b; } + + private: + void reset(); + void update(); + + QString f, b, d, n, r; + bool valid; + }; + + class Stream; + class Stanza + { + public: + enum Kind { Message, Presence, IQ }; + enum ErrorType { Cancel, Continue, Modify, Auth, Wait }; + enum ErrorCond + { + BadRequest, + Conflict, + FeatureNotImplemented, + Forbidden, + InternalServerError, + ItemNotFound, + JidMalformed, + NotAllowed, + PaymentRequired, + RecipientUnavailable, + RegistrationRequired, + ServerNotFound, + ServerTimeout, + ResourceConstraint, + ServiceUnavailable, + SubscriptionRequired, + UndefinedCondition, + UnexpectedRequest + }; + + Stanza(); + Stanza(const Stanza &from); + Stanza & operator=(const Stanza &from); + virtual ~Stanza(); + + class Error + { + public: + Error(int type=Cancel, int condition=UndefinedCondition, const QString &text="", const QDomElement &appSpec=QDomElement()); + + int type; + int condition; + QString text; + QDomElement appSpec; + }; + + bool isNull() const; + + QDomElement element() const; + QString toString() const; + + QDomDocument & doc() const; + QString baseNS() const; + QString xhtmlImNS() const; + QString xhtmlNS() const; + QDomElement createElement(const QString &ns, const QString &tagName); + QDomElement createTextElement(const QString &ns, const QString &tagName, const QString &text); + QDomElement createXHTMLElement(const QString &xHTML); + void appendChild(const QDomElement &e); + + Kind kind() const; + void setKind(Kind k); + + Jid to() const; + Jid from() const; + QString id() const; + QString type() const; + QString lang() const; + + void setTo(const Jid &j); + void setFrom(const Jid &j); + void setId(const QString &id); + void setType(const QString &type); + void setLang(const QString &lang); + + Error error() const; + void setError(const Error &err); + void clearError(); + + private: + friend class Stream; + Stanza(Stream *s, Kind k, const Jid &to, const QString &type, const QString &id); + Stanza(Stream *s, const QDomElement &e); + + class Private; + Private *d; + }; + + class Stream : public QObject + { + Q_OBJECT + public: + enum Error { ErrParse, ErrProtocol, ErrStream, ErrCustom = 10 }; + enum StreamCond { + GenericStreamError, + Conflict, + ConnectionTimeout, + InternalServerError, + InvalidFrom, + InvalidXml, + PolicyViolation, + ResourceConstraint, + SystemShutdown + }; + + Stream(QObject *parent=0); + virtual ~Stream(); + + virtual QDomDocument & doc() const=0; + virtual QString baseNS() const=0; + virtual QString xhtmlImNS() const=0; + virtual QString xhtmlNS() const=0; + virtual bool old() const=0; + + virtual void close()=0; + virtual bool stanzaAvailable() const=0; + virtual Stanza read()=0; + virtual void write(const Stanza &s)=0; + + virtual int errorCondition() const=0; + virtual QString errorText() const=0; + virtual QDomElement errorAppSpec() const=0; + + Stanza createStanza(Stanza::Kind k, const Jid &to="", const QString &type="", const QString &id=""); + Stanza createStanza(const QDomElement &e); + + static QString xmlToString(const QDomElement &e, bool clip=false); + + signals: + void connectionClosed(); + void delayedCloseFinished(); + void readyRead(); + void stanzaWritten(); + void error(int); + }; + + class ClientStream : public Stream + { + Q_OBJECT + public: + enum Error { + ErrConnection = ErrCustom, // Connection error, ask Connector-subclass what's up + ErrNeg, // Negotiation error, see condition + ErrTLS, // TLS error, see condition + ErrAuth, // Auth error, see condition + ErrSecurityLayer, // broken SASL security layer + ErrBind // Resource binding error + }; + enum Warning { + WarnOldVersion, // server uses older XMPP/Jabber "0.9" protocol + WarnNoTLS // there is no chance for TLS at this point + }; + enum NegCond { + HostGone, // host no longer hosted + HostUnknown, // unknown host + RemoteConnectionFailed, // unable to connect to a required remote resource + SeeOtherHost, // a 'redirect', see errorText() for other host + UnsupportedVersion // unsupported XMPP version + }; + enum TLSCond { + TLSStart, // server rejected STARTTLS + TLSFail // TLS failed, ask TLSHandler-subclass what's up + }; + enum SecurityLayer { + LayerTLS, + LayerSASL + }; + enum AuthCond { + GenericAuthError, // all-purpose "can't login" error + NoMech, // No appropriate auth mech available + BadProto, // Bad SASL auth protocol + BadServ, // Server failed mutual auth + EncryptionRequired, // can't use mech without TLS + InvalidAuthzid, // bad input JID + InvalidMech, // bad mechanism + InvalidRealm, // bad realm + MechTooWeak, // can't use mech with this authzid + NotAuthorized, // bad user, bad password, bad creditials + TemporaryAuthFailure // please try again later! + }; + enum BindCond { + BindNotAllowed, // not allowed to bind a resource + BindConflict // resource in-use + }; + + ClientStream(Connector *conn, TLSHandler *tlsHandler=0, QObject *parent=0); + ClientStream(const QString &host, const QString &defRealm, ByteStream *bs, QCA::TLS *tls=0, QObject *parent=0); // server + ~ClientStream(); + + Jid jid() const; + void connectToServer(const Jid &jid, bool auth=true); + void accept(); // server + bool isActive() const; + bool isAuthenticated() const; + + // login params + void setUsername(const QString &s); + void setPassword(const QString &s); + void setRealm(const QString &s); + void continueAfterParams(); + + // SASL information + QString saslMechanism() const; + int saslSSF() const; + + // binding + void setResourceBinding(bool); + + // security options (old protocol only uses the first !) + void setAllowPlain(bool); + void setRequireMutualAuth(bool); + void setSSFRange(int low, int high); + void setOldOnly(bool); + void setSASLMechanism(const QString &s); + void setLocalAddr(const QHostAddress &addr, Q_UINT16 port); + + // reimplemented + QDomDocument & doc() const; + QString baseNS() const; + QString xhtmlImNS() const; + QString xhtmlNS() const; + bool old() const; + + void close(); + bool stanzaAvailable() const; + Stanza read(); + void write(const Stanza &s); + + int errorCondition() const; + QString errorText() const; + QDomElement errorAppSpec() const; + + // extra + void writeDirect(const QString &s); + void setNoopTime(int mills); + + signals: + void connected(); + void securityLayerActivated(int); + void needAuthParams(bool user, bool pass, bool realm); + void authenticated(); + void warning(int); + void incomingXml(const QString &s); + void outgoingXml(const QString &s); + + public slots: + void continueAfterWarning(); + + private slots: + void cr_connected(); + void cr_error(); + + void bs_connectionClosed(); + void bs_delayedCloseFinished(); + void bs_error(int); // server only + + void ss_readyRead(); + void ss_bytesWritten(int); + void ss_tlsHandshaken(); + void ss_tlsClosed(); + void ss_error(int); + + void sasl_clientFirstStep(const QString &mech, const QByteArray *clientInit); + void sasl_nextStep(const QByteArray &stepData); + void sasl_needParams(bool user, bool authzid, bool pass, bool realm); + void sasl_authCheck(const QString &user, const QString &authzid); + void sasl_authenticated(); + void sasl_error(int); + + void doNoop(); + void doReadyRead(); + + private: + class Private; + Private *d; + + void reset(bool all=false); + void processNext(); + int convertedSASLCond() const; + bool handleNeed(); + void handleError(); + void srvProcessNext(); + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/jabber/Makefile.am b/kopete/protocols/jabber/libiris/iris/jabber/Makefile.am new file mode 100644 index 00000000..d480984d --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/Makefile.am @@ -0,0 +1,15 @@ +# we deal with s5b.moc separately since KDE's build system can't cope with Q_OBJECT in .cpp files +METASOURCES = filetransfer.moc xmpp_ibb.moc xmpp_jidlink.moc + +noinst_LTLIBRARIES = libiris_jabber.la +INCLUDES = -I$(srcdir)/../include -I$(srcdir)/../xmpp-core -I$(srcdir)/../xmpp-im -I$(srcdir)/../../cutestuff/util -I$(srcdir)/../../cutestuff/network -I$(srcdir)/../../qca/src $(all_includes) + +libiris_jabber_la_SOURCES = \ + filetransfer.cpp s5b.cpp xmpp_ibb.cpp xmpp_jidlink.cpp all_mocs.cpp + +s5b.lo: s5b.moc + +CLEANFILES = s5b.moc +s5b.moc: $(srcdir)/s5b.cpp $(srcdir)/s5b.h + ${MOC} $(srcdir)/s5b.h > $@ + ${MOC} $(srcdir)/s5b.cpp >> $@ diff --git a/kopete/protocols/jabber/libiris/iris/jabber/all_mocs.cpp b/kopete/protocols/jabber/libiris/iris/jabber/all_mocs.cpp new file mode 100644 index 00000000..f962a854 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/all_mocs.cpp @@ -0,0 +1,23 @@ +/* + * all_mocs.cpp - #include all .moc files in this directory + * Copyright (C) 2004 Richard Smith + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "filetransfer.moc" +#include "xmpp_ibb.moc" +#include "xmpp_jidlink.moc" diff --git a/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.cpp b/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.cpp new file mode 100644 index 00000000..1697b6a2 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.cpp @@ -0,0 +1,770 @@ +/* + * filetransfer.cpp - File Transfer + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"filetransfer.h" + +#include<qtimer.h> +#include<qptrlist.h> +#include<qguardedptr.h> +#include<qfileinfo.h> +#include"xmpp_xmlcommon.h" +#include"s5b.h" + +#define SENDBUFSIZE 65536 + +using namespace XMPP; + +// firstChildElement +// +// Get an element's first child element +static QDomElement firstChildElement(const QDomElement &e) +{ + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + if(n.isElement()) + return n.toElement(); + } + return QDomElement(); +} + +//---------------------------------------------------------------------------- +// FileTransfer +//---------------------------------------------------------------------------- +class FileTransfer::Private +{ +public: + FileTransferManager *m; + JT_FT *ft; + Jid peer; + QString fname; + Q_LLONG size; + Q_LLONG sent; + QString desc; + bool rangeSupported; + Q_LLONG rangeOffset, rangeLength, length; + QString streamType; + bool needStream; + QString id, iq_id; + S5BConnection *c; + Jid proxy; + int state; + bool sender; +}; + +FileTransfer::FileTransfer(FileTransferManager *m, QObject *parent) +:QObject(parent) +{ + d = new Private; + d->m = m; + d->ft = 0; + d->c = 0; + reset(); +} + +FileTransfer::~FileTransfer() +{ + reset(); + delete d; +} + +void FileTransfer::reset() +{ + d->m->unlink(this); + + delete d->ft; + d->ft = 0; + + delete d->c; + d->c = 0; + + d->state = Idle; + d->needStream = false; + d->sent = 0; + d->sender = false; +} + +void FileTransfer::setProxy(const Jid &proxy) +{ + d->proxy = proxy; +} + +void FileTransfer::sendFile(const Jid &to, const QString &fname, Q_LLONG size, const QString &desc) +{ + d->state = Requesting; + d->peer = to; + d->fname = fname; + d->size = size; + d->desc = desc; + d->sender = true; + d->id = d->m->link(this); + + d->ft = new JT_FT(d->m->client()->rootTask()); + connect(d->ft, SIGNAL(finished()), SLOT(ft_finished())); + QStringList list; + list += "http://jabber.org/protocol/bytestreams"; + d->ft->request(to, d->id, fname, size, desc, list); + d->ft->go(true); +} + +int FileTransfer::dataSizeNeeded() const +{ + int pending = d->c->bytesToWrite(); + if(pending >= SENDBUFSIZE) + return 0; + Q_LLONG left = d->length - (d->sent + pending); + int size = SENDBUFSIZE - pending; + if((Q_LLONG)size > left) + size = (int)left; + return size; +} + +void FileTransfer::writeFileData(const QByteArray &a) +{ + int pending = d->c->bytesToWrite(); + Q_LLONG left = d->length - (d->sent + pending); + if(left == 0) + return; + + QByteArray block; + if((Q_LLONG)a.size() > left) { + block = a.copy(); + block.resize((uint)left); + } + else + block = a; + d->c->write(block); +} + +Jid FileTransfer::peer() const +{ + return d->peer; +} + +QString FileTransfer::fileName() const +{ + return d->fname; +} + +Q_LLONG FileTransfer::fileSize() const +{ + return d->size; +} + +QString FileTransfer::description() const +{ + return d->desc; +} + +bool FileTransfer::rangeSupported() const +{ + return d->rangeSupported; +} + +Q_LLONG FileTransfer::offset() const +{ + return d->rangeOffset; +} + +Q_LLONG FileTransfer::length() const +{ + return d->length; +} + +void FileTransfer::accept(Q_LLONG offset, Q_LLONG length) +{ + d->state = Connecting; + d->rangeOffset = offset; + d->rangeLength = length; + if(length > 0) + d->length = length; + else + d->length = d->size; + d->streamType = "http://jabber.org/protocol/bytestreams"; + d->m->con_accept(this); +} + +void FileTransfer::close() +{ + if(d->state == Idle) + return; + if(d->state == WaitingForAccept) + d->m->con_reject(this); + else if(d->state == Active) + d->c->close(); + reset(); +} + +S5BConnection *FileTransfer::s5bConnection() const +{ + return d->c; +} + +void FileTransfer::ft_finished() +{ + JT_FT *ft = d->ft; + d->ft = 0; + + if(ft->success()) { + d->state = Connecting; + d->rangeOffset = ft->rangeOffset(); + d->length = ft->rangeLength(); + if(d->length == 0) + d->length = d->size - d->rangeOffset; + d->streamType = ft->streamType(); + d->c = d->m->client()->s5bManager()->createConnection(); + connect(d->c, SIGNAL(connected()), SLOT(s5b_connected())); + connect(d->c, SIGNAL(connectionClosed()), SLOT(s5b_connectionClosed())); + connect(d->c, SIGNAL(bytesWritten(int)), SLOT(s5b_bytesWritten(int))); + connect(d->c, SIGNAL(error(int)), SLOT(s5b_error(int))); + + if(d->proxy.isValid()) + d->c->setProxy(d->proxy); + d->c->connectToJid(d->peer, d->id); + accepted(); + } + else { + reset(); + if(ft->statusCode() == 403) + error(ErrReject); + else + error(ErrNeg); + } +} + +void FileTransfer::takeConnection(S5BConnection *c) +{ + d->c = c; + connect(d->c, SIGNAL(connected()), SLOT(s5b_connected())); + connect(d->c, SIGNAL(connectionClosed()), SLOT(s5b_connectionClosed())); + connect(d->c, SIGNAL(readyRead()), SLOT(s5b_readyRead())); + connect(d->c, SIGNAL(error(int)), SLOT(s5b_error(int))); + if(d->proxy.isValid()) + d->c->setProxy(d->proxy); + accepted(); + QTimer::singleShot(0, this, SLOT(doAccept())); +} + +void FileTransfer::s5b_connected() +{ + d->state = Active; + connected(); +} + +void FileTransfer::s5b_connectionClosed() +{ + reset(); + error(ErrStream); +} + +void FileTransfer::s5b_readyRead() +{ + QByteArray a = d->c->read(); + Q_LLONG need = d->length - d->sent; + if((Q_LLONG)a.size() > need) + a.resize((uint)need); + d->sent += a.size(); + if(d->sent == d->length) + reset(); + readyRead(a); +} + +void FileTransfer::s5b_bytesWritten(int x) +{ + d->sent += x; + if(d->sent == d->length) + reset(); + bytesWritten(x); +} + +void FileTransfer::s5b_error(int x) +{ + reset(); + if(x == S5BConnection::ErrRefused || x == S5BConnection::ErrConnect) + error(ErrConnect); + else if(x == S5BConnection::ErrProxy) + error(ErrProxy); + else + error(ErrStream); +} + +void FileTransfer::man_waitForAccept(const FTRequest &req) +{ + d->state = WaitingForAccept; + d->peer = req.from; + d->id = req.id; + d->iq_id = req.iq_id; + d->fname = req.fname; + d->size = req.size; + d->desc = req.desc; + d->rangeSupported = req.rangeSupported; +} + +void FileTransfer::doAccept() +{ + d->c->accept(); +} + +//---------------------------------------------------------------------------- +// FileTransferManager +//---------------------------------------------------------------------------- +class FileTransferManager::Private +{ +public: + Client *client; + QPtrList<FileTransfer> list, incoming; + JT_PushFT *pft; +}; + +FileTransferManager::FileTransferManager(Client *client) +:QObject(client) +{ + d = new Private; + d->client = client; + + d->pft = new JT_PushFT(d->client->rootTask()); + connect(d->pft, SIGNAL(incoming(const FTRequest &)), SLOT(pft_incoming(const FTRequest &))); +} + +FileTransferManager::~FileTransferManager() +{ + d->incoming.setAutoDelete(true); + d->incoming.clear(); + delete d->pft; + delete d; +} + +Client *FileTransferManager::client() const +{ + return d->client; +} + +FileTransfer *FileTransferManager::createTransfer() +{ + FileTransfer *ft = new FileTransfer(this); + return ft; +} + +FileTransfer *FileTransferManager::takeIncoming() +{ + if(d->incoming.isEmpty()) + return 0; + + FileTransfer *ft = d->incoming.getFirst(); + d->incoming.removeRef(ft); + + // move to active list + d->list.append(ft); + return ft; +} + +void FileTransferManager::pft_incoming(const FTRequest &req) +{ + bool found = false; + for(QStringList::ConstIterator it = req.streamTypes.begin(); it != req.streamTypes.end(); ++it) { + if((*it) == "http://jabber.org/protocol/bytestreams") { + found = true; + break; + } + } + if(!found) { + d->pft->respondError(req.from, req.iq_id, 400, "No valid stream types"); + return; + } + if(!d->client->s5bManager()->isAcceptableSID(req.from, req.id)) { + d->pft->respondError(req.from, req.iq_id, 400, "SID in use"); + return; + } + + FileTransfer *ft = new FileTransfer(this); + ft->man_waitForAccept(req); + d->incoming.append(ft); + incomingReady(); +} + +void FileTransferManager::s5b_incomingReady(S5BConnection *c) +{ + QPtrListIterator<FileTransfer> it(d->list); + FileTransfer *ft = 0; + for(FileTransfer *i; (i = it.current()); ++it) { + if(i->d->needStream && i->d->peer.compare(c->peer()) && i->d->id == c->sid()) { + ft = i; + break; + } + } + if(!ft) { + c->close(); + delete c; + return; + } + ft->takeConnection(c); +} + +QString FileTransferManager::link(FileTransfer *ft) +{ + d->list.append(ft); + return d->client->s5bManager()->genUniqueSID(ft->d->peer); +} + +void FileTransferManager::con_accept(FileTransfer *ft) +{ + ft->d->needStream = true; + d->pft->respondSuccess(ft->d->peer, ft->d->iq_id, ft->d->rangeOffset, ft->d->rangeLength, ft->d->streamType); +} + +void FileTransferManager::con_reject(FileTransfer *ft) +{ + d->pft->respondError(ft->d->peer, ft->d->iq_id, 403, "Declined"); +} + +void FileTransferManager::unlink(FileTransfer *ft) +{ + d->list.removeRef(ft); +} + +//---------------------------------------------------------------------------- +// JT_FT +//---------------------------------------------------------------------------- +class JT_FT::Private +{ +public: + QDomElement iq; + Jid to; + Q_LLONG size, rangeOffset, rangeLength; + QString streamType; + QStringList streamTypes; +}; + +JT_FT::JT_FT(Task *parent) +:Task(parent) +{ + d = new Private; +} + +JT_FT::~JT_FT() +{ + delete d; +} + +void JT_FT::request(const Jid &to, const QString &_id, const QString &fname, Q_LLONG size, const QString &desc, const QStringList &streamTypes) +{ + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement si = doc()->createElement("si"); + si.setAttribute("xmlns", "http://jabber.org/protocol/si"); + si.setAttribute("id", _id); + si.setAttribute("profile", "http://jabber.org/protocol/si/profile/file-transfer"); + + QDomElement file = doc()->createElement("file"); + file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer"); + file.setAttribute("name", fname); + file.setAttribute("size", QString::number(size)); + if(!desc.isEmpty()) { + QDomElement de = doc()->createElement("desc"); + de.appendChild(doc()->createTextNode(desc)); + file.appendChild(de); + } + QDomElement range = doc()->createElement("range"); + file.appendChild(range); + si.appendChild(file); + + QDomElement feature = doc()->createElement("feature"); + feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg"); + QDomElement x = doc()->createElement("x"); + x.setAttribute("xmlns", "jabber:x:data"); + x.setAttribute("type", "form"); + + QDomElement field = doc()->createElement("field"); + field.setAttribute("var", "stream-method"); + field.setAttribute("type", "list-single"); + for(QStringList::ConstIterator it = streamTypes.begin(); it != streamTypes.end(); ++it) { + QDomElement option = doc()->createElement("option"); + QDomElement value = doc()->createElement("value"); + value.appendChild(doc()->createTextNode(*it)); + option.appendChild(value); + field.appendChild(option); + } + + x.appendChild(field); + feature.appendChild(x); + + si.appendChild(feature); + iq.appendChild(si); + + d->streamTypes = streamTypes; + d->size = size; + d->iq = iq; +} + +Q_LLONG JT_FT::rangeOffset() const +{ + return d->rangeOffset; +} + +Q_LLONG JT_FT::rangeLength() const +{ + return d->rangeLength; +} + +QString JT_FT::streamType() const +{ + return d->streamType; +} + +void JT_FT::onGo() +{ + send(d->iq); +} + +bool JT_FT::take(const QDomElement &x) +{ + if(!iqVerify(x, d->to, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement si = firstChildElement(x); + if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si") { + setError(900, ""); + return true; + } + + QString id = si.attribute("id"); + + Q_LLONG range_offset = 0; + Q_LLONG range_length = 0; + + QDomElement file = si.elementsByTagName("file").item(0).toElement(); + if(!file.isNull()) { + QDomElement range = file.elementsByTagName("range").item(0).toElement(); + if(!range.isNull()) { + int x; + bool ok; + if(range.hasAttribute("offset")) { +#if QT_VERSION >= 0x030200 + x = range.attribute("offset").toLongLong(&ok); +#else + x = range.attribute("offset").toLong(&ok); +#endif + if(!ok || x < 0) { + setError(900, ""); + return true; + } + range_offset = x; + } + if(range.hasAttribute("length")) { +#if QT_VERSION >= 0x030200 + x = range.attribute("length").toLongLong(&ok); +#else + x = range.attribute("length").toLong(&ok); +#endif + if(!ok || x < 0) { + setError(900, ""); + return true; + } + range_length = x; + } + } + } + + if(range_offset > d->size || (range_length > (d->size - range_offset))) { + setError(900, ""); + return true; + } + + QString streamtype; + QDomElement feature = si.elementsByTagName("feature").item(0).toElement(); + if(!feature.isNull() && feature.attribute("xmlns") == "http://jabber.org/protocol/feature-neg") { + QDomElement x = feature.elementsByTagName("x").item(0).toElement(); + if(!x.isNull() && x.attribute("type") == "submit") { + QDomElement field = x.elementsByTagName("field").item(0).toElement(); + if(!field.isNull() && field.attribute("var") == "stream-method") { + QDomElement value = field.elementsByTagName("value").item(0).toElement(); + if(!value.isNull()) + streamtype = value.text(); + } + } + } + + // must be one of the offered streamtypes + bool found = false; + for(QStringList::ConstIterator it = d->streamTypes.begin(); it != d->streamTypes.end(); ++it) { + if((*it) == streamtype) { + found = true; + break; + } + } + if(!found) + return true; + + d->rangeOffset = range_offset; + d->rangeLength = range_length; + d->streamType = streamtype; + setSuccess(); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_PushFT +//---------------------------------------------------------------------------- +JT_PushFT::JT_PushFT(Task *parent) +:Task(parent) +{ +} + +JT_PushFT::~JT_PushFT() +{ +} + +void JT_PushFT::respondSuccess(const Jid &to, const QString &id, Q_LLONG rangeOffset, Q_LLONG rangeLength, const QString &streamType) +{ + QDomElement iq = createIQ(doc(), "result", to.full(), id); + QDomElement si = doc()->createElement("si"); + si.setAttribute("xmlns", "http://jabber.org/protocol/si"); + + if(rangeOffset != 0 || rangeLength != 0) { + QDomElement file = doc()->createElement("file"); + file.setAttribute("xmlns", "http://jabber.org/protocol/si/profile/file-transfer"); + QDomElement range = doc()->createElement("range"); + if(rangeOffset > 0) + range.setAttribute("offset", QString::number(rangeOffset)); + if(rangeLength > 0) + range.setAttribute("length", QString::number(rangeLength)); + file.appendChild(range); + si.appendChild(file); + } + + QDomElement feature = doc()->createElement("feature"); + feature.setAttribute("xmlns", "http://jabber.org/protocol/feature-neg"); + QDomElement x = doc()->createElement("x"); + x.setAttribute("xmlns", "jabber:x:data"); + x.setAttribute("type", "submit"); + + QDomElement field = doc()->createElement("field"); + field.setAttribute("var", "stream-method"); + QDomElement value = doc()->createElement("value"); + value.appendChild(doc()->createTextNode(streamType)); + field.appendChild(value); + + x.appendChild(field); + feature.appendChild(x); + + si.appendChild(feature); + iq.appendChild(si); + send(iq); +} + +void JT_PushFT::respondError(const Jid &to, const QString &id, int code, const QString &str) +{ + QDomElement iq = createIQ(doc(), "error", to.full(), id); + QDomElement err = textTag(doc(), "error", str); + err.setAttribute("code", QString::number(code)); + iq.appendChild(err); + send(iq); +} + +bool JT_PushFT::take(const QDomElement &e) +{ + // must be an iq-set tag + if(e.tagName() != "iq") + return false; + if(e.attribute("type") != "set") + return false; + + QDomElement si = firstChildElement(e); + if(si.attribute("xmlns") != "http://jabber.org/protocol/si" || si.tagName() != "si") + return false; + if(si.attribute("profile") != "http://jabber.org/protocol/si/profile/file-transfer") + return false; + + Jid from(e.attribute("from")); + QString id = si.attribute("id"); + + QDomElement file = si.elementsByTagName("file").item(0).toElement(); + if(file.isNull()) + return true; + + QString fname = file.attribute("name"); + if(fname.isEmpty()) { + respondError(from, id, 400, "Bad file name"); + return true; + } + + // ensure kosher + { + QFileInfo fi(fname); + fname = fi.fileName(); + } + + bool ok; +#if QT_VERSION >= 0x030200 + Q_LLONG size = file.attribute("size").toLongLong(&ok); +#else + Q_LLONG size = file.attribute("size").toLong(&ok); +#endif + if(!ok || size < 0) { + respondError(from, id, 400, "Bad file size"); + return true; + } + + QString desc; + QDomElement de = file.elementsByTagName("desc").item(0).toElement(); + if(!de.isNull()) + desc = de.text(); + + bool rangeSupported = false; + QDomElement range = file.elementsByTagName("range").item(0).toElement(); + if(!range.isNull()) + rangeSupported = true; + + QStringList streamTypes; + QDomElement feature = si.elementsByTagName("feature").item(0).toElement(); + if(!feature.isNull() && feature.attribute("xmlns") == "http://jabber.org/protocol/feature-neg") { + QDomElement x = feature.elementsByTagName("x").item(0).toElement(); + if(!x.isNull() /*&& x.attribute("type") == "form"*/) { + QDomElement field = x.elementsByTagName("field").item(0).toElement(); + if(!field.isNull() && field.attribute("var") == "stream-method" && field.attribute("type") == "list-single") { + QDomNodeList nl = field.elementsByTagName("option"); + for(uint n = 0; n < nl.count(); ++n) { + QDomElement e = nl.item(n).toElement(); + QDomElement value = e.elementsByTagName("value").item(0).toElement(); + if(!value.isNull()) + streamTypes += value.text(); + } + } + } + } + + FTRequest r; + r.from = from; + r.iq_id = e.attribute("id"); + r.id = id; + r.fname = fname; + r.size = size; + r.desc = desc; + r.rangeSupported = rangeSupported; + r.streamTypes = streamTypes; + + incoming(r); + return true; +} diff --git a/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.h b/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.h new file mode 100644 index 00000000..9ad4d403 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/filetransfer.h @@ -0,0 +1,170 @@ +/* + * filetransfer.h - File Transfer + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMPP_FILETRANSFER_H +#define XMPP_FILETRANSFER_H + +#include"im.h" + +#if QT_VERSION < 0x030200 +typedef long int Q_LLONG; +#endif + +namespace XMPP +{ + class S5BConnection; + struct FTRequest; + + class FileTransfer : public QObject + { + Q_OBJECT + public: + enum { ErrReject, ErrNeg, ErrConnect, ErrProxy, ErrStream }; + enum { Idle, Requesting, Connecting, WaitingForAccept, Active }; + ~FileTransfer(); + + void setProxy(const Jid &proxy); + + // send + void sendFile(const Jid &to, const QString &fname, Q_LLONG size, const QString &desc); + Q_LLONG offset() const; + Q_LLONG length() const; + int dataSizeNeeded() const; + void writeFileData(const QByteArray &a); + + // receive + Jid peer() const; + QString fileName() const; + Q_LLONG fileSize() const; + QString description() const; + bool rangeSupported() const; + void accept(Q_LLONG offset=0, Q_LLONG length=0); + + // both + void close(); // reject, or stop sending/receiving + S5BConnection *s5bConnection() const; // active link + + signals: + void accepted(); // indicates S5BConnection has started + void connected(); + void readyRead(const QByteArray &a); + void bytesWritten(int); + void error(int); + + private slots: + void ft_finished(); + void s5b_connected(); + void s5b_connectionClosed(); + void s5b_readyRead(); + void s5b_bytesWritten(int); + void s5b_error(int); + void doAccept(); + + private: + class Private; + Private *d; + + void reset(); + + friend class FileTransferManager; + FileTransfer(FileTransferManager *, QObject *parent=0); + void man_waitForAccept(const FTRequest &req); + void takeConnection(S5BConnection *c); + }; + + class FileTransferManager : public QObject + { + Q_OBJECT + public: + FileTransferManager(Client *); + ~FileTransferManager(); + + Client *client() const; + FileTransfer *createTransfer(); + FileTransfer *takeIncoming(); + + signals: + void incomingReady(); + + private slots: + void pft_incoming(const FTRequest &req); + + private: + class Private; + Private *d; + + friend class Client; + void s5b_incomingReady(S5BConnection *); + + friend class FileTransfer; + QString link(FileTransfer *); + void con_accept(FileTransfer *); + void con_reject(FileTransfer *); + void unlink(FileTransfer *); + }; + + class JT_FT : public Task + { + Q_OBJECT + public: + JT_FT(Task *parent); + ~JT_FT(); + + void request(const Jid &to, const QString &id, const QString &fname, Q_LLONG size, const QString &desc, const QStringList &streamTypes); + Q_LLONG rangeOffset() const; + Q_LLONG rangeLength() const; + QString streamType() const; + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + + struct FTRequest + { + Jid from; + QString iq_id, id; + QString fname; + Q_LLONG size; + QString desc; + bool rangeSupported; + QStringList streamTypes; + }; + class JT_PushFT : public Task + { + Q_OBJECT + public: + JT_PushFT(Task *parent); + ~JT_PushFT(); + + void respondSuccess(const Jid &to, const QString &id, Q_LLONG rangeOffset, Q_LLONG rangeLength, const QString &streamType); + void respondError(const Jid &to, const QString &id, int code, const QString &str); + + bool take(const QDomElement &); + + signals: + void incoming(const FTRequest &req); + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/jabber/s5b.cpp b/kopete/protocols/jabber/libiris/iris/jabber/s5b.cpp new file mode 100644 index 00000000..b4b9be44 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/s5b.cpp @@ -0,0 +1,2538 @@ +/* + * s5b.cpp - direct connection protocol via tcp + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <config.h> + +#include"s5b.h" + +#include<qtimer.h> +#include<qguardedptr.h> +#include<stdlib.h> +#include<qca.h> +#include"xmpp_xmlcommon.h" +#include"hash.h" +#include"socks.h" +#include"safedelete.h" + +#ifdef Q_OS_WIN +# include <windows.h> +#else +# ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +# endif +# include <netinet/in.h> +#endif + +#define MAXSTREAMHOSTS 5 + +//#define S5B_DEBUG + +namespace XMPP { + +static QString makeKey(const QString &sid, const Jid &initiator, const Jid &target) +{ + QString str = sid + initiator.full() + target.full(); + return QCA::SHA1::hashToString(str.utf8()); +} + +static bool haveHost(const StreamHostList &list, const Jid &j) +{ + for(StreamHostList::ConstIterator it = list.begin(); it != list.end(); ++it) { + if((*it).jid().compare(j)) + return true; + } + return false; +} + +class S5BManager::Item : public QObject +{ + Q_OBJECT +public: + enum { Idle, Initiator, Target, Active }; + enum { ErrRefused, ErrConnect, ErrWrongHost, ErrProxy }; + enum { Unknown, Fast, NotFast }; + S5BManager *m; + int state; + QString sid, key, out_key, out_id, in_id; + Jid self, peer; + StreamHostList in_hosts; + JT_S5B *task, *proxy_task; + SocksClient *client, *client_out; + SocksUDP *client_udp, *client_out_udp; + S5BConnector *conn, *proxy_conn; + bool wantFast; + StreamHost proxy; + int targetMode; // initiator sets this once it figures it out + bool fast; // target sets this + bool activated; + bool lateProxy; + bool connSuccess; + bool localFailed, remoteFailed; + bool allowIncoming; + bool udp; + int statusCode; + Jid activatedStream; + + Item(S5BManager *manager); + ~Item(); + + void reset(); + void startInitiator(const QString &_sid, const Jid &_self, const Jid &_peer, bool fast, bool udp); + void startTarget(const QString &_sid, const Jid &_self, const Jid &_peer, const StreamHostList &hosts, const QString &iq_id, bool fast, bool udp); + void handleFast(const StreamHostList &hosts, const QString &iq_id); + + void doOutgoing(); + void doIncoming(); + void setIncomingClient(SocksClient *sc); + void incomingActivate(const Jid &streamHost); + +signals: + void accepted(); + void tryingHosts(const StreamHostList &list); + void proxyConnect(); + void waitingForActivation(); + void connected(); + void error(int); + +private slots: + void jt_finished(); + void conn_result(bool b); + void proxy_result(bool b); + void proxy_finished(); + void sc_readyRead(); + void sc_bytesWritten(int); + void sc_error(int); + +private: + void doConnectError(); + void tryActivation(); + void checkForActivation(); + void checkFailure(); + void finished(); +}; + +//---------------------------------------------------------------------------- +// S5BDatagram +//---------------------------------------------------------------------------- +S5BDatagram::S5BDatagram() +{ + _source = 0; + _dest = 0; +} + +S5BDatagram::S5BDatagram(int source, int dest, const QByteArray &data) +{ + _source = source; + _dest = dest; + _buf = data; +} + +int S5BDatagram::sourcePort() const +{ + return _source; +} + +int S5BDatagram::destPort() const +{ + return _dest; +} + +QByteArray S5BDatagram::data() const +{ + return _buf; +} + +//---------------------------------------------------------------------------- +// S5BConnection +//---------------------------------------------------------------------------- +class S5BConnection::Private +{ +public: + S5BManager *m; + SocksClient *sc; + SocksUDP *su; + int state; + Jid peer; + QString sid; + bool remote; + bool switched; + bool notifyRead, notifyClose; + int id; + S5BRequest req; + Jid proxy; + Mode mode; + QPtrList<S5BDatagram> dglist; +}; + +static int id_conn = 0; +static int num_conn = 0; + +S5BConnection::S5BConnection(S5BManager *m, QObject *parent) +:ByteStream(parent) +{ + d = new Private; + d->m = m; + d->sc = 0; + d->su = 0; + + ++num_conn; + d->id = id_conn++; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: constructing, count=%d, %p\n", d->id, num_conn, this); +#endif + + reset(); +} + +S5BConnection::~S5BConnection() +{ + reset(true); + + --num_conn; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: destructing, count=%d\n", d->id, num_conn); +#endif + + delete d; +} + +void S5BConnection::reset(bool clear) +{ + d->m->con_unlink(this); + if(clear && d->sc) { + delete d->sc; + d->sc = 0; + } + delete d->su; + d->su = 0; + if(clear) { + d->dglist.setAutoDelete(true); + d->dglist.clear(); + d->dglist.setAutoDelete(false); + } + d->state = Idle; + d->peer = Jid(); + d->sid = QString(); + d->remote = false; + d->switched = false; + d->notifyRead = false; + d->notifyClose = false; +} + +Jid S5BConnection::proxy() const +{ + return d->proxy; +} + +void S5BConnection::setProxy(const Jid &proxy) +{ + d->proxy = proxy; +} + +void S5BConnection::connectToJid(const Jid &peer, const QString &sid, Mode m) +{ + reset(true); + if(!d->m->isAcceptableSID(peer, sid)) + return; + + d->peer = peer; + d->sid = sid; + d->state = Requesting; + d->mode = m; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: connecting %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1()); +#endif + d->m->con_connect(this); +} + +void S5BConnection::accept() +{ + if(d->state != WaitingForAccept) + return; + + d->state = Connecting; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: accepting %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1()); +#endif + d->m->con_accept(this); +} + +void S5BConnection::close() +{ + if(d->state == Idle) + return; + + if(d->state == WaitingForAccept) + d->m->con_reject(this); + else if(d->state == Active) + d->sc->close(); +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: closing %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1()); +#endif + reset(); +} + +Jid S5BConnection::peer() const +{ + return d->peer; +} + +QString S5BConnection::sid() const +{ + return d->sid; +} + +bool S5BConnection::isRemote() const +{ + return d->remote; +} + +S5BConnection::Mode S5BConnection::mode() const +{ + return d->mode; +} + +int S5BConnection::state() const +{ + return d->state; +} + +bool S5BConnection::isOpen() const +{ + if(d->state == Active) + return true; + else + return false; +} + +void S5BConnection::write(const QByteArray &buf) +{ + if(d->state == Active && d->mode == Stream) + d->sc->write(buf); +} + +QByteArray S5BConnection::read(int bytes) +{ + if(d->sc) + return d->sc->read(bytes); + else + return QByteArray(); +} + +int S5BConnection::bytesAvailable() const +{ + if(d->sc) + return d->sc->bytesAvailable(); + else + return 0; +} + +int S5BConnection::bytesToWrite() const +{ + if(d->state == Active) + return d->sc->bytesToWrite(); + else + return 0; +} + +void S5BConnection::writeDatagram(const S5BDatagram &i) +{ + QByteArray buf(i.data().size() + 4); + ushort ssp = htons(i.sourcePort()); + ushort sdp = htons(i.destPort()); + QByteArray data = i.data(); + memcpy(buf.data(), &ssp, 2); + memcpy(buf.data() + 2, &sdp, 2); + memcpy(buf.data() + 4, data.data(), data.size()); + sendUDP(buf); +} + +S5BDatagram S5BConnection::readDatagram() +{ + if(d->dglist.isEmpty()) + return S5BDatagram(); + S5BDatagram *i = d->dglist.getFirst(); + d->dglist.removeRef(i); + S5BDatagram val = *i; + delete i; + return val; +} + +int S5BConnection::datagramsAvailable() const +{ + return d->dglist.count(); +} + +void S5BConnection::man_waitForAccept(const S5BRequest &r) +{ + d->state = WaitingForAccept; + d->remote = true; + d->req = r; + d->peer = r.from; + d->sid = r.sid; + d->mode = r.udp ? Datagram : Stream; +} + +void S5BConnection::man_clientReady(SocksClient *sc, SocksUDP *sc_udp) +{ + d->sc = sc; + connect(d->sc, SIGNAL(connectionClosed()), SLOT(sc_connectionClosed())); + connect(d->sc, SIGNAL(delayedCloseFinished()), SLOT(sc_delayedCloseFinished())); + connect(d->sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); + connect(d->sc, SIGNAL(bytesWritten(int)), SLOT(sc_bytesWritten(int))); + connect(d->sc, SIGNAL(error(int)), SLOT(sc_error(int))); + + if(sc_udp) { + d->su = sc_udp; + connect(d->su, SIGNAL(packetReady(const QByteArray &)), SLOT(su_packetReady(const QByteArray &))); + } + + d->state = Active; +#ifdef S5B_DEBUG + printf("S5BConnection[%d]: %s [%s] <<< success >>>\n", d->id, d->peer.full().latin1(), d->sid.latin1()); +#endif + + // bytes already in the stream? + if(d->sc->bytesAvailable()) { +#ifdef S5B_DEBUG + printf("Stream has %d bytes in it.\n", d->sc->bytesAvailable()); +#endif + d->notifyRead = true; + } + // closed before it got here? + if(!d->sc->isOpen()) { +#ifdef S5B_DEBUG + printf("Stream was closed before S5B request finished?\n"); +#endif + d->notifyClose = true; + } + if(d->notifyRead || d->notifyClose) + QTimer::singleShot(0, this, SLOT(doPending())); + connected(); +} + +void S5BConnection::doPending() +{ + if(d->notifyRead) { + if(d->notifyClose) + QTimer::singleShot(0, this, SLOT(doPending())); + sc_readyRead(); + } + else if(d->notifyClose) + sc_connectionClosed(); +} + +void S5BConnection::man_udpReady(const QByteArray &buf) +{ + handleUDP(buf); +} + +void S5BConnection::man_failed(int x) +{ + reset(true); + if(x == S5BManager::Item::ErrRefused) + error(ErrRefused); + if(x == S5BManager::Item::ErrConnect) + error(ErrConnect); + if(x == S5BManager::Item::ErrWrongHost) + error(ErrConnect); + if(x == S5BManager::Item::ErrProxy) + error(ErrProxy); +} + +void S5BConnection::sc_connectionClosed() +{ + // if we have a pending read notification, postpone close + if(d->notifyRead) { +#ifdef S5B_DEBUG + printf("closed while pending read\n"); +#endif + d->notifyClose = true; + return; + } + d->notifyClose = false; + reset(); + connectionClosed(); +} + +void S5BConnection::sc_delayedCloseFinished() +{ + // echo + delayedCloseFinished(); +} + +void S5BConnection::sc_readyRead() +{ + if(d->mode == Datagram) { + // throw the data away + d->sc->read(); + return; + } + + d->notifyRead = false; + // echo + readyRead(); +} + +void S5BConnection::sc_bytesWritten(int x) +{ + // echo + bytesWritten(x); +} + +void S5BConnection::sc_error(int) +{ + reset(); + error(ErrSocket); +} + +void S5BConnection::su_packetReady(const QByteArray &buf) +{ + handleUDP(buf); +} + +void S5BConnection::handleUDP(const QByteArray &buf) +{ + // must be at least 4 bytes, to accomodate virtual ports + if(buf.size() < 4) + return; // drop + + ushort ssp, sdp; + memcpy(&ssp, buf.data(), 2); + memcpy(&sdp, buf.data() + 2, 2); + int source = ntohs(ssp); + int dest = ntohs(sdp); + QByteArray data(buf.size() - 4); + memcpy(data.data(), buf.data() + 4, data.size()); + d->dglist.append(new S5BDatagram(source, dest, data)); + + datagramReady(); +} + +void S5BConnection::sendUDP(const QByteArray &buf) +{ + if(d->su) + d->su->write(buf); + else + d->m->con_sendUDP(this, buf); +} + +//---------------------------------------------------------------------------- +// S5BManager +//---------------------------------------------------------------------------- +class S5BManager::Entry +{ +public: + Entry() + { + i = 0; + query = 0; + udp_init = false; + } + + ~Entry() + { + delete query; + } + + S5BConnection *c; + Item *i; + QString sid; + JT_S5B *query; + StreamHost proxyInfo; + QGuardedPtr<S5BServer> relatedServer; + + bool udp_init; + QHostAddress udp_addr; + int udp_port; +}; + +class S5BManager::Private +{ +public: + Client *client; + S5BServer *serv; + QPtrList<Entry> activeList; + S5BConnectionList incomingConns; + JT_PushS5B *ps; +}; + +S5BManager::S5BManager(Client *parent) +:QObject(parent) +{ + // S5B needs SHA1 + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + d = new Private; + d->client = parent; + d->serv = 0; + d->activeList.setAutoDelete(true); + + d->ps = new JT_PushS5B(d->client->rootTask()); + connect(d->ps, SIGNAL(incoming(const S5BRequest &)), SLOT(ps_incoming(const S5BRequest &))); + connect(d->ps, SIGNAL(incomingUDPSuccess(const Jid &, const QString &)), SLOT(ps_incomingUDPSuccess(const Jid &, const QString &))); + connect(d->ps, SIGNAL(incomingActivate(const Jid &, const QString &, const Jid &)), SLOT(ps_incomingActivate(const Jid &, const QString &, const Jid &))); +} + +S5BManager::~S5BManager() +{ + setServer(0); + d->incomingConns.setAutoDelete(true); + d->incomingConns.clear(); + delete d->ps; + delete d; +} + +Client *S5BManager::client() const +{ + return d->client; +} + +S5BServer *S5BManager::server() const +{ + return d->serv; +} + +void S5BManager::setServer(S5BServer *serv) +{ + if(d->serv) { + d->serv->unlink(this); + d->serv = 0; + } + + if(serv) { + d->serv = serv; + d->serv->link(this); + } +} + +S5BConnection *S5BManager::createConnection() +{ + S5BConnection *c = new S5BConnection(this); + return c; +} + +S5BConnection *S5BManager::takeIncoming() +{ + if(d->incomingConns.isEmpty()) + return 0; + + S5BConnection *c = d->incomingConns.getFirst(); + d->incomingConns.removeRef(c); + + // move to activeList + Entry *e = new Entry; + e->c = c; + e->sid = c->d->sid; + d->activeList.append(e); + + return c; +} + +void S5BManager::ps_incoming(const S5BRequest &req) +{ +#ifdef S5B_DEBUG + printf("S5BManager: incoming from %s\n", req.from.full().latin1()); +#endif + + bool ok = false; + // ensure we don't already have an incoming connection from this peer+sid + S5BConnection *c = findIncoming(req.from, req.sid); + if(!c) { + // do we have an active entry with this sid already? + Entry *e = findEntryBySID(req.from, req.sid); + if(e) { + if(e->i) { + // loopback + if(req.from.compare(d->client->jid()) && (req.id == e->i->out_id)) { +#ifdef S5B_DEBUG + printf("ALLOWED: loopback\n"); +#endif + ok = true; + } + // allowed by 'fast mode' + else if(e->i->state == Item::Initiator && e->i->targetMode == Item::Unknown) { +#ifdef S5B_DEBUG + printf("ALLOWED: fast-mode\n"); +#endif + e->i->handleFast(req.hosts, req.id); + return; + } + } + } + else { +#ifdef S5B_DEBUG + printf("ALLOWED: we don't have it\n"); +#endif + ok = true; + } + } + if(!ok) { + d->ps->respondError(req.from, req.id, 406, "SID in use"); + return; + } + + // create an incoming connection + c = new S5BConnection(this); + c->man_waitForAccept(req); + d->incomingConns.append(c); + incomingReady(); +} + +void S5BManager::ps_incomingUDPSuccess(const Jid &from, const QString &key) +{ + Entry *e = findEntryByHash(key); + if(e && e->i) { + if(e->i->conn) + e->i->conn->man_udpSuccess(from); + else if(e->i->proxy_conn) + e->i->proxy_conn->man_udpSuccess(from); + } +} + +void S5BManager::ps_incomingActivate(const Jid &from, const QString &sid, const Jid &streamHost) +{ + Entry *e = findEntryBySID(from, sid); + if(e && e->i) + e->i->incomingActivate(streamHost); +} + +void S5BManager::doSuccess(const Jid &peer, const QString &id, const Jid &streamHost) +{ + d->ps->respondSuccess(peer, id, streamHost); +} + +void S5BManager::doError(const Jid &peer, const QString &id, int code, const QString &str) +{ + d->ps->respondError(peer, id, code, str); +} + +void S5BManager::doActivate(const Jid &peer, const QString &sid, const Jid &streamHost) +{ + d->ps->sendActivate(peer, sid, streamHost); +} + +QString S5BManager::genUniqueSID(const Jid &peer) const +{ + // get unused key + QString sid; + do { + sid = "s5b_"; + for(int i = 0; i < 4; ++i) { + int word = rand() & 0xffff; + for(int n = 0; n < 4; ++n) { + QString s; + s.sprintf("%x", (word >> (n * 4)) & 0xf); + sid.append(s); + } + } + } while(!isAcceptableSID(peer, sid)); + return sid; +} + +bool S5BManager::isAcceptableSID(const Jid &peer, const QString &sid) const +{ + QString key = makeKey(sid, d->client->jid(), peer); + QString key_out = makeKey(sid, peer, d->client->jid()); + + // if we have a server, then check through it + if(d->serv) { + if(findServerEntryByHash(key) || findServerEntryByHash(key_out)) + return false; + } + else { + if(findEntryByHash(key) || findEntryByHash(key_out)) + return false; + } + return true; +} + +S5BConnection *S5BManager::findIncoming(const Jid &from, const QString &sid) const +{ + QPtrListIterator<S5BConnection> it(d->incomingConns); + for(S5BConnection *c; (c = it.current()); ++it) { + if(c->d->peer.compare(from) && c->d->sid == sid) + return c; + } + return 0; +} + +S5BManager::Entry *S5BManager::findEntry(S5BConnection *c) const +{ + QPtrListIterator<Entry> it(d->activeList); + for(Entry *e; (e = it.current()); ++it) { + if(e->c == c) + return e; + } + return 0; +} + +S5BManager::Entry *S5BManager::findEntry(Item *i) const +{ + QPtrListIterator<Entry> it(d->activeList); + for(Entry *e; (e = it.current()); ++it) { + if(e->i == i) + return e; + } + return 0; +} + +S5BManager::Entry *S5BManager::findEntryByHash(const QString &key) const +{ + QPtrListIterator<Entry> it(d->activeList); + for(Entry *e; (e = it.current()); ++it) { + if(e->i && e->i->key == key) + return e; + } + return 0; +} + +S5BManager::Entry *S5BManager::findEntryBySID(const Jid &peer, const QString &sid) const +{ + QPtrListIterator<Entry> it(d->activeList); + for(Entry *e; (e = it.current()); ++it) { + if(e->i && e->i->peer.compare(peer) && e->sid == sid) + return e; + } + return 0; +} + +S5BManager::Entry *S5BManager::findServerEntryByHash(const QString &key) const +{ + const QPtrList<S5BManager> &manList = d->serv->managerList(); + QPtrListIterator<S5BManager> it(manList); + for(S5BManager *m; (m = it.current()); ++it) { + Entry *e = m->findEntryByHash(key); + if(e) + return e; + } + return 0; +} + +bool S5BManager::srv_ownsHash(const QString &key) const +{ + if(findEntryByHash(key)) + return true; + return false; +} + +void S5BManager::srv_incomingReady(SocksClient *sc, const QString &key) +{ + Entry *e = findEntryByHash(key); + if(!e->i->allowIncoming) { + sc->requestDeny(); + SafeDelete::deleteSingle(sc); + return; + } + if(e->c->d->mode == S5BConnection::Datagram) + sc->grantUDPAssociate("", 0); + else + sc->grantConnect(); + e->relatedServer = (S5BServer *)sender(); + e->i->setIncomingClient(sc); +} + +void S5BManager::srv_incomingUDP(bool init, const QHostAddress &addr, int port, const QString &key, const QByteArray &data) +{ + Entry *e = findEntryByHash(key); + if(!e->c->d->mode != S5BConnection::Datagram) + return; // this key isn't in udp mode? drop! + + if(init) { + if(e->udp_init) + return; // only init once + + // lock on to this sender + e->udp_addr = addr; + e->udp_port = port; + e->udp_init = true; + + // reply that initialization was successful + d->ps->sendUDPSuccess(e->c->d->peer, key); + return; + } + + // not initialized yet? something went wrong + if(!e->udp_init) + return; + + // must come from same source as when initialized + if(addr.toString() != e->udp_addr.toString() || port != e->udp_port) + return; + + e->c->man_udpReady(data); +} + +void S5BManager::srv_unlink() +{ + d->serv = 0; +} + +void S5BManager::con_connect(S5BConnection *c) +{ + if(findEntry(c)) + return; + Entry *e = new Entry; + e->c = c; + e->sid = c->d->sid; + d->activeList.append(e); + + if(c->d->proxy.isValid()) { + queryProxy(e); + return; + } + entryContinue(e); +} + +void S5BManager::con_accept(S5BConnection *c) +{ + Entry *e = findEntry(c); + if(!e) + return; + + if(e->c->d->req.fast) { + if(targetShouldOfferProxy(e)) { + queryProxy(e); + return; + } + } + entryContinue(e); +} + +void S5BManager::con_reject(S5BConnection *c) +{ + d->ps->respondError(c->d->peer, c->d->req.id, 406, "Not acceptable"); +} + +void S5BManager::con_unlink(S5BConnection *c) +{ + Entry *e = findEntry(c); + if(!e) + return; + + // active incoming request? cancel it + if(e->i && e->i->conn) + d->ps->respondError(e->i->peer, e->i->out_id, 406, "Not acceptable"); + delete e->i; + d->activeList.removeRef(e); +} + +void S5BManager::con_sendUDP(S5BConnection *c, const QByteArray &buf) +{ + Entry *e = findEntry(c); + if(!e) + return; + if(!e->udp_init) + return; + + if(e->relatedServer) + e->relatedServer->writeUDP(e->udp_addr, e->udp_port, buf); +} + +void S5BManager::item_accepted() +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->accepted(); // signal +} + +void S5BManager::item_tryingHosts(const StreamHostList &list) +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->tryingHosts(list); // signal +} + +void S5BManager::item_proxyConnect() +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->proxyConnect(); // signal +} + +void S5BManager::item_waitingForActivation() +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->waitingForActivation(); // signal +} + +void S5BManager::item_connected() +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + // grab the client + SocksClient *client = i->client; + i->client = 0; + SocksUDP *client_udp = i->client_udp; + i->client_udp = 0; + + // give it to the connection + e->c->man_clientReady(client, client_udp); +} + +void S5BManager::item_error(int x) +{ + Item *i = (Item *)sender(); + Entry *e = findEntry(i); + + e->c->man_failed(x); +} + +void S5BManager::entryContinue(Entry *e) +{ + e->i = new Item(this); + e->i->proxy = e->proxyInfo; + + connect(e->i, SIGNAL(accepted()), SLOT(item_accepted())); + connect(e->i, SIGNAL(tryingHosts(const StreamHostList &)), SLOT(item_tryingHosts(const StreamHostList &))); + connect(e->i, SIGNAL(proxyConnect()), SLOT(item_proxyConnect())); + connect(e->i, SIGNAL(waitingForActivation()), SLOT(item_waitingForActivation())); + connect(e->i, SIGNAL(connected()), SLOT(item_connected())); + connect(e->i, SIGNAL(error(int)), SLOT(item_error(int))); + + if(e->c->isRemote()) { + const S5BRequest &req = e->c->d->req; + e->i->startTarget(e->sid, d->client->jid(), e->c->d->peer, req.hosts, req.id, req.fast, req.udp); + } + else { + e->i->startInitiator(e->sid, d->client->jid(), e->c->d->peer, true, e->c->d->mode == S5BConnection::Datagram ? true: false); + e->c->requesting(); // signal + } +} + +void S5BManager::queryProxy(Entry *e) +{ + QGuardedPtr<QObject> self = this; + e->c->proxyQuery(); // signal + if(!self) + return; + +#ifdef S5B_DEBUG + printf("querying proxy: [%s]\n", e->c->d->proxy.full().latin1()); +#endif + e->query = new JT_S5B(d->client->rootTask()); + connect(e->query, SIGNAL(finished()), SLOT(query_finished())); + e->query->requestProxyInfo(e->c->d->proxy); + e->query->go(true); +} + +void S5BManager::query_finished() +{ + JT_S5B *query = (JT_S5B *)sender(); + Entry *e; + bool found = false; + QPtrListIterator<Entry> it(d->activeList); + for(; (e = it.current()); ++it) { + if(e->query == query) { + found = true; + break; + } + } + if(!found) + return; + e->query = 0; + +#ifdef S5B_DEBUG + printf("query finished: "); +#endif + if(query->success()) { + e->proxyInfo = query->proxyInfo(); +#ifdef S5B_DEBUG + printf("host/ip=[%s] port=[%d]\n", e->proxyInfo.host().latin1(), e->proxyInfo.port()); +#endif + } + else { +#ifdef S5B_DEBUG + printf("fail\n"); +#endif + } + + QGuardedPtr<QObject> self = this; + e->c->proxyResult(query->success()); // signal + if(!self) + return; + + entryContinue(e); +} + +bool S5BManager::targetShouldOfferProxy(Entry *e) +{ + if(!e->c->d->proxy.isValid()) + return false; + + // if target, don't offer any proxy if the initiator already did + const StreamHostList &hosts = e->c->d->req.hosts; + for(StreamHostList::ConstIterator it = hosts.begin(); it != hosts.end(); ++it) { + if((*it).isProxy()) + return false; + } + + // ensure we don't offer the same proxy as the initiator + if(haveHost(hosts, e->c->d->proxy)) + return false; + + return true; +} + +//---------------------------------------------------------------------------- +// S5BManager::Item +//---------------------------------------------------------------------------- +S5BManager::Item::Item(S5BManager *manager) : QObject(0) +{ + m = manager; + task = 0; + proxy_task = 0; + conn = 0; + proxy_conn = 0; + client_udp = 0; + client = 0; + client_out_udp = 0; + client_out = 0; + reset(); +} + +S5BManager::Item::~Item() +{ + reset(); +} + +void S5BManager::Item::reset() +{ + delete task; + task = 0; + + delete proxy_task; + proxy_task = 0; + + delete conn; + conn = 0; + + delete proxy_conn; + proxy_conn = 0; + + delete client_udp; + client_udp = 0; + + delete client; + client = 0; + + delete client_out_udp; + client_out_udp = 0; + + delete client_out; + client_out = 0; + + state = Idle; + wantFast = false; + targetMode = Unknown; + fast = false; + activated = false; + lateProxy = false; + connSuccess = false; + localFailed = false; + remoteFailed = false; + allowIncoming = false; + udp = false; +} + +void S5BManager::Item::startInitiator(const QString &_sid, const Jid &_self, const Jid &_peer, bool fast, bool _udp) +{ + sid = _sid; + self = _self; + peer = _peer; + key = makeKey(sid, self, peer); + out_key = makeKey(sid, peer, self); + wantFast = fast; + udp = _udp; + +#ifdef S5B_DEBUG + printf("S5BManager::Item initiating request %s [%s]\n", peer.full().latin1(), sid.latin1()); +#endif + state = Initiator; + doOutgoing(); +} + +void S5BManager::Item::startTarget(const QString &_sid, const Jid &_self, const Jid &_peer, const StreamHostList &hosts, const QString &iq_id, bool _fast, bool _udp) +{ + sid = _sid; + peer = _peer; + self = _self; + in_hosts = hosts; + in_id = iq_id; + fast = _fast; + key = makeKey(sid, self, peer); + out_key = makeKey(sid, peer, self); + udp = _udp; + +#ifdef S5B_DEBUG + printf("S5BManager::Item incoming request %s [%s]\n", peer.full().latin1(), sid.latin1()); +#endif + state = Target; + if(fast) + doOutgoing(); + doIncoming(); +} + +void S5BManager::Item::handleFast(const StreamHostList &hosts, const QString &iq_id) +{ + targetMode = Fast; + + QGuardedPtr<QObject> self = this; + accepted(); + if(!self) + return; + + // if we already have a stream, then bounce this request + if(client) { + m->doError(peer, iq_id, 406, "Not acceptable"); + } + else { + in_hosts = hosts; + in_id = iq_id; + doIncoming(); + } +} + +void S5BManager::Item::doOutgoing() +{ + StreamHostList hosts; + S5BServer *serv = m->server(); + if(serv && serv->isActive() && !haveHost(in_hosts, m->client()->jid())) { + QStringList hostList = serv->hostList(); + for(QStringList::ConstIterator it = hostList.begin(); it != hostList.end(); ++it) { + StreamHost h; + h.setJid(m->client()->jid()); + h.setHost(*it); + h.setPort(serv->port()); + hosts += h; + } + } + + // if the proxy is valid, then it's ok to add (the manager already ensured that it doesn't conflict) + if(proxy.jid().isValid()) + hosts += proxy; + + // if we're the target and we have no streamhosts of our own, then don't even bother with fast-mode + if(state == Target && hosts.isEmpty()) { + fast = false; + return; + } + + allowIncoming = true; + + task = new JT_S5B(m->client()->rootTask()); + connect(task, SIGNAL(finished()), SLOT(jt_finished())); + task->request(peer, sid, hosts, state == Initiator ? wantFast : false, udp); + out_id = task->id(); + task->go(true); +} + +void S5BManager::Item::doIncoming() +{ + if(in_hosts.isEmpty()) { + doConnectError(); + return; + } + + StreamHostList list; + if(lateProxy) { + // take just the proxy streamhosts + for(StreamHostList::ConstIterator it = in_hosts.begin(); it != in_hosts.end(); ++it) { + if((*it).isProxy()) + list += *it; + } + lateProxy = false; + } + else { + // only try doing the late proxy trick if using fast mode AND we did not offer a proxy + if((state == Initiator || (state == Target && fast)) && !proxy.jid().isValid()) { + // take just the non-proxy streamhosts + bool hasProxies = false; + for(StreamHostList::ConstIterator it = in_hosts.begin(); it != in_hosts.end(); ++it) { + if((*it).isProxy()) + hasProxies = true; + else + list += *it; + } + if(hasProxies) { + lateProxy = true; + + // no regular streamhosts? wait for remote error + if(list.isEmpty()) + return; + } + } + else + list = in_hosts; + } + + conn = new S5BConnector; + connect(conn, SIGNAL(result(bool)), SLOT(conn_result(bool))); + + QGuardedPtr<QObject> self = this; + tryingHosts(list); + if(!self) + return; + + conn->start(m->client()->jid(), list, out_key, udp, lateProxy ? 10 : 30); +} + +void S5BManager::Item::setIncomingClient(SocksClient *sc) +{ +#ifdef S5B_DEBUG + printf("S5BManager::Item: %s [%s] successful incoming connection\n", peer.full().latin1(), sid.latin1()); +#endif + + connect(sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); + connect(sc, SIGNAL(bytesWritten(int)), SLOT(sc_bytesWritten(int))); + connect(sc, SIGNAL(error(int)), SLOT(sc_error(int))); + + client = sc; + allowIncoming = false; +} + +void S5BManager::Item::incomingActivate(const Jid &streamHost) +{ + if(!activated) { + activatedStream = streamHost; + checkForActivation(); + } +} + +void S5BManager::Item::jt_finished() +{ + JT_S5B *j = task; + task = 0; + +#ifdef S5B_DEBUG + printf("jt_finished: state=%s, %s\n", state == Initiator ? "initiator" : "target", j->success() ? "ok" : "fail"); +#endif + + if(state == Initiator) { + if(targetMode == Unknown) { + targetMode = NotFast; + QGuardedPtr<QObject> self = this; + accepted(); + if(!self) + return; + } + } + + // if we've already reported successfully connecting to them, then this response doesn't matter + if(state == Initiator && connSuccess) { + tryActivation(); + return; + } + + if(j->success()) { + // stop connecting out + if(conn || lateProxy) { + delete conn; + conn = 0; + doConnectError(); + } + + Jid streamHost = j->streamHostUsed(); + + // they connected to us? + if(streamHost.compare(self)) { + if(client) { + if(state == Initiator) { + activatedStream = streamHost; + tryActivation(); + } + else + checkForActivation(); + } + else { +#ifdef S5B_DEBUG + printf("S5BManager::Item %s claims to have connected to us, but we don't see this\n", peer.full().latin1()); +#endif + reset(); + error(ErrWrongHost); + } + } + else if(streamHost.compare(proxy.jid())) { + // toss out any direct incoming, since it won't be used + delete client; + client = 0; + allowIncoming = false; + +#ifdef S5B_DEBUG + printf("attempting to connect to proxy\n"); +#endif + // connect to the proxy + proxy_conn = new S5BConnector; + connect(proxy_conn, SIGNAL(result(bool)), SLOT(proxy_result(bool))); + StreamHostList list; + list += proxy; + + QGuardedPtr<QObject> self = this; + proxyConnect(); + if(!self) + return; + + proxy_conn->start(m->client()->jid(), list, key, udp, 30); + } + else { +#ifdef S5B_DEBUG + printf("S5BManager::Item %s claims to have connected to a streamhost we never offered\n", peer.full().latin1()); +#endif + reset(); + error(ErrWrongHost); + } + } + else { +#ifdef S5B_DEBUG + printf("S5BManager::Item %s [%s] error\n", peer.full().latin1(), sid.latin1()); +#endif + remoteFailed = true; + statusCode = j->statusCode(); + + if(lateProxy) { + if(!conn) + doIncoming(); + } + else { + // if connSuccess is true at this point, then we're a Target + if(connSuccess) + checkForActivation(); + else + checkFailure(); + } + } +} + +void S5BManager::Item::conn_result(bool b) +{ + if(b) { + SocksClient *sc = conn->takeClient(); + SocksUDP *sc_udp = conn->takeUDP(); + StreamHost h = conn->streamHostUsed(); + delete conn; + conn = 0; + connSuccess = true; + +#ifdef S5B_DEBUG + printf("S5BManager::Item: %s [%s] successful outgoing connection\n", peer.full().latin1(), sid.latin1()); +#endif + + connect(sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); + connect(sc, SIGNAL(bytesWritten(int)), SLOT(sc_bytesWritten(int))); + connect(sc, SIGNAL(error(int)), SLOT(sc_error(int))); + + m->doSuccess(peer, in_id, h.jid()); + + // if the first batch works, don't try proxy + lateProxy = false; + + // if initiator, run with this one + if(state == Initiator) { + // if we had an incoming one, toss it + delete client_udp; + client_udp = sc_udp; + delete client; + client = sc; + allowIncoming = false; + activatedStream = peer; + tryActivation(); + } + else { + client_out_udp = sc_udp; + client_out = sc; + checkForActivation(); + } + } + else { + delete conn; + conn = 0; + + // if we delayed the proxies for later, try now + if(lateProxy) { + if(remoteFailed) + doIncoming(); + } + else + doConnectError(); + } +} + +void S5BManager::Item::proxy_result(bool b) +{ +#ifdef S5B_DEBUG + printf("proxy_result: %s\n", b ? "ok" : "fail"); +#endif + if(b) { + SocksClient *sc = proxy_conn->takeClient(); + SocksUDP *sc_udp = proxy_conn->takeUDP(); + delete proxy_conn; + proxy_conn = 0; + + connect(sc, SIGNAL(readyRead()), SLOT(sc_readyRead())); + connect(sc, SIGNAL(bytesWritten(int)), SLOT(sc_bytesWritten(int))); + connect(sc, SIGNAL(error(int)), SLOT(sc_error(int))); + + client = sc; + client_udp = sc_udp; + + // activate +#ifdef S5B_DEBUG + printf("activating proxy stream\n"); +#endif + proxy_task = new JT_S5B(m->client()->rootTask()); + connect(proxy_task, SIGNAL(finished()), SLOT(proxy_finished())); + proxy_task->requestActivation(proxy.jid(), sid, peer); + proxy_task->go(true); + } + else { + delete proxy_conn; + proxy_conn = 0; + reset(); + error(ErrProxy); + } +} + +void S5BManager::Item::proxy_finished() +{ + JT_S5B *j = proxy_task; + proxy_task = 0; + + if(j->success()) { +#ifdef S5B_DEBUG + printf("proxy stream activated\n"); +#endif + if(state == Initiator) { + activatedStream = proxy.jid(); + tryActivation(); + } + else + checkForActivation(); + } + else { + reset(); + error(ErrProxy); + } +} + +void S5BManager::Item::sc_readyRead() +{ +#ifdef S5B_DEBUG + printf("sc_readyRead\n"); +#endif + // only targets check for activation, and only should do it if there is no pending outgoing iq-set + if(state == Target && !task && !proxy_task) + checkForActivation(); +} + +void S5BManager::Item::sc_bytesWritten(int) +{ +#ifdef S5B_DEBUG + printf("sc_bytesWritten\n"); +#endif + // this should only happen to the initiator, and should always be 1 byte (the '\r' sent earlier) + finished(); +} + +void S5BManager::Item::sc_error(int) +{ +#ifdef S5B_DEBUG + printf("sc_error\n"); +#endif + reset(); + error(ErrConnect); +} + +void S5BManager::Item::doConnectError() +{ + localFailed = true; + m->doError(peer, in_id, 404, "Could not connect to given hosts"); + checkFailure(); +} + +void S5BManager::Item::tryActivation() +{ +#ifdef S5B_DEBUG + printf("tryActivation\n"); +#endif + if(activated) { +#ifdef S5B_DEBUG + printf("already activated !?\n"); +#endif + return; + } + + if(targetMode == NotFast) { +#ifdef S5B_DEBUG + printf("tryActivation: NotFast\n"); +#endif + // nothing to activate, we're done + finished(); + } + else if(targetMode == Fast) { + // with fast mode, we don't wait for the iq reply, so delete the task (if any) + delete task; + task = 0; + + activated = true; + + // if udp, activate using special stanza + if(udp) { + m->doActivate(peer, sid, activatedStream); + } + else { +#ifdef S5B_DEBUG + printf("sending extra CR\n"); +#endif + // must send [CR] to activate target streamhost + QByteArray a(1); + a[0] = '\r'; + client->write(a); + } + } +} + +void S5BManager::Item::checkForActivation() +{ + QPtrList<SocksClient> clientList; + if(client) + clientList.append(client); + if(client_out) + clientList.append(client_out); + QPtrListIterator<SocksClient> it(clientList); + for(SocksClient *sc; (sc = it.current()); ++it) { +#ifdef S5B_DEBUG + printf("checking for activation\n"); +#endif + if(fast) { + bool ok = false; + if(udp) { + if((sc == client_out && activatedStream.compare(self)) || (sc == client && !activatedStream.compare(self))) { + clientList.removeRef(sc); + ok = true; + } + } + else { +#ifdef S5B_DEBUG + printf("need CR\n"); +#endif + if(sc->bytesAvailable() >= 1) { + clientList.removeRef(sc); + QByteArray a = sc->read(1); + if(a[0] != '\r') { + delete sc; + return; + } + ok = true; + } + } + + if(ok) { + SocksUDP *sc_udp = 0; + if(sc == client) { + delete client_out_udp; + client_out_udp = 0; + sc_udp = client_udp; + } + else if(sc == client_out) { + delete client_udp; + client_udp = 0; + sc_udp = client_out_udp; + } + + sc->disconnect(this); + clientList.setAutoDelete(true); + clientList.clear(); + client = sc; + client_out = 0; + client_udp = sc_udp; + activated = true; +#ifdef S5B_DEBUG + printf("activation success\n"); +#endif + break; + } + } + else { +#ifdef S5B_DEBUG + printf("not fast mode, no need to wait for anything\n"); +#endif + clientList.removeRef(sc); + sc->disconnect(this); + clientList.setAutoDelete(true); + clientList.clear(); + client = sc; + client_out = 0; + activated = true; + break; + } + } + + if(activated) { + finished(); + } + else { + // only emit waitingForActivation if there is nothing left to do + if((connSuccess || localFailed) && !proxy_task && !proxy_conn) + waitingForActivation(); + } +} + +void S5BManager::Item::checkFailure() +{ + bool failed = false; + if(state == Initiator) { + if(remoteFailed) { + if((localFailed && targetMode == Fast) || targetMode == NotFast) + failed = true; + } + } + else { + if(localFailed) { + if((remoteFailed && fast) || !fast) + failed = true; + } + } + + if(failed) { + if(state == Initiator) { + reset(); + if(statusCode == 404) + error(ErrConnect); + else + error(ErrRefused); + } + else { + reset(); + error(ErrConnect); + } + } +} + +void S5BManager::Item::finished() +{ + client->disconnect(this); + state = Active; +#ifdef S5B_DEBUG + printf("S5BManager::Item %s [%s] linked successfully\n", peer.full().latin1(), sid.latin1()); +#endif + connected(); +} + +//---------------------------------------------------------------------------- +// S5BConnector +//---------------------------------------------------------------------------- +class S5BConnector::Item : public QObject +{ + Q_OBJECT +public: + SocksClient *client; + SocksUDP *client_udp; + StreamHost host; + QString key; + bool udp; + int udp_tries; + QTimer t; + Jid jid; + + Item(const Jid &self, const StreamHost &_host, const QString &_key, bool _udp) : QObject(0) + { + jid = self; + host = _host; + key = _key; + udp = _udp; + client = new SocksClient; + client_udp = 0; + connect(client, SIGNAL(connected()), SLOT(sc_connected())); + connect(client, SIGNAL(error(int)), SLOT(sc_error(int))); + connect(&t, SIGNAL(timeout()), SLOT(trySendUDP())); + } + + ~Item() + { + cleanup(); + } + + void start() + { + client->connectToHost(host.host(), host.port(), key, 0, udp); + } + + void udpSuccess() + { + t.stop(); + client_udp->change(key, 0); // flip over to the data port + success(); + } + +signals: + void result(bool); + +private slots: + void sc_connected() + { + // if udp, need to send init packet before we are good + if(udp) { + // port 1 is init + client_udp = client->createUDP(key, 1, client->peerAddress(), client->peerPort()); + udp_tries = 0; + t.start(5000); + trySendUDP(); + return; + } + + success(); + } + + void sc_error(int) + { +#ifdef S5B_DEBUG + printf("S5BConnector[%s]: error\n", host.host().latin1()); +#endif + cleanup(); + result(false); + } + + void trySendUDP() + { + if(udp_tries == 5) { + t.stop(); + cleanup(); + result(false); + return; + } + + // send initialization with our JID + QCString cs = jid.full().utf8(); + QByteArray a(cs.length()); + memcpy(a.data(), cs.data(), a.size()); + client_udp->write(a); + ++udp_tries; + } + +private: + void cleanup() + { + delete client_udp; + client_udp = 0; + delete client; + client = 0; + } + + void success() + { +#ifdef S5B_DEBUG + printf("S5BConnector[%s]: success\n", host.host().latin1()); +#endif + client->disconnect(this); + result(true); + } +}; + +class S5BConnector::Private +{ +public: + SocksClient *active; + SocksUDP *active_udp; + QPtrList<Item> itemList; + QString key; + StreamHost activeHost; + QTimer t; +}; + +S5BConnector::S5BConnector(QObject *parent) +:QObject(parent) +{ + d = new Private; + d->active = 0; + d->active_udp = 0; + d->itemList.setAutoDelete(true); + connect(&d->t, SIGNAL(timeout()), SLOT(t_timeout())); +} + +S5BConnector::~S5BConnector() +{ + reset(); + delete d; +} + +void S5BConnector::reset() +{ + d->t.stop(); + delete d->active_udp; + d->active_udp = 0; + delete d->active; + d->active = 0; + d->itemList.clear(); +} + +void S5BConnector::start(const Jid &self, const StreamHostList &hosts, const QString &key, bool udp, int timeout) +{ + reset(); + +#ifdef S5B_DEBUG + printf("S5BConnector: starting [%p]!\n", this); +#endif + for(StreamHostList::ConstIterator it = hosts.begin(); it != hosts.end(); ++it) { + Item *i = new Item(self, *it, key, udp); + connect(i, SIGNAL(result(bool)), SLOT(item_result(bool))); + d->itemList.append(i); + i->start(); + } + d->t.start(timeout * 1000); +} + +SocksClient *S5BConnector::takeClient() +{ + SocksClient *c = d->active; + d->active = 0; + return c; +} + +SocksUDP *S5BConnector::takeUDP() +{ + SocksUDP *c = d->active_udp; + d->active_udp = 0; + return c; +} + +StreamHost S5BConnector::streamHostUsed() const +{ + return d->activeHost; +} + +void S5BConnector::item_result(bool b) +{ + Item *i = (Item *)sender(); + if(b) { + d->active = i->client; + i->client = 0; + d->active_udp = i->client_udp; + i->client_udp = 0; + d->activeHost = i->host; + d->itemList.clear(); + d->t.stop(); +#ifdef S5B_DEBUG + printf("S5BConnector: complete! [%p]\n", this); +#endif + result(true); + } + else { + d->itemList.removeRef(i); + if(d->itemList.isEmpty()) { + d->t.stop(); +#ifdef S5B_DEBUG + printf("S5BConnector: failed! [%p]\n", this); +#endif + result(false); + } + } +} + +void S5BConnector::t_timeout() +{ + reset(); +#ifdef S5B_DEBUG + printf("S5BConnector: failed! (timeout)\n"); +#endif + result(false); +} + +void S5BConnector::man_udpSuccess(const Jid &streamHost) +{ + // was anyone sending to this streamhost? + QPtrListIterator<Item> it(d->itemList); + for(Item *i; (i = it.current()); ++it) { + if(i->host.jid().compare(streamHost) && i->client_udp) { + i->udpSuccess(); + return; + } + } +} + +//---------------------------------------------------------------------------- +// S5BServer +//---------------------------------------------------------------------------- +class S5BServer::Item : public QObject +{ + Q_OBJECT +public: + SocksClient *client; + QString host; + QTimer expire; + + Item(SocksClient *c) : QObject(0) + { + client = c; + connect(client, SIGNAL(incomingMethods(int)), SLOT(sc_incomingMethods(int))); + connect(client, SIGNAL(incomingConnectRequest(const QString &, int)), SLOT(sc_incomingConnectRequest(const QString &, int))); + connect(client, SIGNAL(error(int)), SLOT(sc_error(int))); + + connect(&expire, SIGNAL(timeout()), SLOT(doError())); + resetExpiration(); + } + + ~Item() + { + delete client; + } + + void resetExpiration() + { + expire.start(30000); + } + +signals: + void result(bool); + +private slots: + void doError() + { + expire.stop(); + delete client; + client = 0; + result(false); + } + + void sc_incomingMethods(int m) + { + if(m & SocksClient::AuthNone) + client->chooseMethod(SocksClient::AuthNone); + else + doError(); + } + + void sc_incomingConnectRequest(const QString &_host, int port) + { + if(port == 0) { + host = _host; + client->disconnect(this); + result(true); + } + else + doError(); + } + + void sc_error(int) + { + doError(); + } +}; + +class S5BServer::Private +{ +public: + SocksServer serv; + QStringList hostList; + QPtrList<S5BManager> manList; + QPtrList<Item> itemList; +}; + +S5BServer::S5BServer(QObject *parent) +:QObject(parent) +{ + d = new Private; + d->itemList.setAutoDelete(true); + connect(&d->serv, SIGNAL(incomingReady()), SLOT(ss_incomingReady())); + connect(&d->serv, SIGNAL(incomingUDP(const QString &, int, const QHostAddress &, int, const QByteArray &)), SLOT(ss_incomingUDP(const QString &, int, const QHostAddress &, int, const QByteArray &))); +} + +S5BServer::~S5BServer() +{ + unlinkAll(); + delete d; +} + +bool S5BServer::isActive() const +{ + return d->serv.isActive(); +} + +bool S5BServer::start(int port) +{ + d->serv.stop(); + return d->serv.listen(port, true); +} + +void S5BServer::stop() +{ + d->serv.stop(); +} + +void S5BServer::setHostList(const QStringList &list) +{ + d->hostList = list; +} + +QStringList S5BServer::hostList() const +{ + return d->hostList; +} + +int S5BServer::port() const +{ + return d->serv.port(); +} + +void S5BServer::ss_incomingReady() +{ + Item *i = new Item(d->serv.takeIncoming()); +#ifdef S5B_DEBUG + printf("S5BServer: incoming connection from %s:%d\n", i->client->peerAddress().toString().latin1(), i->client->peerPort()); +#endif + connect(i, SIGNAL(result(bool)), SLOT(item_result(bool))); + d->itemList.append(i); +} + +void S5BServer::ss_incomingUDP(const QString &host, int port, const QHostAddress &addr, int sourcePort, const QByteArray &data) +{ + if(port != 0 || port != 1) + return; + + QPtrListIterator<S5BManager> it(d->manList); + for(S5BManager *m; (m = it.current()); ++it) { + if(m->srv_ownsHash(host)) { + m->srv_incomingUDP(port == 1 ? true : false, addr, sourcePort, host, data); + return; + } + } +} + +void S5BServer::item_result(bool b) +{ + Item *i = (Item *)sender(); +#ifdef S5B_DEBUG + printf("S5BServer item result: %d\n", b); +#endif + if(!b) { + d->itemList.removeRef(i); + return; + } + + SocksClient *c = i->client; + i->client = 0; + QString key = i->host; + d->itemList.removeRef(i); + + // find the appropriate manager for this incoming connection + QPtrListIterator<S5BManager> it(d->manList); + for(S5BManager *m; (m = it.current()); ++it) { + if(m->srv_ownsHash(key)) { + m->srv_incomingReady(c, key); + return; + } + } + + // throw it away + delete c; +} + +void S5BServer::link(S5BManager *m) +{ + d->manList.append(m); +} + +void S5BServer::unlink(S5BManager *m) +{ + d->manList.removeRef(m); +} + +void S5BServer::unlinkAll() +{ + QPtrListIterator<S5BManager> it(d->manList); + for(S5BManager *m; (m = it.current()); ++it) + m->srv_unlink(); + d->manList.clear(); +} + +const QPtrList<S5BManager> & S5BServer::managerList() const +{ + return d->manList; +} + +void S5BServer::writeUDP(const QHostAddress &addr, int port, const QByteArray &data) +{ + d->serv.writeUDP(addr, port, data); +} + +//---------------------------------------------------------------------------- +// JT_S5B +//---------------------------------------------------------------------------- +class JT_S5B::Private +{ +public: + QDomElement iq; + Jid to; + Jid streamHost; + StreamHost proxyInfo; + int mode; + QTimer t; +}; + +JT_S5B::JT_S5B(Task *parent) +:Task(parent) +{ + d = new Private; + d->mode = -1; + connect(&d->t, SIGNAL(timeout()), SLOT(t_timeout())); +} + +JT_S5B::~JT_S5B() +{ + delete d; +} + +void JT_S5B::request(const Jid &to, const QString &sid, const StreamHostList &hosts, bool fast, bool udp) +{ + d->mode = 0; + + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + query.setAttribute("sid", sid); + query.setAttribute("mode", udp ? "udp" : "tcp" ); + iq.appendChild(query); + for(StreamHostList::ConstIterator it = hosts.begin(); it != hosts.end(); ++it) { + QDomElement shost = doc()->createElement("streamhost"); + shost.setAttribute("jid", (*it).jid().full()); + shost.setAttribute("host", (*it).host()); + shost.setAttribute("port", QString::number((*it).port())); + if((*it).isProxy()) { + QDomElement p = doc()->createElement("proxy"); + p.setAttribute("xmlns", "http://affinix.com/jabber/stream"); + shost.appendChild(p); + } + query.appendChild(shost); + } + if(fast) { + QDomElement e = doc()->createElement("fast"); + e.setAttribute("xmlns", "http://affinix.com/jabber/stream"); + query.appendChild(e); + } + d->iq = iq; +} + +void JT_S5B::requestProxyInfo(const Jid &to) +{ + d->mode = 1; + + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "get", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + iq.appendChild(query); + d->iq = iq; +} + +void JT_S5B::requestActivation(const Jid &to, const QString &sid, const Jid &target) +{ + d->mode = 2; + + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + query.setAttribute("sid", sid); + iq.appendChild(query); + QDomElement act = doc()->createElement("activate"); + act.appendChild(doc()->createTextNode(target.full())); + query.appendChild(act); + d->iq = iq; +} + +void JT_S5B::onGo() +{ + if(d->mode == 1) + d->t.start(15000, true); + send(d->iq); +} + +void JT_S5B::onDisconnect() +{ + d->t.stop(); +} + +bool JT_S5B::take(const QDomElement &x) +{ + if(d->mode == -1) + return false; + + if(!iqVerify(x, d->to, id())) + return false; + + d->t.stop(); + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + if(d->mode == 0) { + d->streamHost = ""; + if(!q.isNull()) { + QDomElement shost = q.elementsByTagName("streamhost-used").item(0).toElement(); + if(!shost.isNull()) + d->streamHost = shost.attribute("jid"); + } + + setSuccess(); + } + else if(d->mode == 1) { + if(!q.isNull()) { + QDomElement shost = q.elementsByTagName("streamhost").item(0).toElement(); + if(!shost.isNull()) { + Jid j = shost.attribute("jid"); + if(j.isValid()) { + QString host = shost.attribute("host"); + if(!host.isEmpty()) { + int port = shost.attribute("port").toInt(); + StreamHost h; + h.setJid(j); + h.setHost(host); + h.setPort(port); + h.setIsProxy(true); + d->proxyInfo = h; + } + } + } + } + + setSuccess(); + } + else { + setSuccess(); + } + } + else { + setError(x); + } + + return true; +} + +void JT_S5B::t_timeout() +{ + d->mode = -1; + setError(500, "Timed out"); +} + +Jid JT_S5B::streamHostUsed() const +{ + return d->streamHost; +} + +StreamHost JT_S5B::proxyInfo() const +{ + return d->proxyInfo; +} + +//---------------------------------------------------------------------------- +// JT_PushS5B +//---------------------------------------------------------------------------- +JT_PushS5B::JT_PushS5B(Task *parent) +:Task(parent) +{ +} + +JT_PushS5B::~JT_PushS5B() +{ +} + +int JT_PushS5B::priority() const +{ + return 1; +} + +bool JT_PushS5B::take(const QDomElement &e) +{ + // look for udpsuccess + if(e.tagName() == "message") { + QDomElement x = e.elementsByTagName("udpsuccess").item(0).toElement(); + if(!x.isNull() && x.attribute("xmlns") == "http://jabber.org/protocol/bytestreams") { + incomingUDPSuccess(Jid(x.attribute("from")), x.attribute("dstaddr")); + return true; + } + x = e.elementsByTagName("activate").item(0).toElement(); + if(!x.isNull() && x.attribute("xmlns") == "http://affinix.com/jabber/stream") { + incomingActivate(Jid(x.attribute("from")), x.attribute("sid"), Jid(x.attribute("jid"))); + return true; + } + return false; + } + + // must be an iq-set tag + if(e.tagName() != "iq") + return false; + if(e.attribute("type") != "set") + return false; + if(queryNS(e) != "http://jabber.org/protocol/bytestreams") + return false; + + Jid from(e.attribute("from")); + QDomElement q = queryTag(e); + QString sid = q.attribute("sid"); + + StreamHostList hosts; + QDomNodeList nl = q.elementsByTagName("streamhost"); + for(uint n = 0; n < nl.count(); ++n) { + QDomElement shost = nl.item(n).toElement(); + if(hosts.count() < MAXSTREAMHOSTS) { + Jid j = shost.attribute("jid"); + if(!j.isValid()) + continue; + QString host = shost.attribute("host"); + if(host.isEmpty()) + continue; + int port = shost.attribute("port").toInt(); + QDomElement p = shost.elementsByTagName("proxy").item(0).toElement(); + bool isProxy = false; + if(!p.isNull() && p.attribute("xmlns") == "http://affinix.com/jabber/stream") + isProxy = true; + + StreamHost h; + h.setJid(j); + h.setHost(host); + h.setPort(port); + h.setIsProxy(isProxy); + hosts += h; + } + } + + bool fast = false; + QDomElement t; + t = q.elementsByTagName("fast").item(0).toElement(); + if(!t.isNull() && t.attribute("xmlns") == "http://affinix.com/jabber/stream") + fast = true; + + S5BRequest r; + r.from = from; + r.id = e.attribute("id"); + r.sid = sid; + r.hosts = hosts; + r.fast = fast; + r.udp = q.attribute("mode") == "udp" ? true: false; + + incoming(r); + return true; +} + +void JT_PushS5B::respondSuccess(const Jid &to, const QString &id, const Jid &streamHost) +{ + QDomElement iq = createIQ(doc(), "result", to.full(), id); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + iq.appendChild(query); + QDomElement shost = doc()->createElement("streamhost-used"); + shost.setAttribute("jid", streamHost.full()); + query.appendChild(shost); + send(iq); +} + +void JT_PushS5B::respondError(const Jid &to, const QString &id, int code, const QString &str) +{ + QDomElement iq = createIQ(doc(), "error", to.full(), id); + QDomElement err = textTag(doc(), "error", str); + err.setAttribute("code", QString::number(code)); + iq.appendChild(err); + send(iq); +} + +void JT_PushS5B::sendUDPSuccess(const Jid &to, const QString &dstaddr) +{ + QDomElement m = doc()->createElement("message"); + m.setAttribute("to", to.full()); + QDomElement u = doc()->createElement("udpsuccess"); + u.setAttribute("xmlns", "http://jabber.org/protocol/bytestreams"); + u.setAttribute("dstaddr", dstaddr); + m.appendChild(u); + send(m); +} + +void JT_PushS5B::sendActivate(const Jid &to, const QString &sid, const Jid &streamHost) +{ + QDomElement m = doc()->createElement("message"); + m.setAttribute("to", to.full()); + QDomElement act = doc()->createElement("activate"); + act.setAttribute("xmlns", "http://affinix.com/jabber/stream"); + act.setAttribute("sid", sid); + act.setAttribute("jid", streamHost.full()); + m.appendChild(act); + send(m); +} + +//---------------------------------------------------------------------------- +// StreamHost +//---------------------------------------------------------------------------- +StreamHost::StreamHost() +{ + v_port = -1; + proxy = false; +} + +const Jid & StreamHost::jid() const +{ + return j; +} + +const QString & StreamHost::host() const +{ + return v_host; +} + +int StreamHost::port() const +{ + return v_port; +} + +bool StreamHost::isProxy() const +{ + return proxy; +} + +void StreamHost::setJid(const Jid &_j) +{ + j = _j; +} + +void StreamHost::setHost(const QString &host) +{ + v_host = host; +} + +void StreamHost::setPort(int port) +{ + v_port = port; +} + +void StreamHost::setIsProxy(bool b) +{ + proxy = b; +} + +} + +#include"s5b.moc" diff --git a/kopete/protocols/jabber/libiris/iris/jabber/s5b.h b/kopete/protocols/jabber/libiris/iris/jabber/s5b.h new file mode 100644 index 00000000..dec06969 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/s5b.h @@ -0,0 +1,341 @@ +/* + * s5b.h - direct connection protocol via tcp + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMPP_S5B_H +#define XMPP_S5B_H + +#include<qobject.h> +#include<qcstring.h> +#include<qptrlist.h> +#include<qvaluelist.h> +#include"im.h" +#include"bytestream.h" + +class SocksClient; +class SocksUDP; + +namespace XMPP +{ + class StreamHost; + class S5BConnection; + class S5BManager; + class S5BServer; + struct S5BRequest; + typedef QValueList<StreamHost> StreamHostList; + typedef QPtrList<S5BConnection> S5BConnectionList; + typedef QPtrListIterator<S5BConnection> S5BConnectionListIt; + + class S5BDatagram + { + public: + S5BDatagram(); + S5BDatagram(int source, int dest, const QByteArray &data); + + int sourcePort() const; + int destPort() const; + QByteArray data() const; + + private: + int _source, _dest; + QByteArray _buf; + }; + + class S5BConnection : public ByteStream + { + Q_OBJECT + public: + enum Mode { Stream, Datagram }; + enum Error { ErrRefused, ErrConnect, ErrProxy, ErrSocket }; + enum State { Idle, Requesting, Connecting, WaitingForAccept, Active }; + ~S5BConnection(); + + Jid proxy() const; + void setProxy(const Jid &proxy); + + void connectToJid(const Jid &peer, const QString &sid, Mode m = Stream); + void accept(); + void close(); + + Jid peer() const; + QString sid() const; + bool isRemote() const; + Mode mode() const; + int state() const; + + bool isOpen() const; + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + + void writeDatagram(const S5BDatagram &); + S5BDatagram readDatagram(); + int datagramsAvailable() const; + + signals: + void proxyQuery(); // querying proxy for streamhost information + void proxyResult(bool b); // query success / fail + void requesting(); // sent actual S5B request (initiator only) + void accepted(); // target accepted (initiator only + void tryingHosts(const StreamHostList &hosts); // currently connecting to these hosts + void proxyConnect(); // connecting to proxy + void waitingForActivation(); // waiting for activation (target only) + void connected(); // connection active + void datagramReady(); + + private slots: + void doPending(); + + void sc_connectionClosed(); + void sc_delayedCloseFinished(); + void sc_readyRead(); + void sc_bytesWritten(int); + void sc_error(int); + + void su_packetReady(const QByteArray &buf); + + private: + class Private; + Private *d; + + void reset(bool clear=false); + void handleUDP(const QByteArray &buf); + void sendUDP(const QByteArray &buf); + + friend class S5BManager; + void man_waitForAccept(const S5BRequest &r); + void man_clientReady(SocksClient *, SocksUDP *); + void man_udpReady(const QByteArray &buf); + void man_failed(int); + S5BConnection(S5BManager *, QObject *parent=0); + }; + + class S5BManager : public QObject + { + Q_OBJECT + public: + S5BManager(Client *); + ~S5BManager(); + + Client *client() const; + S5BServer *server() const; + void setServer(S5BServer *s); + + bool isAcceptableSID(const Jid &peer, const QString &sid) const; + QString genUniqueSID(const Jid &peer) const; + + S5BConnection *createConnection(); + S5BConnection *takeIncoming(); + + class Item; + class Entry; + + signals: + void incomingReady(); + + private slots: + void ps_incoming(const S5BRequest &req); + void ps_incomingUDPSuccess(const Jid &from, const QString &dstaddr); + void ps_incomingActivate(const Jid &from, const QString &sid, const Jid &streamHost); + void item_accepted(); + void item_tryingHosts(const StreamHostList &list); + void item_proxyConnect(); + void item_waitingForActivation(); + void item_connected(); + void item_error(int); + void query_finished(); + + private: + class Private; + Private *d; + + S5BConnection *findIncoming(const Jid &from, const QString &sid) const; + Entry *findEntry(S5BConnection *) const; + Entry *findEntry(Item *) const; + Entry *findEntryByHash(const QString &key) const; + Entry *findEntryBySID(const Jid &peer, const QString &sid) const; + Entry *findServerEntryByHash(const QString &key) const; + + void entryContinue(Entry *e); + void queryProxy(Entry *e); + bool targetShouldOfferProxy(Entry *e); + + friend class S5BConnection; + void con_connect(S5BConnection *); + void con_accept(S5BConnection *); + void con_reject(S5BConnection *); + void con_unlink(S5BConnection *); + void con_sendUDP(S5BConnection *, const QByteArray &buf); + + friend class S5BServer; + bool srv_ownsHash(const QString &key) const; + void srv_incomingReady(SocksClient *sc, const QString &key); + void srv_incomingUDP(bool init, const QHostAddress &addr, int port, const QString &key, const QByteArray &data); + void srv_unlink(); + + friend class Item; + void doSuccess(const Jid &peer, const QString &id, const Jid &streamHost); + void doError(const Jid &peer, const QString &id, int, const QString &); + void doActivate(const Jid &peer, const QString &sid, const Jid &streamHost); + }; + + class S5BConnector : public QObject + { + Q_OBJECT + public: + S5BConnector(QObject *parent=0); + ~S5BConnector(); + + void reset(); + void start(const Jid &self, const StreamHostList &hosts, const QString &key, bool udp, int timeout); + SocksClient *takeClient(); + SocksUDP *takeUDP(); + StreamHost streamHostUsed() const; + + class Item; + + signals: + void result(bool); + + private slots: + void item_result(bool); + void t_timeout(); + + private: + class Private; + Private *d; + + friend class S5BManager; + void man_udpSuccess(const Jid &streamHost); + }; + + // listens on a port for serving + class S5BServer : public QObject + { + Q_OBJECT + public: + S5BServer(QObject *par=0); + ~S5BServer(); + + bool isActive() const; + bool start(int port); + void stop(); + int port() const; + void setHostList(const QStringList &); + QStringList hostList() const; + + class Item; + + private slots: + void ss_incomingReady(); + void ss_incomingUDP(const QString &host, int port, const QHostAddress &addr, int sourcePort, const QByteArray &data); + void item_result(bool); + + private: + class Private; + Private *d; + + friend class S5BManager; + void link(S5BManager *); + void unlink(S5BManager *); + void unlinkAll(); + const QPtrList<S5BManager> & managerList() const; + void writeUDP(const QHostAddress &addr, int port, const QByteArray &data); + }; + + class JT_S5B : public Task + { + Q_OBJECT + public: + JT_S5B(Task *); + ~JT_S5B(); + + void request(const Jid &to, const QString &sid, const StreamHostList &hosts, bool fast, bool udp=false); + void requestProxyInfo(const Jid &to); + void requestActivation(const Jid &to, const QString &sid, const Jid &target); + + void onGo(); + void onDisconnect(); + bool take(const QDomElement &); + + Jid streamHostUsed() const; + StreamHost proxyInfo() const; + + private slots: + void t_timeout(); + + private: + class Private; + Private *d; + }; + + struct S5BRequest + { + Jid from; + QString id, sid; + StreamHostList hosts; + bool fast; + bool udp; + }; + class JT_PushS5B : public Task + { + Q_OBJECT + public: + JT_PushS5B(Task *); + ~JT_PushS5B(); + + int priority() const; + + void respondSuccess(const Jid &to, const QString &id, const Jid &streamHost); + void respondError(const Jid &to, const QString &id, int code, const QString &str); + void sendUDPSuccess(const Jid &to, const QString &dstaddr); + void sendActivate(const Jid &to, const QString &sid, const Jid &streamHost); + + bool take(const QDomElement &); + + signals: + void incoming(const S5BRequest &req); + void incomingUDPSuccess(const Jid &from, const QString &dstaddr); + void incomingActivate(const Jid &from, const QString &sid, const Jid &streamHost); + }; + + class StreamHost + { + public: + StreamHost(); + + const Jid & jid() const; + const QString & host() const; + int port() const; + bool isProxy() const; + void setJid(const Jid &); + void setHost(const QString &); + void setPort(int); + void setIsProxy(bool); + + private: + Jid j; + QString v_host; + int v_port; + bool proxy; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.cpp b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.cpp new file mode 100644 index 00000000..813157bf --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.cpp @@ -0,0 +1,638 @@ +/* + * ibb.cpp - Inband bytestream + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp_ibb.h" + +#include<qtimer.h> +#include"xmpp_xmlcommon.h" +#include"base64.h" + +#include<stdlib.h> + +#define IBB_PACKET_SIZE 4096 +#define IBB_PACKET_DELAY 0 + +using namespace XMPP; + +static int num_conn = 0; +static int id_conn = 0; + +//---------------------------------------------------------------------------- +// IBBConnection +//---------------------------------------------------------------------------- +class IBBConnection::Private +{ +public: + Private() {} + + int state; + Jid peer; + QString sid; + IBBManager *m; + JT_IBB *j; + QDomElement comment; + QString iq_id; + + int blockSize; + QByteArray recvbuf, sendbuf; + bool closePending, closing; + + int id; +}; + +IBBConnection::IBBConnection(IBBManager *m) +:ByteStream(m) +{ + d = new Private; + d->m = m; + d->j = 0; + reset(); + + ++num_conn; + d->id = id_conn++; + QString dstr; dstr.sprintf("IBBConnection[%d]: constructing, count=%d\n", d->id, num_conn); + d->m->client()->debug(dstr); +} + +void IBBConnection::reset(bool clear) +{ + d->m->unlink(this); + d->state = Idle; + d->closePending = false; + d->closing = false; + + delete d->j; + d->j = 0; + + d->sendbuf.resize(0); + if(clear) + d->recvbuf.resize(0); +} + +IBBConnection::~IBBConnection() +{ + reset(true); + + --num_conn; + QString dstr; dstr.sprintf("IBBConnection[%d]: destructing, count=%d\n", d->id, num_conn); + d->m->client()->debug(dstr); + + delete d; +} + +void IBBConnection::connectToJid(const Jid &peer, const QDomElement &comment) +{ + close(); + reset(true); + + d->state = Requesting; + d->peer = peer; + d->comment = comment; + + QString dstr; dstr.sprintf("IBBConnection[%d]: initiating request to %s\n", d->id, peer.full().latin1()); + d->m->client()->debug(dstr); + + d->j = new JT_IBB(d->m->client()->rootTask()); + connect(d->j, SIGNAL(finished()), SLOT(ibb_finished())); + d->j->request(d->peer, comment); + d->j->go(true); +} + +void IBBConnection::accept() +{ + if(d->state != WaitingForAccept) + return; + + QString dstr; dstr.sprintf("IBBConnection[%d]: accepting %s [%s]\n", d->id, d->peer.full().latin1(), d->sid.latin1()); + d->m->client()->debug(dstr); + + d->m->doAccept(this, d->iq_id); + d->state = Active; + d->m->link(this); +} + +void IBBConnection::close() +{ + if(d->state == Idle) + return; + + if(d->state == WaitingForAccept) { + d->m->doReject(this, d->iq_id, 403, "Rejected"); + reset(); + return; + } + + QString dstr; dstr.sprintf("IBBConnection[%d]: closing\n", d->id); + d->m->client()->debug(dstr); + + if(d->state == Active) { + // if there is data pending to be written, then pend the closing + if(bytesToWrite() > 0) { + d->closePending = true; + trySend(); + return; + } + + // send a close packet + JT_IBB *j = new JT_IBB(d->m->client()->rootTask()); + j->sendData(d->peer, d->sid, QByteArray(), true); + j->go(true); + } + + reset(); +} + +int IBBConnection::state() const +{ + return d->state; +} + +Jid IBBConnection::peer() const +{ + return d->peer; +} + +QString IBBConnection::streamid() const +{ + return d->sid; +} + +QDomElement IBBConnection::comment() const +{ + return d->comment; +} + +bool IBBConnection::isOpen() const +{ + if(d->state == Active) + return true; + else + return false; +} + +void IBBConnection::write(const QByteArray &a) +{ + if(d->state != Active || d->closePending || d->closing) + return; + + // append to the end of our send buffer + int oldsize = d->sendbuf.size(); + d->sendbuf.resize(oldsize + a.size()); + memcpy(d->sendbuf.data() + oldsize, a.data(), a.size()); + + trySend(); +} + +QByteArray IBBConnection::read(int) +{ + // TODO: obey argument + QByteArray a = d->recvbuf.copy(); + d->recvbuf.resize(0); + return a; +} + +int IBBConnection::bytesAvailable() const +{ + return d->recvbuf.size(); +} + +int IBBConnection::bytesToWrite() const +{ + return d->sendbuf.size(); +} + +void IBBConnection::waitForAccept(const Jid &peer, const QString &sid, const QDomElement &comment, const QString &iq_id) +{ + close(); + reset(true); + + d->state = WaitingForAccept; + d->peer = peer; + d->sid = sid; + d->comment = comment; + d->iq_id = iq_id; +} + +void IBBConnection::takeIncomingData(const QByteArray &a, bool close) +{ + // append to the end of our recv buffer + int oldsize = d->recvbuf.size(); + d->recvbuf.resize(oldsize + a.size()); + memcpy(d->recvbuf.data() + oldsize, a.data(), a.size()); + + readyRead(); + + if(close) { + reset(); + connectionClosed(); + } +} + +void IBBConnection::ibb_finished() +{ + JT_IBB *j = d->j; + d->j = 0; + + if(j->success()) { + if(j->mode() == JT_IBB::ModeRequest) { + d->sid = j->streamid(); + + QString dstr; dstr.sprintf("IBBConnection[%d]: %s [%s] accepted.\n", d->id, d->peer.full().latin1(), d->sid.latin1()); + d->m->client()->debug(dstr); + + d->state = Active; + d->m->link(this); + connected(); + } + else { + bytesWritten(d->blockSize); + + if(d->closing) { + reset(); + delayedCloseFinished(); + } + + if(!d->sendbuf.isEmpty() || d->closePending) + QTimer::singleShot(IBB_PACKET_DELAY, this, SLOT(trySend())); + } + } + else { + if(j->mode() == JT_IBB::ModeRequest) { + QString dstr; dstr.sprintf("IBBConnection[%d]: %s refused.\n", d->id, d->peer.full().latin1()); + d->m->client()->debug(dstr); + + reset(true); + error(ErrRequest); + } + else { + reset(true); + error(ErrData); + } + } +} + +void IBBConnection::trySend() +{ + // if we already have an active task, then don't do anything + if(d->j) + return; + + QByteArray a; + if(!d->sendbuf.isEmpty()) { + // take a chunk + if(d->sendbuf.size() < IBB_PACKET_SIZE) + a.resize(d->sendbuf.size()); + else + a.resize(IBB_PACKET_SIZE); + memcpy(a.data(), d->sendbuf.data(), a.size()); + d->sendbuf.resize(d->sendbuf.size() - a.size()); + } + + bool doClose = false; + if(d->sendbuf.isEmpty() && d->closePending) + doClose = true; + + // null operation? + if(a.isEmpty() && !doClose) + return; + + printf("IBBConnection[%d]: sending [%d] bytes ", d->id, a.size()); + if(doClose) + printf("and closing.\n"); + else + printf("(%d bytes left)\n", d->sendbuf.size()); + + if(doClose) { + d->closePending = false; + d->closing = true; + } + + d->blockSize = a.size(); + d->j = new JT_IBB(d->m->client()->rootTask()); + connect(d->j, SIGNAL(finished()), SLOT(ibb_finished())); + d->j->sendData(d->peer, d->sid, a, doClose); + d->j->go(true); +} + + +//---------------------------------------------------------------------------- +// IBBManager +//---------------------------------------------------------------------------- +class IBBManager::Private +{ +public: + Private() {} + + Client *client; + IBBConnectionList activeConns; + IBBConnectionList incomingConns; + JT_IBB *ibb; +}; + +IBBManager::IBBManager(Client *parent) +:QObject(parent) +{ + d = new Private; + d->client = parent; + + d->ibb = new JT_IBB(d->client->rootTask(), true); + connect(d->ibb, SIGNAL(incomingRequest(const Jid &, const QString &, const QDomElement &)), SLOT(ibb_incomingRequest(const Jid &, const QString &, const QDomElement &))); + connect(d->ibb, SIGNAL(incomingData(const Jid &, const QString &, const QString &, const QByteArray &, bool)), SLOT(ibb_incomingData(const Jid &, const QString &, const QString &, const QByteArray &, bool))); +} + +IBBManager::~IBBManager() +{ + d->incomingConns.setAutoDelete(true); + d->incomingConns.clear(); + delete d->ibb; + delete d; +} + +Client *IBBManager::client() const +{ + return d->client; +} + +IBBConnection *IBBManager::takeIncoming() +{ + if(d->incomingConns.isEmpty()) + return 0; + + IBBConnection *c = d->incomingConns.getFirst(); + d->incomingConns.removeRef(c); + return c; +} + +void IBBManager::ibb_incomingRequest(const Jid &from, const QString &id, const QDomElement &comment) +{ + QString sid = genUniqueKey(); + + // create a "waiting" connection + IBBConnection *c = new IBBConnection(this); + c->waitForAccept(from, sid, comment, id); + d->incomingConns.append(c); + incomingReady(); +} + +void IBBManager::ibb_incomingData(const Jid &from, const QString &streamid, const QString &id, const QByteArray &data, bool close) +{ + IBBConnection *c = findConnection(streamid, from); + if(!c) { + d->ibb->respondError(from, id, 404, "No such stream"); + } + else { + d->ibb->respondAck(from, id); + c->takeIncomingData(data, close); + } +} + +QString IBBManager::genKey() const +{ + QString key = "ibb_"; + + for(int i = 0; i < 4; ++i) { + int word = rand() & 0xffff; + for(int n = 0; n < 4; ++n) { + QString s; + s.sprintf("%x", (word >> (n * 4)) & 0xf); + key.append(s); + } + } + + return key; +} + +QString IBBManager::genUniqueKey() const +{ + // get unused key + QString key; + while(1) { + key = genKey(); + + if(!findConnection(key)) + break; + } + + return key; +} + +void IBBManager::link(IBBConnection *c) +{ + d->activeConns.append(c); +} + +void IBBManager::unlink(IBBConnection *c) +{ + d->activeConns.removeRef(c); +} + +IBBConnection *IBBManager::findConnection(const QString &sid, const Jid &peer) const +{ + IBBConnectionListIt it(d->activeConns); + for(IBBConnection *c; (c = it.current()); ++it) { + if(c->streamid() == sid && (peer.isEmpty() || c->peer().compare(peer)) ) + return c; + } + return 0; +} + +void IBBManager::doAccept(IBBConnection *c, const QString &id) +{ + d->ibb->respondSuccess(c->peer(), id, c->streamid()); +} + +void IBBManager::doReject(IBBConnection *c, const QString &id, int code, const QString &str) +{ + d->ibb->respondError(c->peer(), id, code, str); +} + + +//---------------------------------------------------------------------------- +// JT_IBB +//---------------------------------------------------------------------------- +class JT_IBB::Private +{ +public: + Private() {} + + QDomElement iq; + int mode; + bool serve; + Jid to; + QString streamid; +}; + +JT_IBB::JT_IBB(Task *parent, bool serve) +:Task(parent) +{ + d = new Private; + d->serve = serve; +} + +JT_IBB::~JT_IBB() +{ + delete d; +} + +void JT_IBB::request(const Jid &to, const QDomElement &comment) +{ + d->mode = ModeRequest; + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/ibb"); + iq.appendChild(query); + query.appendChild(comment); + d->iq = iq; +} + +void JT_IBB::sendData(const Jid &to, const QString &streamid, const QByteArray &a, bool close) +{ + d->mode = ModeSendData; + QDomElement iq; + d->to = to; + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/ibb"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "streamid", streamid)); + if(!a.isEmpty()) + query.appendChild(textTag(doc(), "data", Base64::arrayToString(a))); + if(close) { + QDomElement c = doc()->createElement("close"); + query.appendChild(c); + } + d->iq = iq; +} + +void JT_IBB::respondSuccess(const Jid &to, const QString &id, const QString &streamid) +{ + QDomElement iq = createIQ(doc(), "result", to.full(), id); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/ibb"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "streamid", streamid)); + send(iq); +} + +void JT_IBB::respondError(const Jid &to, const QString &id, int code, const QString &str) +{ + QDomElement iq = createIQ(doc(), "error", to.full(), id); + QDomElement err = textTag(doc(), "error", str); + err.setAttribute("code", QString::number(code)); + iq.appendChild(err); + send(iq); +} + +void JT_IBB::respondAck(const Jid &to, const QString &id) +{ + QDomElement iq = createIQ(doc(), "result", to.full(), id); + send(iq); +} + +void JT_IBB::onGo() +{ + send(d->iq); +} + +bool JT_IBB::take(const QDomElement &e) +{ + if(d->serve) { + // must be an iq-set tag + if(e.tagName() != "iq" || e.attribute("type") != "set") + return false; + + if(queryNS(e) != "http://jabber.org/protocol/ibb") + return false; + + Jid from(e.attribute("from")); + QString id = e.attribute("id"); + QDomElement q = queryTag(e); + + bool found; + QDomElement s = findSubTag(q, "streamid", &found); + if(!found) { + QDomElement comment = findSubTag(q, "comment", &found); + incomingRequest(from, id, comment); + } + else { + QString sid = tagContent(s); + QByteArray a; + bool close = false; + s = findSubTag(q, "data", &found); + if(found) + a = Base64::stringToArray(tagContent(s)); + s = findSubTag(q, "close", &found); + if(found) + close = true; + + incomingData(from, sid, id, a, close); + } + + return true; + } + else { + Jid from(e.attribute("from")); + if(e.attribute("id") != id() || !d->to.compare(from)) + return false; + + if(e.attribute("type") == "result") { + QDomElement q = queryTag(e); + + // request + if(d->mode == ModeRequest) { + bool found; + QDomElement s = findSubTag(q, "streamid", &found); + if(found) + d->streamid = tagContent(s); + else + d->streamid = ""; + setSuccess(); + } + // sendData + else { + // thank you for the ack, kind sir + setSuccess(); + } + } + else { + setError(e); + } + + return true; + } +} + +QString JT_IBB::streamid() const +{ + return d->streamid; +} + +Jid JT_IBB::jid() const +{ + return d->to; +} + +int JT_IBB::mode() const +{ + return d->mode; +} + diff --git a/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.h b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.h new file mode 100644 index 00000000..73de4ac4 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_ibb.h @@ -0,0 +1,145 @@ +/* + * ibb.h - Inband bytestream + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef JABBER_IBB_H +#define JABBER_IBB_H + +#include<qobject.h> +#include<qdom.h> +#include<qstring.h> +#include<qptrlist.h> +#include"bytestream.h" +#include"im.h" + +namespace XMPP +{ + class Client; + class IBBManager; + + // this is an IBB connection. use it much like a qsocket + class IBBConnection : public ByteStream + { + Q_OBJECT + public: + enum { ErrRequest, ErrData }; + enum { Idle, Requesting, WaitingForAccept, Active }; + IBBConnection(IBBManager *); + ~IBBConnection(); + + void connectToJid(const Jid &peer, const QDomElement &comment); + void accept(); + void close(); + + int state() const; + Jid peer() const; + QString streamid() const; + QDomElement comment() const; + + bool isOpen() const; + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + + signals: + void connected(); + + private slots: + void ibb_finished(); + void trySend(); + + private: + class Private; + Private *d; + + void reset(bool clear=false); + + friend class IBBManager; + void waitForAccept(const Jid &peer, const QString &sid, const QDomElement &comment, const QString &iq_id); + void takeIncomingData(const QByteArray &, bool close); + }; + + typedef QPtrList<IBBConnection> IBBConnectionList; + typedef QPtrListIterator<IBBConnection> IBBConnectionListIt; + class IBBManager : public QObject + { + Q_OBJECT + public: + IBBManager(Client *); + ~IBBManager(); + + Client *client() const; + + IBBConnection *takeIncoming(); + + signals: + void incomingReady(); + + private slots: + void ibb_incomingRequest(const Jid &from, const QString &id, const QDomElement &); + void ibb_incomingData(const Jid &from, const QString &streamid, const QString &id, const QByteArray &data, bool close); + + private: + class Private; + Private *d; + + QString genKey() const; + + friend class IBBConnection; + IBBConnection *findConnection(const QString &sid, const Jid &peer="") const; + QString genUniqueKey() const; + void link(IBBConnection *); + void unlink(IBBConnection *); + void doAccept(IBBConnection *c, const QString &id); + void doReject(IBBConnection *c, const QString &id, int, const QString &); + }; + + class JT_IBB : public Task + { + Q_OBJECT + public: + enum { ModeRequest, ModeSendData }; + JT_IBB(Task *, bool serve=false); + ~JT_IBB(); + + void request(const Jid &, const QDomElement &comment); + void sendData(const Jid &, const QString &streamid, const QByteArray &data, bool close); + void respondSuccess(const Jid &, const QString &id, const QString &streamid); + void respondError(const Jid &, const QString &id, int code, const QString &str); + void respondAck(const Jid &to, const QString &id); + + void onGo(); + bool take(const QDomElement &); + + QString streamid() const; + Jid jid() const; + int mode() const; + + signals: + void incomingRequest(const Jid &from, const QString &id, const QDomElement &); + void incomingData(const Jid &from, const QString &streamid, const QString &id, const QByteArray &data, bool close); + + private: + class Private; + Private *d; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.cpp b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.cpp new file mode 100644 index 00000000..eb140880 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.cpp @@ -0,0 +1,318 @@ +/* + * jidlink.cpp - establish a link between Jabber IDs + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp_jidlink.h" + +#include<qdom.h> +#include<qtimer.h> +#include"im.h" +#include"s5b.h" +#include"xmpp_ibb.h" + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// JidLink +//---------------------------------------------------------------------------- +class JidLink::Private +{ +public: + Client *client; + ByteStream *bs; + int type; + int state; + Jid peer; +}; + +JidLink::JidLink(Client *client) +:QObject(client->jidLinkManager()) +{ + d = new Private; + d->client = client; + d->bs = 0; + + reset(); +} + +JidLink::~JidLink() +{ + reset(true); + + delete d; +} + +void JidLink::reset(bool clear) +{ + d->type = None; + d->state = Idle; + + if(d->bs) { + unlink(); + d->bs->close(); + if(clear) { + delete d->bs; + d->bs = 0; + } + } +} + +void JidLink::connectToJid(const Jid &jid, int type, const QDomElement &comment) +{ + reset(true); + if(type == DTCP) + d->bs = d->client->s5bManager()->createConnection(); + else if(type == IBB) + d->bs = new IBBConnection(d->client->ibbManager()); + else + return; + + d->type = type; + d->peer = jid; + d->state = Connecting; + + link(); + + if(type == DTCP) { + S5BConnection *c = (S5BConnection *)d->bs; + status(StatDTCPRequesting); + c->connectToJid(jid, d->client->s5bManager()->genUniqueSID(jid)); + } + else { + IBBConnection *c = (IBBConnection *)d->bs; + status(StatIBBRequesting); + c->connectToJid(jid, comment); + } +} + +void JidLink::link() +{ + if(d->type == DTCP) { + S5BConnection *c = (S5BConnection *)d->bs; + connect(c, SIGNAL(connected()), SLOT(dtcp_connected())); + connect(c, SIGNAL(accepted()), SLOT(dtcp_accepted())); + } + else { + IBBConnection *c = (IBBConnection *)d->bs; + connect(c, SIGNAL(connected()), SLOT(ibb_connected())); + } + + connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); + connect(d->bs, SIGNAL(error(int)), SLOT(bs_error(int))); + connect(d->bs, SIGNAL(bytesWritten(int)), SLOT(bs_bytesWritten(int))); + connect(d->bs, SIGNAL(readyRead()), SLOT(bs_readyRead())); +} + +void JidLink::unlink() +{ + d->bs->disconnect(this); +} + +void JidLink::accept() +{ + if(d->state != WaitingForAccept) + return; + + QTimer::singleShot(0, this, SLOT(doRealAccept())); +} + +void JidLink::doRealAccept() +{ + if(d->type == DTCP) { + ((S5BConnection *)d->bs)->accept(); + d->state = Connecting; + dtcp_accepted(); + } + else { + ((IBBConnection *)d->bs)->accept(); + d->state = Active; + connected(); + } +} + +bool JidLink::setStream(ByteStream *bs) +{ + reset(true); + int type = None; + if(bs->inherits("XMPP::S5BConnection")) + type = DTCP; + else if(bs->inherits("XMPP::IBBConnection")) + type = IBB; + + if(type == None) + return false; + + d->type = type; + d->bs = bs; + d->state = WaitingForAccept; + + link(); + + if(d->type == DTCP) + d->peer = ((S5BConnection *)d->bs)->peer(); + else + d->peer = ((IBBConnection *)d->bs)->peer(); + + return true; +} + +int JidLink::type() const +{ + return d->type; +} + +Jid JidLink::peer() const +{ + return d->peer; +} + +int JidLink::state() const +{ + return d->state; +} + +bool JidLink::isOpen() const +{ + if(d->state == Active) + return true; + else + return false; +} + +void JidLink::close() +{ + if(d->state == Idle) + return; + reset(); +} + +void JidLink::write(const QByteArray &a) +{ + if(d->state == Active) + d->bs->write(a); +} + +QByteArray JidLink::read(int bytes) +{ + if(d->bs) + return d->bs->read(bytes); + else + return QByteArray(); +} + +int JidLink::bytesAvailable() const +{ + if(d->bs) + return d->bs->bytesAvailable(); + else + return 0; +} + +int JidLink::bytesToWrite() const +{ + if(d->state == Active) + return d->bs->bytesToWrite(); + else + return 0; +} + +void JidLink::dtcp_accepted() +{ + status(StatDTCPAccepted); +} + +void JidLink::dtcp_connected() +{ + d->state = Active; + status(StatDTCPConnected); + connected(); +} + +void JidLink::ibb_connected() +{ + d->state = Active; + status(StatIBBConnected); + connected(); +} + +void JidLink::bs_connectionClosed() +{ + reset(); + connectionClosed(); +} + +void JidLink::bs_error(int) +{ + reset(); + error(ErrConnect); +} + +void JidLink::bs_readyRead() +{ + readyRead(); +} + +void JidLink::bs_bytesWritten(int x) +{ + bytesWritten(x); +} + + +//---------------------------------------------------------------------------- +// JidLinkManager +//---------------------------------------------------------------------------- +class JidLinkManager::Private +{ +public: + Private() {} + + Client *client; + QPtrList<JidLink> incomingList; +}; + +JidLinkManager::JidLinkManager(Client *par) +:QObject(par) +{ + d = new Private; + d->client = par; +} + +JidLinkManager::~JidLinkManager() +{ + d->incomingList.setAutoDelete(true); + d->incomingList.clear(); + delete d; +} + +JidLink *JidLinkManager::takeIncoming() +{ + if(d->incomingList.isEmpty()) + return 0; + + JidLink *j = d->incomingList.getFirst(); + d->incomingList.removeRef(j); + return j; +} + +void JidLinkManager::insertStream(ByteStream *bs) +{ + JidLink *j = new JidLink(d->client); + if(j->setStream(bs)) + d->incomingList.append(j); +} diff --git a/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.h b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.h new file mode 100644 index 00000000..955fce50 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/jabber/xmpp_jidlink.h @@ -0,0 +1,114 @@ +/* + * jidlink.h - establish a link between Jabber IDs + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + NOTE: this is not to be confused with JEP-0041 +*/ + +#ifndef JABBER_JIDLINK_H +#define JABBER_JIDLINK_H + +#include<qobject.h> +#include<qstring.h> +#include"xmpp.h" + +class ByteStream; + +namespace XMPP +{ + class Client; + + class JidLink : public QObject + { + Q_OBJECT + public: + enum { None, DTCP, IBB }; + enum { Idle, Connecting, WaitingForAccept, Active }; + enum { ErrConnect, ErrStream }; + enum { StatDTCPRequesting, StatDTCPAccepted, StatDTCPConnected, StatIBBRequesting, StatIBBConnected }; + JidLink(Client *client); + ~JidLink(); + + void connectToJid(const Jid &jid, int type, const QDomElement &comment); + void accept(); + + int type() const; + Jid peer() const; + int state() const; + + bool isOpen() const; + void close(); + void write(const QByteArray &); + QByteArray read(int bytes=0); + int bytesAvailable() const; + int bytesToWrite() const; + + signals: + void connected(); + void connectionClosed(); + void readyRead(); + void bytesWritten(int); + void error(int); + void status(int); + + private slots: + void dtcp_connected(); + void dtcp_accepted(); + void ibb_connected(); + + void bs_connectionClosed(); + void bs_error(int); + void bs_readyRead(); + void bs_bytesWritten(int); + + void doRealAccept(); + + private: + class Private; + Private *d; + + void reset(bool clear=false); + + void link(); + void unlink(); + + friend class JidLinkManager; + bool setStream(ByteStream *); + }; + + // the job of JidLinkManager is to keep track of streams and properly shut them down + class JidLinkManager : public QObject + { + Q_OBJECT + public: + JidLinkManager(Client *); + ~JidLinkManager(); + + JidLink *takeIncoming(); + + void insertStream(ByteStream *); + + private: + class Private; + Private *d; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/Makefile.am b/kopete/protocols/jabber/libiris/iris/xmpp-core/Makefile.am new file mode 100644 index 00000000..f35b1c68 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/Makefile.am @@ -0,0 +1,30 @@ +# The only Q_OBJECT lines are in securestream.{h,cpp} and we deal with them below. +# Give metasources a file with no Q_OBJECT line to stop unsermake assuming we want METASOURCES = AUTO +METASOURCES = ignore_this_warning.moc + +noinst_LTLIBRARIES = libiris_xmpp_core.la +AM_CPPFLAGS = $(IDN_CFLAGS) +INCLUDES = -I$(srcdir)/../include -I$(srcdir)/../xmpp-core -I$(srcdir)/../xmpp-im -I$(srcdir)/../../cutestuff/util -I$(srcdir)/../../cutestuff/network -I$(srcdir)/../../qca/src $(all_includes) + +libiris_xmpp_core_la_CPPFLAGS = $(IDN_CFLAGS) +libiris_xmpp_core_la_LDFLAGS = $(IDN_LIBS) +libiris_xmpp_core_la_SOURCES = \ + connector.cpp \ + jid.cpp \ + securestream.cpp \ + tlshandler.cpp \ + hash.cpp \ + protocol.cpp \ + stream.cpp \ + xmlprotocol.cpp \ + parser.cpp \ + simplesasl.cpp + +libiris_xmpp_core_la_COMPILE_FIRST = securestream.moc + +CLEANFILES = securestream.moc +securestream.moc: $(srcdir)/securestream.cpp $(srcdir)/securestream.h + ${MOC} $(srcdir)/securestream.h > $@ + ${MOC} $(srcdir)/securestream.cpp >> $@ + +KDE_OPTIONS = nofinal diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/connector.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/connector.cpp new file mode 100644 index 00000000..8ebc3ee8 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/connector.cpp @@ -0,0 +1,719 @@ +/* + * connector.cpp - establish a connection to an XMPP server + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + TODO: + + - Test and analyze all possible branches + + XMPP::AdvancedConnector is "good for now." The only real issue is that + most of what it provides is just to work around the old Jabber/XMPP 0.9 + connection behavior. When XMPP 1.0 has taken over the world, we can + greatly simplify this class. - Sep 3rd, 2003. +*/ + +#include"xmpp.h" + +#include<qguardedptr.h> +#include<qca.h> +#include"safedelete.h" + +#ifdef NO_NDNS +#include<qdns.h> +#else +#include"ndns.h" +#endif + +#include"srvresolver.h" +#include"bsocket.h" +#include"httpconnect.h" +#include"httppoll.h" +#include"socks.h" +#include"hash.h" + +//#define XMPP_DEBUG + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// Connector +//---------------------------------------------------------------------------- +Connector::Connector(QObject *parent) +:QObject(parent) +{ + setUseSSL(false); + setPeerAddressNone(); +} + +Connector::~Connector() +{ +} + +bool Connector::useSSL() const +{ + return ssl; +} + +bool Connector::havePeerAddress() const +{ + return haveaddr; +} + +QHostAddress Connector::peerAddress() const +{ + return addr; +} + +Q_UINT16 Connector::peerPort() const +{ + return port; +} + +void Connector::setUseSSL(bool b) +{ + ssl = b; +} + +void Connector::setPeerAddressNone() +{ + haveaddr = false; + addr = QHostAddress(); + port = 0; +} + +void Connector::setPeerAddress(const QHostAddress &_addr, Q_UINT16 _port) +{ + haveaddr = true; + addr = _addr; + port = _port; +} + + +//---------------------------------------------------------------------------- +// AdvancedConnector::Proxy +//---------------------------------------------------------------------------- +AdvancedConnector::Proxy::Proxy() +{ + t = None; + v_poll = 30; +} + +AdvancedConnector::Proxy::~Proxy() +{ +} + +int AdvancedConnector::Proxy::type() const +{ + return t; +} + +QString AdvancedConnector::Proxy::host() const +{ + return v_host; +} + +Q_UINT16 AdvancedConnector::Proxy::port() const +{ + return v_port; +} + +QString AdvancedConnector::Proxy::url() const +{ + return v_url; +} + +QString AdvancedConnector::Proxy::user() const +{ + return v_user; +} + +QString AdvancedConnector::Proxy::pass() const +{ + return v_pass; +} + +int AdvancedConnector::Proxy::pollInterval() const +{ + return v_poll; +} + +void AdvancedConnector::Proxy::setHttpConnect(const QString &host, Q_UINT16 port) +{ + t = HttpConnect; + v_host = host; + v_port = port; +} + +void AdvancedConnector::Proxy::setHttpPoll(const QString &host, Q_UINT16 port, const QString &url) +{ + t = HttpPoll; + v_host = host; + v_port = port; + v_url = url; +} + +void AdvancedConnector::Proxy::setSocks(const QString &host, Q_UINT16 port) +{ + t = Socks; + v_host = host; + v_port = port; +} + +void AdvancedConnector::Proxy::setUserPass(const QString &user, const QString &pass) +{ + v_user = user; + v_pass = pass; +} + +void AdvancedConnector::Proxy::setPollInterval(int secs) +{ + v_poll = secs; +} + + +//---------------------------------------------------------------------------- +// AdvancedConnector +//---------------------------------------------------------------------------- +enum { Idle, Connecting, Connected }; +class AdvancedConnector::Private +{ +public: + int mode; + ByteStream *bs; +#ifdef NO_NDNS + QDns *qdns; +#else + NDns dns; +#endif + SrvResolver srv; + + QString server; + QString opt_host; + int opt_port; + bool opt_probe, opt_ssl; + Proxy proxy; + + QString host; + int port; + QValueList<QDns::Server> servers; + int errorCode; + + bool multi, using_srv; + bool will_be_ssl; + int probe_mode; + + bool aaaa; + SafeDelete sd; +}; + +AdvancedConnector::AdvancedConnector(QObject *parent) +:Connector(parent) +{ + d = new Private; + d->bs = 0; +#ifdef NO_NDNS + d->qdns = 0; +#else + connect(&d->dns, SIGNAL(resultsReady()), SLOT(dns_done())); +#endif + connect(&d->srv, SIGNAL(resultsReady()), SLOT(srv_done())); + d->opt_probe = false; + d->opt_ssl = false; + cleanup(); + d->errorCode = 0; +} + +AdvancedConnector::~AdvancedConnector() +{ + cleanup(); + delete d; +} + +void AdvancedConnector::cleanup() +{ + d->mode = Idle; + + // stop any dns +#ifdef NO_NDNS + if(d->qdns) { + d->qdns->disconnect(this); + d->qdns->deleteLater(); + //d->sd.deleteLater(d->qdns); + d->qdns = 0; + } +#else + if(d->dns.isBusy()) + d->dns.stop(); +#endif + if(d->srv.isBusy()) + d->srv.stop(); + + // destroy the bytestream, if there is one + delete d->bs; + d->bs = 0; + + d->multi = false; + d->using_srv = false; + d->will_be_ssl = false; + d->probe_mode = -1; + + setUseSSL(false); + setPeerAddressNone(); +} + +void AdvancedConnector::setProxy(const Proxy &proxy) +{ + if(d->mode != Idle) + return; + d->proxy = proxy; +} + +void AdvancedConnector::setOptHostPort(const QString &host, Q_UINT16 _port) +{ + if(d->mode != Idle) + return; + d->opt_host = host; + d->opt_port = _port; +} + +void AdvancedConnector::setOptProbe(bool b) +{ + if(d->mode != Idle) + return; + d->opt_probe = b; +} + +void AdvancedConnector::setOptSSL(bool b) +{ + if(d->mode != Idle) + return; + d->opt_ssl = b; +} + +void AdvancedConnector::connectToServer(const QString &server) +{ + if(d->mode != Idle) + return; + if(server.isEmpty()) + return; + + d->errorCode = 0; + d->server = server; + d->mode = Connecting; + d->aaaa = true; + + if(d->proxy.type() == Proxy::HttpPoll) { + // need SHA1 here + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + HttpPoll *s = new HttpPoll; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(syncStarted()), SLOT(http_syncStarted())); + connect(s, SIGNAL(syncFinished()), SLOT(http_syncFinished())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + if(!d->proxy.user().isEmpty()) + s->setAuth(d->proxy.user(), d->proxy.pass()); + s->setPollInterval(d->proxy.pollInterval()); + + if(d->proxy.host().isEmpty()) + s->connectToUrl(d->proxy.url()); + else + s->connectToHost(d->proxy.host(), d->proxy.port(), d->proxy.url()); + } + else { + if(!d->opt_host.isEmpty()) { + d->host = d->opt_host; + d->port = d->opt_port; + do_resolve(); + } + else { + d->multi = true; + + QGuardedPtr<QObject> self = this; + srvLookup(d->server); + if(!self) + return; + + d->srv.resolveSrvOnly(d->server, "xmpp-client", "tcp"); + } + } +} + +void AdvancedConnector::changePollInterval(int secs) +{ + if(d->bs && (d->bs->inherits("XMPP::HttpPoll") || d->bs->inherits("HttpPoll"))) { + HttpPoll *s = static_cast<HttpPoll*>(d->bs); + s->setPollInterval(secs); + } +} + +ByteStream *AdvancedConnector::stream() const +{ + if(d->mode == Connected) + return d->bs; + else + return 0; +} + +void AdvancedConnector::done() +{ + cleanup(); +} + +int AdvancedConnector::errorCode() const +{ + return d->errorCode; +} + +void AdvancedConnector::do_resolve() +{ +#ifdef NO_NDNS + printf("resolving (aaaa=%d)\n", d->aaaa); + d->qdns = new QDns; + connect(d->qdns, SIGNAL(resultsReady()), SLOT(dns_done())); + if(d->aaaa) + d->qdns->setRecordType(QDns::Aaaa); // IPv6 + else + d->qdns->setRecordType(QDns::A); // IPv4 + d->qdns->setLabel(d->host); +#else + d->dns.resolve(d->host); +#endif +} + +void AdvancedConnector::dns_done() +{ + bool failed = false; + QHostAddress addr; + +#ifdef NO_NDNS + //if(!d->qdns) + // return; + + // apparently we sometimes get this signal even though the results aren' t ready + //if(d->qdns->isWorking()) + // return; + + //SafeDeleteLock s(&d->sd); + + // grab the address list and destroy the qdns object + QValueList<QHostAddress> list = d->qdns->addresses(); + d->qdns->disconnect(this); + d->qdns->deleteLater(); + //d->sd.deleteLater(d->qdns); + d->qdns = 0; + + if(list.isEmpty()) { + if(d->aaaa) { + d->aaaa = false; + do_resolve(); + return; + } + //do_resolve(); + //return; + failed = true; + } + else + addr = list.first(); +#else + if(d->dns.result() == 0) + failed = true; + else + addr = QHostAddress(d->dns.result()); +#endif + + if(failed) { +#ifdef XMPP_DEBUG + printf("dns1\n"); +#endif + // using proxy? then try the unresolved host through the proxy + if(d->proxy.type() != Proxy::None) { +#ifdef XMPP_DEBUG + printf("dns1.1\n"); +#endif + do_connect(); + } + else if(d->using_srv) { +#ifdef XMPP_DEBUG + printf("dns1.2\n"); +#endif + if(d->servers.isEmpty()) { +#ifdef XMPP_DEBUG + printf("dns1.2.1\n"); +#endif + cleanup(); + d->errorCode = ErrConnectionRefused; + error(); + } + else { +#ifdef XMPP_DEBUG + printf("dns1.2.2\n"); +#endif + tryNextSrv(); + return; + } + } + else { +#ifdef XMPP_DEBUG + printf("dns1.3\n"); +#endif + cleanup(); + d->errorCode = ErrHostNotFound; + error(); + } + } + else { +#ifdef XMPP_DEBUG + printf("dns2\n"); +#endif + d->host = addr.toString(); + do_connect(); + } +} + +void AdvancedConnector::do_connect() +{ +#ifdef XMPP_DEBUG + printf("trying %s:%d\n", d->host.latin1(), d->port); +#endif + int t = d->proxy.type(); + if(t == Proxy::None) { +#ifdef XMPP_DEBUG + printf("do_connect1\n"); +#endif + BSocket *s = new BSocket; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + s->connectToHost(d->host, d->port); + } + else if(t == Proxy::HttpConnect) { +#ifdef XMPP_DEBUG + printf("do_connect2\n"); +#endif + HttpConnect *s = new HttpConnect; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + if(!d->proxy.user().isEmpty()) + s->setAuth(d->proxy.user(), d->proxy.pass()); + s->connectToHost(d->proxy.host(), d->proxy.port(), d->host, d->port); + } + else if(t == Proxy::Socks) { +#ifdef XMPP_DEBUG + printf("do_connect3\n"); +#endif + SocksClient *s = new SocksClient; + d->bs = s; + connect(s, SIGNAL(connected()), SLOT(bs_connected())); + connect(s, SIGNAL(error(int)), SLOT(bs_error(int))); + if(!d->proxy.user().isEmpty()) + s->setAuth(d->proxy.user(), d->proxy.pass()); + s->connectToHost(d->proxy.host(), d->proxy.port(), d->host, d->port); + } +} + +void AdvancedConnector::tryNextSrv() +{ +#ifdef XMPP_DEBUG + printf("trying next srv\n"); +#endif + d->host = d->servers.first().name; + d->port = d->servers.first().port; + d->servers.remove(d->servers.begin()); + do_resolve(); +} + +void AdvancedConnector::srv_done() +{ + QGuardedPtr<QObject> self = this; +#ifdef XMPP_DEBUG + printf("srv_done1\n"); +#endif + d->servers = d->srv.servers(); + if(d->servers.isEmpty()) { + srvResult(false); + if(!self) + return; + +#ifdef XMPP_DEBUG + printf("srv_done1.1\n"); +#endif + // fall back to A record + d->using_srv = false; + d->host = d->server; + if(d->opt_probe) { +#ifdef XMPP_DEBUG + printf("srv_done1.1.1\n"); +#endif + d->probe_mode = 0; + d->port = 5223; + d->will_be_ssl = true; + } + else { +#ifdef XMPP_DEBUG + printf("srv_done1.1.2\n"); +#endif + d->probe_mode = 1; + d->port = 5222; + } + do_resolve(); + return; + } + + srvResult(true); + if(!self) + return; + + d->using_srv = true; + tryNextSrv(); +} + +void AdvancedConnector::bs_connected() +{ + if(d->proxy.type() == Proxy::None) { + QHostAddress h = (static_cast<BSocket*>(d->bs))->peerAddress(); + int p = (static_cast<BSocket*>(d->bs))->peerPort(); + setPeerAddress(h, p); + } + + // only allow ssl override if proxy==poll or host:port + if((d->proxy.type() == Proxy::HttpPoll || !d->opt_host.isEmpty()) && d->opt_ssl) + setUseSSL(true); + else if(d->will_be_ssl) + setUseSSL(true); + + d->mode = Connected; + connected(); +} + +void AdvancedConnector::bs_error(int x) +{ + if(d->mode == Connected) { + d->errorCode = ErrStream; + error(); + return; + } + + bool proxyError = false; + int err = ErrConnectionRefused; + int t = d->proxy.type(); + +#ifdef XMPP_DEBUG + printf("bse1\n"); +#endif + + // figure out the error + if(t == Proxy::None) { + if(x == BSocket::ErrHostNotFound) + err = ErrHostNotFound; + else + err = ErrConnectionRefused; + } + else if(t == Proxy::HttpConnect) { + if(x == HttpConnect::ErrConnectionRefused) + err = ErrConnectionRefused; + else if(x == HttpConnect::ErrHostNotFound) + err = ErrHostNotFound; + else { + proxyError = true; + if(x == HttpConnect::ErrProxyAuth) + err = ErrProxyAuth; + else if(x == HttpConnect::ErrProxyNeg) + err = ErrProxyNeg; + else + err = ErrProxyConnect; + } + } + else if(t == Proxy::HttpPoll) { + if(x == HttpPoll::ErrConnectionRefused) + err = ErrConnectionRefused; + else if(x == HttpPoll::ErrHostNotFound) + err = ErrHostNotFound; + else { + proxyError = true; + if(x == HttpPoll::ErrProxyAuth) + err = ErrProxyAuth; + else if(x == HttpPoll::ErrProxyNeg) + err = ErrProxyNeg; + else + err = ErrProxyConnect; + } + } + else if(t == Proxy::Socks) { + if(x == SocksClient::ErrConnectionRefused) + err = ErrConnectionRefused; + else if(x == SocksClient::ErrHostNotFound) + err = ErrHostNotFound; + else { + proxyError = true; + if(x == SocksClient::ErrProxyAuth) + err = ErrProxyAuth; + else if(x == SocksClient::ErrProxyNeg) + err = ErrProxyNeg; + else + err = ErrProxyConnect; + } + } + + // no-multi or proxy error means we quit + if(!d->multi || proxyError) { + cleanup(); + d->errorCode = err; + error(); + return; + } + + if(d->using_srv && !d->servers.isEmpty()) { +#ifdef XMPP_DEBUG + printf("bse1.1\n"); +#endif + tryNextSrv(); + } + else if(!d->using_srv && d->opt_probe && d->probe_mode == 0) { +#ifdef XMPP_DEBUG + printf("bse1.2\n"); +#endif + d->probe_mode = 1; + d->port = 5222; + d->will_be_ssl = false; + do_connect(); + } + else { +#ifdef XMPP_DEBUG + printf("bse1.3\n"); +#endif + cleanup(); + d->errorCode = ErrConnectionRefused; + error(); + } +} + +void AdvancedConnector::http_syncStarted() +{ + httpSyncStarted(); +} + +void AdvancedConnector::http_syncFinished() +{ + httpSyncFinished(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.cpp new file mode 100644 index 00000000..4d7f9e41 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.cpp @@ -0,0 +1,670 @@ +/* + * hash.cpp - hashing functions for SHA1 and MD5 + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"hash.h" + +namespace XMPP +{ + +static bool bigEndian; +static bool haveEndian = false; + +static void ensureEndian() +{ + if(!haveEndian) { + haveEndian = true; + int wordSize; + qSysInfo(&wordSize, &bigEndian); + } +} + +//---------------------------------------------------------------------------- +// MD5 +//---------------------------------------------------------------------------- + +/* NOTE: the following code was modified to not need BYTE_ORDER -- Justin */ + +/* + 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$ */ +/* + 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 + <ghost@aladdin.com>. 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 <string.h> + 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 <stdio.h> 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. + */ + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef Q_UINT8 md5_byte_t; /* 8-bit byte */ +typedef Q_UINT32 md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +/* Initialize the algorithm. */ +void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + +/* Finish the message and return the digest. */ +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#define T_MASK ((md5_word_t)~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) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; + + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; + + { + if(bigEndian) + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + + X = xbuf; /* (dynamic only) */ + + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } + else /* dynamic big-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } + } + +#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.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t 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_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} + + +//---------------------------------------------------------------------------- +// SHA1 - from a public domain implementation by Steve Reid (steve@edmweb.com) +//---------------------------------------------------------------------------- + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15]^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +struct SHA1_CONTEXT +{ + Q_UINT32 state[5]; + Q_UINT32 count[2]; + unsigned char buffer[64]; +}; + +typedef union { + unsigned char c[64]; + Q_UINT32 l[16]; +} CHAR64LONG16; + +class SHA1Context : public QCA_HashContext +{ +public: + SHA1_CONTEXT _context; + CHAR64LONG16* block; + + SHA1Context() + { + reset(); + } + + QCA_HashContext *clone() + { + return new SHA1Context(*this); + } + + void reset() + { + sha1_init(&_context); + } + + void update(const char *in, unsigned int len) + { + sha1_update(&_context, (unsigned char *)in, (unsigned int)len); + } + + void final(QByteArray *out) + { + QByteArray b(20); + sha1_final((unsigned char *)b.data(), &_context); + *out = b; + } + + unsigned long blk0(Q_UINT32 i) + { + if(bigEndian) + return block->l[i]; + else + return (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) | (rol(block->l[i],8)&0x00FF00FF)); + } + + // Hash a single 512-bit block. This is the core of the algorithm. + void transform(Q_UINT32 state[5], unsigned char buffer[64]) + { + Q_UINT32 a, b, c, d, e; + + block = (CHAR64LONG16*)buffer; + + // Copy context->state[] to working vars + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + // 4 rounds of 20 operations each. Loop unrolled. + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + // Add the working vars back into context.state[] + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + // Wipe variables + a = b = c = d = e = 0; + } + + // SHA1Init - Initialize new context + void sha1_init(SHA1_CONTEXT* context) + { + // SHA1 initialization constants + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; + } + + // Run your data through this + void sha1_update(SHA1_CONTEXT* context, unsigned char* data, Q_UINT32 len) + { + Q_UINT32 i, j; + + j = (context->count[0] >> 3) & 63; + if((context->count[0] += len << 3) < (len << 3)) + context->count[1]++; + + context->count[1] += (len >> 29); + + if((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); + } + + // Add padding and return the message digest + void sha1_final(unsigned char digest[20], SHA1_CONTEXT* context) + { + Q_UINT32 i, j; + unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); // Endian independent + } + sha1_update(context, (unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) { + sha1_update(context, (unsigned char *)"\0", 1); + } + sha1_update(context, finalcount, 8); // Should cause a transform() + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + + // Wipe variables + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); + } +}; + +class MD5Context : public QCA_HashContext +{ +public: + MD5Context() + { + reset(); + } + + QCA_HashContext *clone() + { + return new MD5Context(*this); + } + + void reset() + { + md5_init(&md5); + } + + void update(const char *in, unsigned int len) + { + md5_append(&md5, (const md5_byte_t *)in, len); + } + + void final(QByteArray *out) + { + QByteArray b(16); + md5_finish(&md5, (md5_byte_t *)b.data()); + *out = b; + } + + md5_state_t md5; +}; + +class HashProvider : public QCAProvider +{ +public: + HashProvider() {} + ~HashProvider() {} + + void init() + { + ensureEndian(); + } + + int qcaVersion() const + { + return QCA_PLUGIN_VERSION; + } + + int capabilities() const + { + return (QCA::CAP_SHA1 | QCA::CAP_MD5); + } + + void *context(int cap) + { + if(cap == QCA::CAP_SHA1) + return new SHA1Context; + if(cap == QCA::CAP_MD5) + return new MD5Context; + return 0; + } +}; + +QCAProvider *createProviderHash() +{ + return (new HashProvider); +} + +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.h new file mode 100644 index 00000000..a4d2eea8 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/hash.h @@ -0,0 +1,31 @@ +/* + * hash.h - hashing functions for SHA1 and MD5 + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef HASH_H +#define HASH_H + +#include"qcaprovider.h" + +namespace XMPP +{ + QCAProvider *createProviderHash(); +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/jid.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/jid.cpp new file mode 100644 index 00000000..29932513 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/jid.cpp @@ -0,0 +1,409 @@ +/* + * jid.cpp - class for verifying and manipulating Jabber IDs + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp.h" + +#include<qdict.h> +#include<stringprep.h> + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// StringPrepCache +//---------------------------------------------------------------------------- +class StringPrepCache +{ +public: + static bool nameprep(const QString &in, int maxbytes, QString *out) + { + if(in.isEmpty()) + { + if(out) + *out = QString(); + return true; + } + + StringPrepCache *that = get_instance(); + + Result *r = that->nameprep_table.find(in); + if(r) + { + if(!r->norm) + return false; + if(out) + *out = *(r->norm); + return true; + } + + QCString cs = in.utf8(); + cs.resize(maxbytes); + if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_nameprep) != 0) + { + that->nameprep_table.insert(in, new Result); + return false; + } + + QString norm = QString::fromUtf8(cs); + that->nameprep_table.insert(in, new Result(norm)); + if(out) + *out = norm; + return true; + } + + static bool nodeprep(const QString &in, int maxbytes, QString *out) + { + if(in.isEmpty()) + { + if(out) + *out = QString(); + return true; + } + + StringPrepCache *that = get_instance(); + + Result *r = that->nodeprep_table.find(in); + if(r) + { + if(!r->norm) + return false; + if(out) + *out = *(r->norm); + return true; + } + + QCString cs = in.utf8(); + cs.resize(maxbytes); + if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_xmpp_nodeprep) != 0) + { + that->nodeprep_table.insert(in, new Result); + return false; + } + + QString norm = QString::fromUtf8(cs); + that->nodeprep_table.insert(in, new Result(norm)); + if(out) + *out = norm; + return true; + } + + static bool resourceprep(const QString &in, int maxbytes, QString *out) + { + if(in.isEmpty()) + { + if(out) + *out = QString(); + return true; + } + + StringPrepCache *that = get_instance(); + + Result *r = that->resourceprep_table.find(in); + if(r) + { + if(!r->norm) + return false; + if(out) + *out = *(r->norm); + return true; + } + + QCString cs = in.utf8(); + cs.resize(maxbytes); + if(stringprep(cs.data(), maxbytes, (Stringprep_profile_flags)0, stringprep_xmpp_resourceprep) != 0) + { + that->resourceprep_table.insert(in, new Result); + return false; + } + + QString norm = QString::fromUtf8(cs); + that->resourceprep_table.insert(in, new Result(norm)); + if(out) + *out = norm; + return true; + } + +private: + class Result + { + public: + QString *norm; + + Result() : norm(0) + { + } + + Result(const QString &s) : norm(new QString(s)) + { + } + + ~Result() + { + delete norm; + } + }; + + QDict<Result> nameprep_table; + QDict<Result> nodeprep_table; + QDict<Result> resourceprep_table; + + static StringPrepCache *instance; + + static StringPrepCache *get_instance() + { + if(!instance) + instance = new StringPrepCache; + return instance; + } + + StringPrepCache() + { + nameprep_table.setAutoDelete(true); + nodeprep_table.setAutoDelete(true); + resourceprep_table.setAutoDelete(true); + } +}; + +StringPrepCache *StringPrepCache::instance = 0; + +//---------------------------------------------------------------------------- +// Jid +//---------------------------------------------------------------------------- +Jid::Jid() +{ + valid = false; +} + +Jid::~Jid() +{ +} + +Jid::Jid(const QString &s) +{ + set(s); +} + +Jid::Jid(const char *s) +{ + set(QString(s)); +} + +Jid & Jid::operator=(const QString &s) +{ + set(s); + return *this; +} + +Jid & Jid::operator=(const char *s) +{ + set(QString(s)); + return *this; +} + +void Jid::reset() +{ + f = QString(); + b = QString(); + d = QString(); + n = QString(); + r = QString(); + valid = false; +} + +void Jid::update() +{ + // build 'bare' and 'full' jids + if(n.isEmpty()) + b = d; + else + b = n + '@' + d; + + b=b.lower(); // JID are not case sensitive + + if(r.isEmpty()) + f = b; + else + f = b + '/' + r; + if(f.isEmpty()) + valid = false; +} + +void Jid::set(const QString &s) +{ + QString rest, domain, node, resource; + QString norm_domain, norm_node, norm_resource; + int x = s.find('/'); + if(x != -1) { + rest = s.mid(0, x); + resource = s.mid(x+1); + } + else { + rest = s; + resource = QString(); + } + if(!validResource(resource, &norm_resource)) { + reset(); + return; + } + + x = rest.find('@'); + if(x != -1) { + node = rest.mid(0, x); + domain = rest.mid(x+1); + } + else { + node = QString(); + domain = rest; + } + if(!validDomain(domain, &norm_domain) || !validNode(node, &norm_node)) { + reset(); + return; + } + + valid = true; + d = norm_domain; + n = norm_node; + r = norm_resource; + update(); +} + +void Jid::set(const QString &domain, const QString &node, const QString &resource) +{ + QString norm_domain, norm_node, norm_resource; + if(!validDomain(domain, &norm_domain) || !validNode(node, &norm_node) || !validResource(resource, &norm_resource)) { + reset(); + return; + } + valid = true; + d = norm_domain; + n = norm_node; + r = norm_resource; + update(); +} + +void Jid::setDomain(const QString &s) +{ + if(!valid) + return; + QString norm; + if(!validDomain(s, &norm)) { + reset(); + return; + } + d = norm; + update(); +} + +void Jid::setNode(const QString &s) +{ + if(!valid) + return; + QString norm; + if(!validNode(s, &norm)) { + reset(); + return; + } + n = norm; + update(); +} + +void Jid::setResource(const QString &s) +{ + if(!valid) + return; + QString norm; + if(!validResource(s, &norm)) { + reset(); + return; + } + r = norm; + update(); +} + +Jid Jid::withNode(const QString &s) const +{ + Jid j = *this; + j.setNode(s); + return j; +} + +Jid Jid::withResource(const QString &s) const +{ + Jid j = *this; + j.setResource(s); + return j; +} + +bool Jid::isValid() const +{ + return valid; +} + +bool Jid::isEmpty() const +{ + return f.isEmpty(); +} + +bool Jid::compare(const Jid &a, bool compareRes) const +{ + // only compare valid jids + if(!valid || !a.valid) + return false; + + if(compareRes ? (f != a.f) : (b != a.b)) + return false; + + return true; +} + +bool Jid::validDomain(const QString &s, QString *norm) +{ + /*QCString cs = s.utf8(); + cs.resize(1024); + if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_nameprep) != 0) + return false; + if(norm) + *norm = QString::fromUtf8(cs); + return true;*/ + return StringPrepCache::nameprep(s, 1024, norm); +} + +bool Jid::validNode(const QString &s, QString *norm) +{ + /*QCString cs = s.utf8(); + cs.resize(1024); + if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_xmpp_nodeprep) != 0) + return false; + if(norm) + *norm = QString::fromUtf8(cs); + return true;*/ + return StringPrepCache::nodeprep(s, 1024, norm); +} + +bool Jid::validResource(const QString &s, QString *norm) +{ + /*QCString cs = s.utf8(); + cs.resize(1024); + if(stringprep(cs.data(), 1024, (Stringprep_profile_flags)0, stringprep_xmpp_resourceprep) != 0) + return false; + if(norm) + *norm = QString::fromUtf8(cs); + return true;*/ + return StringPrepCache::resourceprep(s, 1024, norm); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.cpp new file mode 100644 index 00000000..e1a64532 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.cpp @@ -0,0 +1,798 @@ +/* + * parser.cpp - parse an XMPP "document" + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + TODO: + + For XMPP::Parser to be "perfect", some things must be solved/changed in the + Qt library: + + - Fix weird QDomElement::haveAttributeNS() bug (patch submitted to + Trolltech on Aug 31st, 2003). + - Fix weird behavior in QXmlSimpleReader of reporting endElement() when + the '/' character of a self-closing tag is reached, instead of when + the final '>' is reached. + - Fix incremental parsing bugs in QXmlSimpleReader. At the moment, the + only bug I've found is related to attribute parsing, but there might + be more (search for '###' in $QTDIR/src/xml/qxml.cpp). + + We have workarounds for all of the above problems in the code below. + + - Deal with the <?xml?> processing instruction as an event type, so that we + can feed it back to the application properly. Right now it is completely + untrackable and is simply tacked into the first event's actualString. We + can't easily do this because QXmlSimpleReader eats an extra byte beyond + the processing instruction before reporting it. + + - Make QXmlInputSource capable of accepting data incrementally, to ensure + proper text encoding detection and processing over a network. This is + technically not a bug, as we have our own subclass below to do it, but + it would be nice if Qt had this already. +*/ + +#include"parser.h" + +#include<qtextcodec.h> +#include<qptrlist.h> +#include<string.h> + +using namespace XMPP; + +static bool qt_bug_check = false; +static bool qt_bug_have; + +//---------------------------------------------------------------------------- +// StreamInput +//---------------------------------------------------------------------------- +class StreamInput : public QXmlInputSource +{ +public: + StreamInput() + { + dec = 0; + reset(); + } + + ~StreamInput() + { + delete dec; + } + + void reset() + { + delete dec; + dec = 0; + in.resize(0); + out = ""; + at = 0; + paused = false; + mightChangeEncoding = true; + checkBad = true; + last = QChar(); + v_encoding = ""; + resetLastData(); + } + + void resetLastData() + { + last_string = ""; + } + + QString lastString() const + { + return last_string; + } + + void appendData(const QByteArray &a) + { + int oldsize = in.size(); + in.resize(oldsize + a.size()); + memcpy(in.data() + oldsize, a.data(), a.size()); + processBuf(); + } + + QChar lastRead() + { + return last; + } + + QChar next() + { + if(paused) + return EndOfData; + else + return readNext(); + } + + // NOTE: setting 'peek' to true allows the same char to be read again, + // however this still advances the internal byte processing. + QChar readNext(bool peek=false) + { + QChar c; + if(mightChangeEncoding) + c = EndOfData; + else { + if(out.isEmpty()) { + QString s; + if(!tryExtractPart(&s)) + c = EndOfData; + else { + out = s; + c = out[0]; + } + } + else + c = out[0]; + if(!peek) + out.remove(0, 1); + } + if(c == EndOfData) { +#ifdef XMPP_PARSER_DEBUG + printf("next() = EOD\n"); +#endif + } + else { +#ifdef XMPP_PARSER_DEBUG + printf("next() = [%c]\n", c.latin1()); +#endif + last = c; + } + + return c; + } + + QByteArray unprocessed() const + { + QByteArray a(in.size() - at); + memcpy(a.data(), in.data() + at, a.size()); + return a; + } + + void pause(bool b) + { + paused = b; + } + + bool isPaused() + { + return paused; + } + + QString encoding() const + { + return v_encoding; + } + +private: + QTextDecoder *dec; + QByteArray in; + QString out; + int at; + bool paused; + bool mightChangeEncoding; + QChar last; + QString v_encoding; + QString last_string; + bool checkBad; + + void processBuf() + { +#ifdef XMPP_PARSER_DEBUG + printf("processing. size=%d, at=%d\n", in.size(), at); +#endif + if(!dec) { + QTextCodec *codec = 0; + uchar *p = (uchar *)in.data() + at; + int size = in.size() - at; + + // do we have enough information to determine the encoding? + if(size == 0) + return; + bool utf16 = false; + if(p[0] == 0xfe || p[0] == 0xff) { + // probably going to be a UTF-16 byte order mark + if(size < 2) + return; + if((p[0] == 0xfe && p[1] == 0xff) || (p[0] == 0xff && p[1] == 0xfe)) { + // ok it is UTF-16 + utf16 = true; + } + } + if(utf16) + codec = QTextCodec::codecForMib(1000); // UTF-16 + else + codec = QTextCodec::codecForMib(106); // UTF-8 + + v_encoding = codec->name(); + dec = codec->makeDecoder(); + + // for utf16, put in the byte order mark + if(utf16) { + out += dec->toUnicode((const char *)p, 2); + at += 2; + } + } + + if(mightChangeEncoding) { + while(1) { + int n = out.find('<'); + if(n != -1) { + // we need a closing bracket + int n2 = out.find('>', n); + if(n2 != -1) { + ++n2; + QString h = out.mid(n, n2-n); + QString enc = processXmlHeader(h); + QTextCodec *codec = 0; + if(!enc.isEmpty()) + codec = QTextCodec::codecForName(enc.latin1()); + + // changing codecs + if(codec) { + v_encoding = codec->name(); + delete dec; + dec = codec->makeDecoder(); + } + mightChangeEncoding = false; + out.truncate(0); + at = 0; + resetLastData(); + break; + } + } + QString s; + if(!tryExtractPart(&s)) + break; + if(checkBad && checkForBadChars(s)) { + // go to the parser + mightChangeEncoding = false; + out.truncate(0); + at = 0; + resetLastData(); + break; + } + out += s; + } + } + } + + QString processXmlHeader(const QString &h) + { + if(h.left(5) != "<?xml") + return ""; + + int endPos = h.find(">"); + int startPos = h.find("encoding"); + if(startPos < endPos && startPos != -1) { + QString encoding; + do { + startPos++; + if(startPos > endPos) { + return ""; + } + } while(h[startPos] != '"' && h[startPos] != '\''); + startPos++; + while(h[startPos] != '"' && h[startPos] != '\'') { + encoding += h[startPos]; + startPos++; + if(startPos > endPos) { + return ""; + } + } + return encoding; + } + else + return ""; + } + + bool tryExtractPart(QString *s) + { + int size = in.size() - at; + if(size == 0) + return false; + uchar *p = (uchar *)in.data() + at; + QString nextChars; + while(1) { + nextChars = dec->toUnicode((const char *)p, 1); + ++p; + ++at; + if(!nextChars.isEmpty()) + break; + if(at == (int)in.size()) + return false; + } + last_string += nextChars; + *s = nextChars; + + // free processed data? + if(at >= 1024) { + char *p = in.data(); + int size = in.size() - at; + memmove(p, p + at, size); + in.resize(size); + at = 0; + } + + return true; + } + + bool checkForBadChars(const QString &s) + { + int len = s.find('<'); + if(len == -1) + len = s.length(); + else + checkBad = false; + for(int n = 0; n < len; ++n) { + if(!s.at(n).isSpace()) + return true; + } + return false; + } +}; + + +//---------------------------------------------------------------------------- +// ParserHandler +//---------------------------------------------------------------------------- +namespace XMPP +{ + class ParserHandler : public QXmlDefaultHandler + { + public: + ParserHandler(StreamInput *_in, QDomDocument *_doc) + { + in = _in; + doc = _doc; + needMore = false; + } + + ~ParserHandler() + { + eventList.setAutoDelete(true); + eventList.clear(); + } + + bool startDocument() + { + depth = 0; + return true; + } + + bool endDocument() + { + return true; + } + + bool startPrefixMapping(const QString &prefix, const QString &uri) + { + if(depth == 0) { + nsnames += prefix; + nsvalues += uri; + } + return true; + } + + bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts) + { + if(depth == 0) { + Parser::Event *e = new Parser::Event; + QXmlAttributes a; + for(int n = 0; n < atts.length(); ++n) { + QString uri = atts.uri(n); + QString ln = atts.localName(n); + if(a.index(uri, ln) == -1) + a.append(atts.qName(n), uri, ln, atts.value(n)); + } + e->setDocumentOpen(namespaceURI, localName, qName, a, nsnames, nsvalues); + nsnames.clear(); + nsvalues.clear(); + e->setActualString(in->lastString()); + + in->resetLastData(); + eventList.append(e); + in->pause(true); + } + else { + QDomElement e = doc->createElementNS(namespaceURI, qName); + for(int n = 0; n < atts.length(); ++n) { + QString uri = atts.uri(n); + QString ln = atts.localName(n); + bool have; + if(!uri.isEmpty()) { + have = e.hasAttributeNS(uri, ln); + if(qt_bug_have) + have = !have; + } + else + have = e.hasAttribute(ln); + if(!have) + e.setAttributeNS(uri, atts.qName(n), atts.value(n)); + } + + if(depth == 1) { + elem = e; + current = e; + } + else { + current.appendChild(e); + current = e; + } + } + ++depth; + return true; + } + + bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName) + { + --depth; + if(depth == 0) { + Parser::Event *e = new Parser::Event; + e->setDocumentClose(namespaceURI, localName, qName); + e->setActualString(in->lastString()); + in->resetLastData(); + eventList.append(e); + in->pause(true); + } + else { + // done with a depth 1 element? + if(depth == 1) { + Parser::Event *e = new Parser::Event; + e->setElement(elem); + e->setActualString(in->lastString()); + in->resetLastData(); + eventList.append(e); + in->pause(true); + + elem = QDomElement(); + current = QDomElement(); + } + else + current = current.parentNode().toElement(); + } + + if(in->lastRead() == '/') + checkNeedMore(); + + return true; + } + + bool characters(const QString &str) + { + if(depth >= 1) { + QString content = str; + if(content.isEmpty()) + return true; + + if(!current.isNull()) { + QDomText text = doc->createTextNode(content); + current.appendChild(text); + } + } + return true; + } + + /*bool processingInstruction(const QString &target, const QString &data) + { + printf("Processing: [%s], [%s]\n", target.latin1(), data.latin1()); + in->resetLastData(); + return true; + }*/ + + void checkNeedMore() + { + // Here we will work around QXmlSimpleReader strangeness and self-closing tags. + // The problem is that endElement() is called when the '/' is read, not when + // the final '>' is read. This is a potential problem when obtaining unprocessed + // bytes from StreamInput after this event, as the '>' character will end up + // in the unprocessed chunk. To work around this, we need to advance StreamInput's + // internal byte processing, but not the xml character data. This way, the '>' + // will get processed and will no longer be in the unprocessed return, but + // QXmlSimpleReader can still read it. To do this, we call StreamInput::readNext + // with 'peek' mode. + QChar c = in->readNext(true); // peek + if(c == QXmlInputSource::EndOfData) { + needMore = true; + } + else { + // We'll assume the next char is a '>'. If it isn't, then + // QXmlSimpleReader will deal with that problem on the next + // parse. We don't need to take any action here. + needMore = false; + + // there should have been a pending event + Parser::Event *e = eventList.getFirst(); + if(e) { + e->setActualString(e->actualString() + '>'); + in->resetLastData(); + } + } + } + + Parser::Event *takeEvent() + { + if(needMore) + return 0; + if(eventList.isEmpty()) + return 0; + + Parser::Event *e = eventList.getFirst(); + eventList.removeRef(e); + in->pause(false); + return e; + } + + StreamInput *in; + QDomDocument *doc; + int depth; + QStringList nsnames, nsvalues; + QDomElement elem, current; + QPtrList<Parser::Event> eventList; + bool needMore; + }; +} + + +//---------------------------------------------------------------------------- +// Event +//---------------------------------------------------------------------------- +class Parser::Event::Private +{ +public: + int type; + QString ns, ln, qn; + QXmlAttributes a; + QDomElement e; + QString str; + QStringList nsnames, nsvalues; +}; + +Parser::Event::Event() +{ + d = 0; +} + +Parser::Event::Event(const Event &from) +{ + d = 0; + *this = from; +} + +Parser::Event & Parser::Event::operator=(const Event &from) +{ + delete d; + d = 0; + if(from.d) + d = new Private(*from.d); + return *this; +} + +Parser::Event::~Event() +{ + delete d; +} + +bool Parser::Event::isNull() const +{ + return (d ? false: true); +} + +int Parser::Event::type() const +{ + if(isNull()) + return -1; + return d->type; +} + +QString Parser::Event::nsprefix(const QString &s) const +{ + QStringList::ConstIterator it = d->nsnames.begin(); + QStringList::ConstIterator it2 = d->nsvalues.begin(); + for(; it != d->nsnames.end(); ++it) { + if((*it) == s) + return (*it2); + ++it2; + } + return QString::null; +} + +QString Parser::Event::namespaceURI() const +{ + return d->ns; +} + +QString Parser::Event::localName() const +{ + return d->ln; +} + +QString Parser::Event::qName() const +{ + return d->qn; +} + +QXmlAttributes Parser::Event::atts() const +{ + return d->a; +} + +QString Parser::Event::actualString() const +{ + return d->str; +} + +QDomElement Parser::Event::element() const +{ + return d->e; +} + +void Parser::Event::setDocumentOpen(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts, const QStringList &nsnames, const QStringList &nsvalues) +{ + if(!d) + d = new Private; + d->type = DocumentOpen; + d->ns = namespaceURI; + d->ln = localName; + d->qn = qName; + d->a = atts; + d->nsnames = nsnames; + d->nsvalues = nsvalues; +} + +void Parser::Event::setDocumentClose(const QString &namespaceURI, const QString &localName, const QString &qName) +{ + if(!d) + d = new Private; + d->type = DocumentClose; + d->ns = namespaceURI; + d->ln = localName; + d->qn = qName; +} + +void Parser::Event::setElement(const QDomElement &elem) +{ + if(!d) + d = new Private; + d->type = Element; + d->e = elem; +} + +void Parser::Event::setError() +{ + if(!d) + d = new Private; + d->type = Error; +} + +void Parser::Event::setActualString(const QString &str) +{ + d->str = str; +} + +//---------------------------------------------------------------------------- +// Parser +//---------------------------------------------------------------------------- +class Parser::Private +{ +public: + Private() + { + doc = 0; + in = 0; + handler = 0; + reader = 0; + reset(); + } + + ~Private() + { + reset(false); + } + + void reset(bool create=true) + { + delete reader; + delete handler; + delete in; + delete doc; + + if(create) { + doc = new QDomDocument; + in = new StreamInput; + handler = new ParserHandler(in, doc); + reader = new QXmlSimpleReader; + reader->setContentHandler(handler); + + // initialize the reader + in->pause(true); + reader->parse(in, true); + in->pause(false); + } + } + + QDomDocument *doc; + StreamInput *in; + ParserHandler *handler; + QXmlSimpleReader *reader; +}; + +Parser::Parser() +{ + d = new Private; + + // check for evil bug in Qt <= 3.2.1 + if(!qt_bug_check) { + qt_bug_check = true; + QDomElement e = d->doc->createElementNS("someuri", "somename"); + if(e.hasAttributeNS("someuri", "somename")) + qt_bug_have = true; + else + qt_bug_have = false; + } +} + +Parser::~Parser() +{ + delete d; +} + +void Parser::reset() +{ + d->reset(); +} + +void Parser::appendData(const QByteArray &a) +{ + d->in->appendData(a); + + // if handler was waiting for more, give it a kick + if(d->handler->needMore) + d->handler->checkNeedMore(); +} + +Parser::Event Parser::readNext() +{ + Event e; + if(d->handler->needMore) + return e; + Event *ep = d->handler->takeEvent(); + if(!ep) { + if(!d->reader->parseContinue()) { + e.setError(); + return e; + } + ep = d->handler->takeEvent(); + if(!ep) + return e; + } + e = *ep; + delete ep; + return e; +} + +QByteArray Parser::unprocessed() const +{ + return d->in->unprocessed(); +} + +QString Parser::encoding() const +{ + return d->in->encoding(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.h new file mode 100644 index 00000000..808b6c3d --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/parser.h @@ -0,0 +1,86 @@ +/* + * parser.h - parse an XMPP "document" + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PARSER_H +#define PARSER_H + +#include<qdom.h> +#include<qxml.h> + +namespace XMPP +{ + class Parser + { + public: + Parser(); + ~Parser(); + + class Event + { + public: + enum Type { DocumentOpen, DocumentClose, Element, Error }; + Event(); + Event(const Event &); + Event & operator=(const Event &); + ~Event(); + + bool isNull() const; + int type() const; + + // for document open + QString nsprefix(const QString &s=QString::null) const; + + // for document open / close + QString namespaceURI() const; + QString localName() const; + QString qName() const; + QXmlAttributes atts() const; + + // for element + QDomElement element() const; + + // for any + QString actualString() const; + + // setup + void setDocumentOpen(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts, const QStringList &nsnames, const QStringList &nsvalues); + void setDocumentClose(const QString &namespaceURI, const QString &localName, const QString &qName); + void setElement(const QDomElement &elem); + void setError(); + void setActualString(const QString &); + + private: + class Private; + Private *d; + }; + + void reset(); + void appendData(const QByteArray &a); + Event readNext(); + QByteArray unprocessed() const; + QString encoding() const; + + private: + class Private; + Private *d; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp new file mode 100644 index 00000000..dfd3253c --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.cpp @@ -0,0 +1,1595 @@ +/* + * protocol.cpp - XMPP-Core protocol state machine + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +// TODO: let the app know if tls is required +// require mutual auth for server out/in +// report ErrProtocol if server uses wrong NS +// use send() instead of writeElement() in CoreProtocol + +#include"protocol.h" + +#include<qca.h> +#include"base64.h" +#include"hash.h" + +#ifdef XMPP_TEST +#include"td.h" +#endif + +using namespace XMPP; + +// printArray +// +// This function prints out an array of bytes as latin characters, converting +// non-printable bytes into hex values as necessary. Useful for displaying +// QByteArrays for debugging purposes. +static QString printArray(const QByteArray &a) +{ + QString s; + for(uint n = 0; n < a.size(); ++n) { + unsigned char c = (unsigned char)a[(int)n]; + if(c < 32 || c >= 127) { + QString str; + str.sprintf("[%02x]", c); + s += str; + } + else + s += c; + } + return s; +} + +// firstChildElement +// +// Get an element's first child element +static QDomElement firstChildElement(const QDomElement &e) +{ + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + if(n.isElement()) + return n.toElement(); + } + return QDomElement(); +} + +//---------------------------------------------------------------------------- +// Version +//---------------------------------------------------------------------------- +Version::Version(int maj, int min) +{ + major = maj; + minor = min; +} + +//---------------------------------------------------------------------------- +// StreamFeatures +//---------------------------------------------------------------------------- +StreamFeatures::StreamFeatures() +{ + tls_supported = false; + sasl_supported = false; + bind_supported = false; + tls_required = false; +} + +//---------------------------------------------------------------------------- +// BasicProtocol +//---------------------------------------------------------------------------- +BasicProtocol::SASLCondEntry BasicProtocol::saslCondTable[] = +{ + { "aborted", Aborted }, + { "incorrect-encoding", IncorrectEncoding }, + { "invalid-authzid", InvalidAuthzid }, + { "invalid-mechanism", InvalidMech }, + { "mechanism-too-weak", MechTooWeak }, + { "not-authorized", NotAuthorized }, + { "temporary-auth-failure", TemporaryAuthFailure }, + { 0, 0 }, +}; + +BasicProtocol::StreamCondEntry BasicProtocol::streamCondTable[] = +{ + { "bad-format", BadFormat }, + { "bad-namespace-prefix", BadNamespacePrefix }, + { "conflict", Conflict }, + { "connection-timeout", ConnectionTimeout }, + { "host-gone", HostGone }, + { "host-unknown", HostUnknown }, + { "improper-addressing", ImproperAddressing }, + { "internal-server-error", InternalServerError }, + { "invalid-from", InvalidFrom }, + { "invalid-id", InvalidId }, + { "invalid-namespace", InvalidNamespace }, + { "invalid-xml", InvalidXml }, + { "not-authorized", StreamNotAuthorized }, + { "policy-violation", PolicyViolation }, + { "remote-connection-failed", RemoteConnectionFailed }, + { "resource-constraint", ResourceConstraint }, + { "restricted-xml", RestrictedXml }, + { "see-other-host", SeeOtherHost }, + { "system-shutdown", SystemShutdown }, + { "undefined-condition", UndefinedCondition }, + { "unsupported-encoding", UnsupportedEncoding }, + { "unsupported-stanza-type", UnsupportedStanzaType }, + { "unsupported-version", UnsupportedVersion }, + { "xml-not-well-formed", XmlNotWellFormed }, + { 0, 0 }, +}; + +BasicProtocol::BasicProtocol() +:XmlProtocol() +{ + init(); +} + +BasicProtocol::~BasicProtocol() +{ +} + +void BasicProtocol::init() +{ + errCond = -1; + sasl_authed = false; + doShutdown = false; + delayedError = false; + closeError = false; + ready = false; + stanzasPending = 0; + stanzasWritten = 0; +} + +void BasicProtocol::reset() +{ + XmlProtocol::reset(); + init(); + + to = QString(); + from = QString(); + id = QString(); + lang = QString(); + version = Version(1,0); + errText = QString(); + errAppSpec = QDomElement(); + otherHost = QString(); + spare.resize(0); + sasl_mech = QString(); + sasl_mechlist.clear(); + sasl_step.resize(0); + stanzaToRecv = QDomElement(); + sendList.clear(); +} + +void BasicProtocol::sendStanza(const QDomElement &e) +{ + SendItem i; + i.stanzaToSend = e; + sendList += i; +} + +void BasicProtocol::sendDirect(const QString &s) +{ + SendItem i; + i.stringToSend = s; + sendList += i; +} + +void BasicProtocol::sendWhitespace() +{ + SendItem i; + i.doWhitespace = true; + sendList += i; +} + +QDomElement BasicProtocol::recvStanza() +{ + QDomElement e = stanzaToRecv; + stanzaToRecv = QDomElement(); + return e; +} + +void BasicProtocol::shutdown() +{ + doShutdown = true; +} + +void BasicProtocol::shutdownWithError(int cond, const QString &str) +{ + otherHost = str; + delayErrorAndClose(cond); +} + +bool BasicProtocol::isReady() const +{ + return ready; +} + +void BasicProtocol::setReady(bool b) +{ + ready = b; +} + +QString BasicProtocol::saslMech() const +{ + return sasl_mech; +} + +QByteArray BasicProtocol::saslStep() const +{ + return sasl_step; +} + +void BasicProtocol::setSASLMechList(const QStringList &list) +{ + sasl_mechlist = list; +} + +void BasicProtocol::setSASLFirst(const QString &mech, const QByteArray &step) +{ + sasl_mech = mech; + sasl_step = step; +} + +void BasicProtocol::setSASLNext(const QByteArray &step) +{ + sasl_step = step; +} + +void BasicProtocol::setSASLAuthed() +{ + sasl_authed = true; +} + +int BasicProtocol::stringToSASLCond(const QString &s) +{ + for(int n = 0; saslCondTable[n].str; ++n) { + if(s == saslCondTable[n].str) + return saslCondTable[n].cond; + } + return -1; +} + +int BasicProtocol::stringToStreamCond(const QString &s) +{ + for(int n = 0; streamCondTable[n].str; ++n) { + if(s == streamCondTable[n].str) + return streamCondTable[n].cond; + } + return -1; +} + +QString BasicProtocol::saslCondToString(int x) +{ + for(int n = 0; saslCondTable[n].str; ++n) { + if(x == saslCondTable[n].cond) + return saslCondTable[n].str; + } + return QString(); +} + +QString BasicProtocol::streamCondToString(int x) +{ + for(int n = 0; streamCondTable[n].str; ++n) { + if(x == streamCondTable[n].cond) + return streamCondTable[n].str; + } + return QString(); +} + +void BasicProtocol::extractStreamError(const QDomElement &e) +{ + QString text; + QDomElement appSpec; + + QDomElement t = firstChildElement(e); + if(t.isNull() || t.namespaceURI() != NS_STREAMS) { + // probably old-style error + errCond = -1; + errText = e.text(); + } + else + errCond = stringToStreamCond(t.tagName()); + + if(errCond != -1) { + if(errCond == SeeOtherHost) + otherHost = t.text(); + + t = e.elementsByTagNameNS(NS_STREAMS, "text").item(0).toElement(); + if(!t.isNull()) + text = t.text(); + + // find first non-standard namespaced element + QDomNodeList nl = e.childNodes(); + for(uint n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement() && i.namespaceURI() != NS_STREAMS) { + appSpec = i.toElement(); + break; + } + } + + errText = text; + errAppSpec = appSpec; + } +} + +void BasicProtocol::send(const QDomElement &e, bool clip) +{ + writeElement(e, TypeElement, false, clip); +} + +void BasicProtocol::sendStreamError(int cond, const QString &text, const QDomElement &appSpec) +{ + QDomElement se = doc.createElementNS(NS_ETHERX, "stream:error"); + QDomElement err = doc.createElementNS(NS_STREAMS, streamCondToString(cond)); + if(!otherHost.isEmpty()) + err.appendChild(doc.createTextNode(otherHost)); + se.appendChild(err); + if(!text.isEmpty()) { + QDomElement te = doc.createElementNS(NS_STREAMS, "text"); + te.setAttributeNS(NS_XML, "xml:lang", "en"); + te.appendChild(doc.createTextNode(text)); + se.appendChild(te); + } + se.appendChild(appSpec); + + writeElement(se, 100, false); +} + +void BasicProtocol::sendStreamError(const QString &text) +{ + QDomElement se = doc.createElementNS(NS_ETHERX, "stream:error"); + se.appendChild(doc.createTextNode(text)); + + writeElement(se, 100, false); +} + +bool BasicProtocol::errorAndClose(int cond, const QString &text, const QDomElement &appSpec) +{ + closeError = true; + errCond = cond; + errText = text; + errAppSpec = appSpec; + sendStreamError(cond, text, appSpec); + return close(); +} + +bool BasicProtocol::error(int code) +{ + event = EError; + errorCode = code; + return true; +} + +void BasicProtocol::delayErrorAndClose(int cond, const QString &text, const QDomElement &appSpec) +{ + errorCode = ErrStream; + errCond = cond; + errText = text; + errAppSpec = appSpec; + delayedError = true; +} + +void BasicProtocol::delayError(int code) +{ + errorCode = code; + delayedError = true; +} + +QDomElement BasicProtocol::docElement() +{ + // create the root element + QDomElement e = doc.createElementNS(NS_ETHERX, "stream:stream"); + + QString defns = defaultNamespace(); + QStringList list = extraNamespaces(); + + // HACK: using attributes seems to be the only way to get additional namespaces in here + if(!defns.isEmpty()) + e.setAttribute("xmlns", defns); + for(QStringList::ConstIterator it = list.begin(); it != list.end();) { + QString prefix = *(it++); + QString uri = *(it++); + e.setAttribute(QString("xmlns:") + prefix, uri); + } + + // additional attributes + if(!isIncoming() && !to.isEmpty()) + e.setAttribute("to", to); + if(isIncoming() && !from.isEmpty()) + e.setAttribute("from", from); + if(!id.isEmpty()) + e.setAttribute("id", id); + if(!lang.isEmpty()) + e.setAttributeNS(NS_XML, "xml:lang", lang); + if(version.major > 0 || version.minor > 0) + e.setAttribute("version", QString::number(version.major) + '.' + QString::number(version.minor)); + + return e; +} + +void BasicProtocol::handleDocOpen(const Parser::Event &pe) +{ + if(isIncoming()) { + if(xmlEncoding() != "UTF-8") { + delayErrorAndClose(UnsupportedEncoding); + return; + } + } + + if(pe.namespaceURI() == NS_ETHERX && pe.localName() == "stream") { + QXmlAttributes atts = pe.atts(); + + // grab the version + int major = 0; + int minor = 0; + QString verstr = atts.value("version"); + if(!verstr.isEmpty()) { + int n = verstr.find('.'); + if(n != -1) { + major = verstr.mid(0, n).toInt(); + minor = verstr.mid(n+1).toInt(); + } + else { + major = verstr.toInt(); + minor = 0; + } + } + version = Version(major, minor); + + if(isIncoming()) { + to = atts.value("to"); + QString peerLang = atts.value(NS_XML, "lang"); + if(!peerLang.isEmpty()) + lang = peerLang; + } + // outgoing + else { + from = atts.value("from"); + lang = atts.value(NS_XML, "lang"); + id = atts.value("id"); + } + + handleStreamOpen(pe); + } + else { + if(isIncoming()) + delayErrorAndClose(BadFormat); + else + delayError(ErrProtocol); + } +} + +bool BasicProtocol::handleError() +{ + if(isIncoming()) + return errorAndClose(XmlNotWellFormed); + else + return error(ErrParse); +} + +bool BasicProtocol::handleCloseFinished() +{ + if(closeError) { + event = EError; + errorCode = ErrStream; + // note: errCond and friends are already set at this point + } + else + event = EClosed; + return true; +} + +bool BasicProtocol::doStep(const QDomElement &e) +{ + // handle pending error + if(delayedError) { + if(isIncoming()) + return errorAndClose(errCond, errText, errAppSpec); + else + return error(errorCode); + } + + // shutdown? + if(doShutdown) { + doShutdown = false; + return close(); + } + + if(!e.isNull()) { + // check for error + if(e.namespaceURI() == NS_ETHERX && e.tagName() == "error") { + extractStreamError(e); + return error(ErrStream); + } + } + + if(ready) { + // stanzas written? + if(stanzasWritten > 0) { + --stanzasWritten; + event = EStanzaSent; + return true; + } + // send items? + if(!sendList.isEmpty()) { + SendItem i; + { + QValueList<SendItem>::Iterator it = sendList.begin(); + i = (*it); + sendList.remove(it); + } + + // outgoing stanza? + if(!i.stanzaToSend.isNull()) { + ++stanzasPending; + writeElement(i.stanzaToSend, TypeStanza, true); + event = ESend; + } + // direct send? + else if(!i.stringToSend.isEmpty()) { + writeString(i.stringToSend, TypeDirect, true); + event = ESend; + } + // whitespace keepalive? + else if(i.doWhitespace) { + writeString("\n", TypePing, false); + event = ESend; + } + return true; + } + else { + // if we have pending outgoing stanzas, ask for write notification + if(stanzasPending) + notify |= NSend; + } + } + + return doStep2(e); +} + +void BasicProtocol::itemWritten(int id, int) +{ + if(id == TypeStanza) { + --stanzasPending; + ++stanzasWritten; + } +} + +QString BasicProtocol::defaultNamespace() +{ + // default none + return QString(); +} + +QStringList BasicProtocol::extraNamespaces() +{ + // default none + return QStringList(); +} + +void BasicProtocol::handleStreamOpen(const Parser::Event &) +{ + // default does nothing +} + +//---------------------------------------------------------------------------- +// CoreProtocol +//---------------------------------------------------------------------------- +CoreProtocol::CoreProtocol() +:BasicProtocol() +{ + init(); +} + +CoreProtocol::~CoreProtocol() +{ +} + +void CoreProtocol::init() +{ + step = Start; + + // ?? + server = false; + dialback = false; + dialback_verify = false; + + // settings + jid = Jid(); + password = QString(); + oldOnly = false; + allowPlain = false; + doTLS = true; + doAuth = true; + doBinding = true; + + // input + user = QString(); + host = QString(); + + // status + old = false; + digest = false; + tls_started = false; + sasl_started = false; +} + +void CoreProtocol::reset() +{ + BasicProtocol::reset(); + init(); +} + +void CoreProtocol::startClientOut(const Jid &_jid, bool _oldOnly, bool tlsActive, bool _doAuth) +{ + jid = _jid; + to = _jid.domain(); + oldOnly = _oldOnly; + doAuth = _doAuth; + tls_started = tlsActive; + + if(oldOnly) + version = Version(0,0); + startConnect(); +} + +void CoreProtocol::startServerOut(const QString &_to) +{ + server = true; + to = _to; + startConnect(); +} + +void CoreProtocol::startDialbackOut(const QString &_to, const QString &_from) +{ + server = true; + dialback = true; + to = _to; + self_from = _from; + startConnect(); +} + +void CoreProtocol::startDialbackVerifyOut(const QString &_to, const QString &_from, const QString &id, const QString &key) +{ + server = true; + dialback = true; + dialback_verify = true; + to = _to; + self_from = _from; + dialback_id = id; + dialback_key = key; + startConnect(); +} + +void CoreProtocol::startClientIn(const QString &_id) +{ + id = _id; + startAccept(); +} + +void CoreProtocol::startServerIn(const QString &_id) +{ + server = true; + id = _id; + startAccept(); +} + +void CoreProtocol::setLang(const QString &s) +{ + lang = s; +} + +void CoreProtocol::setAllowTLS(bool b) +{ + doTLS = b; +} + +void CoreProtocol::setAllowBind(bool b) +{ + doBinding = b; +} + +void CoreProtocol::setAllowPlain(bool b) +{ + allowPlain = b; +} + +void CoreProtocol::setPassword(const QString &s) +{ + password = s; +} + +void CoreProtocol::setFrom(const QString &s) +{ + from = s; +} + +void CoreProtocol::setDialbackKey(const QString &s) +{ + dialback_key = s; +} + +bool CoreProtocol::loginComplete() +{ + setReady(true); + + event = EReady; + step = Done; + return true; +} + +int CoreProtocol::getOldErrorCode(const QDomElement &e) +{ + QDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement(); + if(err.isNull() || !err.hasAttribute("code")) + return -1; + return err.attribute("code").toInt(); +} + +/*QString CoreProtocol::xmlToString(const QDomElement &e, bool clip) +{ + // determine an appropriate 'fakeNS' to use + QString ns; + if(e.prefix() == "stream") + ns = NS_ETHERX; + else if(e.prefix() == "db") + ns = NS_DIALBACK; + else + ns = NS_CLIENT; + return ::xmlToString(e, ns, "stream:stream", clip); +}*/ + +bool CoreProtocol::stepAdvancesParser() const +{ + if(stepRequiresElement()) + return true; + else if(isReady()) + return true; + return false; +} + +// all element-needing steps need to be registered here +bool CoreProtocol::stepRequiresElement() const +{ + switch(step) { + case GetFeatures: + case GetTLSProceed: + case GetSASLChallenge: + case GetBindResponse: + case GetAuthGetResponse: + case GetAuthSetResponse: + case GetRequest: + case GetSASLResponse: + return true; + } + return false; +} + +void CoreProtocol::stringSend(const QString &s) +{ +#ifdef XMPP_TEST + TD::outgoingTag(s); +#endif +} + +void CoreProtocol::stringRecv(const QString &s) +{ +#ifdef XMPP_TEST + TD::incomingTag(s); +#endif +} + +QString CoreProtocol::defaultNamespace() +{ + if(server) + return NS_SERVER; + else + return NS_CLIENT; +} + +QStringList CoreProtocol::extraNamespaces() +{ + QStringList list; + if(dialback) { + list += "db"; + list += NS_DIALBACK; + } + return list; +} + +void CoreProtocol::handleStreamOpen(const Parser::Event &pe) +{ + if(isIncoming()) { + QString ns = pe.nsprefix(); + QString db; + if(server) { + db = pe.nsprefix("db"); + if(!db.isEmpty()) + dialback = true; + } + + // verify namespace + if((!server && ns != NS_CLIENT) || (server && ns != NS_SERVER) || (dialback && db != NS_DIALBACK)) { + delayErrorAndClose(InvalidNamespace); + return; + } + + // verify version + if(version.major < 1 && !dialback) { + delayErrorAndClose(UnsupportedVersion); + return; + } + } + else { + if(!dialback) { + if(version.major >= 1 && !oldOnly) + old = false; + else + old = true; + } + } +} + +void CoreProtocol::elementSend(const QDomElement &e) +{ +#ifdef XMPP_TEST + TD::outgoingXml(e); +#endif +} + +void CoreProtocol::elementRecv(const QDomElement &e) +{ +#ifdef XMPP_TEST + TD::incomingXml(e); +#endif +} + +bool CoreProtocol::doStep2(const QDomElement &e) +{ + if(dialback) + return dialbackStep(e); + else + return normalStep(e); +} + +bool CoreProtocol::isValidStanza(const QDomElement &e) const +{ + QString s = e.tagName(); + if(e.namespaceURI() == (server ? NS_SERVER : NS_CLIENT) && (s == "message" || s == "presence" || s == "iq")) + return true; + else + return false; +} + +bool CoreProtocol::grabPendingItem(const Jid &to, const Jid &from, int type, DBItem *item) +{ + for(QValueList<DBItem>::Iterator it = dbpending.begin(); it != dbpending.end(); ++it) { + const DBItem &i = *it; + if(i.type == type && i.to.compare(to) && i.from.compare(from)) { + const DBItem &i = (*it); + *item = i; + dbpending.remove(it); + return true; + } + } + return false; +} + +bool CoreProtocol::dialbackStep(const QDomElement &e) +{ + if(step == Start) { + setReady(true); + step = Done; + event = EReady; + return true; + } + + if(!dbrequests.isEmpty()) { + // process a request + DBItem i; + { + QValueList<DBItem>::Iterator it = dbrequests.begin(); + i = (*it); + dbrequests.remove(it); + } + + QDomElement r; + if(i.type == DBItem::ResultRequest) { + r = doc.createElementNS(NS_DIALBACK, "db:result"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.appendChild(doc.createTextNode(i.key)); + dbpending += i; + } + else if(i.type == DBItem::ResultGrant) { + r = doc.createElementNS(NS_DIALBACK, "db:result"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.setAttribute("type", i.ok ? "valid" : "invalid"); + if(i.ok) { + i.type = DBItem::Validated; + dbvalidated += i; + } + else { + // TODO: disconnect after writing element + } + } + else if(i.type == DBItem::VerifyRequest) { + r = doc.createElementNS(NS_DIALBACK, "db:verify"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.setAttribute("id", i.id); + r.appendChild(doc.createTextNode(i.key)); + dbpending += i; + } + // VerifyGrant + else { + r = doc.createElementNS(NS_DIALBACK, "db:verify"); + r.setAttribute("to", i.to.full()); + r.setAttribute("from", i.from.full()); + r.setAttribute("id", i.id); + r.setAttribute("type", i.ok ? "valid" : "invalid"); + } + + writeElement(r, TypeElement, false); + event = ESend; + return true; + } + + if(!e.isNull()) { + if(e.namespaceURI() == NS_DIALBACK) { + if(e.tagName() == "result") { + Jid to, from; + to.set(e.attribute("to"), ""); + from.set(e.attribute("from"), ""); + if(isIncoming()) { + QString key = e.text(); + // TODO: report event + } + else { + bool ok = (e.attribute("type") == "valid") ? true: false; + DBItem i; + if(grabPendingItem(from, to, DBItem::ResultRequest, &i)) { + if(ok) { + i.type = DBItem::Validated; + i.ok = true; + dbvalidated += i; + // TODO: report event + } + else { + // TODO: report event + } + } + } + } + else if(e.tagName() == "verify") { + Jid to, from; + to.set(e.attribute("to"), ""); + from.set(e.attribute("from"), ""); + QString id = e.attribute("id"); + if(isIncoming()) { + QString key = e.text(); + // TODO: report event + } + else { + bool ok = (e.attribute("type") == "valid") ? true: false; + DBItem i; + if(grabPendingItem(from, to, DBItem::VerifyRequest, &i)) { + if(ok) { + // TODO: report event + } + else { + // TODO: report event + } + } + } + } + } + else { + if(isReady()) { + if(isValidStanza(e)) { + // TODO: disconnect if stanza is from unverified sender + // TODO: ignore packets from receiving servers + stanzaToRecv = e; + event = EStanzaReady; + return true; + } + } + } + } + + need = NNotify; + notify |= NRecv; + return false; +} + +bool CoreProtocol::normalStep(const QDomElement &e) +{ + if(step == Start) { + if(isIncoming()) { + need = NSASLMechs; + step = SendFeatures; + return false; + } + else { + if(old) { + if(doAuth) + step = HandleAuthGet; + else + return loginComplete(); + } + else + step = GetFeatures; + + return processStep(); + } + } + else if(step == HandleFeatures) { + // deal with TLS? + if(doTLS && !tls_started && !sasl_authed && features.tls_supported) { + QDomElement e = doc.createElementNS(NS_TLS, "starttls"); + + send(e, true); + event = ESend; + step = GetTLSProceed; + return true; + } + + // deal with SASL? + if(!sasl_authed) { + if(!features.sasl_supported) { + // SASL MUST be supported + event = EError; + errorCode = ErrProtocol; + return true; + } + +#ifdef XMPP_TEST + TD::msg("starting SASL authentication..."); +#endif + need = NSASLFirst; + step = GetSASLFirst; + return false; + } + + if(server) { + return loginComplete(); + } + else { + if(!doBinding) + return loginComplete(); + } + + // deal with bind + if(!features.bind_supported) { + // bind MUST be supported + event = EError; + errorCode = ErrProtocol; + return true; + } + + QDomElement e = doc.createElement("iq"); + e.setAttribute("type", "set"); + e.setAttribute("id", "bind_1"); + QDomElement b = doc.createElementNS(NS_BIND, "bind"); + + // request specific resource? + QString resource = jid.resource(); + if(!resource.isEmpty()) { + QDomElement r = doc.createElement("resource"); + r.appendChild(doc.createTextNode(jid.resource())); + b.appendChild(r); + } + + e.appendChild(b); + + send(e); + event = ESend; + step = GetBindResponse; + return true; + } + else if(step == GetSASLFirst) { + QDomElement e = doc.createElementNS(NS_SASL, "auth"); + e.setAttribute("mechanism", sasl_mech); + if(!sasl_step.isEmpty()) { +#ifdef XMPP_TEST + TD::msg(QString("SASL OUT: [%1]").arg(printArray(sasl_step))); +#endif + e.appendChild(doc.createTextNode(Base64::arrayToString(sasl_step))); + } + + send(e, true); + event = ESend; + step = GetSASLChallenge; + return true; + } + else if(step == GetSASLNext) { + if(isIncoming()) { + if(sasl_authed) { + QDomElement e = doc.createElementNS(NS_SASL, "success"); + writeElement(e, TypeElement, false, true); + event = ESend; + step = IncHandleSASLSuccess; + return true; + } + else { + QByteArray stepData = sasl_step; + QDomElement e = doc.createElementNS(NS_SASL, "challenge"); + if(!stepData.isEmpty()) + e.appendChild(doc.createTextNode(Base64::arrayToString(stepData))); + + writeElement(e, TypeElement, false, true); + event = ESend; + step = GetSASLResponse; + return true; + } + } + else { + QByteArray stepData = sasl_step; +#ifdef XMPP_TEST + TD::msg(QString("SASL OUT: [%1]").arg(printArray(sasl_step))); +#endif + QDomElement e = doc.createElementNS(NS_SASL, "response"); + if(!stepData.isEmpty()) + e.appendChild(doc.createTextNode(Base64::arrayToString(stepData))); + + send(e, true); + event = ESend; + step = GetSASLChallenge; + return true; + } + } + else if(step == HandleSASLSuccess) { + need = NSASLLayer; + spare = resetStream(); + step = Start; + return false; + } + else if(step == HandleAuthGet) { + QDomElement e = doc.createElement("iq"); + e.setAttribute("to", to); + e.setAttribute("type", "get"); + e.setAttribute("id", "auth_1"); + QDomElement q = doc.createElementNS("jabber:iq:auth", "query"); + QDomElement u = doc.createElement("username"); + u.appendChild(doc.createTextNode(jid.node())); + q.appendChild(u); + e.appendChild(q); + + send(e); + event = ESend; + step = GetAuthGetResponse; + return true; + } + else if(step == HandleAuthSet) { + QDomElement e = doc.createElement("iq"); + e.setAttribute("to", to); + e.setAttribute("type", "set"); + e.setAttribute("id", "auth_2"); + QDomElement q = doc.createElementNS("jabber:iq:auth", "query"); + QDomElement u = doc.createElement("username"); + u.appendChild(doc.createTextNode(jid.node())); + q.appendChild(u); + QDomElement p; + if(digest) { + // need SHA1 here + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + p = doc.createElement("digest"); + QCString cs = id.utf8() + password.utf8(); + p.appendChild(doc.createTextNode(QCA::SHA1::hashToString(cs))); + } + else { + p = doc.createElement("password"); + p.appendChild(doc.createTextNode(password)); + } + q.appendChild(p); + QDomElement r = doc.createElement("resource"); + r.appendChild(doc.createTextNode(jid.resource())); + q.appendChild(r); + e.appendChild(q); + + send(e, true); + event = ESend; + step = GetAuthSetResponse; + return true; + } + // server + else if(step == SendFeatures) { + QDomElement f = doc.createElementNS(NS_ETHERX, "stream:features"); + if(!tls_started && !sasl_authed) { // don't offer tls if we are already sasl'd + QDomElement tls = doc.createElementNS(NS_TLS, "starttls"); + f.appendChild(tls); + } + + if(sasl_authed) { + if(!server) { + QDomElement bind = doc.createElementNS(NS_BIND, "bind"); + f.appendChild(bind); + } + } + else { + QDomElement mechs = doc.createElementNS(NS_SASL, "mechanisms"); + for(QStringList::ConstIterator it = sasl_mechlist.begin(); it != sasl_mechlist.end(); ++it) { + QDomElement m = doc.createElement("mechanism"); + m.appendChild(doc.createTextNode(*it)); + mechs.appendChild(m); + } + f.appendChild(mechs); + } + + writeElement(f, TypeElement, false); + event = ESend; + step = GetRequest; + return true; + } + // server + else if(step == HandleTLS) { + tls_started = true; + need = NStartTLS; + spare = resetStream(); + step = Start; + return false; + } + // server + else if(step == IncHandleSASLSuccess) { + event = ESASLSuccess; + spare = resetStream(); + step = Start; + printf("sasl success\n"); + return true; + } + else if(step == GetFeatures) { + // we are waiting for stream features + if(e.namespaceURI() == NS_ETHERX && e.tagName() == "features") { + // extract features + StreamFeatures f; + QDomElement s = e.elementsByTagNameNS(NS_TLS, "starttls").item(0).toElement(); + if(!s.isNull()) { + f.tls_supported = true; + f.tls_required = s.elementsByTagNameNS(NS_TLS, "required").count() > 0; + } + QDomElement m = e.elementsByTagNameNS(NS_SASL, "mechanisms").item(0).toElement(); + if(!m.isNull()) { + f.sasl_supported = true; + QDomNodeList l = m.elementsByTagNameNS(NS_SASL, "mechanism"); + for(uint n = 0; n < l.count(); ++n) + f.sasl_mechs += l.item(n).toElement().text(); + } + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + if(!b.isNull()) + f.bind_supported = true; + + if(f.tls_supported) { +#ifdef XMPP_TEST + QString s = "STARTTLS is available"; + if(f.tls_required) + s += " (required)"; + TD::msg(s); +#endif + } + if(f.sasl_supported) { +#ifdef XMPP_TEST + QString s = "SASL mechs:"; + for(QStringList::ConstIterator it = f.sasl_mechs.begin(); it != f.sasl_mechs.end(); ++it) + s += QString(" [%1]").arg((*it)); + TD::msg(s); +#endif + } + + if(doAuth) { + event = EFeatures; + features = f; + step = HandleFeatures; + return true; + } + else + return loginComplete(); + } + else { + // ignore + } + } + else if(step == GetTLSProceed) { + // waiting for proceed to starttls + if(e.namespaceURI() == NS_TLS) { + if(e.tagName() == "proceed") { +#ifdef XMPP_TEST + TD::msg("Server wants us to proceed with ssl handshake"); +#endif + tls_started = true; + need = NStartTLS; + spare = resetStream(); + step = Start; + return false; + } + else if(e.tagName() == "failure") { + event = EError; + errorCode = ErrStartTLS; + return true; + } + else { + event = EError; + errorCode = ErrProtocol; + return true; + } + } + else { + // ignore + } + } + else if(step == GetSASLChallenge) { + // waiting for sasl challenge/success/fail + if(e.namespaceURI() == NS_SASL) { + if(e.tagName() == "challenge") { + QByteArray a = Base64::stringToArray(e.text()); +#ifdef XMPP_TEST + TD::msg(QString("SASL IN: [%1]").arg(printArray(a))); +#endif + sasl_step = a; + need = NSASLNext; + step = GetSASLNext; + return false; + } + else if(e.tagName() == "success") { + sasl_authed = true; + event = ESASLSuccess; + step = HandleSASLSuccess; + return true; + } + else if(e.tagName() == "failure") { + QDomElement t = firstChildElement(e); + if(t.isNull() || t.namespaceURI() != NS_SASL) + errCond = -1; + else + errCond = stringToSASLCond(t.tagName()); + + event = EError; + errorCode = ErrAuth; + return true; + } + else { + event = EError; + errorCode = ErrProtocol; + return true; + } + } + } + else if(step == GetBindResponse) { + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + QString type(e.attribute("type")); + QString id(e.attribute("id")); + + if(id == "bind_1" && (type == "result" || type == "error")) { + if(type == "result") { + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + Jid j; + if(!b.isNull()) { + QDomElement je = e.elementsByTagName("jid").item(0).toElement(); + j = je.text(); + } + if(!j.isValid()) { + event = EError; + errorCode = ErrProtocol; + return true; + } + jid = j; + return loginComplete(); + } + else { + errCond = -1; + + QDomElement err = e.elementsByTagNameNS(NS_CLIENT, "error").item(0).toElement(); + if(!err.isNull()) { + // get error condition + QDomNodeList nl = err.childNodes(); + QDomElement t; + for(uint n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement()) { + t = i.toElement(); + break; + } + } + if(!t.isNull() && t.namespaceURI() == NS_STANZAS) { + QString cond = t.tagName(); + if(cond == "not-allowed") + errCond = BindNotAllowed; + else if(cond == "conflict") + errCond = BindConflict; + } + } + + event = EError; + errorCode = ErrBind; + return true; + } + } + else { + // ignore + } + } + else { + // ignore + } + } + else if(step == GetAuthGetResponse) { + // waiting for an iq + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + Jid from(e.attribute("from")); + QString type(e.attribute("type")); + QString id(e.attribute("id")); + + bool okfrom = (from.isEmpty() || from.compare(Jid(to))); + if(okfrom && id == "auth_1" && (type == "result" || type == "error")) { + if(type == "result") { + QDomElement q = e.elementsByTagNameNS("jabber:iq:auth", "query").item(0).toElement(); + if(q.isNull() || q.elementsByTagName("username").item(0).isNull() || q.elementsByTagName("resource").item(0).isNull()) { + event = EError; + errorCode = ErrProtocol; + return true; + } + bool plain_supported = !q.elementsByTagName("password").item(0).isNull(); + bool digest_supported = !q.elementsByTagName("digest").item(0).isNull(); + + if(!digest_supported && !plain_supported) { + event = EError; + errorCode = ErrProtocol; + return true; + } + + // plain text not allowed? + if(!digest_supported && !allowPlain) { + event = EError; + errorCode = ErrPlain; + return true; + } + + digest = digest_supported; + need = NPassword; + step = HandleAuthSet; + return false; + } + else { + errCond = getOldErrorCode(e); + + event = EError; + errorCode = ErrAuth; + return true; + } + } + else { + // ignore + } + } + else { + // ignore + } + } + else if(step == GetAuthSetResponse) { + // waiting for an iq + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + Jid from(e.attribute("from")); + QString type(e.attribute("type")); + QString id(e.attribute("id")); + + bool okfrom = (from.isEmpty() || from.compare(Jid(to))); + if(okfrom && id == "auth_2" && (type == "result" || type == "error")) { + if(type == "result") { + return loginComplete(); + } + else { + errCond = getOldErrorCode(e); + + event = EError; + errorCode = ErrAuth; + return true; + } + } + else { + // ignore + } + } + else { + // ignore + } + } + // server + else if(step == GetRequest) { + printf("get request: [%s], %s\n", e.namespaceURI().latin1(), e.tagName().latin1()); + if(e.namespaceURI() == NS_TLS && e.localName() == "starttls") { + // TODO: don't let this be done twice + + QDomElement e = doc.createElementNS(NS_TLS, "proceed"); + writeElement(e, TypeElement, false, true); + event = ESend; + step = HandleTLS; + return true; + } + if(e.namespaceURI() == NS_SASL) { + if(e.localName() == "auth") { + if(sasl_started) { + // TODO + printf("error\n"); + return false; + } + + sasl_started = true; + sasl_mech = e.attribute("mechanism"); + // TODO: if child text missing, don't pass it + sasl_step = Base64::stringToArray(e.text()); + need = NSASLFirst; + step = GetSASLNext; + return false; + } + else { + // TODO + printf("unknown sasl tag\n"); + return false; + } + } + if(e.namespaceURI() == NS_CLIENT && e.tagName() == "iq") { + QDomElement b = e.elementsByTagNameNS(NS_BIND, "bind").item(0).toElement(); + if(!b.isNull()) { + QDomElement res = b.elementsByTagName("resource").item(0).toElement(); + QString resource = res.text(); + + QDomElement r = doc.createElement("iq"); + r.setAttribute("type", "result"); + r.setAttribute("id", e.attribute("id")); + QDomElement bind = doc.createElementNS(NS_BIND, "bind"); + QDomElement jid = doc.createElement("jid"); + Jid j = user + '@' + host + '/' + resource; + jid.appendChild(doc.createTextNode(j.full())); + bind.appendChild(jid); + r.appendChild(bind); + + writeElement(r, TypeElement, false); + event = ESend; + // TODO + return true; + } + else { + // TODO + } + } + } + else if(step == GetSASLResponse) { + if(e.namespaceURI() == NS_SASL && e.localName() == "response") { + sasl_step = Base64::stringToArray(e.text()); + need = NSASLNext; + step = GetSASLNext; + return false; + } + } + + if(isReady()) { + if(!e.isNull() && isValidStanza(e)) { + stanzaToRecv = e; + event = EStanzaReady; + setIncomingAsExternal(); + return true; + } + } + + need = NNotify; + notify |= NRecv; + return false; +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.h new file mode 100644 index 00000000..8511ce32 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/protocol.h @@ -0,0 +1,355 @@ +/* + * protocol.h - XMPP-Core protocol state machine + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include<qpair.h> +#include"xmlprotocol.h" +#include"xmpp.h" + +#define NS_ETHERX "http://etherx.jabber.org/streams" +#define NS_CLIENT "jabber:client" +#define NS_SERVER "jabber:server" +#define NS_DIALBACK "jabber:server:dialback" +#define NS_STREAMS "urn:ietf:params:xml:ns:xmpp-streams" +#define NS_TLS "urn:ietf:params:xml:ns:xmpp-tls" +#define NS_SASL "urn:ietf:params:xml:ns:xmpp-sasl" +#define NS_SESSION "urn:ietf:params:xml:ns:xmpp-session" +#define NS_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas" +#define NS_BIND "urn:ietf:params:xml:ns:xmpp-bind" +#define NS_XHTML_IM "http://jabber.org/protocol/xhtml-im" +#define NS_XHTML "http://www.w3.org/1999/xhtml" +#define NS_CHATSTATES "http://jabber.org/protocol/chatstates" + +namespace XMPP +{ + class Version + { + public: + Version(int maj=0, int min=0); + + int major, minor; + }; + + class StreamFeatures + { + public: + StreamFeatures(); + + bool tls_supported, sasl_supported, bind_supported; + bool tls_required; + QStringList sasl_mechs; + }; + + class BasicProtocol : public XmlProtocol + { + public: + // xmpp 1.0 error conditions + enum SASLCond { + Aborted, + IncorrectEncoding, + InvalidAuthzid, + InvalidMech, + MechTooWeak, + NotAuthorized, + TemporaryAuthFailure + }; + enum StreamCond { + BadFormat, + BadNamespacePrefix, + Conflict, + ConnectionTimeout, + HostGone, + HostUnknown, + ImproperAddressing, + InternalServerError, + InvalidFrom, + InvalidId, + InvalidNamespace, + InvalidXml, + StreamNotAuthorized, + PolicyViolation, + RemoteConnectionFailed, + ResourceConstraint, + RestrictedXml, + SeeOtherHost, + SystemShutdown, + UndefinedCondition, + UnsupportedEncoding, + UnsupportedStanzaType, + UnsupportedVersion, + XmlNotWellFormed + }; + enum BindCond { + BindBadRequest, + BindNotAllowed, + BindConflict + }; + + // extend the XmlProtocol enums + enum Need { + NSASLMechs = XmlProtocol::NCustom, // need SASL mechlist + NStartTLS, // need to switch on TLS layer + NSASLFirst, // need SASL first step + NSASLNext, // need SASL next step + NSASLLayer, // need to switch on SASL layer + NCustom = XmlProtocol::NCustom+10 + }; + enum Event { + EFeatures = XmlProtocol::ECustom, // breakpoint after features packet is received + ESASLSuccess, // breakpoint after successful sasl auth + EStanzaReady, // a stanza was received + EStanzaSent, // a stanza was sent + EReady, // stream is ready for stanza use + ECustom = XmlProtocol::ECustom+10 + }; + enum Error { + ErrProtocol = XmlProtocol::ErrCustom, // there was an error in the xmpp-core protocol exchange + ErrStream, // <stream:error>, see errCond, errText, and errAppSpec for details + ErrStartTLS, // server refused starttls + ErrAuth, // authorization error. errCond holds sasl condition (or numeric code for old-protocol) + ErrBind, // server refused resource bind + ErrCustom = XmlProtocol::ErrCustom+10 + }; + + BasicProtocol(); + ~BasicProtocol(); + + void reset(); + + // for outgoing xml + QDomDocument doc; + + // sasl-related + QString saslMech() const; + QByteArray saslStep() const; + void setSASLMechList(const QStringList &list); + void setSASLFirst(const QString &mech, const QByteArray &step); + void setSASLNext(const QByteArray &step); + void setSASLAuthed(); + + // send / recv + void sendStanza(const QDomElement &e); + void sendDirect(const QString &s); + void sendWhitespace(); + QDomElement recvStanza(); + + // shutdown + void shutdown(); + void shutdownWithError(int cond, const QString &otherHost=""); + + // <stream> information + QString to, from, id, lang; + Version version; + + // error output + int errCond; + QString errText; + QDomElement errAppSpec; + QString otherHost; + + QByteArray spare; // filled with unprocessed data on NStartTLS and NSASLLayer + + bool isReady() const; + + enum { TypeElement, TypeStanza, TypeDirect, TypePing }; + + protected: + static int stringToSASLCond(const QString &s); + static int stringToStreamCond(const QString &s); + static QString saslCondToString(int); + static QString streamCondToString(int); + + void send(const QDomElement &e, bool clip=false); + void sendStreamError(int cond, const QString &text="", const QDomElement &appSpec=QDomElement()); + void sendStreamError(const QString &text); // old-style + + bool errorAndClose(int cond, const QString &text="", const QDomElement &appSpec=QDomElement()); + bool error(int code); + void delayErrorAndClose(int cond, const QString &text="", const QDomElement &appSpec=QDomElement()); + void delayError(int code); + + // reimplemented + QDomElement docElement(); + void handleDocOpen(const Parser::Event &pe); + bool handleError(); + bool handleCloseFinished(); + bool doStep(const QDomElement &e); + void itemWritten(int id, int size); + + virtual QString defaultNamespace(); + virtual QStringList extraNamespaces(); // stringlist: prefix,uri,prefix,uri, [...] + virtual void handleStreamOpen(const Parser::Event &pe); + virtual bool doStep2(const QDomElement &e)=0; + + void setReady(bool b); + + QString sasl_mech; + QStringList sasl_mechlist; + QByteArray sasl_step; + bool sasl_authed; + + QDomElement stanzaToRecv; + + private: + struct SASLCondEntry + { + const char *str; + int cond; + }; + static SASLCondEntry saslCondTable[]; + + struct StreamCondEntry + { + const char *str; + int cond; + }; + static StreamCondEntry streamCondTable[]; + + struct SendItem + { + QDomElement stanzaToSend; + QString stringToSend; + bool doWhitespace; + }; + QValueList<SendItem> sendList; + + bool doShutdown, delayedError, closeError, ready; + int stanzasPending, stanzasWritten; + + void init(); + void extractStreamError(const QDomElement &e); + }; + + class CoreProtocol : public BasicProtocol + { + public: + enum { + NPassword = NCustom, // need password for old-mode + EDBVerify = ECustom, // breakpoint after db:verify request + ErrPlain = ErrCustom // server only supports plain, but allowPlain is false locally + }; + + CoreProtocol(); + ~CoreProtocol(); + + void reset(); + + void startClientOut(const Jid &jid, bool oldOnly, bool tlsActive, bool doAuth); + void startServerOut(const QString &to); + void startDialbackOut(const QString &to, const QString &from); + void startDialbackVerifyOut(const QString &to, const QString &from, const QString &id, const QString &key); + void startClientIn(const QString &id); + void startServerIn(const QString &id); + + void setLang(const QString &s); + void setAllowTLS(bool b); + void setAllowBind(bool b); + void setAllowPlain(bool b); // old-mode + + void setPassword(const QString &s); + void setFrom(const QString &s); + void setDialbackKey(const QString &s); + + // input + QString user, host; + + // status + bool old; + + StreamFeatures features; + + //static QString xmlToString(const QDomElement &e, bool clip=false); + + class DBItem + { + public: + enum { ResultRequest, ResultGrant, VerifyRequest, VerifyGrant, Validated }; + int type; + Jid to, from; + QString key, id; + bool ok; + }; + + private: + enum Step { + Start, + Done, + SendFeatures, + GetRequest, + HandleTLS, + GetSASLResponse, + IncHandleSASLSuccess, + GetFeatures, // read features packet + HandleFeatures, // act on features, by initiating tls, sasl, or bind + GetTLSProceed, // read <proceed/> tls response + GetSASLFirst, // perform sasl first step using provided data + GetSASLChallenge, // read server sasl challenge + GetSASLNext, // perform sasl next step using provided data + HandleSASLSuccess, // handle what must be done after reporting sasl success + GetBindResponse, // read bind response + HandleAuthGet, // send old-protocol auth-get + GetAuthGetResponse, // read auth-get response + HandleAuthSet, // send old-protocol auth-set + GetAuthSetResponse // read auth-set response + }; + + QValueList<DBItem> dbrequests, dbpending, dbvalidated; + + bool server, dialback, dialback_verify; + int step; + + bool digest; + bool tls_started, sasl_started; + + Jid jid; + bool oldOnly; + bool allowPlain; + bool doTLS, doAuth, doBinding; + QString password; + + QString dialback_id, dialback_key; + QString self_from; + + void init(); + static int getOldErrorCode(const QDomElement &e); + bool loginComplete(); + + bool isValidStanza(const QDomElement &e) const; + bool grabPendingItem(const Jid &to, const Jid &from, int type, DBItem *item); + bool normalStep(const QDomElement &e); + bool dialbackStep(const QDomElement &e); + + // reimplemented + bool stepAdvancesParser() const; + bool stepRequiresElement() const; + void stringSend(const QString &s); + void stringRecv(const QString &s); + QString defaultNamespace(); + QStringList extraNamespaces(); + void handleStreamOpen(const Parser::Event &pe); + bool doStep2(const QDomElement &e); + void elementSend(const QDomElement &e); + void elementRecv(const QDomElement &e); + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/qcaprovider.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/qcaprovider.h new file mode 100644 index 00000000..a7f1805b --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/qcaprovider.h @@ -0,0 +1,191 @@ +/* + * qcaprovider.h - QCA Plugin API + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef QCAPROVIDER_H +#define QCAPROVIDER_H + +#include<qglobal.h> +#include<qstring.h> +#include<qdatetime.h> +#include<qobject.h> +#include<qhostaddress.h> +#include"qca.h" + +#define QCA_PLUGIN_VERSION 1 + +class QCAProvider +{ +public: + QCAProvider() {} + virtual ~QCAProvider() {} + + virtual void init()=0; + virtual int qcaVersion() const=0; + virtual int capabilities() const=0; + virtual void *context(int cap)=0; +}; + +class QCA_HashContext +{ +public: + virtual ~QCA_HashContext() {} + + virtual QCA_HashContext *clone()=0; + virtual void reset()=0; + virtual void update(const char *in, unsigned int len)=0; + virtual void final(QByteArray *out)=0; +}; + +class QCA_CipherContext +{ +public: + virtual ~QCA_CipherContext() {} + + virtual QCA_CipherContext *clone()=0; + virtual int keySize()=0; + virtual int blockSize()=0; + virtual bool generateKey(char *out, int keysize=-1)=0; + virtual bool generateIV(char *out)=0; + + virtual bool setup(int dir, int mode, const char *key, int keysize, const char *iv, bool pad)=0; + virtual bool update(const char *in, unsigned int len)=0; + virtual bool final(QByteArray *out)=0; +}; + +class QCA_RSAKeyContext +{ +public: + virtual ~QCA_RSAKeyContext() {} + + virtual QCA_RSAKeyContext *clone() const=0; + virtual bool isNull() const=0; + virtual bool havePublic() const=0; + virtual bool havePrivate() const=0; + virtual bool createFromDER(const char *in, unsigned int len)=0; + virtual bool createFromPEM(const char *in, unsigned int len)=0; + virtual bool createFromNative(void *in)=0; + virtual bool generate(unsigned int bits)=0; + virtual bool toDER(QByteArray *out, bool publicOnly)=0; + virtual bool toPEM(QByteArray *out, bool publicOnly)=0; + + virtual bool encrypt(const QByteArray &in, QByteArray *out, bool oaep)=0; + virtual bool decrypt(const QByteArray &in, QByteArray *out, bool oaep)=0; +}; + +struct QCA_CertProperty +{ + QString var; + QString val; +}; + +class QCA_CertContext +{ +public: + virtual ~QCA_CertContext() {} + + virtual QCA_CertContext *clone() const=0; + virtual bool isNull() const=0; + virtual bool createFromDER(const char *in, unsigned int len)=0; + virtual bool createFromPEM(const char *in, unsigned int len)=0; + virtual bool toDER(QByteArray *out)=0; + virtual bool toPEM(QByteArray *out)=0; + + virtual QString serialNumber() const=0; + virtual QString subjectString() const=0; + virtual QString issuerString() const=0; + virtual QValueList<QCA_CertProperty> subject() const=0; + virtual QValueList<QCA_CertProperty> issuer() const=0; + virtual QDateTime notBefore() const=0; + virtual QDateTime notAfter() const=0; + virtual bool matchesAddress(const QString &realHost) const=0; +}; + +class QCA_TLSContext +{ +public: + enum Result { Success, Error, Continue }; + virtual ~QCA_TLSContext() {} + + virtual void reset()=0; + virtual bool startClient(const QPtrList<QCA_CertContext> &store, const QCA_CertContext &cert, const QCA_RSAKeyContext &key)=0; + virtual bool startServer(const QPtrList<QCA_CertContext> &store, const QCA_CertContext &cert, const QCA_RSAKeyContext &key)=0; + + virtual int handshake(const QByteArray &in, QByteArray *out)=0; + virtual int shutdown(const QByteArray &in, QByteArray *out)=0; + virtual bool encode(const QByteArray &plain, QByteArray *to_net, int *encoded)=0; + virtual bool decode(const QByteArray &from_net, QByteArray *plain, QByteArray *to_net)=0; + virtual bool eof() const=0; + virtual QByteArray unprocessed()=0; + + virtual QCA_CertContext *peerCertificate() const=0; + virtual int validityResult() const=0; +}; + +struct QCA_SASLHostPort +{ + QHostAddress addr; + Q_UINT16 port; +}; + +struct QCA_SASLNeedParams +{ + bool user, authzid, pass, realm; +}; + +class QCA_SASLContext +{ +public: + enum Result { Success, Error, NeedParams, AuthCheck, Continue }; + virtual ~QCA_SASLContext() {} + + // common + virtual void reset()=0; + virtual void setCoreProps(const QString &service, const QString &host, QCA_SASLHostPort *local, QCA_SASLHostPort *remote)=0; + virtual void setSecurityProps(bool noPlain, bool noActive, bool noDict, bool noAnon, bool reqForward, bool reqCreds, bool reqMutual, int ssfMin, int ssfMax, const QString &_ext_authid, int _ext_ssf)=0; + virtual int security() const=0; + virtual int errorCond() const=0; + + // init / first step + virtual bool clientStart(const QStringList &mechlist)=0; + virtual int clientFirstStep(bool allowClientSendFirst)=0; + virtual bool serverStart(const QString &realm, QStringList *mechlist, const QString &name)=0; + virtual int serverFirstStep(const QString &mech, const QByteArray *in)=0; + + // get / set params + virtual QCA_SASLNeedParams clientParamsNeeded() const=0; + virtual void setClientParams(const QString *user, const QString *authzid, const QString *pass, const QString *realm)=0; + virtual QString username() const=0; + virtual QString authzid() const=0; + + // continue steps + virtual int nextStep(const QByteArray &in)=0; + virtual int tryAgain()=0; + + // results + virtual QString mech() const=0; + virtual const QByteArray *clientInit() const=0; + virtual QByteArray result() const=0; + + // security layer + virtual bool encode(const QByteArray &in, QByteArray *out)=0; + virtual bool decode(const QByteArray &in, QByteArray *out)=0; +}; + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.cpp new file mode 100644 index 00000000..6bd902d9 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.cpp @@ -0,0 +1,589 @@ +/* + * securestream.cpp - combines a ByteStream with TLS and SASL + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + Note: SecureStream depends on the underlying security layers to signal + plain-to-encrypted results immediately (as opposed to waiting for the + event loop) so that the user cannot add/remove security layers during + this conversion moment. QCA::TLS and QCA::SASL behave as expected, + but future layers might not. +*/ + +#include"securestream.h" + +#include<qguardedptr.h> +#include<qvaluelist.h> +#include<qtimer.h> + +#ifdef USE_TLSHANDLER +#include"xmpp.h" +#endif + +//---------------------------------------------------------------------------- +// LayerTracker +//---------------------------------------------------------------------------- +class LayerTracker +{ +public: + struct Item + { + int plain; + int encoded; + }; + + LayerTracker(); + + void reset(); + void addPlain(int plain); + void specifyEncoded(int encoded, int plain); + int finished(int encoded); + + int p; + QValueList<Item> list; +}; + +LayerTracker::LayerTracker() +{ + p = 0; +} + +void LayerTracker::reset() +{ + p = 0; + list.clear(); +} + +void LayerTracker::addPlain(int plain) +{ + p += plain; +} + +void LayerTracker::specifyEncoded(int encoded, int plain) +{ + // can't specify more bytes than we have + if(plain > p) + plain = p; + p -= plain; + Item i; + i.plain = plain; + i.encoded = encoded; + list += i; +} + +int LayerTracker::finished(int encoded) +{ + int plain = 0; + for(QValueList<Item>::Iterator it = list.begin(); it != list.end();) { + Item &i = *it; + + // not enough? + if(encoded < i.encoded) { + i.encoded -= encoded; + break; + } + + encoded -= i.encoded; + plain += i.plain; + it = list.remove(it); + } + return plain; +} + +//---------------------------------------------------------------------------- +// SecureStream +//---------------------------------------------------------------------------- +class SecureLayer : public QObject +{ + Q_OBJECT +public: + enum { TLS, SASL, TLSH }; + int type; + union { + QCA::TLS *tls; + QCA::SASL *sasl; +#ifdef USE_TLSHANDLER + XMPP::TLSHandler *tlsHandler; +#endif + } p; + LayerTracker layer; + bool tls_done; + int prebytes; + + SecureLayer(QCA::TLS *t) + { + type = TLS; + p.tls = t; + init(); + connect(p.tls, SIGNAL(handshaken()), SLOT(tls_handshaken())); + connect(p.tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); + connect(p.tls, SIGNAL(readyReadOutgoing(int)), SLOT(tls_readyReadOutgoing(int))); + connect(p.tls, SIGNAL(closed()), SLOT(tls_closed())); + connect(p.tls, SIGNAL(error(int)), SLOT(tls_error(int))); + } + + SecureLayer(QCA::SASL *s) + { + type = SASL; + p.sasl = s; + init(); + connect(p.sasl, SIGNAL(readyRead()), SLOT(sasl_readyRead())); + connect(p.sasl, SIGNAL(readyReadOutgoing(int)), SLOT(sasl_readyReadOutgoing(int))); + connect(p.sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); + } + +#ifdef USE_TLSHANDLER + SecureLayer(XMPP::TLSHandler *t) + { + type = TLSH; + p.tlsHandler = t; + init(); + connect(p.tlsHandler, SIGNAL(success()), SLOT(tlsHandler_success())); + connect(p.tlsHandler, SIGNAL(fail()), SLOT(tlsHandler_fail())); + connect(p.tlsHandler, SIGNAL(closed()), SLOT(tlsHandler_closed())); + connect(p.tlsHandler, SIGNAL(readyRead(const QByteArray &)), SLOT(tlsHandler_readyRead(const QByteArray &))); + connect(p.tlsHandler, SIGNAL(readyReadOutgoing(const QByteArray &, int)), SLOT(tlsHandler_readyReadOutgoing(const QByteArray &, int))); + } +#endif + + void init() + { + tls_done = false; + prebytes = 0; + } + + void write(const QByteArray &a) + { + layer.addPlain(a.size()); + switch(type) { + case TLS: { p.tls->write(a); break; } + case SASL: { p.sasl->write(a); break; } +#ifdef USE_TLSHANDLER + case TLSH: { p.tlsHandler->write(a); break; } +#endif + } + } + + void writeIncoming(const QByteArray &a) + { + switch(type) { + case TLS: { p.tls->writeIncoming(a); break; } + case SASL: { p.sasl->writeIncoming(a); break; } +#ifdef USE_TLSHANDLER + case TLSH: { p.tlsHandler->writeIncoming(a); break; } +#endif + } + } + + int finished(int plain) + { + int written = 0; + + // deal with prebytes (bytes sent prior to this security layer) + if(prebytes > 0) { + if(prebytes >= plain) { + written += plain; + prebytes -= plain; + plain = 0; + } + else { + written += prebytes; + plain -= prebytes; + prebytes = 0; + } + } + + // put remainder into the layer tracker + if(type == SASL || tls_done) + written += layer.finished(plain); + + return written; + } + +signals: + void tlsHandshaken(); + void tlsClosed(const QByteArray &); + void readyRead(const QByteArray &); + void needWrite(const QByteArray &); + void error(int); + +private slots: + void tls_handshaken() + { + tls_done = true; + tlsHandshaken(); + } + + void tls_readyRead() + { + QByteArray a = p.tls->read(); + readyRead(a); + } + + void tls_readyReadOutgoing(int plainBytes) + { + QByteArray a = p.tls->readOutgoing(); + if(tls_done) + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); + } + + void tls_closed() + { + QByteArray a = p.tls->readUnprocessed(); + tlsClosed(a); + } + + void tls_error(int x) + { + error(x); + } + + void sasl_readyRead() + { + QByteArray a = p.sasl->read(); + readyRead(a); + } + + void sasl_readyReadOutgoing(int plainBytes) + { + QByteArray a = p.sasl->readOutgoing(); + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); + } + + void sasl_error(int x) + { + error(x); + } + +#ifdef USE_TLSHANDLER + void tlsHandler_success() + { + tls_done = true; + tlsHandshaken(); + } + + void tlsHandler_fail() + { + error(0); + } + + void tlsHandler_closed() + { + tlsClosed(QByteArray()); + } + + void tlsHandler_readyRead(const QByteArray &a) + { + readyRead(a); + } + + void tlsHandler_readyReadOutgoing(const QByteArray &a, int plainBytes) + { + if(tls_done) + layer.specifyEncoded(a.size(), plainBytes); + needWrite(a); + } +#endif +}; + +#include"securestream.moc" + +class SecureStream::Private +{ +public: + ByteStream *bs; + QPtrList<SecureLayer> layers; + int pending; + int errorCode; + bool active; + bool topInProgress; + + bool haveTLS() const + { + QPtrListIterator<SecureLayer> it(layers); + for(SecureLayer *s; (s = it.current()); ++it) { + if(s->type == SecureLayer::TLS +#ifdef USE_TLSHANDLER + || s->type == SecureLayer::TLSH +#endif + ) { + return true; + } + } + return false; + } + + bool haveSASL() const + { + QPtrListIterator<SecureLayer> it(layers); + for(SecureLayer *s; (s = it.current()); ++it) { + if(s->type == SecureLayer::SASL) + return true; + } + return false; + } +}; + +SecureStream::SecureStream(ByteStream *s) +:ByteStream(0) +{ + d = new Private; + + d->bs = s; + connect(d->bs, SIGNAL(readyRead()), SLOT(bs_readyRead())); + connect(d->bs, SIGNAL(bytesWritten(int)), SLOT(bs_bytesWritten(int))); + + d->layers.setAutoDelete(true); + d->pending = 0; + d->active = true; + d->topInProgress = false; +} + +SecureStream::~SecureStream() +{ + delete d; +} + +void SecureStream::linkLayer(QObject *s) +{ + connect(s, SIGNAL(tlsHandshaken()), SLOT(layer_tlsHandshaken())); + connect(s, SIGNAL(tlsClosed(const QByteArray &)), SLOT(layer_tlsClosed(const QByteArray &))); + connect(s, SIGNAL(readyRead(const QByteArray &)), SLOT(layer_readyRead(const QByteArray &))); + connect(s, SIGNAL(needWrite(const QByteArray &)), SLOT(layer_needWrite(const QByteArray &))); + connect(s, SIGNAL(error(int)), SLOT(layer_error(int))); +} + +int SecureStream::calcPrebytes() const +{ + int x = 0; + QPtrListIterator<SecureLayer> it(d->layers); + for(SecureLayer *s; (s = it.current()); ++it) + x += s->prebytes; + return (d->pending - x); +} + +void SecureStream::startTLSClient(QCA::TLS *t, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + insertData(spare); +} + +void SecureStream::startTLSServer(QCA::TLS *t, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + insertData(spare); +} + +void SecureStream::setLayerSASL(QCA::SASL *sasl, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveSASL()) + return; + + SecureLayer *s = new SecureLayer(sasl); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + + insertData(spare); +} + +#ifdef USE_TLSHANDLER +void SecureStream::startTLSClient(XMPP::TLSHandler *t, const QString &server, const QByteArray &spare) +{ + if(!d->active || d->topInProgress || d->haveTLS()) + return; + + SecureLayer *s = new SecureLayer(t); + s->prebytes = calcPrebytes(); + linkLayer(s); + d->layers.append(s); + d->topInProgress = true; + + // unlike QCA::TLS, XMPP::TLSHandler has no return value + s->p.tlsHandler->startClient(server); + + insertData(spare); +} +#endif + +void SecureStream::closeTLS() +{ + SecureLayer *s = d->layers.getLast(); + if(s) { + if(s->type == SecureLayer::TLS) + s->p.tls->close(); + } +} + +int SecureStream::errorCode() const +{ + return d->errorCode; +} + +bool SecureStream::isOpen() const +{ + return d->active; +} + +void SecureStream::write(const QByteArray &a) +{ + if(!isOpen()) + return; + + d->pending += a.size(); + + // send to the last layer + SecureLayer *s = d->layers.getLast(); + if(s) + s->write(a); + else + writeRawData(a); +} + +int SecureStream::bytesToWrite() const +{ + return d->pending; +} + +void SecureStream::bs_readyRead() +{ + QByteArray a = d->bs->read(); + + // send to the first layer + SecureLayer *s = d->layers.getFirst(); + if(s) + s->writeIncoming(a); + else + incomingData(a); +} + +void SecureStream::bs_bytesWritten(int bytes) +{ + QPtrListIterator<SecureLayer> it(d->layers); + for(SecureLayer *s; (s = it.current()); ++it) + bytes = s->finished(bytes); + + if(bytes > 0) { + d->pending -= bytes; + bytesWritten(bytes); + } +} + +void SecureStream::layer_tlsHandshaken() +{ + d->topInProgress = false; + tlsHandshaken(); +} + +void SecureStream::layer_tlsClosed(const QByteArray &) +{ + d->active = false; + d->layers.clear(); + tlsClosed(); +} + +void SecureStream::layer_readyRead(const QByteArray &a) +{ + SecureLayer *s = (SecureLayer *)sender(); + QPtrListIterator<SecureLayer> it(d->layers); + while(it.current() != s) + ++it; + + // pass upwards + ++it; + s = it.current(); + if(s) + s->writeIncoming(a); + else + incomingData(a); +} + +void SecureStream::layer_needWrite(const QByteArray &a) +{ + SecureLayer *s = (SecureLayer *)sender(); + QPtrListIterator<SecureLayer> it(d->layers); + while(it.current() != s) + ++it; + + // pass downwards + --it; + s = it.current(); + if(s) + s->write(a); + else + writeRawData(a); +} + +void SecureStream::layer_error(int x) +{ + SecureLayer *s = (SecureLayer *)sender(); + int type = s->type; + d->errorCode = x; + d->active = false; + d->layers.clear(); + if(type == SecureLayer::TLS) + error(ErrTLS); + else if(type == SecureLayer::SASL) + error(ErrSASL); +#ifdef USE_TLSHANDLER + else if(type == SecureLayer::TLSH) + error(ErrTLS); +#endif +} + +void SecureStream::insertData(const QByteArray &a) +{ + if(!a.isEmpty()) { + SecureLayer *s = d->layers.getLast(); + if(s) + s->writeIncoming(a); + else + incomingData(a); + } +} + +void SecureStream::writeRawData(const QByteArray &a) +{ + d->bs->write(a); +} + +void SecureStream::incomingData(const QByteArray &a) +{ + appendRead(a); + if(bytesAvailable()) + readyRead(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.h new file mode 100644 index 00000000..c5787a2b --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/securestream.h @@ -0,0 +1,84 @@ +/* + * securestream.h - combines a ByteStream with TLS and SASL + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef SECURESTREAM_H +#define SECURESTREAM_H + +#include<qca.h> +#include"bytestream.h" + +#define USE_TLSHANDLER + +#ifdef USE_TLSHANDLER +namespace XMPP +{ + class TLSHandler; +} +#endif + +class SecureStream : public ByteStream +{ + Q_OBJECT +public: + enum Error { ErrTLS = ErrCustom, ErrSASL }; + SecureStream(ByteStream *s); + ~SecureStream(); + + void startTLSClient(QCA::TLS *t, const QByteArray &spare=QByteArray()); + void startTLSServer(QCA::TLS *t, const QByteArray &spare=QByteArray()); + void setLayerSASL(QCA::SASL *s, const QByteArray &spare=QByteArray()); +#ifdef USE_TLSHANDLER + void startTLSClient(XMPP::TLSHandler *t, const QString &server, const QByteArray &spare=QByteArray()); +#endif + + void closeTLS(); + int errorCode() const; + + // reimplemented + bool isOpen() const; + void write(const QByteArray &); + int bytesToWrite() const; + +signals: + void tlsHandshaken(); + void tlsClosed(); + +private slots: + void bs_readyRead(); + void bs_bytesWritten(int); + + void layer_tlsHandshaken(); + void layer_tlsClosed(const QByteArray &); + void layer_readyRead(const QByteArray &); + void layer_needWrite(const QByteArray &); + void layer_error(int); + +private: + void linkLayer(QObject *); + int calcPrebytes() const; + void insertData(const QByteArray &a); + void writeRawData(const QByteArray &a); + void incomingData(const QByteArray &a); + + class Private; + Private *d; +}; + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.cpp new file mode 100644 index 00000000..54c4f405 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.cpp @@ -0,0 +1,459 @@ +/* + * simplesasl.cpp - Simple SASL implementation + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"simplesasl.h" + +#include<qhostaddress.h> +#include<qstringlist.h> +#include<qptrlist.h> +#include<qvaluelist.h> +#include<qca.h> +#include<stdlib.h> +#include"base64.h" + +namespace XMPP +{ + +struct Prop +{ + QCString var, val; +}; + +class PropList : public QValueList<Prop> +{ +public: + PropList() : QValueList<Prop>() + { + } + + void set(const QCString &var, const QCString &val) + { + Prop p; + p.var = var; + p.val = val; + append(p); + } + + QCString get(const QCString &var) + { + for(ConstIterator it = begin(); it != end(); ++it) { + if((*it).var == var) + return (*it).val; + } + return QCString(); + } + + QCString toString() const + { + QCString str; + bool first = true; + for(ConstIterator it = begin(); it != end(); ++it) { + if(!first) + str += ','; + str += (*it).var + "=\"" + (*it).val + '\"'; + first = false; + } + return str; + } + + bool fromString(const QCString &str) + { + PropList list; + int at = 0; + while(1) { + int n = str.find('=', at); + if(n == -1) + break; + QCString var, val; + var = str.mid(at, n-at); + at = n + 1; + if(str[at] == '\"') { + ++at; + n = str.find('\"', at); + if(n == -1) + break; + val = str.mid(at, n-at); + at = n + 1; + } + else { + n = str.find(',', at); + if(n != -1) { + val = str.mid(at, n-at); + at = n; + } + else { + val = str.mid(at); + at = str.length()-1; + } + } + Prop prop; + prop.var = var; + prop.val = val; + list.append(prop); + + if(str[at] != ',') + break; + ++at; + } + + // integrity check + if(list.varCount("nonce") != 1) + return false; + if(list.varCount("algorithm") != 1) + return false; + *this = list; + return true; + } + + int varCount(const QCString &var) + { + int n = 0; + for(ConstIterator it = begin(); it != end(); ++it) { + if((*it).var == var) + ++n; + } + return n; + } + + QStringList getValues(const QCString &var) + { + QStringList list; + for(ConstIterator it = begin(); it != end(); ++it) { + if((*it).var == var) + list += (*it).val; + } + return list; + } +}; + +class SimpleSASLContext : public QCA_SASLContext +{ +public: + // core props + QString service, host; + + // state + int step; + QByteArray in_buf; + QString out_mech; + QByteArray out_buf; + bool capable; + int err; + + QCA_SASLNeedParams need; + QCA_SASLNeedParams have; + QString user, authz, pass, realm; + + SimpleSASLContext() + { + reset(); + } + + ~SimpleSASLContext() + { + reset(); + } + + void reset() + { + resetState(); + resetParams(); + } + + void resetState() + { + out_mech = QString(); + out_buf.resize(0); + err = -1; + } + + void resetParams() + { + capable = true; + need.user = false; + need.authzid = false; + need.pass = false; + need.realm = false; + have.user = false; + have.authzid = false; + have.pass = false; + have.realm = false; + user = QString(); + authz = QString(); + pass = QString(); + realm = QString(); + } + + void setCoreProps(const QString &_service, const QString &_host, QCA_SASLHostPort *, QCA_SASLHostPort *) + { + service = _service; + host = _host; + } + + void setSecurityProps(bool, bool, bool, bool, bool reqForward, bool reqCreds, bool reqMutual, int ssfMin, int, const QString &, int) + { + if(reqForward || reqCreds || reqMutual || ssfMin > 0) + capable = false; + else + capable = true; + } + + int security() const + { + return 0; + } + + int errorCond() const + { + return err; + } + + bool clientStart(const QStringList &mechlist) + { + bool haveMech = false; + for(QStringList::ConstIterator it = mechlist.begin(); it != mechlist.end(); ++it) { + if((*it) == "DIGEST-MD5") { + haveMech = true; + break; + } + } + if(!capable || !haveMech) { + err = QCA::SASL::NoMech; + return false; + } + + resetState(); + step = 0; + return true; + } + + int clientFirstStep(bool) + { + return clientTryAgain(); + } + + bool serverStart(const QString &, QStringList *, const QString &) + { + return false; + } + + int serverFirstStep(const QString &, const QByteArray *) + { + return Error; + } + + QCA_SASLNeedParams clientParamsNeeded() const + { + return need; + } + + void setClientParams(const QString *_user, const QString *_authzid, const QString *_pass, const QString *_realm) + { + if(_user) { + user = *_user; + need.user = false; + have.user = true; + } + if(_authzid) { + authz = *_authzid; + need.authzid = false; + have.authzid = true; + } + if(_pass) { + pass = *_pass; + need.pass = false; + have.pass = true; + } + if(_realm) { + realm = *_realm; + need.realm = false; + have.realm = true; + } + } + + QString username() const + { + return QString(); + } + + QString authzid() const + { + return QString(); + } + + int nextStep(const QByteArray &in) + { + in_buf = in.copy(); + return tryAgain(); + } + + int tryAgain() + { + return clientTryAgain(); + } + + QString mech() const + { + return out_mech; + } + + const QByteArray *clientInit() const + { + return 0; + } + + QByteArray result() const + { + return out_buf; + } + + int clientTryAgain() + { + if(step == 0) { + out_mech = "DIGEST-MD5"; + ++step; + return Continue; + } + else if(step == 1) { + // if we still need params, then the app has failed us! + if(need.user || need.authzid || need.pass || need.realm) { + err = -1; + return Error; + } + + // see if some params are needed + if(!have.user) + need.user = true; + if(!have.authzid) + need.authzid = true; + if(!have.pass) + need.pass = true; + if(need.user || need.authzid || need.pass) + return NeedParams; + + // get props + QCString cs(in_buf.data(), in_buf.size()+1); + PropList in; + if(!in.fromString(cs)) { + err = QCA::SASL::BadProto; + return Error; + } + + // make a cnonce + QByteArray a(32); + for(int n = 0; n < (int)a.size(); ++n) + a[n] = (char)(256.0*rand()/(RAND_MAX+1.0)); + QCString cnonce = Base64::arrayToString(a).latin1(); + + // make other variables + realm = host; + QCString nonce = in.get("nonce"); + QCString nc = "00000001"; + QCString uri = service.utf8() + '/' + host.utf8(); + QCString qop = "auth"; + + // build 'response' + QCString X = user.utf8() + ':' + realm.utf8() + ':' + pass.utf8(); + QByteArray Y = QCA::MD5::hash(X); + QCString tmp = QCString(":") + nonce + ':' + cnonce + ':' + authz.utf8(); + QByteArray A1(Y.size() + tmp.length()); + memcpy(A1.data(), Y.data(), Y.size()); + memcpy(A1.data() + Y.size(), tmp.data(), tmp.length()); + QCString A2 = "AUTHENTICATE:" + uri; + QCString HA1 = QCA::MD5::hashToString(A1).latin1(); + QCString HA2 = QCA::MD5::hashToString(A2).latin1(); + QCString KD = HA1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + HA2; + QCString Z = QCA::MD5::hashToString(KD).latin1(); + + // build output + PropList out; + out.set("username", user.utf8()); + out.set("realm", host.utf8()); + out.set("nonce", nonce); + out.set("cnonce", cnonce); + out.set("nc", nc); + out.set("serv-type", service.utf8()); + out.set("host", host.utf8()); + out.set("digest-uri", uri); + out.set("qop", qop); + out.set("response", Z); + out.set("charset", "utf-8"); + out.set("authzid", authz.utf8()); + QCString s = out.toString(); + + // done + out_buf.resize(s.length()); + memcpy(out_buf.data(), s.data(), out_buf.size()); + ++step; + return Continue; + } + else { + out_buf.resize(0); + return Success; + } + } + + bool encode(const QByteArray &a, QByteArray *b) + { + *b = a.copy(); + return true; + } + + bool decode(const QByteArray &a, QByteArray *b) + { + *b = a.copy(); + return true; + } +}; + +class QCASimpleSASL : public QCAProvider +{ +public: + QCASimpleSASL() {} + ~QCASimpleSASL() {} + + void init() + { + } + + int qcaVersion() const + { + return QCA_PLUGIN_VERSION; + } + + int capabilities() const + { + return QCA::CAP_SASL; + } + + void *context(int cap) + { + if(cap == QCA::CAP_SASL) + return new SimpleSASLContext; + return 0; + } +}; + +QCAProvider *createProviderSimpleSASL() +{ + return (new QCASimpleSASL); +} + +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.h new file mode 100644 index 00000000..12a08c0e --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/simplesasl.h @@ -0,0 +1,31 @@ +/* + * simplesasl.h - Simple SASL implementation + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef SIMPLESASL_H +#define SIMPLESASL_H + +#include"qcaprovider.h" + +namespace XMPP +{ + QCAProvider *createProviderSimpleSASL(); +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp new file mode 100644 index 00000000..bfcc218c --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/stream.cpp @@ -0,0 +1,1762 @@ +/* + * stream.cpp - handles a client stream + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + Notes: + - For Non-SASL auth (JEP-0078), username and resource fields are required. + + TODO: + - sasl needParams is totally jacked? PLAIN requires authzid, etc + - server error handling + - reply with protocol errors if the client send something wrong + - don't necessarily disconnect on protocol error. prepare for more. + - server function + - deal with stream 'to' attribute dynamically + - flag tls/sasl/binding support dynamically (have the ability to specify extra stream:features) + - inform the caller about the user authentication information + - sasl security settings + - resource-binding interaction + - timeouts + - allow exchanges of non-standard stanzas + - send </stream:stream> even if we close prematurely? + - ensure ClientStream and child classes are fully deletable after signals + - xml:lang in root (<stream>) element + - sasl external + - sasl anonymous +*/ + +#include"xmpp.h" + +#include<qtextstream.h> +#include<qguardedptr.h> +#include<qtimer.h> +#include<qca.h> +#include<stdlib.h> +#include"bytestream.h" +#include"base64.h" +#include"hash.h" +#include"simplesasl.h" +#include"securestream.h" +#include"protocol.h" + +#ifdef XMPP_TEST +#include"td.h" +#endif + +//#define XMPP_DEBUG + +using namespace XMPP; + +static Debug *debug_ptr = 0; +void XMPP::setDebug(Debug *p) +{ + debug_ptr = p; +} + +static QByteArray randomArray(int size) +{ + QByteArray a(size); + for(int n = 0; n < size; ++n) + a[n] = (char)(256.0*rand()/(RAND_MAX+1.0)); + return a; +} + +static QString genId() +{ + // need SHA1 here + if(!QCA::isSupported(QCA::CAP_SHA1)) + QCA::insertProvider(createProviderHash()); + + return QCA::SHA1::hashToString(randomArray(128)); +} + +//---------------------------------------------------------------------------- +// Stanza +//---------------------------------------------------------------------------- +Stanza::Error::Error(int _type, int _condition, const QString &_text, const QDomElement &_appSpec) +{ + type = _type; + condition = _condition; + text = _text; + appSpec = _appSpec; +} + +class Stanza::Private +{ +public: + struct ErrorTypeEntry + { + const char *str; + int type; + }; + static ErrorTypeEntry errorTypeTable[]; + + struct ErrorCondEntry + { + const char *str; + int cond; + }; + static ErrorCondEntry errorCondTable[]; + + static int stringToKind(const QString &s) + { + if(s == "message") + return Message; + else if(s == "presence") + return Presence; + else if(s == "iq") + return IQ; + else + return -1; + } + + static QString kindToString(Kind k) + { + if(k == Message) + return "message"; + else if(k == Presence) + return "presence"; + else + return "iq"; + } + + static int stringToErrorType(const QString &s) + { + for(int n = 0; errorTypeTable[n].str; ++n) { + if(s == errorTypeTable[n].str) + return errorTypeTable[n].type; + } + return -1; + } + + static QString errorTypeToString(int x) + { + for(int n = 0; errorTypeTable[n].str; ++n) { + if(x == errorTypeTable[n].type) + return errorTypeTable[n].str; + } + return QString(); + } + + static int stringToErrorCond(const QString &s) + { + for(int n = 0; errorCondTable[n].str; ++n) { + if(s == errorCondTable[n].str) + return errorCondTable[n].cond; + } + return -1; + } + + static QString errorCondToString(int x) + { + for(int n = 0; errorCondTable[n].str; ++n) { + if(x == errorCondTable[n].cond) + return errorCondTable[n].str; + } + return QString(); + } + + Stream *s; + QDomElement e; +}; + +Stanza::Private::ErrorTypeEntry Stanza::Private::errorTypeTable[] = +{ + { "cancel", Cancel }, + { "continue", Continue }, + { "modify", Modify }, + { "auth", Auth }, + { "wait", Wait }, + { 0, 0 }, +}; + +Stanza::Private::ErrorCondEntry Stanza::Private::errorCondTable[] = +{ + { "bad-request", BadRequest }, + { "conflict", Conflict }, + { "feature-not-implemented", FeatureNotImplemented }, + { "forbidden", Forbidden }, + { "internal-server-error", InternalServerError }, + { "item-not-found", ItemNotFound }, + { "jid-malformed", JidMalformed }, + { "not-allowed", NotAllowed }, + { "payment-required", PaymentRequired }, + { "recipient-unavailable", RecipientUnavailable }, + { "registration-required", RegistrationRequired }, + { "remote-server-not-found", ServerNotFound }, + { "remote-server-timeout", ServerTimeout }, + { "resource-constraint", ResourceConstraint }, + { "service-unavailable", ServiceUnavailable }, + { "subscription-required", SubscriptionRequired }, + { "undefined-condition", UndefinedCondition }, + { "unexpected-request", UnexpectedRequest }, + { 0, 0 }, +}; + +Stanza::Stanza() +{ + d = 0; +} + +Stanza::Stanza(Stream *s, Kind k, const Jid &to, const QString &type, const QString &id) +{ + d = new Private; + + Kind kind; + if(k == Message || k == Presence || k == IQ) + kind = k; + else + kind = Message; + + d->s = s; + d->e = d->s->doc().createElementNS(s->baseNS(), Private::kindToString(kind)); + if(to.isValid()) + setTo(to); + if(!type.isEmpty()) + setType(type); + if(!id.isEmpty()) + setId(id); +} + +Stanza::Stanza(Stream *s, const QDomElement &e) +{ + d = 0; + if(e.namespaceURI() != s->baseNS()) + return; + int x = Private::stringToKind(e.tagName()); + if(x == -1) + return; + d = new Private; + d->s = s; + d->e = e; +} + +Stanza::Stanza(const Stanza &from) +{ + d = 0; + *this = from; +} + +Stanza & Stanza::operator=(const Stanza &from) +{ + delete d; + d = 0; + if(from.d) + d = new Private(*from.d); + return *this; +} + +Stanza::~Stanza() +{ + delete d; +} + +bool Stanza::isNull() const +{ + return (d ? false: true); +} + +QDomElement Stanza::element() const +{ + return d->e; +} + +QString Stanza::toString() const +{ + return Stream::xmlToString(d->e); +} + +QDomDocument & Stanza::doc() const +{ + return d->s->doc(); +} + +QString Stanza::baseNS() const +{ + return d->s->baseNS(); +} + +QString Stanza::xhtmlImNS() const +{ + return d->s->xhtmlImNS(); +} + +QString Stanza::xhtmlNS() const +{ + return d->s->xhtmlNS(); +} + +QDomElement Stanza::createElement(const QString &ns, const QString &tagName) +{ + return d->s->doc().createElementNS(ns, tagName); +} + +QDomElement Stanza::createTextElement(const QString &ns, const QString &tagName, const QString &text) +{ + QDomElement e = d->s->doc().createElementNS(ns, tagName); + e.appendChild(d->s->doc().createTextNode(text)); + return e; +} + +QDomElement Stanza::createXHTMLElement(const QString &xHTML) +{ + QDomDocument doc; + + doc.setContent(xHTML, true); + QDomElement root = doc.documentElement(); + //QDomElement e; + return (root); +} + +void Stanza::appendChild(const QDomElement &e) +{ + d->e.appendChild(e); +} + +Stanza::Kind Stanza::kind() const +{ + return (Kind)Private::stringToKind(d->e.tagName()); +} + +void Stanza::setKind(Kind k) +{ + d->e.setTagName(Private::kindToString(k)); +} + +Jid Stanza::to() const +{ + return Jid(d->e.attribute("to")); +} + +Jid Stanza::from() const +{ + return Jid(d->e.attribute("from")); +} + +QString Stanza::id() const +{ + return d->e.attribute("id"); +} + +QString Stanza::type() const +{ + return d->e.attribute("type"); +} + +QString Stanza::lang() const +{ + return d->e.attributeNS(NS_XML, "lang", QString()); +} + +void Stanza::setTo(const Jid &j) +{ + d->e.setAttribute("to", j.full()); +} + +void Stanza::setFrom(const Jid &j) +{ + d->e.setAttribute("from", j.full()); +} + +void Stanza::setId(const QString &id) +{ + d->e.setAttribute("id", id); +} + +void Stanza::setType(const QString &type) +{ + d->e.setAttribute("type", type); +} + +void Stanza::setLang(const QString &lang) +{ + d->e.setAttribute("xml:lang", lang); +} + +Stanza::Error Stanza::error() const +{ + Error err; + QDomElement e = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(e.isNull()) + return err; + + // type + int x = Private::stringToErrorType(e.attribute("type")); + if(x != -1) + err.type = x; + + // condition: find first element + QDomNodeList nl = e.childNodes(); + QDomElement t; + uint n; + for(n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement()) { + t = i.toElement(); + break; + } + } + if(!t.isNull() && t.namespaceURI() == NS_STANZAS) { + x = Private::stringToErrorCond(t.tagName()); + if(x != -1) + err.condition = x; + } + + // text + t = e.elementsByTagNameNS(NS_STANZAS, "text").item(0).toElement(); + if(!t.isNull()) + err.text = t.text(); + else + err.text = e.text(); + + // appspec: find first non-standard namespaced element + nl = e.childNodes(); + for(n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement() && i.namespaceURI() != NS_STANZAS) { + err.appSpec = i.toElement(); + break; + } + } + return err; +} + +void Stanza::setError(const Error &err) +{ + // create the element if necessary + QDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(errElem.isNull()) { + errElem = d->e.ownerDocument().createElementNS(d->s->baseNS(), "error"); + d->e.appendChild(errElem); + } + + // error type/condition + if(d->s->old()) { + errElem.setAttribute("code", QString::number(err.condition)); + } + else { + QString stype = Private::errorTypeToString(err.type); + if(stype.isEmpty()) + return; + QString scond = Private::errorCondToString(err.condition); + if(scond.isEmpty()) + return; + + errElem.setAttribute("type", stype); + errElem.appendChild(d->e.ownerDocument().createElementNS(d->s->baseNS(), scond)); + } + + // text + if(d->s->old()) { + errElem.appendChild(d->e.ownerDocument().createTextNode(err.text)); + } + else { + QDomElement te = d->e.ownerDocument().createElementNS(d->s->baseNS(), "text"); + te.appendChild(d->e.ownerDocument().createTextNode(err.text)); + errElem.appendChild(te); + } + + // application specific + errElem.appendChild(err.appSpec); +} + +void Stanza::clearError() +{ + QDomElement errElem = d->e.elementsByTagNameNS(d->s->baseNS(), "error").item(0).toElement(); + if(!errElem.isNull()) + d->e.removeChild(errElem); +} + +//---------------------------------------------------------------------------- +// Stream +//---------------------------------------------------------------------------- +static XmlProtocol *foo = 0; +Stream::Stream(QObject *parent) +:QObject(parent) +{ +} + +Stream::~Stream() +{ +} + +Stanza Stream::createStanza(Stanza::Kind k, const Jid &to, const QString &type, const QString &id) +{ + return Stanza(this, k, to, type, id); +} + +Stanza Stream::createStanza(const QDomElement &e) +{ + return Stanza(this, e); +} + +QString Stream::xmlToString(const QDomElement &e, bool clip) +{ + if(!foo) + foo = new CoreProtocol; + return foo->elementToString(e, clip); +} + +//---------------------------------------------------------------------------- +// ClientStream +//---------------------------------------------------------------------------- +enum { + Idle, + Connecting, + WaitVersion, + WaitTLS, + NeedParams, + Active, + Closing +}; + +enum { + Client, + Server +}; + +class ClientStream::Private +{ +public: + Private() + { + conn = 0; + bs = 0; + ss = 0; + tlsHandler = 0; + tls = 0; + sasl = 0; + in.setAutoDelete(true); + + oldOnly = false; + allowPlain = false; + mutualAuth = false; + haveLocalAddr = false; + minimumSSF = 0; + maximumSSF = 0; + doBinding = true; + + in_rrsig = false; + + reset(); + } + + void reset() + { + state = Idle; + notify = 0; + newStanzas = false; + sasl_ssf = 0; + tls_warned = false; + using_tls = false; + } + + Jid jid; + QString server; + bool oldOnly; + bool allowPlain, mutualAuth; + bool haveLocalAddr; + QHostAddress localAddr; + Q_UINT16 localPort; + int minimumSSF, maximumSSF; + QString sasl_mech; + bool doBinding; + + bool in_rrsig; + + Connector *conn; + ByteStream *bs; + TLSHandler *tlsHandler; + QCA::TLS *tls; + QCA::SASL *sasl; + SecureStream *ss; + CoreProtocol client; + CoreProtocol srv; + + QString defRealm; + + int mode; + int state; + int notify; + bool newStanzas; + int sasl_ssf; + bool tls_warned, using_tls; + bool doAuth; + + QStringList sasl_mechlist; + + int errCond; + QString errText; + QDomElement errAppSpec; + + QPtrList<Stanza> in; + + QTimer noopTimer; + int noop_time; +}; + +ClientStream::ClientStream(Connector *conn, TLSHandler *tlsHandler, QObject *parent) +:Stream(parent) +{ + d = new Private; + d->mode = Client; + d->conn = conn; + connect(d->conn, SIGNAL(connected()), SLOT(cr_connected())); + connect(d->conn, SIGNAL(error()), SLOT(cr_error())); + + d->noop_time = 0; + connect(&d->noopTimer, SIGNAL(timeout()), SLOT(doNoop())); + + d->tlsHandler = tlsHandler; +} + +ClientStream::ClientStream(const QString &host, const QString &defRealm, ByteStream *bs, QCA::TLS *tls, QObject *parent) +:Stream(parent) +{ + d = new Private; + d->mode = Server; + d->bs = bs; + connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); + connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished())); + connect(d->bs, SIGNAL(error(int)), SLOT(bs_error(int))); + + QByteArray spare = d->bs->read(); + + d->ss = new SecureStream(d->bs); + connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); + connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int))); + connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken())); + connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed())); + connect(d->ss, SIGNAL(error(int)), SLOT(ss_error(int))); + + d->server = host; + d->defRealm = defRealm; + + d->tls = tls; + + d->srv.startClientIn(genId()); + //d->srv.startServerIn(genId()); + //d->state = Connecting; + //d->jid = Jid(); + //d->server = QString(); +} + +ClientStream::~ClientStream() +{ + reset(); + delete d; +} + +void ClientStream::reset(bool all) +{ + d->reset(); + d->noopTimer.stop(); + + // delete securestream + delete d->ss; + d->ss = 0; + + // reset sasl + delete d->sasl; + d->sasl = 0; + + // client + if(d->mode == Client) { + // reset tls + if(d->tlsHandler) + d->tlsHandler->reset(); + + // reset connector + if(d->bs) { + d->bs->close(); + d->bs = 0; + } + d->conn->done(); + + // reset state machine + d->client.reset(); + } + // server + else { + if(d->tls) + d->tls->reset(); + + if(d->bs) { + d->bs->close(); + d->bs = 0; + } + + d->srv.reset(); + } + + if(all) + d->in.clear(); +} + +Jid ClientStream::jid() const +{ + return d->jid; +} + +void ClientStream::connectToServer(const Jid &jid, bool auth) +{ + reset(true); + d->state = Connecting; + d->jid = jid; + d->doAuth = auth; + d->server = d->jid.domain(); + + d->conn->connectToServer(d->server); +} + +void ClientStream::continueAfterWarning() +{ + if(d->state == WaitVersion) { + // if we don't have TLS yet, then we're never going to get it + if(!d->tls_warned && !d->using_tls) { + d->tls_warned = true; + d->state = WaitTLS; + warning(WarnNoTLS); + return; + } + d->state = Connecting; + processNext(); + } + else if(d->state == WaitTLS) { + d->state = Connecting; + processNext(); + } +} + +void ClientStream::accept() +{ + d->srv.host = d->server; + processNext(); +} + +bool ClientStream::isActive() const +{ + return (d->state != Idle) ? true: false; +} + +bool ClientStream::isAuthenticated() const +{ + return (d->state == Active) ? true: false; +} + +void ClientStream::setUsername(const QString &s) +{ + if(d->sasl) + d->sasl->setUsername(s); +} + +void ClientStream::setPassword(const QString &s) +{ + if(d->client.old) { + d->client.setPassword(s); + } + else { + if(d->sasl) + d->sasl->setPassword(s); + } +} + +void ClientStream::setRealm(const QString &s) +{ + if(d->sasl) + d->sasl->setRealm(s); +} + +void ClientStream::continueAfterParams() +{ + if(d->state == NeedParams) { + d->state = Connecting; + if(d->client.old) { + processNext(); + } + else { + if(d->sasl) + d->sasl->continueAfterParams(); + } + } +} + +void ClientStream::setResourceBinding(bool b) +{ + d->doBinding = b; +} + +void ClientStream::setNoopTime(int mills) +{ + d->noop_time = mills; + + if(d->state != Active) + return; + + if(d->noop_time == 0) { + d->noopTimer.stop(); + return; + } + d->noopTimer.start(d->noop_time); +} + +QString ClientStream::saslMechanism() const +{ + return d->client.saslMech(); +} + +int ClientStream::saslSSF() const +{ + return d->sasl_ssf; +} + +void ClientStream::setSASLMechanism(const QString &s) +{ + d->sasl_mech = s; +} + +void ClientStream::setLocalAddr(const QHostAddress &addr, Q_UINT16 port) +{ + d->haveLocalAddr = true; + d->localAddr = addr; + d->localPort = port; +} + +int ClientStream::errorCondition() const +{ + return d->errCond; +} + +QString ClientStream::errorText() const +{ + return d->errText; +} + +QDomElement ClientStream::errorAppSpec() const +{ + return d->errAppSpec; +} + +bool ClientStream::old() const +{ + return d->client.old; +} + +void ClientStream::close() +{ + if(d->state == Active) { + d->state = Closing; + d->client.shutdown(); + processNext(); + } + else if(d->state != Idle && d->state != Closing) { + reset(); + } +} + +QDomDocument & ClientStream::doc() const +{ + return d->client.doc; +} + +QString ClientStream::baseNS() const +{ + return NS_CLIENT; +} + +QString ClientStream::xhtmlImNS() const +{ + return NS_XHTML_IM; +} + +QString ClientStream::xhtmlNS() const +{ + return NS_XHTML; +} + +void ClientStream::setAllowPlain(bool b) +{ + d->allowPlain = b; +} + +void ClientStream::setRequireMutualAuth(bool b) +{ + d->mutualAuth = b; +} + +void ClientStream::setSSFRange(int low, int high) +{ + d->minimumSSF = low; + d->maximumSSF = high; +} + +void ClientStream::setOldOnly(bool b) +{ + d->oldOnly = b; +} + +bool ClientStream::stanzaAvailable() const +{ + return (!d->in.isEmpty()); +} + +Stanza ClientStream::read() +{ + if(d->in.isEmpty()) + return Stanza(); + else { + Stanza *sp = d->in.getFirst(); + Stanza s = *sp; + d->in.removeRef(sp); + return s; + } +} + +void ClientStream::write(const Stanza &s) +{ + if(d->state == Active) { + d->client.sendStanza(s.element()); + processNext(); + } +} + +void ClientStream::cr_connected() +{ + d->bs = d->conn->stream(); + connect(d->bs, SIGNAL(connectionClosed()), SLOT(bs_connectionClosed())); + connect(d->bs, SIGNAL(delayedCloseFinished()), SLOT(bs_delayedCloseFinished())); + + QByteArray spare = d->bs->read(); + + d->ss = new SecureStream(d->bs); + connect(d->ss, SIGNAL(readyRead()), SLOT(ss_readyRead())); + connect(d->ss, SIGNAL(bytesWritten(int)), SLOT(ss_bytesWritten(int))); + connect(d->ss, SIGNAL(tlsHandshaken()), SLOT(ss_tlsHandshaken())); + connect(d->ss, SIGNAL(tlsClosed()), SLOT(ss_tlsClosed())); + connect(d->ss, SIGNAL(error(int)), SLOT(ss_error(int))); + + //d->client.startDialbackOut("andbit.net", "im.pyxa.org"); + //d->client.startServerOut(d->server); + + d->client.startClientOut(d->jid, d->oldOnly, d->conn->useSSL(), d->doAuth); + d->client.setAllowTLS(d->tlsHandler ? true: false); + d->client.setAllowBind(d->doBinding); + d->client.setAllowPlain(d->allowPlain); + + /*d->client.jid = d->jid; + d->client.server = d->server; + d->client.allowPlain = d->allowPlain; + d->client.oldOnly = d->oldOnly; + d->client.sasl_mech = d->sasl_mech; + d->client.doTLS = d->tlsHandler ? true: false; + d->client.doBinding = d->doBinding;*/ + + QGuardedPtr<QObject> self = this; + connected(); + if(!self) + return; + + // immediate SSL? + if(d->conn->useSSL()) { + d->using_tls = true; + d->ss->startTLSClient(d->tlsHandler, d->server, spare); + } + else { + d->client.addIncomingData(spare); + processNext(); + } +} + +void ClientStream::cr_error() +{ + reset(); + error(ErrConnection); +} + +void ClientStream::bs_connectionClosed() +{ + reset(); + connectionClosed(); +} + +void ClientStream::bs_delayedCloseFinished() +{ + // we don't care about this (we track all important data ourself) +} + +void ClientStream::bs_error(int) +{ + // TODO +} + +void ClientStream::ss_readyRead() +{ + QByteArray a = d->ss->read(); + +#ifdef XMPP_DEBUG + QCString cs(a.data(), a.size()+1); + fprintf(stderr, "ClientStream: recv: %d [%s]\n", a.size(), cs.data()); +#endif + + if(d->mode == Client) + d->client.addIncomingData(a); + else + d->srv.addIncomingData(a); + if(d->notify & CoreProtocol::NRecv) { +#ifdef XMPP_DEBUG + printf("We needed data, so let's process it\n"); +#endif + processNext(); + } +} + +void ClientStream::ss_bytesWritten(int bytes) +{ + if(d->mode == Client) + d->client.outgoingDataWritten(bytes); + else + d->srv.outgoingDataWritten(bytes); + + if(d->notify & CoreProtocol::NSend) { +#ifdef XMPP_DEBUG + printf("We were waiting for data to be written, so let's process\n"); +#endif + processNext(); + } +} + +void ClientStream::ss_tlsHandshaken() +{ + QGuardedPtr<QObject> self = this; + securityLayerActivated(LayerTLS); + if(!self) + return; + processNext(); +} + +void ClientStream::ss_tlsClosed() +{ + reset(); + connectionClosed(); +} + +void ClientStream::ss_error(int x) +{ + if(x == SecureStream::ErrTLS) { + reset(); + d->errCond = TLSFail; + error(ErrTLS); + } + else { + reset(); + error(ErrSecurityLayer); + } +} + +void ClientStream::sasl_clientFirstStep(const QString &mech, const QByteArray *stepData) +{ + d->client.setSASLFirst(mech, stepData ? *stepData : QByteArray()); + //d->client.sasl_mech = mech; + //d->client.sasl_firstStep = stepData ? true : false; + //d->client.sasl_step = stepData ? *stepData : QByteArray(); + + processNext(); +} + +void ClientStream::sasl_nextStep(const QByteArray &stepData) +{ + if(d->mode == Client) + d->client.setSASLNext(stepData); + //d->client.sasl_step = stepData; + else + d->srv.setSASLNext(stepData); + //d->srv.sasl_step = stepData; + + processNext(); +} + +void ClientStream::sasl_needParams(bool user, bool authzid, bool pass, bool realm) +{ +#ifdef XMPP_DEBUG + printf("need params: %d,%d,%d,%d\n", user, authzid, pass, realm); +#endif + if(authzid && !user) { + d->sasl->setAuthzid(d->jid.bare()); + //d->sasl->setAuthzid("infiniti.homelesshackers.org"); + } + if(user || pass || realm) { + d->state = NeedParams; + needAuthParams(user, pass, realm); + } + else + d->sasl->continueAfterParams(); +} + +void ClientStream::sasl_authCheck(const QString &user, const QString &) +{ +//#ifdef XMPP_DEBUG +// printf("authcheck: [%s], [%s]\n", user.latin1(), authzid.latin1()); +//#endif + QString u = user; + int n = u.find('@'); + if(n != -1) + u.truncate(n); + d->srv.user = u; + d->sasl->continueAfterAuthCheck(); +} + +void ClientStream::sasl_authenticated() +{ +#ifdef XMPP_DEBUG + printf("sasl authed!!\n"); +#endif + d->sasl_ssf = d->sasl->ssf(); + + if(d->mode == Server) { + d->srv.setSASLAuthed(); + processNext(); + } +} + +void ClientStream::sasl_error(int) +{ +//#ifdef XMPP_DEBUG +// printf("sasl error: %d\n", c); +//#endif + // has to be auth error + int x = convertedSASLCond(); + reset(); + d->errCond = x; + error(ErrAuth); +} + +void ClientStream::srvProcessNext() +{ + while(1) { + printf("Processing step...\n"); + if(!d->srv.processStep()) { + int need = d->srv.need; + if(need == CoreProtocol::NNotify) { + d->notify = d->srv.notify; + if(d->notify & CoreProtocol::NSend) + printf("More data needs to be written to process next step\n"); + if(d->notify & CoreProtocol::NRecv) + printf("More data is needed to process next step\n"); + } + else if(need == CoreProtocol::NSASLMechs) { + if(!d->sasl) { + d->sasl = new QCA::SASL; + connect(d->sasl, SIGNAL(authCheck(const QString &, const QString &)), SLOT(sasl_authCheck(const QString &, const QString &))); + connect(d->sasl, SIGNAL(nextStep(const QByteArray &)), SLOT(sasl_nextStep(const QByteArray &))); + connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated())); + connect(d->sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); + + //d->sasl->setAllowAnonymous(false); + //d->sasl->setRequirePassCredentials(true); + //d->sasl->setExternalAuthID("localhost"); + + d->sasl->setMinimumSSF(0); + d->sasl->setMaximumSSF(256); + + QStringList list; + // TODO: d->server is probably wrong here + if(!d->sasl->startServer("xmpp", d->server, d->defRealm, &list)) { + printf("Error initializing SASL\n"); + return; + } + d->sasl_mechlist = list; + } + d->srv.setSASLMechList(d->sasl_mechlist); + continue; + } + else if(need == CoreProtocol::NStartTLS) { + printf("Need StartTLS\n"); + if(!d->tls->startServer()) { + printf("unable to start server!\n"); + // TODO + return; + } + QByteArray a = d->srv.spare; + d->ss->startTLSServer(d->tls, a); + } + else if(need == CoreProtocol::NSASLFirst) { + printf("Need SASL First Step\n"); + QByteArray a = d->srv.saslStep(); + d->sasl->putServerFirstStep(d->srv.saslMech(), a); + } + else if(need == CoreProtocol::NSASLNext) { + printf("Need SASL Next Step\n"); + QByteArray a = d->srv.saslStep(); + QCString cs(a.data(), a.size()+1); + printf("[%s]\n", cs.data()); + d->sasl->putStep(a); + } + else if(need == CoreProtocol::NSASLLayer) { + } + + // now we can announce stanzas + //if(!d->in.isEmpty()) + // readyRead(); + return; + } + + d->notify = 0; + + int event = d->srv.event; + printf("event: %d\n", event); + switch(event) { + case CoreProtocol::EError: { + printf("Error! Code=%d\n", d->srv.errorCode); + reset(); + error(ErrProtocol); + //handleError(); + return; + } + case CoreProtocol::ESend: { + QByteArray a = d->srv.takeOutgoingData(); + QCString cs(a.size()+1); + memcpy(cs.data(), a.data(), a.size()); + printf("Need Send: {%s}\n", cs.data()); + d->ss->write(a); + break; + } + case CoreProtocol::ERecvOpen: { + printf("Break (RecvOpen)\n"); + + // calculate key + QCString str = QCA::SHA1::hashToString("secret").utf8(); + str = QCA::SHA1::hashToString(str + "im.pyxa.org").utf8(); + str = QCA::SHA1::hashToString(str + d->srv.id.utf8()).utf8(); + d->srv.setDialbackKey(str); + + //d->srv.setDialbackKey("3c5d721ea2fcc45b163a11420e4e358f87e3142a"); + + if(d->srv.to != d->server) { + // host-gone, host-unknown, see-other-host + d->srv.shutdownWithError(CoreProtocol::HostUnknown); + } + else + d->srv.setFrom(d->server); + break; + } + case CoreProtocol::ESASLSuccess: { + printf("Break SASL Success\n"); + disconnect(d->sasl, SIGNAL(error(int)), this, SLOT(sasl_error(int))); + QByteArray a = d->srv.spare; + d->ss->setLayerSASL(d->sasl, a); + break; + } + case CoreProtocol::EPeerClosed: { + // TODO: this isn' an error + printf("peer closed\n"); + reset(); + error(ErrProtocol); + return; + } + } + } +} + +void ClientStream::doReadyRead() +{ + //QGuardedPtr<QObject> self = this; + readyRead(); + //if(!self) + // return; + //d->in_rrsig = false; +} + +void ClientStream::processNext() +{ + if(d->mode == Server) { + srvProcessNext(); + return; + } + + QGuardedPtr<QObject> self = this; + + while(1) { +#ifdef XMPP_DEBUG + printf("Processing step...\n"); +#endif + bool ok = d->client.processStep(); + // deal with send/received items + for(QValueList<XmlProtocol::TransferItem>::ConstIterator it = d->client.transferItemList.begin(); it != d->client.transferItemList.end(); ++it) { + const XmlProtocol::TransferItem &i = *it; + if(i.isExternal) + continue; + QString str; + if(i.isString) { + // skip whitespace pings + if(i.str.stripWhiteSpace().isEmpty()) + continue; + str = i.str; + } + else + str = d->client.elementToString(i.elem); + if(i.isSent) + outgoingXml(str); + else + incomingXml(str); + } + + if(!ok) { + bool cont = handleNeed(); + + // now we can announce stanzas + //if(!d->in_rrsig && !d->in.isEmpty()) { + if(!d->in.isEmpty()) { + //d->in_rrsig = true; + QTimer::singleShot(0, this, SLOT(doReadyRead())); + } + + if(cont) + continue; + return; + } + + int event = d->client.event; + d->notify = 0; + switch(event) { + case CoreProtocol::EError: { +#ifdef XMPP_DEBUG + printf("Error! Code=%d\n", d->client.errorCode); +#endif + handleError(); + return; + } + case CoreProtocol::ESend: { + QByteArray a = d->client.takeOutgoingData(); +#ifdef XMPP_DEBUG + QCString cs(a.size()+1); + memcpy(cs.data(), a.data(), a.size()); + printf("Need Send: {%s}\n", cs.data()); +#endif + d->ss->write(a); + break; + } + case CoreProtocol::ERecvOpen: { +#ifdef XMPP_DEBUG + printf("Break (RecvOpen)\n"); +#endif + +#ifdef XMPP_TEST + QString s = QString("handshake success (lang=[%1]").arg(d->client.lang); + if(!d->client.from.isEmpty()) + s += QString(", from=[%1]").arg(d->client.from); + s += ')'; + TD::msg(s); +#endif + + if(d->client.old) { + d->state = WaitVersion; + warning(WarnOldVersion); + return; + } + break; + } + case CoreProtocol::EFeatures: { +#ifdef XMPP_DEBUG + printf("Break (Features)\n"); +#endif + if(!d->tls_warned && !d->using_tls && !d->client.features.tls_supported) { + d->tls_warned = true; + d->state = WaitTLS; + warning(WarnNoTLS); + return; + } + break; + } + case CoreProtocol::ESASLSuccess: { +#ifdef XMPP_DEBUG + printf("Break SASL Success\n"); +#endif + break; + } + case CoreProtocol::EReady: { +#ifdef XMPP_DEBUG + printf("Done!\n"); +#endif + // grab the JID, in case it changed + // TODO: d->jid = d->client.jid; + d->state = Active; + setNoopTime(d->noop_time); + authenticated(); + if(!self) + return; + break; + } + case CoreProtocol::EPeerClosed: { +#ifdef XMPP_DEBUG + printf("DocumentClosed\n"); +#endif + reset(); + connectionClosed(); + return; + } + case CoreProtocol::EStanzaReady: { +#ifdef XMPP_DEBUG + printf("StanzaReady\n"); +#endif + // store the stanza for now, announce after processing all events + Stanza s = createStanza(d->client.recvStanza()); + if(s.isNull()) + break; + d->in.append(new Stanza(s)); + break; + } + case CoreProtocol::EStanzaSent: { +#ifdef XMPP_DEBUG + printf("StanzasSent\n"); +#endif + stanzaWritten(); + if(!self) + return; + break; + } + case CoreProtocol::EClosed: { +#ifdef XMPP_DEBUG + printf("Closed\n"); +#endif + reset(); + delayedCloseFinished(); + return; + } + } + } +} + +bool ClientStream::handleNeed() +{ + int need = d->client.need; + if(need == CoreProtocol::NNotify) { + d->notify = d->client.notify; +#ifdef XMPP_DEBUG + if(d->notify & CoreProtocol::NSend) + printf("More data needs to be written to process next step\n"); + if(d->notify & CoreProtocol::NRecv) + printf("More data is needed to process next step\n"); +#endif + return false; + } + + d->notify = 0; + switch(need) { + case CoreProtocol::NStartTLS: { +#ifdef XMPP_DEBUG + printf("Need StartTLS\n"); +#endif + d->using_tls = true; + d->ss->startTLSClient(d->tlsHandler, d->server, d->client.spare); + return false; + } + case CoreProtocol::NSASLFirst: { +#ifdef XMPP_DEBUG + printf("Need SASL First Step\n"); +#endif + // no SASL plugin? fall back to Simple SASL + if(!QCA::isSupported(QCA::CAP_SASL)) { + // Simple SASL needs MD5. do we have that either? + if(!QCA::isSupported(QCA::CAP_MD5)) + QCA::insertProvider(createProviderHash()); + QCA::insertProvider(createProviderSimpleSASL()); + } + + d->sasl = new QCA::SASL; + connect(d->sasl, SIGNAL(clientFirstStep(const QString &, const QByteArray *)), SLOT(sasl_clientFirstStep(const QString &, const QByteArray *))); + connect(d->sasl, SIGNAL(nextStep(const QByteArray &)), SLOT(sasl_nextStep(const QByteArray &))); + connect(d->sasl, SIGNAL(needParams(bool, bool, bool, bool)), SLOT(sasl_needParams(bool, bool, bool, bool))); + connect(d->sasl, SIGNAL(authenticated()), SLOT(sasl_authenticated())); + connect(d->sasl, SIGNAL(error(int)), SLOT(sasl_error(int))); + + if(d->haveLocalAddr) + d->sasl->setLocalAddr(d->localAddr, d->localPort); + if(d->conn->havePeerAddress()) + d->sasl->setRemoteAddr(d->conn->peerAddress(), d->conn->peerPort()); + + d->sasl->setAllowAnonymous(false); + + //d->sasl_mech = "ANONYMOUS"; + //d->sasl->setRequirePassCredentials(true); + + //d->sasl->setExternalAuthID("localhost"); + //d->sasl->setExternalSSF(64); + //d->sasl_mech = "EXTERNAL"; + + d->sasl->setAllowPlain(d->allowPlain); + d->sasl->setRequireMutualAuth(d->mutualAuth); + + d->sasl->setMinimumSSF(d->minimumSSF); + d->sasl->setMaximumSSF(d->maximumSSF); + + QStringList ml; + if(!d->sasl_mech.isEmpty()) + ml += d->sasl_mech; + else + ml = d->client.features.sasl_mechs; + + if(!d->sasl->startClient("xmpp", d->server, ml, true)) { + int x = convertedSASLCond(); + reset(); + d->errCond = x; + error(ErrAuth); + return false; + } + return false; + } + case CoreProtocol::NSASLNext: { +#ifdef XMPP_DEBUG + printf("Need SASL Next Step\n"); +#endif + QByteArray a = d->client.saslStep(); + d->sasl->putStep(a); + return false; + } + case CoreProtocol::NSASLLayer: { + // SecureStream will handle the errors from this point + disconnect(d->sasl, SIGNAL(error(int)), this, SLOT(sasl_error(int))); + d->ss->setLayerSASL(d->sasl, d->client.spare); + if(d->sasl_ssf > 0) { + QGuardedPtr<QObject> self = this; + securityLayerActivated(LayerSASL); + if(!self) + return false; + } + break; + } + case CoreProtocol::NPassword: { +#ifdef XMPP_DEBUG + printf("Need Password\n"); +#endif + d->state = NeedParams; + needAuthParams(false, true, false); + return false; + } + } + + return true; +} + +int ClientStream::convertedSASLCond() const +{ + int x = d->sasl->errorCondition(); + if(x == QCA::SASL::NoMech) + return NoMech; + else if(x == QCA::SASL::BadProto) + return BadProto; + else if(x == QCA::SASL::BadServ) + return BadServ; + else if(x == QCA::SASL::TooWeak) + return MechTooWeak; + else + return GenericAuthError; +} + +void ClientStream::doNoop() +{ + if(d->state == Active) { +#ifdef XMPP_DEBUG + printf("doPing\n"); +#endif + d->client.sendWhitespace(); + processNext(); + } +} + +void ClientStream::writeDirect(const QString &s) +{ + if(d->state == Active) { +#ifdef XMPP_DEBUG + printf("writeDirect\n"); +#endif + d->client.sendDirect(s); + processNext(); + } +} + +void ClientStream::handleError() +{ + int c = d->client.errorCode; + if(c == CoreProtocol::ErrParse) { + reset(); + error(ErrParse); + } + else if(c == CoreProtocol::ErrProtocol) { + reset(); + error(ErrProtocol); + } + else if(c == CoreProtocol::ErrStream) { + int x = d->client.errCond; + QString text = d->client.errText; + QDomElement appSpec = d->client.errAppSpec; + + int connErr = -1; + int strErr = -1; + + switch(x) { + case CoreProtocol::BadFormat: { break; } // should NOT happen (we send the right format) + case CoreProtocol::BadNamespacePrefix: { break; } // should NOT happen (we send prefixes) + case CoreProtocol::Conflict: { strErr = Conflict; break; } + case CoreProtocol::ConnectionTimeout: { strErr = ConnectionTimeout; break; } + case CoreProtocol::HostGone: { connErr = HostGone; break; } + case CoreProtocol::HostUnknown: { connErr = HostUnknown; break; } + case CoreProtocol::ImproperAddressing: { break; } // should NOT happen (we aren't a server) + case CoreProtocol::InternalServerError: { strErr = InternalServerError; break; } + case CoreProtocol::InvalidFrom: { strErr = InvalidFrom; break; } + case CoreProtocol::InvalidId: { break; } // should NOT happen (clients don't specify id) + case CoreProtocol::InvalidNamespace: { break; } // should NOT happen (we set the right ns) + case CoreProtocol::InvalidXml: { strErr = InvalidXml; break; } // shouldn't happen either, but just in case ... + case CoreProtocol::StreamNotAuthorized: { break; } // should NOT happen (we're not stupid) + case CoreProtocol::PolicyViolation: { strErr = PolicyViolation; break; } + case CoreProtocol::RemoteConnectionFailed: { connErr = RemoteConnectionFailed; break; } + case CoreProtocol::ResourceConstraint: { strErr = ResourceConstraint; break; } + case CoreProtocol::RestrictedXml: { strErr = InvalidXml; break; } // group with this one + case CoreProtocol::SeeOtherHost: { connErr = SeeOtherHost; break; } + case CoreProtocol::SystemShutdown: { strErr = SystemShutdown; break; } + case CoreProtocol::UndefinedCondition: { break; } // leave as null error + case CoreProtocol::UnsupportedEncoding: { break; } // should NOT happen (we send good encoding) + case CoreProtocol::UnsupportedStanzaType: { break; } // should NOT happen (we're not stupid) + case CoreProtocol::UnsupportedVersion: { connErr = UnsupportedVersion; break; } + case CoreProtocol::XmlNotWellFormed: { strErr = InvalidXml; break; } // group with this one + default: { break; } + } + + reset(); + + d->errText = text; + d->errAppSpec = appSpec; + if(connErr != -1) { + d->errCond = connErr; + error(ErrNeg); + } + else { + if(strErr != -1) + d->errCond = strErr; + else + d->errCond = GenericStreamError; + error(ErrStream); + } + } + else if(c == CoreProtocol::ErrStartTLS) { + reset(); + d->errCond = TLSStart; + error(ErrTLS); + } + else if(c == CoreProtocol::ErrAuth) { + int x = d->client.errCond; + int r = GenericAuthError; + if(d->client.old) { + if(x == 401) // not authorized + r = NotAuthorized; + else if(x == 409) // conflict + r = GenericAuthError; + else if(x == 406) // not acceptable (this should NOT happen) + r = GenericAuthError; + } + else { + switch(x) { + case CoreProtocol::Aborted: { r = GenericAuthError; break; } // should NOT happen (we never send <abort/>) + case CoreProtocol::IncorrectEncoding: { r = GenericAuthError; break; } // should NOT happen + case CoreProtocol::InvalidAuthzid: { r = InvalidAuthzid; break; } + case CoreProtocol::InvalidMech: { r = InvalidMech; break; } + case CoreProtocol::MechTooWeak: { r = MechTooWeak; break; } + case CoreProtocol::NotAuthorized: { r = NotAuthorized; break; } + case CoreProtocol::TemporaryAuthFailure: { r = TemporaryAuthFailure; break; } + } + } + reset(); + d->errCond = r; + error(ErrAuth); + } + else if(c == CoreProtocol::ErrPlain) { + reset(); + d->errCond = NoMech; + error(ErrAuth); + } + else if(c == CoreProtocol::ErrBind) { + int r = -1; + if(d->client.errCond == CoreProtocol::BindBadRequest) { + // should NOT happen + } + else if(d->client.errCond == CoreProtocol::BindNotAllowed) { + r = BindNotAllowed; + } + else if(d->client.errCond == CoreProtocol::BindConflict) { + r = BindConflict; + } + + if(r != -1) { + reset(); + d->errCond = r; + error(ErrBind); + } + else { + reset(); + error(ErrProtocol); + } + } +} + +//---------------------------------------------------------------------------- +// Debug +//---------------------------------------------------------------------------- +Debug::~Debug() +{ +} + +#ifdef XMPP_TEST +TD::TD() +{ +} + +TD::~TD() +{ +} + +void TD::msg(const QString &s) +{ + if(debug_ptr) + debug_ptr->msg(s); +} + +void TD::outgoingTag(const QString &s) +{ + if(debug_ptr) + debug_ptr->outgoingTag(s); +} + +void TD::incomingTag(const QString &s) +{ + if(debug_ptr) + debug_ptr->incomingTag(s); +} + +void TD::outgoingXml(const QDomElement &e) +{ + if(debug_ptr) + debug_ptr->outgoingXml(e); +} + +void TD::incomingXml(const QDomElement &e) +{ + if(debug_ptr) + debug_ptr->incomingXml(e); +} +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/td.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/td.h new file mode 100644 index 00000000..b636e190 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/td.h @@ -0,0 +1,20 @@ +#ifndef TESTDEBUG_H +#define TESTDEBUG_H + +#include<qdom.h> + +class TD +{ +public: + TD(); + ~TD(); + + static void msg(const QString &); + static void outgoingTag(const QString &); + static void incomingTag(const QString &); + static void outgoingXml(const QDomElement &); + static void incomingXml(const QDomElement &); +}; + +#endif + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/tlshandler.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/tlshandler.cpp new file mode 100644 index 00000000..f3ac0067 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/tlshandler.cpp @@ -0,0 +1,138 @@ +/* + * tlshandler.cpp - abstract wrapper for TLS + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp.h" + +#include<qtimer.h> +#include"qca.h" + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// TLSHandler +//---------------------------------------------------------------------------- +TLSHandler::TLSHandler(QObject *parent) +:QObject(parent) +{ +} + +TLSHandler::~TLSHandler() +{ +} + + +//---------------------------------------------------------------------------- +// QCATLSHandler +//---------------------------------------------------------------------------- +class QCATLSHandler::Private +{ +public: + QCA::TLS *tls; + int state, err; +}; + +QCATLSHandler::QCATLSHandler(QCA::TLS *parent) +:TLSHandler(parent) +{ + d = new Private; + d->tls = parent; + connect(d->tls, SIGNAL(handshaken()), SLOT(tls_handshaken())); + connect(d->tls, SIGNAL(readyRead()), SLOT(tls_readyRead())); + connect(d->tls, SIGNAL(readyReadOutgoing(int)), SLOT(tls_readyReadOutgoing(int))); + connect(d->tls, SIGNAL(closed()), SLOT(tls_closed())); + connect(d->tls, SIGNAL(error(int)), SLOT(tls_error(int))); + d->state = 0; + d->err = -1; +} + +QCATLSHandler::~QCATLSHandler() +{ + delete d; +} + +QCA::TLS *QCATLSHandler::tls() const +{ + return d->tls; +} + +int QCATLSHandler::tlsError() const +{ + return d->err; +} + +void QCATLSHandler::reset() +{ + d->tls->reset(); + d->state = 0; +} + +void QCATLSHandler::startClient(const QString &host) +{ + d->state = 0; + d->err = -1; + if(!d->tls->startClient(host)) + QTimer::singleShot(0, this, SIGNAL(fail())); +} + +void QCATLSHandler::write(const QByteArray &a) +{ + d->tls->write(a); +} + +void QCATLSHandler::writeIncoming(const QByteArray &a) +{ + d->tls->writeIncoming(a); +} + +void QCATLSHandler::continueAfterHandshake() +{ + if(d->state == 2) { + success(); + d->state = 3; + } +} + +void QCATLSHandler::tls_handshaken() +{ + d->state = 2; + tlsHandshaken(); +} + +void QCATLSHandler::tls_readyRead() +{ + readyRead(d->tls->read()); +} + +void QCATLSHandler::tls_readyReadOutgoing(int plainBytes) +{ + readyReadOutgoing(d->tls->readOutgoing(), plainBytes); +} + +void QCATLSHandler::tls_closed() +{ + closed(); +} + +void QCATLSHandler::tls_error(int x) +{ + d->err = x; + d->state = 0; + fail(); +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp new file mode 100644 index 00000000..c70a04a9 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.cpp @@ -0,0 +1,543 @@ +/* + * xmlprotocol.cpp - state machine for 'jabber-like' protocols + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmlprotocol.h" + +#include"bytestream.h" + +using namespace XMPP; + +// stripExtraNS +// +// This function removes namespace information from various nodes for +// display purposes only (the element is pretty much useless for processing +// after this). We do this because QXml is a bit overzealous about outputting +// redundant namespaces. +static QDomElement stripExtraNS(const QDomElement &e) +{ + // find closest parent with a namespace + QDomNode par = e.parentNode(); + while(!par.isNull() && par.namespaceURI().isNull()) + par = par.parentNode(); + bool noShowNS = false; + if(!par.isNull() && par.namespaceURI() == e.namespaceURI()) + noShowNS = true; + + // build qName (prefix:localName) + QString qName; + if(!e.prefix().isEmpty()) + qName = e.prefix() + ':' + e.localName(); + else + qName = e.tagName(); + + QDomElement i; + uint x; + if(noShowNS) + i = e.ownerDocument().createElement(qName); + else + i = e.ownerDocument().createElementNS(e.namespaceURI(), qName); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) { + QDomAttr a = al.item(x).cloneNode().toAttr(); + + // don't show xml namespace + if(a.namespaceURI() == NS_XML) + i.setAttribute(QString("xml:") + a.name(), a.value()); + else + i.setAttributeNodeNS(a); + } + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(stripExtraNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + return i; +} + +// xmlToString +// +// This function converts a QDomElement into a QString, using stripExtraNS +// to make it pretty. +static QString xmlToString(const QDomElement &e, const QString &fakeNS, const QString &fakeQName, bool clip) +{ + QDomElement i = e.cloneNode().toElement(); + + // It seems QDom can only have one namespace attribute at a time (see docElement 'HACK'). + // Fortunately we only need one kind depending on the input, so it is specified here. + QDomElement fake = e.ownerDocument().createElementNS(fakeNS, fakeQName); + fake.appendChild(i); + fake = stripExtraNS(fake); + QString out; + { + QTextStream ts(&out, IO_WriteOnly); + fake.firstChild().save(ts, 0); + } + // 'clip' means to remove any unwanted (and unneeded) characters, such as a trailing newline + if(clip) { + int n = out.findRev('>'); + out.truncate(n+1); + } + return out; +} + +// createRootXmlTags +// +// This function creates three QStrings, one being an <?xml .. ?> processing +// instruction, and the others being the opening and closing tags of an +// element, <foo> and </foo>. This basically allows us to get the raw XML +// text needed to open/close an XML stream, without resorting to generating +// the XML ourselves. This function uses QDom to do the generation, which +// ensures proper encoding and entity output. +static void createRootXmlTags(const QDomElement &root, QString *xmlHeader, QString *tagOpen, QString *tagClose) +{ + QDomElement e = root.cloneNode(false).toElement(); + + // insert a dummy element to ensure open and closing tags are generated + QDomElement dummy = e.ownerDocument().createElement("dummy"); + e.appendChild(dummy); + + // convert to xml->text + QString str; + { + QTextStream ts(&str, IO_WriteOnly); + e.save(ts, 0); + } + + // parse the tags out + int n = str.find('<'); + int n2 = str.find('>', n); + ++n2; + *tagOpen = str.mid(n, n2-n); + n2 = str.findRev('>'); + n = str.findRev('<'); + ++n2; + *tagClose = str.mid(n, n2-n); + + // generate a nice xml processing header + *xmlHeader = "<?xml version=\"1.0\"?>"; +} + +//---------------------------------------------------------------------------- +// Protocol +//---------------------------------------------------------------------------- +XmlProtocol::TransferItem::TransferItem() +{ +} + +XmlProtocol::TransferItem::TransferItem(const QString &_str, bool sent, bool external) +{ + isString = true; + isSent = sent; + isExternal = external; + str = _str; +} + +XmlProtocol::TransferItem::TransferItem(const QDomElement &_elem, bool sent, bool external) +{ + isString = false; + isSent = sent; + isExternal = external; + elem = _elem; +} + +XmlProtocol::XmlProtocol() +{ + init(); +} + +XmlProtocol::~XmlProtocol() +{ +} + +void XmlProtocol::init() +{ + incoming = false; + peerClosed = false; + closeWritten = false; +} + +void XmlProtocol::reset() +{ + init(); + + elem = QDomElement(); + tagOpen = QString(); + tagClose = QString(); + xml.reset(); + outData.resize(0); + trackQueue.clear(); + transferItemList.clear(); +} + +void XmlProtocol::addIncomingData(const QByteArray &a) +{ + xml.appendData(a); +} + +QByteArray XmlProtocol::takeOutgoingData() +{ + QByteArray a = outData.copy(); + outData.resize(0); + return a; +} + +void XmlProtocol::outgoingDataWritten(int bytes) +{ + for(QValueList<TrackItem>::Iterator it = trackQueue.begin(); it != trackQueue.end();) { + TrackItem &i = *it; + + // enough bytes? + if(bytes < i.size) { + i.size -= bytes; + break; + } + int type = i.type; + int id = i.id; + int size = i.size; + bytes -= i.size; + it = trackQueue.remove(it); + + if(type == TrackItem::Raw) { + // do nothing + } + else if(type == TrackItem::Close) { + closeWritten = true; + } + else if(type == TrackItem::Custom) { + itemWritten(id, size); + } + } +} + +bool XmlProtocol::processStep() +{ + Parser::Event pe; + notify = 0; + transferItemList.clear(); + + if(state != Closing && (state == RecvOpen || stepAdvancesParser())) { + // if we get here, then it's because we're in some step that advances the parser + pe = xml.readNext(); + if(!pe.isNull()) { + // note: error/close events should be handled for ALL steps, so do them here + switch(pe.type()) { + case Parser::Event::DocumentOpen: { + transferItemList += TransferItem(pe.actualString(), false); + + //stringRecv(pe.actualString()); + break; + } + case Parser::Event::DocumentClose: { + transferItemList += TransferItem(pe.actualString(), false); + + //stringRecv(pe.actualString()); + if(incoming) { + sendTagClose(); + event = ESend; + peerClosed = true; + state = Closing; + } + else { + event = EPeerClosed; + } + return true; + } + case Parser::Event::Element: { + transferItemList += TransferItem(pe.element(), false); + + //elementRecv(pe.element()); + break; + } + case Parser::Event::Error: { + if(incoming) { + // If we get a parse error during the initial element exchange, + // flip immediately into 'open' mode so that we can report an error. + if(state == RecvOpen) { + sendTagOpen(); + state = Open; + } + return handleError(); + } + else { + event = EError; + errorCode = ErrParse; + return true; + } + } + } + } + else { + if(state == RecvOpen || stepRequiresElement()) { + need = NNotify; + notify |= NRecv; + return false; + } + } + } + + return baseStep(pe); +} + +QString XmlProtocol::xmlEncoding() const +{ + return xml.encoding(); +} + +QString XmlProtocol::elementToString(const QDomElement &e, bool clip) +{ + if(elem.isNull()) + elem = elemDoc.importNode(docElement(), true).toElement(); + + // Determine the appropriate 'fakeNS' to use + QString ns; + + // first, check root namespace + QString pre = e.prefix(); + if(pre.isNull()) + pre = ""; + if(pre == elem.prefix()) { + ns = elem.namespaceURI(); + } + else { + // scan the root attributes for 'xmlns' (oh joyous hacks) + QDomNamedNodeMap al = elem.attributes(); + uint n; + for(n = 0; n < al.count(); ++n) { + QDomAttr a = al.item(n).toAttr(); + QString s = a.name(); + int x = s.find(':'); + if(x != -1) + s = s.mid(x+1); + else + s = ""; + if(pre == s) { + ns = a.value(); + break; + } + } + if(n >= al.count()) { + // if we get here, then no appropriate ns was found. use root then.. + ns = elem.namespaceURI(); + } + } + + // build qName + QString qn; + if(!elem.prefix().isEmpty()) + qn = elem.prefix() + ':'; + qn += elem.localName(); + + // make the string + return xmlToString(e, ns, qn, clip); +} + +bool XmlProtocol::stepRequiresElement() const +{ + // default returns false + return false; +} + +void XmlProtocol::itemWritten(int, int) +{ + // default does nothing +} + +void XmlProtocol::stringSend(const QString &) +{ + // default does nothing +} + +void XmlProtocol::stringRecv(const QString &) +{ + // default does nothing +} + +void XmlProtocol::elementSend(const QDomElement &) +{ + // default does nothing +} + +void XmlProtocol::elementRecv(const QDomElement &) +{ + // default does nothing +} + +void XmlProtocol::startConnect() +{ + incoming = false; + state = SendOpen; +} + +void XmlProtocol::startAccept() +{ + incoming = true; + state = RecvOpen; +} + +bool XmlProtocol::close() +{ + sendTagClose(); + event = ESend; + state = Closing; + return true; +} + +int XmlProtocol::writeString(const QString &s, int id, bool external) +{ + transferItemList += TransferItem(s, true, external); + return internalWriteString(s, TrackItem::Custom, id); +} + +int XmlProtocol::writeElement(const QDomElement &e, int id, bool external, bool clip) +{ + if(e.isNull()) + return 0; + transferItemList += TransferItem(e, true, external); + + //elementSend(e); + QString out = elementToString(e, clip); + return internalWriteString(out, TrackItem::Custom, id); +} + +QByteArray XmlProtocol::resetStream() +{ + // reset the state + if(incoming) + state = RecvOpen; + else + state = SendOpen; + + // grab unprocessed data before resetting + QByteArray spare = xml.unprocessed(); + xml.reset(); + return spare; +} + +int XmlProtocol::internalWriteData(const QByteArray &a, TrackItem::Type t, int id) +{ + TrackItem i; + i.type = t; + i.id = id; + i.size = a.size(); + trackQueue += i; + + ByteStream::appendArray(&outData, a); + return a.size(); +} + +int XmlProtocol::internalWriteString(const QString &s, TrackItem::Type t, int id) +{ + QCString cs = s.utf8(); + QByteArray a(cs.length()); + memcpy(a.data(), cs.data(), a.size()); + return internalWriteData(a, t, id); +} + +void XmlProtocol::sendTagOpen() +{ + if(elem.isNull()) + elem = elemDoc.importNode(docElement(), true).toElement(); + + QString xmlHeader; + createRootXmlTags(elem, &xmlHeader, &tagOpen, &tagClose); + + QString s; + s += xmlHeader + '\n'; + s += tagOpen + '\n'; + + transferItemList += TransferItem(xmlHeader, true); + transferItemList += TransferItem(tagOpen, true); + + //stringSend(xmlHeader); + //stringSend(tagOpen); + internalWriteString(s, TrackItem::Raw); +} + +void XmlProtocol::sendTagClose() +{ + transferItemList += TransferItem(tagClose, true); + + //stringSend(tagClose); + internalWriteString(tagClose, TrackItem::Close); +} + +bool XmlProtocol::baseStep(const Parser::Event &pe) +{ + // Basic + if(state == SendOpen) { + sendTagOpen(); + event = ESend; + if(incoming) + state = Open; + else + state = RecvOpen; + return true; + } + else if(state == RecvOpen) { + if(incoming) + state = SendOpen; + else + state = Open; + + // note: event will always be DocumentOpen here + handleDocOpen(pe); + event = ERecvOpen; + return true; + } + else if(state == Open) { + QDomElement e; + if(pe.type() == Parser::Event::Element) + e = pe.element(); + return doStep(e); + } + // Closing + else { + if(closeWritten) { + if(peerClosed) { + event = EPeerClosed; + return true; + } + else + return handleCloseFinished(); + } + + need = NNotify; + notify = NSend; + return false; + } +} + +void XmlProtocol::setIncomingAsExternal() +{ + for(QValueList<TransferItem>::Iterator it = transferItemList.begin(); it != transferItemList.end(); ++it) { + TransferItem &i = *it; + // look for elements received + if(!i.isString && !i.isSent) + i.isExternal = true; + } +} + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.h b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.h new file mode 100644 index 00000000..5bf2cbda --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-core/xmlprotocol.h @@ -0,0 +1,145 @@ +/* + * xmlprotocol.h - state machine for 'jabber-like' protocols + * Copyright (C) 2004 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef XMLPROTOCOL_H +#define XMLPROTOCOL_H + +#include<qdom.h> +#include<qvaluelist.h> +#include"parser.h" + +#define NS_XML "http://www.w3.org/XML/1998/namespace" + +namespace XMPP +{ + class XmlProtocol + { + public: + enum Need { + NNotify, // need a data send and/or recv update + NCustom = 10 + }; + enum Event { + EError, // unrecoverable error, see errorCode for details + ESend, // data needs to be sent, use takeOutgoingData() + ERecvOpen, // breakpoint after root element open tag is received + EPeerClosed, // root element close tag received + EClosed, // finished closing + ECustom = 10 + }; + enum Error { + ErrParse, // there was an error parsing the xml + ErrCustom = 10 + }; + enum Notify { + NSend = 0x01, // need to know if data has been written + NRecv = 0x02 // need incoming data + }; + + XmlProtocol(); + virtual ~XmlProtocol(); + + virtual void reset(); + + // byte I/O for the stream + void addIncomingData(const QByteArray &); + QByteArray takeOutgoingData(); + void outgoingDataWritten(int); + + // advance the state machine + bool processStep(); + + // set these before returning from a step + int need, event, errorCode, notify; + + inline bool isIncoming() const { return incoming; } + QString xmlEncoding() const; + QString elementToString(const QDomElement &e, bool clip=false); + + class TransferItem + { + public: + TransferItem(); + TransferItem(const QString &str, bool sent, bool external=false); + TransferItem(const QDomElement &elem, bool sent, bool external=false); + + bool isSent; // else, received + bool isString; // else, is element + bool isExternal; // not owned by protocol + QString str; + QDomElement elem; + }; + QValueList<TransferItem> transferItemList; + void setIncomingAsExternal(); + + protected: + virtual QDomElement docElement()=0; + virtual void handleDocOpen(const Parser::Event &pe)=0; + virtual bool handleError()=0; + virtual bool handleCloseFinished()=0; + virtual bool stepAdvancesParser() const=0; + virtual bool stepRequiresElement() const; + virtual bool doStep(const QDomElement &e)=0; + virtual void itemWritten(int id, int size); + + // 'debug' + virtual void stringSend(const QString &s); + virtual void stringRecv(const QString &s); + virtual void elementSend(const QDomElement &e); + virtual void elementRecv(const QDomElement &e); + + void startConnect(); + void startAccept(); + bool close(); + int writeString(const QString &s, int id, bool external); + int writeElement(const QDomElement &e, int id, bool external, bool clip=false); + QByteArray resetStream(); + + private: + enum { SendOpen, RecvOpen, Open, Closing }; + class TrackItem + { + public: + enum Type { Raw, Close, Custom }; + int type, id, size; + }; + + bool incoming; + QDomDocument elemDoc; + QDomElement elem; + QString tagOpen, tagClose; + int state; + bool peerClosed; + bool closeWritten; + + Parser xml; + QByteArray outData; + QValueList<TrackItem> trackQueue; + + void init(); + int internalWriteData(const QByteArray &a, TrackItem::Type t, int id=-1); + int internalWriteString(const QString &s, TrackItem::Type t, int id=-1); + void sendTagOpen(); + void sendTagClose(); + bool baseStep(const Parser::Event &pe); + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/Makefile.am b/kopete/protocols/jabber/libiris/iris/xmpp-im/Makefile.am new file mode 100644 index 00000000..c6dff330 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/Makefile.am @@ -0,0 +1,19 @@ +METASOURCES = AUTO + +noinst_LTLIBRARIES = libiris_xmpp_im.la +INCLUDES = -I$(srcdir)/../include -I$(srcdir)/../xmpp-core -I$(srcdir)/../xmpp-im -I$(srcdir)/../jabber -I$(srcdir)/../../cutestuff/util -I$(srcdir)/../../cutestuff/network -I$(srcdir)/../../qca/src $(all_includes) + +libiris_xmpp_im_la_SOURCES = \ + client.cpp \ + types.cpp \ + xmpp_tasks.cpp \ + xmpp_vcard.cpp \ + xmpp_xmlcommon.cpp + +CLEANFILES = types.moc +types.lo: types.moc +types.moc: $(top_builddir)/kopete/protocols/jabber/libiris/iris/xmpp-im/Makefile + ${MOC} -o types.moc $(srcdir)/../xmpp-im/types.cpp + +KDE_OPTIONS = nofinal + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/client.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-im/client.cpp new file mode 100644 index 00000000..0baeb820 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/client.cpp @@ -0,0 +1,1522 @@ +/* + * client.cpp - IM Client + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"im.h" +#include"safedelete.h" + +//! \class Client client.h +//! \brief Communicates with the Jabber network. Start here. +//! +//! Client controls an active Jabber connection. It allows you to connect, +//! authenticate, manipulate the roster, and send / receive messages and +//! presence. It is the centerpiece of this library, and all Tasks must pass +//! through it. +//! +//! For convenience, many Tasks are handled internally to Client (such as +//! JT_Auth). However, for accessing features beyond the basics provided by +//! Client, you will need to manually invoke Tasks. Fortunately, the +//! process is very simple. +//! +//! The entire Task system is heavily founded on Qt. All Tasks have a parent, +//! except for the root Task, and are considered QObjects. By using Qt's RTTI +//! facilities (QObject::sender(), QObject::isA(), etc), you can use a +//! "fire and forget" approach with Tasks. +//! +//! \code +//! #include "client.h" +//! using namespace Jabber; +//! +//! ... +//! +//! Client *client; +//! +//! Session::Session() +//! { +//! client = new Client; +//! connect(client, SIGNAL(handshaken()), SLOT(clientHandshaken())); +//! connect(client, SIGNAL(authFinished(bool, int, const QString &)), SLOT(authFinished(bool, int, const QString &))); +//! client->connectToHost("jabber.org"); +//! } +//! +//! void Session::clientHandshaken() +//! { +//! client->authDigest("jabtest", "12345", "Psi"); +//! } +//! +//! void Session::authFinished(bool success, int, const QString &err) +//! { +//! if(success) +//! printf("Login success!"); +//! else +//! printf("Login failed. Here's why: %s\n", err.latin1()); +//! } +//! \endcode + +#include<stdarg.h> +#include<qmap.h> +#include<qobjectlist.h> +#include<qtimer.h> +#include<qguardedptr.h> +#include"xmpp_tasks.h" +#include"xmpp_xmlcommon.h" +#include"s5b.h" +#include"xmpp_ibb.h" +#include"xmpp_jidlink.h" +#include"filetransfer.h" + +/*#include<stdio.h> +#include<stdarg.h> +#include<qstring.h> +#include<qdom.h> +#include<qobjectlist.h> +#include<qtimer.h> +#include"xmpp_stream.h" +#include"xmpp_tasks.h" +#include"xmpp_xmlcommon.h" +#include"xmpp_dtcp.h" +#include"xmpp_ibb.h" +#include"xmpp_jidlink.h" + +using namespace Jabber;*/ + +#ifdef Q_WS_WIN +#define vsnprintf _vsnprintf +#endif + +namespace XMPP +{ + +//---------------------------------------------------------------------------- +// Client +//---------------------------------------------------------------------------- +class Client::GroupChat +{ +public: + enum { Connecting, Connected, Closing }; + GroupChat() {} + + Jid j; + int status; +}; + +class Client::ClientPrivate +{ +public: + ClientPrivate() {} + + ClientStream *stream; + QDomDocument doc; + int id_seed; + Task *root; + QString host, user, pass, resource; + QString osname, tzname, clientName, clientVersion, capsNode, capsVersion, capsExt; + DiscoItem::Identity identity; + QMap<QString,Features> extension_features; + int tzoffset; + bool active; + + LiveRoster roster; + ResourceList resourceList; + S5BManager *s5bman; + IBBManager *ibbman; + JidLinkManager *jlman; + FileTransferManager *ftman; + bool ftEnabled; + QValueList<GroupChat> groupChatList; +}; + + +Client::Client(QObject *par) +:QObject(par) +{ + d = new ClientPrivate; + d->tzoffset = 0; + d->active = false; + d->osname = "N/A"; + d->clientName = "N/A"; + d->clientVersion = "0.0"; + d->capsNode = ""; + d->capsVersion = ""; + d->capsExt = ""; + + d->id_seed = 0xaaaa; + d->root = new Task(this, true); + + d->stream = 0; + + d->s5bman = new S5BManager(this); + connect(d->s5bman, SIGNAL(incomingReady()), SLOT(s5b_incomingReady())); + + d->ibbman = new IBBManager(this); + connect(d->ibbman, SIGNAL(incomingReady()), SLOT(ibb_incomingReady())); + + d->jlman = new JidLinkManager(this); + + d->ftman = 0; +} + +Client::~Client() +{ + close(true); + + delete d->ftman; + delete d->jlman; + delete d->ibbman; + delete d->s5bman; + delete d->root; + //delete d->stream; + delete d; +} + +void Client::connectToServer(ClientStream *s, const Jid &j, bool auth) +{ + d->stream = s; + //connect(d->stream, SIGNAL(connected()), SLOT(streamConnected())); + //connect(d->stream, SIGNAL(handshaken()), SLOT(streamHandshaken())); + connect(d->stream, SIGNAL(error(int)), SLOT(streamError(int))); + //connect(d->stream, SIGNAL(sslCertificateReady(const QSSLCert &)), SLOT(streamSSLCertificateReady(const QSSLCert &))); + connect(d->stream, SIGNAL(readyRead()), SLOT(streamReadyRead())); + //connect(d->stream, SIGNAL(closeFinished()), SLOT(streamCloseFinished())); + connect(d->stream, SIGNAL(incomingXml(const QString &)), SLOT(streamIncomingXml(const QString &))); + connect(d->stream, SIGNAL(outgoingXml(const QString &)), SLOT(streamOutgoingXml(const QString &))); + + d->stream->connectToServer(j, auth); +} + +void Client::start(const QString &host, const QString &user, const QString &pass, const QString &_resource) +{ + // TODO + d->host = host; + d->user = user; + d->pass = pass; + d->resource = _resource; + + Status stat; + stat.setIsAvailable(false); + d->resourceList += Resource(resource(), stat); + + JT_PushPresence *pp = new JT_PushPresence(rootTask()); + connect(pp, SIGNAL(subscription(const Jid &, const QString &)), SLOT(ppSubscription(const Jid &, const QString &))); + connect(pp, SIGNAL(presence(const Jid &, const Status &)), SLOT(ppPresence(const Jid &, const Status &))); + + JT_PushMessage *pm = new JT_PushMessage(rootTask()); + connect(pm, SIGNAL(message(const Message &)), SLOT(pmMessage(const Message &))); + + JT_PushRoster *pr = new JT_PushRoster(rootTask()); + connect(pr, SIGNAL(roster(const Roster &)), SLOT(prRoster(const Roster &))); + + new JT_ServInfo(rootTask()); + + d->active = true; +} + +void Client::setFileTransferEnabled(bool b) +{ + if(b) { + if(!d->ftman) + d->ftman = new FileTransferManager(this); + } + else { + if(d->ftman) { + delete d->ftman; + d->ftman = 0; + } + } +} + +FileTransferManager *Client::fileTransferManager() const +{ + return d->ftman; +} + +JidLinkManager *Client::jidLinkManager() const +{ + return d->jlman; +} + +S5BManager *Client::s5bManager() const +{ + return d->s5bman; +} + +IBBManager *Client::ibbManager() const +{ + return d->ibbman; +} + +bool Client::isActive() const +{ + return d->active; +} + +void Client::groupChatChangeNick(const QString &host, const QString &room, const QString &nick, const Status &_s) +{ + Jid jid(room + "@" + host + "/" + nick); + for(QValueList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + GroupChat &i = *it; + if(i.j.compare(jid, false)) { + i.j = jid; + + Status s = _s; + s.setIsAvailable(true); + + JT_Presence *j = new JT_Presence(rootTask()); + j->pres(jid, s); + j->go(true); + + break; + } + } +} + +bool Client::groupChatJoin(const QString &host, const QString &room, const QString &nick) +{ + Jid jid(room + "@" + host + "/" + nick); + for(QValueList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end();) { + GroupChat &i = *it; + if(i.j.compare(jid, false)) { + // if this room is shutting down, then free it up + if(i.status == GroupChat::Closing) + it = d->groupChatList.remove(it); + else + return false; + } + else + ++it; + } + + debug(QString("Client: Joined: [%1]\n").arg(jid.full())); + GroupChat i; + i.j = jid; + i.status = GroupChat::Connecting; + d->groupChatList += i; + + JT_Presence *j = new JT_Presence(rootTask()); + j->pres(jid, Status()); + j->go(true); + + return true; +} + +bool Client::groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &password) +{ + Jid jid(room + "@" + host + "/" + nick); + for(QValueList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end();) { + GroupChat &i = *it; + if(i.j.compare(jid, false)) { + // if this room is shutting down, then free it up + if(i.status == GroupChat::Closing) + it = d->groupChatList.remove(it); + else + return false; + } + else + ++it; + } + + debug(QString("Client: Joined: [%1]\n").arg(jid.full())); + GroupChat i; + i.j = jid; + i.status = GroupChat::Connecting; + d->groupChatList += i; + + JT_MucPresence *j = new JT_MucPresence(rootTask()); + j->pres(jid, Status(), password); + j->go(true); + + return true; +} + +void Client::groupChatSetStatus(const QString &host, const QString &room, const Status &_s) +{ + Jid jid(room + "@" + host); + bool found = false; + for(QValueList<GroupChat>::ConstIterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + const GroupChat &i = *it; + if(i.j.compare(jid, false)) { + found = true; + jid = i.j; + break; + } + } + if(!found) + return; + + Status s = _s; + s.setIsAvailable(true); + + JT_Presence *j = new JT_Presence(rootTask()); + j->pres(jid, s); + j->go(true); +} + +void Client::groupChatLeave(const QString &host, const QString &room) +{ + Jid jid(room + "@" + host); + for(QValueList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + GroupChat &i = *it; + + if(!i.j.compare(jid, false)) + continue; + + i.status = GroupChat::Closing; + debug(QString("Client: Leaving: [%1]\n").arg(i.j.full())); + + JT_Presence *j = new JT_Presence(rootTask()); + Status s; + s.setIsAvailable(false); + j->pres(i.j, s); + j->go(true); + } +} + +/*void Client::start() +{ + if(d->stream->old()) { + // old has no activation step + d->active = true; + activated(); + } + else { + // TODO: IM session + } +}*/ + +// TODO: fast close +void Client::close(bool) +{ + if(d->stream) { + if(d->active) { + for(QValueList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + GroupChat &i = *it; + i.status = GroupChat::Closing; + + JT_Presence *j = new JT_Presence(rootTask()); + Status s; + s.setIsAvailable(false); + j->pres(i.j, s); + j->go(true); + } + } + + d->stream->disconnect(this); + d->stream->close(); + d->stream = 0; + } + disconnected(); + cleanup(); +} + +void Client::cleanup() +{ + d->active = false; + //d->authed = false; + d->groupChatList.clear(); +} + +/*void Client::continueAfterCert() +{ + d->stream->continueAfterCert(); +} + +void Client::streamConnected() +{ + connected(); +} + +void Client::streamHandshaken() +{ + handshaken(); +}*/ + +void Client::streamError(int) +{ + //StreamError e = err; + //error(e); + + //if(!e.isWarning()) { + disconnected(); + cleanup(); + //} +} + +/*void Client::streamSSLCertificateReady(const QSSLCert &cert) +{ + sslCertReady(cert); +} + +void Client::streamCloseFinished() +{ + closeFinished(); +}*/ + +static QDomElement oldStyleNS(const QDomElement &e) +{ + // find closest parent with a namespace + QDomNode par = e.parentNode(); + while(!par.isNull() && par.namespaceURI().isNull()) + par = par.parentNode(); + bool noShowNS = false; + if(!par.isNull() && par.namespaceURI() == e.namespaceURI()) + noShowNS = true; + + QDomElement i; + uint x; + //if(noShowNS) + i = e.ownerDocument().createElement(e.tagName()); + //else + // i = e.ownerDocument().createElementNS(e.namespaceURI(), e.tagName()); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) + i.setAttributeNode(al.item(x).cloneNode().toAttr()); + + if(!noShowNS) + i.setAttribute("xmlns", e.namespaceURI()); + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(oldStyleNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + return i; +} + +void Client::streamReadyRead() +{ + // HACK HACK HACK + QGuardedPtr<ClientStream> pstream = d->stream; + + while(pstream && d->stream->stanzaAvailable()) { + Stanza s = d->stream->read(); + + QString out = s.toString(); + debug(QString("Client: incoming: [\n%1]\n").arg(out)); + xmlIncoming(out); + + QDomElement x = oldStyleNS(s.element()); + distribute(x); + } +} + +void Client::streamIncomingXml(const QString &s) +{ + QString str = s; + if(str.at(str.length()-1) != '\n') + str += '\n'; + xmlIncoming(str); +} + +void Client::streamOutgoingXml(const QString &s) +{ + QString str = s; + if(str.at(str.length()-1) != '\n') + str += '\n'; + xmlOutgoing(str); +} + +void Client::debug(const QString &str) +{ + debugText(str); +} + +QString Client::genUniqueId() +{ + QString s; + s.sprintf("a%x", d->id_seed); + d->id_seed += 0x10; + return s; +} + +Task *Client::rootTask() +{ + return d->root; +} + +QDomDocument *Client::doc() const +{ + return &d->doc; +} + +void Client::distribute(const QDomElement &x) +{ + if(x.hasAttribute("from")) { + Jid j(x.attribute("from")); + if(!j.isValid()) { + debug("Client: bad 'from' JID\n"); + return; + } + } + + if(!rootTask()->take(x)) { + debug("Client: packet was ignored.\n"); + } +} + +static QDomElement addCorrectNS(const QDomElement &e) +{ + uint x; + + // grab child nodes + /*QDomDocumentFragment frag = e.ownerDocument().createDocumentFragment(); + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) + frag.appendChild(nl.item(x).cloneNode());*/ + + // find closest xmlns + QDomNode n = e; + while(!n.isNull() && !n.toElement().hasAttribute("xmlns")) + n = n.parentNode(); + QString ns; + if(n.isNull() || !n.toElement().hasAttribute("xmlns")) + ns = "jabber:client"; + else + ns = n.toElement().attribute("xmlns"); + + // make a new node + QDomElement i = e.ownerDocument().createElementNS(ns, e.tagName()); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) { + QDomAttr a = al.item(x).toAttr(); + if(a.name() != "xmlns") + i.setAttributeNodeNS(a.cloneNode().toAttr()); + } + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(addCorrectNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + + //i.appendChild(frag); + return i; +} + +void Client::send(const QDomElement &x) +{ + if(!d->stream) + return; + + //QString out; + //QTextStream ts(&out, IO_WriteOnly); + //x.save(ts, 0); + + //QString out = Stream::xmlToString(x); + //debug(QString("Client: outgoing: [\n%1]\n").arg(out)); + //xmlOutgoing(out); + + QDomElement e = addCorrectNS(x); + Stanza s = d->stream->createStanza(e); + if(s.isNull()) { + //printf("bad stanza??\n"); + return; + } + + QString out = s.toString(); + debug(QString("Client: outgoing: [\n%1]\n").arg(out)); + xmlOutgoing(out); + + //printf("x[%s] x2[%s] s[%s]\n", Stream::xmlToString(x).latin1(), Stream::xmlToString(e).latin1(), s.toString().latin1()); + d->stream->write(s); +} + +void Client::send(const QString &str) +{ + if(!d->stream) + return; + + debug(QString("Client: outgoing: [\n%1]\n").arg(str)); + xmlOutgoing(str); + static_cast<ClientStream*>(d->stream)->writeDirect(str); +} + +Stream & Client::stream() +{ + return *d->stream; +} + +const LiveRoster & Client::roster() const +{ + return d->roster; +} + +const ResourceList & Client::resourceList() const +{ + return d->resourceList; +} + +QString Client::host() const +{ + return d->host; +} + +QString Client::user() const +{ + return d->user; +} + +QString Client::pass() const +{ + return d->pass; +} + +QString Client::resource() const +{ + return d->resource; +} + +Jid Client::jid() const +{ + QString s; + if(!d->user.isEmpty()) + s += d->user + '@'; + s += d->host; + if(!d->resource.isEmpty()) { + s += '/'; + s += d->resource; + } + + return Jid(s); +} + +void Client::ppSubscription(const Jid &j, const QString &s) +{ + subscription(j, s); +} + +void Client::ppPresence(const Jid &j, const Status &s) +{ + if(s.isAvailable()) + debug(QString("Client: %1 is available.\n").arg(j.full())); + else + debug(QString("Client: %1 is unavailable.\n").arg(j.full())); + + for(QValueList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + GroupChat &i = *it; + + if(i.j.compare(j, false)) { + bool us = (i.j.resource() == j.resource() || j.resource().isEmpty()) ? true: false; + + debug(QString("for groupchat i=[%1] pres=[%2], [us=%3].\n").arg(i.j.full()).arg(j.full()).arg(us)); + switch(i.status) { + case GroupChat::Connecting: + if(us && s.hasError()) { + Jid j = i.j; + d->groupChatList.remove(it); + groupChatError(j, s.errorCode(), s.errorString()); + } + else { + // don't signal success unless it is a non-error presence + if(!s.hasError()) { + i.status = GroupChat::Connected; + groupChatJoined(i.j); + } + groupChatPresence(j, s); + } + break; + case GroupChat::Connected: + groupChatPresence(j, s); + break; + case GroupChat::Closing: + if(us && !s.isAvailable()) { + Jid j = i.j; + d->groupChatList.remove(it); + groupChatLeft(j); + } + break; + default: + break; + } + + return; + } + } + + if(s.hasError()) { + presenceError(j, s.errorCode(), s.errorString()); + return; + } + + // is it me? + if(j.compare(jid(), false)) { + updateSelfPresence(j, s); + } + else { + // update all relavent roster entries + for(LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end(); ++it) { + LiveRosterItem &i = *it; + + if(!i.jid().compare(j, false)) + continue; + + // roster item has its own resource? + if(!i.jid().resource().isEmpty()) { + if(i.jid().resource() != j.resource()) + continue; + } + + updatePresence(&i, j, s); + } + } +} + +void Client::updateSelfPresence(const Jid &j, const Status &s) +{ + ResourceList::Iterator rit = d->resourceList.find(j.resource()); + bool found = (rit == d->resourceList.end()) ? false: true; + + // unavailable? remove the resource + if(!s.isAvailable()) { + if(found) { + debug(QString("Client: Removing self resource: name=[%1]\n").arg(j.resource())); + (*rit).setStatus(s); + resourceUnavailable(j, *rit); + d->resourceList.remove(rit); + } + } + // available? add/update the resource + else { + Resource r; + if(!found) { + r = Resource(j.resource(), s); + d->resourceList += r; + debug(QString("Client: Adding self resource: name=[%1]\n").arg(j.resource())); + } + else { + (*rit).setStatus(s); + r = *rit; + debug(QString("Client: Updating self resource: name=[%1]\n").arg(j.resource())); + } + + resourceAvailable(j, r); + } +} + +void Client::updatePresence(LiveRosterItem *i, const Jid &j, const Status &s) +{ + ResourceList::Iterator rit = i->resourceList().find(j.resource()); + bool found = (rit == i->resourceList().end()) ? false: true; + + // unavailable? remove the resource + if(!s.isAvailable()) { + if(found) { + (*rit).setStatus(s); + debug(QString("Client: Removing resource from [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); + resourceUnavailable(j, *rit); + i->resourceList().remove(rit); + i->setLastUnavailableStatus(s); + } + } + // available? add/update the resource + else { + Resource r; + if(!found) { + r = Resource(j.resource(), s); + i->resourceList() += r; + debug(QString("Client: Adding resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); + } + else { + (*rit).setStatus(s); + r = *rit; + debug(QString("Client: Updating resource to [%1]: name=[%2]\n").arg(i->jid().full()).arg(j.resource())); + } + + resourceAvailable(j, r); + } +} + +void Client::pmMessage(const Message &m) +{ + debug(QString("Client: Message from %1\n").arg(m.from().full())); + + if(m.type() == "groupchat") { + for(QValueList<GroupChat>::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end(); it++) { + const GroupChat &i = *it; + + if(!i.j.compare(m.from(), false)) + continue; + + if(i.status == GroupChat::Connected) + messageReceived(m); + } + } + else + messageReceived(m); +} + +void Client::prRoster(const Roster &r) +{ + importRoster(r); +} + +void Client::rosterRequest() +{ + if(!d->active) + return; + + JT_Roster *r = new JT_Roster(rootTask()); + connect(r, SIGNAL(finished()), SLOT(slotRosterRequestFinished())); + r->get(); + d->roster.flagAllForDelete(); // mod_groups patch + r->go(true); +} + +void Client::slotRosterRequestFinished() +{ + JT_Roster *r = (JT_Roster *)sender(); + // on success, let's take it + if(r->success()) { + //d->roster.flagAllForDelete(); // mod_groups patch + + importRoster(r->roster()); + + for(LiveRoster::Iterator it = d->roster.begin(); it != d->roster.end();) { + LiveRosterItem &i = *it; + if(i.flagForDelete()) { + rosterItemRemoved(i); + it = d->roster.remove(it); + } + else + ++it; + } + } + else { + // don't report a disconnect. Client::error() will do that. + if(r->statusCode() == Task::ErrDisc) + return; + } + + // report success / fail + rosterRequestFinished(r->success(), r->statusCode(), r->statusString()); +} + +void Client::importRoster(const Roster &r) +{ + for(Roster::ConstIterator it = r.begin(); it != r.end(); ++it) { + importRosterItem(*it); + } +} + +void Client::importRosterItem(const RosterItem &item) +{ + QString substr; + switch(item.subscription().type()) { + case Subscription::Both: + substr = "<-->"; break; + case Subscription::From: + substr = " ->"; break; + case Subscription::To: + substr = "<- "; break; + case Subscription::Remove: + substr = "xxxx"; break; + case Subscription::None: + default: + substr = "----"; break; + } + + QString dstr, str; + str.sprintf(" %s %-32s", substr.latin1(), item.jid().full().latin1()); + if(!item.name().isEmpty()) + str += QString(" [") + item.name() + "]"; + str += '\n'; + + // Remove + if(item.subscription().type() == Subscription::Remove) { + LiveRoster::Iterator it = d->roster.find(item.jid()); + if(it != d->roster.end()) { + rosterItemRemoved(*it); + d->roster.remove(it); + } + dstr = "Client: (Removed) "; + } + // Add/Update + else { + LiveRoster::Iterator it = d->roster.find(item.jid()); + if(it != d->roster.end()) { + LiveRosterItem &i = *it; + i.setFlagForDelete(false); + i.setRosterItem(item); + rosterItemUpdated(i); + dstr = "Client: (Updated) "; + } + else { + LiveRosterItem i(item); + d->roster += i; + + // signal it + rosterItemAdded(i); + dstr = "Client: (Added) "; + } + } + + debug(dstr + str); +} + +void Client::sendMessage(const Message &m) +{ + JT_Message *j = new JT_Message(rootTask(), m); + j->go(true); +} + +void Client::sendSubscription(const Jid &jid, const QString &type) +{ + JT_Presence *j = new JT_Presence(rootTask()); + j->sub(jid, type); + j->go(true); +} + +void Client::setPresence(const Status &s) +{ + JT_Presence *j = new JT_Presence(rootTask()); + j->pres(s); + j->go(true); + + // update our resourceList + ppPresence(jid(), s); + //ResourceList::Iterator rit = d->resourceList.find(resource()); + //Resource &r = *rit; + //r.setStatus(s); +} + +QString Client::OSName() const +{ + return d->osname; +} + +QString Client::timeZone() const +{ + return d->tzname; +} + +int Client::timeZoneOffset() const +{ + return d->tzoffset; +} + +QString Client::clientName() const +{ + return d->clientName; +} + +QString Client::clientVersion() const +{ + return d->clientVersion; +} + +QString Client::capsNode() const +{ + return d->capsNode; +} + +QString Client::capsVersion() const +{ + return d->capsVersion; +} + +QString Client::capsExt() const +{ + return d->capsExt; +} + +void Client::setOSName(const QString &name) +{ + d->osname = name; +} + +void Client::setTimeZone(const QString &name, int offset) +{ + d->tzname = name; + d->tzoffset = offset; +} + +void Client::setClientName(const QString &s) +{ + d->clientName = s; +} + +void Client::setClientVersion(const QString &s) +{ + d->clientVersion = s; +} + +void Client::setCapsNode(const QString &s) +{ + d->capsNode = s; +} + +void Client::setCapsVersion(const QString &s) +{ + d->capsVersion = s; +} + +DiscoItem::Identity Client::identity() +{ + return d->identity; +} + +void Client::setIdentity(DiscoItem::Identity identity) +{ + d->identity = identity; +} + +void Client::addExtension(const QString& ext, const Features& features) +{ + if (!ext.isEmpty()) { + d->extension_features[ext] = features; + d->capsExt = extensions().join(" "); + } +} + +void Client::removeExtension(const QString& ext) +{ + if (d->extension_features.contains(ext)) { + d->extension_features.remove(ext); + d->capsExt = extensions().join(" "); + } +} + +QStringList Client::extensions() const +{ + return d->extension_features.keys(); +} + +const Features& Client::extension(const QString& ext) const +{ + return d->extension_features[ext]; +} + +void Client::s5b_incomingReady() +{ + S5BConnection *c = d->s5bman->takeIncoming(); + if(!c) + return; + if(!d->ftman) { + c->close(); + c->deleteLater(); + return; + } + d->ftman->s5b_incomingReady(c); + //d->jlman->insertStream(c); + //incomingJidLink(); +} + +void Client::ibb_incomingReady() +{ + IBBConnection *c = d->ibbman->takeIncoming(); + if(!c) + return; + c->deleteLater(); + //d->jlman->insertStream(c); + //incomingJidLink(); +} + +//---------------------------------------------------------------------------- +// Task +//---------------------------------------------------------------------------- +class Task::TaskPrivate +{ +public: + TaskPrivate() {} + + QString id; + bool success; + int statusCode; + QString statusString; + Client *client; + bool insig, deleteme, autoDelete; + bool done; +}; + +Task::Task(Task *parent) +:QObject(parent) +{ + init(); + + d->client = parent->client(); + d->id = client()->genUniqueId(); + connect(d->client, SIGNAL(disconnected()), SLOT(clientDisconnected())); +} + +Task::Task(Client *parent, bool) +:QObject(0) +{ + init(); + + d->client = parent; + connect(d->client, SIGNAL(disconnected()), SLOT(clientDisconnected())); +} + +Task::~Task() +{ + delete d; +} + +void Task::init() +{ + d = new TaskPrivate; + d->success = false; + d->insig = false; + d->deleteme = false; + d->autoDelete = false; + d->done = false; +} + +Task *Task::parent() const +{ + return (Task *)QObject::parent(); +} + +Client *Task::client() const +{ + return d->client; +} + +QDomDocument *Task::doc() const +{ + return client()->doc(); +} + +QString Task::id() const +{ + return d->id; +} + +bool Task::success() const +{ + return d->success; +} + +int Task::statusCode() const +{ + return d->statusCode; +} + +const QString & Task::statusString() const +{ + return d->statusString; +} + +void Task::go(bool autoDelete) +{ + d->autoDelete = autoDelete; + + onGo(); +} + +bool Task::take(const QDomElement &x) +{ + const QObjectList *p = children(); + if(!p) + return false; + + // pass along the xml + QObjectListIt it(*p); + Task *t; + for(; it.current(); ++it) { + QObject *obj = it.current(); + if(!obj->inherits("XMPP::Task")) + continue; + + t = static_cast<Task*>(obj); + if(t->take(x)) + return true; + } + + return false; +} + +void Task::safeDelete() +{ + if(d->deleteme) + return; + + d->deleteme = true; + if(!d->insig) + SafeDelete::deleteSingle(this); +} + +void Task::onGo() +{ +} + +void Task::onDisconnect() +{ + if(!d->done) { + d->success = false; + d->statusCode = ErrDisc; + d->statusString = tr("Disconnected"); + + // delay this so that tasks that react don't block the shutdown + QTimer::singleShot(0, this, SLOT(done())); + } +} + +void Task::send(const QDomElement &x) +{ + client()->send(x); +} + +void Task::setSuccess(int code, const QString &str) +{ + if(!d->done) { + d->success = true; + d->statusCode = code; + d->statusString = str; + done(); + } +} + +void Task::setError(const QDomElement &e) +{ + if(!d->done) { + d->success = false; + getErrorFromElement(e, &d->statusCode, &d->statusString); + done(); + } +} + +void Task::setError(int code, const QString &str) +{ + if(!d->done) { + d->success = false; + d->statusCode = code; + d->statusString = str; + done(); + } +} + +void Task::done() +{ + if(d->done || d->insig) + return; + d->done = true; + + if(d->deleteme || d->autoDelete) + d->deleteme = true; + + d->insig = true; + finished(); + d->insig = false; + + if(d->deleteme) + SafeDelete::deleteSingle(this); +} + +void Task::clientDisconnected() +{ + onDisconnect(); +} + +void Task::debug(const char *fmt, ...) +{ + char *buf; + QString str; + int size = 1024; + int r; + + do { + buf = new char[size]; + va_list ap; + va_start(ap, fmt); + r = vsnprintf(buf, size, fmt, ap); + va_end(ap); + + if(r != -1) + str = QString(buf); + + delete [] buf; + + size *= 2; + } while(r == -1); + + debug(str); +} + +void Task::debug(const QString &str) +{ + client()->debug(QString("%1: ").arg(className()) + str); +} + +bool Task::iqVerify(const QDomElement &x, const Jid &to, const QString &id, const QString &xmlns) +{ + if(x.tagName() != "iq") + return false; + + Jid from(x.attribute("from")); + Jid local = client()->jid(); + Jid server = client()->host(); + + // empty 'from' ? + if(from.isEmpty()) { + // allowed if we are querying the server + if(!to.isEmpty() && !to.compare(server)) + return false; + } + // from ourself? + else if(from.compare(local, false)) { + // allowed if we are querying ourself or the server + if(!to.isEmpty() && !to.compare(local, false) && !to.compare(server)) + return false; + } + // from anywhere else? + else { + if(!from.compare(to)) + return false; + } + + if(!id.isEmpty()) { + if(x.attribute("id") != id) + return false; + } + + if(!xmlns.isEmpty()) { + if(queryNS(x) != xmlns) + return false; + } + + return true; +} + +//--------------------------------------------------------------------------- +// LiveRosterItem +//--------------------------------------------------------------------------- +LiveRosterItem::LiveRosterItem(const Jid &jid) +:RosterItem(jid) +{ + setFlagForDelete(false); +} + +LiveRosterItem::LiveRosterItem(const RosterItem &i) +{ + setRosterItem(i); + setFlagForDelete(false); +} + +LiveRosterItem::~LiveRosterItem() +{ +} + +void LiveRosterItem::setRosterItem(const RosterItem &i) +{ + setJid(i.jid()); + setName(i.name()); + setGroups(i.groups()); + setSubscription(i.subscription()); + setAsk(i.ask()); + setIsPush(i.isPush()); +} + +ResourceList & LiveRosterItem::resourceList() +{ + return v_resourceList; +} + +ResourceList::Iterator LiveRosterItem::priority() +{ + return v_resourceList.priority(); +} + +const ResourceList & LiveRosterItem::resourceList() const +{ + return v_resourceList; +} + +ResourceList::ConstIterator LiveRosterItem::priority() const +{ + return v_resourceList.priority(); +} + +bool LiveRosterItem::isAvailable() const +{ + if(v_resourceList.count() > 0) + return true; + return false; +} + +const Status & LiveRosterItem::lastUnavailableStatus() const +{ + return v_lastUnavailableStatus; +} + +bool LiveRosterItem::flagForDelete() const +{ + return v_flagForDelete; +} + +void LiveRosterItem::setLastUnavailableStatus(const Status &s) +{ + v_lastUnavailableStatus = s; +} + +void LiveRosterItem::setFlagForDelete(bool b) +{ + v_flagForDelete = b; +} + +//--------------------------------------------------------------------------- +// LiveRoster +//--------------------------------------------------------------------------- +LiveRoster::LiveRoster() +:QValueList<LiveRosterItem>() +{ +} + +LiveRoster::~LiveRoster() +{ +} + +void LiveRoster::flagAllForDelete() +{ + for(Iterator it = begin(); it != end(); ++it) + (*it).setFlagForDelete(true); +} + +LiveRoster::Iterator LiveRoster::find(const Jid &j, bool compareRes) +{ + Iterator it; + for(it = begin(); it != end(); ++it) { + if((*it).jid().compare(j, compareRes)) + break; + } + return it; +} + +LiveRoster::ConstIterator LiveRoster::find(const Jid &j, bool compareRes) const +{ + ConstIterator it; + for(it = begin(); it != end(); ++it) { + if((*it).jid().compare(j, compareRes)) + break; + } + return it; +} + +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/types.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-im/types.cpp new file mode 100644 index 00000000..1e457584 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/types.cpp @@ -0,0 +1,1876 @@ +/* + * types.cpp - IM data types + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"im.h" +#include "protocol.h" +#include<qmap.h> +#include<qapplication.h> + +#define NS_XML "http://www.w3.org/XML/1998/namespace" + +static QDomElement textTag(QDomDocument *doc, const QString &name, const QString &content) +{ + QDomElement tag = doc->createElement(name); + QDomText text = doc->createTextNode(content); + tag.appendChild(text); + + return tag; +} + +static QString tagContent(const QDomElement &e) +{ + // look for some tag content + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomText i = n.toText(); + if(i.isNull()) + continue; + return i.data(); + } + + return ""; +} + +static QDateTime stamp2TS(const QString &ts) +{ + if(ts.length() != 17) + return QDateTime(); + + int year = ts.mid(0,4).toInt(); + int month = ts.mid(4,2).toInt(); + int day = ts.mid(6,2).toInt(); + + int hour = ts.mid(9,2).toInt(); + int min = ts.mid(12,2).toInt(); + int sec = ts.mid(15,2).toInt(); + + QDate xd; + xd.setYMD(year, month, day); + if(!xd.isValid()) + return QDateTime(); + + QTime xt; + xt.setHMS(hour, min, sec); + if(!xt.isValid()) + return QDateTime(); + + return QDateTime(xd, xt); +} + +/*static QString TS2stamp(const QDateTime &d) +{ + QString str; + + str.sprintf("%04d%02d%02dT%02d:%02d:%02d", + d.date().year(), + d.date().month(), + d.date().day(), + d.time().hour(), + d.time().minute(), + d.time().second()); + + return str; +}*/ + +namespace XMPP +{ + +//---------------------------------------------------------------------------- +// Url +//---------------------------------------------------------------------------- +class Url::Private +{ +public: + QString url; + QString desc; +}; + +//! \brief Construct Url object with a given URL and Description. +//! +//! This function will construct a Url object. +//! \param QString - url (default: empty string) +//! \param QString - description of url (default: empty string) +//! \sa setUrl() setDesc() +Url::Url(const QString &url, const QString &desc) +{ + d = new Private; + d->url = url; + d->desc = desc; +} + +//! \brief Construct Url object. +//! +//! Overloaded constructor which will constructs a exact copy of the Url object that was passed to the constructor. +//! \param Url - Url Object +Url::Url(const Url &from) +{ + d = new Private; + *this = from; +} + +//! \brief operator overloader needed for d pointer (Internel). +Url & Url::operator=(const Url &from) +{ + *d = *from.d; + return *this; +} + +//! \brief destroy Url object. +Url::~Url() +{ + delete d; +} + +//! \brief Get url information. +//! +//! Returns url information. +QString Url::url() const +{ + return d->url; +} + +//! \brief Get Description information. +//! +//! Returns desction of the URL. +QString Url::desc() const +{ + return d->desc; +} + +//! \brief Set Url information. +//! +//! Set url information. +//! \param url - url string (eg: http://psi.affinix.com/) +void Url::setUrl(const QString &url) +{ + d->url = url; +} + +//! \brief Set Description information. +//! +//! Set description of the url. +//! \param desc - description of url +void Url::setDesc(const QString &desc) +{ + d->desc = desc; +} + +//---------------------------------------------------------------------------- +// Message +//---------------------------------------------------------------------------- +class Message::Private +{ +public: + Jid to, from; + QString id, type, lang; + + StringMap subject, body, xHTMLBody; + + QString thread; + Stanza::Error error; + + // extensions + QDateTime timeStamp; + UrlList urlList; + QValueList<MsgEvent> eventList; + QString eventId; + QString xencrypted, invite; + + bool spooled, wasEncrypted; +}; + +//! \brief Constructs Message with given Jid information. +//! +//! This function will construct a Message container. +//! \param to - specify reciver (default: empty string) +Message::Message(const Jid &to) +{ + d = new Private; + d->to = to; + d->spooled = false; + d->wasEncrypted = false; + /*d->flag = false; + d->spooled = false; + d->wasEncrypted = false; + d->errorCode = -1;*/ +} + +//! \brief Constructs a copy of Message object +//! +//! Overloaded constructor which will constructs a exact copy of the Message +//! object that was passed to the constructor. +//! \param from - Message object you want to copy +Message::Message(const Message &from) +{ + d = new Private; + *this = from; +} + +//! \brief Required for internel use. +Message & Message::operator=(const Message &from) +{ + *d = *from.d; + return *this; +} + +//! \brief Destroy Message object. +Message::~Message() +{ + delete d; +} + +//! \brief Return receiver's Jid information. +Jid Message::to() const +{ + return d->to; +} + +//! \brief Return sender's Jid information. +Jid Message::from() const +{ + return d->from; +} + +QString Message::id() const +{ + return d->id; +} + +//! \brief Return type information +QString Message::type() const +{ + return d->type; +} + +QString Message::lang() const +{ + return d->lang; +} + +//! \brief Return subject information. +QString Message::subject(const QString &lang) const +{ + return d->subject[lang]; +} + +//! \brief Return body information. +//! +//! This function will return a plain text or the Richtext version if it +//! it exists. +//! \param rich - Returns richtext if true and plain text if false. (default: false) +//! \note Richtext is in Qt's richtext format and not in xhtml. +QString Message::body(const QString &lang) const +{ + return d->body[lang]; +} + +QString Message::xHTMLBody(const QString &lang) const +{ + return d->xHTMLBody[lang]; +} + +QString Message::thread() const +{ + return d->thread; +} + +Stanza::Error Message::error() const +{ + return d->error; +} + +//! \brief Set receivers information +//! +//! \param to - Receivers Jabber id +void Message::setTo(const Jid &j) +{ + d->to = j; + //d->flag = false; +} + +void Message::setFrom(const Jid &j) +{ + d->from = j; + //d->flag = false; +} + +void Message::setId(const QString &s) +{ + d->id = s; +} + +//! \brief Set Type of message +//! +//! \param type - type of message your going to send +void Message::setType(const QString &s) +{ + d->type = s; + //d->flag = false; +} + +void Message::setLang(const QString &s) +{ + d->lang = s; +} + +//! \brief Set subject +//! +//! \param subject - Subject information +void Message::setSubject(const QString &s, const QString &lang) +{ + d->subject[lang] = s; + //d->flag = false; +} + +//! \brief Set body +//! +//! \param body - body information +//! \param rich - set richtext if true and set plaintext if false. +//! \note Richtext support will be implemented in the future... Sorry. +void Message::setBody(const QString &s, const QString &lang) +{ + d->body[lang] = s; +} + +void Message::setXHTMLBody(const QString &s, const QString &lang, const QString &attr) +{ + //ugly but needed if s is not a node but a list of leaf + + QString content = "<body xmlns='" + QString(NS_XHTML) + "' "+attr+" >\n" + s +"\n</body>"; + d->xHTMLBody[lang] = content; +} + +void Message::setThread(const QString &s) +{ + d->thread = s; +} + +void Message::setError(const Stanza::Error &err) +{ + d->error = err; +} + +QDateTime Message::timeStamp() const +{ + return d->timeStamp; +} + +void Message::setTimeStamp(const QDateTime &ts) +{ + d->timeStamp = ts; +} + +//! \brief Return list of urls attached to message. +UrlList Message::urlList() const +{ + return d->urlList; +} + +//! \brief Add Url to the url list. +//! +//! \param url - url to append +void Message::urlAdd(const Url &u) +{ + d->urlList += u; +} + +//! \brief clear out the url list. +void Message::urlsClear() +{ + d->urlList.clear(); +} + +//! \brief Set urls to send +//! +//! \param urlList - list of urls to send +void Message::setUrlList(const UrlList &list) +{ + d->urlList = list; +} + +QString Message::eventId() const +{ + return d->eventId; +} + +void Message::setEventId(const QString& id) +{ + d->eventId = id; +} + +bool Message::containsEvents() const +{ + return !d->eventList.isEmpty(); +} + +bool Message::containsEvent(MsgEvent e) const +{ + return d->eventList.contains(e); +} + +void Message::addEvent(MsgEvent e) +{ + if (!d->eventList.contains(e)) { + if (e == CancelEvent || containsEvent(CancelEvent)) + d->eventList.clear(); // Reset list + d->eventList += e; + } +} + +QString Message::xencrypted() const +{ + return d->xencrypted; +} + +void Message::setXEncrypted(const QString &s) +{ + d->xencrypted = s; +} + +QString Message::invite() const +{ + return d->invite; +} + +void Message::setInvite(const QString &s) +{ + d->invite = s; +} + +bool Message::spooled() const +{ + return d->spooled; +} + +void Message::setSpooled(bool b) +{ + d->spooled = b; +} + +bool Message::wasEncrypted() const +{ + return d->wasEncrypted; +} + +void Message::setWasEncrypted(bool b) +{ + d->wasEncrypted = b; +} + +Stanza Message::toStanza(Stream *stream) const +{ + Stanza s = stream->createStanza(Stanza::Message, d->to, d->type); + if(!d->from.isEmpty()) + s.setFrom(d->from); + if(!d->id.isEmpty()) + s.setId(d->id); + if(!d->lang.isEmpty()) + s.setLang(d->lang); + + StringMap::ConstIterator it; + for(it = d->subject.begin(); it != d->subject.end(); ++it) { + const QString &str = it.data(); + if(!str.isEmpty()) { + QDomElement e = s.createTextElement(s.baseNS(), "subject", str); + if(!it.key().isEmpty()) + e.setAttributeNS(NS_XML, "xml:lang", it.key()); + s.appendChild(e); + } + } + for(it = d->body.begin(); it != d->body.end(); ++it) { + const QString &str = it.data(); + if(!str.isEmpty()) { + QDomElement e = s.createTextElement(s.baseNS(), "body", str); + if(!it.key().isEmpty()) + e.setAttributeNS(NS_XML, "xml:lang", it.key()); + s.appendChild(e); + } + } + if ( !d->xHTMLBody.isEmpty()) { + QDomElement parent = s.createElement(s.xhtmlImNS(), "html"); + for(it = d->xHTMLBody.begin(); it != d->xHTMLBody.end(); ++it) { + const QString &str = it.data(); + if(!str.isEmpty()) { + QDomElement child = s.createXHTMLElement(str); + if(!it.key().isEmpty()) + child.setAttributeNS(NS_XML, "xml:lang", it.key()); + parent.appendChild(child); + } + } + s.appendChild(parent); + } + if(d->type == "error") + s.setError(d->error); + + // timestamp + /*if(!d->timeStamp.isNull()) { + QDomElement e = s.createElement("jabber:x:delay", "x"); + e.setAttribute("stamp", TS2stamp(d->timeStamp)); + s.appendChild(e); + }*/ + + // urls + for(QValueList<Url>::ConstIterator uit = d->urlList.begin(); uit != d->urlList.end(); ++uit) { + QDomElement x = s.createElement("jabber:x:oob", "x"); + x.appendChild(s.createTextElement("jabber:x:oob", "url", (*uit).url())); + if(!(*uit).desc().isEmpty()) + x.appendChild(s.createTextElement("jabber:x:oob", "desc", (*uit).desc())); + s.appendChild(x); + } + + // events + if (!d->eventList.isEmpty()) { + QDomElement x = s.createElement("jabber:x:event", "x"); + + if (d->body.isEmpty()) { + if (d->eventId.isEmpty()) + x.appendChild(s.createElement("jabber:x:event","id")); + else + x.appendChild(s.createTextElement("jabber:x:event","id",d->eventId)); + } + else if (d->type=="chat" || d->type=="groupchat") + s.appendChild( s.createElement(NS_CHATSTATES , "active" ) ); + + bool need_x_event=false; + for(QValueList<MsgEvent>::ConstIterator ev = d->eventList.begin(); ev != d->eventList.end(); ++ev) { + switch (*ev) { + case OfflineEvent: + x.appendChild(s.createElement("jabber:x:event", "offline")); + need_x_event=true; + break; + case DeliveredEvent: + x.appendChild(s.createElement("jabber:x:event", "delivered")); + need_x_event=true; + break; + case DisplayedEvent: + x.appendChild(s.createElement("jabber:x:event", "displayed")); + need_x_event=true; + break; + case ComposingEvent: + x.appendChild(s.createElement("jabber:x:event", "composing")); + need_x_event=true; + if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "composing" ) ); + break; + case CancelEvent: + need_x_event=true; + if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "paused" ) ); + break; + case InactiveEvent: + if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "inactive" ) ); + break; + case GoneEvent: + if (d->body.isEmpty() && (d->type=="chat" || d->type=="groupchat") ) + s.appendChild( s.createElement(NS_CHATSTATES , "gone" ) ); + break; + } + } + if(need_x_event) //we don't need to have the (empty) x:event element if this is only <gone> or <inactive> + s.appendChild(x); + } + + + // xencrypted + if(!d->xencrypted.isEmpty()) + s.appendChild(s.createTextElement("jabber:x:encrypted", "x", d->xencrypted)); + + // invite + if(!d->invite.isEmpty()) { + QDomElement e = s.createElement("jabber:x:conference", "x"); + e.setAttribute("jid", d->invite); + s.appendChild(e); + } + + return s; +} + +bool Message::fromStanza(const Stanza &s, int timeZoneOffset) +{ + if(s.kind() != Stanza::Message) + return false; + + setTo(s.to()); + setFrom(s.from()); + setId(s.id()); + setType(s.type()); + setLang(s.lang()); + + d->subject.clear(); + d->body.clear(); + d->thread = QString(); + d->eventList.clear(); + + QDomElement root = s.element(); + + QDomNodeList nl = root.childNodes(); + uint n; + for(n = 0; n < nl.count(); ++n) { + QDomNode i = nl.item(n); + if(i.isElement()) { + QDomElement e = i.toElement(); + if(e.namespaceURI() == s.baseNS()) { + if(e.tagName() == "subject") { + QString lang = e.attributeNS(NS_XML, "lang", ""); + d->subject[lang] = e.text(); + } + else if(e.tagName() == "body") { + QString lang = e.attributeNS(NS_XML, "lang", ""); + d->body[lang] = e.text(); + } + else if(e.tagName() == "thread") + d->thread = e.text(); + } + else if (e.namespaceURI() == s.xhtmlImNS()) { + if (e.tagName() == "html") { + QDomNodeList htmlNL= e.childNodes(); + for (unsigned int x = 0; x < htmlNL.count(); x++) { + QDomElement i = htmlNL.item(x).toElement(); + + if (i.tagName() == "body") { + QDomDocument RichText; + QString lang = i.attributeNS(NS_XML, "lang", ""); + RichText.appendChild(i); + d-> xHTMLBody[lang] = RichText.toString(); + } + } + } + } + else if (e.namespaceURI() == NS_CHATSTATES) + { + if(e.tagName() == "active") + { + //like in JEP-0022 we let the client know that we can receive ComposingEvent + // (we can do that according to §4.6 of the JEP-0085) + d->eventList += ComposingEvent; + d->eventList += InactiveEvent; + d->eventList += GoneEvent; + } + else if (e.tagName() == "composing") + { + d->eventList += ComposingEvent; + } + else if (e.tagName() == "paused") + { + d->eventList += CancelEvent; + } + else if (e.tagName() == "inactive") + { + d->eventList += InactiveEvent; + } + else if (e.tagName() == "gone") + { + d->eventList += GoneEvent; + } + } + else { + //printf("extension element: [%s]\n", e.tagName().latin1()); + } + } + } + + if(s.type() == "error") + d->error = s.error(); + + // timestamp + QDomElement t = root.elementsByTagNameNS("jabber:x:delay", "x").item(0).toElement(); + if(!t.isNull()) { + d->timeStamp = stamp2TS(t.attribute("stamp")); + d->timeStamp = d->timeStamp.addSecs(timeZoneOffset * 3600); + d->spooled = true; + } + else { + d->timeStamp = QDateTime::currentDateTime(); + d->spooled = false; + } + + // urls + d->urlList.clear(); + nl = root.elementsByTagNameNS("jabber:x:oob", "x"); + for(n = 0; n < nl.count(); ++n) { + QDomElement t = nl.item(n).toElement(); + Url u; + u.setUrl(t.elementsByTagName("url").item(0).toElement().text()); + u.setDesc(t.elementsByTagName("desc").item(0).toElement().text()); + d->urlList += u; + } + + // events + nl = root.elementsByTagNameNS("jabber:x:event", "x"); + if (nl.count()) { + nl = nl.item(0).childNodes(); + for(n = 0; n < nl.count(); ++n) { + QString evtag = nl.item(n).toElement().tagName(); + if (evtag == "id") { + d->eventId = nl.item(n).toElement().text(); + } + else if (evtag == "displayed") + d->eventList += DisplayedEvent; + else if (evtag == "composing") + d->eventList += ComposingEvent; + else if (evtag == "delivered") + d->eventList += DeliveredEvent; + else if (evtag == "offline") + d->eventList += OfflineEvent; + } + if (d->eventList.isEmpty()) + d->eventList += CancelEvent; + } + + // xencrypted + t = root.elementsByTagNameNS("jabber:x:encrypted", "x").item(0).toElement(); + if(!t.isNull()) + d->xencrypted = t.text(); + else + d->xencrypted = QString(); + + // invite + t = root.elementsByTagNameNS("jabber:x:conference", "x").item(0).toElement(); + if(!t.isNull()) + d->invite = t.attribute("jid"); + else + d->invite = QString(); + + return true; +} + +//--------------------------------------------------------------------------- +// Subscription +//--------------------------------------------------------------------------- +Subscription::Subscription(SubType type) +{ + value = type; +} + +int Subscription::type() const +{ + return value; +} + +QString Subscription::toString() const +{ + switch(value) { + case Remove: + return "remove"; + case Both: + return "both"; + case From: + return "from"; + case To: + return "to"; + case None: + default: + return "none"; + } +} + +bool Subscription::fromString(const QString &s) +{ + if(s == "remove") + value = Remove; + else if(s == "both") + value = Both; + else if(s == "from") + value = From; + else if(s == "to") + value = To; + else if(s == "none") + value = None; + else + return false; + + return true; +} + + +//--------------------------------------------------------------------------- +// Status +//--------------------------------------------------------------------------- +Status::Status(const QString &show, const QString &status, int priority, bool available) +{ + v_isAvailable = available; + v_show = show; + v_status = status; + v_priority = priority; + v_timeStamp = QDateTime::currentDateTime(); + v_isInvisible = false; + ecode = -1; +} + +Status::~Status() +{ +} + +bool Status::hasError() const +{ + return (ecode != -1); +} + +void Status::setError(int code, const QString &str) +{ + ecode = code; + estr = str; +} + +void Status::setIsAvailable(bool available) +{ + v_isAvailable = available; +} + +void Status::setIsInvisible(bool invisible) +{ + v_isInvisible = invisible; +} + +void Status::setPriority(int x) +{ + v_priority = x; +} + +void Status::setShow(const QString & _show) +{ + v_show = _show; +} + +void Status::setStatus(const QString & _status) +{ + v_status = _status; +} + +void Status::setTimeStamp(const QDateTime & _timestamp) +{ + v_timeStamp = _timestamp; +} + +void Status::setKeyID(const QString &key) +{ + v_key = key; +} + +void Status::setXSigned(const QString &s) +{ + v_xsigned = s; +} + +void Status::setSongTitle(const QString & _songtitle) +{ + v_songTitle = _songtitle; +} + +void Status::setCapsNode(const QString & _capsNode) +{ + v_capsNode = _capsNode; +} + +void Status::setCapsVersion(const QString & _capsVersion) +{ + v_capsVersion = _capsVersion; +} + +void Status::setCapsExt(const QString & _capsExt) +{ + v_capsExt = _capsExt; +} + +bool Status::isAvailable() const +{ + return v_isAvailable; +} + +bool Status::isAway() const +{ + if(v_show == "away" || v_show == "xa" || v_show == "dnd") + return true; + + return false; +} + +bool Status::isInvisible() const +{ + return v_isInvisible; +} + +int Status::priority() const +{ + return v_priority; +} + +const QString & Status::show() const +{ + return v_show; +} +const QString & Status::status() const +{ + return v_status; +} + +QDateTime Status::timeStamp() const +{ + return v_timeStamp; +} + +const QString & Status::keyID() const +{ + return v_key; +} + +const QString & Status::xsigned() const +{ + return v_xsigned; +} + +const QString & Status::songTitle() const +{ + return v_songTitle; +} + +const QString & Status::capsNode() const +{ + return v_capsNode; +} + +const QString & Status::capsVersion() const +{ + return v_capsVersion; +} + +const QString & Status::capsExt() const +{ + return v_capsExt; +} + +int Status::errorCode() const +{ + return ecode; +} + +const QString & Status::errorString() const +{ + return estr; +} + + +//--------------------------------------------------------------------------- +// Resource +//--------------------------------------------------------------------------- +Resource::Resource(const QString &name, const Status &stat) +{ + v_name = name; + v_status = stat; +} + +Resource::~Resource() +{ +} + +const QString & Resource::name() const +{ + return v_name; +} + +int Resource::priority() const +{ + return v_status.priority(); +} + +const Status & Resource::status() const +{ + return v_status; +} + +void Resource::setName(const QString & _name) +{ + v_name = _name; +} + +void Resource::setStatus(const Status & _status) +{ + v_status = _status; +} + + +//--------------------------------------------------------------------------- +// ResourceList +//--------------------------------------------------------------------------- +ResourceList::ResourceList() +:QValueList<Resource>() +{ +} + +ResourceList::~ResourceList() +{ +} + +ResourceList::Iterator ResourceList::find(const QString & _find) +{ + for(ResourceList::Iterator it = begin(); it != end(); ++it) { + if((*it).name() == _find) + return it; + } + + return end(); +} + +ResourceList::Iterator ResourceList::priority() +{ + ResourceList::Iterator highest = end(); + + for(ResourceList::Iterator it = begin(); it != end(); ++it) { + if(highest == end() || (*it).priority() > (*highest).priority()) + highest = it; + } + + return highest; +} + +ResourceList::ConstIterator ResourceList::find(const QString & _find) const +{ + for(ResourceList::ConstIterator it = begin(); it != end(); ++it) { + if((*it).name() == _find) + return it; + } + + return end(); +} + +ResourceList::ConstIterator ResourceList::priority() const +{ + ResourceList::ConstIterator highest = end(); + + for(ResourceList::ConstIterator it = begin(); it != end(); ++it) { + if(highest == end() || (*it).priority() > (*highest).priority()) + highest = it; + } + + return highest; +} + + +//--------------------------------------------------------------------------- +// RosterItem +//--------------------------------------------------------------------------- +RosterItem::RosterItem(const Jid &_jid) +{ + v_jid = _jid; +} + +RosterItem::~RosterItem() +{ +} + +const Jid & RosterItem::jid() const +{ + return v_jid; +} + +const QString & RosterItem::name() const +{ + return v_name; +} + +const QStringList & RosterItem::groups() const +{ + return v_groups; +} + +const Subscription & RosterItem::subscription() const +{ + return v_subscription; +} + +const QString & RosterItem::ask() const +{ + return v_ask; +} + +bool RosterItem::isPush() const +{ + return v_push; +} + +bool RosterItem::inGroup(const QString &g) const +{ + for(QStringList::ConstIterator it = v_groups.begin(); it != v_groups.end(); ++it) { + if(*it == g) + return true; + } + return false; +} + +void RosterItem::setJid(const Jid &_jid) +{ + v_jid = _jid; +} + +void RosterItem::setName(const QString &_name) +{ + v_name = _name; +} + +void RosterItem::setGroups(const QStringList &_groups) +{ + v_groups = _groups; +} + +void RosterItem::setSubscription(const Subscription &type) +{ + v_subscription = type; +} + +void RosterItem::setAsk(const QString &_ask) +{ + v_ask = _ask; +} + +void RosterItem::setIsPush(bool b) +{ + v_push = b; +} + +bool RosterItem::addGroup(const QString &g) +{ + if(inGroup(g)) + return false; + + v_groups += g; + return true; +} + +bool RosterItem::removeGroup(const QString &g) +{ + for(QStringList::Iterator it = v_groups.begin(); it != v_groups.end(); ++it) { + if(*it == g) { + v_groups.remove(it); + return true; + } + } + + return false; +} + +QDomElement RosterItem::toXml(QDomDocument *doc) const +{ + QDomElement item = doc->createElement("item"); + item.setAttribute("jid", v_jid.full()); + item.setAttribute("name", v_name); + item.setAttribute("subscription", v_subscription.toString()); + if(!v_ask.isEmpty()) + item.setAttribute("ask", v_ask); + for(QStringList::ConstIterator it = v_groups.begin(); it != v_groups.end(); ++it) + item.appendChild(textTag(doc, "group", *it)); + + return item; +} + +bool RosterItem::fromXml(const QDomElement &item) +{ + if(item.tagName() != "item") + return false; + Jid j(item.attribute("jid")); + if(!j.isValid()) + return false; + QString na = item.attribute("name"); + Subscription s; + if(!s.fromString(item.attribute("subscription")) ) + return false; + QStringList g; + for(QDomNode n = item.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + if(i.tagName() == "group") + g += tagContent(i); + } + QString a = item.attribute("ask"); + + v_jid = j; + v_name = na; + v_subscription = s; + v_groups = g; + v_ask = a; + + return true; +} + + +//--------------------------------------------------------------------------- +// Roster +//--------------------------------------------------------------------------- +Roster::Roster() +:QValueList<RosterItem>() +{ +} + +Roster::~Roster() +{ +} + +Roster::Iterator Roster::find(const Jid &j) +{ + for(Roster::Iterator it = begin(); it != end(); ++it) { + if((*it).jid().compare(j)) + return it; + } + + return end(); +} + +Roster::ConstIterator Roster::find(const Jid &j) const +{ + for(Roster::ConstIterator it = begin(); it != end(); ++it) { + if((*it).jid().compare(j)) + return it; + } + + return end(); +} + + +//--------------------------------------------------------------------------- +// FormField +//--------------------------------------------------------------------------- +FormField::FormField(const QString &type, const QString &value) +{ + v_type = misc; + if(!type.isEmpty()) { + int x = tagNameToType(type); + if(x != -1) + v_type = x; + } + v_value = value; +} + +FormField::~FormField() +{ +} + +int FormField::type() const +{ + return v_type; +} + +QString FormField::realName() const +{ + return typeToTagName(v_type); +} + +QString FormField::fieldName() const +{ + switch(v_type) { + case username: return QObject::tr("Username"); + case nick: return QObject::tr("Nickname"); + case password: return QObject::tr("Password"); + case name: return QObject::tr("Name"); + case first: return QObject::tr("First Name"); + case last: return QObject::tr("Last Name"); + case email: return QObject::tr("E-mail"); + case address: return QObject::tr("Address"); + case city: return QObject::tr("City"); + case state: return QObject::tr("State"); + case zip: return QObject::tr("Zipcode"); + case phone: return QObject::tr("Phone"); + case url: return QObject::tr("URL"); + case date: return QObject::tr("Date"); + case misc: return QObject::tr("Misc"); + default: return ""; + }; +} + +bool FormField::isSecret() const +{ + return (type() == password); +} + +const QString & FormField::value() const +{ + return v_value; +} + +void FormField::setType(int x) +{ + v_type = x; +} + +bool FormField::setType(const QString &in) +{ + int x = tagNameToType(in); + if(x == -1) + return false; + + v_type = x; + return true; +} + +void FormField::setValue(const QString &in) +{ + v_value = in; +} + +int FormField::tagNameToType(const QString &in) const +{ + if(!in.compare("username")) return username; + if(!in.compare("nick")) return nick; + if(!in.compare("password")) return password; + if(!in.compare("name")) return name; + if(!in.compare("first")) return first; + if(!in.compare("last")) return last; + if(!in.compare("email")) return email; + if(!in.compare("address")) return address; + if(!in.compare("city")) return city; + if(!in.compare("state")) return state; + if(!in.compare("zip")) return zip; + if(!in.compare("phone")) return phone; + if(!in.compare("url")) return url; + if(!in.compare("date")) return date; + if(!in.compare("misc")) return misc; + + return -1; +} + +QString FormField::typeToTagName(int type) const +{ + switch(type) { + case username: return "username"; + case nick: return "nick"; + case password: return "password"; + case name: return "name"; + case first: return "first"; + case last: return "last"; + case email: return "email"; + case address: return "address"; + case city: return "city"; + case state: return "state"; + case zip: return "zipcode"; + case phone: return "phone"; + case url: return "url"; + case date: return "date"; + case misc: return "misc"; + default: return ""; + }; +} + + +//--------------------------------------------------------------------------- +// Form +//--------------------------------------------------------------------------- +Form::Form(const Jid &j) +:QValueList<FormField>() +{ + setJid(j); +} + +Form::~Form() +{ +} + +Jid Form::jid() const +{ + return v_jid; +} + +QString Form::instructions() const +{ + return v_instructions; +} + +QString Form::key() const +{ + return v_key; +} + +void Form::setJid(const Jid &j) +{ + v_jid = j; +} + +void Form::setInstructions(const QString &s) +{ + v_instructions = s; +} + +void Form::setKey(const QString &s) +{ + v_key = s; +} + + +//--------------------------------------------------------------------------- +// SearchResult +//--------------------------------------------------------------------------- +SearchResult::SearchResult(const Jid &jid) +{ + setJid(jid); +} + +SearchResult::~SearchResult() +{ +} + +const Jid & SearchResult::jid() const +{ + return v_jid; +} + +const QString & SearchResult::nick() const +{ + return v_nick; +} + +const QString & SearchResult::first() const +{ + return v_first; +} + +const QString & SearchResult::last() const +{ + return v_last; +} + +const QString & SearchResult::email() const +{ + return v_email; +} + +void SearchResult::setJid(const Jid &jid) +{ + v_jid = jid; +} + +void SearchResult::setNick(const QString &nick) +{ + v_nick = nick; +} + +void SearchResult::setFirst(const QString &first) +{ + v_first = first; +} + +void SearchResult::setLast(const QString &last) +{ + v_last = last; +} + +void SearchResult::setEmail(const QString &email) +{ + v_email = email; +} + +//--------------------------------------------------------------------------- +// Features +//--------------------------------------------------------------------------- + +Features::Features() +{ +} + +Features::Features(const QStringList &l) +{ + setList(l); +} + +Features::Features(const QString &str) +{ + QStringList l; + l << str; + + setList(l); +} + +Features::~Features() +{ +} + +QStringList Features::list() const +{ + return _list; +} + +void Features::setList(const QStringList &l) +{ + _list = l; +} + +bool Features::test(const QStringList &ns) const +{ + QStringList::ConstIterator it = ns.begin(); + for ( ; it != ns.end(); ++it) + if ( _list.find( *it ) != _list.end() ) + return true; + + return false; +} + +#define FID_REGISTER "jabber:iq:register" +bool Features::canRegister() const +{ + QStringList ns; + ns << FID_REGISTER; + + return test(ns); +} + +#define FID_SEARCH "jabber:iq:search" +bool Features::canSearch() const +{ + QStringList ns; + ns << FID_SEARCH; + + return test(ns); +} + +#define FID_XHTML "http://jabber.org/protocol/xhtml-im" +bool Features::canXHTML() const +{ + QStringList ns; + + ns << FID_XHTML; + + return test(ns); +} + +#define FID_GROUPCHAT "jabber:iq:conference" +bool Features::canGroupchat() const +{ + QStringList ns; + ns << "http://jabber.org/protocol/muc"; + ns << FID_GROUPCHAT; + + return test(ns); +} + +#define FID_VOICE "http://www.google.com/xmpp/protocol/voice/v1" +bool Features::canVoice() const +{ + QStringList ns; + ns << FID_VOICE; + + return test(ns); +} + +#define FID_GATEWAY "jabber:iq:gateway" +bool Features::isGateway() const +{ + QStringList ns; + ns << FID_GATEWAY; + + return test(ns); +} + +#define FID_DISCO "http://jabber.org/protocol/disco" +bool Features::canDisco() const +{ + QStringList ns; + ns << FID_DISCO; + ns << "http://jabber.org/protocol/disco#info"; + ns << "http://jabber.org/protocol/disco#items"; + + return test(ns); +} + +#define FID_VCARD "vcard-temp" +bool Features::haveVCard() const +{ + QStringList ns; + ns << FID_VCARD; + + return test(ns); +} + +// custom Psi acitons +#define FID_ADD "psi:add" + +class Features::FeatureName : public QObject +{ + Q_OBJECT +public: + FeatureName() + : QObject(qApp) + { + id2s[FID_Invalid] = tr("ERROR: Incorrect usage of Features class"); + id2s[FID_None] = tr("None"); + id2s[FID_Register] = tr("Register"); + id2s[FID_Search] = tr("Search"); + id2s[FID_Groupchat] = tr("Groupchat"); + id2s[FID_Gateway] = tr("Gateway"); + id2s[FID_Disco] = tr("Service Discovery"); + id2s[FID_VCard] = tr("VCard"); + + // custom Psi actions + id2s[FID_Add] = tr("Add to roster"); + + // compute reverse map + //QMap<QString, long>::Iterator it = id2s.begin(); + //for ( ; it != id2s.end(); ++it) + // s2id[it.data()] = it.key(); + + id2f[FID_Register] = FID_REGISTER; + id2f[FID_Search] = FID_SEARCH; + id2f[FID_Groupchat] = FID_GROUPCHAT; + id2f[FID_Gateway] = FID_GATEWAY; + id2f[FID_Disco] = FID_DISCO; + id2f[FID_VCard] = FID_VCARD; + + // custom Psi actions + id2f[FID_Add] = FID_ADD; + } + + //QMap<QString, long> s2id; + QMap<long, QString> id2s; + QMap<long, QString> id2f; +}; + +static Features::FeatureName *featureName = 0; + +long Features::id() const +{ + if ( _list.count() > 1 ) + return FID_Invalid; + else if ( canRegister() ) + return FID_Register; + else if ( canSearch() ) + return FID_Search; + else if ( canGroupchat() ) + return FID_Groupchat; + else if ( isGateway() ) + return FID_Gateway; + else if ( canDisco() ) + return FID_Disco; + else if ( haveVCard() ) + return FID_VCard; + else if ( test(FID_ADD) ) + return FID_Add; + + return FID_None; +} + +long Features::id(const QString &feature) +{ + Features f(feature); + return f.id(); +} + +QString Features::feature(long id) +{ + if ( !featureName ) + featureName = new FeatureName(); + + return featureName->id2f[id]; +} + +QString Features::name(long id) +{ + if ( !featureName ) + featureName = new FeatureName(); + + return featureName->id2s[id]; +} + +QString Features::name() const +{ + return name(id()); +} + +QString Features::name(const QString &feature) +{ + Features f(feature); + return f.name(f.id()); +} + +//--------------------------------------------------------------------------- +// DiscoItem +//--------------------------------------------------------------------------- +class DiscoItem::Private +{ +public: + Private() + { + action = None; + } + + Jid jid; + QString name; + QString node; + Action action; + + Features features; + Identities identities; +}; + +DiscoItem::DiscoItem() +{ + d = new Private; +} + +DiscoItem::DiscoItem(const DiscoItem &from) +{ + d = new Private; + *this = from; +} + +DiscoItem & DiscoItem::operator= (const DiscoItem &from) +{ + d->jid = from.d->jid; + d->name = from.d->name; + d->node = from.d->node; + d->action = from.d->action; + d->features = from.d->features; + d->identities = from.d->identities; + + return *this; +} + +DiscoItem::~DiscoItem() +{ + delete d; +} + +AgentItem DiscoItem::toAgentItem() const +{ + AgentItem ai; + + ai.setJid( jid() ); + ai.setName( name() ); + + Identity id; + if ( !identities().isEmpty() ) + id = identities().first(); + + ai.setCategory( id.category ); + ai.setType( id.type ); + + ai.setFeatures( d->features ); + + return ai; +} + +void DiscoItem::fromAgentItem(const AgentItem &ai) +{ + setJid( ai.jid() ); + setName( ai.name() ); + + Identity id; + id.category = ai.category(); + id.type = ai.type(); + id.name = ai.name(); + + Identities idList; + idList << id; + + setIdentities( idList ); + + setFeatures( ai.features() ); +} + +const Jid &DiscoItem::jid() const +{ + return d->jid; +} + +void DiscoItem::setJid(const Jid &j) +{ + d->jid = j; +} + +const QString &DiscoItem::name() const +{ + return d->name; +} + +void DiscoItem::setName(const QString &n) +{ + d->name = n; +} + +const QString &DiscoItem::node() const +{ + return d->node; +} + +void DiscoItem::setNode(const QString &n) +{ + d->node = n; +} + +DiscoItem::Action DiscoItem::action() const +{ + return d->action; +} + +void DiscoItem::setAction(Action a) +{ + d->action = a; +} + +const Features &DiscoItem::features() const +{ + return d->features; +} + +void DiscoItem::setFeatures(const Features &f) +{ + d->features = f; +} + +const DiscoItem::Identities &DiscoItem::identities() const +{ + return d->identities; +} + +void DiscoItem::setIdentities(const Identities &i) +{ + d->identities = i; + + if ( name().isEmpty() && i.count() ) + setName( i.first().name ); +} + + +DiscoItem::Action DiscoItem::string2action(QString s) +{ + Action a; + + if ( s == "update" ) + a = Update; + else if ( s == "remove" ) + a = Remove; + else + a = None; + + return a; +} + +QString DiscoItem::action2string(Action a) +{ + QString s; + + if ( a == Update ) + s = "update"; + else if ( a == Remove ) + s = "remove"; + else + s = QString::null; + + return s; +} + +} + +#include"types.moc" diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.cpp new file mode 100644 index 00000000..ffd7e6ae --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.cpp @@ -0,0 +1,2120 @@ +/* + * tasks.cpp - basic tasks + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp_tasks.h" + +#include"base64.h" +//#include"sha1.h" +#include"xmpp_xmlcommon.h" +//#include"xmpp_stream.h" +//#include"xmpp_types.h" +#include"xmpp_vcard.h" + +#include<qregexp.h> +#include<qvaluelist.h> + +using namespace XMPP; + + +static QString lineEncode(QString str) +{ + str.replace(QRegExp("\\\\"), "\\\\"); // backslash to double-backslash + str.replace(QRegExp("\\|"), "\\p"); // pipe to \p + str.replace(QRegExp("\n"), "\\n"); // newline to \n + return str; +} + +static QString lineDecode(const QString &str) +{ + QString ret; + + for(unsigned int n = 0; n < str.length(); ++n) { + if(str.at(n) == '\\') { + ++n; + if(n >= str.length()) + break; + + if(str.at(n) == 'n') + ret.append('\n'); + if(str.at(n) == 'p') + ret.append('|'); + if(str.at(n) == '\\') + ret.append('\\'); + } + else { + ret.append(str.at(n)); + } + } + + return ret; +} + +static Roster xmlReadRoster(const QDomElement &q, bool push) +{ + Roster r; + + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "item") { + RosterItem item; + item.fromXml(i); + + if(push) + item.setIsPush(true); + + r += item; + } + } + + return r; +} + + +//---------------------------------------------------------------------------- +// JT_Register +//---------------------------------------------------------------------------- +class JT_Register::Private +{ +public: + Private() {} + + Form form; + Jid jid; + int type; +}; + +JT_Register::JT_Register(Task *parent) +:Task(parent) +{ + d = new Private; + d->type = -1; +} + +JT_Register::~JT_Register() +{ + delete d; +} + +void JT_Register::reg(const QString &user, const QString &pass) +{ + d->type = 0; + to = client()->host(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "username", user)); + query.appendChild(textTag(doc(), "password", pass)); +} + +void JT_Register::changepw(const QString &pass) +{ + d->type = 1; + to = client()->host(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "username", client()->user())); + query.appendChild(textTag(doc(), "password", pass)); +} + +void JT_Register::unreg(const Jid &j) +{ + d->type = 2; + to = j.isEmpty() ? client()->host() : j.full(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); + + // this may be useful + if(!d->form.key().isEmpty()) + query.appendChild(textTag(doc(), "key", d->form.key())); + + query.appendChild(doc()->createElement("remove")); +} + +void JT_Register::getForm(const Jid &j) +{ + d->type = 3; + to = j; + iq = createIQ(doc(), "get", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); +} + +void JT_Register::setForm(const Form &form) +{ + d->type = 4; + to = form.jid(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:register"); + iq.appendChild(query); + + // key? + if(!form.key().isEmpty()) + query.appendChild(textTag(doc(), "key", form.key())); + + // fields + for(Form::ConstIterator it = form.begin(); it != form.end(); ++it) { + const FormField &f = *it; + query.appendChild(textTag(doc(), f.realName(), f.value())); + } +} + +const Form & JT_Register::form() const +{ + return d->form; +} + +void JT_Register::onGo() +{ + send(iq); +} + +bool JT_Register::take(const QDomElement &x) +{ + if(!iqVerify(x, to, id())) + return false; + + Jid from(x.attribute("from")); + if(x.attribute("type") == "result") { + if(d->type == 3) { + d->form.clear(); + d->form.setJid(from); + + QDomElement q = queryTag(x); + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "instructions") + d->form.setInstructions(tagContent(i)); + else if(i.tagName() == "key") + d->form.setKey(tagContent(i)); + else { + FormField f; + if(f.setType(i.tagName())) { + f.setValue(tagContent(i)); + d->form += f; + } + } + } + } + + setSuccess(); + } + else + setError(x); + + return true; +} + +//---------------------------------------------------------------------------- +// JT_UnRegister +//---------------------------------------------------------------------------- +class JT_UnRegister::Private +{ +public: + Private() { } + + Jid j; + JT_Register *jt_reg; +}; + +JT_UnRegister::JT_UnRegister(Task *parent) +: Task(parent) +{ + d = new Private; + d->jt_reg = 0; +} + +JT_UnRegister::~JT_UnRegister() +{ + delete d->jt_reg; + delete d; +} + +void JT_UnRegister::unreg(const Jid &j) +{ + d->j = j; +} + +void JT_UnRegister::onGo() +{ + delete d->jt_reg; + + d->jt_reg = new JT_Register(this); + d->jt_reg->getForm(d->j); + connect(d->jt_reg, SIGNAL(finished()), SLOT(getFormFinished())); + d->jt_reg->go(false); +} + +void JT_UnRegister::getFormFinished() +{ + disconnect(d->jt_reg, 0, this, 0); + + d->jt_reg->unreg(d->j); + connect(d->jt_reg, SIGNAL(finished()), SLOT(unregFinished())); + d->jt_reg->go(false); +} + +void JT_UnRegister::unregFinished() +{ + if ( d->jt_reg->success() ) + setSuccess(); + else + setError(d->jt_reg->statusCode(), d->jt_reg->statusString()); + + delete d->jt_reg; + d->jt_reg = 0; +} + +//---------------------------------------------------------------------------- +// JT_Roster +//---------------------------------------------------------------------------- +class JT_Roster::Private +{ +public: + Private() {} + + Roster roster; + QValueList<QDomElement> itemList; +}; + +JT_Roster::JT_Roster(Task *parent) +:Task(parent) +{ + type = -1; + d = new Private; +} + +JT_Roster::~JT_Roster() +{ + delete d; +} + +void JT_Roster::get() +{ + type = 0; + //to = client()->host(); + iq = createIQ(doc(), "get", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:roster"); + iq.appendChild(query); +} + +void JT_Roster::set(const Jid &jid, const QString &name, const QStringList &groups) +{ + type = 1; + //to = client()->host(); + QDomElement item = doc()->createElement("item"); + item.setAttribute("jid", jid.full()); + if(!name.isEmpty()) + item.setAttribute("name", name); + for(QStringList::ConstIterator it = groups.begin(); it != groups.end(); ++it) + item.appendChild(textTag(doc(), "group", *it)); + d->itemList += item; +} + +void JT_Roster::remove(const Jid &jid) +{ + type = 1; + //to = client()->host(); + QDomElement item = doc()->createElement("item"); + item.setAttribute("jid", jid.full()); + item.setAttribute("subscription", "remove"); + d->itemList += item; +} + +void JT_Roster::onGo() +{ + if(type == 0) + send(iq); + else if(type == 1) { + //to = client()->host(); + iq = createIQ(doc(), "set", to.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:roster"); + iq.appendChild(query); + for(QValueList<QDomElement>::ConstIterator it = d->itemList.begin(); it != d->itemList.end(); ++it) + query.appendChild(*it); + send(iq); + } +} + +const Roster & JT_Roster::roster() const +{ + return d->roster; +} + +QString JT_Roster::toString() const +{ + if(type != 1) + return ""; + + QDomElement i = doc()->createElement("request"); + i.setAttribute("type", "JT_Roster"); + for(QValueList<QDomElement>::ConstIterator it = d->itemList.begin(); it != d->itemList.end(); ++it) + i.appendChild(*it); + return lineEncode(Stream::xmlToString(i)); + return ""; +} + +bool JT_Roster::fromString(const QString &str) +{ + QDomDocument *dd = new QDomDocument; + if(!dd->setContent(lineDecode(str).utf8())) + return false; + QDomElement e = doc()->importNode(dd->documentElement(), true).toElement(); + delete dd; + + if(e.tagName() != "request" || e.attribute("type") != "JT_Roster") + return false; + + type = 1; + d->itemList.clear(); + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + d->itemList += i; + } + + return true; +} + +bool JT_Roster::take(const QDomElement &x) +{ + if(!iqVerify(x, client()->host(), id())) + return false; + + // get + if(type == 0) { + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + d->roster = xmlReadRoster(q, false); + setSuccess(); + } + else { + setError(x); + } + + return true; + } + // set + else if(type == 1) { + if(x.attribute("type") == "result") + setSuccess(); + else + setError(x); + + return true; + } + // remove + else if(type == 2) { + setSuccess(); + return true; + } + + return false; +} + + +//---------------------------------------------------------------------------- +// JT_PushRoster +//---------------------------------------------------------------------------- +JT_PushRoster::JT_PushRoster(Task *parent) +:Task(parent) +{ +} + +JT_PushRoster::~JT_PushRoster() +{ +} + +bool JT_PushRoster::take(const QDomElement &e) +{ + // must be an iq-set tag + if(e.tagName() != "iq" || e.attribute("type") != "set") + return false; + + if(!iqVerify(e, client()->host(), "", "jabber:iq:roster")) + return false; + + roster(xmlReadRoster(queryTag(e), true)); + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_Presence +//---------------------------------------------------------------------------- +JT_Presence::JT_Presence(Task *parent) +:Task(parent) +{ + type = -1; +} + +JT_Presence::~JT_Presence() +{ +} + +void JT_Presence::pres(const Status &s) +{ + type = 0; + + tag = doc()->createElement("presence"); + if(!s.isAvailable()) { + tag.setAttribute("type", "unavailable"); + if(!s.status().isEmpty()) + tag.appendChild(textTag(doc(), "status", s.status())); + } + else { + if(s.isInvisible()) + tag.setAttribute("type", "invisible"); + + if(!s.show().isEmpty()) + tag.appendChild(textTag(doc(), "show", s.show())); + if(!s.status().isEmpty()) + tag.appendChild(textTag(doc(), "status", s.status())); + + tag.appendChild( textTag(doc(), "priority", QString("%1").arg(s.priority()) ) ); + + if(!s.keyID().isEmpty()) { + QDomElement x = textTag(doc(), "x", s.keyID()); + x.setAttribute("xmlns", "http://jabber.org/protocol/e2e"); + tag.appendChild(x); + } + if(!s.xsigned().isEmpty()) { + QDomElement x = textTag(doc(), "x", s.xsigned()); + x.setAttribute("xmlns", "jabber:x:signed"); + tag.appendChild(x); + } + + if(!s.capsNode().isEmpty() && !s.capsVersion().isEmpty()) { + QDomElement c = doc()->createElement("c"); + c.setAttribute("xmlns","http://jabber.org/protocol/caps"); + c.setAttribute("node",s.capsNode()); + c.setAttribute("ver",s.capsVersion()); + if (!s.capsExt().isEmpty()) + c.setAttribute("ext",s.capsExt()); + tag.appendChild(c); + } + } +} + +void JT_Presence::pres(const Jid &to, const Status &s) +{ + pres(s); + tag.setAttribute("to", to.full()); +} + +void JT_Presence::sub(const Jid &to, const QString &subType) +{ + type = 1; + + tag = doc()->createElement("presence"); + tag.setAttribute("to", to.full()); + tag.setAttribute("type", subType); +} + +void JT_Presence::onGo() +{ + send(tag); + setSuccess(); +} + + +//---------------------------------------------------------------------------- +// JT_PushPresence +//---------------------------------------------------------------------------- +JT_PushPresence::JT_PushPresence(Task *parent) +:Task(parent) +{ +} + +JT_PushPresence::~JT_PushPresence() +{ +} + +bool JT_PushPresence::take(const QDomElement &e) +{ + if(e.tagName() != "presence") + return false; + + Jid j(e.attribute("from")); + Status p; + + if(e.hasAttribute("type")) { + QString type = e.attribute("type"); + if(type == "unavailable") { + p.setIsAvailable(false); + } + else if(type == "error") { + QString str = ""; + int code = 0; + getErrorFromElement(e, &code, &str); + p.setError(code, str); + } + else { + subscription(j, type); + return true; + } + } + + QDomElement tag; + bool found; + + tag = findSubTag(e, "status", &found); + if(found) + p.setStatus(tagContent(tag)); + tag = findSubTag(e, "show", &found); + if(found) + p.setShow(tagContent(tag)); + tag = findSubTag(e, "priority", &found); + if(found) + p.setPriority(tagContent(tag).toInt()); + + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "x" && i.attribute("xmlns") == "jabber:x:delay") { + if(i.hasAttribute("stamp")) { + QDateTime dt; + if(stamp2TS(i.attribute("stamp"), &dt)) + dt = dt.addSecs(client()->timeZoneOffset() * 3600); + p.setTimeStamp(dt); + } + } + else if(i.tagName() == "x" && i.attribute("xmlns") == "gabber:x:music:info") { + QDomElement t; + bool found; + QString title, state; + + t = findSubTag(i, "title", &found); + if(found) + title = tagContent(t); + t = findSubTag(i, "state", &found); + if(found) + state = tagContent(t); + + if(!title.isEmpty() && state == "playing") + p.setSongTitle(title); + } + else if(i.tagName() == "x" && i.attribute("xmlns") == "jabber:x:signed") { + p.setXSigned(tagContent(i)); + } + else if(i.tagName() == "x" && i.attribute("xmlns") == "http://jabber.org/protocol/e2e") { + p.setKeyID(tagContent(i)); + } + else if(i.tagName() == "c" && i.attribute("xmlns") == "http://jabber.org/protocol/caps") { + p.setCapsNode(i.attribute("node")); + p.setCapsVersion(i.attribute("ver")); + p.setCapsExt(i.attribute("ext")); + } + } + + presence(j, p); + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_Message +//---------------------------------------------------------------------------- +static QDomElement oldStyleNS(const QDomElement &e) +{ + // find closest parent with a namespace + QDomNode par = e.parentNode(); + while(!par.isNull() && par.namespaceURI().isNull()) + par = par.parentNode(); + bool noShowNS = false; + if(!par.isNull() && par.namespaceURI() == e.namespaceURI()) + noShowNS = true; + + QDomElement i; + uint x; + //if(noShowNS) + i = e.ownerDocument().createElement(e.tagName()); + //else + // i = e.ownerDocument().createElementNS(e.namespaceURI(), e.tagName()); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) + i.setAttributeNode(al.item(x).cloneNode().toAttr()); + + if(!noShowNS) + i.setAttribute("xmlns", e.namespaceURI()); + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(oldStyleNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + return i; +} + +JT_Message::JT_Message(Task *parent, const Message &msg) +:Task(parent) +{ + m = msg; + m.setId(id()); +} + +JT_Message::~JT_Message() +{ +} + +void JT_Message::onGo() +{ + Stanza s = m.toStanza(&(client()->stream())); + QDomElement e = oldStyleNS(s.element()); + send(e); + setSuccess(); +} + + +//---------------------------------------------------------------------------- +// JT_PushMessage +//---------------------------------------------------------------------------- +static QDomElement addCorrectNS(const QDomElement &e) +{ + uint x; + + // grab child nodes + /*QDomDocumentFragment frag = e.ownerDocument().createDocumentFragment(); + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) + frag.appendChild(nl.item(x).cloneNode());*/ + + // find closest xmlns + QDomNode n = e; + while(!n.isNull() && !n.toElement().hasAttribute("xmlns")) + n = n.parentNode(); + QString ns; + if(n.isNull() || !n.toElement().hasAttribute("xmlns")) + ns = "jabber:client"; + else + ns = n.toElement().attribute("xmlns"); + + // make a new node + QDomElement i = e.ownerDocument().createElementNS(ns, e.tagName()); + + // copy attributes + QDomNamedNodeMap al = e.attributes(); + for(x = 0; x < al.count(); ++x) { + QDomAttr a = al.item(x).toAttr(); + if(a.name() != "xmlns") + i.setAttributeNodeNS(al.item(x).cloneNode().toAttr()); + } + + // copy children + QDomNodeList nl = e.childNodes(); + for(x = 0; x < nl.count(); ++x) { + QDomNode n = nl.item(x); + if(n.isElement()) + i.appendChild(addCorrectNS(n.toElement())); + else + i.appendChild(n.cloneNode()); + } + + //i.appendChild(frag); + return i; +} + +JT_PushMessage::JT_PushMessage(Task *parent) +:Task(parent) +{ +} + +JT_PushMessage::~JT_PushMessage() +{ +} + +bool JT_PushMessage::take(const QDomElement &e) +{ + if(e.tagName() != "message") + return false; + + Stanza s = client()->stream().createStanza(addCorrectNS(e)); + if(s.isNull()) { + //printf("take: bad stanza??\n"); + return false; + } + + Message m; + if(!m.fromStanza(s, client()->timeZoneOffset())) { + //printf("bad message\n"); + return false; + } + + message(m); + return true; +} + + +//---------------------------------------------------------------------------- +// JT_GetLastActivity +//---------------------------------------------------------------------------- +class JT_GetLastActivity::Private +{ +public: + Private() {} + + int seconds; + QString message; +}; + +JT_GetLastActivity::JT_GetLastActivity(Task *parent) +:Task(parent) +{ + d = new Private; +} + +JT_GetLastActivity::~JT_GetLastActivity() +{ + delete d; +} + +void JT_GetLastActivity::get(const Jid &j) +{ + jid = j; + iq = createIQ(doc(), "get", jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:last"); + iq.appendChild(query); +} + +int JT_GetLastActivity::seconds() const +{ + return d->seconds; +} + +const QString &JT_GetLastActivity::message() const +{ + return d->message; +} + +void JT_GetLastActivity::onGo() +{ + send(iq); +} + +bool JT_GetLastActivity::take(const QDomElement &x) +{ + if(!iqVerify(x, jid, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + + d->message = q.text(); + bool ok; + d->seconds = q.attribute("seconds").toInt(&ok); + + setSuccess(ok); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_GetServices +//---------------------------------------------------------------------------- +JT_GetServices::JT_GetServices(Task *parent) +:Task(parent) +{ +} + +void JT_GetServices::get(const Jid &j) +{ + agentList.clear(); + + jid = j; + iq = createIQ(doc(), "get", jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:agents"); + iq.appendChild(query); +} + +const AgentList & JT_GetServices::agents() const +{ + return agentList; +} + +void JT_GetServices::onGo() +{ + send(iq); +} + +bool JT_GetServices::take(const QDomElement &x) +{ + if(!iqVerify(x, jid, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + + // agents + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "agent") { + AgentItem a; + + a.setJid(Jid(i.attribute("jid"))); + + QDomElement tag; + bool found; + + tag = findSubTag(i, "name", &found); + if(found) + a.setName(tagContent(tag)); + + // determine which namespaces does item support + QStringList ns; + + tag = findSubTag(i, "register", &found); + if(found) + ns << "jabber:iq:register"; + tag = findSubTag(i, "search", &found); + if(found) + ns << "jabber:iq:search"; + tag = findSubTag(i, "groupchat", &found); + if(found) + ns << "jabber:iq:conference"; + tag = findSubTag(i, "transport", &found); + if(found) + ns << "jabber:iq:gateway"; + + a.setFeatures(ns); + + agentList += a; + } + } + + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_VCard +//---------------------------------------------------------------------------- +class JT_VCard::Private +{ +public: + Private() {} + + QDomElement iq; + Jid jid; + VCard vcard; +}; + +JT_VCard::JT_VCard(Task *parent) +:Task(parent) +{ + type = -1; + d = new Private; +} + +JT_VCard::~JT_VCard() +{ + delete d; +} + +void JT_VCard::get(const Jid &_jid) +{ + type = 0; + d->jid = _jid; + d->iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement v = doc()->createElement("vCard"); + v.setAttribute("xmlns", "vcard-temp"); + v.setAttribute("version", "2.0"); + v.setAttribute("prodid", "-//HandGen//NONSGML vGen v1.0//EN"); + d->iq.appendChild(v); +} + +const Jid & JT_VCard::jid() const +{ + return d->jid; +} + +const VCard & JT_VCard::vcard() const +{ + return d->vcard; +} + +void JT_VCard::set(const VCard &card) +{ + type = 1; + d->vcard = card; + d->jid = ""; + d->iq = createIQ(doc(), "set", d->jid.full(), id()); + d->iq.appendChild(card.toXml(doc()) ); +} + +void JT_VCard::onGo() +{ + send(d->iq); +} + +bool JT_VCard::take(const QDomElement &x) +{ + Jid to = d->jid; + if (to.userHost() == client()->jid().userHost()) + to = client()->host(); + if(!iqVerify(x, to, id())) + return false; + + if(x.attribute("type") == "result") { + if(type == 0) { + for(QDomNode n = x.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement q = n.toElement(); + if(q.isNull()) + continue; + + if(q.tagName().upper() == "VCARD") { + if(d->vcard.fromXml(q)) { + setSuccess(); + return true; + } + } + } + + setError(ErrDisc + 1, tr("No VCard available")); + return true; + } + else { + setSuccess(); + return true; + } + } + else { + setError(x); + } + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_Search +//---------------------------------------------------------------------------- +class JT_Search::Private +{ +public: + Private() {} + + Jid jid; + Form form; + QValueList<SearchResult> resultList; +}; + +JT_Search::JT_Search(Task *parent) +:Task(parent) +{ + d = new Private; + type = -1; +} + +JT_Search::~JT_Search() +{ + delete d; +} + +void JT_Search::get(const Jid &jid) +{ + type = 0; + d->jid = jid; + iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:search"); + iq.appendChild(query); +} + +void JT_Search::set(const Form &form) +{ + type = 1; + d->jid = form.jid(); + iq = createIQ(doc(), "set", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:search"); + iq.appendChild(query); + + // key? + if(!form.key().isEmpty()) + query.appendChild(textTag(doc(), "key", form.key())); + + // fields + for(Form::ConstIterator it = form.begin(); it != form.end(); ++it) { + const FormField &f = *it; + query.appendChild(textTag(doc(), f.realName(), f.value())); + } +} + +const Form & JT_Search::form() const +{ + return d->form; +} + +const QValueList<SearchResult> & JT_Search::results() const +{ + return d->resultList; +} + +void JT_Search::onGo() +{ + send(iq); +} + +bool JT_Search::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + Jid from(x.attribute("from")); + if(x.attribute("type") == "result") { + if(type == 0) { + d->form.clear(); + d->form.setJid(from); + + QDomElement q = queryTag(x); + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "instructions") + d->form.setInstructions(tagContent(i)); + else if(i.tagName() == "key") + d->form.setKey(tagContent(i)); + else { + FormField f; + if(f.setType(i.tagName())) { + f.setValue(tagContent(i)); + d->form += f; + } + } + } + } + else { + d->resultList.clear(); + + QDomElement q = queryTag(x); + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "item") { + SearchResult r(Jid(i.attribute("jid"))); + + QDomElement tag; + bool found; + + tag = findSubTag(i, "nick", &found); + if(found) + r.setNick(tagContent(tag)); + tag = findSubTag(i, "first", &found); + if(found) + r.setFirst(tagContent(tag)); + tag = findSubTag(i, "last", &found); + if(found) + r.setLast(tagContent(tag)); + tag = findSubTag(i, "email", &found); + if(found) + r.setEmail(tagContent(tag)); + + d->resultList += r; + } + } + } + setSuccess(); + } + else { + setError(x); + } + + return true; +} + + +//---------------------------------------------------------------------------- +// JT_ClientVersion +//---------------------------------------------------------------------------- +JT_ClientVersion::JT_ClientVersion(Task *parent) +:Task(parent) +{ +} + +void JT_ClientVersion::get(const Jid &jid) +{ + j = jid; + iq = createIQ(doc(), "get", j.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:version"); + iq.appendChild(query); +} + +void JT_ClientVersion::onGo() +{ + send(iq); +} + +bool JT_ClientVersion::take(const QDomElement &x) +{ + if(!iqVerify(x, j, id())) + return false; + + if(x.attribute("type") == "result") { + bool found; + QDomElement q = queryTag(x); + QDomElement tag; + tag = findSubTag(q, "name", &found); + if(found) + v_name = tagContent(tag); + tag = findSubTag(q, "version", &found); + if(found) + v_ver = tagContent(tag); + tag = findSubTag(q, "os", &found); + if(found) + v_os = tagContent(tag); + + setSuccess(); + } + else { + setError(x); + } + + return true; +} + +const Jid & JT_ClientVersion::jid() const +{ + return j; +} + +const QString & JT_ClientVersion::name() const +{ + return v_name; +} + +const QString & JT_ClientVersion::version() const +{ + return v_ver; +} + +const QString & JT_ClientVersion::os() const +{ + return v_os; +} + + +//---------------------------------------------------------------------------- +// JT_ClientTime +//---------------------------------------------------------------------------- +/*JT_ClientTime::JT_ClientTime(Task *parent, const Jid &_j) +:Task(parent) +{ + j = _j; + iq = createIQ("get", j.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:time"); + iq.appendChild(query); +} + +void JT_ClientTime::go() +{ + send(iq); +} + +bool JT_ClientTime::take(const QDomElement &x) +{ + if(x.attribute("id") != id()) + return FALSE; + + if(x.attribute("type") == "result") { + bool found; + QDomElement q = queryTag(x); + QDomElement tag; + tag = findSubTag(q, "utc", &found); + if(found) + stamp2TS(tagContent(tag), &utc); + tag = findSubTag(q, "tz", &found); + if(found) + timezone = tagContent(tag); + tag = findSubTag(q, "display", &found); + if(found) + display = tagContent(tag); + + setSuccess(TRUE); + } + else { + setError(getErrorString(x)); + setSuccess(FALSE); + } + + return TRUE; +} +*/ + + +//---------------------------------------------------------------------------- +// JT_ServInfo +//---------------------------------------------------------------------------- +JT_ServInfo::JT_ServInfo(Task *parent) +:Task(parent) +{ +} + +JT_ServInfo::~JT_ServInfo() +{ +} + +bool JT_ServInfo::take(const QDomElement &e) +{ + if(e.tagName() != "iq" || e.attribute("type") != "get") + return false; + + QString ns = queryNS(e); + if(ns == "jabber:iq:version") { + QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:version"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "name", client()->clientName())); + query.appendChild(textTag(doc(), "version", client()->clientVersion())); + query.appendChild(textTag(doc(), "os", client()->OSName())); + send(iq); + return true; + } + //else if(ns == "jabber:iq:time") { + // QDomElement iq = createIQ("result", e.attribute("from"), e.attribute("id")); + // QDomElement query = doc()->createElement("query"); + // query.setAttribute("xmlns", "jabber:iq:time"); + // iq.appendChild(query); + // QDateTime local = QDateTime::currentDateTime(); + // QDateTime utc = local.addSecs(-getTZOffset() * 3600); + // QString str = getTZString(); + // query.appendChild(textTag("utc", TS2stamp(utc))); + // query.appendChild(textTag("tz", str)); + // query.appendChild(textTag("display", QString("%1 %2").arg(local.toString()).arg(str))); + // send(iq); + // return TRUE; + //} + else if(ns == "http://jabber.org/protocol/disco#info") { + // Find out the node + QString node; + bool found; + QDomElement q = findSubTag(e, "query", &found); + if(found) // NOTE: Should always be true, since a NS was found above + node = q.attribute("node"); + + QDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id")); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/disco#info"); + if (!node.isEmpty()) + query.setAttribute("node", node); + iq.appendChild(query); + + // Identity + DiscoItem::Identity identity = client()->identity(); + QDomElement id = doc()->createElement("identity"); + if (!identity.category.isEmpty() && !identity.type.isEmpty()) { + id.setAttribute("category",identity.category); + id.setAttribute("type",identity.type); + if (!identity.name.isEmpty()) { + id.setAttribute("name",identity.name); + } + } + else { + // Default values + id.setAttribute("category","client"); + id.setAttribute("type","pc"); + } + query.appendChild(id); + + QDomElement feature; + if (node.isEmpty() || node == client()->capsNode() + "#" + client()->capsVersion()) { + // Standard features + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/bytestreams"); + query.appendChild(feature); + + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/si"); + query.appendChild(feature); + + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/si/profile/file-transfer"); + query.appendChild(feature); + + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/xhtml-im"); + query.appendChild(feature); + + feature = doc()->createElement("feature"); + feature.setAttribute("var", "http://jabber.org/protocol/disco#info"); + query.appendChild(feature); + + if (node.isEmpty()) { + // Extended features + QStringList exts = client()->extensions(); + for (QStringList::ConstIterator i = exts.begin(); i != exts.end(); ++i) { + const QStringList& l = client()->extension(*i).list(); + for ( QStringList::ConstIterator j = l.begin(); j != l.end(); ++j ) { + feature = doc()->createElement("feature"); + feature.setAttribute("var", *j); + query.appendChild(feature); + } + } + } + } + else if (node.startsWith(client()->capsNode() + "#")) { + QString ext = node.right(node.length()-client()->capsNode().length()-1); + if (client()->extensions().contains(ext)) { + const QStringList& l = client()->extension(ext).list(); + for ( QStringList::ConstIterator it = l.begin(); it != l.end(); ++it ) { + feature = doc()->createElement("feature"); + feature.setAttribute("var", *it); + query.appendChild(feature); + } + } + else { + // TODO: ERROR + } + } + else { + // TODO: ERROR + } + + send(iq); + return true; + } + + return false; +} + + +//---------------------------------------------------------------------------- +// JT_Gateway +//---------------------------------------------------------------------------- +JT_Gateway::JT_Gateway(Task *parent) +:Task(parent) +{ + type = -1; +} + +void JT_Gateway::get(const Jid &jid) +{ + type = 0; + v_jid = jid; + iq = createIQ(doc(), "get", v_jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:gateway"); + iq.appendChild(query); +} + +void JT_Gateway::set(const Jid &jid, const QString &prompt) +{ + type = 1; + v_jid = jid; + v_prompt = prompt; + iq = createIQ(doc(), "set", v_jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:gateway"); + iq.appendChild(query); + query.appendChild(textTag(doc(), "prompt", v_prompt)); +} + +void JT_Gateway::onGo() +{ + send(iq); +} + +Jid JT_Gateway::jid() const +{ + return v_jid; +} + +QString JT_Gateway::desc() const +{ + return v_desc; +} + +QString JT_Gateway::prompt() const +{ + return v_prompt; +} + +bool JT_Gateway::take(const QDomElement &x) +{ + if(!iqVerify(x, v_jid, id())) + return false; + + if(x.attribute("type") == "result") { + if(type == 0) { + QDomElement query = queryTag(x); + bool found; + QDomElement tag; + tag = findSubTag(query, "desc", &found); + if(found) + v_desc = tagContent(tag); + tag = findSubTag(query, "prompt", &found); + if(found) + v_prompt = tagContent(tag); + } + else { + QDomElement query = queryTag(x); + bool found; + QDomElement tag; + tag = findSubTag(query, "prompt", &found); + if(found) + v_prompt = tagContent(tag); + } + + setSuccess(); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_Browse +//---------------------------------------------------------------------------- +class JT_Browse::Private +{ +public: + QDomElement iq; + Jid jid; + AgentList agentList; + AgentItem root; +}; + +JT_Browse::JT_Browse (Task *parent) +:Task (parent) +{ + d = new Private; +} + +JT_Browse::~JT_Browse () +{ + delete d; +} + +void JT_Browse::get (const Jid &j) +{ + d->agentList.clear(); + + d->jid = j; + d->iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement query = doc()->createElement("item"); + query.setAttribute("xmlns", "jabber:iq:browse"); + d->iq.appendChild(query); +} + +const AgentList & JT_Browse::agents() const +{ + return d->agentList; +} + +const AgentItem & JT_Browse::root() const +{ + return d->root; +} + +void JT_Browse::onGo () +{ + send(d->iq); +} + +AgentItem JT_Browse::browseHelper (const QDomElement &i) +{ + AgentItem a; + + if ( i.tagName() == "ns" ) + return a; + + a.setName ( i.attribute("name") ); + a.setJid ( i.attribute("jid") ); + + // there are two types of category/type specification: + // + // 1. <item category="category_name" type="type_name" /> + // 2. <category_name type="type_name" /> + + if ( i.tagName() == "item" || i.tagName() == "query" ) + a.setCategory ( i.attribute("category") ); + else + a.setCategory ( i.tagName() ); + + a.setType ( i.attribute("type") ); + + QStringList ns; + for(QDomNode n = i.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if ( i.tagName() == "ns" ) + ns << i.text(); + } + + // For now, conference.jabber.org returns proper namespace only + // when browsing individual rooms. So it's a quick client-side fix. + if ( !a.features().canGroupchat() && a.category() == "conference" ) + ns << "jabber:iq:conference"; + + a.setFeatures (ns); + + return a; +} + +bool JT_Browse::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + if(x.attribute("type") == "result") { + for(QDomNode n = x.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + d->root = browseHelper (i); + + for(QDomNode nn = i.firstChild(); !nn.isNull(); nn = nn.nextSibling()) { + QDomElement e = nn.toElement(); + if ( e.isNull() ) + continue; + if ( e.tagName() == "ns" ) + continue; + + d->agentList += browseHelper (e); + } + } + + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_DiscoItems +//---------------------------------------------------------------------------- +class JT_DiscoItems::Private +{ +public: + Private() { } + + QDomElement iq; + Jid jid; + DiscoList items; +}; + +JT_DiscoItems::JT_DiscoItems(Task *parent) +: Task(parent) +{ + d = new Private; +} + +JT_DiscoItems::~JT_DiscoItems() +{ + delete d; +} + +void JT_DiscoItems::get(const DiscoItem &item) +{ + get(item.jid(), item.node()); +} + +void JT_DiscoItems::get (const Jid &j, const QString &node) +{ + d->items.clear(); + + d->jid = j; + d->iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/disco#items"); + + if ( !node.isEmpty() ) + query.setAttribute("node", node); + + d->iq.appendChild(query); +} + +const DiscoList &JT_DiscoItems::items() const +{ + return d->items; +} + +void JT_DiscoItems::onGo () +{ + send(d->iq); +} + +bool JT_DiscoItems::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement e = n.toElement(); + if( e.isNull() ) + continue; + + if ( e.tagName() == "item" ) { + DiscoItem item; + + item.setJid ( e.attribute("jid") ); + item.setName( e.attribute("name") ); + item.setNode( e.attribute("node") ); + item.setAction( DiscoItem::string2action(e.attribute("action")) ); + + d->items.append( item ); + } + } + + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_DiscoInfo +//---------------------------------------------------------------------------- +class JT_DiscoInfo::Private +{ +public: + Private() { } + + QDomElement iq; + Jid jid; + QString node; + DiscoItem item; +}; + +JT_DiscoInfo::JT_DiscoInfo(Task *parent) +: Task(parent) +{ + d = new Private; +} + +JT_DiscoInfo::~JT_DiscoInfo() +{ + delete d; +} + +void JT_DiscoInfo::get(const DiscoItem &item) +{ + DiscoItem::Identity id; + if ( item.identities().count() == 1 ) + id = item.identities().first(); + get(item.jid(), item.node(), id); +} + +void JT_DiscoInfo::get (const Jid &j, const QString &node, DiscoItem::Identity ident) +{ + d->item = DiscoItem(); // clear item + + d->jid = j; + d->node = node; + d->iq = createIQ(doc(), "get", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/disco#info"); + + if ( !node.isEmpty() ) + query.setAttribute("node", node); + + if ( !ident.category.isEmpty() && !ident.type.isEmpty() ) { + QDomElement i = doc()->createElement("item"); + + i.setAttribute("category", ident.category); + i.setAttribute("type", ident.type); + if ( !ident.name.isEmpty() ) + i.setAttribute("name", ident.name); + + query.appendChild( i ); + + } + + d->iq.appendChild(query); +} + + +/** + * Original requested jid. + * Is here because sometimes the responder does not include this information + * in the reply. + */ +const Jid& JT_DiscoInfo::jid() const +{ + return d->jid; +} + +/** + * Original requested node. + * Is here because sometimes the responder does not include this information + * in the reply. + */ +const QString& JT_DiscoInfo::node() const +{ + return d->node; +} + + + +const DiscoItem &JT_DiscoInfo::item() const +{ + return d->item; +} + +void JT_DiscoInfo::onGo () +{ + send(d->iq); +} + +bool JT_DiscoInfo::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + if(x.attribute("type") == "result") { + QDomElement q = queryTag(x); + + DiscoItem item; + + item.setJid( d->jid ); + item.setNode( q.attribute("node") ); + + QStringList features; + DiscoItem::Identities identities; + + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement e = n.toElement(); + if( e.isNull() ) + continue; + + if ( e.tagName() == "feature" ) { + features << e.attribute("var"); + } + else if ( e.tagName() == "identity" ) { + DiscoItem::Identity id; + + id.category = e.attribute("category"); + id.name = e.attribute("name"); + id.type = e.attribute("type"); + + identities.append( id ); + } + } + + item.setFeatures( features ); + item.setIdentities( identities ); + + d->item = item; + + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_DiscoPublish +//---------------------------------------------------------------------------- +class JT_DiscoPublish::Private +{ +public: + Private() { } + + QDomElement iq; + Jid jid; + DiscoList list; +}; + +JT_DiscoPublish::JT_DiscoPublish(Task *parent) +: Task(parent) +{ + d = new Private; +} + +JT_DiscoPublish::~JT_DiscoPublish() +{ + delete d; +} + +void JT_DiscoPublish::set(const Jid &j, const DiscoList &list) +{ + d->list = list; + d->jid = j; + + d->iq = createIQ(doc(), "set", d->jid.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "http://jabber.org/protocol/disco#items"); + + // FIXME: unsure about this + //if ( !node.isEmpty() ) + // query.setAttribute("node", node); + + DiscoList::ConstIterator it = list.begin(); + for ( ; it != list.end(); ++it) { + QDomElement w = doc()->createElement("item"); + + w.setAttribute("jid", (*it).jid().full()); + if ( !(*it).name().isEmpty() ) + w.setAttribute("name", (*it).name()); + if ( !(*it).node().isEmpty() ) + w.setAttribute("node", (*it).node()); + w.setAttribute("action", DiscoItem::action2string((*it).action())); + + query.appendChild( w ); + } + + d->iq.appendChild(query); +} + +void JT_DiscoPublish::onGo () +{ + send(d->iq); +} + +bool JT_DiscoPublish::take(const QDomElement &x) +{ + if(!iqVerify(x, d->jid, id())) + return false; + + if(x.attribute("type") == "result") { + setSuccess(true); + } + else { + setError(x); + } + + return true; +} + +//---------------------------------------------------------------------------- +// JT_MucPresence +//---------------------------------------------------------------------------- +JT_MucPresence::JT_MucPresence(Task *parent) +:Task(parent) +{ + type = -1; +} + +JT_MucPresence::~JT_MucPresence() +{ +} + +void JT_MucPresence::pres(const Status &s) +{ + type = 0; + + tag = doc()->createElement("presence"); + if(!s.isAvailable()) { + tag.setAttribute("type", "unavailable"); + if(!s.status().isEmpty()) + tag.appendChild(textTag(doc(), "status", s.status())); + } + else { + if(s.isInvisible()) + tag.setAttribute("type", "invisible"); + + if(!s.show().isEmpty()) + tag.appendChild(textTag(doc(), "show", s.show())); + if(!s.status().isEmpty()) + tag.appendChild(textTag(doc(), "status", s.status())); + + tag.appendChild( textTag(doc(), "priority", QString("%1").arg(s.priority()) ) ); + + if(!s.keyID().isEmpty()) { + QDomElement x = textTag(doc(), "x", s.keyID()); + x.setAttribute("xmlns", "http://jabber.org/protocol/e2e"); + tag.appendChild(x); + } + if(!s.xsigned().isEmpty()) { + QDomElement x = textTag(doc(), "x", s.xsigned()); + x.setAttribute("xmlns", "jabber:x:signed"); + tag.appendChild(x); + } + + if(!s.capsNode().isEmpty() && !s.capsVersion().isEmpty()) { + QDomElement c = doc()->createElement("c"); + c.setAttribute("xmlns","http://jabber.org/protocol/caps"); + c.setAttribute("node",s.capsNode()); + c.setAttribute("ver",s.capsVersion()); + if (!s.capsExt().isEmpty()) + c.setAttribute("ext",s.capsExt()); + tag.appendChild(c); + } + } +} + +void JT_MucPresence::pres(const Jid &to, const Status &s, const QString &password) +{ + pres(s); + tag.setAttribute("to", to.full()); + QDomElement x = textTag(doc(), "x", s.xsigned()); + x.setAttribute("xmlns", "http://jabber.org/protocol/muc"); + x.appendChild( textTag(doc(), "password", password.latin1()) ); + tag.appendChild(x); +} + +void JT_MucPresence::onGo() +{ + send(tag); + setSuccess(); +} + + +//---------------------------------------------------------------------------- +// JT_PrivateStorage +//---------------------------------------------------------------------------- +class JT_PrivateStorage::Private +{ + public: + Private() : type(-1) {} + + QDomElement iq; + QDomElement elem; + int type; +}; + +JT_PrivateStorage::JT_PrivateStorage(Task *parent) + :Task(parent) +{ + d = new Private; +} + +JT_PrivateStorage::~JT_PrivateStorage() +{ + delete d; +} + +void JT_PrivateStorage::get(const QString& tag, const QString& xmlns) +{ + d->type = 0; + d->iq = createIQ(doc(), "get" , QString() , id() ); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:private"); + d->iq.appendChild(query); + QDomElement s = doc()->createElement(tag); + if(!xmlns.isEmpty()) + s.setAttribute("xmlns", xmlns); + query.appendChild(s); +} + +void JT_PrivateStorage::set(const QDomElement& element) +{ + d->type = 1; + d->elem=element; + QDomNode n=doc()->importNode(element,true); + + d->iq = createIQ(doc(), "set" , QString() , id() ); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:private"); + d->iq.appendChild(query); + query.appendChild(n); +} + +void JT_PrivateStorage::onGo() +{ + send(d->iq); +} + +bool JT_PrivateStorage::take(const QDomElement &x) +{ + QString to = client()->host(); + if(!iqVerify(x, to, id())) + return false; + + if(x.attribute("type") == "result") { + if(d->type == 0) { + QDomElement q = queryTag(x); + for(QDomNode n = q.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + d->elem=i; + break; + } + } + setSuccess(); + return true; + } + else { + setError(x); + } + + return true; +} + + +QDomElement JT_PrivateStorage::element( ) +{ + return d->elem; +} + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.h b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.h new file mode 100644 index 00000000..ceb1e294 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_tasks.h @@ -0,0 +1,485 @@ +/* + * tasks.h - basic tasks + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef JABBER_TASKS_H +#define JABBER_TASKS_H + +#include<qstring.h> +#include<qdom.h> + +#include"im.h" +#include"xmpp_vcard.h" + +namespace XMPP +{ + class Roster; + class Status; + + class JT_Register : public Task + { + Q_OBJECT + public: + JT_Register(Task *parent); + ~JT_Register(); + + void reg(const QString &user, const QString &pass); + void changepw(const QString &pass); + void unreg(const Jid &j=""); + + const Form & form() const; + void getForm(const Jid &); + void setForm(const Form &); + + void onGo(); + bool take(const QDomElement &); + + private: + QDomElement iq; + Jid to; + + class Private; + Private *d; + }; + + class JT_UnRegister : public Task + { + Q_OBJECT + public: + JT_UnRegister(Task *parent); + ~JT_UnRegister(); + + void unreg(const Jid &); + + void onGo(); + + private slots: + void getFormFinished(); + void unregFinished(); + + private: + class Private; + Private *d; + }; + + class JT_Roster : public Task + { + Q_OBJECT + public: + JT_Roster(Task *parent); + ~JT_Roster(); + + void get(); + void set(const Jid &, const QString &name, const QStringList &groups); + void remove(const Jid &); + + const Roster & roster() const; + + QString toString() const; + bool fromString(const QString &); + + void onGo(); + bool take(const QDomElement &x); + + private: + int type; + QDomElement iq; + Jid to; + + class Private; + Private *d; + }; + + class JT_PushRoster : public Task + { + Q_OBJECT + public: + JT_PushRoster(Task *parent); + ~JT_PushRoster(); + + bool take(const QDomElement &); + + signals: + void roster(const Roster &); + + private: + class Private; + Private *d; + }; + + class JT_Presence : public Task + { + Q_OBJECT + public: + JT_Presence(Task *parent); + ~JT_Presence(); + + void pres(const Status &); + void pres(const Jid &, const Status &); + void sub(const Jid &, const QString &subType); + + void onGo(); + + private: + QDomElement tag; + int type; + + class Private; + Private *d; + }; + + class JT_PushPresence : public Task + { + Q_OBJECT + public: + JT_PushPresence(Task *parent); + ~JT_PushPresence(); + + bool take(const QDomElement &); + + signals: + void presence(const Jid &, const Status &); + void subscription(const Jid &, const QString &); + + private: + class Private; + Private *d; + }; + + class JT_Message : public Task + { + Q_OBJECT + public: + JT_Message(Task *parent, const Message &); + ~JT_Message(); + + void onGo(); + + private: + Message m; + + class Private; + Private *d; + }; + + class JT_PushMessage : public Task + { + Q_OBJECT + public: + JT_PushMessage(Task *parent); + ~JT_PushMessage(); + + bool take(const QDomElement &); + + signals: + void message(const Message &); + + private: + class Private; + Private *d; + }; + + class JT_GetLastActivity : public Task + { + Q_OBJECT + public: + JT_GetLastActivity(Task *); + ~JT_GetLastActivity(); + + void get(const Jid &); + + int seconds() const; + const QString &message() const; + + void onGo(); + bool take(const QDomElement &x); + + private: + class Private; + Private *d; + + QDomElement iq; + Jid jid; + }; + + class JT_GetServices : public Task + { + Q_OBJECT + public: + JT_GetServices(Task *); + + void get(const Jid &); + + const AgentList & agents() const; + + void onGo(); + bool take(const QDomElement &x); + + private: + class Private; + Private *d; + + QDomElement iq; + Jid jid; + AgentList agentList; + }; + + class JT_VCard : public Task + { + Q_OBJECT + public: + JT_VCard(Task *parent); + ~JT_VCard(); + + void get(const Jid &); + void set(const VCard &); + + const Jid & jid() const; + const VCard & vcard() const; + + void onGo(); + bool take(const QDomElement &x); + + private: + int type; + + class Private; + Private *d; + }; + + class JT_Search : public Task + { + Q_OBJECT + public: + JT_Search(Task *parent); + ~JT_Search(); + + const Form & form() const; + const QValueList<SearchResult> & results() const; + + void get(const Jid &); + void set(const Form &); + + void onGo(); + bool take(const QDomElement &x); + + private: + QDomElement iq; + int type; + + class Private; + Private *d; + }; + + class JT_ClientVersion : public Task + { + Q_OBJECT + public: + JT_ClientVersion(Task *); + + void get(const Jid &); + void onGo(); + bool take(const QDomElement &); + + const Jid & jid() const; + const QString & name() const; + const QString & version() const; + const QString & os() const; + + private: + QDomElement iq; + + Jid j; + QString v_name, v_ver, v_os; + }; +/* + class JT_ClientTime : public Task + { + Q_OBJECT + public: + JT_ClientTime(Task *, const Jid &); + + void go(); + bool take(const QDomElement &); + + Jid j; + QDateTime utc; + QString timezone, display; + + private: + QDomElement iq; + }; +*/ + class JT_ServInfo : public Task + { + Q_OBJECT + public: + JT_ServInfo(Task *); + ~JT_ServInfo(); + + bool take(const QDomElement &); + }; + + class JT_Gateway : public Task + { + Q_OBJECT + public: + JT_Gateway(Task *); + + void get(const Jid &); + void set(const Jid &, const QString &prompt); + void onGo(); + bool take(const QDomElement &); + + Jid jid() const; + QString desc() const; + QString prompt() const; + + private: + QDomElement iq; + + int type; + Jid v_jid; + QString v_prompt, v_desc; + }; + + class JT_Browse : public Task + { + Q_OBJECT + public: + JT_Browse(Task *); + ~JT_Browse(); + + void get(const Jid &); + + const AgentList & agents() const; + const AgentItem & root() const; + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + + AgentItem browseHelper (const QDomElement &i); + }; + + class JT_DiscoItems : public Task + { + Q_OBJECT + public: + JT_DiscoItems(Task *); + ~JT_DiscoItems(); + + void get(const Jid &, const QString &node = QString::null); + void get(const DiscoItem &); + + const DiscoList &items() const; + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + + class JT_DiscoInfo : public Task + { + Q_OBJECT + public: + JT_DiscoInfo(Task *); + ~JT_DiscoInfo(); + + void get(const Jid &, const QString &node = QString::null, const DiscoItem::Identity = DiscoItem::Identity()); + void get(const DiscoItem &); + + const DiscoItem &item() const; + const Jid& jid() const; + const QString& node() const; + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + + class JT_DiscoPublish : public Task + { + Q_OBJECT + public: + JT_DiscoPublish(Task *); + ~JT_DiscoPublish(); + + void set(const Jid &, const DiscoList &); + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + + class JT_MucPresence : public Task + { + Q_OBJECT + public: + JT_MucPresence(Task *parent); + ~JT_MucPresence(); + + void pres(const Status &); + void pres(const Jid &, const Status &, const QString &password); + + void onGo(); + + private: + QDomElement tag; + int type; + + class Private; + Private *d; + }; + + class JT_PrivateStorage : public Task + { + Q_OBJECT + public: + JT_PrivateStorage(Task *parent); + ~JT_PrivateStorage(); + + void set(const QDomElement &); + void get(const QString &tag, const QString& xmlns); + + QDomElement element(); + + void onGo(); + bool take(const QDomElement &); + + private: + class Private; + Private *d; + }; + +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.cpp new file mode 100644 index 00000000..296c53c6 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.cpp @@ -0,0 +1,1241 @@ +/* + * xmpp_vcard.cpp - classes for handling vCards + * Copyright (C) 2003 Michail Pishchagin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "xmpp_vcard.h" + +#include "base64.h" + +#include <qdom.h> +#include <qdatetime.h> + +#include <qimage.h> // needed for image format recognition +#include <qbuffer.h> + +// Justin's XML helper functions + +static QDomElement textTag(QDomDocument *doc, const QString &name, const QString &content) +{ + QDomElement tag = doc->createElement(name); + QDomText text = doc->createTextNode(content); + tag.appendChild(text); + + return tag; +} + +static QDomElement findSubTag(const QDomElement &e, const QString &name, bool *found) +{ + if(found) + *found = FALSE; + + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + if(i.tagName().upper() == name.upper()) { // mblsha: ignore case when searching + if(found) + *found = TRUE; + return i; + } + } + + QDomElement tmp; + return tmp; +} + +// mblsha's own functions + +static QDomElement emptyTag(QDomDocument *doc, const QString &name) +{ + QDomElement tag = doc->createElement(name); + + return tag; +} + +static bool hasSubTag(const QDomElement &e, const QString &name) +{ + bool found; + findSubTag(e, name, &found); + return found; +} + +static QString subTagText(const QDomElement &e, const QString &name) +{ + bool found; + QDomElement i = findSubTag(e, name, &found); + if ( found ) + return i.text().stripWhiteSpace(); + return QString::null; +} + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// VCard +//---------------------------------------------------------------------------- +static QString image2type(const QByteArray &ba) +{ + QBuffer buf(ba); + buf.open(IO_ReadOnly); + QString format = QImageIO::imageFormat( &buf ); + + // TODO: add more formats + if ( format == "PNG" || format == "PsiPNG" ) + return "image/png"; + if ( format == "MNG" ) + return "video/x-mng"; + if ( format == "GIF" ) + return "image/gif"; + if ( format == "BMP" ) + return "image/bmp"; + if ( format == "XPM" ) + return "image/x-xpm"; + if ( format == "SVG" ) + return "image/svg+xml"; + if ( format == "JPEG" ) + return "image/jpeg"; + + qWarning("WARNING! VCard::image2type: unknown format = '%s'", format.latin1()); + + return "image/unknown"; +} + +// Long lines of encoded binary data SHOULD BE folded to 75 characters using the folding method defined in [MIME-DIR]. +static QString foldString(const QString &s) +{ + QString ret; + + for (int i = 0; i < (int)s.length(); i++) { + if ( !(i % 75) ) + ret += '\n'; + ret += s[i]; + } + + return ret; +} + +class VCard::Private +{ +public: + Private(); + ~Private(); + + QString version; + QString fullName; + QString familyName, givenName, middleName, prefixName, suffixName; + QString nickName; + + QByteArray photo; + QString photoURI; + + QString bday; + AddressList addressList; + LabelList labelList; + PhoneList phoneList; + EmailList emailList; + QString jid; + QString mailer; + QString timezone; + Geo geo; + QString title; + QString role; + + QByteArray logo; + QString logoURI; + + VCard *agent; + QString agentURI; + + Org org; + QStringList categories; + QString note; + QString prodId; + QString rev; + QString sortString; + + QByteArray sound; + QString soundURI, soundPhonetic; + + QString uid; + QString url; + QString desc; + PrivacyClass privacyClass; + QByteArray key; + + bool isEmpty(); +}; + +VCard::Private::Private() +{ + privacyClass = pcNone; + agent = 0; +} + +VCard::Private::~Private() +{ + delete agent; +} + +bool VCard::Private::isEmpty() +{ + if ( !version.isEmpty() || + !fullName.isEmpty() || + !familyName.isEmpty() || !givenName.isEmpty() || !middleName.isEmpty() || !prefixName.isEmpty() || !suffixName.isEmpty() || + !nickName.isEmpty() || + !photo.isEmpty() || !photoURI.isEmpty() || + !bday.isEmpty() || + !addressList.isEmpty() || + !labelList.isEmpty() || + !phoneList.isEmpty() || + !emailList.isEmpty() || + !jid.isEmpty() || + !mailer.isEmpty() || + !timezone.isEmpty() || + !geo.lat.isEmpty() || !geo.lon.isEmpty() || + !title.isEmpty() || + !role.isEmpty() || + !logo.isEmpty() || !logoURI.isEmpty() || + (agent && !agent->isEmpty()) || !agentURI.isEmpty() || + !org.name.isEmpty() || !org.unit.isEmpty() || + !categories.isEmpty() || + !note.isEmpty() || + !prodId.isEmpty() || + !rev.isEmpty() || + !sortString.isEmpty() || + !sound.isEmpty() || !soundURI.isEmpty() || !soundPhonetic.isEmpty() || + !uid.isEmpty() || + !url.isEmpty() || + !desc.isEmpty() || + (privacyClass != pcNone) || + !key.isEmpty() ) + { + return false; + } + return true; +} + +VCard::VCard() +{ + d = new Private; +} + +VCard::VCard(const VCard &from) +{ + d = new Private; + *this = from; +} + +VCard & VCard::operator=(const VCard &from) +{ + if(d->agent) { + delete d->agent; + d->agent = 0; + } + + *d = *from.d; + + if(from.d->agent) { + // dup the agent + d->agent = new VCard(*from.d->agent); + } + + return *this; +} + +VCard::~VCard() +{ + delete d; +} + +QDomElement VCard::toXml(QDomDocument *doc) const +{ + QDomElement v = doc->createElement("vCard"); + v.setAttribute("version", "2.0"); + v.setAttribute("prodid", "-//HandGen//NONSGML vGen v1.0//EN"); + v.setAttribute("xmlns", "vcard-temp"); + + if ( !d->version.isEmpty() ) + v.appendChild( textTag(doc, "VERSION", d->version) ); + if ( !d->fullName.isEmpty() ) + v.appendChild( textTag(doc, "FN", d->fullName) ); + + if ( !d->familyName.isEmpty() || !d->givenName.isEmpty() || !d->middleName.isEmpty() || + !d->prefixName.isEmpty() || !d->suffixName.isEmpty() ) { + QDomElement w = doc->createElement("N"); + + if ( !d->familyName.isEmpty() ) + w.appendChild( textTag(doc, "FAMILY", d->familyName) ); + if ( !d->givenName.isEmpty() ) + w.appendChild( textTag(doc, "GIVEN", d->givenName) ); + if ( !d->middleName.isEmpty() ) + w.appendChild( textTag(doc, "MIDDLE", d->middleName) ); + if ( !d->prefixName.isEmpty() ) + w.appendChild( textTag(doc, "PREFIX", d->prefixName) ); + if ( !d->suffixName.isEmpty() ) + w.appendChild( textTag(doc, "SUFFIX", d->suffixName) ); + + v.appendChild(w); + } + + if ( !d->nickName.isEmpty() ) + v.appendChild( textTag(doc, "NICKNAME", d->nickName) ); + + if ( !d->photo.isEmpty() || !d->photoURI.isEmpty() ) { + QDomElement w = doc->createElement("PHOTO"); + + if ( !d->photo.isEmpty() ) { + w.appendChild( textTag(doc, "TYPE", image2type(d->photo)) ); + w.appendChild( textTag(doc, "BINVAL", foldString( Base64::arrayToString(d->photo)) ) ); + } + else if ( !d->photoURI.isEmpty() ) + w.appendChild( textTag(doc, "EXTVAL", d->photoURI) ); + + v.appendChild(w); + } + + if ( !d->bday.isEmpty() ) + v.appendChild( textTag(doc, "BDAY", d->bday) ); + + if ( !d->addressList.isEmpty() ) { + AddressList::Iterator it = d->addressList.begin(); + for ( ; it != d->addressList.end(); ++it ) { + QDomElement w = doc->createElement("ADR"); + Address a = *it; + + if ( a.home ) + w.appendChild( emptyTag(doc, "HOME") ); + if ( a.work ) + w.appendChild( emptyTag(doc, "WORK") ); + if ( a.postal ) + w.appendChild( emptyTag(doc, "POSTAL") ); + if ( a.parcel ) + w.appendChild( emptyTag(doc, "PARCEL") ); + if ( a.dom ) + w.appendChild( emptyTag(doc, "DOM") ); + if ( a.intl ) + w.appendChild( emptyTag(doc, "INTL") ); + if ( a.pref ) + w.appendChild( emptyTag(doc, "PREF") ); + + if ( !a.pobox.isEmpty() ) + w.appendChild( textTag(doc, "POBOX", a.pobox) ); + if ( !a.extaddr.isEmpty() ) + w.appendChild( textTag(doc, "EXTADR", a.extaddr) ); + if ( !a.street.isEmpty() ) + w.appendChild( textTag(doc, "STREET", a.street) ); + if ( !a.locality.isEmpty() ) + w.appendChild( textTag(doc, "LOCALITY", a.locality) ); + if ( !a.region.isEmpty() ) + w.appendChild( textTag(doc, "REGION", a.region) ); + if ( !a.pcode.isEmpty() ) + w.appendChild( textTag(doc, "PCODE", a.pcode) ); + if ( !a.country.isEmpty() ) + w.appendChild( textTag(doc, "CTRY", a.country) ); + + v.appendChild(w); + } + } + + if ( !d->labelList.isEmpty() ) { + LabelList::Iterator it = d->labelList.begin(); + for ( ; it != d->labelList.end(); ++it ) { + QDomElement w = doc->createElement("LABEL"); + Label l = *it; + + if ( l.home ) + w.appendChild( emptyTag(doc, "HOME") ); + if ( l.work ) + w.appendChild( emptyTag(doc, "WORK") ); + if ( l.postal ) + w.appendChild( emptyTag(doc, "POSTAL") ); + if ( l.parcel ) + w.appendChild( emptyTag(doc, "PARCEL") ); + if ( l.dom ) + w.appendChild( emptyTag(doc, "DOM") ); + if ( l.intl ) + w.appendChild( emptyTag(doc, "INTL") ); + if ( l.pref ) + w.appendChild( emptyTag(doc, "PREF") ); + + if ( !l.lines.isEmpty() ) { + QStringList::Iterator it = l.lines.begin(); + for ( ; it != l.lines.end(); ++it ) + w.appendChild( textTag(doc, "LINE", *it) ); + } + + v.appendChild(w); + } + } + + if ( !d->phoneList.isEmpty() ) { + PhoneList::Iterator it = d->phoneList.begin(); + for ( ; it != d->phoneList.end(); ++it ) { + QDomElement w = doc->createElement("TEL"); + Phone p = *it; + + if ( p.home ) + w.appendChild( emptyTag(doc, "HOME") ); + if ( p.work ) + w.appendChild( emptyTag(doc, "WORK") ); + if ( p.voice ) + w.appendChild( emptyTag(doc, "VOICE") ); + if ( p.fax ) + w.appendChild( emptyTag(doc, "FAX") ); + if ( p.pager ) + w.appendChild( emptyTag(doc, "PAGER") ); + if ( p.msg ) + w.appendChild( emptyTag(doc, "MSG") ); + if ( p.cell ) + w.appendChild( emptyTag(doc, "CELL") ); + if ( p.video ) + w.appendChild( emptyTag(doc, "VIDEO") ); + if ( p.bbs ) + w.appendChild( emptyTag(doc, "BBS") ); + if ( p.modem ) + w.appendChild( emptyTag(doc, "MODEM") ); + if ( p.isdn ) + w.appendChild( emptyTag(doc, "ISDN") ); + if ( p.pcs ) + w.appendChild( emptyTag(doc, "PCS") ); + if ( p.pref ) + w.appendChild( emptyTag(doc, "PREF") ); + + if ( !p.number.isEmpty() ) + w.appendChild( textTag(doc, "NUMBER", p.number) ); + + v.appendChild(w); + } + } + + if ( !d->emailList.isEmpty() ) { + EmailList::Iterator it = d->emailList.begin(); + for ( ; it != d->emailList.end(); ++it ) { + QDomElement w = doc->createElement("EMAIL"); + Email e = *it; + + if ( e.home ) + w.appendChild( emptyTag(doc, "HOME") ); + if ( e.work ) + w.appendChild( emptyTag(doc, "WORK") ); + if ( e.internet ) + w.appendChild( emptyTag(doc, "INTERNET") ); + if ( e.x400 ) + w.appendChild( emptyTag(doc, "X400") ); + + if ( !e.userid.isEmpty() ) + w.appendChild( textTag(doc, "USERID", e.userid) ); + + v.appendChild(w); + } + } + + if ( !d->jid.isEmpty() ) + v.appendChild( textTag(doc, "JABBERID", d->jid) ); + if ( !d->mailer.isEmpty() ) + v.appendChild( textTag(doc, "MAILER", d->mailer) ); + if ( !d->timezone.isEmpty() ) + v.appendChild( textTag(doc, "TZ", d->timezone) ); + + if ( !d->geo.lat.isEmpty() || !d->geo.lon.isEmpty() ) { + QDomElement w = doc->createElement("GEO"); + + if ( !d->geo.lat.isEmpty() ) + w.appendChild( textTag(doc, "LAT", d->geo.lat) ); + if ( !d->geo.lon.isEmpty() ) + w.appendChild( textTag(doc, "LON", d->geo.lon)); + + v.appendChild(w); + } + + if ( !d->title.isEmpty() ) + v.appendChild( textTag(doc, "TITLE", d->title) ); + if ( !d->role.isEmpty() ) + v.appendChild( textTag(doc, "ROLE", d->role) ); + + if ( !d->logo.isEmpty() || !d->logoURI.isEmpty() ) { + QDomElement w = doc->createElement("LOGO"); + + if ( !d->logo.isEmpty() ) { + w.appendChild( textTag(doc, "TYPE", image2type(d->logo)) ); + w.appendChild( textTag(doc, "BINVAL", foldString( Base64::arrayToString(d->logo)) ) ); + } + else if ( !d->logoURI.isEmpty() ) + w.appendChild( textTag(doc, "EXTVAL", d->logoURI) ); + + v.appendChild(w); + } + + if ( !d->agentURI.isEmpty() || (d->agent && d->agent->isEmpty()) ) { + QDomElement w = doc->createElement("AGENT"); + + if ( d->agent && !d->agent->isEmpty() ) + w.appendChild( d->agent->toXml(doc) ); + else if ( !d->agentURI.isEmpty() ) + w.appendChild( textTag(doc, "EXTVAL", d->agentURI) ); + + v.appendChild(w); + } + + if ( !d->org.name.isEmpty() || !d->org.unit.isEmpty() ) { + QDomElement w = doc->createElement("ORG"); + + if ( !d->org.name.isEmpty() ) + w.appendChild( textTag(doc, "ORGNAME", d->org.name) ); + + if ( !d->org.unit.isEmpty() ) { + QStringList::Iterator it = d->org.unit.begin(); + for ( ; it != d->org.unit.end(); ++it ) + w.appendChild( textTag(doc, "ORGUNIT", *it) ); + } + + v.appendChild(w); + } + + if ( !d->categories.isEmpty() ) { + QDomElement w = doc->createElement("CATEGORIES"); + + QStringList::Iterator it = d->categories.begin(); + for ( ; it != d->categories.end(); ++it ) + w.appendChild( textTag(doc, "KEYWORD", *it) ); + + v.appendChild(w); + } + + if ( !d->note.isEmpty() ) + v.appendChild( textTag(doc, "NOTE", d->note) ); + if ( !d->prodId.isEmpty() ) + v.appendChild( textTag(doc, "PRODID", d->prodId) ); + if ( !d->rev.isEmpty() ) + v.appendChild( textTag(doc, "REV", d->rev) ); + if ( !d->sortString.isEmpty() ) + v.appendChild( textTag(doc, "SORT-STRING", d->sortString) ); + + if ( !d->sound.isEmpty() || !d->soundURI.isEmpty() || !d->soundPhonetic.isEmpty() ) { + QDomElement w = doc->createElement("SOUND"); + + if ( !d->sound.isEmpty() ) + w.appendChild( textTag(doc, "BINVAL", foldString( Base64::arrayToString(d->sound)) ) ); + else if ( !d->soundURI.isEmpty() ) + w.appendChild( textTag(doc, "EXTVAL", d->soundURI) ); + else if ( !d->soundPhonetic.isEmpty() ) + w.appendChild( textTag(doc, "PHONETIC", d->soundPhonetic) ); + + v.appendChild(w); + } + + if ( !d->uid.isEmpty() ) + v.appendChild( textTag(doc, "UID", d->uid) ); + if ( !d->url.isEmpty() ) + v.appendChild( textTag(doc, "URL", d->url) ); + if ( !d->desc.isEmpty() ) + v.appendChild( textTag(doc, "DESC", d->desc) ); + + if ( d->privacyClass != pcNone ) { + QDomElement w = doc->createElement("CLASS"); + + if ( d->privacyClass == pcPublic ) + w.appendChild( emptyTag(doc, "PUBLIC") ); + else if ( d->privacyClass == pcPrivate ) + w.appendChild( emptyTag(doc, "PRIVATE") ); + else if ( d->privacyClass == pcConfidential ) + w.appendChild( emptyTag(doc, "CONFIDENTIAL") ); + + v.appendChild(w); + } + + if ( !d->key.isEmpty() ) { + QDomElement w = doc->createElement("KEY"); + + // TODO: Justin, please check out this code + w.appendChild( textTag(doc, "TYPE", "text/plain")); // FIXME + w.appendChild( textTag(doc, "CRED", QString::fromUtf8(d->key)) ); // FIXME + + v.appendChild(w); + } + + return v; +} + +bool VCard::fromXml(const QDomElement &q) +{ + if ( q.tagName().upper() != "VCARD" ) + return false; + + QDomNode n = q.firstChild(); + for ( ; !n.isNull(); n = n.nextSibling() ) { + QDomElement i = n.toElement(); + if ( i.isNull() ) + continue; + + QString tag = i.tagName().upper(); + + bool found; + QDomElement e; + + if ( tag == "VERSION" ) + d->version = i.text().stripWhiteSpace(); + else if ( tag == "FN" ) + d->fullName = i.text().stripWhiteSpace(); + else if ( tag == "N" ) { + d->familyName = subTagText(i, "FAMILY"); + d->givenName = subTagText(i, "GIVEN"); + d->middleName = subTagText(i, "MIDDLE"); + d->prefixName = subTagText(i, "PREFIX"); + d->suffixName = subTagText(i, "SUFFIX"); + } + else if ( tag == "NICKNAME" ) + d->nickName = i.text().stripWhiteSpace(); + else if ( tag == "PHOTO" ) { + d->photo = Base64::stringToArray( subTagText(i, "BINVAL") ); + d->photoURI = subTagText(i, "EXTVAL"); + } + else if ( tag == "BDAY" ) + d->bday = i.text().stripWhiteSpace(); + else if ( tag == "ADR" ) { + Address a; + + a.home = hasSubTag(i, "HOME"); + a.work = hasSubTag(i, "WORK"); + a.postal = hasSubTag(i, "POSTAL"); + a.parcel = hasSubTag(i, "PARCEL"); + a.dom = hasSubTag(i, "DOM"); + a.intl = hasSubTag(i, "INTL"); + a.pref = hasSubTag(i, "PREF"); + + a.pobox = subTagText(i, "POBOX"); + a.extaddr = subTagText(i, "EXTADR"); + a.street = subTagText(i, "STREET"); + a.locality = subTagText(i, "LOCALITY"); + a.region = subTagText(i, "REGION"); + a.pcode = subTagText(i, "PCODE"); + a.country = subTagText(i, "CTRY"); + + if ( a.country.isEmpty() ) // FIXME: Workaround for Psi prior to 0.9 + if ( hasSubTag(i, "COUNTRY") ) + a.country = subTagText(i, "COUNTRY"); + + if ( a.extaddr.isEmpty() ) // FIXME: Workaround for Psi prior to 0.9 + if ( hasSubTag(i, "EXTADD") ) + a.extaddr = subTagText(i, "EXTADD"); + + d->addressList.append ( a ); + } + else if ( tag == "LABEL" ) { + Label l; + + l.home = hasSubTag(i, "HOME"); + l.work = hasSubTag(i, "WORK"); + l.postal = hasSubTag(i, "POSTAL"); + l.parcel = hasSubTag(i, "PARCEL"); + l.dom = hasSubTag(i, "DOM"); + l.intl = hasSubTag(i, "INTL"); + l.pref = hasSubTag(i, "PREF"); + + QDomNode nn = i.firstChild(); + for ( ; !nn.isNull(); nn = nn.nextSibling() ) { + QDomElement ii = nn.toElement(); + if ( ii.isNull() ) + continue; + + if ( ii.tagName().upper() == "LINE" ) + l.lines.append ( ii.text().stripWhiteSpace() ); + } + + d->labelList.append ( l ); + } + else if ( tag == "TEL" ) { + Phone p; + + p.home = hasSubTag(i, "HOME"); + p.work = hasSubTag(i, "WORK"); + p.voice = hasSubTag(i, "VOICE"); + p.fax = hasSubTag(i, "FAX"); + p.pager = hasSubTag(i, "PAGER"); + p.msg = hasSubTag(i, "MSG"); + p.cell = hasSubTag(i, "CELL"); + p.video = hasSubTag(i, "VIDEO"); + p.bbs = hasSubTag(i, "BBS"); + p.modem = hasSubTag(i, "MODEM"); + p.isdn = hasSubTag(i, "ISDN"); + p.pcs = hasSubTag(i, "PCS"); + p.pref = hasSubTag(i, "PREF"); + + p.number = subTagText(i, "NUMBER"); + + if ( p.number.isEmpty() ) // FIXME: Workaround for Psi prior to 0.9 + if ( hasSubTag(i, "VOICE") ) + p.number = subTagText(i, "VOICE"); + + d->phoneList.append ( p ); + } + else if ( tag == "EMAIL" ) { + Email m; + + m.home = hasSubTag(i, "HOME"); + m.work = hasSubTag(i, "WORK"); + m.internet = hasSubTag(i, "INTERNET"); + m.x400 = hasSubTag(i, "X400"); + + m.userid = subTagText(i, "USERID"); + + if ( m.userid.isEmpty() ) // FIXME: Workaround for Psi prior to 0.9 + if ( !i.text().isEmpty() ) + m.userid = i.text().stripWhiteSpace(); + + d->emailList.append ( m ); + } + else if ( tag == "JABBERID" ) + d->jid = i.text().stripWhiteSpace(); + else if ( tag == "MAILER" ) + d->mailer = i.text().stripWhiteSpace(); + else if ( tag == "TZ" ) + d->timezone = i.text().stripWhiteSpace(); + else if ( tag == "GEO" ) { + d->geo.lat = subTagText(i, "LAT"); + d->geo.lon = subTagText(i, "LON"); + } + else if ( tag == "TITLE" ) + d->title = i.text().stripWhiteSpace(); + else if ( tag == "ROLE" ) + d->role = i.text().stripWhiteSpace(); + else if ( tag == "LOGO" ) { + d->logo = Base64::stringToArray( subTagText(i, "BINVAL") ); + d->logoURI = subTagText(i, "EXTVAL"); + } + else if ( tag == "AGENT" ) { + e = findSubTag(i, "VCARD", &found); + if ( found ) { + VCard a; + if ( a.fromXml(e) ) { + if ( !d->agent ) + d->agent = new VCard; + *(d->agent) = a; + } + } + + d->agentURI = subTagText(i, "EXTVAL"); + } + else if ( tag == "ORG" ) { + d->org.name = subTagText(i, "ORGNAME"); + + QDomNode nn = i.firstChild(); + for ( ; !nn.isNull(); nn = nn.nextSibling() ) { + QDomElement ii = nn.toElement(); + if ( ii.isNull() ) + continue; + + if ( ii.tagName().upper() == "ORGUNIT" ) + d->org.unit.append( ii.text().stripWhiteSpace() ); + } + } + else if ( tag == "CATEGORIES") { + QDomNode nn = i.firstChild(); + for ( ; !nn.isNull(); nn = nn.nextSibling() ) { + QDomElement ee = nn.toElement(); + if ( ee.isNull() ) + continue; + + if ( ee.tagName().upper() == "KEYWORD" ) + d->categories << ee.text().stripWhiteSpace(); + } + } + else if ( tag == "NOTE" ) + d->note = i.text().stripWhiteSpace(); + else if ( tag == "PRODID" ) + d->prodId = i.text().stripWhiteSpace(); + else if ( tag == "REV" ) + d->rev = i.text().stripWhiteSpace(); + else if ( tag == "SORT-STRING" ) + d->sortString = i.text().stripWhiteSpace(); + else if ( tag == "SOUND" ) { + d->sound = Base64::stringToArray( subTagText(i, "BINVAL") ); + d->soundURI = subTagText(i, "EXTVAL"); + d->soundPhonetic = subTagText(i, "PHONETIC"); + } + else if ( tag == "UID" ) + d->uid = i.text().stripWhiteSpace(); + else if ( tag == "URL") + d->url = i.text().stripWhiteSpace(); + else if ( tag == "DESC" ) + d->desc = i.text().stripWhiteSpace(); + else if ( tag == "CLASS" ) { + if ( hasSubTag(i, "PUBLIC") ) + d->privacyClass = pcPublic; + else if ( hasSubTag(i, "PRIVATE") ) + d->privacyClass = pcPrivate; + else if ( hasSubTag(i, "CONFIDENTIAL") ) + d->privacyClass = pcConfidential; + } + else if ( tag == "KEY" ) { + // TODO: Justin, please check out this code + e = findSubTag(i, "TYPE", &found); + QString type = "text/plain"; + if ( found ) + type = e.text().stripWhiteSpace(); + + e = findSubTag(i, "CRED", &found ); + if ( !found ) + e = findSubTag(i, "BINVAL", &found); // case for very clever clients ;-) + + if ( found ) + d->key = e.text().utf8(); // FIXME + } + } + + return true; +} + +bool VCard::isEmpty() const +{ + return d->isEmpty(); +} + +// Some constructors + +VCard::Address::Address() +{ + home = work = postal = parcel = dom = intl = pref = false; +} + +VCard::Label::Label() +{ + home = work = postal = parcel = dom = intl = pref = false; +} + +VCard::Phone::Phone() +{ + home = work = voice = fax = pager = msg = cell = video = bbs = modem = isdn = pcs = pref = false; +} + +VCard::Email::Email() +{ + home = work = internet = x400 = false; +} + +VCard::Geo::Geo() +{ +} + +VCard::Org::Org() +{ +} + +// vCard properties... + +const QString &VCard::version() const +{ + return d->version; +} + +void VCard::setVersion(const QString &v) +{ + d->version = v; +} + +const QString &VCard::fullName() const +{ + return d->fullName; +} + +void VCard::setFullName(const QString &n) +{ + d->fullName = n; +} + +const QString &VCard::familyName() const +{ + return d->familyName; +} + +void VCard::setFamilyName(const QString &n) +{ + d->familyName = n; +} + +const QString &VCard::givenName() const +{ + return d->givenName; +} + +void VCard::setGivenName(const QString &n) +{ + d->givenName = n; +} + +const QString &VCard::middleName() const +{ + return d->middleName; +} + +void VCard::setMiddleName(const QString &n) +{ + d->middleName = n; +} + +const QString &VCard::prefixName() const +{ + return d->prefixName; +} + +void VCard::setPrefixName(const QString &p) +{ + d->prefixName = p; +} + +const QString &VCard::suffixName() const +{ + return d->suffixName; +} + +void VCard::setSuffixName(const QString &s) +{ + d->suffixName = s; +} + +const QString &VCard::nickName() const +{ + return d->nickName; +} + +void VCard::setNickName(const QString &n) +{ + d->nickName = n; +} + +const QByteArray &VCard::photo() const +{ + return d->photo; +} + +void VCard::setPhoto(const QByteArray &i) +{ + d->photo = i; +} + +const QString &VCard::photoURI() const +{ + return d->photoURI; +} + +void VCard::setPhotoURI(const QString &p) +{ + d->photoURI = p; +} + +const QDate VCard::bday() const +{ + return QDate::fromString(d->bday); +} + +void VCard::setBday(const QDate &date) +{ + d->bday = date.toString(); +} + +const QString &VCard::bdayStr() const +{ + return d->bday; +} + +void VCard::setBdayStr(const QString &date) +{ + d->bday = date; +} + +const VCard::AddressList &VCard::addressList() const +{ + return d->addressList; +} + +void VCard::setAddressList(const VCard::AddressList &a) +{ + d->addressList = a; +} + +const VCard::LabelList &VCard::labelList() const +{ + return d->labelList; +} + +void VCard::setLabelList(const VCard::LabelList &l) +{ + d->labelList = l; +} + +const VCard::PhoneList &VCard::phoneList() const +{ + return d->phoneList; +} + +void VCard::setPhoneList(const VCard::PhoneList &p) +{ + d->phoneList = p; +} + +const VCard::EmailList &VCard::emailList() const +{ + return d->emailList; +} + +void VCard::setEmailList(const VCard::EmailList &e) +{ + d->emailList = e; +} + +const QString &VCard::jid() const +{ + return d->jid; +} + +void VCard::setJid(const QString &j) +{ + d->jid = j; +} + +const QString &VCard::mailer() const +{ + return d->mailer; +} + +void VCard::setMailer(const QString &m) +{ + d->mailer = m; +} + +const QString &VCard::timezone() const +{ + return d->timezone; +} + +void VCard::setTimezone(const QString &t) +{ + d->timezone = t; +} + +const VCard::Geo &VCard::geo() const +{ + return d->geo; +} + +void VCard::setGeo(const VCard::Geo &g) +{ + d->geo = g; +} + +const QString &VCard::title() const +{ + return d->title; +} + +void VCard::setTitle(const QString &t) +{ + d->title = t; +} + +const QString &VCard::role() const +{ + return d->role; +} + +void VCard::setRole(const QString &r) +{ + d->role = r; +} + +const QByteArray &VCard::logo() const +{ + return d->logo; +} + +void VCard::setLogo(const QByteArray &i) +{ + d->logo = i; +} + +const QString &VCard::logoURI() const +{ + return d->logoURI; +} + +void VCard::setLogoURI(const QString &l) +{ + d->logoURI = l; +} + +const VCard *VCard::agent() const +{ + return d->agent; +} + +void VCard::setAgent(const VCard &v) +{ + if ( !d->agent ) + d->agent = new VCard; + *(d->agent) = v; +} + +const QString VCard::agentURI() const +{ + return d->agentURI; +} + +void VCard::setAgentURI(const QString &a) +{ + d->agentURI = a; +} + +const VCard::Org &VCard::org() const +{ + return d->org; +} + +void VCard::setOrg(const VCard::Org &o) +{ + d->org = o; +} + +const QStringList &VCard::categories() const +{ + return d->categories; +} + +void VCard::setCategories(const QStringList &c) +{ + d->categories = c; +} + +const QString &VCard::note() const +{ + return d->note; +} + +void VCard::setNote(const QString &n) +{ + d->note = n; +} + +const QString &VCard::prodId() const +{ + return d->prodId; +} + +void VCard::setProdId(const QString &p) +{ + d->prodId = p; +} + +const QString &VCard::rev() const +{ + return d->rev; +} + +void VCard::setRev(const QString &r) +{ + d->rev = r; +} + +const QString &VCard::sortString() const +{ + return d->sortString; +} + +void VCard::setSortString(const QString &s) +{ + d->sortString = s; +} + +const QByteArray &VCard::sound() const +{ + return d->sound; +} + +void VCard::setSound(const QByteArray &s) +{ + d->sound = s; +} + +const QString &VCard::soundURI() const +{ + return d->soundURI; +} + +void VCard::setSoundURI(const QString &s) +{ + d->soundURI = s; +} + +const QString &VCard::soundPhonetic() const +{ + return d->soundPhonetic; +} + +void VCard::setSoundPhonetic(const QString &s) +{ + d->soundPhonetic = s; +} + +const QString &VCard::uid() const +{ + return d->uid; +} + +void VCard::setUid(const QString &u) +{ + d->uid = u; +} + +const QString &VCard::url() const +{ + return d->url; +} + +void VCard::setUrl(const QString &u) +{ + d->url = u; +} + +const QString &VCard::desc() const +{ + return d->desc; +} + +void VCard::setDesc(const QString &desc) +{ + d->desc = desc; +} + +const VCard::PrivacyClass &VCard::privacyClass() const +{ + return d->privacyClass; +} + +void VCard::setPrivacyClass(const VCard::PrivacyClass &c) +{ + d->privacyClass = c; +} + +const QByteArray &VCard::key() const +{ + return d->key; +} + +void VCard::setKey(const QByteArray &k) +{ + d->key = k; +} diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.h b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.h new file mode 100644 index 00000000..ae8cc873 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_vcard.h @@ -0,0 +1,284 @@ +/* + * xmpp_vcard.h - classes for handling vCards + * Copyright (C) 2003 Michail Pishchagin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef JABBER_VCARD_H +#define JABBER_VCARD_H + +#include <qstring.h> +#include <qstringlist.h> +#include <qcstring.h> + +#include <qvaluelist.h> +#include <qdom.h> + +class QDate; + +namespace XMPP +{ + class VCard + { + public: + VCard(); + VCard(const VCard &); + VCard & operator=(const VCard &); + ~VCard(); + + QDomElement toXml(QDomDocument *) const; + bool fromXml(const QDomElement &); + bool isEmpty() const; + + const QString &version() const; + void setVersion(const QString &); + + const QString &fullName() const; + void setFullName(const QString &); + + + const QString &familyName() const; + void setFamilyName(const QString &); + + const QString &givenName() const; + void setGivenName(const QString &); + + const QString &middleName() const; + void setMiddleName(const QString &); + + const QString &prefixName() const; + void setPrefixName(const QString &); + + const QString &suffixName() const; + void setSuffixName(const QString &); + + + const QString &nickName() const; + void setNickName(const QString &); + + + const QByteArray &photo() const; + void setPhoto(const QByteArray &); + + const QString &photoURI() const; + void setPhotoURI(const QString &); + + + const QDate bday() const; + void setBday(const QDate &); + + const QString &bdayStr() const; + void setBdayStr(const QString &); + + + class Address { + public: + Address(); + + bool home; + bool work; + bool postal; + bool parcel; + + bool dom; + bool intl; + + bool pref; + + QString pobox; + QString extaddr; + QString street; + QString locality; + QString region; + QString pcode; + QString country; + }; + typedef QValueList<Address> AddressList; + const AddressList &addressList() const; + void setAddressList(const AddressList &); + + class Label { + public: + Label(); + + bool home; + bool work; + bool postal; + bool parcel; + + bool dom; + bool intl; + + bool pref; + + QStringList lines; + }; + typedef QValueList<Label> LabelList; + const LabelList &labelList() const; + void setLabelList(const LabelList &); + + + class Phone { + public: + Phone(); + + bool home; + bool work; + bool voice; + bool fax; + bool pager; + bool msg; + bool cell; + bool video; + bool bbs; + bool modem; + bool isdn; + bool pcs; + bool pref; + + QString number; + }; + typedef QValueList<Phone> PhoneList; + const PhoneList &phoneList() const; + void setPhoneList(const PhoneList &); + + + class Email { + public: + Email(); + + bool home; + bool work; + bool internet; + bool x400; + + QString userid; + }; + typedef QValueList<Email> EmailList; + const EmailList &emailList() const; + void setEmailList(const EmailList &); + + + const QString &jid() const; + void setJid(const QString &); + + const QString &mailer() const; + void setMailer(const QString &); + + const QString &timezone() const; + void setTimezone(const QString &); + + + class Geo { + public: + Geo(); + + QString lat; + QString lon; + }; + const Geo &geo() const; + void setGeo(const Geo &); + + + const QString &title() const; + void setTitle(const QString &); + + const QString &role() const; + void setRole(const QString &); + + + const QByteArray &logo() const; + void setLogo(const QByteArray &); + + const QString &logoURI() const; + void setLogoURI(const QString &); + + + const VCard *agent() const; + void setAgent(const VCard &); + + const QString agentURI() const; + void setAgentURI(const QString &); + + + class Org { + public: + Org(); + + QString name; + QStringList unit; + }; + const Org &org() const; + void setOrg(const Org &); + + + const QStringList &categories() const; + void setCategories(const QStringList &); + + const QString ¬e() const; + void setNote(const QString &); + + const QString &prodId() const; // it must equal to "Psi" ;-) + void setProdId(const QString &); + + const QString &rev() const; + void setRev(const QString &); + + const QString &sortString() const; + void setSortString(const QString &); + + + const QByteArray &sound() const; + void setSound(const QByteArray &); + + const QString &soundURI() const; + void setSoundURI(const QString &); + + const QString &soundPhonetic() const; + void setSoundPhonetic(const QString &); + + + const QString &uid() const; + void setUid(const QString &); + + const QString &url() const; + void setUrl(const QString &); + + const QString &desc() const; + void setDesc(const QString &); + + + enum PrivacyClass { + pcNone = 0, + pcPublic = 1, + pcPrivate, + pcConfidential + }; + const PrivacyClass &privacyClass() const; + void setPrivacyClass(const PrivacyClass &); + + + const QByteArray &key() const; + void setKey(const QByteArray &); + + private: + class Private; + Private *d; + }; +} + +#endif diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_xmlcommon.cpp b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_xmlcommon.cpp new file mode 100644 index 00000000..2715faf8 --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_xmlcommon.cpp @@ -0,0 +1,386 @@ +/* + * xmlcommon.cpp - helper functions for dealing with XML + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include"xmpp_xmlcommon.h" + +#include <qstring.h> +#include <qdom.h> +#include <qdatetime.h> +#include <qsize.h> +#include <qrect.h> +#include <qstringlist.h> +#include <qcolor.h> + +#include"im.h" + +bool stamp2TS(const QString &ts, QDateTime *d) +{ + if(ts.length() != 17) + return false; + + int year = ts.mid(0,4).toInt(); + int month = ts.mid(4,2).toInt(); + int day = ts.mid(6,2).toInt(); + + int hour = ts.mid(9,2).toInt(); + int min = ts.mid(12,2).toInt(); + int sec = ts.mid(15,2).toInt(); + + QDate xd; + xd.setYMD(year, month, day); + if(!xd.isValid()) + return false; + + QTime xt; + xt.setHMS(hour, min, sec); + if(!xt.isValid()) + return false; + + d->setDate(xd); + d->setTime(xt); + + return true; +} + +QString TS2stamp(const QDateTime &d) +{ + QString str; + + str.sprintf("%04d%02d%02dT%02d:%02d:%02d", + d.date().year(), + d.date().month(), + d.date().day(), + d.time().hour(), + d.time().minute(), + d.time().second()); + + return str; +} + +QDomElement textTag(QDomDocument *doc, const QString &name, const QString &content) +{ + QDomElement tag = doc->createElement(name); + QDomText text = doc->createTextNode(content); + tag.appendChild(text); + + return tag; +} + +QString tagContent(const QDomElement &e) +{ + // look for some tag content + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomText i = n.toText(); + if(i.isNull()) + continue; + return i.data(); + } + + return ""; +} + +QDomElement findSubTag(const QDomElement &e, const QString &name, bool *found) +{ + if(found) + *found = false; + + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + if(i.tagName() == name) { + if(found) + *found = true; + return i; + } + } + + QDomElement tmp; + return tmp; +} + +QDomElement createIQ(QDomDocument *doc, const QString &type, const QString &to, const QString &id) +{ + QDomElement iq = doc->createElement("iq"); + if(!type.isEmpty()) + iq.setAttribute("type", type); + if(!to.isEmpty()) + iq.setAttribute("to", to); + if(!id.isEmpty()) + iq.setAttribute("id", id); + + return iq; +} + +QDomElement queryTag(const QDomElement &e) +{ + bool found; + QDomElement q = findSubTag(e, "query", &found); + return q; +} + +QString queryNS(const QDomElement &e) +{ + bool found; + QDomElement q = findSubTag(e, "query", &found); + if(found) + return q.attribute("xmlns"); + + return ""; +} + +void getErrorFromElement(const QDomElement &e, int *code, QString *str) +{ + bool found; + QDomElement tag = findSubTag(e, "error", &found); + if(!found) + return; + + if(code) + *code = tag.attribute("code").toInt(); + if(str) + *str = tagContent(tag); +} + +//---------------------------------------------------------------------------- +// XMLHelper +//---------------------------------------------------------------------------- + +namespace XMLHelper { + +QDomElement emptyTag(QDomDocument *doc, const QString &name) +{ + QDomElement tag = doc->createElement(name); + + return tag; +} + +bool hasSubTag(const QDomElement &e, const QString &name) +{ + bool found; + findSubTag(e, name, &found); + return found; +} + +QString subTagText(const QDomElement &e, const QString &name) +{ + bool found; + QDomElement i = findSubTag(e, name, &found); + if ( found ) + return i.text(); + return QString::null; +} + +QDomElement textTag(QDomDocument &doc, const QString &name, const QString &content) +{ + QDomElement tag = doc.createElement(name); + QDomText text = doc.createTextNode(content); + tag.appendChild(text); + + return tag; +} + +QDomElement textTag(QDomDocument &doc, const QString &name, int content) +{ + QDomElement tag = doc.createElement(name); + QDomText text = doc.createTextNode(QString::number(content)); + tag.appendChild(text); + + return tag; +} + +QDomElement textTag(QDomDocument &doc, const QString &name, bool content) +{ + QDomElement tag = doc.createElement(name); + QDomText text = doc.createTextNode(content ? "true" : "false"); + tag.appendChild(text); + + return tag; +} + +QDomElement textTag(QDomDocument &doc, const QString &name, QSize &s) +{ + QString str; + str.sprintf("%d,%d", s.width(), s.height()); + + QDomElement tag = doc.createElement(name); + QDomText text = doc.createTextNode(str); + tag.appendChild(text); + + return tag; +} + +QDomElement textTag(QDomDocument &doc, const QString &name, QRect &r) +{ + QString str; + str.sprintf("%d,%d,%d,%d", r.x(), r.y(), r.width(), r.height()); + + QDomElement tag = doc.createElement(name); + QDomText text = doc.createTextNode(str); + tag.appendChild(text); + + return tag; +} + +QDomElement stringListToXml(QDomDocument &doc, const QString &name, const QStringList &l) +{ + QDomElement tag = doc.createElement(name); + for(QStringList::ConstIterator it = l.begin(); it != l.end(); ++it) + tag.appendChild(textTag(doc, "item", *it)); + + return tag; +} + +/*QString tagContent(const QDomElement &e) +{ + // look for some tag content + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomText i = n.toText(); + if(i.isNull()) + continue; + return i.data(); + } + + return ""; +}*/ + +/*QDomElement findSubTag(const QDomElement &e, const QString &name, bool *found) +{ + if(found) + *found = FALSE; + + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + if(i.tagName() == name) { + if(found) + *found = TRUE; + return i; + } + } + + QDomElement tmp; + return tmp; +}*/ + +void readEntry(const QDomElement &e, const QString &name, QString *v) +{ + bool found = FALSE; + QDomElement tag = findSubTag(e, name, &found); + if(!found) + return; + *v = tagContent(tag); +} + +void readNumEntry(const QDomElement &e, const QString &name, int *v) +{ + bool found = FALSE; + QDomElement tag = findSubTag(e, name, &found); + if(!found) + return; + *v = tagContent(tag).toInt(); +} + +void readBoolEntry(const QDomElement &e, const QString &name, bool *v) +{ + bool found = FALSE; + QDomElement tag = findSubTag(e, name, &found); + if(!found) + return; + *v = (tagContent(tag) == "true") ? TRUE: FALSE; +} + +void readSizeEntry(const QDomElement &e, const QString &name, QSize *v) +{ + bool found = FALSE; + QDomElement tag = findSubTag(e, name, &found); + if(!found) + return; + QStringList list = QStringList::split(',', tagContent(tag)); + if(list.count() != 2) + return; + QSize s; + s.setWidth(list[0].toInt()); + s.setHeight(list[1].toInt()); + *v = s; +} + +void readRectEntry(const QDomElement &e, const QString &name, QRect *v) +{ + bool found = FALSE; + QDomElement tag = findSubTag(e, name, &found); + if(!found) + return; + QStringList list = QStringList::split(',', tagContent(tag)); + if(list.count() != 4) + return; + QRect r; + r.setX(list[0].toInt()); + r.setY(list[1].toInt()); + r.setWidth(list[2].toInt()); + r.setHeight(list[3].toInt()); + *v = r; +} + +void readColorEntry(const QDomElement &e, const QString &name, QColor *v) +{ + bool found = FALSE; + QDomElement tag = findSubTag(e, name, &found); + if(!found) + return; + QColor c; + c.setNamedColor(tagContent(tag)); + if(c.isValid()) + *v = c; +} + +void xmlToStringList(const QDomElement &e, const QString &name, QStringList *v) +{ + bool found = false; + QDomElement tag = findSubTag(e, name, &found); + if(!found) + return; + QStringList list; + for(QDomNode n = tag.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + if(i.tagName() == "item") + list += tagContent(i); + } + *v = list; +} + +void setBoolAttribute(QDomElement e, const QString &name, bool b) +{ + e.setAttribute(name, b ? "true" : "false"); +} + +void readBoolAttribute(QDomElement e, const QString &name, bool *v) +{ + if(e.hasAttribute(name)) { + QString s = e.attribute(name); + *v = (s == "true") ? TRUE: FALSE; + } +} + +} + diff --git a/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_xmlcommon.h b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_xmlcommon.h new file mode 100644 index 00000000..f0499c4b --- /dev/null +++ b/kopete/protocols/jabber/libiris/iris/xmpp-im/xmpp_xmlcommon.h @@ -0,0 +1,71 @@ +/* + * xmlcommon.h - helper functions for dealing with XML + * Copyright (C) 2001, 2002 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef JABBER_XMLCOMMON_H +#define JABBER_XMLCOMMON_H + +#include<qdom.h> + +class QDateTime; +class QRect; +class QSize; +class QColor; +class QStringList; + +bool stamp2TS(const QString &ts, QDateTime *d); +QString TS2stamp(const QDateTime &d); +QDomElement textTag(QDomDocument *doc, const QString &name, const QString &content); +QString tagContent(const QDomElement &e); +QDomElement findSubTag(const QDomElement &e, const QString &name, bool *found); +QDomElement createIQ(QDomDocument *doc, const QString &type, const QString &to, const QString &id); +QDomElement queryTag(const QDomElement &e); +QString queryNS(const QDomElement &e); +void getErrorFromElement(const QDomElement &e, int *code, QString *str); + +namespace XMLHelper { + //QDomElement findSubTag(const QDomElement &e, const QString &name, bool *found); + bool hasSubTag(const QDomElement &e, const QString &name); + + QDomElement emptyTag(QDomDocument *doc, const QString &name); + QString subTagText(const QDomElement &e, const QString &name); + + QDomElement textTag(QDomDocument &doc, const QString &name, const QString &content); + QDomElement textTag(QDomDocument &doc, const QString &name, int content); + QDomElement textTag(QDomDocument &doc, const QString &name, bool content); + QDomElement textTag(QDomDocument &doc, const QString &name, QSize &s); + QDomElement textTag(QDomDocument &doc, const QString &name, QRect &r); + QDomElement stringListToXml(QDomDocument &doc, const QString &name, const QStringList &l); + + void readEntry(const QDomElement &e, const QString &name, QString *v); + void readNumEntry(const QDomElement &e, const QString &name, int *v); + void readBoolEntry(const QDomElement &e, const QString &name, bool *v); + void readSizeEntry(const QDomElement &e, const QString &name, QSize *v); + void readRectEntry(const QDomElement &e, const QString &name, QRect *v); + void readColorEntry(const QDomElement &e, const QString &name, QColor *v); + + void xmlToStringList(const QDomElement &e, const QString &name, QStringList *v); + + void setBoolAttribute(QDomElement e, const QString &name, bool b); + void readBoolAttribute(QDomElement e, const QString &name, bool *v); + + //QString tagContent(const QDomElement &e); // obsolete; +} + +#endif |