diff options
| author | Jeremy Lainé <jeremy.laine@m4x.org> | 2012-02-08 12:51:15 +0000 |
|---|---|---|
| committer | Jeremy Lainé <jeremy.laine@m4x.org> | 2012-02-08 12:51:15 +0000 |
| commit | deb9d6cb53057ca8b90d10d6a3bdc5dcfd1b3ee4 (patch) | |
| tree | d956bad28e28aadc3c83dbf88b3eddb5e1d9a9f4 /src/base | |
| parent | e8a1ad0cc608f12874ba4bafbd8282fa537ec9fb (diff) | |
| download | qxmpp-deb9d6cb53057ca8b90d10d6a3bdc5dcfd1b3ee4.tar.gz | |
move files common to client/server into "base"
Diffstat (limited to 'src/base')
79 files changed, 19756 insertions, 0 deletions
diff --git a/src/base/QXmppArchiveIq.cpp b/src/base/QXmppArchiveIq.cpp new file mode 100644 index 00000000..e8bef8fc --- /dev/null +++ b/src/base/QXmppArchiveIq.cpp @@ -0,0 +1,572 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> + +#include "QXmppArchiveIq.h" +#include "QXmppUtils.h" + +static const char *ns_archive = "urn:xmpp:archive"; +static const char *ns_rsm = "http://jabber.org/protocol/rsm"; + +QXmppArchiveMessage::QXmppArchiveMessage() + : m_received(false) +{ +} + +/// Returns the archived message's body. + +QString QXmppArchiveMessage::body() const +{ + return m_body; +} + +/// Sets the archived message's body. +/// +/// \param body +void QXmppArchiveMessage::setBody(const QString &body) +{ + m_body = body; +} + +/// Returns the archived message's date. + +QDateTime QXmppArchiveMessage::date() const +{ + return m_date; +} + +//// Sets the archived message's date. +/// +/// \param date + +void QXmppArchiveMessage::setDate(const QDateTime &date) +{ + m_date = date; +} + +/// Returns true if the archived message was received, false if it was sent. + +bool QXmppArchiveMessage::isReceived() const +{ + return m_received; +} + +/// Set to true if the archived message was received, false if it was sent. +/// +/// \param isReceived + +void QXmppArchiveMessage::setReceived(bool isReceived) +{ + m_received = isReceived; +} + +QXmppArchiveChat::QXmppArchiveChat() + : m_version(0) +{ +} + +void QXmppArchiveChat::parse(const QDomElement &element) +{ + m_with = element.attribute("with"); + m_start = datetimeFromString(element.attribute("start")); + m_subject = element.attribute("subject"); + m_thread = element.attribute("thread"); + m_version = element.attribute("version").toInt(); + + QDomElement child = element.firstChildElement(); + while (!child.isNull()) + { + if ((child.tagName() == "from") || (child.tagName() == "to")) + { + QXmppArchiveMessage message; + message.setBody(child.firstChildElement("body").text()); + message.setDate(m_start.addSecs(child.attribute("secs").toInt())); + message.setReceived(child.tagName() == "from"); + m_messages << message; + } + child = child.nextSiblingElement(); + } +} + +void QXmppArchiveChat::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("chat"); + writer->writeAttribute("xmlns", ns_archive); + helperToXmlAddAttribute(writer, "with", m_with); + if (m_start.isValid()) + helperToXmlAddAttribute(writer, "start", datetimeToString(m_start)); + helperToXmlAddAttribute(writer, "subject", m_subject); + helperToXmlAddAttribute(writer, "thread", m_thread); + if (m_version) + helperToXmlAddAttribute(writer, "version", QString::number(m_version)); + foreach (const QXmppArchiveMessage &message, m_messages) + { + writer->writeStartElement(message.isReceived() ? "from" : "to"); + helperToXmlAddAttribute(writer, "secs", QString::number(m_start.secsTo(message.date()))); + writer->writeTextElement("body", message.body()); + writer->writeEndElement(); + } + writer->writeEndElement(); +} + +/// Returns the conversation's messages. + +QList<QXmppArchiveMessage> QXmppArchiveChat::messages() const +{ + return m_messages; +} + +/// Sets the conversation's messages. + +void QXmppArchiveChat::setMessages(const QList<QXmppArchiveMessage> &messages) +{ + m_messages = messages; +} + +/// Returns the start of this conversation. + +QDateTime QXmppArchiveChat::start() const +{ + return m_start; +} + +/// Sets the start of this conversation. + +void QXmppArchiveChat::setStart(const QDateTime &start) +{ + m_start = start; +} + +/// Returns the conversation's subject. + +QString QXmppArchiveChat::subject() const +{ + return m_subject; +} + +/// Sets the conversation's subject. + +void QXmppArchiveChat::setSubject(const QString &subject) +{ + m_subject = subject; +} + +/// Returns the conversation's thread. + +QString QXmppArchiveChat::thread() const +{ + return m_thread; +} + +/// Sets the conversation's thread. + +void QXmppArchiveChat::setThread(const QString &thread) +{ + m_thread = thread; +} + +/// Returns the conversation's version. + +int QXmppArchiveChat::version() const +{ + return m_version; +} + +/// Sets the conversation's version. + +void QXmppArchiveChat::setVersion(int version) +{ + m_version = version; +} + +/// Returns the JID of the remote party. + +QString QXmppArchiveChat::with() const +{ + return m_with; +} + +/// Sets the JID of the remote party. + +void QXmppArchiveChat::setWith(const QString &with) +{ + m_with = with; +} + +/// Returns the chat conversation carried by this IQ. + +QXmppArchiveChat QXmppArchiveChatIq::chat() const +{ + return m_chat; +} + +/// Sets the chat conversation carried by this IQ. + +void QXmppArchiveChatIq::setChat(const QXmppArchiveChat &chat) +{ + m_chat = chat; +} + +bool QXmppArchiveChatIq::isArchiveChatIq(const QDomElement &element) +{ + QDomElement chatElement = element.firstChildElement("chat"); + return !chatElement.attribute("with").isEmpty(); + //return (chatElement.namespaceURI() == ns_archive); +} + +void QXmppArchiveChatIq::parseElementFromChild(const QDomElement &element) +{ + m_chat.parse(element.firstChildElement("chat")); +} + +void QXmppArchiveChatIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + m_chat.toXml(writer); +} + +/// Constructs a QXmppArchiveListIq. + +QXmppArchiveListIq::QXmppArchiveListIq() + : QXmppIq(QXmppIq::Get), m_max(0) +{ +} + +/// Returns the list of chat conversations. + +QList<QXmppArchiveChat> QXmppArchiveListIq::chats() const +{ + return m_chats; +} + +/// Sets the list of chat conversations. + +void QXmppArchiveListIq::setChats(const QList<QXmppArchiveChat> &chats) +{ + m_chats = chats; +} + +/// Returns the maximum number of results. +/// + +int QXmppArchiveListIq::max() const +{ + return m_max; +} + +/// Sets the maximum number of results. +/// +/// \param max + +void QXmppArchiveListIq::setMax(int max) +{ + m_max = max; +} + +/// Returns the JID which archived conversations must match. +/// + +QString QXmppArchiveListIq::with() const +{ + return m_with; +} + +/// Sets the JID which archived conversations must match. +/// +/// \param with + +void QXmppArchiveListIq::setWith(const QString &with) +{ + m_with = with; +} + +/// Returns the start date/time for the archived conversations. +/// + +QDateTime QXmppArchiveListIq::start() const +{ + return m_start; +} + +/// Sets the start date/time for the archived conversations. +/// +/// \param start + +void QXmppArchiveListIq::setStart(const QDateTime &start) +{ + m_start = start; +} + +/// Returns the end date/time for the archived conversations. +/// + +QDateTime QXmppArchiveListIq::end() const +{ + return m_end; +} + +/// Sets the end date/time for the archived conversations. +/// +/// \param end + +void QXmppArchiveListIq::setEnd(const QDateTime &end) +{ + m_end = end; +} + +bool QXmppArchiveListIq::isArchiveListIq(const QDomElement &element) +{ + QDomElement listElement = element.firstChildElement("list"); + return (listElement.namespaceURI() == ns_archive); +} + +void QXmppArchiveListIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement listElement = element.firstChildElement("list"); + m_with = listElement.attribute("with"); + m_start = datetimeFromString(listElement.attribute("start")); + m_end = datetimeFromString(listElement.attribute("end")); + + QDomElement setElement = listElement.firstChildElement("set"); + if (setElement.namespaceURI() == ns_rsm) + m_max = setElement.firstChildElement("max").text().toInt(); + + QDomElement child = listElement.firstChildElement(); + while (!child.isNull()) + { + if (child.tagName() == "chat") + { + QXmppArchiveChat chat; + chat.parse(child); + m_chats << chat; + } + child = child.nextSiblingElement(); + } +} + +void QXmppArchiveListIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("list"); + writer->writeAttribute("xmlns", ns_archive); + if (!m_with.isEmpty()) + helperToXmlAddAttribute(writer, "with", m_with); + if (m_start.isValid()) + helperToXmlAddAttribute(writer, "start", datetimeToString(m_start)); + if (m_end.isValid()) + helperToXmlAddAttribute(writer, "end", datetimeToString(m_end)); + if (m_max > 0) + { + writer->writeStartElement("set"); + writer->writeAttribute("xmlns", ns_rsm); + helperToXmlAddTextElement(writer, "max", QString::number(m_max)); + writer->writeEndElement(); + } + foreach (const QXmppArchiveChat &chat, m_chats) + chat.toXml(writer); + writer->writeEndElement(); +} + +bool QXmppArchivePrefIq::isArchivePrefIq(const QDomElement &element) +{ + QDomElement prefElement = element.firstChildElement("pref"); + return (prefElement.namespaceURI() == ns_archive); +} + +void QXmppArchivePrefIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement queryElement = element.firstChildElement("pref"); + Q_UNUSED(queryElement); +} + +void QXmppArchivePrefIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("pref"); + writer->writeAttribute("xmlns", ns_archive); + writer->writeEndElement(); +} + +/// Returns the JID which archived conversations must match. +/// + +QString QXmppArchiveRemoveIq::with() const +{ + return m_with; +} + +/// Sets the JID which archived conversations must match. +/// +/// \param with + +void QXmppArchiveRemoveIq::setWith(const QString &with) +{ + m_with = with; +} + +/// Returns the start date/time for the archived conversations. +/// + +QDateTime QXmppArchiveRemoveIq::start() const +{ + return m_start; +} + +/// Sets the start date/time for the archived conversations. +/// +/// \param start + +void QXmppArchiveRemoveIq::setStart(const QDateTime &start) +{ + m_start = start; +} + +/// Returns the end date/time for the archived conversations. +/// + +QDateTime QXmppArchiveRemoveIq::end() const +{ + return m_end; +} + +/// Sets the end date/time for the archived conversations. +/// +/// \param end + +void QXmppArchiveRemoveIq::setEnd(const QDateTime &end) +{ + m_end = end; +} + +bool QXmppArchiveRemoveIq::isArchiveRemoveIq(const QDomElement &element) +{ + QDomElement retrieveElement = element.firstChildElement("remove"); + return (retrieveElement.namespaceURI() == ns_archive); +} + +void QXmppArchiveRemoveIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement listElement = element.firstChildElement("remove"); + m_with = listElement.attribute("with"); + m_start = datetimeFromString(listElement.attribute("start")); + m_end = datetimeFromString(listElement.attribute("end")); +} + +void QXmppArchiveRemoveIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("remove"); + writer->writeAttribute("xmlns", ns_archive); + if (!m_with.isEmpty()) + helperToXmlAddAttribute(writer, "with", m_with); + if (m_start.isValid()) + helperToXmlAddAttribute(writer, "start", datetimeToString(m_start)); + if (m_end.isValid()) + helperToXmlAddAttribute(writer, "end", datetimeToString(m_end)); + writer->writeEndElement(); +} + +QXmppArchiveRetrieveIq::QXmppArchiveRetrieveIq() + : QXmppIq(QXmppIq::Get), m_max(0) +{ +} + +/// Returns the maximum number of results. +/// + +int QXmppArchiveRetrieveIq::max() const +{ + return m_max; +} + +/// Sets the maximum number of results. +/// +/// \param max + +void QXmppArchiveRetrieveIq::setMax(int max) +{ + m_max = max; +} + +/// Returns the start date/time for the archived conversations. +/// + +QDateTime QXmppArchiveRetrieveIq::start() const +{ + return m_start; +} + +/// Sets the start date/time for the archived conversations. +/// +/// \param start + +void QXmppArchiveRetrieveIq::setStart(const QDateTime &start) +{ + m_start = start; +} + +/// Returns the JID which archived conversations must match. +/// + +QString QXmppArchiveRetrieveIq::with() const +{ + return m_with; +} + +/// Sets the JID which archived conversations must match. +/// +/// \param with + +void QXmppArchiveRetrieveIq::setWith(const QString &with) +{ + m_with = with; +} + +bool QXmppArchiveRetrieveIq::isArchiveRetrieveIq(const QDomElement &element) +{ + QDomElement retrieveElement = element.firstChildElement("retrieve"); + return (retrieveElement.namespaceURI() == ns_archive); +} + +void QXmppArchiveRetrieveIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement retrieveElement = element.firstChildElement("retrieve"); + m_with = retrieveElement.attribute("with"); + m_start = datetimeFromString(retrieveElement.attribute("start")); + QDomElement setElement = retrieveElement.firstChildElement("set"); + if (setElement.namespaceURI() == ns_rsm) + m_max = setElement.firstChildElement("max").text().toInt(); +} + +void QXmppArchiveRetrieveIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("retrieve"); + writer->writeAttribute("xmlns", ns_archive); + helperToXmlAddAttribute(writer, "with", m_with); + helperToXmlAddAttribute(writer, "start", datetimeToString(m_start)); + if (m_max > 0) + { + writer->writeStartElement("set"); + writer->writeAttribute("xmlns", ns_rsm); + helperToXmlAddTextElement(writer, "max", QString::number(m_max)); + writer->writeEndElement(); + } + writer->writeEndElement(); +} diff --git a/src/base/QXmppArchiveIq.h b/src/base/QXmppArchiveIq.h new file mode 100644 index 00000000..c0784891 --- /dev/null +++ b/src/base/QXmppArchiveIq.h @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPARCHIVEIQ_H +#define QXMPPARCHIVEIQ_H + +#include "QXmppIq.h" + +#include <QDateTime> + +class QXmlStreamWriter; +class QDomElement; + +/// \brief The QXmppArchiveMessage represents an archived message +/// as defined by XEP-0136: Message Archiving. + +class QXmppArchiveMessage +{ +public: + QXmppArchiveMessage(); + + QString body() const; + void setBody(const QString &body); + + QDateTime date() const; + void setDate(const QDateTime &date); + + bool isReceived() const; + void setReceived(bool isReceived); + +private: + QString m_body; + QDateTime m_date; + bool m_received; +}; + +/// \brief The QXmppArchiveChat represents an archived conversation +/// as defined by XEP-0136: Message Archiving. + +class QXmppArchiveChat +{ +public: + QXmppArchiveChat(); + + QList<QXmppArchiveMessage> messages() const; + void setMessages(const QList<QXmppArchiveMessage> &messages); + + QDateTime start() const; + void setStart(const QDateTime &start); + + QString subject() const; + void setSubject(const QString &subject); + + QString thread() const; + void setThread(const QString &thread); + + int version() const; + void setVersion(int version); + + QString with() const; + void setWith(const QString &with); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QList<QXmppArchiveMessage> m_messages; + QDateTime m_start; + QString m_subject; + QString m_thread; + int m_version; + QString m_with; +}; + +/// \brief Represents an archive chat as defined by XEP-0136: Message Archiving. +/// +/// It is used to get chat as a QXmppArchiveChat. +/// +/// \ingroup Stanzas + +class QXmppArchiveChatIq : public QXmppIq +{ +public: + QXmppArchiveChat chat() const; + void setChat(const QXmppArchiveChat &chat); + + /// \cond + static bool isArchiveChatIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QXmppArchiveChat m_chat; +}; + +/// \brief Represents an archive list as defined by XEP-0136: Message Archiving. +/// +/// \ingroup Stanzas + +class QXmppArchiveListIq : public QXmppIq +{ +public: + QXmppArchiveListIq(); + + QList<QXmppArchiveChat> chats() const; + void setChats(const QList<QXmppArchiveChat> &chats); + + int max() const; + void setMax(int max); + + QString with() const; + void setWith( const QString &with ); + + QDateTime start() const; + void setStart(const QDateTime &start ); + + QDateTime end() const; + void setEnd(const QDateTime &end ); + + /// \cond + static bool isArchiveListIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + int m_max; + QString m_with; + QDateTime m_start; + QDateTime m_end; + QList<QXmppArchiveChat> m_chats; +}; + +/// \brief Represents an archive remove IQ as defined by XEP-0136: Message Archiving. +/// +/// \ingroup Stanzas + +class QXmppArchiveRemoveIq : public QXmppIq +{ +public: + QString with() const; + void setWith( const QString &with ); + + QDateTime start() const; + void setStart(const QDateTime &start ); + + QDateTime end() const; + void setEnd(const QDateTime &end ); + + /// \cond + static bool isArchiveRemoveIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QString m_with; + QDateTime m_start; + QDateTime m_end; +}; + +/// \brief Represents an archive retrieve IQ as defined by XEP-0136: Message Archiving. +/// +/// \ingroup Stanzas + +class QXmppArchiveRetrieveIq : public QXmppIq +{ +public: + QXmppArchiveRetrieveIq(); + + int max() const; + void setMax(int max); + + QDateTime start() const; + void setStart(const QDateTime &start); + + QString with() const; + void setWith(const QString &with); + + /// \cond + static bool isArchiveRetrieveIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + int m_max; + QString m_with; + QDateTime m_start; +}; + +/// \brief Represents an archive preference IQ as defined by XEP-0136: Message Archiving. +/// +/// \ingroup Stanzas + +class QXmppArchivePrefIq : public QXmppIq +{ +public: + /// \cond + static bool isArchivePrefIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond +}; + +#endif // QXMPPARCHIVEIQ_H diff --git a/src/base/QXmppBindIq.cpp b/src/base/QXmppBindIq.cpp new file mode 100644 index 00000000..f818e002 --- /dev/null +++ b/src/base/QXmppBindIq.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> +#include <QTextStream> +#include <QXmlStreamWriter> + +#include "QXmppBindIq.h" +#include "QXmppUtils.h" +#include "QXmppConstants.h" + +/// Returns the bound JID. +/// + +QString QXmppBindIq::jid() const +{ + return m_jid; +} + +/// Sets the bound JID. +/// +/// \param jid + +void QXmppBindIq::setJid(const QString& jid) +{ + m_jid = jid; +} + +/// Returns the requested resource. +/// + +QString QXmppBindIq::resource() const +{ + return m_resource; +} + +/// Sets the requested resource. +/// +/// \param resource + +void QXmppBindIq::setResource(const QString& resource) +{ + m_resource = resource; +} + +bool QXmppBindIq::isBindIq(const QDomElement &element) +{ + QDomElement bindElement = element.firstChildElement("bind"); + return (bindElement.namespaceURI() == ns_bind); +} + +void QXmppBindIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement bindElement = element.firstChildElement("bind"); + m_jid = bindElement.firstChildElement("jid").text(); + m_resource = bindElement.firstChildElement("resource").text(); +} + +void QXmppBindIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("bind"); + writer->writeAttribute("xmlns", ns_bind); + if (!m_jid.isEmpty()) + helperToXmlAddTextElement(writer, "jid", m_jid); + if (!m_resource.isEmpty()) + helperToXmlAddTextElement(writer, "resource", m_resource); + writer->writeEndElement(); +} + diff --git a/src/base/QXmppBindIq.h b/src/base/QXmppBindIq.h new file mode 100644 index 00000000..0131510c --- /dev/null +++ b/src/base/QXmppBindIq.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPBINDIQ_H +#define QXMPPBINDIQ_H + +#include "QXmppIq.h" + +/// \brief The QXmppBindIq class represents an IQ used for resource +/// binding as defined by RFC 5921. +/// +/// \ingroup Stanzas + +class QXmppBindIq : public QXmppIq +{ +public: + QString jid() const; + void setJid(const QString&); + + QString resource() const; + void setResource(const QString&); + + /// \cond + static bool isBindIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QString m_jid; + QString m_resource; +}; + +#endif // QXMPPBIND_H diff --git a/src/base/QXmppBookmarkSet.cpp b/src/base/QXmppBookmarkSet.cpp new file mode 100644 index 00000000..b418d498 --- /dev/null +++ b/src/base/QXmppBookmarkSet.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> + +#include "QXmppBookmarkSet.h" +#include "QXmppUtils.h" + +static const char *ns_bookmarks = "storage:bookmarks"; + +/// Constructs a new conference room bookmark. +/// + +QXmppBookmarkConference::QXmppBookmarkConference() + : m_autoJoin(false) +{ +} + +/// Returns whether the client should automatically join the conference room +/// on login. +/// + +bool QXmppBookmarkConference::autoJoin() const +{ + return m_autoJoin; +} + +/// Sets whether the client should automatically join the conference room +/// on login. +/// +/// \param autoJoin + +void QXmppBookmarkConference::setAutoJoin(bool autoJoin) +{ + m_autoJoin = autoJoin; +} + +/// Returns the JID of the conference room. +/// + +QString QXmppBookmarkConference::jid() const +{ + return m_jid; +} + +/// Sets the JID of the conference room. +/// +/// \param jid + +void QXmppBookmarkConference::setJid(const QString &jid) +{ + m_jid = jid; +} + +/// Returns the friendly name for the bookmark. +/// + +QString QXmppBookmarkConference::name() const +{ + return m_name; +} + +/// Sets the friendly name for the bookmark. +/// +/// \param name + +void QXmppBookmarkConference::setName(const QString &name) +{ + m_name = name; +} + +/// Returns the preferred nickname for the conference room. +/// + +QString QXmppBookmarkConference::nickName() const +{ + return m_nickName; +} + +/// Sets the preferred nickname for the conference room. +/// +/// \param nickName + +void QXmppBookmarkConference::setNickName(const QString &nickName) +{ + m_nickName = nickName; +} + +/// Returns the friendly name for the bookmark. +/// + +QString QXmppBookmarkUrl::name() const +{ + return m_name; +} + +/// Sets the friendly name for the bookmark. +/// +/// \param name + +void QXmppBookmarkUrl::setName(const QString &name) +{ + m_name = name; +} + +/// Returns the URL for the web page. +/// + +QUrl QXmppBookmarkUrl::url() const +{ + return m_url; +} + +/// Sets the URL for the web page. +/// +/// \param url + +void QXmppBookmarkUrl::setUrl(const QUrl &url) +{ + m_url = url; +} + +/// Returns the conference rooms bookmarks in this bookmark set. +/// + +QList<QXmppBookmarkConference> QXmppBookmarkSet::conferences() const +{ + return m_conferences; +} + +/// Sets the conference rooms bookmarks in this bookmark set. +/// +/// \param conferences + +void QXmppBookmarkSet::setConferences(const QList<QXmppBookmarkConference> &conferences) +{ + m_conferences = conferences; +} + +/// Returns the web page bookmarks in this bookmark set. +/// + +QList<QXmppBookmarkUrl> QXmppBookmarkSet::urls() const +{ + return m_urls; +} + +/// Sets the web page bookmarks in this bookmark set. +/// +/// \param urls + +void QXmppBookmarkSet::setUrls(const QList<QXmppBookmarkUrl> &urls) +{ + m_urls = urls; +} + +bool QXmppBookmarkSet::isBookmarkSet(const QDomElement &element) +{ + return element.tagName() == "storage" && + element.namespaceURI() == ns_bookmarks; +} + +void QXmppBookmarkSet::parse(const QDomElement &element) +{ + QDomElement childElement = element.firstChildElement(); + while (!childElement.isNull()) + { + if (childElement.tagName() == "conference") + { + QXmppBookmarkConference conference; + conference.setAutoJoin(childElement.attribute("autojoin") == "true"); + conference.setJid(childElement.attribute("jid")); + conference.setName(childElement.attribute("name")); + conference.setNickName(childElement.firstChildElement("nick").text()); + m_conferences << conference; + } + else if (childElement.tagName() == "url") + { + QXmppBookmarkUrl url; + url.setName(childElement.attribute("name")); + url.setUrl(childElement.attribute("url")); + m_urls << url; + } + childElement = childElement.nextSiblingElement(); + } +} + +void QXmppBookmarkSet::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("storage"); + writer->writeAttribute("xmlns", ns_bookmarks); + foreach (const QXmppBookmarkConference &conference, m_conferences) + { + writer->writeStartElement("conference"); + if (conference.autoJoin()) + helperToXmlAddAttribute(writer, "autojoin", "true"); + helperToXmlAddAttribute(writer, "jid", conference.jid()); + helperToXmlAddAttribute(writer, "name", conference.name()); + if (!conference.nickName().isEmpty()) + helperToXmlAddTextElement(writer, "nick", conference.nickName()); + writer->writeEndElement(); + } + foreach (const QXmppBookmarkUrl &url, m_urls) + { + writer->writeStartElement("url"); + helperToXmlAddAttribute(writer, "name", url.name()); + helperToXmlAddAttribute(writer, "url", url.url().toString()); + writer->writeEndElement(); + } + writer->writeEndElement(); +} + diff --git a/src/base/QXmppBookmarkSet.h b/src/base/QXmppBookmarkSet.h new file mode 100644 index 00000000..aa90f2cf --- /dev/null +++ b/src/base/QXmppBookmarkSet.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPBOOKMARKSET_H +#define QXMPPBOOKMARKSET_H + +#include <QList> +#include <QString> +#include <QUrl> +#include <QXmlStreamWriter> + +class QDomElement; + +/// \brief The QXmppBookmarkConference class represents a bookmark for a conference room, +/// as defined by XEP-0048: Bookmarks. +/// +class QXmppBookmarkConference +{ +public: + QXmppBookmarkConference(); + + bool autoJoin() const; + void setAutoJoin(bool autoJoin); + + QString jid() const; + void setJid(const QString &jid); + + QString name() const; + void setName(const QString &name); + + QString nickName() const; + void setNickName(const QString &nickName); + +private: + bool m_autoJoin; + QString m_jid; + QString m_name; + QString m_nickName; +}; + +/// \brief The QXmppBookmarkUrl class represents a bookmark for a web page, +/// as defined by XEP-0048: Bookmarks. +/// +class QXmppBookmarkUrl +{ +public: + QString name() const; + void setName(const QString &name); + + QUrl url() const; + void setUrl(const QUrl &url); + +private: + QString m_name; + QUrl m_url; +}; + +/// \brief The QXmppbookmarkSets class represents a set of bookmarks, as defined +/// by XEP-0048: Bookmarks. +/// +class QXmppBookmarkSet +{ +public: + QList<QXmppBookmarkConference> conferences() const; + void setConferences(const QList<QXmppBookmarkConference> &conferences); + + QList<QXmppBookmarkUrl> urls() const; + void setUrls(const QList<QXmppBookmarkUrl> &urls); + + /// \cond + static bool isBookmarkSet(const QDomElement &element); + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endconf + +private: + QList<QXmppBookmarkConference> m_conferences; + QList<QXmppBookmarkUrl> m_urls; +}; + +#endif diff --git a/src/base/QXmppByteStreamIq.cpp b/src/base/QXmppByteStreamIq.cpp new file mode 100644 index 00000000..e1628e72 --- /dev/null +++ b/src/base/QXmppByteStreamIq.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> + +#include "QXmppByteStreamIq.h" +#include "QXmppConstants.h" +#include "QXmppUtils.h" + +QHostAddress QXmppByteStreamIq::StreamHost::host() const +{ + return m_host; +} + +void QXmppByteStreamIq::StreamHost::setHost(const QHostAddress &host) +{ + m_host = host; +} + +QString QXmppByteStreamIq::StreamHost::jid() const +{ + return m_jid; +} + +void QXmppByteStreamIq::StreamHost::setJid(const QString &jid) +{ + m_jid = jid; +} + +quint16 QXmppByteStreamIq::StreamHost::port() const +{ + return m_port; +} + +void QXmppByteStreamIq::StreamHost::setPort(quint16 port) +{ + m_port = port; +} + +QString QXmppByteStreamIq::StreamHost::zeroconf() const +{ + return m_zeroconf; +} + +void QXmppByteStreamIq::StreamHost::setZeroconf(const QString &zeroconf) +{ + m_zeroconf = zeroconf; +} + +QXmppByteStreamIq::Mode QXmppByteStreamIq::mode() const +{ + return m_mode; +} + +void QXmppByteStreamIq::setMode(QXmppByteStreamIq::Mode mode) +{ + m_mode = mode; +} + +QString QXmppByteStreamIq::sid() const +{ + return m_sid; +} + +void QXmppByteStreamIq::setSid(const QString &sid) +{ + m_sid = sid; +} + +QString QXmppByteStreamIq::activate() const +{ + return m_activate; +} + +void QXmppByteStreamIq::setActivate(const QString &activate) +{ + m_activate = activate; +} + +QList<QXmppByteStreamIq::StreamHost> QXmppByteStreamIq::streamHosts() const +{ + return m_streamHosts; +} + +void QXmppByteStreamIq::setStreamHosts(const QList<QXmppByteStreamIq::StreamHost> &streamHosts) +{ + m_streamHosts = streamHosts; +} + +QString QXmppByteStreamIq::streamHostUsed() const +{ + return m_streamHostUsed; +} + +void QXmppByteStreamIq::setStreamHostUsed(const QString &jid) +{ + m_streamHostUsed = jid; +} + +bool QXmppByteStreamIq::isByteStreamIq(const QDomElement &element) +{ + return element.firstChildElement("query").namespaceURI() == ns_bytestreams; +} + +void QXmppByteStreamIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement queryElement = element.firstChildElement("query"); + m_sid = queryElement.attribute("sid"); + const QString modeStr = queryElement.attribute("mode"); + if (modeStr == "tcp") + m_mode = Tcp; + else if (modeStr == "udp") + m_mode = Udp; + else + m_mode = None; + + QDomElement hostElement = queryElement.firstChildElement("streamhost"); + while (!hostElement.isNull()) + { + StreamHost streamHost; + streamHost.setHost(QHostAddress(hostElement.attribute("host"))); + streamHost.setJid(hostElement.attribute("jid")); + streamHost.setPort(hostElement.attribute("port").toInt()); + streamHost.setZeroconf(hostElement.attribute("zeroconf")); + m_streamHosts.append(streamHost); + + hostElement = hostElement.nextSiblingElement("streamhost"); + } + m_activate = queryElement.firstChildElement("activate").text(); + m_streamHostUsed = queryElement.firstChildElement("streamhost-used").attribute("jid"); +} + +void QXmppByteStreamIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("query"); + writer->writeAttribute("xmlns", ns_bytestreams); + helperToXmlAddAttribute(writer, "sid", m_sid); + QString modeStr; + if (m_mode == Tcp) + modeStr = "tcp"; + else if (m_mode == Udp) + modeStr = "udp"; + helperToXmlAddAttribute(writer, "mode", modeStr); + foreach (const StreamHost& streamHost, m_streamHosts) + { + writer->writeStartElement("streamhost"); + helperToXmlAddAttribute(writer, "host", streamHost.host().toString()); + helperToXmlAddAttribute(writer, "jid", streamHost.jid()); + helperToXmlAddAttribute(writer, "port", QString::number(streamHost.port())); + helperToXmlAddAttribute(writer, "zeroconf", streamHost.zeroconf()); + writer->writeEndElement(); + } + if (!m_activate.isEmpty()) + helperToXmlAddTextElement(writer, "activate", m_activate); + if (!m_streamHostUsed.isEmpty()) + { + writer->writeStartElement("streamhost-used"); + helperToXmlAddAttribute(writer, "jid", m_streamHostUsed); + writer->writeEndElement(); + } + + writer->writeEndElement(); +} diff --git a/src/base/QXmppByteStreamIq.h b/src/base/QXmppByteStreamIq.h new file mode 100644 index 00000000..604c12e5 --- /dev/null +++ b/src/base/QXmppByteStreamIq.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPBYTESTREAMIQ_H +#define QXMPPBYTESTREAMIQ_H + +#include "QXmppIq.h" + +#include <QHostAddress> + +class QDomElement; +class QXmlStreamWriter; + +class QXmppByteStreamIq : public QXmppIq +{ +public: + enum Mode { + None = 0, + Tcp, + Udp, + }; + + class StreamHost + { + public: + QString jid() const; + void setJid(const QString &jid); + + QHostAddress host() const; + void setHost(const QHostAddress &host); + + quint16 port() const; + void setPort(quint16 port); + + QString zeroconf() const; + void setZeroconf(const QString &zeroconf); + + private: + QHostAddress m_host; + QString m_jid; + quint16 m_port; + QString m_zeroconf; + }; + + QXmppByteStreamIq::Mode mode() const; + void setMode(QXmppByteStreamIq::Mode mode); + + QString sid() const; + void setSid(const QString &sid); + + QString activate() const; + void setActivate(const QString &activate); + + QList<QXmppByteStreamIq::StreamHost> streamHosts() const; + void setStreamHosts(const QList<QXmppByteStreamIq::StreamHost> &streamHosts); + + QString streamHostUsed() const; + void setStreamHostUsed(const QString &jid); + + static bool isByteStreamIq(const QDomElement &element); + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + Mode m_mode; + QString m_sid; + + QString m_activate; + QList<StreamHost> m_streamHosts; + QString m_streamHostUsed; +}; + +#endif diff --git a/src/base/QXmppCodec.cpp b/src/base/QXmppCodec.cpp new file mode 100644 index 00000000..a614ce01 --- /dev/null +++ b/src/base/QXmppCodec.cpp @@ -0,0 +1,1218 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +/* + * G.711 based on reference implementation by Sun Microsystems, Inc. + */ + +#include <QDataStream> +#include <QDebug> +#include <QSize> + +#include "QXmppCodec.h" +#include "QXmppRtpChannel.h" + +#include <cstring> + +#ifdef QXMPP_USE_SPEEX +#include <speex/speex.h> +#endif + +#ifdef QXMPP_USE_THEORA +#include <theora/theoradec.h> +#include <theora/theoraenc.h> +#endif + +#ifdef QXMPP_USE_VPX +#define VPX_CODEC_DISABLE_COMPAT 1 +#include <vpx/vpx_decoder.h> +#include <vpx/vpx_encoder.h> +#include <vpx/vp8cx.h> +#include <vpx/vp8dx.h> +#endif + +#define BIAS (0x84) /* Bias for linear code. */ +#define CLIP 8159 + +#define SIGN_BIT (0x80) /* Sign bit for a A-law byte. */ +#define QUANT_MASK (0xf) /* Quantization field mask. */ +#define NSEGS (8) /* Number of A-law segments. */ +#define SEG_SHIFT (4) /* Left shift for segment number. */ +#define SEG_MASK (0x70) /* Segment field mask. */ + +enum FragmentType { + NoFragment = 0, + StartFragment, + MiddleFragment, + EndFragment, +}; + +static qint16 seg_aend[8] = {0x1F, 0x3F, 0x7F, 0xFF, + 0x1FF, 0x3FF, 0x7FF, 0xFFF}; +static qint16 seg_uend[8] = {0x3F, 0x7F, 0xFF, 0x1FF, + 0x3FF, 0x7FF, 0xFFF, 0x1FFF}; + +static qint16 search(qint16 val, qint16 *table, qint16 size) +{ + qint16 i; + + for (i = 0; i < size; i++) { + if (val <= *table++) + return (i); + } + return (size); +} + +/* + * linear2alaw() - Convert a 16-bit linear PCM value to 8-bit A-law + * + * Accepts a 16-bit integer and encodes it as A-law data. + * + * Linear Input Code Compressed Code + * ------------------------ --------------- + * 0000000wxyza 000wxyz + * 0000001wxyza 001wxyz + * 000001wxyzab 010wxyz + * 00001wxyzabc 011wxyz + * 0001wxyzabcd 100wxyz + * 001wxyzabcde 101wxyz + * 01wxyzabcdef 110wxyz + * 1wxyzabcdefg 111wxyz + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ +quint8 linear2alaw(qint16 pcm_val) +{ + qint16 mask; + qint16 seg; + quint8 aval; + + pcm_val = pcm_val >> 3; + + if (pcm_val >= 0) { + mask = 0xD5; /* sign (7th) bit = 1 */ + } else { + mask = 0x55; /* sign bit = 0 */ + pcm_val = -pcm_val - 1; + } + + /* Convert the scaled magnitude to segment number. */ + seg = search(pcm_val, seg_aend, 8); + + /* Combine the sign, segment, and quantization bits. */ + + if (seg >= 8) /* out of range, return maximum value. */ + return (quint8) (0x7F ^ mask); + else { + aval = (quint8) seg << SEG_SHIFT; + if (seg < 2) + aval |= (pcm_val >> 1) & QUANT_MASK; + else + aval |= (pcm_val >> seg) & QUANT_MASK; + return (aval ^ mask); + } +} + +/* + * alaw2linear() - Convert an A-law value to 16-bit linear PCM + * + */ +qint16 alaw2linear(quint8 a_val) +{ + qint16 t; + qint16 seg; + + a_val ^= 0x55; + + t = (a_val & QUANT_MASK) << 4; + seg = ((qint16)a_val & SEG_MASK) >> SEG_SHIFT; + switch (seg) { + case 0: + t += 8; + break; + case 1: + t += 0x108; + break; + default: + t += 0x108; + t <<= seg - 1; + } + return ((a_val & SIGN_BIT) ? t : -t); +} + +/* + * linear2ulaw() - Convert a linear PCM value to u-law + * + * In order to simplify the encoding process, the original linear magnitude + * is biased by adding 33 which shifts the encoding range from (0 - 8158) to + * (33 - 8191). The result can be seen in the following encoding table: + * + * Biased Linear Input Code Compressed Code + * ------------------------ --------------- + * 00000001wxyza 000wxyz + * 0000001wxyzab 001wxyz + * 000001wxyzabc 010wxyz + * 00001wxyzabcd 011wxyz + * 0001wxyzabcde 100wxyz + * 001wxyzabcdef 101wxyz + * 01wxyzabcdefg 110wxyz + * 1wxyzabcdefgh 111wxyz + * + * Each biased linear code has a leading 1 which identifies the segment + * number. The value of the segment number is equal to 7 minus the number + * of leading 0's. The quantization interval is directly available as the + * four bits wxyz. * The trailing bits (a - h) are ignored. + * + * Ordinarily the complement of the resulting code word is used for + * transmission, and so the code word is complemented before it is returned. + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ +quint8 linear2ulaw(qint16 pcm_val) +{ + qint16 mask; + qint16 seg; + quint8 uval; + + /* Get the sign and the magnitude of the value. */ + pcm_val = pcm_val >> 2; + if (pcm_val < 0) { + pcm_val = -pcm_val; + mask = 0x7F; + } else { + mask = 0xFF; + } + if (pcm_val > CLIP) pcm_val = CLIP; /* clip the magnitude */ + pcm_val += (BIAS >> 2); + + /* Convert the scaled magnitude to segment number. */ + seg = search(pcm_val, seg_uend, 8); + + /* + * Combine the sign, segment, quantization bits; + * and complement the code word. + */ + if (seg >= 8) /* out of range, return maximum value. */ + return (quint8) (0x7F ^ mask); + else { + uval = (quint8) (seg << 4) | ((pcm_val >> (seg + 1)) & 0xF); + return (uval ^ mask); + } +} + +/* + * ulaw2linear() - Convert a u-law value to 16-bit linear PCM + * + * First, a biased linear code is derived from the code word. An unbiased + * output can then be obtained by subtracting 33 from the biased code. + * + * Note that this function expects to be passed the complement of the + * original code word. This is in keeping with ISDN conventions. + */ +qint16 ulaw2linear(quint8 u_val) +{ + qint16 t; + + /* Complement to obtain normal u-law value. */ + u_val = ~u_val; + + /* + * Extract and bias the quantization bits. Then + * shift up by the segment number and subtract out the bias. + */ + t = ((u_val & QUANT_MASK) << 3) + BIAS; + t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT; + + return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); +} + +QXmppG711aCodec::QXmppG711aCodec(int clockrate) +{ + m_frequency = clockrate; +} + +qint64 QXmppG711aCodec::encode(QDataStream &input, QDataStream &output) +{ + qint64 samples = 0; + qint16 pcm; + while (!input.atEnd()) + { + input >> pcm; + output << linear2alaw(pcm); + ++samples; + } + return samples; +} + +qint64 QXmppG711aCodec::decode(QDataStream &input, QDataStream &output) +{ + qint64 samples = 0; + quint8 g711; + while (!input.atEnd()) + { + input >> g711; + output << alaw2linear(g711); + ++samples; + } + return samples; +} + +QXmppG711uCodec::QXmppG711uCodec(int clockrate) +{ + m_frequency = clockrate; +} + +qint64 QXmppG711uCodec::encode(QDataStream &input, QDataStream &output) +{ + qint64 samples = 0; + qint16 pcm; + while (!input.atEnd()) + { + input >> pcm; + output << linear2ulaw(pcm); + ++samples; + } + return samples; +} + +qint64 QXmppG711uCodec::decode(QDataStream &input, QDataStream &output) +{ + qint64 samples = 0; + quint8 g711; + while (!input.atEnd()) + { + input >> g711; + output << ulaw2linear(g711); + ++samples; + } + return samples; +} + +#ifdef QXMPP_USE_SPEEX +QXmppSpeexCodec::QXmppSpeexCodec(int clockrate) +{ + const SpeexMode *mode = &speex_nb_mode; + if (clockrate == 32000) + mode = &speex_uwb_mode; + else if (clockrate == 16000) + mode = &speex_wb_mode; + else if (clockrate == 8000) + mode = &speex_nb_mode; + else + qWarning() << "QXmppSpeexCodec got invalid clockrate" << clockrate; + + // encoder + encoder_bits = new SpeexBits; + speex_bits_init(encoder_bits); + encoder_state = speex_encoder_init(mode); + + // decoder + decoder_bits = new SpeexBits; + speex_bits_init(decoder_bits); + decoder_state = speex_decoder_init(mode); + + // get frame size in samples + speex_encoder_ctl(encoder_state, SPEEX_GET_FRAME_SIZE, &frame_samples); +} + +QXmppSpeexCodec::~QXmppSpeexCodec() +{ + delete encoder_bits; + delete decoder_bits; +} + +qint64 QXmppSpeexCodec::encode(QDataStream &input, QDataStream &output) +{ + QByteArray pcm_buffer(frame_samples * 2, 0); + const int length = input.readRawData(pcm_buffer.data(), pcm_buffer.size()); + if (length != pcm_buffer.size()) + { + qWarning() << "Read only read" << length << "bytes"; + return 0; + } + speex_bits_reset(encoder_bits); + speex_encode_int(encoder_state, (short*)pcm_buffer.data(), encoder_bits); + QByteArray speex_buffer(speex_bits_nbytes(encoder_bits), 0); + speex_bits_write(encoder_bits, speex_buffer.data(), speex_buffer.size()); + output.writeRawData(speex_buffer.data(), speex_buffer.size()); + return frame_samples; +} + +qint64 QXmppSpeexCodec::decode(QDataStream &input, QDataStream &output) +{ + const int length = input.device()->bytesAvailable(); + QByteArray speex_buffer(length, 0); + input.readRawData(speex_buffer.data(), speex_buffer.size()); + speex_bits_read_from(decoder_bits, speex_buffer.data(), speex_buffer.size()); + QByteArray pcm_buffer(frame_samples * 2, 0); + speex_decode_int(decoder_state, decoder_bits, (short*)pcm_buffer.data()); + output.writeRawData(pcm_buffer.data(), pcm_buffer.size()); + return frame_samples; +} + +#endif + +#ifdef QXMPP_USE_THEORA + +class QXmppTheoraDecoderPrivate +{ +public: + bool decodeFrame(const QByteArray &buffer, QXmppVideoFrame *frame); + + th_comment comment; + th_info info; + th_setup_info *setup_info; + th_dec_ctx *ctx; + + QByteArray packetBuffer; +}; + +bool QXmppTheoraDecoderPrivate::decodeFrame(const QByteArray &buffer, QXmppVideoFrame *frame) +{ + if (!ctx) + return false; + + ogg_packet packet; + packet.packet = (unsigned char*) buffer.data(); + packet.bytes = buffer.size(); + packet.b_o_s = 1; + packet.e_o_s = 0; + packet.granulepos = -1; + packet.packetno = 0; + if (th_decode_packetin(ctx, &packet, 0) != 0) { + qWarning("Theora packet could not be decoded"); + return false; + } + + th_ycbcr_buffer ycbcr_buffer; + if (th_decode_ycbcr_out(ctx, ycbcr_buffer) != 0) { + qWarning("Theora packet has no Y'CbCr"); + return false; + } + + if (info.pixel_fmt == TH_PF_420) { + if (!frame->isValid()) { + const int bytes = ycbcr_buffer[0].stride * ycbcr_buffer[0].height + + ycbcr_buffer[1].stride * ycbcr_buffer[1].height + + ycbcr_buffer[2].stride * ycbcr_buffer[2].height; + + *frame = QXmppVideoFrame(bytes, + QSize(ycbcr_buffer[0].width, ycbcr_buffer[0].height), + ycbcr_buffer[0].stride, + QXmppVideoFrame::Format_YUV420P); + } + uchar *output = frame->bits(); + for (int i = 0; i < 3; ++i) { + const int length = ycbcr_buffer[i].stride * ycbcr_buffer[i].height; + memcpy(output, ycbcr_buffer[i].data, length); + output += length; + } + return true; + } else if (info.pixel_fmt == TH_PF_422) { + if (!frame->isValid()) { + const int bytes = ycbcr_buffer[0].width * ycbcr_buffer[0].height * 2; + + *frame = QXmppVideoFrame(bytes, + QSize(ycbcr_buffer[0].width, ycbcr_buffer[0].height), + ycbcr_buffer[0].width * 2, + QXmppVideoFrame::Format_YUYV); + } + + // YUV 4:2:2 packing + const int width = ycbcr_buffer[0].width; + const int height = ycbcr_buffer[0].height; + const int y_stride = ycbcr_buffer[0].stride; + const int c_stride = ycbcr_buffer[1].stride; + const uchar *y_row = ycbcr_buffer[0].data; + const uchar *cb_row = ycbcr_buffer[1].data; + const uchar *cr_row = ycbcr_buffer[2].data; + uchar *output = frame->bits(); + for (int y = 0; y < height; ++y) { + const uchar *y_ptr = y_row; + const uchar *cb_ptr = cb_row; + const uchar *cr_ptr = cr_row; + for (int x = 0; x < width; x += 2) { + *(output++) = *(y_ptr++); + *(output++) = *(cb_ptr++); + *(output++) = *(y_ptr++); + *(output++) = *(cr_ptr++); + } + y_row += y_stride; + cb_row += c_stride; + cr_row += c_stride; + } + return true; + } else { + qWarning("Theora decoder received an unsupported frame format"); + return false; + } +} + +QXmppTheoraDecoder::QXmppTheoraDecoder() +{ + d = new QXmppTheoraDecoderPrivate; + th_comment_init(&d->comment); + th_info_init(&d->info); + d->setup_info = 0; + d->ctx = 0; +} + +QXmppTheoraDecoder::~QXmppTheoraDecoder() +{ + th_comment_clear(&d->comment); + th_info_clear(&d->info); + if (d->setup_info) + th_setup_free(d->setup_info); + if (d->ctx) + th_decode_free(d->ctx); + delete d; +} + +QXmppVideoFormat QXmppTheoraDecoder::format() const +{ + QXmppVideoFormat format; + format.setFrameSize(QSize(d->info.frame_width, d->info.frame_height)); + if (d->info.pixel_fmt == TH_PF_420) + format.setPixelFormat(QXmppVideoFrame::Format_YUV420P); + else if (d->info.pixel_fmt == TH_PF_422) + format.setPixelFormat(QXmppVideoFrame::Format_YUYV); + else + format.setPixelFormat(QXmppVideoFrame::Format_Invalid); + if (d->info.fps_denominator > 0) + format.setFrameRate(qreal(d->info.fps_numerator) / qreal(d->info.fps_denominator)); + return format; +} + +QList<QXmppVideoFrame> QXmppTheoraDecoder::handlePacket(const QXmppRtpPacket &packet) +{ + QList<QXmppVideoFrame> frames; + + // theora deframing: draft-ietf-avt-rtp-theora-00 + QDataStream stream(packet.payload); + quint32 theora_header; + stream >> theora_header; + + quint32 theora_ident = (theora_header >> 8) & 0xffffff; + Q_UNUSED(theora_ident); + quint8 theora_frag = (theora_header & 0xc0) >> 6; + quint8 theora_type = (theora_header & 0x30) >> 4; + quint8 theora_packets = (theora_header & 0x0f); + + //qDebug("ident: 0x%08x, F: %d, TDT: %d, packets: %d", theora_ident, theora_frag, theora_type, theora_packets); + + // We only handle raw theora data + if (theora_type != 0) + return frames; + + QXmppVideoFrame frame; + quint16 packetLength; + + if (theora_frag == NoFragment) { + // unfragmented packet(s) + for (int i = 0; i < theora_packets; ++i) { + stream >> packetLength; + if (packetLength > stream.device()->bytesAvailable()) { + qWarning("Theora unfragmented packet has an invalid length"); + return frames; + } + + d->packetBuffer.resize(packetLength); + stream.readRawData(d->packetBuffer.data(), packetLength); + if (d->decodeFrame(d->packetBuffer, &frame)) + frames << frame; + d->packetBuffer.resize(0); + } + } else { + // fragments + stream >> packetLength; + if (packetLength > stream.device()->bytesAvailable()) { + qWarning("Theora packet has an invalid length"); + return frames; + } + + int pos; + if (theora_frag == StartFragment) { + // start fragment + pos = 0; + d->packetBuffer.resize(packetLength); + } else { + // continuation or end fragment + pos = d->packetBuffer.size(); + d->packetBuffer.resize(pos + packetLength); + } + stream.readRawData(d->packetBuffer.data() + pos, packetLength); + + if (theora_frag == EndFragment) { + // end fragment + if (d->decodeFrame(d->packetBuffer, &frame)) + frames << frame; + d->packetBuffer.resize(0); + } + } + return frames; +} + +bool QXmppTheoraDecoder::setParameters(const QMap<QString, QString> ¶meters) +{ + QByteArray config = QByteArray::fromBase64(parameters.value("configuration").toAscii()); + QDataStream stream(config); + const QIODevice *device = stream.device(); + + if (device->bytesAvailable() < 4) { + qWarning("Theora configuration is too small"); + return false; + } + + // Process packed headers + int done = 0; + quint32 header_count; + stream >> header_count; + for (quint32 i = 0; i < header_count; ++i) { + if (device->bytesAvailable() < 6) { + qWarning("Theora configuration is too small"); + return false; + } + QByteArray ident(3, 0); + quint16 length; + quint8 h_count; + + stream.readRawData(ident.data(), ident.size()); + stream >> length; + stream >> h_count; +#ifdef QXMPP_DEBUG_THEORA + qDebug("Theora packed header %u ident=%s bytes=%u count=%u", i, ident.toHex().data(), length, h_count); +#endif + + // get header sizes + QList<qint64> h_sizes; + for (int h = 0; h < h_count; ++h) { + quint16 h_size = 0; + quint8 b; + do { + if (device->bytesAvailable() < 1) { + qWarning("Theora configuration is too small"); + return false; + } + stream >> b; + h_size = (h_size << 7) | (b & 0x7f); + } while (b & 0x80); + h_sizes << h_size; +#ifdef QXMPP_DEBUG_THEORA + qDebug("Theora header %d size %u", h_sizes.size() - 1, h_sizes.last()); +#endif + length -= h_size; + } + h_sizes << length; +#ifdef QXMPP_DEBUG_THEORA + qDebug("Theora header %d size %u", h_sizes.size() - 1, h_sizes.last()); +#endif + + // decode headers + ogg_packet packet; + packet.b_o_s = 1; + packet.e_o_s = 0; + packet.granulepos = -1; + packet.packetno = 0; + + foreach (int h_size, h_sizes) { + if (device->bytesAvailable() < h_size) { + qWarning("Theora configuration is too small"); + return false; + } + + packet.packet = (unsigned char*) (config.data() + device->pos()); + packet.bytes = h_size; + int ret = th_decode_headerin(&d->info, &d->comment, &d->setup_info, &packet); + if (ret < 0) { + qWarning("Theora header could not be decoded"); + return false; + } + done += ret; + stream.skipRawData(h_size); + } + } + + // check for completion + if (done < 3) { + qWarning("Theora configuration did not contain enough headers"); + return false; + } + +#ifdef QXMPP_DEBUG_THEORA + qDebug("Theora frame_width %i, frame_height %i, colorspace %i, pixel_fmt: %i, target_bitrate: %i, quality: %i, keyframe_granule_shift: %i", + d->info.frame_width, + d->info.frame_height, + d->info.colorspace, + d->info.pixel_fmt, + d->info.target_bitrate, + d->info.quality, + d->info.keyframe_granule_shift); +#endif + if (d->info.pixel_fmt != TH_PF_420 && d->info.pixel_fmt != TH_PF_422) { + qWarning("Theora frames have an unsupported pixel format %d", d->info.pixel_fmt); + return false; + } + if (d->ctx) + th_decode_free(d->ctx); + d->ctx = th_decode_alloc(&d->info, d->setup_info); + if (!d->ctx) { + qWarning("Theora decoder could not be allocated"); + return false; + } + return true; +} + +class QXmppTheoraEncoderPrivate +{ +public: + void writeFragment(QDataStream &stream, FragmentType frag_type, quint8 theora_packets, const char *data, quint16 length); + + th_comment comment; + th_info info; + th_setup_info *setup_info; + th_enc_ctx *ctx; + th_ycbcr_buffer ycbcr_buffer; + + QByteArray buffer; + QByteArray configuration; + QByteArray ident; +}; + +void QXmppTheoraEncoderPrivate::writeFragment(QDataStream &stream, FragmentType frag_type, quint8 theora_packets, const char *data, quint16 length) +{ + // theora framing: draft-ietf-avt-rtp-theora-00 + const quint8 theora_type = 0; // raw data + stream.writeRawData(ident.constData(), ident.size()); + stream << quint8(((frag_type << 6) & 0xc0) | + ((theora_type << 4) & 0x30) | + (theora_packets & 0x0f)); + stream << quint16(length); + stream.writeRawData(data, length); +} + +QXmppTheoraEncoder::QXmppTheoraEncoder() +{ + d = new QXmppTheoraEncoderPrivate; + d->ident = QByteArray("\xc3\x45\xae"); + th_comment_init(&d->comment); + th_info_init(&d->info); + d->setup_info = 0; + d->ctx = 0; +} + +QXmppTheoraEncoder::~QXmppTheoraEncoder() +{ + th_comment_clear(&d->comment); + th_info_clear(&d->info); + if (d->setup_info) + th_setup_free(d->setup_info); + if (d->ctx) + th_encode_free(d->ctx); + delete d; +} + +bool QXmppTheoraEncoder::setFormat(const QXmppVideoFormat &format) +{ + const QXmppVideoFrame::PixelFormat pixelFormat = format.pixelFormat(); + if ((pixelFormat != QXmppVideoFrame::Format_YUV420P) && + (pixelFormat != QXmppVideoFrame::Format_YUYV)) { + qWarning("Theora encoder does not support the given format"); + return false; + } + + d->info.frame_width = format.frameSize().width(); + d->info.frame_height = format.frameSize().height(); + d->info.pic_height = format.frameSize().height(); + d->info.pic_width = format.frameSize().width(); + d->info.pic_x = 0; + d->info.pic_y = 0; + d->info.colorspace = TH_CS_UNSPECIFIED; + d->info.target_bitrate = 0; + d->info.quality = 48; + d->info.keyframe_granule_shift = 6; + + // FIXME: how do we handle floating point frame rates? + d->info.fps_numerator = format.frameRate(); + d->info.fps_denominator = 1; + + if (pixelFormat == QXmppVideoFrame::Format_YUV420P) { + d->info.pixel_fmt = TH_PF_420; + d->ycbcr_buffer[0].width = d->info.frame_width; + d->ycbcr_buffer[0].height = d->info.frame_height; + d->ycbcr_buffer[1].width = d->ycbcr_buffer[0].width / 2; + d->ycbcr_buffer[1].height = d->ycbcr_buffer[0].height / 2; + d->ycbcr_buffer[2].width = d->ycbcr_buffer[1].width; + d->ycbcr_buffer[2].height = d->ycbcr_buffer[1].height; + } else if (pixelFormat == QXmppVideoFrame::Format_YUYV) { + d->info.pixel_fmt = TH_PF_422; + d->buffer.resize(d->info.frame_width * d->info.frame_height * 2); + d->ycbcr_buffer[0].width = d->info.frame_width; + d->ycbcr_buffer[0].height = d->info.frame_height; + d->ycbcr_buffer[0].stride = d->info.frame_width; + d->ycbcr_buffer[0].data = (uchar*) d->buffer.data(); + d->ycbcr_buffer[1].width = d->ycbcr_buffer[0].width / 2; + d->ycbcr_buffer[1].height = d->ycbcr_buffer[0].height; + d->ycbcr_buffer[1].stride = d->ycbcr_buffer[0].stride / 2; + d->ycbcr_buffer[1].data = d->ycbcr_buffer[0].data + d->ycbcr_buffer[0].stride * d->ycbcr_buffer[0].height; + d->ycbcr_buffer[2].width = d->ycbcr_buffer[1].width; + d->ycbcr_buffer[2].height = d->ycbcr_buffer[1].height; + d->ycbcr_buffer[2].stride = d->ycbcr_buffer[1].stride; + d->ycbcr_buffer[2].data = d->ycbcr_buffer[1].data + d->ycbcr_buffer[1].stride * d->ycbcr_buffer[1].height; + } + + // create encoder + if (d->ctx) { + th_encode_free(d->ctx); + d->ctx = 0; + } + d->ctx = th_encode_alloc(&d->info); + if (!d->ctx) { + qWarning("Theora encoder could not be allocated"); + return false; + } + + // fetch headers + QList<QByteArray> headers; + ogg_packet packet; + while (th_encode_flushheader(d->ctx, &d->comment, &packet) > 0) + headers << QByteArray((const char*)packet.packet, packet.bytes); + + // store configuration + d->configuration.clear(); + QDataStream stream(&d->configuration, QIODevice::WriteOnly); + stream << quint32(1); + + quint16 length = 0; + foreach (const QByteArray &header, headers) + length += header.size(); + + quint8 h_count = headers.size() - 1; + stream.writeRawData(d->ident.constData(), d->ident.size()); + stream << length; + stream << h_count; +#ifdef QXMPP_DEBUG_THEORA + qDebug("Theora packed header %u ident=%s bytes=%u count=%u", 0, d->ident.toHex().data(), length, h_count); +#endif + + // write header sizes + for (int h = 0; h < h_count; ++h) { + quint16 h_size = headers[h].size(); + do { + quint8 b = (h_size & 0x7f); + h_size >>= 7; + if (h_size) + b |= 0x80; + stream << b; + } while (h_size); + } + + // write headers + for (int h = 0; h < headers.size(); ++h) { +#ifdef QXMPP_DEBUG_THEORA + qDebug("Header %d size %d", h, headers[h].size()); +#endif + stream.writeRawData(headers[h].data(), headers[h].size()); + } + + return true; +} + +QList<QByteArray> QXmppTheoraEncoder::handleFrame(const QXmppVideoFrame &frame) +{ + QList<QByteArray> packets; + const int PACKET_MAX = 1388; + + if (!d->ctx) + return packets; + + if (d->info.pixel_fmt == TH_PF_420) { + d->ycbcr_buffer[0].stride = frame.bytesPerLine(); + d->ycbcr_buffer[0].data = (unsigned char*) frame.bits(); + d->ycbcr_buffer[1].stride = d->ycbcr_buffer[0].stride / 2; + d->ycbcr_buffer[1].data = d->ycbcr_buffer[0].data + d->ycbcr_buffer[0].stride * d->ycbcr_buffer[0].height; + d->ycbcr_buffer[2].stride = d->ycbcr_buffer[1].stride; + d->ycbcr_buffer[2].data = d->ycbcr_buffer[1].data + d->ycbcr_buffer[1].stride * d->ycbcr_buffer[1].height; + } else if (d->info.pixel_fmt == TH_PF_422) { + // YUV 4:2:2 unpacking + const int width = frame.width(); + const int height = frame.height(); + const int stride = frame.bytesPerLine(); + const uchar *row = frame.bits(); + uchar *y_out = d->ycbcr_buffer[0].data; + uchar *cb_out = d->ycbcr_buffer[1].data; + uchar *cr_out = d->ycbcr_buffer[2].data; + for (int y = 0; y < height; ++y) { + const uchar *ptr = row; + for (int x = 0; x < width; x += 2) { + *(y_out++) = *(ptr++); + *(cb_out++) = *(ptr++); + *(y_out++) = *(ptr++); + *(cr_out++) = *(ptr++); + } + row += stride; + } + } else { + qWarning("Theora encoder received an unsupported frame format"); + return packets; + } + + if (th_encode_ycbcr_in(d->ctx, d->ycbcr_buffer) != 0) { + qWarning("Theora encoder could not handle frame"); + return packets; + } + + QByteArray payload; + ogg_packet packet; + while (th_encode_packetout(d->ctx, 0, &packet) > 0) { +#ifdef QXMPP_DEBUG_THEORA + qDebug("Theora encoded packet %d bytes", packet.bytes); +#endif + QDataStream stream(&payload, QIODevice::WriteOnly); + const char *data = (const char*) packet.packet; + int size = packet.bytes; + if (size <= PACKET_MAX) { + // no fragmentation + stream.device()->reset(); + payload.resize(0); + d->writeFragment(stream, NoFragment, 1, data, size); + packets << payload; + } else { + // fragmentation + FragmentType frag_type = StartFragment; + while (size) { + const int length = qMin(PACKET_MAX, size); + stream.device()->reset(); + payload.resize(0); + d->writeFragment(stream, frag_type, 0, data, length); + data += length; + size -= length; + frag_type = (size > PACKET_MAX) ? MiddleFragment : EndFragment; + packets << payload; + } + } + } + + return packets; +} + +QMap<QString, QString> QXmppTheoraEncoder::parameters() const +{ + QMap<QString, QString> params; + if (d->ctx) { + params.insert("delivery-method", "inline"); + params.insert("configuration", d->configuration.toBase64()); + } + return params; +} + +#endif + +#ifdef QXMPP_USE_VPX + +class QXmppVpxDecoderPrivate +{ +public: + bool decodeFrame(const QByteArray &buffer, QXmppVideoFrame *frame); + + vpx_codec_ctx_t codec; + QByteArray packetBuffer; +}; + +bool QXmppVpxDecoderPrivate::decodeFrame(const QByteArray &buffer, QXmppVideoFrame *frame) +{ + if (vpx_codec_decode(&codec, (const uint8_t*)buffer.constData(), buffer.size(), NULL, 0) != VPX_CODEC_OK) { + qWarning("Vpx packet could not be decoded: %s", vpx_codec_error_detail(&codec)); + return false; + } + + vpx_codec_iter_t iter = NULL; + vpx_image_t *img; + while ((img = vpx_codec_get_frame(&codec, &iter))) { + if (img->fmt == VPX_IMG_FMT_I420) { + if (!frame->isValid()) { + const int bytes = img->d_w * img->d_h * 3 / 2; + + *frame = QXmppVideoFrame(bytes, + QSize(img->d_w, img->d_h), + img->d_w, + QXmppVideoFrame::Format_YUV420P); + } + uchar *output = frame->bits(); + + for (int i = 0; i < 3; ++i) { + uchar *input = img->planes[i]; + const int div = (i == 0) ? 1 : 2; + for (unsigned int y = 0; y < img->d_h / div; ++y) { + memcpy(output, input, img->d_w / div); + input += img->stride[i]; + output += img->d_w / div; + } + } + } else { + qWarning("Vpx decoder received an unsupported frame format: %d", img->fmt); + } + } + + return true; +} + +QXmppVpxDecoder::QXmppVpxDecoder() +{ + d = new QXmppVpxDecoderPrivate; + if (vpx_codec_dec_init(&d->codec, vpx_codec_vp8_dx(), NULL, 0) != VPX_CODEC_OK) { + qWarning("Vpx decoder could not be initialised"); + } +} + +QXmppVpxDecoder::~QXmppVpxDecoder() +{ + vpx_codec_destroy(&d->codec); + delete d; +} + +QXmppVideoFormat QXmppVpxDecoder::format() const +{ + QXmppVideoFormat format; + format.setFrameRate(15.0); + format.setFrameSize(QSize(320, 240)); + format.setPixelFormat(QXmppVideoFrame::Format_YUV420P); + return format; +} + +QList<QXmppVideoFrame> QXmppVpxDecoder::handlePacket(const QXmppRtpPacket &packet) +{ + QList<QXmppVideoFrame> frames; + + // vp8 deframing: http://tools.ietf.org/html/draft-westin-payload-vp8-00 + QDataStream stream(packet.payload); + quint8 vpx_header; + stream >> vpx_header; + + const bool have_id = (vpx_header & 0x10) != 0; + const quint8 frag_type = (vpx_header & 0x6) >> 1; + if (have_id) { + qWarning("Vpx decoder does not support pictureId yet"); + return frames; + } + + const int packetLength = packet.payload.size() - 1; +#ifdef QXMPP_DEBUG_VPX + qDebug("Vpx fragment FI: %d, size %d", frag_type, packetLength); +#endif + + QXmppVideoFrame frame; + + if (frag_type == NoFragment) { + // unfragmented packet + if (d->decodeFrame(packet.payload.mid(1), &frame)) + frames << frame; + d->packetBuffer.resize(0); + } else { + // fragments + if (frag_type == StartFragment) { + // start fragment + d->packetBuffer = packet.payload.mid(1); + } else { + // continuation or end fragment + const int packetPos = d->packetBuffer.size(); + d->packetBuffer.resize(packetPos + packetLength); + stream.readRawData(d->packetBuffer.data() + packetPos, packetLength); + } + + if (frag_type == EndFragment) { + // end fragment + if (d->decodeFrame(d->packetBuffer, &frame)) + frames << frame; + d->packetBuffer.resize(0); + } + + } + + return frames; +} + +bool QXmppVpxDecoder::setParameters(const QMap<QString, QString> ¶meters) +{ + return true; +} + +class QXmppVpxEncoderPrivate +{ +public: + void writeFragment(QDataStream &stream, FragmentType frag_type, const char *data, quint16 length); + + vpx_codec_ctx_t codec; + vpx_codec_enc_cfg_t cfg; + vpx_image_t *imageBuffer; + int frameCount; +}; + +void QXmppVpxEncoderPrivate::writeFragment(QDataStream &stream, FragmentType frag_type, const char *data, quint16 length) +{ + // vp8 framing: http://tools.ietf.org/html/draft-westin-payload-vp8-00 +#ifdef QXMPP_DEBUG_VPX + qDebug("Vpx encoder writing packet frag: %i, size: %u", frag_type, length); +#endif + stream << quint8(((frag_type << 1) & 0x6) | + (frag_type == NoFragment || frag_type == StartFragment)); + stream.writeRawData(data, length); +} + +QXmppVpxEncoder::QXmppVpxEncoder() +{ + d = new QXmppVpxEncoderPrivate; + d->frameCount = 0; + d->imageBuffer = 0; + vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &d->cfg, 0); +} + +QXmppVpxEncoder::~QXmppVpxEncoder() +{ + vpx_codec_destroy(&d->codec); + if (d->imageBuffer) + vpx_img_free(d->imageBuffer); + delete d; +} + +bool QXmppVpxEncoder::setFormat(const QXmppVideoFormat &format) +{ + const QXmppVideoFrame::PixelFormat pixelFormat = format.pixelFormat(); + if (pixelFormat != QXmppVideoFrame::Format_YUYV) { + qWarning("Vpx encoder does not support the given format"); + return false; + } + + d->cfg.rc_target_bitrate = format.frameSize().width() * format.frameSize().height() * d->cfg.rc_target_bitrate / d->cfg.g_w / d->cfg.g_h; + d->cfg.g_w = format.frameSize().width(); + d->cfg.g_h = format.frameSize().height(); + if (vpx_codec_enc_init(&d->codec, vpx_codec_vp8_cx(), &d->cfg, 0) != VPX_CODEC_OK) { + qWarning("Vpx encoder could not be initialised"); + return false; + } + + d->imageBuffer = vpx_img_alloc(NULL, VPX_IMG_FMT_I420, + format.frameSize().width(), format.frameSize().height(), 1); + return true; +} + +QList<QByteArray> QXmppVpxEncoder::handleFrame(const QXmppVideoFrame &frame) +{ + const int PACKET_MAX = 1388; + QList<QByteArray> packets; + + // try to encode frame + if (frame.pixelFormat() == QXmppVideoFrame::Format_YUYV) { + // YUYV -> YUV420P + const int width = frame.width(); + const int height = frame.height(); + const int stride = frame.bytesPerLine(); + const uchar *row = frame.bits(); + uchar *y_row = d->imageBuffer->planes[VPX_PLANE_Y]; + uchar *cb_row = d->imageBuffer->planes[VPX_PLANE_U]; + uchar *cr_row = d->imageBuffer->planes[VPX_PLANE_V]; + for (int y = 0; y < height; y += 2) { + // odd row + const uchar *ptr = row; + uchar *y_out = y_row; + uchar *cb_out = cb_row; + uchar *cr_out = cr_row; + for (int x = 0; x < width; x += 2) { + *(y_out++) = *(ptr++); + *(cb_out++) = *(ptr++); + *(y_out++) = *(ptr++); + *(cr_out++) = *(ptr++); + } + row += stride; + y_row += d->imageBuffer->stride[VPX_PLANE_Y]; + cb_row += d->imageBuffer->stride[VPX_PLANE_U]; + cr_row += d->imageBuffer->stride[VPX_PLANE_V]; + + // even row + ptr = row; + y_out = y_row; + for (int x = 0; x < width; x += 2) { + *(y_out++) = *(ptr++); + ptr++; + *(y_out++) = *(ptr++); + ptr++; + } + row += stride; + y_row += d->imageBuffer->stride[VPX_PLANE_Y]; + } + } else { + qWarning("Vpx encoder does not support the given format"); + return packets; + } + + if (vpx_codec_encode(&d->codec, d->imageBuffer, d->frameCount, 1, 0, VPX_DL_REALTIME) != VPX_CODEC_OK) { + qWarning("Vpx encoder could not handle frame: %s", vpx_codec_error_detail(&d->codec)); + return packets; + } + + // extract data + QByteArray payload; + vpx_codec_iter_t iter = NULL; + const vpx_codec_cx_pkt_t *pkt; + while ((pkt = vpx_codec_get_cx_data(&d->codec, &iter))) { + if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { +#ifdef QXMPP_DEBUG_VPX + qDebug("Vpx encoded packet %lu bytes", pkt->data.frame.sz); +#endif + QDataStream stream(&payload, QIODevice::WriteOnly); + const char *data = (const char*) pkt->data.frame.buf; + int size = pkt->data.frame.sz; + if (size <= PACKET_MAX) { + // no fragmentation + stream.device()->reset(); + payload.resize(0); + d->writeFragment(stream, NoFragment, data, size); + packets << payload; + } else { + // fragmentation + FragmentType frag_type = StartFragment; + while (size) { + const int length = qMin(PACKET_MAX, size); + stream.device()->reset(); + payload.resize(0); + d->writeFragment(stream, frag_type, data, length); + data += length; + size -= length; + frag_type = (size > PACKET_MAX) ? MiddleFragment : EndFragment; + packets << payload; + } + } + } + } + d->frameCount++; + + return packets; +} + +QMap<QString, QString> QXmppVpxEncoder::parameters() const +{ + return QMap<QString, QString>(); +} + +#endif diff --git a/src/base/QXmppCodec.h b/src/base/QXmppCodec.h new file mode 100644 index 00000000..61dd45a2 --- /dev/null +++ b/src/base/QXmppCodec.h @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPCODEC_H +#define QXMPPCODEC_H + +#include <QtGlobal> + +class QXmppRtpPacket; +class QXmppVideoFormat; +class QXmppVideoFrame; + +/// \brief The QXmppCodec class is the base class for audio codecs capable of +/// encoding and decoding audio samples. +/// +/// Samples must be 16-bit little endian. + +class QXmppCodec +{ +public: + /// Reads samples from the input stream, encodes them and writes the + /// encoded data to the output stream. + virtual qint64 encode(QDataStream &input, QDataStream &output) = 0; + + /// Reads encoded data from the input stream, decodes it and writes the + /// decoded samples to the output stream. + virtual qint64 decode(QDataStream &input, QDataStream &output) = 0; +}; + +/// \internal +/// +/// The QXmppG711aCodec class represent a G.711 a-law PCM codec. + +class QXmppG711aCodec : public QXmppCodec +{ +public: + QXmppG711aCodec(int clockrate); + + qint64 encode(QDataStream &input, QDataStream &output); + qint64 decode(QDataStream &input, QDataStream &output); + +private: + int m_frequency; +}; + +/// \internal +/// +/// The QXmppG711uCodec class represent a G.711 u-law PCM codec. + +class QXmppG711uCodec : public QXmppCodec +{ +public: + QXmppG711uCodec(int clockrate); + + qint64 encode(QDataStream &input, QDataStream &output); + qint64 decode(QDataStream &input, QDataStream &output); + +private: + int m_frequency; +}; + +#ifdef QXMPP_USE_SPEEX +typedef struct SpeexBits SpeexBits; + +/// \internal +/// +/// The QXmppSpeexCodec class represent a SPEEX codec. + +class QXmppSpeexCodec : public QXmppCodec +{ +public: + QXmppSpeexCodec(int clockrate); + ~QXmppSpeexCodec(); + + qint64 encode(QDataStream &input, QDataStream &output); + qint64 decode(QDataStream &input, QDataStream &output); + +private: + SpeexBits *encoder_bits; + void *encoder_state; + SpeexBits *decoder_bits; + void *decoder_state; + int frame_samples; +}; +#endif + +/// \brief The QXmppVideoDecoder class is the base class for video decoders. +/// + +class QXmppVideoDecoder +{ +public: + virtual QXmppVideoFormat format() const = 0; + virtual QList<QXmppVideoFrame> handlePacket(const QXmppRtpPacket &packet) = 0; + virtual bool setParameters(const QMap<QString, QString> ¶meters) = 0; +}; + +class QXmppVideoEncoder +{ +public: + virtual bool setFormat(const QXmppVideoFormat &format) = 0; + virtual QList<QByteArray> handleFrame(const QXmppVideoFrame &frame) = 0; + virtual QMap<QString, QString> parameters() const = 0; +}; + +#ifdef QXMPP_USE_THEORA +class QXmppTheoraDecoderPrivate; +class QXmppTheoraEncoderPrivate; + +class QXmppTheoraDecoder : public QXmppVideoDecoder +{ +public: + QXmppTheoraDecoder(); + ~QXmppTheoraDecoder(); + + QXmppVideoFormat format() const; + QList<QXmppVideoFrame> handlePacket(const QXmppRtpPacket &packet); + bool setParameters(const QMap<QString, QString> ¶meters); + +private: + QXmppTheoraDecoderPrivate *d; +}; + +class QXmppTheoraEncoder : public QXmppVideoEncoder +{ +public: + QXmppTheoraEncoder(); + ~QXmppTheoraEncoder(); + + bool setFormat(const QXmppVideoFormat &format); + QList<QByteArray> handleFrame(const QXmppVideoFrame &frame); + QMap<QString, QString> parameters() const; + +private: + QXmppTheoraEncoderPrivate *d; +}; +#endif + +#ifdef QXMPP_USE_VPX +class QXmppVpxDecoderPrivate; +class QXmppVpxEncoderPrivate; + +class QXmppVpxDecoder : public QXmppVideoDecoder +{ +public: + QXmppVpxDecoder(); + ~QXmppVpxDecoder(); + + QXmppVideoFormat format() const; + QList<QXmppVideoFrame> handlePacket(const QXmppRtpPacket &packet); + bool setParameters(const QMap<QString, QString> ¶meters); + +private: + QXmppVpxDecoderPrivate *d; +}; + +class QXmppVpxEncoder : public QXmppVideoEncoder +{ +public: + QXmppVpxEncoder(); + ~QXmppVpxEncoder(); + + bool setFormat(const QXmppVideoFormat &format); + QList<QByteArray> handleFrame(const QXmppVideoFrame &frame); + QMap<QString, QString> parameters() const; + +private: + QXmppVpxEncoderPrivate *d; +}; +#endif + +#endif diff --git a/src/base/QXmppConstants.cpp b/src/base/QXmppConstants.cpp new file mode 100644 index 00000000..c77859b6 --- /dev/null +++ b/src/base/QXmppConstants.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#include "QXmppConstants.h" + +const char* ns_stream = "http://etherx.jabber.org/streams"; +const char* ns_client = "jabber:client"; +const char* ns_server = "jabber:server"; +const char* ns_server_dialback = "jabber:server:dialback"; +const char* ns_roster = "jabber:iq:roster"; +const char* ns_tls = "urn:ietf:params:xml:ns:xmpp-tls"; +const char* ns_sasl = "urn:ietf:params:xml:ns:xmpp-sasl"; +const char* ns_bind = "urn:ietf:params:xml:ns:xmpp-bind"; +const char* ns_session = "urn:ietf:params:xml:ns:xmpp-session"; +const char* ns_stanza = "urn:ietf:params:xml:ns:xmpp-stanzas"; +const char* ns_vcard = "vcard-temp"; +const char* ns_vcard_update = "vcard-temp:x:update"; +const char* ns_auth = "jabber:iq:auth"; +const char* ns_authFeature = "http://jabber.org/features/iq-auth"; +const char* ns_capabilities = "http://jabber.org/protocol/caps"; +const char* ns_compress = "http://jabber.org/protocol/compress"; +const char* ns_compressFeature = "http://jabber.org/features/compress"; +const char* ns_disco_info = "http://jabber.org/protocol/disco#info"; +const char* ns_disco_items = "http://jabber.org/protocol/disco#items"; +const char* ns_ibb = "http://jabber.org/protocol/ibb"; +const char* ns_rpc = "jabber:iq:rpc"; +const char *ns_ping = "urn:xmpp:ping"; +const char *ns_conference = "jabber:x:conference"; +const char *ns_message_receipts = "urn:xmpp:receipts"; +const char *ns_delayed_delivery = "urn:xmpp:delay"; +const char *ns_legacy_delayed_delivery = "jabber:x:delay"; +const char *ns_muc = "http://jabber.org/protocol/muc"; +const char *ns_muc_admin = "http://jabber.org/protocol/muc#admin"; +const char *ns_muc_owner = "http://jabber.org/protocol/muc#owner"; +const char *ns_muc_user = "http://jabber.org/protocol/muc#user"; +const char *ns_chat_states = "http://jabber.org/protocol/chatstates"; +const char *ns_stream_initiation = "http://jabber.org/protocol/si"; +const char *ns_stream_initiation_file_transfer = "http://jabber.org/protocol/si/profile/file-transfer"; +const char *ns_feature_negotiation = "http://jabber.org/protocol/feature-neg"; +const char *ns_bytestreams = "http://jabber.org/protocol/bytestreams"; +// XEP-0092: Software Version +const char *ns_version = "jabber:iq:version"; +const char *ns_data = "jabber:x:data"; +// XEP-0166: Jingle +const char *ns_jingle = "urn:xmpp:jingle:1"; +const char *ns_jingle_raw_udp = "urn:xmpp:jingle:transports:raw-udp:1"; +const char* ns_jingle_ice_udp = "urn:xmpp:jingle:transports:ice-udp:1"; +const char *ns_jingle_rtp = "urn:xmpp:jingle:apps:rtp:1"; +const char *ns_jingle_rtp_audio = "urn:xmpp:jingle:apps:rtp:audio"; +const char *ns_jingle_rtp_video = "urn:xmpp:jingle:apps:rtp:video"; +// XEP-0202: Entity Time +const char *ns_entity_time = "urn:xmpp:time"; +// XEP-0224: Attention +const char *ns_attention = "urn:xmpp:attention:0"; diff --git a/src/base/QXmppConstants.h b/src/base/QXmppConstants.h new file mode 100644 index 00000000..909bddd9 --- /dev/null +++ b/src/base/QXmppConstants.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPCONSTANTS_H +#define QXMPPCONSTANTS_H + +extern const char* ns_stream; +extern const char* ns_client; +extern const char* ns_server; +extern const char* ns_server_dialback; +extern const char* ns_roster; +extern const char* ns_tls; +extern const char* ns_sasl; +extern const char* ns_bind; +extern const char* ns_session; +extern const char* ns_stanza; +extern const char* ns_vcard; +extern const char* ns_vcard_update; +extern const char* ns_auth; +extern const char* ns_authFeature; +extern const char* ns_capabilities; +extern const char* ns_compress; +extern const char* ns_compressFeature; +extern const char* ns_disco_info; +extern const char* ns_disco_items; +extern const char* ns_ibb; +extern const char* ns_rpc; +extern const char* ns_ping; +extern const char *ns_conference; +extern const char *ns_message_receipts; +extern const char *ns_delayed_delivery; +extern const char *ns_legacy_delayed_delivery; +extern const char *ns_muc; +extern const char *ns_muc_admin; +extern const char *ns_muc_owner; +extern const char *ns_muc_user; +extern const char *ns_chat_states; +extern const char *ns_stream_initiation; +extern const char *ns_stream_initiation_file_transfer; +extern const char *ns_feature_negotiation; +extern const char *ns_bytestreams; +extern const char *ns_version; +extern const char *ns_data; +extern const char *ns_jingle; +extern const char* ns_jingle_ice_udp; +extern const char* ns_jingle_raw_udp; +extern const char *ns_jingle_rtp; +extern const char *ns_jingle_rtp_audio; +extern const char *ns_jingle_rtp_video; +extern const char *ns_entity_time; +extern const char *ns_attention; + +#endif // QXMPPCONSTANTS_H diff --git a/src/base/QXmppDataForm.cpp b/src/base/QXmppDataForm.cpp new file mode 100644 index 00000000..aab3857d --- /dev/null +++ b/src/base/QXmppDataForm.cpp @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDebug> +#include <QDomElement> +#include <QStringList> + +#include "QXmppConstants.h" +#include "QXmppDataForm.h" +#include "QXmppUtils.h" + +struct field_type { + QXmppDataForm::Field::Type type; + const char *str; +}; + +static field_type field_types[] = { + {QXmppDataForm::Field::BooleanField, "boolean"}, + {QXmppDataForm::Field::FixedField, "fixed"}, + {QXmppDataForm::Field::HiddenField, "hidden"}, + {QXmppDataForm::Field::JidMultiField, "jid-multi"}, + {QXmppDataForm::Field::JidSingleField, "jid-single"}, + {QXmppDataForm::Field::ListMultiField, "list-multi"}, + {QXmppDataForm::Field::ListSingleField, "list-single"}, + {QXmppDataForm::Field::TextMultiField, "text-multi"}, + {QXmppDataForm::Field::TextPrivateField, "text-private"}, + {QXmppDataForm::Field::TextSingleField, "text-single"}, + {static_cast<QXmppDataForm::Field::Type>(-1), NULL}, +}; + +/// Constructs a QXmppDataForm::Field of the specified \a type. +/// +/// \param type + +QXmppDataForm::Field::Field(QXmppDataForm::Field::Type type) + : m_required(false), + m_type(type) +{ +} + +/// Returns the field's description. + +QString QXmppDataForm::Field::description() const +{ + return m_description; +} + +/// Sets the field's description. +/// +/// \param description + +void QXmppDataForm::Field::setDescription(const QString &description) +{ + m_description = description; +} + +/// Returns the field's key. + +QString QXmppDataForm::Field::key() const +{ + return m_key; +} + +/// Sets the field's key. +/// +/// \param key + +void QXmppDataForm::Field::setKey(const QString &key) +{ + m_key = key; +} + +/// Returns the field's label. + +QString QXmppDataForm::Field::label() const +{ + return m_label; +} + +/// Sets the field's label. +/// +/// \param label + +void QXmppDataForm::Field::setLabel(const QString &label) +{ + m_label = label; +} + +/// Returns the field's options. + +QList<QPair<QString, QString> > QXmppDataForm::Field::options() const +{ + return m_options; +} + +/// Sets the field's options. +/// +/// \param options + +void QXmppDataForm::Field::setOptions(const QList<QPair<QString, QString> > &options) +{ + m_options = options; +} + +/// Returns true if the field is required, false otherwise. + +bool QXmppDataForm::Field::isRequired() const +{ + return m_required; +} + +/// Set to true if the field is required, false otherwise. +/// +/// \param required + +void QXmppDataForm::Field::setRequired(bool required) +{ + m_required = required; +} + +/// Returns the field's type. + +QXmppDataForm::Field::Type QXmppDataForm::Field::type() const +{ + return m_type; +} + +/// Sets the field's type. +/// +/// \param type + +void QXmppDataForm::Field::setType(QXmppDataForm::Field::Type type) +{ + m_type = type; +} + +/// Returns the field's value. + +QVariant QXmppDataForm::Field::value() const +{ + return m_value; +} + +/// Sets the field's value. +/// +/// \param value + +void QXmppDataForm::Field::setValue(const QVariant &value) +{ + m_value = value; +} + +/// Constructs a QXmppDataForm of the specified \a type. +/// +/// \param type + +QXmppDataForm::QXmppDataForm(QXmppDataForm::Type type) + : m_type(type) +{ +} + +/// Returns the form's fields. + +QList<QXmppDataForm::Field> QXmppDataForm::fields() const +{ + return m_fields; +} + +/// Returns the form's fields by reference. + +QList<QXmppDataForm::Field> &QXmppDataForm::fields() +{ + return m_fields; +} + +/// Sets the form's fields. +/// +/// \param fields + +void QXmppDataForm::setFields(const QList<QXmppDataForm::Field> &fields) +{ + m_fields = fields; +} + +/// Returns the form's instructions. + +QString QXmppDataForm::instructions() const +{ + return m_instructions; +} + +/// Sets the form's instructions. +/// +/// \param instructions + +void QXmppDataForm::setInstructions(const QString &instructions) +{ + m_instructions = instructions; +} + +/// Returns the form's title. + +QString QXmppDataForm::title() const +{ + return m_title; +} + +/// Sets the form's title. +/// +/// \param title + +void QXmppDataForm::setTitle(const QString &title) +{ + m_title = title; +} + +/// Returns the form's type. + +QXmppDataForm::Type QXmppDataForm::type() const +{ + return m_type; +} + +/// Sets the form's type. +/// +/// \param type + +void QXmppDataForm::setType(QXmppDataForm::Type type) +{ + m_type = type; +} + +/// Returns true if the form has an unknown type. + +bool QXmppDataForm::isNull() const +{ + return m_type == QXmppDataForm::None; +} + +void QXmppDataForm::parse(const QDomElement &element) +{ + if (element.isNull()) + return; + + /* form type */ + const QString typeStr = element.attribute("type"); + if (typeStr == "form") + m_type = QXmppDataForm::Form; + else if (typeStr == "submit") + m_type = QXmppDataForm::Submit; + else if (typeStr == "cancel") + m_type = QXmppDataForm::Cancel; + else if (typeStr == "result") + m_type = QXmppDataForm::Result; + else + { + qWarning() << "Unknown form type" << typeStr; + return; + } + + /* form properties */ + m_title = element.firstChildElement("title").text(); + m_instructions = element.firstChildElement("instructions").text(); + + QDomElement fieldElement = element.firstChildElement("field"); + while (!fieldElement.isNull()) + { + QXmppDataForm::Field field; + + /* field type */ + QXmppDataForm::Field::Type type = QXmppDataForm::Field::TextSingleField; + const QString typeStr = fieldElement.attribute("type"); + struct field_type *ptr; + for (ptr = field_types; ptr->str; ptr++) + { + if (typeStr == ptr->str) + { + type = ptr->type; + break; + } + } + field.setType(type); + + /* field attributes */ + field.setLabel(fieldElement.attribute("label")); + field.setKey(fieldElement.attribute("var")); + + /* field value(s) */ + if (type == QXmppDataForm::Field::BooleanField) + { + const QString valueStr = fieldElement.firstChildElement("value").text(); + field.setValue(valueStr == "1" || valueStr == "true"); + } + else if (type == QXmppDataForm::Field::ListMultiField || + type == QXmppDataForm::Field::JidMultiField || + type == QXmppDataForm::Field::TextMultiField) + { + QStringList values; + QDomElement valueElement = fieldElement.firstChildElement("value"); + while (!valueElement.isNull()) + { + values.append(valueElement.text()); + valueElement = valueElement.nextSiblingElement("value"); + } + field.setValue(values); + } + else + { + field.setValue(fieldElement.firstChildElement("value").text()); + } + + /* field options */ + if (type == QXmppDataForm::Field::ListMultiField || + type == QXmppDataForm::Field::ListSingleField) + { + QList<QPair<QString, QString> > options; + QDomElement optionElement = fieldElement.firstChildElement("option"); + while (!optionElement.isNull()) + { + options.append(QPair<QString, QString>(optionElement.attribute("label"), + optionElement.firstChildElement("value").text())); + optionElement = optionElement.nextSiblingElement("option"); + } + field.setOptions(options); + } + + /* other properties */ + field.setDescription(fieldElement.firstChildElement("description").text()); + field.setRequired(!fieldElement.firstChildElement("required").isNull()); + + m_fields.append(field); + + fieldElement = fieldElement.nextSiblingElement("field"); + } +} + +void QXmppDataForm::toXml(QXmlStreamWriter *writer) const +{ + if (isNull()) + return; + + writer->writeStartElement("x"); + writer->writeAttribute("xmlns", ns_data); + + /* form type */ + QString typeStr; + if (m_type == QXmppDataForm::Form) + typeStr = "form"; + else if (m_type == QXmppDataForm::Submit) + typeStr = "submit"; + else if (m_type == QXmppDataForm::Cancel) + typeStr = "cancel"; + else if (m_type == QXmppDataForm::Result) + typeStr = "result"; + helperToXmlAddAttribute(writer, "type", typeStr); + + /* form properties */ + if (!m_title.isEmpty()) + helperToXmlAddTextElement(writer, "title", m_title); + if (!m_instructions.isEmpty()) + helperToXmlAddTextElement(writer, "instructions", m_instructions); + + + foreach (const QXmppDataForm::Field &field, m_fields) + { + writer->writeStartElement("field"); + + /* field type */ + const QXmppDataForm::Field::Type type = field.type(); + QString typeStr; + struct field_type *ptr; + for (ptr = field_types; ptr->str; ptr++) + { + if (type == ptr->type) + { + typeStr = ptr->str; + break; + } + } + helperToXmlAddAttribute(writer, "type", typeStr); + + /* field attributes */ + helperToXmlAddAttribute(writer, "label", field.label()); + helperToXmlAddAttribute(writer, "var", field.key()); + + /* field value(s) */ + if (type == QXmppDataForm::Field::BooleanField) + { + helperToXmlAddTextElement(writer, "value", field.value().toBool() ? "1" : "0"); + } + else if (type == QXmppDataForm::Field::ListMultiField || + type == QXmppDataForm::Field::JidMultiField || + type == QXmppDataForm::Field::TextMultiField) + { + foreach (const QString &value, field.value().toStringList()) + helperToXmlAddTextElement(writer, "value", value); + } + else + { + helperToXmlAddTextElement(writer, "value", field.value().toString()); + } + + /* field options */ + if (type == QXmppDataForm::Field::ListMultiField || + type == QXmppDataForm::Field::ListSingleField) + { + QPair<QString, QString> option; + foreach (option, field.options()) + { + writer->writeStartElement("option"); + helperToXmlAddAttribute(writer, "label", option.first); + helperToXmlAddTextElement(writer, "value", option.second); + writer->writeEndElement(); + } + } + + /* other properties */ + if (!field.description().isEmpty()) + helperToXmlAddTextElement(writer, "description", field.description()); + if (field.isRequired()) + helperToXmlAddTextElement(writer, "required", ""); + + writer->writeEndElement(); + } + + writer->writeEndElement(); +} + diff --git a/src/base/QXmppDataForm.h b/src/base/QXmppDataForm.h new file mode 100644 index 00000000..4f38a932 --- /dev/null +++ b/src/base/QXmppDataForm.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPDATAFORM_H +#define QXMPPDATAFORM_H + +#include <QPair> +#include <QString> +#include <QVariant> +#include <QXmlStreamWriter> + +class QDomElement; + +/// \brief The QXmppDataForm class represents a data form as defined by +/// XEP-0004: Data Forms. +/// + +class QXmppDataForm +{ +public: + /// \brief The QXmppDataForm::Field class represents a data form field + /// as defined by XEP-0004: Data Forms. + /// + + class Field + { + public: + /// This enum is used to describe a field's type. + enum Type + { + BooleanField, + FixedField, + HiddenField, + JidMultiField, + JidSingleField, + ListMultiField, + ListSingleField, + TextMultiField, + TextPrivateField, + TextSingleField, + }; + + Field(QXmppDataForm::Field::Type type = QXmppDataForm::Field::TextSingleField); + + QString description() const; + void setDescription(const QString &description); + + QString key() const; + void setKey(const QString &key); + + QString label() const; + void setLabel(const QString &label); + + QList<QPair<QString, QString> > options() const; + void setOptions(const QList<QPair<QString, QString> > &options); + + bool isRequired() const; + void setRequired(bool required); + + QXmppDataForm::Field::Type type() const; + void setType(QXmppDataForm::Field::Type type); + + QVariant value() const; + void setValue(const QVariant &value); + + private: + QString m_description; + QString m_key; + QString m_label; + QList<QPair<QString, QString> > m_options; + bool m_required; + QXmppDataForm::Field::Type m_type; + QVariant m_value; + }; + + /// This enum is used to describe a form's type. + enum Type + { + None, ///< Unknown form type + Form, ///< The form-processing entity is asking the form-submitting + ///< entity to complete a form. + Submit, ///< The form-submitting entity is submitting data to the + ///< form-processing entity. + Cancel, ///< The form-submitting entity has cancelled submission + ///< of data to the form-processing entity. + Result, ///< The form-processing entity is returning data + ///< (e.g., search results) to the form-submitting entity, + ///< or the data is a generic data set. + }; + + QXmppDataForm(QXmppDataForm::Type type = QXmppDataForm::None); + + QString instructions() const; + void setInstructions(const QString &instructions); + + QList<Field> fields() const; + QList<Field> &fields(); + void setFields(const QList<QXmppDataForm::Field> &fields); + + QString title() const; + void setTitle(const QString &title); + + QXmppDataForm::Type type() const; + void setType(QXmppDataForm::Type type); + + bool isNull() const; + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QString m_instructions; + QList<Field> m_fields; + QString m_title; + QXmppDataForm::Type m_type; +}; + +#endif diff --git a/src/base/QXmppDiscoveryIq.cpp b/src/base/QXmppDiscoveryIq.cpp new file mode 100644 index 00000000..6e0d33d5 --- /dev/null +++ b/src/base/QXmppDiscoveryIq.cpp @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QCryptographicHash> +#include <QDomElement> + +#include "QXmppConstants.h" +#include "QXmppDiscoveryIq.h" +#include "QXmppUtils.h" + +static bool identityLessThan(const QXmppDiscoveryIq::Identity &i1, const QXmppDiscoveryIq::Identity &i2) +{ + if (i1.category() < i2.category()) + return true; + else if (i1.category() > i2.category()) + return false; + + if (i1.type() < i2.type()) + return true; + else if (i1.type() > i2.type()) + return false; + + if (i1.language() < i2.language()) + return true; + else if (i1.language() > i2.language()) + return false; + + if (i1.name() < i2.name()) + return true; + else if (i1.name() > i2.name()) + return false; + + return false; +} + +QString QXmppDiscoveryIq::Identity::category() const +{ + return m_category; +} + +void QXmppDiscoveryIq::Identity::setCategory(const QString &category) +{ + m_category = category; +} + +QString QXmppDiscoveryIq::Identity::language() const +{ + return m_language; +} + +void QXmppDiscoveryIq::Identity::setLanguage(const QString &language) +{ + m_language = language; +} + +QString QXmppDiscoveryIq::Identity::name() const +{ + return m_name; +} + +void QXmppDiscoveryIq::Identity::setName(const QString &name) +{ + m_name = name; +} + +QString QXmppDiscoveryIq::Identity::type() const +{ + return m_type; +} + +void QXmppDiscoveryIq::Identity::setType(const QString &type) +{ + m_type = type; +} + +QString QXmppDiscoveryIq::Item::jid() const +{ + return m_jid; +} + +void QXmppDiscoveryIq::Item::setJid(const QString &jid) +{ + m_jid = jid; +} + +QString QXmppDiscoveryIq::Item::name() const +{ + return m_name; +} + +void QXmppDiscoveryIq::Item::setName(const QString &name) +{ + m_name = name; +} + +QString QXmppDiscoveryIq::Item::node() const +{ + return m_node; +} + +void QXmppDiscoveryIq::Item::setNode(const QString &node) +{ + m_node = node; +} + +QStringList QXmppDiscoveryIq::features() const +{ + return m_features; +} + +void QXmppDiscoveryIq::setFeatures(const QStringList &features) +{ + m_features = features; +} + +QList<QXmppDiscoveryIq::Identity> QXmppDiscoveryIq::identities() const +{ + return m_identities; +} + +void QXmppDiscoveryIq::setIdentities(const QList<QXmppDiscoveryIq::Identity> &identities) +{ + m_identities = identities; +} + +QList<QXmppDiscoveryIq::Item> QXmppDiscoveryIq::items() const +{ + return m_items; +} + +void QXmppDiscoveryIq::setItems(const QList<QXmppDiscoveryIq::Item> &items) +{ + m_items = items; +} + +/// Returns the QXmppDataForm for this IQ, as defined by +/// XEP-0128: Service Discovery Extensions. +/// + +QXmppDataForm QXmppDiscoveryIq::form() const +{ + return m_form; +} + +/// Sets the QXmppDataForm for this IQ, as define by +/// XEP-0128: Service Discovery Extensions. +/// +/// \param form +/// + +void QXmppDiscoveryIq::setForm(const QXmppDataForm &form) +{ + m_form = form; +} + +QString QXmppDiscoveryIq::queryNode() const +{ + return m_queryNode; +} + +void QXmppDiscoveryIq::setQueryNode(const QString &node) +{ + m_queryNode = node; +} + +enum QXmppDiscoveryIq::QueryType QXmppDiscoveryIq::queryType() const +{ + return m_queryType; +} + +void QXmppDiscoveryIq::setQueryType(enum QXmppDiscoveryIq::QueryType type) +{ + m_queryType = type; +} + +/// Calculate the verification string for XEP-0115 : Entity Capabilities + +QByteArray QXmppDiscoveryIq::verificationString() const +{ + QString S; + QList<QXmppDiscoveryIq::Identity> sortedIdentities = m_identities; + qSort(sortedIdentities.begin(), sortedIdentities.end(), identityLessThan); + QStringList sortedFeatures = m_features; + qSort(sortedFeatures); + foreach (const QXmppDiscoveryIq::Identity &identity, sortedIdentities) + S += QString("%1/%2/%3/%4<").arg(identity.category(), identity.type(), identity.language(), identity.name()); + foreach (const QString &feature, sortedFeatures) + S += feature + QLatin1String("<"); + QCryptographicHash hasher(QCryptographicHash::Sha1); + hasher.addData(S.toUtf8()); + return hasher.result(); +} + +bool QXmppDiscoveryIq::isDiscoveryIq(const QDomElement &element) +{ + QDomElement queryElement = element.firstChildElement("query"); + return (queryElement.namespaceURI() == ns_disco_info || + queryElement.namespaceURI() == ns_disco_items); +} + +void QXmppDiscoveryIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement queryElement = element.firstChildElement("query"); + m_queryNode = queryElement.attribute("node"); + if (queryElement.namespaceURI() == ns_disco_items) + m_queryType = ItemsQuery; + else + m_queryType = InfoQuery; + + QDomElement itemElement = queryElement.firstChildElement(); + while (!itemElement.isNull()) + { + if (itemElement.tagName() == "feature") + { + m_features.append(itemElement.attribute("var")); + } + else if (itemElement.tagName() == "identity") + { + QXmppDiscoveryIq::Identity identity; + identity.setLanguage(itemElement.attribute("xml:lang")); + identity.setCategory(itemElement.attribute("category")); + identity.setName(itemElement.attribute("name")); + identity.setType(itemElement.attribute("type")); + m_identities.append(identity); + } + else if (itemElement.tagName() == "item") + { + QXmppDiscoveryIq::Item item; + item.setJid(itemElement.attribute("jid")); + item.setName(itemElement.attribute("name")); + item.setNode(itemElement.attribute("node")); + m_items.append(item); + } + else if (itemElement.tagName() == "x" && + itemElement.namespaceURI() == ns_data) + { + m_form.parse(itemElement); + } + itemElement = itemElement.nextSiblingElement(); + } +} + +void QXmppDiscoveryIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("query"); + writer->writeAttribute("xmlns", + m_queryType == InfoQuery ? ns_disco_info : ns_disco_items); + helperToXmlAddAttribute(writer, "node", m_queryNode); + + foreach (const QString &feature, m_features) + { + writer->writeStartElement("feature"); + helperToXmlAddAttribute(writer, "var", feature); + writer->writeEndElement(); + } + + foreach (const QXmppDiscoveryIq::Identity& identity, m_identities) + { + writer->writeStartElement("identity"); + helperToXmlAddAttribute(writer, "xml:lang", identity.language()); + helperToXmlAddAttribute(writer, "category", identity.category()); + helperToXmlAddAttribute(writer, "name", identity.name()); + helperToXmlAddAttribute(writer, "type", identity.type()); + writer->writeEndElement(); + } + + foreach (const QXmppDiscoveryIq::Item& item, m_items) + { + writer->writeStartElement("item"); + helperToXmlAddAttribute(writer, "jid", item.jid()); + helperToXmlAddAttribute(writer, "name", item.name()); + helperToXmlAddAttribute(writer, "node", item.node()); + writer->writeEndElement(); + } + + m_form.toXml(writer); + + writer->writeEndElement(); +} + diff --git a/src/base/QXmppDiscoveryIq.h b/src/base/QXmppDiscoveryIq.h new file mode 100644 index 00000000..40aa1a3b --- /dev/null +++ b/src/base/QXmppDiscoveryIq.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPDISCOVERY_H +#define QXMPPDISCOVERY_H + +#include "QXmppDataForm.h" +#include "QXmppIq.h" + +class QDomElement; + +class QXmppDiscoveryIq : public QXmppIq +{ +public: + class Identity + { + public: + QString category() const; + void setCategory(const QString &category); + + QString language() const; + void setLanguage(const QString &language); + + QString name() const; + void setName(const QString &name); + + QString type() const; + void setType(const QString &type); + + private: + QString m_category; + QString m_language; + QString m_name; + QString m_type; + }; + + class Item + { + public: + QString jid() const; + void setJid(const QString &jid); + + QString name() const; + void setName(const QString &name); + + QString node() const; + void setNode(const QString &node); + + private: + QString m_jid; + QString m_name; + QString m_node; + }; + + enum QueryType { + InfoQuery, + ItemsQuery, + }; + + QStringList features() const; + void setFeatures(const QStringList &features); + + QList<QXmppDiscoveryIq::Identity> identities() const; + void setIdentities(const QList<QXmppDiscoveryIq::Identity> &identities); + + QList<QXmppDiscoveryIq::Item> items() const; + void setItems(const QList<QXmppDiscoveryIq::Item> &items); + + QXmppDataForm form() const; + void setForm(const QXmppDataForm &form); + + QString queryNode() const; + void setQueryNode(const QString &node); + + enum QueryType queryType() const; + void setQueryType(enum QueryType type); + + QByteArray verificationString() const; + + static bool isDiscoveryIq(const QDomElement &element); + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QStringList m_features; + QList<QXmppDiscoveryIq::Identity> m_identities; + QList<QXmppDiscoveryIq::Item> m_items; + QXmppDataForm m_form; + QString m_queryNode; + enum QueryType m_queryType; +}; + +#endif diff --git a/src/base/QXmppElement.cpp b/src/base/QXmppElement.cpp new file mode 100644 index 00000000..904ab473 --- /dev/null +++ b/src/base/QXmppElement.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include "QXmppElement.h" +#include "QXmppUtils.h" + +#include <QDomElement> + +class QXmppElementPrivate +{ +public: + QXmppElementPrivate(); + QXmppElementPrivate(const QDomElement &element); + ~QXmppElementPrivate(); + + QAtomicInt counter; + + QXmppElementPrivate *parent; + QMap<QString, QString> attributes; + QList<QXmppElementPrivate*> children; + QString name; + QString value; +}; + +QXmppElementPrivate::QXmppElementPrivate() + : counter(1), parent(NULL) +{ +} + +QXmppElementPrivate::QXmppElementPrivate(const QDomElement &element) + : counter(1), parent(NULL) +{ + if (element.isNull()) + return; + + name = element.tagName(); + QString xmlns = element.namespaceURI(); + QString parentns = element.parentNode().namespaceURI(); + if (!xmlns.isEmpty() && xmlns != parentns) + attributes.insert("xmlns", xmlns); + QDomNamedNodeMap attrs = element.attributes(); + for (int i = 0; i < attrs.size(); i++) + { + QDomAttr attr = attrs.item(i).toAttr(); + attributes.insert(attr.name(), attr.value()); + } + + QDomNode childNode = element.firstChild(); + while (!childNode.isNull()) + { + if (childNode.isElement()) + { + QXmppElementPrivate *child = new QXmppElementPrivate(childNode.toElement()); + child->parent = this; + children.append(child); + } else if (childNode.isText()) { + value += childNode.toText().data(); + } + childNode = childNode.nextSibling(); + } +} + +QXmppElementPrivate::~QXmppElementPrivate() +{ + foreach (QXmppElementPrivate *child, children) + if (!child->counter.deref()) + delete child; +} + +QXmppElement::QXmppElement() +{ + d = new QXmppElementPrivate(); +} + +QXmppElement::QXmppElement(const QXmppElement &other) +{ + other.d->counter.ref(); + d = other.d; +} + +QXmppElement::QXmppElement(QXmppElementPrivate *other) +{ + other->counter.ref(); + d = other; +} + +QXmppElement::QXmppElement(const QDomElement &element) +{ + d = new QXmppElementPrivate(element); +} + +QXmppElement::~QXmppElement() +{ + if (!d->counter.deref()) + delete d; +} + +QXmppElement &QXmppElement::operator=(const QXmppElement &other) +{ + other.d->counter.ref(); + if (!d->counter.deref()) + delete d; + d = other.d; + return *this; +} + +QStringList QXmppElement::attributeNames() const +{ + return d->attributes.keys(); +} + +QString QXmppElement::attribute(const QString &name) const +{ + return d->attributes.value(name); +} + +void QXmppElement::setAttribute(const QString &name, const QString &value) +{ + d->attributes.insert(name, value); +} + +void QXmppElement::appendChild(const QXmppElement &child) +{ + if (child.d->parent == d) + return; + + if (child.d->parent) + child.d->parent->children.removeAll(child.d); + else + child.d->counter.ref(); + child.d->parent = d; + d->children.append(child.d); +} + +QXmppElement QXmppElement::firstChildElement(const QString &name) const +{ + foreach (QXmppElementPrivate *child_d, d->children) + if (name.isEmpty() || child_d->name == name) + return QXmppElement(child_d); + return QXmppElement(); +} + +QXmppElement QXmppElement::nextSiblingElement(const QString &name) const +{ + if (!d->parent) + return QXmppElement(); + const QList<QXmppElementPrivate*> &siblings_d = d->parent->children; + for (int i = siblings_d.indexOf(d) + 1; i < siblings_d.size(); i++) + if (name.isEmpty() || siblings_d[i]->name == name) + return QXmppElement(siblings_d[i]); + return QXmppElement(); +} + +bool QXmppElement::isNull() const +{ + return d->name.isEmpty(); +} + +void QXmppElement::removeChild(const QXmppElement &child) +{ + if (child.d->parent != d) + return; + + d->children.removeAll(child.d); + child.d->counter.deref(); + child.d->parent = NULL; +} + +QString QXmppElement::tagName() const +{ + return d->name; +} + +void QXmppElement::setTagName(const QString &tagName) +{ + d->name = tagName; +} + +QString QXmppElement::value() const +{ + return d->value; +} + +void QXmppElement::setValue(const QString &value) +{ + d->value = value; +} + +void QXmppElement::toXml(QXmlStreamWriter *writer) const +{ + if (isNull()) + return; + + writer->writeStartElement(d->name); + if (d->attributes.contains("xmlns")) + writer->writeAttribute("xmlns", d->attributes.value("xmlns")); + foreach (const QString &attr, d->attributes.keys()) + if (attr != "xmlns") + helperToXmlAddAttribute(writer, attr, d->attributes.value(attr)); + if (!d->value.isEmpty()) + writer->writeCharacters(d->value); + foreach (const QXmppElement &child, d->children) + child.toXml(writer); + writer->writeEndElement(); +} + +QXmppElementList::QXmppElementList() +{ +} + +QXmppElementList::QXmppElementList(const QXmppElement &element) +{ + append(element); +} + + +QXmppElementList::QXmppElementList(const QList<QXmppElement> &other) + : QList<QXmppElement>(other) +{ +} + diff --git a/src/base/QXmppElement.h b/src/base/QXmppElement.h new file mode 100644 index 00000000..7fa75494 --- /dev/null +++ b/src/base/QXmppElement.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPELEMENT_H +#define QXMPPELEMENT_H + +#include <QMap> +#include <QStringList> +#include <QXmlStreamWriter> + +class QDomElement; +class QXmppElement; +class QXmppElementPrivate; + +class QXmppElementList : public QList<QXmppElement> +{ +public: + QXmppElementList(); + QXmppElementList(const QXmppElement &element); + QXmppElementList(const QList<QXmppElement> &other); +}; + +class QXmppElement +{ +public: + QXmppElement(); + QXmppElement(const QXmppElement &other); + QXmppElement(const QDomElement &element); + ~QXmppElement(); + + QStringList attributeNames() const; + + QString attribute(const QString &name) const; + void setAttribute(const QString &name, const QString &value); + + void appendChild(const QXmppElement &child); + QXmppElement firstChildElement(const QString &name = QString()) const; + QXmppElement nextSiblingElement(const QString &name = QString()) const; + void removeChild(const QXmppElement &child); + + QString tagName() const; + void setTagName(const QString &type); + + QString value() const; + void setValue(const QString &text); + + bool isNull() const; + void toXml(QXmlStreamWriter *writer) const; + + QXmppElement &operator=(const QXmppElement &other); + +private: + QXmppElement(QXmppElementPrivate *other); + QXmppElementPrivate *d; +}; + +#endif diff --git a/src/base/QXmppEntityTimeIq.cpp b/src/base/QXmppEntityTimeIq.cpp new file mode 100644 index 00000000..5e41e39f --- /dev/null +++ b/src/base/QXmppEntityTimeIq.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#include "QXmppEntityTimeIq.h" + +#include <QDomElement> + +#include "QXmppConstants.h" +#include "QXmppUtils.h" + +/// Returns the timezone offset in seconds. +/// + +int QXmppEntityTimeIq::tzo() const +{ + return m_tzo; +} + +/// Sets the timezone offset in seconds. +/// +/// \param tzo + +void QXmppEntityTimeIq::setTzo(int tzo) +{ + m_tzo = tzo; +} + +/// Returns the date/time in Coordinated Universal Time (UTC). +/// + +QDateTime QXmppEntityTimeIq::utc() const +{ + return m_utc; +} + +/// Sets the date/time in Coordinated Universal Time (UTC). +/// +/// \param utc + +void QXmppEntityTimeIq::setUtc(const QDateTime &utc) +{ + m_utc = utc; +} + +bool QXmppEntityTimeIq::isEntityTimeIq(const QDomElement &element) +{ + QDomElement timeElement = element.firstChildElement("time"); + return timeElement.namespaceURI() == ns_entity_time; +} + +void QXmppEntityTimeIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement timeElement = element.firstChildElement("time"); + m_tzo = timezoneOffsetFromString(timeElement.firstChildElement("tzo").text()); + m_utc = datetimeFromString(timeElement.firstChildElement("utc").text()); +} + +void QXmppEntityTimeIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("time"); + writer->writeAttribute("xmlns", ns_entity_time); + + if(m_utc.isValid()) + { + helperToXmlAddTextElement(writer, "tzo", timezoneOffsetToString(m_tzo)); + helperToXmlAddTextElement(writer, "utc", datetimeToString(m_utc)); + } + writer->writeEndElement(); +} diff --git a/src/base/QXmppEntityTimeIq.h b/src/base/QXmppEntityTimeIq.h new file mode 100644 index 00000000..fa3d5f12 --- /dev/null +++ b/src/base/QXmppEntityTimeIq.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPENTITYTIMEIQ_H +#define QXMPPENTITYTIMEIQ_H + +#include <QDateTime> + +#include "QXmppIq.h" + +/// \ingroup Stanzas + +class QXmppEntityTimeIq : public QXmppIq +{ +public: + int tzo() const; + void setTzo(int tzo); + + QDateTime utc() const; + void setUtc(const QDateTime &utc); + + static bool isEntityTimeIq(const QDomElement &element); + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + int m_tzo; + QDateTime m_utc; +}; + +#endif //QXMPPENTITYTIMEIQ_H diff --git a/src/base/QXmppGlobal.cpp b/src/base/QXmppGlobal.cpp new file mode 100644 index 00000000..2735788a --- /dev/null +++ b/src/base/QXmppGlobal.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#include "QXmppGlobal.h" + +QString QXmppVersion() +{ + return QString("%1.%2.%3").arg( + QString::number((QXMPP_VERSION >> 16) & 0xff), + QString::number((QXMPP_VERSION >> 8) & 0xff), + QString::number(QXMPP_VERSION & 0xff)); +} + diff --git a/src/base/QXmppGlobal.h b/src/base/QXmppGlobal.h new file mode 100644 index 00000000..36122d1a --- /dev/null +++ b/src/base/QXmppGlobal.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPGLOBAL_H +#define QXMPPGLOBAL_H + +#include <QString> + +/// This macro expands a numeric value of the form 0xMMNNPP (MM = +/// major, NN = minor, PP = patch) that specifies QXmpp's version +/// number. For example, if you compile your application against +/// QXmpp 1.2.3, the QXMPP_VERSION macro will expand to 0x010203. +/// +/// You can use QXMPP_VERSION to use the latest QXmpp features where +/// available. +/// + +#define QXMPP_VERSION 0x00035b + +QString QXmppVersion(); + +#endif //QXMPPGLOBAL_H diff --git a/src/base/QXmppIbbIq.cpp b/src/base/QXmppIbbIq.cpp new file mode 100644 index 00000000..85aba54a --- /dev/null +++ b/src/base/QXmppIbbIq.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> +#include <QXmlStreamWriter> + +#include "QXmppConstants.h" +#include "QXmppIbbIq.h" + +QXmppIbbOpenIq::QXmppIbbOpenIq() : QXmppIq(QXmppIq::Set), m_block_size(1024) +{ + +} + +long QXmppIbbOpenIq::blockSize() const +{ + return m_block_size; +} + +void QXmppIbbOpenIq::setBlockSize( long block_size ) +{ + m_block_size = block_size; +} + +QString QXmppIbbOpenIq::sid() const +{ + return m_sid; +} + +void QXmppIbbOpenIq::setSid( const QString &sid ) +{ + m_sid = sid; +} + +bool QXmppIbbOpenIq::isIbbOpenIq(const QDomElement &element) +{ + QDomElement openElement = element.firstChildElement("open"); + return openElement.namespaceURI() == ns_ibb; +} + +void QXmppIbbOpenIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement openElement = element.firstChildElement("open"); + m_sid = openElement.attribute( "sid" ); + m_block_size = openElement.attribute( "block-size" ).toLong(); +} + +void QXmppIbbOpenIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("open"); + writer->writeAttribute( "xmlns",ns_ibb); + writer->writeAttribute( "sid",m_sid); + writer->writeAttribute( "block-size",QString::number(m_block_size) ); + writer->writeEndElement(); +} + +QXmppIbbCloseIq::QXmppIbbCloseIq() : QXmppIq(QXmppIq::Set) +{ + +} + +QString QXmppIbbCloseIq::sid() const +{ + return m_sid; +} + +void QXmppIbbCloseIq::setSid( const QString &sid ) +{ + m_sid = sid; +} + +bool QXmppIbbCloseIq::isIbbCloseIq(const QDomElement &element) +{ + QDomElement openElement = element.firstChildElement("close"); + return openElement.namespaceURI() == ns_ibb; +} + +void QXmppIbbCloseIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement openElement = element.firstChildElement("close"); + m_sid = openElement.attribute( "sid" ); +} + +void QXmppIbbCloseIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("close"); + writer->writeAttribute( "xmlns",ns_ibb); + writer->writeAttribute( "sid",m_sid); + writer->writeEndElement(); +} + +QXmppIbbDataIq::QXmppIbbDataIq() : QXmppIq( QXmppIq::Set ), m_seq(0) +{ +} + +quint16 QXmppIbbDataIq::sequence() const +{ + return m_seq; +} + +void QXmppIbbDataIq::setSequence( quint16 seq ) +{ + m_seq = seq; +} + +QString QXmppIbbDataIq::sid() const +{ + return m_sid; +} + +void QXmppIbbDataIq::setSid( const QString &sid ) +{ + m_sid = sid; +} + +QByteArray QXmppIbbDataIq::payload() const +{ + return m_payload; +} + +void QXmppIbbDataIq::setPayload( const QByteArray &data ) +{ + m_payload = data; +} + +bool QXmppIbbDataIq::isIbbDataIq(const QDomElement &element) +{ + QDomElement dataElement = element.firstChildElement("data"); + return dataElement.namespaceURI() == ns_ibb; +} + +void QXmppIbbDataIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement dataElement = element.firstChildElement("data"); + m_sid = dataElement.attribute( "sid" ); + m_seq = dataElement.attribute( "seq" ).toLong(); + m_payload = QByteArray::fromBase64( dataElement.text().toLatin1() ); +} + +void QXmppIbbDataIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("data"); + writer->writeAttribute( "xmlns",ns_ibb); + writer->writeAttribute( "sid",m_sid); + writer->writeAttribute( "seq",QString::number(m_seq) ); + writer->writeCharacters( m_payload.toBase64() ); + writer->writeEndElement(); +} diff --git a/src/base/QXmppIbbIq.h b/src/base/QXmppIbbIq.h new file mode 100644 index 00000000..592b12f4 --- /dev/null +++ b/src/base/QXmppIbbIq.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPIBBIQ_H +#define QXMPPIBBIQ_H + +#include "QXmppIq.h" + +class QDomElement; +class QXmlStreamWriter; + +class QXmppIbbOpenIq: public QXmppIq +{ +public: + QXmppIbbOpenIq(); + + long blockSize() const; + void setBlockSize( long block_size ); + + QString sid() const; + void setSid( const QString &sid ); + + static bool isIbbOpenIq(const QDomElement &element); + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + long m_block_size; + QString m_sid; +}; + +class QXmppIbbCloseIq: public QXmppIq +{ +public: + QXmppIbbCloseIq(); + + QString sid() const; + void setSid( const QString &sid ); + + static bool isIbbCloseIq(const QDomElement &element); + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QString m_sid; +}; + +class QXmppIbbDataIq : public QXmppIq +{ +public: + QXmppIbbDataIq(); + + quint16 sequence() const; + void setSequence( quint16 seq ); + + QString sid() const; + void setSid( const QString &sid ); + + QByteArray payload() const; + void setPayload( const QByteArray &data ); + + static bool isIbbDataIq(const QDomElement &element); + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + quint16 m_seq; + QString m_sid; + QByteArray m_payload; +}; + +#endif // QXMPPIBBIQS_H diff --git a/src/base/QXmppIq.cpp b/src/base/QXmppIq.cpp new file mode 100644 index 00000000..d0ee0a7a --- /dev/null +++ b/src/base/QXmppIq.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#include "QXmppUtils.h" +#include "QXmppIq.h" + +#include <QDomElement> +#include <QXmlStreamWriter> + +/// Constructs a QXmppIq with the specified \a type. +/// +/// \param type + +QXmppIq::QXmppIq(QXmppIq::Type type) + : QXmppStanza(), m_type(type) +{ + generateAndSetNextId(); +} + +/// Returns the IQ's type. +/// + +QXmppIq::Type QXmppIq::type() const +{ + return m_type; +} + +/// Sets the IQ's type. +/// +/// \param type + +void QXmppIq::setType(QXmppIq::Type type) +{ + m_type = type; +} + +void QXmppIq::parse(const QDomElement &element) +{ + QXmppStanza::parse(element); + setTypeFromStr(element.attribute("type")); + parseElementFromChild(element); +} + +void QXmppIq::parseElementFromChild(const QDomElement &element) +{ + QXmppElementList extensions; + QDomElement itemElement = element.firstChildElement(); + while (!itemElement.isNull()) + { + extensions.append(QXmppElement(itemElement)); + itemElement = itemElement.nextSiblingElement(); + } + setExtensions(extensions); +} + +void QXmppIq::toXml( QXmlStreamWriter *xmlWriter ) const +{ + xmlWriter->writeStartElement("iq"); + + helperToXmlAddAttribute(xmlWriter, "id", id()); + helperToXmlAddAttribute(xmlWriter, "to", to()); + helperToXmlAddAttribute(xmlWriter, "from", from()); + if(getTypeStr().isEmpty()) + helperToXmlAddAttribute(xmlWriter, "type", "get"); + else + helperToXmlAddAttribute(xmlWriter, "type", getTypeStr()); + toXmlElementFromChild(xmlWriter); + error().toXml(xmlWriter); + xmlWriter->writeEndElement(); +} + +void QXmppIq::toXmlElementFromChild( QXmlStreamWriter *writer ) const +{ + foreach (const QXmppElement &extension, extensions()) + extension.toXml(writer); +} + +QString QXmppIq::getTypeStr() const +{ + switch(m_type) + { + case QXmppIq::Error: + return "error"; + case QXmppIq::Get: + return "get"; + case QXmppIq::Set: + return "set"; + case QXmppIq::Result: + return "result"; + default: + qWarning("QXmppIq::getTypeStr() invalid type %d", (int)m_type); + return ""; + } +} + +void QXmppIq::setTypeFromStr(const QString& str) +{ + if(str == "error") + { + setType(QXmppIq::Error); + return; + } + else if(str == "get") + { + setType(QXmppIq::Get); + return; + } + else if(str == "set") + { + setType(QXmppIq::Set); + return; + } + else if(str == "result") + { + setType(QXmppIq::Result); + return; + } + else + { + setType(static_cast<QXmppIq::Type>(-1)); + qWarning("QXmppIq::setTypeFromStr() invalid input string type: %s", + qPrintable(str)); + return; + } +} + diff --git a/src/base/QXmppIq.h b/src/base/QXmppIq.h new file mode 100644 index 00000000..bbde3d06 --- /dev/null +++ b/src/base/QXmppIq.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPIQ_H +#define QXMPPIQ_H + +#include "QXmppStanza.h" + +// forward declarations of QXmlStream* classes will not work on Mac, we need to +// include the whole header. +// See http://lists.trolltech.com/qt-interest/2008-07/thread00798-0.html +// for an explanation. +#include <QXmlStreamWriter> + +/// \brief The QXmppIq class is the base class for all IQs. +/// +/// \ingroup Stanzas + +class QXmppIq : public QXmppStanza +{ +public: + /// This enum describes the type of IQ. + enum Type + { + Error = 0, ///< Error response. + Get, ///< Get request. + Set, ///< Set request. + Result ///< Result. + }; + + QXmppIq(QXmppIq::Type type = QXmppIq::Get); + + QXmppIq::Type type() const; + void setType(QXmppIq::Type); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + +protected: + virtual void parseElementFromChild(const QDomElement &element); + virtual void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QString getTypeStr() const; + void setTypeFromStr(const QString& str); + + Type m_type; +}; + +#endif // QXMPPIQ_H diff --git a/src/base/QXmppJingleIq.cpp b/src/base/QXmppJingleIq.cpp new file mode 100644 index 00000000..5eb11322 --- /dev/null +++ b/src/base/QXmppJingleIq.cpp @@ -0,0 +1,848 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> + +#include "QXmppConstants.h" +#include "QXmppJingleIq.h" +#include "QXmppUtils.h" + +static const char* ns_jingle_rtp_info = "urn:xmpp:jingle:apps:rtp:info:1"; + +static const char* jingle_actions[] = { + "content-accept", + "content-add", + "content-modify", + "content-reject", + "content-remove", + "description-info", + "security-info", + "session-accept", + "session-info", + "session-initiate", + "session-terminate", + "transport-accept", + "transport-info", + "transport-reject", + "transport-replace", +}; + +static const char* jingle_reasons[] = { + "", + "alternative-session", + "busy", + "cancel", + "connectivity-error", + "decline", + "expired", + "failed-application", + "failed-transport", + "general-error", + "gone", + "incompatible-parameters", + "media-error", + "security-error", + "success", + "timeout", + "unsupported-applications", + "unsupported-transports", +}; + +QXmppJingleIq::Content::Content() +{ +} + +QString QXmppJingleIq::Content::creator() const +{ + return m_creator; +} + +void QXmppJingleIq::Content::setCreator(const QString &creator) +{ + m_creator = creator; +} + +QString QXmppJingleIq::Content::name() const +{ + return m_name; +} + +void QXmppJingleIq::Content::setName(const QString &name) +{ + m_name = name; +} + +QString QXmppJingleIq::Content::senders() const +{ + return m_senders; +} + +void QXmppJingleIq::Content::setSenders(const QString &senders) +{ + m_senders = senders; +} + +QString QXmppJingleIq::Content::descriptionMedia() const +{ + return m_descriptionMedia; +} + +void QXmppJingleIq::Content::setDescriptionMedia(const QString &media) +{ + m_descriptionMedia = media; +} + +void QXmppJingleIq::Content::addPayloadType(const QXmppJinglePayloadType &payload) +{ + m_descriptionType = ns_jingle_rtp; + m_payloadTypes << payload; +} + +QList<QXmppJinglePayloadType> QXmppJingleIq::Content::payloadTypes() const +{ + return m_payloadTypes; +} + +void QXmppJingleIq::Content::setPayloadTypes(const QList<QXmppJinglePayloadType> &payloadTypes) +{ + m_descriptionType = payloadTypes.isEmpty() ? QString() : ns_jingle_rtp; + m_payloadTypes = payloadTypes; +} + +void QXmppJingleIq::Content::addTransportCandidate(const QXmppJingleCandidate &candidate) +{ + m_transportType = ns_jingle_ice_udp; + m_transportCandidates << candidate; +} + +QList<QXmppJingleCandidate> QXmppJingleIq::Content::transportCandidates() const +{ + return m_transportCandidates; +} + +QString QXmppJingleIq::Content::transportUser() const +{ + return m_transportUser; +} + +void QXmppJingleIq::Content::setTransportUser(const QString &user) +{ + m_transportUser = user; +} + +QString QXmppJingleIq::Content::transportPassword() const +{ + return m_transportPassword; +} + +void QXmppJingleIq::Content::setTransportPassword(const QString &password) +{ + m_transportPassword = password; +} + +void QXmppJingleIq::Content::parse(const QDomElement &element) +{ + m_creator = element.attribute("creator"); + m_disposition = element.attribute("disposition"); + m_name = element.attribute("name"); + m_senders = element.attribute("senders"); + + // description + QDomElement descriptionElement = element.firstChildElement("description"); + m_descriptionType = descriptionElement.namespaceURI(); + m_descriptionMedia = descriptionElement.attribute("media"); + QDomElement child = descriptionElement.firstChildElement("payload-type"); + while (!child.isNull()) + { + QXmppJinglePayloadType payload; + payload.parse(child); + m_payloadTypes << payload; + child = child.nextSiblingElement("payload-type"); + } + + // transport + QDomElement transportElement = element.firstChildElement("transport"); + m_transportType = transportElement.namespaceURI(); + m_transportUser = transportElement.attribute("ufrag"); + m_transportPassword = transportElement.attribute("pwd"); + child = transportElement.firstChildElement("candidate"); + while (!child.isNull()) + { + QXmppJingleCandidate candidate; + candidate.parse(child); + m_transportCandidates << candidate; + child = child.nextSiblingElement("candidate"); + } +} + +void QXmppJingleIq::Content::toXml(QXmlStreamWriter *writer) const +{ + if (m_creator.isEmpty() || m_name.isEmpty()) + return; + + writer->writeStartElement("content"); + helperToXmlAddAttribute(writer, "creator", m_creator); + helperToXmlAddAttribute(writer, "disposition", m_disposition); + helperToXmlAddAttribute(writer, "name", m_name); + helperToXmlAddAttribute(writer, "senders", m_senders); + + // description + if (!m_descriptionType.isEmpty() || !m_payloadTypes.isEmpty()) + { + writer->writeStartElement("description"); + writer->writeAttribute("xmlns", m_descriptionType); + helperToXmlAddAttribute(writer, "media", m_descriptionMedia); + foreach (const QXmppJinglePayloadType &payload, m_payloadTypes) + payload.toXml(writer); + writer->writeEndElement(); + } + + // transport + if (!m_transportType.isEmpty() || !m_transportCandidates.isEmpty()) + { + writer->writeStartElement("transport"); + writer->writeAttribute("xmlns", m_transportType); + helperToXmlAddAttribute(writer, "ufrag", m_transportUser); + helperToXmlAddAttribute(writer, "pwd", m_transportPassword); + foreach (const QXmppJingleCandidate &candidate, m_transportCandidates) + candidate.toXml(writer); + writer->writeEndElement(); + } + writer->writeEndElement(); +} + +QXmppJingleIq::Reason::Reason() + : m_type(None) +{ +} + +QString QXmppJingleIq::Reason::text() const +{ + return m_text; +} + +void QXmppJingleIq::Reason::setText(const QString &text) +{ + m_text = text; +} + +QXmppJingleIq::Reason::Type QXmppJingleIq::Reason::type() const +{ + return m_type; +} + +void QXmppJingleIq::Reason::setType(QXmppJingleIq::Reason::Type type) +{ + m_type = type; +} + +void QXmppJingleIq::Reason::parse(const QDomElement &element) +{ + m_text = element.firstChildElement("text").text(); + for (int i = AlternativeSession; i <= UnsupportedTransports; i++) + { + if (!element.firstChildElement(jingle_reasons[i]).isNull()) + { + m_type = static_cast<Type>(i); + break; + } + } +} + +void QXmppJingleIq::Reason::toXml(QXmlStreamWriter *writer) const +{ + if (m_type < AlternativeSession || m_type > UnsupportedTransports) + return; + + writer->writeStartElement("reason"); + if (!m_text.isEmpty()) + helperToXmlAddTextElement(writer, "text", m_text); + writer->writeEmptyElement(jingle_reasons[m_type]); + writer->writeEndElement(); +} + +/// Constructs a QXmppJingleIq. + +QXmppJingleIq::QXmppJingleIq() + : m_ringing(false) +{ +} + +/// Returns the Jingle IQ's action. + +QXmppJingleIq::Action QXmppJingleIq::action() const +{ + return m_action; +} + +/// Sets the Jingle IQ's action. +/// +/// \param action + +void QXmppJingleIq::setAction(QXmppJingleIq::Action action) +{ + m_action = action; +} + +/// Returns the session initiator. + +QString QXmppJingleIq::initiator() const +{ + return m_initiator; +} + +/// Sets the session initiator. +/// +/// \param initiator + +void QXmppJingleIq::setInitiator(const QString &initiator) +{ + m_initiator = initiator; +} + +/// Returns the session responder. + +QString QXmppJingleIq::responder() const +{ + return m_responder; +} + +/// Sets the session responder. +/// +/// \param responder + +void QXmppJingleIq::setResponder(const QString &responder) +{ + m_responder = responder; +} + +/// Returns the session ID. + +QString QXmppJingleIq::sid() const +{ + return m_sid; +} + +/// Sets the session ID. +/// +/// \param sid + +void QXmppJingleIq::setSid(const QString &sid) +{ + m_sid = sid; +} + +bool QXmppJingleIq::isJingleIq(const QDomElement &element) +{ + QDomElement jingleElement = element.firstChildElement("jingle"); + return (jingleElement.namespaceURI() == ns_jingle); +} + +/// Returns true if the call is ringing. + +bool QXmppJingleIq::ringing() const +{ + return m_ringing; +} + +/// Set to true if the call is ringing. +/// +/// \param ringing + +void QXmppJingleIq::setRinging(bool ringing) +{ + m_ringing = ringing; +} + +void QXmppJingleIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement jingleElement = element.firstChildElement("jingle"); + const QString action = jingleElement.attribute("action"); + for (int i = ContentAccept; i <= TransportReplace; i++) + { + if (action == jingle_actions[i]) + { + m_action = static_cast<Action>(i); + break; + } + } + m_initiator = jingleElement.attribute("initiator"); + m_responder = jingleElement.attribute("responder"); + m_sid = jingleElement.attribute("sid"); + + // content + QDomElement contentElement = jingleElement.firstChildElement("content"); + m_content.parse(contentElement); + QDomElement reasonElement = jingleElement.firstChildElement("reason"); + m_reason.parse(reasonElement); + + // ringing + QDomElement ringingElement = jingleElement.firstChildElement("ringing"); + m_ringing = (ringingElement.namespaceURI() == ns_jingle_rtp_info); +} + +void QXmppJingleIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("jingle"); + writer->writeAttribute("xmlns", ns_jingle); + helperToXmlAddAttribute(writer, "action", jingle_actions[m_action]); + helperToXmlAddAttribute(writer, "initiator", m_initiator); + helperToXmlAddAttribute(writer, "responder", m_responder); + helperToXmlAddAttribute(writer, "sid", m_sid); + m_content.toXml(writer); + m_reason.toXml(writer); + + // ringing + if (m_ringing) + { + writer->writeStartElement("ringing"); + writer->writeAttribute("xmlns", ns_jingle_rtp_info); + writer->writeEndElement(); + } + + writer->writeEndElement(); +} + +QXmppJingleCandidate::QXmppJingleCandidate() + : m_component(0), + m_foundation(0), + m_generation(0), + m_network(0), + m_port(0), + m_priority(0), + m_type(HostType) +{ +} + +/// Returns the candidate's component ID. + +int QXmppJingleCandidate::component() const +{ + return m_component; +} + +/// Sets the candidates's component ID. +/// +/// \param component + +void QXmppJingleCandidate::setComponent(int component) +{ + m_component = component; +} + +/// Returns the candidate's foundation. + +int QXmppJingleCandidate::foundation() const +{ + return m_foundation; +} + +/// Sets the candidate's foundation. +/// +/// \param foundation + +void QXmppJingleCandidate::setFoundation(int foundation) +{ + m_foundation = foundation; +} + +/// Returns the candidate's host address. +/// + +QHostAddress QXmppJingleCandidate::host() const +{ + return m_host; +} + +/// Sets the candidate's host address. +/// +/// \param host + +void QXmppJingleCandidate::setHost(const QHostAddress &host) +{ + m_host = host; +} + +/// Returns the candidate's unique identifier. +/// + +QString QXmppJingleCandidate::id() const +{ + return m_id; +} + +/// Sets the candidate's unique identifier. +/// +/// \param id + +void QXmppJingleCandidate::setId(const QString &id) +{ + m_id = id; +} + +/// Returns the network index (starting at 0) the candidate is on. +/// + +int QXmppJingleCandidate::network() const +{ + return m_network; +} + +/// Sets the network index (starting at 0) the candidate is on. +/// +/// \param network + +void QXmppJingleCandidate::setNetwork(int network) +{ + m_network = network; +} + +/// Returns the candidate's port number. +/// + +quint16 QXmppJingleCandidate::port() const +{ + return m_port; +} + +/// Sets the candidate's port number. +/// +/// \param port + +void QXmppJingleCandidate::setPort(quint16 port) +{ + m_port = port; +} + +/// Returns the candidate's priority. +/// + +int QXmppJingleCandidate::priority() const +{ + return m_priority; +} + +/// Sets the candidate's priority. +/// +/// \param priority + +void QXmppJingleCandidate::setPriority(int priority) +{ + m_priority = priority; +} + +/// Returns the candidate's protocol (e.g. "udp"). +/// + +QString QXmppJingleCandidate::protocol() const +{ + return m_protocol; +} + +/// Sets the candidate's protocol (e.g. "udp"). +/// +/// \param protocol + +void QXmppJingleCandidate::setProtocol(const QString &protocol) +{ + m_protocol = protocol; +} + +/// Returns the candidate type (e.g. "host"). +/// + +QXmppJingleCandidate::Type QXmppJingleCandidate::type() const +{ + return m_type; +} + +/// Sets the candidate type (e.g. "host"). +/// +/// \param type + +void QXmppJingleCandidate::setType(QXmppJingleCandidate::Type type) +{ + m_type = type; +} + +/// Returns true if the host address or port are empty. +/// + +bool QXmppJingleCandidate::isNull() const +{ + return m_host.isNull() || !m_port; +} + +void QXmppJingleCandidate::parse(const QDomElement &element) +{ + m_component = element.attribute("component").toInt(); + m_foundation = element.attribute("foundation").toInt(); + m_generation = element.attribute("generation").toInt(); + m_host = QHostAddress(element.attribute("ip")); + m_id = element.attribute("id"); + m_network = element.attribute("network").toInt(); + m_port = element.attribute("port").toInt(); + m_priority = element.attribute("priority").toInt(); + m_protocol = element.attribute("protocol"); + m_type = typeFromString(element.attribute("type")); +} + +void QXmppJingleCandidate::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("candidate"); + helperToXmlAddAttribute(writer, "component", QString::number(m_component)); + helperToXmlAddAttribute(writer, "foundation", QString::number(m_foundation)); + helperToXmlAddAttribute(writer, "generation", QString::number(m_generation)); + helperToXmlAddAttribute(writer, "id", m_id); + helperToXmlAddAttribute(writer, "ip", m_host.toString()); + helperToXmlAddAttribute(writer, "network", QString::number(m_network)); + helperToXmlAddAttribute(writer, "port", QString::number(m_port)); + helperToXmlAddAttribute(writer, "priority", QString::number(m_priority)); + helperToXmlAddAttribute(writer, "protocol", m_protocol); + helperToXmlAddAttribute(writer, "type", typeToString(m_type)); + writer->writeEndElement(); +} + +QXmppJingleCandidate::Type QXmppJingleCandidate::typeFromString(const QString &typeStr, bool *ok) +{ + QXmppJingleCandidate::Type type; + if (typeStr == "host") + type = HostType; + else if (typeStr == "prflx") + type = PeerReflexiveType; + else if (typeStr == "srflx") + type = ServerReflexiveType; + else if (typeStr == "relay") + type = RelayedType; + else { + qWarning() << "Unknown candidate type" << typeStr; + if (ok) + *ok = false; + return HostType; + } + if (ok) + *ok = true; + return type; +} + +QString QXmppJingleCandidate::typeToString(QXmppJingleCandidate::Type type) +{ + QString typeStr; + switch (type) + { + case HostType: + typeStr = "host"; + break; + case PeerReflexiveType: + typeStr = "prflx"; + break; + case ServerReflexiveType: + typeStr = "srflx"; + break; + case RelayedType: + typeStr = "relay"; + break; + } + return typeStr; +} + +QXmppJinglePayloadType::QXmppJinglePayloadType() + : m_channels(1), + m_clockrate(0), + m_id(0), + m_maxptime(0), + m_ptime(0) +{ +} + +/// Returns the number of channels (e.g. 1 for mono, 2 for stereo). +/// + +unsigned char QXmppJinglePayloadType::channels() const +{ + return m_channels; +} + +/// Sets the number of channels (e.g. 1 for mono, 2 for stereo). +/// +/// \param channels + +void QXmppJinglePayloadType::setChannels(unsigned char channels) +{ + m_channels = channels; +} + +/// Returns the clockrate in Hz, i.e. the number of samples per second. +/// + +unsigned int QXmppJinglePayloadType::clockrate() const +{ + return m_clockrate; +} + +/// Sets the clockrate in Hz, i.e. the number of samples per second. +/// +/// \param clockrate + +void QXmppJinglePayloadType::setClockrate(unsigned int clockrate) +{ + m_clockrate = clockrate; +} + +/// Returns the payload type identifier. +/// + +unsigned char QXmppJinglePayloadType::id() const +{ + return m_id; +} + +/// Sets the payload type identifier. +/// + +void QXmppJinglePayloadType::setId(unsigned char id) +{ + Q_ASSERT(id <= 127); + m_id = id; +} + +/// Returns the maximum packet time in milliseconds. +/// + +unsigned int QXmppJinglePayloadType::maxptime() const +{ + return m_maxptime; +} + +/// Sets the maximum packet type in milliseconds. +/// +/// \param maxptime + +void QXmppJinglePayloadType::setMaxptime(unsigned int maxptime) +{ + m_maxptime = maxptime; +} + +/// Returns the payload type name. +/// + +QString QXmppJinglePayloadType::name() const +{ + return m_name; +} + +/// Sets the payload type name. +/// +/// \param name + +void QXmppJinglePayloadType::setName(const QString &name) +{ + m_name = name; +} + +/// Returns the payload parameters. + +QMap<QString,QString> QXmppJinglePayloadType::parameters() const +{ + return m_parameters; +} + +/// Sets the payload parameters. + +void QXmppJinglePayloadType::setParameters(const QMap<QString, QString> ¶meters) +{ + m_parameters = parameters; +} + +/// Returns the packet time in milliseconds (20 by default). +/// + +unsigned int QXmppJinglePayloadType::ptime() const +{ + return m_ptime ? m_ptime : 20; +} + +/// Sets the packet time in milliseconds (20 by default). +/// +/// \param ptime + +void QXmppJinglePayloadType::setPtime(unsigned int ptime) +{ + m_ptime = ptime; +} + +void QXmppJinglePayloadType::parse(const QDomElement &element) +{ + m_id = element.attribute("id").toInt(); + m_name = element.attribute("name"); + m_channels = element.attribute("channels").toInt(); + if (!m_channels) + m_channels = 1; + m_clockrate = element.attribute("clockrate").toInt(); + m_maxptime = element.attribute("maxptime").toInt(); + m_ptime = element.attribute("ptime").toInt(); + + QDomElement child = element.firstChildElement("parameter"); + while (!child.isNull()) { + m_parameters.insert(child.attribute("name"), child.attribute("value")); + child = child.nextSiblingElement("parameter"); + } +} + +void QXmppJinglePayloadType::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("payload-type"); + helperToXmlAddAttribute(writer, "id", QString::number(m_id)); + helperToXmlAddAttribute(writer, "name", m_name); + if (m_channels > 1) + helperToXmlAddAttribute(writer, "channels", QString::number(m_channels)); + if (m_clockrate > 0) + helperToXmlAddAttribute(writer, "clockrate", QString::number(m_clockrate)); + if (m_maxptime > 0) + helperToXmlAddAttribute(writer, "maxptime", QString::number(m_maxptime)); + if (m_ptime > 0) + helperToXmlAddAttribute(writer, "ptime", QString::number(m_ptime)); + + foreach (const QString &key, m_parameters.keys()) { + writer->writeStartElement("parameter"); + writer->writeAttribute("name", key); + writer->writeAttribute("value", m_parameters.value(key)); + writer->writeEndElement(); + } + writer->writeEndElement(); +} + +/// Returns true if this QXmppJinglePayloadType and \a other refer to the same payload type. +/// +/// \param other + +bool QXmppJinglePayloadType::operator==(const QXmppJinglePayloadType &other) const +{ + // FIXME : what to do with m_ptime and m_maxptime? + if (m_id <= 95) + return other.m_id == m_id && other.m_clockrate == m_clockrate; + else + return other.m_channels == m_channels && + other.m_clockrate == m_clockrate && + other.m_name.toLower() == m_name.toLower(); +} diff --git a/src/base/QXmppJingleIq.h b/src/base/QXmppJingleIq.h new file mode 100644 index 00000000..ef5b66ec --- /dev/null +++ b/src/base/QXmppJingleIq.h @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPJINGLEIQ_H +#define QXMPPJINGLEIQ_H + +#include <QHostAddress> + +#include "QXmppIq.h" + +/// \brief The QXmppJinglePayloadType class represents a payload type +/// as specified by XEP-0167: Jingle RTP Sessions and RFC 5245. +/// + +class QXmppJinglePayloadType +{ +public: + QXmppJinglePayloadType(); + + unsigned char channels() const; + void setChannels(unsigned char channels); + + unsigned int clockrate() const; + void setClockrate(unsigned int clockrate); + + unsigned char id() const; + void setId(unsigned char id); + + unsigned int maxptime() const; + void setMaxptime(unsigned int maxptime); + + QString name() const; + void setName(const QString &name); + + QMap<QString, QString> parameters() const; + void setParameters(const QMap<QString, QString> ¶meters); + + unsigned int ptime() const; + void setPtime(unsigned int ptime); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + bool operator==(const QXmppJinglePayloadType &other) const; + +private: + unsigned char m_channels; + unsigned int m_clockrate; + unsigned char m_id; + unsigned int m_maxptime; + QString m_name; + QMap<QString, QString> m_parameters; + unsigned int m_ptime; +}; + +/// \brief The QXmppJingleCandidate class represents a transport candidate +/// as specified by XEP-0176: Jingle ICE-UDP Transport Method. +/// + +class QXmppJingleCandidate +{ +public: + /// This enum is used to describe a candidate's type. + enum Type + { + HostType, ///< Host candidate, a local address/port. + PeerReflexiveType, ///< Peer-reflexive candidate, + ///< the address/port as seen from the peer. + ServerReflexiveType, ///< Server-reflexive candidate, + ///< the address/port as seen by the STUN server + RelayedType, ///< Relayed candidate, a candidate from + ///< a TURN relay. + }; + + QXmppJingleCandidate(); + + int component() const; + void setComponent(int component); + + int foundation() const; + void setFoundation(int foundation); + + QHostAddress host() const; + void setHost(const QHostAddress &host); + + QString id() const; + void setId(const QString &id); + + int network() const; + void setNetwork(int network); + + quint16 port() const; + void setPort(quint16 port); + + int priority() const; + void setPriority(int priority); + + QString protocol() const; + void setProtocol(const QString &protocol); + + QXmppJingleCandidate::Type type() const; + void setType(QXmppJingleCandidate::Type); + + bool isNull() const; + + static QXmppJingleCandidate::Type typeFromString(const QString &typeStr, bool *ok = 0); + static QString typeToString(QXmppJingleCandidate::Type type); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + int m_component; + int m_foundation; + int m_generation; + QHostAddress m_host; + QString m_id; + int m_network; + quint16 m_port; + QString m_protocol; + int m_priority; + QXmppJingleCandidate::Type m_type; +}; + +/// \brief The QXmppJingleIq class represents an IQ used for initiating media +/// sessions as specified by XEP-0166: Jingle. +/// +/// \ingroup Stanzas + +class QXmppJingleIq : public QXmppIq +{ +public: + /// This enum is used to describe a Jingle action. + enum Action { + ContentAccept, + ContentAdd, + ContentModify, + ContentReject, + ContentRemove, + DescriptionInfo, + SecurityInfo, + SessionAccept, + SessionInfo, + SessionInitiate, + SessionTerminate, + TransportAccept, + TransportInfo, + TransportReject, + TransportReplace, + }; + + /// \internal + /// + /// The QXmppJingleIq::Content class represents the "content" element of a + /// QXmppJingleIq. + + class Content + { + public: + Content(); + + QString creator() const; + void setCreator(const QString &creator); + + QString name() const; + void setName(const QString &name); + + QString senders() const; + void setSenders(const QString &senders); + + // XEP-0167: Jingle RTP Sessions + QString descriptionMedia() const; + void setDescriptionMedia(const QString &media); + + void addPayloadType(const QXmppJinglePayloadType &payload); + QList<QXmppJinglePayloadType> payloadTypes() const; + void setPayloadTypes(const QList<QXmppJinglePayloadType> &payloadTypes); + + void addTransportCandidate(const QXmppJingleCandidate &candidate); + QList<QXmppJingleCandidate> transportCandidates() const; + + QString transportUser() const; + void setTransportUser(const QString &user); + + QString transportPassword() const; + void setTransportPassword(const QString &password); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + private: + QString m_creator; + QString m_disposition; + QString m_name; + QString m_senders; + + QString m_descriptionMedia; + QString m_descriptionType; + QString m_transportType; + QString m_transportUser; + QString m_transportPassword; + QList<QXmppJinglePayloadType> m_payloadTypes; + QList<QXmppJingleCandidate> m_transportCandidates; + }; + + /// \internal + /// + /// The QXmppJingleIq::Reason class represents the "reason" element of a + /// QXmppJingleIq. + + class Reason + { + public: + enum Type { + None, + AlternativeSession, + Busy, + Cancel, + ConnectivityError, + Decline, + Expired, + FailedApplication, + FailedTransport, + GeneralError, + Gone, + IncompatibleParameters, + MediaError, + SecurityError, + Success, + Timeout, + UnsupportedApplications, + UnsupportedTransports, + }; + + Reason(); + + QString text() const; + void setText(const QString &text); + + Type type() const; + void setType(Type type); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + private: + QString m_text; + Type m_type; + }; + + QXmppJingleIq(); + + Action action() const; + void setAction(Action action); + + QString initiator() const; + void setInitiator(const QString &initiator); + + QString responder() const; + void setResponder(const QString &responder); + + QString sid() const; + void setSid(const QString &sid); + + /// Returns a reference to the IQ's content element. + Content& content() { return m_content; }; + + /// Returns a const reference to the IQ's content element. + const Content& content() const { return m_content; }; + + /// Returns a reference to the IQ's reason element. + Reason& reason() { return m_reason; }; + + /// Returns a const reference to the IQ's reason element. + const Reason& reason() const { return m_reason; }; + + // XEP-0167: Jingle RTP Sessions + bool ringing() const; + void setRinging(bool ringing); + + /// \cond + static bool isJingleIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + Action m_action; + QString m_initiator; + QString m_responder; + QString m_sid; + + Content m_content; + Reason m_reason; + bool m_ringing; +}; + +#endif diff --git a/src/base/QXmppLogger.cpp b/src/base/QXmppLogger.cpp new file mode 100644 index 00000000..107f72d9 --- /dev/null +++ b/src/base/QXmppLogger.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <iostream> + +#include <QChildEvent> +#include <QDateTime> +#include <QFile> +#include <QMetaType> +#include <QTextStream> + +#include "QXmppLogger.h" + +QXmppLogger* QXmppLogger::m_logger = 0; + +static const char *typeName(QXmppLogger::MessageType type) +{ + switch (type) + { + case QXmppLogger::DebugMessage: + return "DEBUG"; + case QXmppLogger::InformationMessage: + return "INFO"; + case QXmppLogger::WarningMessage: + return "WARNING"; + case QXmppLogger::ReceivedMessage: + return "RECEIVED"; + case QXmppLogger::SentMessage: + return "SENT"; + default: + return ""; + } +} + +static QString formatted(QXmppLogger::MessageType type, const QString& text) +{ + return QDateTime::currentDateTime().toString() + " " + + QString::fromLatin1(typeName(type)) + " " + + text; +} + +/// Constructs a new QXmppLoggable. +/// +/// \param parent + +QXmppLoggable::QXmppLoggable(QObject *parent) + : QObject(parent) +{ + QXmppLoggable *logParent = qobject_cast<QXmppLoggable*>(parent); + if (logParent) { + connect(this, SIGNAL(logMessage(QXmppLogger::MessageType,QString)), + logParent, SIGNAL(logMessage(QXmppLogger::MessageType,QString))); + } +} + +void QXmppLoggable::childEvent(QChildEvent *event) +{ + QXmppLoggable *child = qobject_cast<QXmppLoggable*>(event->child()); + if (!child) + return; + + if (event->added()) { + connect(child, SIGNAL(logMessage(QXmppLogger::MessageType,QString)), + this, SIGNAL(logMessage(QXmppLogger::MessageType,QString))); + } else if (event->removed()) { + disconnect(child, SIGNAL(logMessage(QXmppLogger::MessageType,QString)), + this, SIGNAL(logMessage(QXmppLogger::MessageType,QString))); + } +} + +class QXmppLoggerPrivate +{ +public: + QXmppLoggerPrivate(QXmppLogger *qq); + + QXmppLogger::LoggingType loggingType; + QFile *logFile; + QString logFilePath; + QXmppLogger::MessageTypes messageTypes; + +private: + QXmppLogger *q; +}; + +QXmppLoggerPrivate::QXmppLoggerPrivate(QXmppLogger *qq) + : loggingType(QXmppLogger::NoLogging), + logFile(0), + logFilePath("QXmppClientLog.log"), + messageTypes(QXmppLogger::AnyMessage), + q(qq) +{ +} + +/// Constructs a new QXmppLogger. +/// +/// \param parent + +QXmppLogger::QXmppLogger(QObject *parent) + : QObject(parent) +{ + d = new QXmppLoggerPrivate(this); + + // make it possible to pass QXmppLogger::MessageType between threads + qRegisterMetaType< QXmppLogger::MessageType >("QXmppLogger::MessageType"); +} + +QXmppLogger::~QXmppLogger() +{ + delete d; +} + +/// Returns the default logger. +/// + +QXmppLogger* QXmppLogger::getLogger() +{ + if(!m_logger) + m_logger = new QXmppLogger(); + + return m_logger; +} + +/// Returns the handler for logging messages. +/// + +QXmppLogger::LoggingType QXmppLogger::loggingType() +{ + return d->loggingType; +} + +/// Sets the handler for logging messages. +/// +/// \param type + +void QXmppLogger::setLoggingType(QXmppLogger::LoggingType type) +{ + if (d->loggingType != type) { + d->loggingType = type; + reopen(); + } +} + +/// Returns the types of messages to log. +/// + +QXmppLogger::MessageTypes QXmppLogger::messageTypes() +{ + return d->messageTypes; +} + +/// Sets the types of messages to log. +/// +/// \param types + +void QXmppLogger::setMessageTypes(QXmppLogger::MessageTypes types) +{ + d->messageTypes = types; +} + +/// Add a logging message. +/// +/// \param type +/// \param text + +void QXmppLogger::log(QXmppLogger::MessageType type, const QString& text) +{ + // filter messages + if (!d->messageTypes.testFlag(type)) + return; + + switch(d->loggingType) + { + case QXmppLogger::FileLogging: + if (!d->logFile) { + d->logFile = new QFile(d->logFilePath); + d->logFile->open(QIODevice::WriteOnly | QIODevice::Append); + } + QTextStream(d->logFile) << formatted(type, text) << "\n"; + break; + case QXmppLogger::StdoutLogging: + std::cout << qPrintable(formatted(type, text)) << std::endl; + break; + case QXmppLogger::SignalLogging: + emit message(type, text); + break; + default: + break; + } +} + +/// Returns the path to which logging messages should be written. +/// +/// \sa loggingType() + +QString QXmppLogger::logFilePath() +{ + return d->logFilePath; +} + +/// Sets the path to which logging messages should be written. +/// +/// \param path +/// +/// \sa setLoggingType() + +void QXmppLogger::setLogFilePath(const QString &path) +{ + if (d->logFilePath != path) { + d->logFilePath = path; + reopen(); + } +} + +/// If logging to a file, causes the file to be re-opened. +/// + +void QXmppLogger::reopen() +{ + if (d->logFile) { + delete d->logFile; + d->logFile = 0; + } +} + diff --git a/src/base/QXmppLogger.h b/src/base/QXmppLogger.h new file mode 100644 index 00000000..93de33f7 --- /dev/null +++ b/src/base/QXmppLogger.h @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPLOGGER_H +#define QXMPPLOGGER_H + +#include <QObject> + +#ifdef QXMPP_LOGGABLE_TRACE +#define qxmpp_loggable_trace(x) QString("%1(0x%2) %3").arg(metaObject()->className(), QString::number(reinterpret_cast<qint64>(this), 16), x) +#else +#define qxmpp_loggable_trace(x) (x) +#endif + +class QXmppLoggerPrivate; + +/// \brief The QXmppLogger class represents a sink for logging messages. +/// +/// \ingroup Core + +class QXmppLogger : public QObject +{ + Q_OBJECT + Q_ENUMS(LoggingType) + Q_FLAGS(MessageType MessageTypes) + Q_PROPERTY(QString logFilePath READ logFilePath WRITE setLogFilePath) + Q_PROPERTY(LoggingType loggingType READ loggingType WRITE setLoggingType) + Q_PROPERTY(MessageTypes messageTypes READ messageTypes WRITE setMessageTypes) + +public: + /// This enum describes how log message are handled. + enum LoggingType + { + NoLogging = 0, ///< Log messages are discarded + FileLogging = 1, ///< Log messages are written to a file + StdoutLogging = 2, ///< Log messages are written to the standard output + SignalLogging = 4, ///< Log messages are emitted as a signal + }; + + /// This enum describes a type of log message. + enum MessageType + { + NoMessage = 0, ///< No message type + DebugMessage = 1, ///< Debugging message + InformationMessage = 2, ///< Informational message + WarningMessage = 4, ///< Warning message + ReceivedMessage = 8, ///< Message received from server + SentMessage = 16, ///< Message sent to server + AnyMessage = 31, ///< Any message type + }; + Q_DECLARE_FLAGS(MessageTypes, MessageType) + + QXmppLogger(QObject *parent = 0); + ~QXmppLogger(); + + static QXmppLogger* getLogger(); + + QXmppLogger::LoggingType loggingType(); + void setLoggingType(QXmppLogger::LoggingType type); + + QString logFilePath(); + void setLogFilePath(const QString &path); + + QXmppLogger::MessageTypes messageTypes(); + void setMessageTypes(QXmppLogger::MessageTypes types); + +public slots: + void log(QXmppLogger::MessageType type, const QString& text); + void reopen(); + +signals: + /// This signal is emitted whenever a log message is received. + void message(QXmppLogger::MessageType type, const QString &text); + +private: + static QXmppLogger* m_logger; + QXmppLoggerPrivate *d; +}; + +/// \brief The QXmppLoggable class represents a source of logging messages. +/// +/// \ingroup Core + +class QXmppLoggable : public QObject +{ + Q_OBJECT + +public: + QXmppLoggable(QObject *parent = 0); + +protected: + /// \cond + virtual void childEvent(QChildEvent *event); + /// \endcond + + /// Logs a debugging message. + /// + /// \param message + + void debug(const QString &message) + { + emit logMessage(QXmppLogger::DebugMessage, qxmpp_loggable_trace(message)); + } + + /// Logs an informational message. + /// + /// \param message + + void info(const QString &message) + { + emit logMessage(QXmppLogger::InformationMessage, qxmpp_loggable_trace(message)); + } + + /// Logs a warning message. + /// + /// \param message + + void warning(const QString &message) + { + emit logMessage(QXmppLogger::WarningMessage, qxmpp_loggable_trace(message)); + } + + /// Logs a received packet. + /// + /// \param message + + void logReceived(const QString &message) + { + emit logMessage(QXmppLogger::ReceivedMessage, qxmpp_loggable_trace(message)); + } + + /// Logs a sent packet. + /// + /// \param message + + void logSent(const QString &message) + { + emit logMessage(QXmppLogger::SentMessage, qxmpp_loggable_trace(message)); + } + +signals: + /// This signal is emitted to send logging messages. + void logMessage(QXmppLogger::MessageType type, const QString &msg); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QXmppLogger::MessageTypes) +#endif // QXMPPLOGGER_H diff --git a/src/base/QXmppMessage.cpp b/src/base/QXmppMessage.cpp new file mode 100644 index 00000000..109c0d84 --- /dev/null +++ b/src/base/QXmppMessage.cpp @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> +#include <QXmlStreamWriter> + +#include "QXmppConstants.h" +#include "QXmppMessage.h" +#include "QXmppUtils.h" + +static const char* chat_states[] = { + "", + "active", + "inactive", + "gone", + "composing", + "paused", +}; + +/// Constructs a QXmppMessage. +/// +/// \param from +/// \param to +/// \param body +/// \param thread + +QXmppMessage::QXmppMessage(const QString& from, const QString& to, const + QString& body, const QString& thread) + : QXmppStanza(from, to), + m_type(Chat), + m_stampType(QXmppMessage::DelayedDelivery), + m_state(None), + m_attentionRequested(false), + m_body(body), + m_thread(thread), + m_receiptRequested(false) +{ +} + +QXmppMessage::~QXmppMessage() +{ + +} + +/// Returns the message's body. +/// + +QString QXmppMessage::body() const +{ + return m_body; +} + +/// Sets the message's body. +/// +/// \param body + +void QXmppMessage::setBody(const QString& body) +{ + m_body = body; +} + +/// Returns true if the user's attention is requested, as defined +/// by XEP-0224: Attention. + +bool QXmppMessage::isAttentionRequested() const +{ + return m_attentionRequested; +} + +/// Sets whether the user's attention is requested, as defined +/// by XEP-0224: Attention. +/// +/// \a param requested + +void QXmppMessage::setAttentionRequested(bool requested) +{ + m_attentionRequested = requested; +} + +/// Returns true if a delivery receipt is requested, as defined +/// by XEP-0184: Message Delivery Receipts. + +bool QXmppMessage::isReceiptRequested() const +{ + return m_receiptRequested; +} + +/// Sets whether a delivery receipt is requested, as defined +/// by XEP-0184: Message Delivery Receipts. +/// +/// \a param requested + +void QXmppMessage::setReceiptRequested(bool requested) +{ + m_receiptRequested = requested; + if (requested && id().isEmpty()) + generateAndSetNextId(); +} + +/// If this message is a delivery receipt, returns the ID of the +/// original message. + +QString QXmppMessage::receiptId() const +{ + return m_receiptId; +} + +/// Make this message a delivery receipt for the message with +/// the given \a id. + +void QXmppMessage::setReceiptId(const QString &id) +{ + m_receiptId = id; +} + +/// Returns the message's type. +/// + +QXmppMessage::Type QXmppMessage::type() const +{ + return m_type; +} + +QString QXmppMessage::getTypeStr() const +{ + switch(m_type) + { + case QXmppMessage::Error: + return "error"; + case QXmppMessage::Normal: + return "normal"; + case QXmppMessage::Chat: + return "chat"; + case QXmppMessage::GroupChat: + return "groupchat"; + case QXmppMessage::Headline: + return "headline"; + default: + qWarning("QXmppMessage::getTypeStr() invalid type %d", (int)m_type); + return ""; + } +} + +/// Sets the message's type. +/// +/// \param type + +void QXmppMessage::setType(QXmppMessage::Type type) +{ + m_type = type; +} + +void QXmppMessage::setTypeFromStr(const QString& str) +{ + if(str == "error") + { + setType(QXmppMessage::Error); + return; + } + else if(str == "") // if no type is specified + { + setType(QXmppMessage::Normal); + return; + } + else if(str == "normal") + { + setType(QXmppMessage::Normal); + return; + } + else if(str == "chat") + { + setType(QXmppMessage::Chat); + return; + } + else if(str == "groupchat") + { + setType(QXmppMessage::GroupChat); + return; + } + else if(str == "headline") + { + setType(QXmppMessage::Headline); + return; + } + else + { + setType(static_cast<QXmppMessage::Type>(-1)); + qWarning("QXmppMessage::setTypeFromStr() invalid input string type: %s", + qPrintable(str)); + return; + } +} + +/// Returns the message's timestamp (if any). + +QDateTime QXmppMessage::stamp() const +{ + return m_stamp; +} + +/// Sets the message's timestamp. +/// +/// \param stamp + +void QXmppMessage::setStamp(const QDateTime &stamp) +{ + m_stamp = stamp; +} + +/// Returns the message's chat state. +/// + +QXmppMessage::State QXmppMessage::state() const +{ + return m_state; +} + +/// Sets the message's chat state. +/// +/// \param state + +void QXmppMessage::setState(QXmppMessage::State state) +{ + m_state = state; +} + +void QXmppMessage::parse(const QDomElement &element) +{ + QXmppStanza::parse(element); + + setTypeFromStr(element.attribute("type")); + m_body = element.firstChildElement("body").text(); + m_subject = element.firstChildElement("subject").text(); + m_thread = element.firstChildElement("thread").text(); + + // chat states + for (int i = Active; i <= Paused; i++) + { + QDomElement stateElement = element.firstChildElement(chat_states[i]); + if (!stateElement.isNull() && + stateElement.namespaceURI() == ns_chat_states) + { + m_state = static_cast<QXmppMessage::State>(i); + break; + } + } + + // XEP-0184: Message Delivery Receipts + QDomElement receivedElement = element.firstChildElement("received"); + if (!receivedElement.isNull() && receivedElement.namespaceURI() == ns_message_receipts) { + m_receiptId = receivedElement.attribute("id"); + + // compatibility with old-style XEP + if (m_receiptId.isEmpty()) + m_receiptId = id(); + } else { + m_receiptId = QString(); + } + m_receiptRequested = element.firstChildElement("request").namespaceURI() == ns_message_receipts; + + // XEP-0203: Delayed Delivery + QDomElement delayElement = element.firstChildElement("delay"); + if (!delayElement.isNull() && delayElement.namespaceURI() == ns_delayed_delivery) + { + const QString str = delayElement.attribute("stamp"); + m_stamp = datetimeFromString(str); + m_stampType = QXmppMessage::DelayedDelivery; + } + + // XEP-0224: Attention + m_attentionRequested = element.firstChildElement("attention").namespaceURI() == ns_attention; + + QXmppElementList extensions; + QDomElement xElement = element.firstChildElement("x"); + while (!xElement.isNull()) + { + if (xElement.namespaceURI() == ns_legacy_delayed_delivery) + { + // XEP-0091: Legacy Delayed Delivery + const QString str = xElement.attribute("stamp"); + m_stamp = QDateTime::fromString(str, "yyyyMMddThh:mm:ss"); + m_stamp.setTimeSpec(Qt::UTC); + m_stampType = QXmppMessage::LegacyDelayedDelivery; + } else { + // other extensions + extensions << QXmppElement(xElement); + } + xElement = xElement.nextSiblingElement("x"); + } + setExtensions(extensions); +} + +void QXmppMessage::toXml(QXmlStreamWriter *xmlWriter) const +{ + + xmlWriter->writeStartElement("message"); + helperToXmlAddAttribute(xmlWriter, "xml:lang", lang()); + helperToXmlAddAttribute(xmlWriter, "id", id()); + helperToXmlAddAttribute(xmlWriter, "to", to()); + helperToXmlAddAttribute(xmlWriter, "from", from()); + helperToXmlAddAttribute(xmlWriter, "type", getTypeStr()); + if (!m_subject.isEmpty()) + helperToXmlAddTextElement(xmlWriter, "subject", m_subject); + if (!m_body.isEmpty()) + helperToXmlAddTextElement(xmlWriter, "body", m_body); + if (!m_thread.isEmpty()) + helperToXmlAddTextElement(xmlWriter, "thread", m_thread); + error().toXml(xmlWriter); + + // chat states + if (m_state > None && m_state <= Paused) + { + xmlWriter->writeStartElement(chat_states[m_state]); + xmlWriter->writeAttribute("xmlns", ns_chat_states); + xmlWriter->writeEndElement(); + } + + // time stamp + if (m_stamp.isValid()) + { + QDateTime utcStamp = m_stamp.toUTC(); + if (m_stampType == QXmppMessage::DelayedDelivery) + { + // XEP-0203: Delayed Delivery + xmlWriter->writeStartElement("delay"); + xmlWriter->writeAttribute("xmlns", ns_delayed_delivery); + helperToXmlAddAttribute(xmlWriter, "stamp", datetimeToString(utcStamp)); + xmlWriter->writeEndElement(); + } else { + // XEP-0091: Legacy Delayed Delivery + xmlWriter->writeStartElement("x"); + xmlWriter->writeAttribute("xmlns", ns_legacy_delayed_delivery); + helperToXmlAddAttribute(xmlWriter, "stamp", utcStamp.toString("yyyyMMddThh:mm:ss")); + xmlWriter->writeEndElement(); + } + } + + // XEP-0184: Message Delivery Receipts + if (!m_receiptId.isEmpty()) { + xmlWriter->writeStartElement("received"); + xmlWriter->writeAttribute("xmlns", ns_message_receipts); + xmlWriter->writeAttribute("id", m_receiptId); + xmlWriter->writeEndElement(); + } + if (m_receiptRequested) { + xmlWriter->writeStartElement("request"); + xmlWriter->writeAttribute("xmlns", ns_message_receipts); + xmlWriter->writeEndElement(); + } + + // XEP-0224: Attention + if (m_attentionRequested) { + xmlWriter->writeStartElement("attention"); + xmlWriter->writeAttribute("xmlns", ns_attention); + xmlWriter->writeEndElement(); + } + + // other extensions + foreach (const QXmppElement &extension, extensions()) + extension.toXml(xmlWriter); + xmlWriter->writeEndElement(); +} + +/// Returns the message's subject. +/// + +QString QXmppMessage::subject() const +{ + return m_subject; +} + +/// Sets the message's subject. +/// +/// \param subject + +void QXmppMessage::setSubject(const QString& subject) +{ + m_subject = subject; +} + +/// Returns the message's thread. + +QString QXmppMessage::thread() const +{ + return m_thread; +} + +/// Sets the message's thread. +/// +/// \param thread + +void QXmppMessage::setThread(const QString& thread) +{ + m_thread = thread; +} + diff --git a/src/base/QXmppMessage.h b/src/base/QXmppMessage.h new file mode 100644 index 00000000..a981f1db --- /dev/null +++ b/src/base/QXmppMessage.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPMESSAGE_H +#define QXMPPMESSAGE_H + +#include <QDateTime> +#include "QXmppStanza.h" + +/// \brief The QXmppMessage class represents an XMPP message. +/// +/// \ingroup Stanzas +/// + +class QXmppMessage : public QXmppStanza +{ +public: + /// This enum described a message type. + enum Type + { + Error = 0, + Normal, + Chat, + GroupChat, + Headline + }; + + /// This enum describes a chat state as defined by + /// XEP-0085 : Chat State Notifications. + enum State + { + None = 0, ///< The message does not contain any chat state information. + Active, ///< User is actively participating in the chat session. + Inactive, ///< User has not been actively participating in the chat session. + Gone, ///< User has effectively ended their participation in the chat session. + Composing, ///< User is composing a message. + Paused, ///< User had been composing but now has stopped. + }; + + QXmppMessage(const QString& from = "", const QString& to = "", + const QString& body = "", const QString& thread = ""); + ~QXmppMessage(); + + QString body() const; + void setBody(const QString&); + + bool isAttentionRequested() const; + void setAttentionRequested(bool requested); + + bool isReceiptRequested() const; + void setReceiptRequested(bool requested); + + QString receiptId() const; + void setReceiptId(const QString &id); + + QDateTime stamp() const; + void setStamp(const QDateTime &stamp); + + QXmppMessage::State state() const; + void setState(QXmppMessage::State); + + QString subject() const; + void setSubject(const QString&); + + QString thread() const; + void setThread(const QString&); + + QXmppMessage::Type type() const; + void setType(QXmppMessage::Type); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + /// This enum describe a type of message timestamp. + enum StampType + { + LegacyDelayedDelivery, ///< XEP-0091: Legacy Delayed Delivery + DelayedDelivery, ///< XEP-0203: Delayed Delivery + }; + + QString getTypeStr() const; + void setTypeFromStr(const QString&); + + Type m_type; + QDateTime m_stamp; + StampType m_stampType; + State m_state; + + bool m_attentionRequested; + QString m_body; + QString m_subject; + QString m_thread; + + // Request message receipt as per XEP-0184. + QString m_receiptId; + bool m_receiptRequested; +}; + +#endif // QXMPPMESSAGE_H diff --git a/src/base/QXmppMucIq.cpp b/src/base/QXmppMucIq.cpp new file mode 100644 index 00000000..97328a2f --- /dev/null +++ b/src/base/QXmppMucIq.cpp @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> + +#include "QXmppConstants.h" +#include "QXmppMucIq.h" +#include "QXmppUtils.h" + +QXmppMucItem::QXmppMucItem() + : m_affiliation(QXmppMucItem::UnspecifiedAffiliation), + m_role(QXmppMucItem::UnspecifiedRole) +{ +} + +bool QXmppMucItem::isNull() const +{ + return m_actor.isEmpty() && + m_affiliation == UnspecifiedAffiliation && + m_jid.isEmpty() && + m_nick.isEmpty() && + m_reason.isEmpty() && + m_role == UnspecifiedRole; +} + +QString QXmppMucItem::actor() const +{ + return m_actor; +} + +void QXmppMucItem::setActor(const QString &actor) +{ + m_actor = actor; +} + +/// Returns the user's affiliation, i.e. long-lived permissions. + +QXmppMucItem::Affiliation QXmppMucItem::affiliation() const +{ + return m_affiliation; +} + +QXmppMucItem::Affiliation QXmppMucItem::affiliationFromString(const QString &affiliationStr) +{ + if (affiliationStr == "owner") + return QXmppMucItem::OwnerAffiliation; + else if (affiliationStr == "admin") + return QXmppMucItem::AdminAffiliation; + else if (affiliationStr == "member") + return QXmppMucItem::MemberAffiliation; + else if (affiliationStr == "outcast") + return QXmppMucItem::OutcastAffiliation; + else if (affiliationStr == "none") + return QXmppMucItem::NoAffiliation; + else + return QXmppMucItem::UnspecifiedAffiliation; +} + +QString QXmppMucItem::affiliationToString(Affiliation affiliation) +{ + switch (affiliation) { + case QXmppMucItem::OwnerAffiliation: + return "owner"; + case QXmppMucItem::AdminAffiliation: + return "admin"; + case QXmppMucItem::MemberAffiliation: + return "member"; + case QXmppMucItem::OutcastAffiliation: + return "outcast"; + case QXmppMucItem::NoAffiliation: + return "none"; + default: + return QString(); + } +} + +/// Sets the user's affiliation, i.e. long-lived permissions. +/// +/// \param affiliation + +void QXmppMucItem::setAffiliation(Affiliation affiliation) +{ + m_affiliation = affiliation; +} + +/// Returns the user's real JID. + +QString QXmppMucItem::jid() const +{ + return m_jid; +} + +/// Sets the user's real JID. +/// +/// \param jid + +void QXmppMucItem::setJid(const QString &jid) +{ + m_jid = jid; +} + +/// Returns the user's nickname. + +QString QXmppMucItem::nick() const +{ + return m_nick; +} + +/// Sets the user's nickname. +/// +/// \param nick + +void QXmppMucItem::setNick(const QString &nick) +{ + m_nick = nick; +} + +QString QXmppMucItem::reason() const +{ + return m_reason; +} + +void QXmppMucItem::setReason(const QString &reason) +{ + m_reason = reason; +} + +/// Returns the user's role, i.e. short-lived permissions. + +QXmppMucItem::Role QXmppMucItem::role() const +{ + return m_role; +} + +QXmppMucItem::Role QXmppMucItem::roleFromString(const QString &roleStr) +{ + if (roleStr == "moderator") + return QXmppMucItem::ModeratorRole; + else if (roleStr == "participant") + return QXmppMucItem::ParticipantRole; + else if (roleStr == "visitor") + return QXmppMucItem::VisitorRole; + else if (roleStr == "none") + return QXmppMucItem::NoRole; + else + return QXmppMucItem::UnspecifiedRole; +} + +QString QXmppMucItem::roleToString(Role role) +{ + switch (role) { + case QXmppMucItem::ModeratorRole: + return "moderator"; + case QXmppMucItem::ParticipantRole: + return "participant"; + case QXmppMucItem::VisitorRole: + return "visitor"; + case QXmppMucItem::NoRole: + return "none"; + default: + return QString(); + } +} + +/// Sets the user's role, i.e. short-lived permissions. +/// +/// \param role + +void QXmppMucItem::setRole(Role role) +{ + m_role = role; +} + +void QXmppMucItem::parse(const QDomElement &element) +{ + m_affiliation = QXmppMucItem::affiliationFromString(element.attribute("affiliation").toLower()); + m_jid = element.attribute("jid"); + m_nick = element.attribute("nick"); + m_role = QXmppMucItem::roleFromString(element.attribute("role").toLower()); + m_actor = element.firstChildElement("actor").attribute("jid"); + m_reason = element.firstChildElement("reason").text(); +} + +void QXmppMucItem::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("item"); + helperToXmlAddAttribute(writer, "affiliation", affiliationToString(m_affiliation)); + helperToXmlAddAttribute(writer, "jid", m_jid); + helperToXmlAddAttribute(writer, "nick", m_nick); + helperToXmlAddAttribute(writer, "role", roleToString(m_role)); + if (!m_actor.isEmpty()) { + writer->writeStartElement("actor"); + helperToXmlAddAttribute(writer, "jid", m_actor); + writer->writeEndElement(); + } + if (!m_reason.isEmpty()) + helperToXmlAddTextElement(writer, "reason", m_reason); + writer->writeEndElement(); +} + +/// Returns the IQ's items. + +QList<QXmppMucItem> QXmppMucAdminIq::items() const +{ + return m_items; +} + +/// Sets the IQ's items. +/// +/// \param items + +void QXmppMucAdminIq::setItems(const QList<QXmppMucItem> &items) +{ + m_items = items; +} + +bool QXmppMucAdminIq::isMucAdminIq(const QDomElement &element) +{ + QDomElement queryElement = element.firstChildElement("query"); + return (queryElement.namespaceURI() == ns_muc_admin); +} + +void QXmppMucAdminIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement queryElement = element.firstChildElement("query"); + QDomElement child = queryElement.firstChildElement("item"); + while (!child.isNull()) + { + QXmppMucItem item; + item.parse(child); + m_items << item; + child = child.nextSiblingElement("item"); + } +} + +void QXmppMucAdminIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("query"); + writer->writeAttribute("xmlns", ns_muc_admin); + foreach (const QXmppMucItem &item, m_items) + item.toXml(writer); + writer->writeEndElement(); +} + +/// Returns the IQ's data form. + +QXmppDataForm QXmppMucOwnerIq::form() const +{ + return m_form; +} + +/// Sets the IQ's data form. +/// +/// \param form + +void QXmppMucOwnerIq::setForm(const QXmppDataForm &form) +{ + m_form = form; +} + +bool QXmppMucOwnerIq::isMucOwnerIq(const QDomElement &element) +{ + QDomElement queryElement = element.firstChildElement("query"); + return (queryElement.namespaceURI() == ns_muc_owner); +} + +void QXmppMucOwnerIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement queryElement = element.firstChildElement("query"); + m_form.parse(queryElement.firstChildElement("x")); +} + +void QXmppMucOwnerIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("query"); + writer->writeAttribute("xmlns", ns_muc_owner); + m_form.toXml(writer); + writer->writeEndElement(); +} + diff --git a/src/base/QXmppMucIq.h b/src/base/QXmppMucIq.h new file mode 100644 index 00000000..5e73fc9b --- /dev/null +++ b/src/base/QXmppMucIq.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPMUCIQ_H +#define QXMPPMUCIQ_H + +#include "QXmppDataForm.h" +#include "QXmppIq.h" + +/// \brief The QXmppMucItem class represents a chat room "item". +/// +/// It is used to convey information such as permissions. +/// +/// \ingroup Stanzas + +class QXmppMucItem +{ +public: + /// This enum is used to represent long-lived permissions in a room (affiliations). + enum Affiliation { + UnspecifiedAffiliation, + OutcastAffiliation, + NoAffiliation, + MemberAffiliation, + AdminAffiliation, + OwnerAffiliation, + }; + + /// This enum is used to represent short-lived permissions in a room (roles). + enum Role { + UnspecifiedRole, + NoRole, + VisitorRole, + ParticipantRole, + ModeratorRole, + }; + + QXmppMucItem(); + bool isNull() const; + + QString actor() const; + void setActor(const QString &actor); + + Affiliation affiliation() const; + void setAffiliation(Affiliation affiliation); + + QString jid() const; + void setJid(const QString &jid); + + QString nick() const; + void setNick(const QString &nick); + + QString reason() const; + void setReason(const QString &reason); + + Role role() const; + void setRole(Role role); + + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + + /// \cond + static Affiliation affiliationFromString(const QString &affiliationStr); + static QString affiliationToString(Affiliation affiliation); + static Role roleFromString(const QString &roleStr); + static QString roleToString(Role role); + /// \endcond +private: + QString m_actor; + Affiliation m_affiliation; + QString m_jid; + QString m_nick; + QString m_reason; + Role m_role; +}; + +/// \brief The QXmppMucAdminIq class represents a chat room administration IQ +/// as defined by XEP-0045: Multi-User Chat. +/// +/// It is used to get or modify room memberships. +/// +/// \ingroup Stanzas + +class QXmppMucAdminIq : public QXmppIq +{ +public: + QList<QXmppMucItem> items() const; + void setItems(const QList<QXmppMucItem> &items); + + /// \cond + static bool isMucAdminIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QList<QXmppMucItem> m_items; +}; + +/// \brief The QXmppMucOwnerIq class represents a chat room configuration IQ as +/// defined by XEP-0045: Multi-User Chat. +/// +/// It is used to get or modify room configuration options. +/// +/// \sa QXmppDataForm +/// + +class QXmppMucOwnerIq : public QXmppIq +{ +public: + QXmppDataForm form() const; + void setForm(const QXmppDataForm &form); + + /// \cond + static bool isMucOwnerIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QXmppDataForm m_form; +}; + +#endif diff --git a/src/base/QXmppNonSASLAuth.cpp b/src/base/QXmppNonSASLAuth.cpp new file mode 100644 index 00000000..0dea6184 --- /dev/null +++ b/src/base/QXmppNonSASLAuth.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QCryptographicHash> +#include <QDomElement> +#include <QXmlStreamWriter> + +#include "QXmppConstants.h" +#include "QXmppNonSASLAuth.h" +#include "QXmppUtils.h" + +QXmppNonSASLAuthIq::QXmppNonSASLAuthIq() + : QXmppIq(QXmppIq::Set) +{ +} + +bool QXmppNonSASLAuthIq::isNonSASLAuthIq(const QDomElement &element) +{ + QDomElement queryElement = element.firstChildElement("query"); + return queryElement.namespaceURI() == ns_auth; +} + +void QXmppNonSASLAuthIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement queryElement = element.firstChildElement("query"); + m_username = queryElement.firstChildElement("username").text(); + m_password = queryElement.firstChildElement("password").text(); + m_digest = QByteArray::fromHex(queryElement.firstChildElement("digest").text().toAscii()); + m_resource = queryElement.firstChildElement("resource").text(); +} + +void QXmppNonSASLAuthIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("query"); + writer->writeAttribute("xmlns", ns_auth); + if (!m_username.isEmpty()) + writer->writeTextElement("username", m_username); + if (!m_digest.isEmpty()) + writer->writeTextElement("digest", m_digest.toHex()); + if (!m_password.isEmpty()) + writer->writeTextElement("password", m_password); + if (!m_resource.isEmpty()) + writer->writeTextElement("resource", m_resource); + writer->writeEndElement(); +} + +QString QXmppNonSASLAuthIq::username() const +{ + return m_username; +} + +void QXmppNonSASLAuthIq::setUsername( const QString &username ) +{ + m_username = username; +} + +QByteArray QXmppNonSASLAuthIq::digest() const +{ + return m_digest; +} + +void QXmppNonSASLAuthIq::setDigest(const QString &streamId, const QString &password) +{ + m_digest = QCryptographicHash::hash(streamId.toUtf8() + password.toUtf8(), QCryptographicHash::Sha1); +} + +QString QXmppNonSASLAuthIq::password() const +{ + return m_password; +} + +void QXmppNonSASLAuthIq::setPassword( const QString &password ) +{ + m_password = password; +} + +QString QXmppNonSASLAuthIq::resource() const +{ + return m_resource; +} + +void QXmppNonSASLAuthIq::setResource(const QString &resource) +{ + m_resource = resource; +} + diff --git a/src/base/QXmppNonSASLAuth.h b/src/base/QXmppNonSASLAuth.h new file mode 100644 index 00000000..a17436e7 --- /dev/null +++ b/src/base/QXmppNonSASLAuth.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXmppNonSASLAuth_H +#define QXmppNonSASLAuth_H + +#include "QXmppIq.h" + +class QXmppNonSASLAuthIq : public QXmppIq +{ +public: + QXmppNonSASLAuthIq(); + + QString username() const; + void setUsername(const QString &username); + + QByteArray digest() const; + void setDigest(const QString &streamId, const QString &password); + + QString password() const; + void setPassword(const QString &password); + + QString resource() const; + void setResource(const QString &resource); + + static bool isNonSASLAuthIq(const QDomElement &element); + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QString m_username; + QByteArray m_digest; + QString m_password; + QString m_resource; +}; + +#endif // QXmppNonSASLAuth_H diff --git a/src/base/QXmppPacket.cpp b/src/base/QXmppPacket.cpp new file mode 100644 index 00000000..1d284325 --- /dev/null +++ b/src/base/QXmppPacket.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#include "QXmppPacket.h" + +QXmppPacket::QXmppPacket() +{ +} + +QXmppPacket::~QXmppPacket() +{ +} + diff --git a/src/base/QXmppPacket.h b/src/base/QXmppPacket.h new file mode 100644 index 00000000..427217c7 --- /dev/null +++ b/src/base/QXmppPacket.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPPACKET_H +#define QXMPPPACKET_H + +#include <QByteArray> + +// forward declarations of QXmlStream* classes will not work on Mac, we need to +// include the whole header. +// See http://lists.trolltech.com/qt-interest/2008-07/thread00798-0.html +// for an explanation. +#include <QXmlStreamWriter> + +class QDomElement; + +class QXmppPacket +{ +public: + QXmppPacket(); + virtual ~QXmppPacket(); + + virtual void parse(const QDomElement &element) = 0; + virtual void toXml( QXmlStreamWriter *writer ) const = 0; +}; + +#endif // QXMPPPACKET_H diff --git a/src/base/QXmppPingIq.cpp b/src/base/QXmppPingIq.cpp new file mode 100644 index 00000000..7058c6d0 --- /dev/null +++ b/src/base/QXmppPingIq.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include "QXmppConstants.h" +#include "QXmppPingIq.h" +#include "QXmppUtils.h" + +#include <QDomElement> + +QXmppPingIq::QXmppPingIq() : QXmppIq(QXmppIq::Get) +{ +} + +bool QXmppPingIq::isPingIq(const QDomElement &element) +{ + QDomElement pingElement = element.firstChildElement("ping"); + return (element.attribute("type") == "get" && + pingElement.namespaceURI() == ns_ping); +} + +void QXmppPingIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("ping"); + writer->writeAttribute("xmlns", ns_ping); + writer->writeEndElement(); +} + diff --git a/src/base/QXmppPingIq.h b/src/base/QXmppPingIq.h new file mode 100644 index 00000000..527eeb68 --- /dev/null +++ b/src/base/QXmppPingIq.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPPINGIQ_H +#define QXMPPPINGIQ_H + +#include "QXmppIq.h" + +class QXmlStreamWriter; +class QDomElement; + +class QXmppPingIq : public QXmppIq +{ +public: + QXmppPingIq(); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + static bool isPingIq(const QDomElement &element); +}; + +#endif diff --git a/src/base/QXmppPresence.cpp b/src/base/QXmppPresence.cpp new file mode 100644 index 00000000..b967c8e5 --- /dev/null +++ b/src/base/QXmppPresence.cpp @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#include "QXmppPresence.h" +#include "QXmppUtils.h" +#include <QtDebug> +#include <QDomElement> +#include <QXmlStreamWriter> +#include "QXmppConstants.h" + +/// Constructs a QXmppPresence. +/// +/// \param type +/// \param status + +QXmppPresence::QXmppPresence(QXmppPresence::Type type, + const QXmppPresence::Status& status) + : QXmppStanza(), + m_type(type), + m_status(status), + m_vCardUpdateType(VCardUpdateNone) +{ + +} + +/// Destroys a QXmppPresence. + +QXmppPresence::~QXmppPresence() +{ + +} + +/// Returns the presence type. +/// +/// You can use this method to determine the action which needs to be +/// taken in response to receiving the presence. For instance, if the type is +/// QXmppPresence::Available or QXmppPresence::Unavailable, you could update +/// the icon representing a contact's availability. + +QXmppPresence::Type QXmppPresence::type() const +{ + return m_type; +} + +/// Sets the presence type. +/// +/// \param type + +void QXmppPresence::setType(QXmppPresence::Type type) +{ + m_type = type; +} + +/// Returns the presence status. + +const QXmppPresence::Status& QXmppPresence::status() const +{ + return m_status; +} + +/// Returns a reference to the presence status, allowing you to change it. + +QXmppPresence::Status& QXmppPresence::status() +{ + return m_status; +} + +/// Sets the presence status. +/// +/// \param status + +void QXmppPresence::setStatus(const QXmppPresence::Status& status) +{ + m_status = status; +} + +void QXmppPresence::parse(const QDomElement &element) +{ + QXmppStanza::parse(element); + + setTypeFromStr(element.attribute("type")); + m_status.parse(element); + + QXmppElementList extensions; + QDomElement xElement = element.firstChildElement(); + m_vCardUpdateType = VCardUpdateNone; + while(!xElement.isNull()) + { + // XEP-0045: Multi-User Chat + if(xElement.namespaceURI() == ns_muc_user) + { + QDomElement itemElement = xElement.firstChildElement("item"); + m_mucItem.parse(itemElement); + QDomElement statusElement = xElement.firstChildElement("status"); + m_mucStatusCodes.clear(); + while (!statusElement.isNull()) { + m_mucStatusCodes << statusElement.attribute("code").toInt(); + statusElement = statusElement.nextSiblingElement("status"); + } + } + // XEP-0153: vCard-Based Avatars + else if(xElement.namespaceURI() == ns_vcard_update) + { + QDomElement photoElement = xElement.firstChildElement("photo"); + if(!photoElement.isNull()) + { + m_photoHash = QByteArray::fromHex(photoElement.text().toAscii()); + if(m_photoHash.isEmpty()) + m_vCardUpdateType = VCardUpdateNoPhoto; + else + m_vCardUpdateType = VCardUpdateValidPhoto; + } + else + { + m_photoHash = QByteArray(); + m_vCardUpdateType = VCardUpdateNotReady; + } + } + // XEP-0115: Entity Capabilities + else if(xElement.tagName() == "c" && xElement.namespaceURI() == ns_capabilities) + { + m_capabilityNode = xElement.attribute("node"); + m_capabilityVer = QByteArray::fromBase64(xElement.attribute("ver").toAscii()); + m_capabilityHash = xElement.attribute("hash"); + m_capabilityExt = xElement.attribute("ext").split(" ", QString::SkipEmptyParts); + } + else if (xElement.tagName() == "error") + { + } + else if (xElement.tagName() == "show") + { + } + else if (xElement.tagName() == "status") + { + } + else if (xElement.tagName() == "priority") + { + } + else + { + // other extensions + extensions << QXmppElement(xElement); + } + xElement = xElement.nextSiblingElement(); + } + setExtensions(extensions); +} + +void QXmppPresence::toXml(QXmlStreamWriter *xmlWriter) const +{ + xmlWriter->writeStartElement("presence"); + helperToXmlAddAttribute(xmlWriter,"xml:lang", lang()); + helperToXmlAddAttribute(xmlWriter,"id", id()); + helperToXmlAddAttribute(xmlWriter,"to", to()); + helperToXmlAddAttribute(xmlWriter,"from", from()); + helperToXmlAddAttribute(xmlWriter,"type", getTypeStr()); + m_status.toXml(xmlWriter); + + error().toXml(xmlWriter); + + // XEP-0045: Multi-User Chat + if(!m_mucItem.isNull() || !m_mucStatusCodes.isEmpty()) + { + xmlWriter->writeStartElement("x"); + xmlWriter->writeAttribute("xmlns", ns_muc_user); + if (!m_mucItem.isNull()) + m_mucItem.toXml(xmlWriter); + foreach (int code, m_mucStatusCodes) { + xmlWriter->writeStartElement("status"); + xmlWriter->writeAttribute("code", QString::number(code)); + xmlWriter->writeEndElement(); + } + xmlWriter->writeEndElement(); + } + + // XEP-0153: vCard-Based Avatars + if(m_vCardUpdateType != VCardUpdateNone) + { + xmlWriter->writeStartElement("x"); + xmlWriter->writeAttribute("xmlns", ns_vcard_update); + switch(m_vCardUpdateType) + { + case VCardUpdateNoPhoto: + helperToXmlAddTextElement(xmlWriter, "photo", ""); + break; + case VCardUpdateValidPhoto: + helperToXmlAddTextElement(xmlWriter, "photo", m_photoHash.toHex()); + break; + case VCardUpdateNotReady: + break; + default: + break; + } + xmlWriter->writeEndElement(); + } + + if(!m_capabilityNode.isEmpty() && !m_capabilityVer.isEmpty() + && !m_capabilityHash.isEmpty()) + { + xmlWriter->writeStartElement("c"); + xmlWriter->writeAttribute("xmlns", ns_capabilities); + helperToXmlAddAttribute(xmlWriter, "hash", m_capabilityHash); + helperToXmlAddAttribute(xmlWriter, "node", m_capabilityNode); + helperToXmlAddAttribute(xmlWriter, "ver", m_capabilityVer.toBase64()); + xmlWriter->writeEndElement(); + } + + foreach (const QXmppElement &extension, extensions()) + extension.toXml(xmlWriter); + + xmlWriter->writeEndElement(); +} + +QString QXmppPresence::getTypeStr() const +{ + switch(m_type) { + case QXmppPresence::Error: + return "error"; + case QXmppPresence::Available: + return ""; + case QXmppPresence::Unavailable: + return "unavailable"; + case QXmppPresence::Subscribe: + return "subscribe"; + case QXmppPresence::Subscribed: + return "subscribed"; + case QXmppPresence::Unsubscribe: + return "unsubscribe"; + case QXmppPresence::Unsubscribed: + return "unsubscribed"; + case QXmppPresence::Probe: + return "probe"; + default: + qWarning("QXmppPresence::getTypeStr() invalid type %d", (int)m_type); + return ""; + } +} + +void QXmppPresence::setTypeFromStr(const QString& str) +{ + if(str == "error") + m_type = QXmppPresence::Error; + else if(str == "") + m_type = QXmppPresence::Available; + else if(str == "unavailable") + m_type = QXmppPresence::Unavailable; + else if(str == "subscribe") + m_type = QXmppPresence::Subscribe; + else if(str == "subscribed") + m_type = QXmppPresence::Subscribed; + else if(str == "unsubscribe") + m_type = QXmppPresence::Unsubscribe; + else if(str == "unsubscribed") + m_type = QXmppPresence::Unsubscribed; + else if(str == "probe") + m_type = QXmppPresence::Probe; + else { + qWarning("QXmppPresence::setTypeFromStr() invalid input string type: %s", + qPrintable(str)); + m_type = QXmppPresence::Error; + } +} + +/// Constructs a presence status. + +QXmppPresence::Status::Status(QXmppPresence::Status::Type type, + const QString statusText, int priority) : + m_type(type), + m_statusText(statusText), m_priority(priority) +{ +} + +/// Returns the status type, for instance busy or away. + +QXmppPresence::Status::Type QXmppPresence::Status::type() const +{ + return m_type; +} + +/// Sets the status type. + +void QXmppPresence::Status::setType(QXmppPresence::Status::Type type) +{ + m_type = type; +} + +void QXmppPresence::Status::setTypeFromStr(const QString& str) +{ + // FIXME: there is no keyword for Offline + if(str == "") + m_type = QXmppPresence::Status::Online; + else if(str == "away") + m_type = QXmppPresence::Status::Away; + else if(str == "chat") + m_type = QXmppPresence::Status::Chat; + else if(str == "dnd") + m_type = QXmppPresence::Status::DND; + else if(str == "xa") + m_type = QXmppPresence::Status::XA; + else { + qWarning("QXmppPresence::Status::setTypeFromStr() invalid input string type %s", + qPrintable(str)); + m_type = QXmppPresence::Status::Online; + } +} + +QString QXmppPresence::Status::getTypeStr() const +{ + switch(m_type) { + case QXmppPresence::Status::Online: + return ""; + case QXmppPresence::Status::Offline: + // FIXME: there is no keyword for Offline + return ""; + case QXmppPresence::Status::Away: + return "away"; + case QXmppPresence::Status::XA: + return "xa"; + case QXmppPresence::Status::DND: + return "dnd"; + case QXmppPresence::Status::Chat: + return "chat"; + default: + qWarning("QXmppPresence::Status::getTypeStr() invalid type %d", + (int)m_type); + return ""; + } +} + +/// Returns the status text, a textual description of the user's status. + +QString QXmppPresence::Status::statusText() const +{ + return m_statusText; +} + +/// Sets the status text, a textual description of the user's status. +/// +/// \param str The status text, for example "Gone fishing". + +void QXmppPresence::Status::setStatusText(const QString& str) +{ + m_statusText = str; +} + +/// Returns the priority level of the resource. + +int QXmppPresence::Status::priority() const +{ + return m_priority; +} + +/// Sets the priority level of the resource. +/// +/// \param priority + +void QXmppPresence::Status::setPriority(int priority) +{ + m_priority = priority; +} + +void QXmppPresence::Status::parse(const QDomElement &element) +{ + setTypeFromStr(element.firstChildElement("show").text()); + m_statusText = element.firstChildElement("status").text(); + m_priority = element.firstChildElement("priority").text().toInt(); +} + +void QXmppPresence::Status::toXml(QXmlStreamWriter *xmlWriter) const +{ + const QString show = getTypeStr(); + if (!show.isEmpty()) + helperToXmlAddTextElement(xmlWriter, "show", getTypeStr()); + if (!m_statusText.isEmpty()) + helperToXmlAddTextElement(xmlWriter, "status", m_statusText); + if (m_priority != 0) + helperToXmlAddTextElement(xmlWriter, "priority", QString::number(m_priority)); +} + +/// Returns the photo-hash of the VCardUpdate. +/// +/// \return QByteArray + +QByteArray QXmppPresence::photoHash() const +{ + return m_photoHash; +} + +/// Sets the photo-hash of the VCardUpdate. +/// +/// \param photoHash as QByteArray + +void QXmppPresence::setPhotoHash(const QByteArray& photoHash) +{ + m_photoHash = photoHash; +} + +/// Returns the type of VCardUpdate +/// +/// \return VCardUpdateType + +QXmppPresence::VCardUpdateType QXmppPresence::vCardUpdateType() const +{ + return m_vCardUpdateType; +} + +/// Sets the type of VCardUpdate +/// +/// \param type VCardUpdateType + +void QXmppPresence::setVCardUpdateType(VCardUpdateType type) +{ + m_vCardUpdateType = type; +} + +/// XEP-0115: Entity Capabilities +QString QXmppPresence::capabilityHash() const +{ + return m_capabilityHash; +} + +/// XEP-0115: Entity Capabilities +void QXmppPresence::setCapabilityHash(const QString& hash) +{ + m_capabilityHash = hash; +} + +/// XEP-0115: Entity Capabilities +QString QXmppPresence::capabilityNode() const +{ + return m_capabilityNode; +} + +/// XEP-0115: Entity Capabilities +void QXmppPresence::setCapabilityNode(const QString& node) +{ + m_capabilityNode = node; +} + +/// XEP-0115: Entity Capabilities +QByteArray QXmppPresence::capabilityVer() const +{ + return m_capabilityVer; +} + +/// XEP-0115: Entity Capabilities +void QXmppPresence::setCapabilityVer(const QByteArray& ver) +{ + m_capabilityVer = ver; +} + +/// Legacy XEP-0115: Entity Capabilities +QStringList QXmppPresence::capabilityExt() const +{ + return m_capabilityExt; +} + +/// Returns the MUC item. + +QXmppMucItem QXmppPresence::mucItem() const +{ + return m_mucItem; +} + +/// Sets the MUC item. +/// +/// \param item + +void QXmppPresence::setMucItem(const QXmppMucItem &item) +{ + m_mucItem = item; +} + +/// Returns the MUC status codes. + +QList<int> QXmppPresence::mucStatusCodes() const +{ + return m_mucStatusCodes; +} + +/// Sets the MUC status codes. +/// +/// \param codes + +void QXmppPresence::setMucStatusCodes(const QList<int> &codes) +{ + m_mucStatusCodes = codes; +} + diff --git a/src/base/QXmppPresence.h b/src/base/QXmppPresence.h new file mode 100644 index 00000000..8b85e01b --- /dev/null +++ b/src/base/QXmppPresence.h @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPPRESENCE_H +#define QXMPPPRESENCE_H + +#include "QXmppStanza.h" +#include "QXmppMucIq.h" + +/// \brief The QXmppPresence class represents an XMPP presence stanza. +/// +/// \ingroup Stanzas +class QXmppPresence : public QXmppStanza +{ +public: + /// This enum is used to describe a presence type. + enum Type + { + Error = 0, ///< An error has occurred regarding processing or delivery of a previously-sent presence stanza. + Available, ///< Signals that the sender is online and available for communication. + Unavailable, ///< Signals that the sender is no longer available for communication. + Subscribe, ///< The sender wishes to subscribe to the recipient's presence. + Subscribed, ///< The sender has allowed the recipient to receive their presence. + Unsubscribe, ///< The sender is unsubscribing from another entity's presence. + Unsubscribed, ///< The subscription request has been denied or a previously-granted subscription has been cancelled. + Probe ///< A request for an entity's current presence; SHOULD be generated only by a server on behalf of a user. + }; + + // XEP-0153: vCard-Based Avatars + enum VCardUpdateType + { + VCardUpdateNone = 0, ///< Protocol is not supported + VCardUpdateNoPhoto, ///< User is not using any image + VCardUpdateValidPhoto, ///< User is advertising an image + VCardUpdateNotReady ///< User is not ready to advertise an image + +/// \note This enables recipients to distinguish between the absence of an image +/// (empty photo element) and mere support for the protocol (empty update child). + }; + + /// \brief The QXmppPresence::Status class represents the status of an XMPP entity. + /// + /// It stores information such as the "away", "busy" status of a user, or + /// a human-readable description. + + class Status + { + public: + /// This enum is used to describe an availability status. + enum Type + { + Offline = 0, + Online, ///< The entity or resource is online. + Away, ///< The entity or resource is temporarily away. + XA, ///< The entity or resource is away for an extended period. + DND, ///< The entity or resource is busy ("Do Not Disturb"). + Chat, ///< The entity or resource is actively interested in chatting. + }; + + Status(QXmppPresence::Status::Type type = QXmppPresence::Status::Online, + const QString statusText = "", int priority = 0); + + QXmppPresence::Status::Type type() const; + void setType(QXmppPresence::Status::Type); + + QString statusText() const; + void setStatusText(const QString&); + + int priority() const; + void setPriority(int); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + private: + QString getTypeStr() const; + void setTypeFromStr(const QString&); + + QXmppPresence::Status::Type m_type; + QString m_statusText; + int m_priority; + }; + + QXmppPresence(QXmppPresence::Type type = QXmppPresence::Available, + const QXmppPresence::Status& status = QXmppPresence::Status()); + ~QXmppPresence(); + + QXmppPresence::Type type() const; + void setType(QXmppPresence::Type); + + QXmppPresence::Status& status(); + const QXmppPresence::Status& status() const; + void setStatus(const QXmppPresence::Status&); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + // XEP-0045: Multi-User Chat + QXmppMucItem mucItem() const; + void setMucItem(const QXmppMucItem &item); + + QList<int> mucStatusCodes() const; + void setMucStatusCodes(const QList<int> &codes); + + /// XEP-0153: vCard-Based Avatars + QByteArray photoHash() const; + void setPhotoHash(const QByteArray&); + + VCardUpdateType vCardUpdateType() const; + void setVCardUpdateType(VCardUpdateType type); + + // XEP-0115: Entity Capabilities + QString capabilityHash() const; + void setCapabilityHash(const QString&); + + QString capabilityNode() const; + void setCapabilityNode(const QString&); + + QByteArray capabilityVer() const; + void setCapabilityVer(const QByteArray&); + + QStringList capabilityExt() const; + +private: + QString getTypeStr() const; + void setTypeFromStr(const QString&); + + Type m_type; + QXmppPresence::Status m_status; + + + /// XEP-0153: vCard-Based Avatars + + /// m_photoHash: the SHA1 hash of the avatar image data itself (not the base64-encoded version) + /// in accordance with RFC 3174 + QByteArray m_photoHash; + VCardUpdateType m_vCardUpdateType; + + // XEP-0115: Entity Capabilities + QString m_capabilityHash; + QString m_capabilityNode; + QByteArray m_capabilityVer; + // Legacy XEP-0115: Entity Capabilities + QStringList m_capabilityExt; + + // XEP-0045: Multi-User Chat + QXmppMucItem m_mucItem; + QList<int> m_mucStatusCodes; +}; + +#endif // QXMPPPRESENCE_H diff --git a/src/base/QXmppPubSubIq.cpp b/src/base/QXmppPubSubIq.cpp new file mode 100644 index 00000000..1ad1c39d --- /dev/null +++ b/src/base/QXmppPubSubIq.cpp @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> + +#include "QXmppConstants.h" +#include "QXmppPubSubIq.h" +#include "QXmppUtils.h" + +static const char *ns_pubsub = "http://jabber.org/protocol/pubsub"; + +static const char *pubsub_queries[] = { + "affiliations", + "default", + "items", + "publish", + "retract", + "subscribe", + "subscription", + "subscriptions", + "unsubscribe", +}; + +/// Returns the ID of the PubSub item. +/// + +QString QXmppPubSubItem::id() const +{ + return m_id; +} + +/// Sets the ID of the PubSub item. +/// +/// \param id + +void QXmppPubSubItem::setId(const QString &id) +{ + m_id = id; +} + +/// Returns the contents of the PubSub item. +/// + +QXmppElement QXmppPubSubItem::contents() const +{ + return m_contents; +} + +/// Sets the contents of the PubSub item. +/// +/// \param contents + +void QXmppPubSubItem::setContents(const QXmppElement &contents) +{ + m_contents = contents; +} + +void QXmppPubSubItem::parse(const QDomElement &element) +{ + m_id = element.attribute("id"); + m_contents = QXmppElement(element.firstChildElement()); +} + +void QXmppPubSubItem::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("item"); + helperToXmlAddAttribute(writer, "id", m_id); + m_contents.toXml(writer); + writer->writeEndElement(); +} + +/// Returns the PubSub queryType for this IQ. +/// + +QXmppPubSubIq::QueryType QXmppPubSubIq::queryType() const +{ + return m_queryType; +} + +/// Sets the PubSub queryType for this IQ. +/// +/// \param queryType + +void QXmppPubSubIq::setQueryType(QXmppPubSubIq::QueryType queryType) +{ + m_queryType = queryType; +} + +/// Returns the JID being queried. +/// + +QString QXmppPubSubIq::queryJid() const +{ + return m_queryJid; +} + +/// Sets the JID being queried. +/// +/// \param queryJid + +void QXmppPubSubIq::setQueryJid(const QString &queryJid) +{ + m_queryJid = queryJid; +} + +/// Returns the node being queried. +/// + +QString QXmppPubSubIq::queryNode() const +{ + return m_queryNode; +} + +/// Sets the node being queried. +/// +/// \param queryNode + +void QXmppPubSubIq::setQueryNode(const QString &queryNode) +{ + m_queryNode = queryNode; +} + +/// Returns the subscription ID. +/// + +QString QXmppPubSubIq::subscriptionId() const +{ + return m_subscriptionId; +} + +/// Sets the subscription ID. +/// +/// \param subscriptionId + +void QXmppPubSubIq::setSubscriptionId(const QString &subscriptionId) +{ + m_subscriptionId = subscriptionId; +} + +/// Returns the IQ's items. +/// + +QList<QXmppPubSubItem> QXmppPubSubIq::items() const +{ + return m_items; +} + +/// Sets the IQ's items. +/// +/// \param items + +void QXmppPubSubIq::setItems(const QList<QXmppPubSubItem> &items) +{ + m_items = items; +} + +bool QXmppPubSubIq::isPubSubIq(const QDomElement &element) +{ + const QDomElement pubSubElement = element.firstChildElement("pubsub"); + return pubSubElement.namespaceURI() == ns_pubsub; +} + +void QXmppPubSubIq::parseElementFromChild(const QDomElement &element) +{ + const QDomElement pubSubElement = element.firstChildElement("pubsub"); + + const QDomElement queryElement = pubSubElement.firstChildElement(); + + // determine query type + const QString tagName = queryElement.tagName(); + for (int i = ItemsQuery; i <= SubscriptionsQuery; i++) + { + if (tagName == pubsub_queries[i]) + { + m_queryType = static_cast<QueryType>(i); + break; + } + } + m_queryJid = queryElement.attribute("jid"); + m_queryNode = queryElement.attribute("node"); + + // parse contents + QDomElement childElement; + switch (m_queryType) + { + case QXmppPubSubIq::ItemsQuery: + case QXmppPubSubIq::PublishQuery: + childElement = queryElement.firstChildElement("item"); + while (!childElement.isNull()) + { + QXmppPubSubItem item; + item.parse(childElement); + m_items << item; + childElement = childElement.nextSiblingElement("item"); + } + break; + case QXmppPubSubIq::SubscriptionQuery: + m_subscriptionId = queryElement.attribute("subid"); + m_subscriptionType = queryElement.attribute("subscription"); + break; + default: + break; + } +} + +void QXmppPubSubIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("pubsub"); + writer->writeAttribute("xmlns", ns_pubsub); + + // write query type + writer->writeStartElement(pubsub_queries[m_queryType]); + helperToXmlAddAttribute(writer, "jid", m_queryJid); + helperToXmlAddAttribute(writer, "node", m_queryNode); + + // write contents + switch (m_queryType) + { + case QXmppPubSubIq::ItemsQuery: + case QXmppPubSubIq::PublishQuery: + foreach (const QXmppPubSubItem &item, m_items) + item.toXml(writer); + break; + case QXmppPubSubIq::SubscriptionQuery: + helperToXmlAddAttribute(writer, "subid", m_subscriptionId); + helperToXmlAddAttribute(writer, "subscription", m_subscriptionType); + break; + default: + break; + } + writer->writeEndElement(); + writer->writeEndElement(); +} diff --git a/src/base/QXmppPubSubIq.h b/src/base/QXmppPubSubIq.h new file mode 100644 index 00000000..01dc3a92 --- /dev/null +++ b/src/base/QXmppPubSubIq.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPPUBSUBIQ_H +#define QXMPPPUBSUBIQ_H + +#include "QXmppIq.h" + +/// \brief The QXmppPubSubItem class represents a publish-subscribe item +/// as defined by XEP-0060: Publish-Subscribe. +/// + +class QXmppPubSubItem +{ +public: + QString id() const; + void setId(const QString &id); + + QXmppElement contents() const; + void setContents(const QXmppElement &contents); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QString m_id; + QXmppElement m_contents; +}; + +/// \brief The QXmppPubSubIq class represents an IQ used for the +/// publish-subscribe mechanisms defined by XEP-0060: Publish-Subscribe. +/// +/// \ingroup Stanzas + +class QXmppPubSubIq : public QXmppIq +{ +public: + /// This enum is used to describe a publish-subscribe query type. + enum QueryType + { + AffiliationsQuery, + DefaultQuery, + ItemsQuery, + PublishQuery, + RetractQuery, + SubscribeQuery, + SubscriptionQuery, + SubscriptionsQuery, + UnsubscribeQuery, + }; + + QXmppPubSubIq::QueryType queryType() const; + void setQueryType(QXmppPubSubIq::QueryType queryType); + + QString queryJid() const; + void setQueryJid(const QString &jid); + + QString queryNode() const; + void setQueryNode(const QString &node); + + QList<QXmppPubSubItem> items() const; + void setItems(const QList<QXmppPubSubItem> &items); + + QString subscriptionId() const; + void setSubscriptionId(const QString &id); + + /// \cond + static bool isPubSubIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement&); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QXmppPubSubIq::QueryType m_queryType; + QString m_queryJid; + QString m_queryNode; + QList<QXmppPubSubItem> m_items; + QString m_subscriptionId; + QString m_subscriptionType; +}; + +#endif diff --git a/src/base/QXmppRosterIq.cpp b/src/base/QXmppRosterIq.cpp new file mode 100644 index 00000000..0b2e1716 --- /dev/null +++ b/src/base/QXmppRosterIq.cpp @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> +#include <QXmlStreamWriter> + +#include "QXmppRosterIq.h" +#include "QXmppConstants.h" +#include "QXmppUtils.h" + +/// Adds an item to the roster IQ. +/// +/// \param item + +void QXmppRosterIq::addItem(const Item& item) +{ + m_items.append(item); +} + +/// Returns the roster IQ's items. + +QList<QXmppRosterIq::Item> QXmppRosterIq::items() const +{ + return m_items; +} + +bool QXmppRosterIq::isRosterIq(const QDomElement &element) +{ + return (element.firstChildElement("query").namespaceURI() == ns_roster); +} + +void QXmppRosterIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement itemElement = element. + firstChildElement("query"). + firstChildElement("item"); + while(!itemElement.isNull()) + { + QXmppRosterIq::Item item; + item.parse(itemElement); + m_items.append(item); + itemElement = itemElement.nextSiblingElement(); + } +} + +void QXmppRosterIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("query"); + writer->writeAttribute( "xmlns", ns_roster); + + for(int i = 0; i < m_items.count(); ++i) + m_items.at(i).toXml(writer); + writer->writeEndElement(); +} + +/// Returns the bareJid of the roster entry. +/// +/// \return bareJid as a QString +/// + +QString QXmppRosterIq::Item::bareJid() const +{ + return m_bareJid; +} + +/// Sets the bareJid of the roster entry. +/// +/// \param bareJid as a QString +/// + +void QXmppRosterIq::Item::setBareJid(const QString &bareJid) +{ + m_bareJid = bareJid; +} + +/// Returns the groups of the roster entry. +/// +/// \return QSet<QString> list of all the groups +/// + +QSet<QString> QXmppRosterIq::Item::groups() const +{ + return m_groups; +} + +/// Sets the groups of the roster entry. +/// +/// \param groups list of all the groups as a QSet<QString> +/// + +void QXmppRosterIq::Item::setGroups(const QSet<QString>& groups) +{ + m_groups = groups; +} + +/// Returns the name of the roster entry. +/// +/// \return name as a QString +/// + +QString QXmppRosterIq::Item::name() const +{ + return m_name; +} + +/// Sets the name of the roster entry. +/// +/// \param name as a QString +/// + +void QXmppRosterIq::Item::setName(const QString &name) +{ + m_name = name; +} + +/// Returns the subscription status of the roster entry. It is the "ask" +/// attribute in the Roster IQ stanza. Its value can be "subscribe" or "unsubscribe" +/// or empty. +/// +/// \return subscription status as a QString +/// +/// + +QString QXmppRosterIq::Item::subscriptionStatus() const +{ + return m_subscriptionStatus; +} + +/// Sets the subscription status of the roster entry. It is the "ask" +/// attribute in the Roster IQ stanza. Its value can be "subscribe" or "unsubscribe" +/// or empty. +/// +/// \param status as a QString +/// + +void QXmppRosterIq::Item::setSubscriptionStatus(const QString &status) +{ + m_subscriptionStatus = status; +} + +/// Returns the subscription type of the roster entry. +/// + +QXmppRosterIq::Item::SubscriptionType + QXmppRosterIq::Item::subscriptionType() const +{ + return m_type; +} + +/// Sets the subscription type of the roster entry. +/// +/// \param type +/// + +void QXmppRosterIq::Item::setSubscriptionType(SubscriptionType type) +{ + m_type = type; +} + +QString QXmppRosterIq::Item::getSubscriptionTypeStr() const +{ + switch(m_type) + { + case NotSet: + return ""; + case None: + return "none"; + case Both: + return "both"; + case From: + return "from"; + case To: + return "to"; + case Remove: + return "remove"; + default: + { + qWarning("QXmppRosterIq::Item::getTypeStr(): invalid type"); + return ""; + } + } +} + +void QXmppRosterIq::Item::setSubscriptionTypeFromStr(const QString& type) +{ + if(type == "") + setSubscriptionType(NotSet); + else if(type == "none") + setSubscriptionType(None); + else if(type == "both") + setSubscriptionType(Both); + else if(type == "from") + setSubscriptionType(From); + else if(type == "to") + setSubscriptionType(To); + else if(type == "remove") + setSubscriptionType(Remove); + else + qWarning("QXmppRosterIq::Item::setTypeFromStr(): invalid type"); +} + +void QXmppRosterIq::Item::parse(const QDomElement &element) +{ + m_name = element.attribute("name"); + m_bareJid = element.attribute("jid"); + setSubscriptionTypeFromStr(element.attribute("subscription")); + setSubscriptionStatus(element.attribute("ask")); + + QDomElement groupElement = element.firstChildElement("group"); + while(!groupElement.isNull()) + { + m_groups << groupElement.text(); + groupElement = groupElement.nextSiblingElement("group"); + } +} + +void QXmppRosterIq::Item::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("item"); + helperToXmlAddAttribute(writer,"jid", m_bareJid); + helperToXmlAddAttribute(writer,"name", m_name); + helperToXmlAddAttribute(writer,"subscription", getSubscriptionTypeStr()); + helperToXmlAddAttribute(writer, "ask", subscriptionStatus()); + + QSet<QString>::const_iterator i = m_groups.constBegin(); + while(i != m_groups.constEnd()) + { + helperToXmlAddTextElement(writer,"group", *i); + ++i; + } + writer->writeEndElement(); +} diff --git a/src/base/QXmppRosterIq.h b/src/base/QXmppRosterIq.h new file mode 100644 index 00000000..9680a176 --- /dev/null +++ b/src/base/QXmppRosterIq.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPROSTERIQ_H +#define QXMPPROSTERIQ_H + +#include "QXmppIq.h" +#include <QList> +#include <QSet> + +/// \brief The QXmppRosterIq class represents a roster IQ. +/// +/// \ingroup Stanzas + +class QXmppRosterIq : public QXmppIq +{ +public: + + /// \brief The QXmppRosterIq::Item class represents a roster entry. + class Item + { + public: + /// An enumeration for type of subscription with the bareJid in the roster. + enum SubscriptionType + { + None = 0, ///< the user does not have a subscription to the + ///< contact's presence information, and the contact does + ///< not have a subscription to the user's presence information + From = 1, ///< the contact has a subscription to the user's presence information, + ///< but the user does not have a subscription to the contact's presence information + To = 2, ///< the user has a subscription to the contact's presence information, + ///< but the contact does not have a subscription to the user's presence information + Both = 3, ///< both the user and the contact have subscriptions to each + ///< other's presence information + Remove = 4, ///< to delete a roster item + NotSet = 8 ///< the subscription state was not specified + }; + + QString bareJid() const; + QSet<QString> groups() const; + QString name() const; + QString subscriptionStatus() const; + SubscriptionType subscriptionType() const; + + void setBareJid(const QString&); + void setGroups(const QSet<QString>&); + void setName(const QString&); + void setSubscriptionStatus(const QString&); + void setSubscriptionType(SubscriptionType); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + private: + QString getSubscriptionTypeStr() const; + void setSubscriptionTypeFromStr(const QString&); + + QString m_bareJid; + SubscriptionType m_type; + QString m_name; + // can be subscribe/unsubscribe (attribute "ask") + QString m_subscriptionStatus; + QSet<QString> m_groups; + }; + + void addItem(const Item&); + QList<Item> items() const; + + /// \cond + static bool isRosterIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QList<Item> m_items; +}; + +#endif // QXMPPROSTERIQ_H diff --git a/src/base/QXmppRpcIq.cpp b/src/base/QXmppRpcIq.cpp new file mode 100644 index 00000000..a2647077 --- /dev/null +++ b/src/base/QXmppRpcIq.cpp @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Ian Reinhart Geiser + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> +#include <QMap> +#include <QVariant> +#include <QDateTime> +#include <QStringList> + +#include "QXmppConstants.h" +#include "QXmppRpcIq.h" +#include "QXmppUtils.h" + +void XMLRPC::marshall(QXmlStreamWriter *writer, const QVariant &value) +{ + writer->writeStartElement("value"); + switch( value.type() ) + { + case QVariant::Int: + case QVariant::UInt: + case QVariant::LongLong: + case QVariant::ULongLong: + writer->writeTextElement("i4", value.toString()); + break; + case QVariant::Double: + writer->writeTextElement("double", value.toString()); + break; + case QVariant::Bool: + writer->writeTextElement("boolean", value.toBool() ? "1" : "0"); + break; + case QVariant::Date: + writer->writeTextElement("dateTime.iso8601", value.toDate().toString( Qt::ISODate ) ); + break; + case QVariant::DateTime: + writer->writeTextElement("dateTime.iso8601", value.toDateTime().toString( Qt::ISODate ) ); + break; + case QVariant::Time: + writer->writeTextElement("dateTime.iso8601", value.toTime().toString( Qt::ISODate ) ); + break; + case QVariant::StringList: + case QVariant::List: + { + writer->writeStartElement("array"); + writer->writeStartElement("data"); + foreach(const QVariant &item, value.toList()) + marshall(writer, item); + writer->writeEndElement(); + writer->writeEndElement(); + break; + } + case QVariant::Map: + { + writer->writeStartElement("struct"); + QMap<QString, QVariant> map = value.toMap(); + QMap<QString, QVariant>::ConstIterator index = map.begin(); + while( index != map.end() ) + { + writer->writeStartElement("member"); + writer->writeTextElement("name", index.key()); + marshall( writer, *index ); + writer->writeEndElement(); + ++index; + } + writer->writeEndElement(); + break; + } + case QVariant::ByteArray: + { + writer->writeTextElement("base64", value.toByteArray().toBase64() ); + break; + } + default: + { + if (value.isNull()) + writer->writeEmptyElement("nil"); + else if( value.canConvert(QVariant::String) ) + { + writer->writeTextElement("string", value.toString() ); + } + break; + } + } + writer->writeEndElement(); +} + +QVariant XMLRPC::demarshall(const QDomElement &elem, QStringList &errors) +{ + if ( elem.tagName().toLower() != "value" ) + { + errors << "Bad param value"; + return QVariant(); + } + + if ( !elem.firstChild().isElement() ) + { + return QVariant( elem.text() ); + } + + const QDomElement typeData = elem.firstChild().toElement(); + const QString typeName = typeData.tagName().toLower(); + + if (typeName == "nil") + { + return QVariant(); + } + if ( typeName == "string" ) + { + return QVariant( typeData.text() ); + } + else if (typeName == "int" || typeName == "i4" ) + { + bool ok = false; + QVariant val( typeData.text().toInt( &ok ) ); + if (ok) + return val; + errors << "I was looking for an integer but data was courupt"; + return QVariant(); + } + else if( typeName == "double" ) + { + bool ok = false; + QVariant val( typeData.text().toDouble( &ok ) ); + if (ok) + return val; + errors << "I was looking for an double but data was corrupt"; + } + else if( typeName == "boolean" ) + return QVariant( typeData.text() == "1" || typeData.text().toLower() == "true" ); + else if( typeName == "datetime" || typeName == "datetime.iso8601" ) + return QVariant( QDateTime::fromString( typeData.text(), Qt::ISODate ) ); + else if( typeName == "array" ) + { + QVariantList arr; + QDomElement valueNode = typeData.firstChildElement("data").firstChildElement(); + while (!valueNode.isNull() && errors.isEmpty()) + { + arr.append(demarshall(valueNode, errors)); + valueNode = valueNode.nextSiblingElement(); + } + return QVariant( arr ); + } + else if( typeName == "struct" ) + { + QMap<QString,QVariant> stct; + QDomNode valueNode = typeData.firstChild(); + while(!valueNode.isNull() && errors.isEmpty()) + { + const QDomElement memberNode = valueNode.toElement().elementsByTagName("name").item(0).toElement(); + const QDomElement dataNode = valueNode.toElement().elementsByTagName("value").item(0).toElement(); + stct[ memberNode.text() ] = demarshall(dataNode, errors); + valueNode = valueNode.nextSibling(); + } + return QVariant(stct); + } + else if( typeName == "base64" ) + { + QVariant returnVariant; + QByteArray dest; + QByteArray src = typeData.text().toLatin1(); + return QVariant(QByteArray::fromBase64(src)); + } + + errors << QString( "Cannot handle type %1").arg(typeName); + return QVariant(); +} + +QXmppRpcErrorIq::QXmppRpcErrorIq() : QXmppIq( QXmppIq::Error ) +{ + +} + +QXmppRpcInvokeIq QXmppRpcErrorIq::query() const +{ + return m_query; +} + +void QXmppRpcErrorIq::setQuery(const QXmppRpcInvokeIq &query) +{ + m_query = query; +} + +bool QXmppRpcErrorIq::isRpcErrorIq(const QDomElement &element) +{ + QString type = element.attribute("type"); + QDomElement errorElement = element.firstChildElement("error"); + QDomElement queryElement = element.firstChildElement("query"); + return (type == "error") && + !errorElement.isNull() && + queryElement.namespaceURI() == ns_rpc; +} + +void QXmppRpcErrorIq::parseElementFromChild(const QDomElement &element) +{ + m_query.parseElementFromChild(element); +} + +void QXmppRpcErrorIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + m_query.toXmlElementFromChild(writer); +} + +QXmppRpcResponseIq::QXmppRpcResponseIq() + : QXmppIq(QXmppIq::Result), + m_faultCode(0) +{ +} + +/// Returns the fault code. +/// + +int QXmppRpcResponseIq::faultCode() const +{ + return m_faultCode; +} + +/// Sets the fault code. +/// +/// \param faultCode + +void QXmppRpcResponseIq::setFaultCode(int faultCode) +{ + m_faultCode = faultCode; +} + +/// Returns the fault string. +/// + +QString QXmppRpcResponseIq::faultString() const +{ + return m_faultString; +} + +/// Sets the fault string. +/// +/// \param faultString + +void QXmppRpcResponseIq::setFaultString(const QString& faultString) +{ + m_faultString = faultString; +} + +/// Returns the response values. +/// + +QVariantList QXmppRpcResponseIq::values() const +{ + return m_values; +} + +/// Sets the response values. +/// +/// \param values + +void QXmppRpcResponseIq::setValues(const QVariantList &values) +{ + m_values = values; +} + +bool QXmppRpcResponseIq::isRpcResponseIq(const QDomElement &element) +{ + QString type = element.attribute("type"); + QDomElement dataElement = element.firstChildElement("query"); + return dataElement.namespaceURI() == ns_rpc && + type == "result"; +} + +void QXmppRpcResponseIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement queryElement = element.firstChildElement("query"); + QDomElement methodElement = queryElement.firstChildElement("methodResponse"); + + const QDomElement contents = methodElement.firstChildElement(); + if( contents.tagName().toLower() == "params") + { + QDomNode param = contents.firstChildElement("param"); + while (!param.isNull()) + { + QStringList errors; + const QVariant value = XMLRPC::demarshall(param.firstChildElement("value"), errors); + if (!errors.isEmpty()) + break; + m_values << value; + param = param.nextSiblingElement("param"); + } + } + else if( contents.tagName().toLower() == "fault") + { + QStringList errors; + const QDomElement errElement = contents.firstChildElement("value"); + const QVariant error = XMLRPC::demarshall(errElement, errors); + if (!errors.isEmpty()) + return; + m_faultCode = error.toMap()["faultCode"].toInt(); + m_faultString = error.toMap()["faultString"].toString(); + } +} + +void QXmppRpcResponseIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("query"); + writer->writeAttribute("xmlns", ns_rpc); + + writer->writeStartElement("methodResponse"); + if (m_faultCode) + { + writer->writeStartElement("fault"); + QMap<QString,QVariant> fault; + fault["faultCode"] = m_faultCode; + fault["faultString"] = m_faultString; + XMLRPC::marshall(writer, fault); + writer->writeEndElement(); + } + else if (!m_values.isEmpty()) + { + writer->writeStartElement("params"); + foreach (const QVariant &arg, m_values) + { + writer->writeStartElement("param"); + XMLRPC::marshall(writer, arg); + writer->writeEndElement(); + } + writer->writeEndElement(); + } + writer->writeEndElement(); + + writer->writeEndElement(); +} + +QXmppRpcInvokeIq::QXmppRpcInvokeIq() + : QXmppIq(QXmppIq::Set) +{ +} + +/// Returns the method arguments. +/// + +QVariantList QXmppRpcInvokeIq::arguments() const +{ + return m_arguments; +} + +/// Sets the method arguments. +/// +/// \param arguments + +void QXmppRpcInvokeIq::setArguments(const QVariantList &arguments) +{ + m_arguments = arguments; +} + +/// Returns the method name. +/// + +QString QXmppRpcInvokeIq::method() const +{ + return m_method; +} + +/// Sets the method name. +/// +/// \param method + +void QXmppRpcInvokeIq::setMethod(const QString &method) +{ + m_method = method; +} + +bool QXmppRpcInvokeIq::isRpcInvokeIq(const QDomElement &element) +{ + QString type = element.attribute("type"); + QDomElement dataElement = element.firstChildElement("query"); + return dataElement.namespaceURI() == ns_rpc && + type == "set"; +} + +void QXmppRpcInvokeIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement queryElement = element.firstChildElement("query"); + QDomElement methodElement = queryElement.firstChildElement("methodCall"); + + m_method = methodElement.firstChildElement("methodName").text(); + + const QDomElement methodParams = methodElement.firstChildElement("params"); + m_arguments.clear(); + if( !methodParams.isNull() ) + { + QDomNode param = methodParams.firstChildElement("param"); + while (!param.isNull()) + { + QStringList errors; + QVariant arg = XMLRPC::demarshall(param.firstChildElement("value"), errors); + if (!errors.isEmpty()) + break; + m_arguments << arg; + param = param.nextSiblingElement("param"); + } + } +} + +void QXmppRpcInvokeIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("query"); + writer->writeAttribute("xmlns", ns_rpc); + + writer->writeStartElement("methodCall"); + writer->writeTextElement("methodName", m_method); + if (!m_arguments.isEmpty()) + { + writer->writeStartElement("params"); + foreach(const QVariant &arg, m_arguments) + { + writer->writeStartElement("param"); + XMLRPC::marshall(writer, arg); + writer->writeEndElement(); + } + writer->writeEndElement(); + } + writer->writeEndElement(); + + writer->writeEndElement(); +} + diff --git a/src/base/QXmppRpcIq.h b/src/base/QXmppRpcIq.h new file mode 100644 index 00000000..f557686a --- /dev/null +++ b/src/base/QXmppRpcIq.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Ian Reinhart Geiser + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPRPCIQ_H +#define QXMPPRPCIQ_H + +#include "QXmppIq.h" +#include <QVariant> + +class QXmlStreamWriter; +class QDomElement; + +namespace XMLRPC +{ + void marshall( QXmlStreamWriter *writer, const QVariant &value); + QVariant demarshall(const QDomElement &elem, QStringList &errors); +} + +/// \brief The QXmppRpcResponseIq class represents an IQ used to carry +/// an RPC response as specified by XEP-0009: Jabber-RPC. +/// +/// \ingroup Stanzas + +class QXmppRpcResponseIq : public QXmppIq +{ +public: + QXmppRpcResponseIq(); + + int faultCode() const; + void setFaultCode(int faultCode); + + QString faultString() const; + void setFaultString(const QString &faultString); + + QVariantList values() const; + void setValues(const QVariantList &values); + + /// \cond + static bool isRpcResponseIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + int m_faultCode; + QString m_faultString; + QVariantList m_values; +}; + +/// \brief The QXmppRpcInvokeIq class represents an IQ used to carry +/// an RPC invocation as specified by XEP-0009: Jabber-RPC. +/// +/// \ingroup Stanzas + +class QXmppRpcInvokeIq : public QXmppIq +{ +public: + QXmppRpcInvokeIq(); + + QString method() const; + void setMethod( const QString &method ); + + QVariantList arguments() const; + void setArguments(const QVariantList &arguments); + + /// \cond + static bool isRpcInvokeIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QVariantList m_arguments; + QString m_method; + + friend class QXmppRpcErrorIq; +}; + +class QXmppRpcErrorIq : public QXmppIq +{ +public: + QXmppRpcErrorIq(); + + QXmppRpcInvokeIq query() const; + void setQuery(const QXmppRpcInvokeIq &query); + + /// \cond + static bool isRpcErrorIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QXmppRpcInvokeIq m_query; +}; + +#endif // QXMPPRPCIQ_H diff --git a/src/base/QXmppRtpChannel.cpp b/src/base/QXmppRtpChannel.cpp new file mode 100644 index 00000000..df26bc30 --- /dev/null +++ b/src/base/QXmppRtpChannel.cpp @@ -0,0 +1,1042 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <cmath> + +#include <QDataStream> +#include <QMetaType> +#include <QTimer> + +#include "QXmppCodec.h" +#include "QXmppJingleIq.h" +#include "QXmppRtpChannel.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327950288 +#endif + +//#define QXMPP_DEBUG_RTP +//#define QXMPP_DEBUG_RTP_BUFFER +#define SAMPLE_BYTES 2 + +const quint8 RTP_VERSION = 0x02; + +/// Parses an RTP packet. +/// +/// \param ba + +bool QXmppRtpPacket::decode(const QByteArray &ba) +{ + if (ba.isEmpty()) + return false; + + // fixed header + quint8 tmp; + QDataStream stream(ba); + stream >> tmp; + version = (tmp >> 6); + const quint8 cc = (tmp >> 1) & 0xf; + const int hlen = 12 + 4 * cc; + if (version != RTP_VERSION || ba.size() < hlen) + return false; + stream >> tmp; + marker = (tmp >> 7); + type = tmp & 0x7f; + stream >> sequence; + stream >> stamp; + stream >> ssrc; + + // contributing source IDs + csrc.clear(); + quint32 src; + for (int i = 0; i < cc; ++i) { + stream >> src; + csrc << src; + } + + // retrieve payload + payload = ba.right(ba.size() - hlen); + return true; +} + +/// Encodes an RTP packet. + +QByteArray QXmppRtpPacket::encode() const +{ + Q_ASSERT(csrc.size() < 16); + + // fixed header + QByteArray ba; + ba.resize(payload.size() + 12 + 4 * csrc.size()); + QDataStream stream(&ba, QIODevice::WriteOnly); + stream << quint8(((version & 0x3) << 6) | + ((csrc.size() & 0xf) << 1)); + stream << quint8((type & 0x7f) | (marker << 7)); + stream << sequence; + stream << stamp; + stream << ssrc; + + // contributing source ids + foreach (const quint32 &src, csrc) + stream << src; + + stream.writeRawData(payload.constData(), payload.size()); + return ba; +} + +/// Returns a string representation of the RTP header. + +QString QXmppRtpPacket::toString() const +{ + return QString("RTP packet seq %1 stamp %2 marker %3 type %4 size %5").arg( + QString::number(sequence), + QString::number(stamp), + QString::number(marker), + QString::number(type), + QString::number(payload.size())); +} + +/// Creates a new RTP channel. + +QXmppRtpChannel::QXmppRtpChannel() + : m_outgoingPayloadNumbered(false) +{ +} + +/// Returns the local payload types. +/// + +QList<QXmppJinglePayloadType> QXmppRtpChannel::localPayloadTypes() +{ + m_outgoingPayloadNumbered = true; + return m_outgoingPayloadTypes; +} + +/// Sets the remote payload types. +/// +/// \param remotePayloadTypes + +void QXmppRtpChannel::setRemotePayloadTypes(const QList<QXmppJinglePayloadType> &remotePayloadTypes) +{ + QList<QXmppJinglePayloadType> commonOutgoingTypes; + QList<QXmppJinglePayloadType> commonIncomingTypes; + + foreach (const QXmppJinglePayloadType &incomingType, remotePayloadTypes) { + // check we support this payload type + int outgoingIndex = m_outgoingPayloadTypes.indexOf(incomingType); + if (outgoingIndex < 0) + continue; + QXmppJinglePayloadType outgoingType = m_outgoingPayloadTypes[outgoingIndex]; + + // be kind and try to adopt the other agent's numbering + if (!m_outgoingPayloadNumbered && outgoingType.id() > 95) { + outgoingType.setId(incomingType.id()); + } + commonIncomingTypes << incomingType; + commonOutgoingTypes << outgoingType; + } + if (commonOutgoingTypes.isEmpty()) { + qWarning("QXmppRtpChannel could not negociate a common codec"); + return; + } + m_incomingPayloadTypes = commonIncomingTypes; + m_outgoingPayloadTypes = commonOutgoingTypes; + m_outgoingPayloadNumbered = true; + + // call hook + payloadTypesChanged(); +} + +void QXmppRtpChannel::payloadTypesChanged() +{ +} + +enum CodecId { + G711u = 0, + GSM = 3, + G723 = 4, + G711a = 8, + G722 = 9, + L16Stereo = 10, + L16Mono = 11, + G728 = 15, + G729 = 18, +}; + +struct ToneInfo +{ + QXmppRtpAudioChannel::Tone tone; + quint32 incomingStart; + quint32 outgoingStart; + bool finished; +}; + +static QPair<int, int> toneFreqs(QXmppRtpAudioChannel::Tone tone) +{ + switch (tone) { + case QXmppRtpAudioChannel::Tone_1: return qMakePair(697, 1209); + case QXmppRtpAudioChannel::Tone_2: return qMakePair(697, 1336); + case QXmppRtpAudioChannel::Tone_3: return qMakePair(697, 1477); + case QXmppRtpAudioChannel::Tone_A: return qMakePair(697, 1633); + case QXmppRtpAudioChannel::Tone_4: return qMakePair(770, 1209); + case QXmppRtpAudioChannel::Tone_5: return qMakePair(770, 1336); + case QXmppRtpAudioChannel::Tone_6: return qMakePair(770, 1477); + case QXmppRtpAudioChannel::Tone_B: return qMakePair(770, 1633); + case QXmppRtpAudioChannel::Tone_7: return qMakePair(852, 1209); + case QXmppRtpAudioChannel::Tone_8: return qMakePair(852, 1336); + case QXmppRtpAudioChannel::Tone_9: return qMakePair(852, 1477); + case QXmppRtpAudioChannel::Tone_C: return qMakePair(852, 1633); + case QXmppRtpAudioChannel::Tone_Star: return qMakePair(941, 1209); + case QXmppRtpAudioChannel::Tone_0: return qMakePair(941, 1336); + case QXmppRtpAudioChannel::Tone_Pound: return qMakePair(941, 1477); + case QXmppRtpAudioChannel::Tone_D: return qMakePair(941, 1633); + } + return qMakePair(0, 0); +} + +QByteArray renderTone(QXmppRtpAudioChannel::Tone tone, int clockrate, quint32 clockTick, qint64 samples) +{ + QPair<int,int> tf = toneFreqs(tone); + const float clockMult = 2.0 * M_PI / float(clockrate); + QByteArray chunk; + chunk.reserve(samples * SAMPLE_BYTES); + QDataStream output(&chunk, QIODevice::WriteOnly); + output.setByteOrder(QDataStream::LittleEndian); + for (quint32 i = 0; i < samples; ++i) { + quint16 val = 16383.0 * (sin(clockMult * clockTick * tf.first) + sin(clockMult * clockTick * tf.second)); + output << val; + clockTick++; + } + return chunk; +} + +class QXmppRtpAudioChannelPrivate +{ +public: + QXmppRtpAudioChannelPrivate(QXmppRtpAudioChannel *qq); + QXmppCodec *codecForPayloadType(const QXmppJinglePayloadType &payloadType); + + // signals + bool signalsEmitted; + qint64 writtenSinceLastEmit; + + // RTP + QHostAddress remoteHost; + quint16 remotePort; + + QByteArray incomingBuffer; + bool incomingBuffering; + QMap<int, QXmppCodec*> incomingCodecs; + int incomingMinimum; + int incomingMaximum; + // position of the head of the incoming buffer, in bytes + qint64 incomingPos; + quint16 incomingSequence; + + QByteArray outgoingBuffer; + quint16 outgoingChunk; + QXmppCodec *outgoingCodec; + bool outgoingMarker; + bool outgoingPayloadNumbered; + quint16 outgoingSequence; + quint32 outgoingStamp; + QTimer *outgoingTimer; + QList<ToneInfo> outgoingTones; + QXmppJinglePayloadType outgoingTonesType; + + quint32 outgoingSsrc; + QXmppJinglePayloadType payloadType; + +private: + QXmppRtpAudioChannel *q; +}; + +QXmppRtpAudioChannelPrivate::QXmppRtpAudioChannelPrivate(QXmppRtpAudioChannel *qq) + : signalsEmitted(false), + writtenSinceLastEmit(0), + incomingBuffering(true), + incomingMinimum(0), + incomingMaximum(0), + incomingPos(0), + incomingSequence(0), + outgoingCodec(0), + outgoingMarker(true), + outgoingPayloadNumbered(false), + outgoingSequence(1), + outgoingStamp(0), + outgoingSsrc(0), + q(qq) +{ + qRegisterMetaType<QXmppRtpAudioChannel::Tone>("QXmppRtpAudioChannel::Tone"); + outgoingSsrc = qrand(); +} + +/// Returns the audio codec for the given payload type. +/// + +QXmppCodec *QXmppRtpAudioChannelPrivate::codecForPayloadType(const QXmppJinglePayloadType &payloadType) +{ + if (payloadType.id() == G711u) + return new QXmppG711uCodec(payloadType.clockrate()); + else if (payloadType.id() == G711a) + return new QXmppG711aCodec(payloadType.clockrate()); +#ifdef QXMPP_USE_SPEEX + else if (payloadType.name().toLower() == "speex") + return new QXmppSpeexCodec(payloadType.clockrate()); +#endif + return 0; +} + +/// Creates a new RTP audio channel. +/// +/// \param parent + +QXmppRtpAudioChannel::QXmppRtpAudioChannel(QObject *parent) + : QIODevice(parent) +{ + d = new QXmppRtpAudioChannelPrivate(this); + QXmppLoggable *logParent = qobject_cast<QXmppLoggable*>(parent); + if (logParent) { + connect(this, SIGNAL(logMessage(QXmppLogger::MessageType,QString)), + logParent, SIGNAL(logMessage(QXmppLogger::MessageType,QString))); + } + d->outgoingTimer = new QTimer(this); + connect(d->outgoingTimer, SIGNAL(timeout()), this, SLOT(writeDatagram())); + + // set supported codecs + QXmppJinglePayloadType payload; + +#ifdef QXMPP_USE_SPEEX + payload.setId(96); + payload.setChannels(1); + payload.setName("speex"); + payload.setClockrate(8000); + m_outgoingPayloadTypes << payload; +#endif + + payload.setId(G711u); + payload.setChannels(1); + payload.setName("PCMU"); + payload.setClockrate(8000); + m_outgoingPayloadTypes << payload; + + payload.setId(G711a); + payload.setChannels(1); + payload.setName("PCMA"); + payload.setClockrate(8000); + m_outgoingPayloadTypes << payload; + + QMap<QString, QString> parameters; + parameters.insert("events", "0-15"); + payload.setId(101); + payload.setChannels(1); + payload.setName("telephone-event"); + payload.setClockrate(8000); + payload.setParameters(parameters); + m_outgoingPayloadTypes << payload; +} + +/// Destroys an RTP audio channel. +/// + +QXmppRtpAudioChannel::~QXmppRtpAudioChannel() +{ + foreach (QXmppCodec *codec, d->incomingCodecs) + delete codec; + if (d->outgoingCodec) + delete d->outgoingCodec; + delete d; +} + +/// Returns the number of bytes that are available for reading. +/// + +qint64 QXmppRtpAudioChannel::bytesAvailable() const +{ + return d->incomingBuffer.size(); +} + +/// Closes the RTP channel. +/// + +void QXmppRtpAudioChannel::close() +{ + d->outgoingTimer->stop(); + QIODevice::close(); +} + +/// Processes an incoming RTP packet. +/// +/// \param ba + +void QXmppRtpAudioChannel::datagramReceived(const QByteArray &ba) +{ + QXmppRtpPacket packet; + if (!packet.decode(ba)) + return; + +#ifdef QXMPP_DEBUG_RTP + logReceived(packet.toString()); +#endif + + // check sequence number +#if 0 + if (d->incomingSequence && packet.sequence != d->incomingSequence + 1) + warning(QString("RTP packet seq %1 is out of order, previous was %2") + .arg(QString::number(packet.sequence)) + .arg(QString::number(d->incomingSequence))); +#endif + d->incomingSequence = packet.sequence; + + // get or create codec + QXmppCodec *codec = 0; + if (!d->incomingCodecs.contains(packet.type)) { + foreach (const QXmppJinglePayloadType &payload, m_incomingPayloadTypes) { + if (packet.type == payload.id()) { + codec = d->codecForPayloadType(payload); + break; + } + } + if (codec) + d->incomingCodecs.insert(packet.type, codec); + else + warning(QString("Could not find codec for RTP type %1").arg(QString::number(packet.type))); + } else { + codec = d->incomingCodecs.value(packet.type); + } + if (!codec) + return; + + // determine packet's position in the buffer (in bytes) + qint64 packetOffset = 0; + if (!d->incomingBuffer.isEmpty()) { + packetOffset = packet.stamp * SAMPLE_BYTES - d->incomingPos; + if (packetOffset < 0) { +#ifdef QXMPP_DEBUG_RTP_BUFFER + warning(QString("RTP packet stamp %1 is too old, buffer start is %2") + .arg(QString::number(packet.stamp)) + .arg(QString::number(d->incomingPos))); +#endif + return; + } + } else { + d->incomingPos = packet.stamp * SAMPLE_BYTES + (d->incomingPos % SAMPLE_BYTES); + } + + // allocate space for new packet + // FIXME: this is wrong, we want the decoded data size! + qint64 packetLength = packet.payload.size(); + if (packetOffset + packetLength > d->incomingBuffer.size()) + d->incomingBuffer += QByteArray(packetOffset + packetLength - d->incomingBuffer.size(), 0); + QDataStream input(packet.payload); + QDataStream output(&d->incomingBuffer, QIODevice::WriteOnly); + output.device()->seek(packetOffset); + output.setByteOrder(QDataStream::LittleEndian); + codec->decode(input, output); + + // check whether we are running late + if (d->incomingBuffer.size() > d->incomingMaximum) + { + qint64 droppedSize = d->incomingBuffer.size() - d->incomingMinimum; + const int remainder = droppedSize % SAMPLE_BYTES; + if (remainder) + droppedSize -= remainder; +#ifdef QXMPP_DEBUG_RTP_BUFFER + warning(QString("Incoming RTP buffer is too full, dropping %1 bytes") + .arg(QString::number(droppedSize))); +#endif + d->incomingBuffer.remove(0, droppedSize); + d->incomingPos += droppedSize; + } + // check whether we have filled the initial buffer + if (d->incomingBuffer.size() >= d->incomingMinimum) + d->incomingBuffering = false; + if (!d->incomingBuffering) + emit readyRead(); +} + +void QXmppRtpAudioChannel::emitSignals() +{ + emit bytesWritten(d->writtenSinceLastEmit); + d->writtenSinceLastEmit = 0; + d->signalsEmitted = false; +} + +/// Returns true, as the RTP channel is a sequential device. +/// + +bool QXmppRtpAudioChannel::isSequential() const +{ + return true; +} + +QIODevice::OpenMode QXmppRtpAudioChannel::openMode() const +{ + return QIODevice::openMode(); +} + +qint64 QXmppRtpAudioChannel::readData(char * data, qint64 maxSize) +{ + // if we are filling the buffer, return empty samples + if (d->incomingBuffering) + { + // FIXME: if we are asked for a non-integer number of samples, + // we will return junk on next read as we don't increment d->incomingPos + memset(data, 0, maxSize); + return maxSize; + } + + qint64 readSize = qMin(maxSize, qint64(d->incomingBuffer.size())); + memcpy(data, d->incomingBuffer.constData(), readSize); + d->incomingBuffer.remove(0, readSize); + if (readSize < maxSize) + { +#ifdef QXMPP_DEBUG_RTP + debug(QString("QXmppRtpAudioChannel::readData missing %1 bytes").arg(QString::number(maxSize - readSize))); +#endif + memset(data + readSize, 0, maxSize - readSize); + } + + // add local DTMF echo + if (!d->outgoingTones.isEmpty()) { + const int headOffset = d->incomingPos % SAMPLE_BYTES; + const int samples = (headOffset + maxSize + SAMPLE_BYTES - 1) / SAMPLE_BYTES; + const QByteArray chunk = renderTone( + d->outgoingTones[0].tone, + d->payloadType.clockrate(), + d->incomingPos / SAMPLE_BYTES - d->outgoingTones[0].incomingStart, + samples); + memcpy(data, chunk.constData() + headOffset, maxSize); + } + + d->incomingPos += maxSize; + return maxSize; +} + +/// Returns the RTP channel's payload type. +/// +/// You can use this to determine the QAudioFormat to use with your +/// QAudioInput/QAudioOutput. + +QXmppJinglePayloadType QXmppRtpAudioChannel::payloadType() const +{ + return d->payloadType; +} + +void QXmppRtpAudioChannel::payloadTypesChanged() +{ + // delete incoming codecs + foreach (QXmppCodec *codec, d->incomingCodecs) + delete codec; + d->incomingCodecs.clear(); + + // delete outgoing codec + if (d->outgoingCodec) { + delete d->outgoingCodec; + d->outgoingCodec = 0; + } + + // create outgoing codec + foreach (const QXmppJinglePayloadType &outgoingType, m_outgoingPayloadTypes) { + // check for telephony events + if (outgoingType.name() == "telephone-event") { + d->outgoingTonesType = outgoingType; + } + else if (!d->outgoingCodec) { + QXmppCodec *codec = d->codecForPayloadType(outgoingType); + if (codec) { + d->payloadType = outgoingType; + d->outgoingCodec = codec; + } + } + } + + // size in bytes of an decoded packet + d->outgoingChunk = SAMPLE_BYTES * d->payloadType.ptime() * d->payloadType.clockrate() / 1000; + d->outgoingTimer->setInterval(d->payloadType.ptime()); + + d->incomingMinimum = d->outgoingChunk * 5; + d->incomingMaximum = d->outgoingChunk * 15; + + open(QIODevice::ReadWrite | QIODevice::Unbuffered); +} + +/// Returns the position in the received audio data. + +qint64 QXmppRtpAudioChannel::pos() const +{ + return d->incomingPos; +} + +/// Seeks in the received audio data. +/// +/// Seeking backwards will result in empty samples being added at the start +/// of the buffer. +/// +/// \param pos + +bool QXmppRtpAudioChannel::seek(qint64 pos) +{ + qint64 delta = pos - d->incomingPos; + if (delta < 0) + d->incomingBuffer.prepend(QByteArray(-delta, 0)); + else + d->incomingBuffer.remove(0, delta); + d->incomingPos = pos; + return true; +} + +/// Starts sending the specified DTMF tone. +/// +/// \param tone + +void QXmppRtpAudioChannel::startTone(QXmppRtpAudioChannel::Tone tone) +{ + ToneInfo info; + info.tone = tone; + info.incomingStart = d->incomingPos / SAMPLE_BYTES; + info.outgoingStart = d->outgoingStamp; + info.finished = false; + d->outgoingTones << info; +} + +/// Stops sending the specified DTMF tone. +/// +/// \param tone + +void QXmppRtpAudioChannel::stopTone(QXmppRtpAudioChannel::Tone tone) +{ + for (int i = 0; i < d->outgoingTones.size(); ++i) { + if (d->outgoingTones[i].tone == tone) { + d->outgoingTones[i].finished = true; + break; + } + } +} + +qint64 QXmppRtpAudioChannel::writeData(const char * data, qint64 maxSize) +{ + if (!d->outgoingCodec) { + warning("QXmppRtpAudioChannel::writeData before codec was set"); + return -1; + } + + d->outgoingBuffer += QByteArray::fromRawData(data, maxSize); + + // start sending audio chunks + if (!d->outgoingTimer->isActive()) + d->outgoingTimer->start(); + + return maxSize; +} + +void QXmppRtpAudioChannel::writeDatagram() +{ + // read audio chunk + QByteArray chunk; + if (d->outgoingBuffer.size() < d->outgoingChunk) { +#ifdef QXMPP_DEBUG_RTP_BUFFER + warning("Outgoing RTP buffer is starved"); +#endif + chunk = QByteArray(d->outgoingChunk, 0); + } else { + chunk = d->outgoingBuffer.left(d->outgoingChunk); + d->outgoingBuffer.remove(0, d->outgoingChunk); + } + + bool sendAudio = true; + if (!d->outgoingTones.isEmpty()) { + const quint32 packetTicks = (d->payloadType.clockrate() * d->payloadType.ptime()) / 1000; + const ToneInfo info = d->outgoingTones[0]; + + if (d->outgoingTonesType.id()) { + // send RFC 2833 DTMF + QXmppRtpPacket packet; + packet.version = RTP_VERSION; + packet.marker = (info.outgoingStart == d->outgoingStamp); + packet.type = d->outgoingTonesType.id(); + packet.sequence = d->outgoingSequence; + packet.stamp = info.outgoingStart; + packet.ssrc = d->outgoingSsrc; + + QDataStream output(&packet.payload, QIODevice::WriteOnly); + output << quint8(info.tone); + output << quint8(info.finished ? 0x80 : 0x00); + output << quint16(d->outgoingStamp + packetTicks - info.outgoingStart); +#ifdef QXMPP_DEBUG_RTP + logSent(packet.toString()); +#endif + emit sendDatagram(packet.encode()); + d->outgoingSequence++; + d->outgoingStamp += packetTicks; + + sendAudio = false; + } else { + // generate in-band DTMF + chunk = renderTone(info.tone, d->payloadType.clockrate(), d->outgoingStamp - info.outgoingStart, packetTicks); + } + + // if the tone is finished, remove it + if (info.finished) + d->outgoingTones.removeFirst(); + } + + if (sendAudio) { + // send audio data + QXmppRtpPacket packet; + packet.version = RTP_VERSION; + if (d->outgoingMarker) + { + packet.marker = true; + d->outgoingMarker = false; + } else { + packet.marker = false; + } + packet.type = d->payloadType.id(); + packet.sequence = d->outgoingSequence; + packet.stamp = d->outgoingStamp; + packet.ssrc = d->outgoingSsrc; + + // encode audio chunk + QDataStream input(chunk); + input.setByteOrder(QDataStream::LittleEndian); + QDataStream output(&packet.payload, QIODevice::WriteOnly); + const qint64 packetTicks = d->outgoingCodec->encode(input, output); + +#ifdef QXMPP_DEBUG_RTP + logSent(packet.toString()); +#endif + emit sendDatagram(packet.encode()); + d->outgoingSequence++; + d->outgoingStamp += packetTicks; + } + + // queue signals + d->writtenSinceLastEmit += chunk.size(); + if (!d->signalsEmitted && !signalsBlocked()) { + d->signalsEmitted = true; + QMetaObject::invokeMethod(this, "emitSignals", Qt::QueuedConnection); + } +} + +/** Constructs a null video frame. + */ +QXmppVideoFrame::QXmppVideoFrame() + : m_bytesPerLine(0), + m_height(0), + m_mappedBytes(0), + m_pixelFormat(Format_Invalid), + m_width(0) +{ +} + +/** Constructs a video frame of the given pixel format and size in pixels. + * + * @param bytes + * @param size + * @param bytesPerLine + * @param format + */ +QXmppVideoFrame::QXmppVideoFrame(int bytes, const QSize &size, int bytesPerLine, PixelFormat format) + : m_bytesPerLine(bytesPerLine), + m_height(size.height()), + m_mappedBytes(bytes), + m_pixelFormat(format), + m_width(size.width()) +{ + m_data.resize(bytes); +} + +uchar *QXmppVideoFrame::bits() +{ + return (uchar*)m_data.data(); +} + +const uchar *QXmppVideoFrame::bits() const +{ + return (const uchar*)m_data.constData(); +} + +/** Returns the number of bytes in a scan line. + */ +int QXmppVideoFrame::bytesPerLine() const +{ + return m_bytesPerLine; +} + +/** Returns the height of a video frame. + */ +int QXmppVideoFrame::height() const +{ + return m_height; +} + +/** Returns true if the frame is valid. + */ +bool QXmppVideoFrame::isValid() const +{ + return m_pixelFormat != Format_Invalid && + m_height > 0 && m_width > 0 && + m_mappedBytes > 0; +} + +/** Returns the number of bytes occupied by the mapped frame data. + */ +int QXmppVideoFrame::mappedBytes() const +{ + return m_mappedBytes; +} + +/** Returns the color format of a video frame. + */ +QXmppVideoFrame::PixelFormat QXmppVideoFrame::pixelFormat() const +{ + return m_pixelFormat; +} + +/** Returns the size of a video frame. + */ +QSize QXmppVideoFrame::size() const +{ + return QSize(m_width, m_height); +} + +/** Returns the width of a video frame. + */ +int QXmppVideoFrame::width() const +{ + return m_width; +} + +class QXmppRtpVideoChannelPrivate +{ +public: + QXmppRtpVideoChannelPrivate(); + QMap<int, QXmppVideoDecoder*> decoders; + QXmppVideoEncoder *encoder; + QList<QXmppVideoFrame> frames; + + // local + QXmppVideoFormat outgoingFormat; + quint8 outgoingId; + quint16 outgoingSequence; + quint32 outgoingStamp; + quint32 outgoingSsrc; +}; + +QXmppRtpVideoChannelPrivate::QXmppRtpVideoChannelPrivate() + : encoder(0), + outgoingId(0), + outgoingSequence(1), + outgoingStamp(0), + outgoingSsrc(0) +{ + outgoingSsrc = qrand(); +} + +QXmppRtpVideoChannel::QXmppRtpVideoChannel(QObject *parent) + : QXmppLoggable(parent) +{ + d = new QXmppRtpVideoChannelPrivate; + d->outgoingFormat.setFrameRate(15.0); + d->outgoingFormat.setFrameSize(QSize(320, 240)); + d->outgoingFormat.setPixelFormat(QXmppVideoFrame::Format_YUYV); + + // set supported codecs + QXmppVideoEncoder *encoder; + QXmppJinglePayloadType payload; + Q_UNUSED(encoder); + Q_UNUSED(payload); + +#ifdef QXMPP_USE_VPX + encoder = new QXmppVpxEncoder; + encoder->setFormat(d->outgoingFormat); + payload.setId(96); + payload.setName("vp8"); + payload.setClockrate(90000); + payload.setParameters(encoder->parameters()); + m_outgoingPayloadTypes << payload; + delete encoder; +#endif + +#ifdef QXMPP_USE_THEORA + encoder = new QXmppTheoraEncoder; + encoder->setFormat(d->outgoingFormat); + payload.setId(97); + payload.setName("theora"); + payload.setClockrate(90000); + payload.setParameters(encoder->parameters()); + m_outgoingPayloadTypes << payload; + delete encoder; +#endif +} + +QXmppRtpVideoChannel::~QXmppRtpVideoChannel() +{ + foreach (QXmppVideoDecoder *decoder, d->decoders) + delete decoder; + if (d->encoder) + delete d->encoder; + delete d; +} + +/// Closes the RTP channel. +/// + +void QXmppRtpVideoChannel::close() +{ +} + +/// Processes an incoming RTP video packet. +/// +/// \param ba + +void QXmppRtpVideoChannel::datagramReceived(const QByteArray &ba) +{ + QXmppRtpPacket packet; + if (!packet.decode(ba)) + return; + +#ifdef QXMPP_DEBUG_RTP + logReceived(packet.toString()); +#endif + + // get codec + QXmppVideoDecoder *decoder = d->decoders.value(packet.type); + if (!decoder) + return; + d->frames << decoder->handlePacket(packet); +} + +QXmppVideoFormat QXmppRtpVideoChannel::decoderFormat() const +{ + if (d->decoders.isEmpty()) + return QXmppVideoFormat(); + const int key = d->decoders.keys().first(); + return d->decoders.value(key)->format(); +} + +QXmppVideoFormat QXmppRtpVideoChannel::encoderFormat() const +{ + return d->outgoingFormat; +} + +void QXmppRtpVideoChannel::setEncoderFormat(const QXmppVideoFormat &format) +{ + if (d->encoder && !d->encoder->setFormat(format)) + return; + d->outgoingFormat = format; +} + +QIODevice::OpenMode QXmppRtpVideoChannel::openMode() const +{ + QIODevice::OpenMode mode = QIODevice::NotOpen; + if (!d->decoders.isEmpty()) + mode |= QIODevice::ReadOnly; + if (d->encoder) + mode |= QIODevice::WriteOnly; + return mode; +} + +void QXmppRtpVideoChannel::payloadTypesChanged() +{ + // refresh decoders + foreach (QXmppVideoDecoder *decoder, d->decoders) + delete decoder; + d->decoders.clear(); + foreach (const QXmppJinglePayloadType &payload, m_incomingPayloadTypes) { + QXmppVideoDecoder *decoder = 0; + if (false) + {} +#ifdef QXMPP_USE_THEORA + else if (payload.name().toLower() == "theora") + decoder = new QXmppTheoraDecoder; +#endif +#ifdef QXMPP_USE_VPX + else if (payload.name().toLower() == "vp8") + decoder = new QXmppVpxDecoder; +#endif + if (decoder) { + decoder->setParameters(payload.parameters()); + d->decoders.insert(payload.id(), decoder); + } + } + + // refresh encoder + if (d->encoder) { + delete d->encoder; + d->encoder = 0; + } + foreach (const QXmppJinglePayloadType &payload, m_outgoingPayloadTypes) { + QXmppVideoEncoder *encoder = 0; + if (false) + {} +#ifdef QXMPP_USE_THEORA + else if (payload.name().toLower() == "theora") + encoder = new QXmppTheoraEncoder; +#endif +#ifdef QXMPP_USE_VPX + else if (payload.name().toLower() == "vp8") { + encoder = new QXmppVpxEncoder; + } +#endif + if (encoder) { + encoder->setFormat(d->outgoingFormat); + d->encoder = encoder; + d->outgoingId = payload.id(); + break; + } + } +} + +QList<QXmppVideoFrame> QXmppRtpVideoChannel::readFrames() +{ + const QList<QXmppVideoFrame> frames = d->frames; + d->frames.clear(); + return frames; +} + +void QXmppRtpVideoChannel::writeFrame(const QXmppVideoFrame &frame) +{ + if (!d->encoder) { + warning("QXmppRtpVideoChannel::writeFrame before codec was set"); + return; + } + + QXmppRtpPacket packet; + packet.version = RTP_VERSION; + packet.marker = false; + packet.type = d->outgoingId; + packet.ssrc = d->outgoingSsrc; + foreach (const QByteArray &payload, d->encoder->handleFrame(frame)) { + packet.sequence = d->outgoingSequence++; + packet.stamp = d->outgoingStamp; + packet.payload = payload; +#ifdef QXMPP_DEBUG_RTP + logSent(packet.toString()); +#endif + emit sendDatagram(packet.encode()); + } + d->outgoingStamp += 1; +} + diff --git a/src/base/QXmppRtpChannel.h b/src/base/QXmppRtpChannel.h new file mode 100644 index 00000000..0af6596c --- /dev/null +++ b/src/base/QXmppRtpChannel.h @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPRTPCHANNEL_H +#define QXMPPRTPCHANNEL_H + +#include <QIODevice> +#include <QSize> + +#include "QXmppJingleIq.h" +#include "QXmppLogger.h" + +class QXmppCodec; +class QXmppJinglePayloadType; +class QXmppRtpAudioChannelPrivate; +class QXmppRtpVideoChannelPrivate; + +/// \brief The QXmppRtpPacket class represents an RTP packet. +/// + +class QXmppRtpPacket +{ +public: + bool decode(const QByteArray &ba); + QByteArray encode() const; + QString toString() const; + + quint8 version; + bool marker; + quint8 type; + quint32 ssrc; + QList<quint32> csrc; + quint16 sequence; + quint32 stamp; + QByteArray payload; +}; + +class QXmppRtpChannel +{ +public: + QXmppRtpChannel(); + + virtual void close() = 0; + virtual QIODevice::OpenMode openMode() const = 0; + QList<QXmppJinglePayloadType> localPayloadTypes(); + void setRemotePayloadTypes(const QList<QXmppJinglePayloadType> &remotePayloadTypes); + +protected: + virtual void payloadTypesChanged(); + + QList<QXmppJinglePayloadType> m_incomingPayloadTypes; + QList<QXmppJinglePayloadType> m_outgoingPayloadTypes; + bool m_outgoingPayloadNumbered; +}; + +/// \brief The QXmppRtpAudioChannel class represents an RTP audio channel to a remote party. +/// +/// It acts as a QIODevice so that you can read / write audio samples, for +/// instance using a QAudioOutput and a QAudioInput. +/// +/// \note THIS API IS NOT FINALIZED YET + +class QXmppRtpAudioChannel : public QIODevice, public QXmppRtpChannel +{ + Q_OBJECT + Q_ENUMS(Tone) + +public: + /// This enum is used to describe a DTMF tone. + enum Tone { + Tone_0 = 0, ///< Tone for the 0 key. + Tone_1, ///< Tone for the 1 key. + Tone_2, ///< Tone for the 2 key. + Tone_3, ///< Tone for the 3 key. + Tone_4, ///< Tone for the 4 key. + Tone_5, ///< Tone for the 5 key. + Tone_6, ///< Tone for the 6 key. + Tone_7, ///< Tone for the 7 key. + Tone_8, ///< Tone for the 8 key. + Tone_9, ///< Tone for the 9 key. + Tone_Star, ///< Tone for the * key. + Tone_Pound, ///< Tone for the # key. + Tone_A, ///< Tone for the A key. + Tone_B, ///< Tone for the B key. + Tone_C, ///< Tone for the C key. + Tone_D ///< Tone for the D key. + }; + + QXmppRtpAudioChannel(QObject *parent = 0); + ~QXmppRtpAudioChannel(); + + QXmppJinglePayloadType payloadType() const; + + /// \cond + qint64 bytesAvailable() const; + void close(); + bool isSequential() const; + QIODevice::OpenMode openMode() const; + qint64 pos() const; + bool seek(qint64 pos); + /// \endcond + +signals: + /// \brief This signal is emitted when a datagram needs to be sent. + void sendDatagram(const QByteArray &ba); + + /// \brief This signal is emitted to send logging messages. + void logMessage(QXmppLogger::MessageType type, const QString &msg); + +public slots: + void datagramReceived(const QByteArray &ba); + void startTone(QXmppRtpAudioChannel::Tone tone); + void stopTone(QXmppRtpAudioChannel::Tone tone); + +protected: + /// \cond + void debug(const QString &message) + { + emit logMessage(QXmppLogger::DebugMessage, qxmpp_loggable_trace(message)); + } + + void warning(const QString &message) + { + emit logMessage(QXmppLogger::WarningMessage, qxmpp_loggable_trace(message)); + } + + void logReceived(const QString &message) + { + emit logMessage(QXmppLogger::ReceivedMessage, qxmpp_loggable_trace(message)); + } + + void logSent(const QString &message) + { + emit logMessage(QXmppLogger::SentMessage, qxmpp_loggable_trace(message)); + } + + void payloadTypesChanged(); + qint64 readData(char * data, qint64 maxSize); + qint64 writeData(const char * data, qint64 maxSize); + /// \endcond + +private slots: + void emitSignals(); + void writeDatagram(); + +private: + friend class QXmppRtpAudioChannelPrivate; + QXmppRtpAudioChannelPrivate * d; +}; + +/// \brief The QXmppVideoFrame class provides a representation of a frame of video data. +/// +/// \note THIS API IS NOT FINALIZED YET + +class QXmppVideoFrame +{ +public: + enum PixelFormat { + Format_Invalid = 0, + Format_RGB32 = 3, + Format_RGB24 = 4, + Format_YUV420P = 18, + Format_UYVY = 20, + Format_YUYV = 21, + }; + + QXmppVideoFrame(); + QXmppVideoFrame(int bytes, const QSize &size, int bytesPerLine, PixelFormat format); + uchar *bits(); + const uchar *bits() const; + int bytesPerLine() const; + int height() const; + bool isValid() const; + int mappedBytes() const; + PixelFormat pixelFormat() const; + QSize size() const; + int width() const; + +private: + int m_bytesPerLine; + QByteArray m_data; + int m_height; + int m_mappedBytes; + PixelFormat m_pixelFormat; + int m_width; +}; + +class QXmppVideoFormat +{ +public: + int frameHeight() const { + return m_frameSize.height(); + } + + int frameWidth() const { + return m_frameSize.width(); + } + + qreal frameRate() const { + return m_frameRate; + } + + void setFrameRate(qreal frameRate) { + m_frameRate = frameRate; + } + + QSize frameSize() const { + return m_frameSize; + } + + void setFrameSize(const QSize &frameSize) { + m_frameSize = frameSize; + } + + QXmppVideoFrame::PixelFormat pixelFormat() const { + return m_pixelFormat; + } + + void setPixelFormat(QXmppVideoFrame::PixelFormat pixelFormat) { + m_pixelFormat = pixelFormat; + } + +private: + qreal m_frameRate; + QSize m_frameSize; + QXmppVideoFrame::PixelFormat m_pixelFormat; +}; + + +/// \brief The QXmppRtpVideoChannel class represents an RTP video channel to a remote party. +/// +/// \note THIS API IS NOT FINALIZED YET + +class QXmppRtpVideoChannel : public QXmppLoggable, public QXmppRtpChannel +{ + Q_OBJECT + +public: + QXmppRtpVideoChannel(QObject *parent = 0); + ~QXmppRtpVideoChannel(); + + // incoming stream + QXmppVideoFormat decoderFormat() const; + QList<QXmppVideoFrame> readFrames(); + + // outgoing stream + QXmppVideoFormat encoderFormat() const; + void setEncoderFormat(const QXmppVideoFormat &format); + void writeFrame(const QXmppVideoFrame &frame); + + QIODevice::OpenMode openMode() const; + void close(); + +signals: + /// \brief This signal is emitted when a datagram needs to be sent. + void sendDatagram(const QByteArray &ba); + +public slots: + void datagramReceived(const QByteArray &ba); + +protected: + void payloadTypesChanged(); + +private: + friend class QXmppRtpVideoChannelPrivate; + QXmppRtpVideoChannelPrivate * d; +}; + +#endif diff --git a/src/base/QXmppSaslAuth.cpp b/src/base/QXmppSaslAuth.cpp new file mode 100644 index 00000000..a528cd6d --- /dev/null +++ b/src/base/QXmppSaslAuth.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <cstdlib> + +#include <QCryptographicHash> + +#include "QXmppSaslAuth.h" +#include "QXmppUtils.h" + +QByteArray QXmppSaslDigestMd5::authzid() const +{ + return m_authzid; +} + +void QXmppSaslDigestMd5::setAuthzid(const QByteArray &authzid) +{ + m_authzid = authzid; +} + +QByteArray QXmppSaslDigestMd5::cnonce() const +{ + return m_cnonce; +} + +void QXmppSaslDigestMd5::setCnonce(const QByteArray &cnonce) +{ + m_cnonce = cnonce; +} + +QByteArray QXmppSaslDigestMd5::digestUri() const +{ + return m_digestUri; +} + +void QXmppSaslDigestMd5::setDigestUri(const QByteArray &digestUri) +{ + m_digestUri = digestUri; +} + +QByteArray QXmppSaslDigestMd5::nc() const +{ + return m_nc; +} + +void QXmppSaslDigestMd5::setNc(const QByteArray &nc) +{ + m_nc = nc; +} + +QByteArray QXmppSaslDigestMd5::nonce() const +{ + return m_nonce; +} + +void QXmppSaslDigestMd5::setNonce(const QByteArray &nonce) +{ + m_nonce = nonce; +} + +QByteArray QXmppSaslDigestMd5::qop() const +{ + return m_qop; +} + +void QXmppSaslDigestMd5::setQop(const QByteArray &qop) +{ + m_qop = qop; +} + +void QXmppSaslDigestMd5::setSecret(const QByteArray &secret) +{ + m_secret = secret; +} + +QByteArray QXmppSaslDigestMd5::generateNonce() +{ + QByteArray nonce = generateRandomBytes(32); + + // The random data can the '=' char is not valid as it is a delimiter, + // so to be safe, base64 the nonce + return nonce.toBase64(); +} + +/// Calculate digest response for use with XMPP/SASL. +/// +/// \param A2 +/// + +QByteArray QXmppSaslDigestMd5::calculateDigest(const QByteArray &A2) const +{ + QByteArray ha1 = m_secret + ':' + m_nonce + ':' + m_cnonce; + + if (!m_authzid.isEmpty()) + ha1 += ':' + m_authzid; + + return calculateDigest(ha1, A2); +} + +/// Calculate generic digest response. +/// +/// \param A1 +/// \param A2 +/// + +QByteArray QXmppSaslDigestMd5::calculateDigest(const QByteArray &A1, const QByteArray &A2) const +{ + QByteArray HA1 = QCryptographicHash::hash(A1, QCryptographicHash::Md5).toHex(); + QByteArray HA2 = QCryptographicHash::hash(A2, QCryptographicHash::Md5).toHex(); + QByteArray KD; + if (m_qop == "auth" || m_qop == "auth-int") + KD = HA1 + ':' + m_nonce + ':' + m_nc + ':' + m_cnonce + ':' + m_qop + ':' + HA2; + else + KD = HA1 + ':' + m_nonce + ':' + HA2; + return QCryptographicHash::hash(KD, QCryptographicHash::Md5).toHex(); +} + +QMap<QByteArray, QByteArray> QXmppSaslDigestMd5::parseMessage(const QByteArray &ba) +{ + QMap<QByteArray, QByteArray> map; + int startIndex = 0; + int pos = 0; + while ((pos = ba.indexOf("=", startIndex)) >= 0) + { + // key get name and skip equals + const QByteArray key = ba.mid(startIndex, pos - startIndex).trimmed(); + pos++; + + // check whether string is quoted + if (ba.at(pos) == '"') + { + // skip opening quote + pos++; + int endPos = ba.indexOf('"', pos); + // skip quoted quotes + while (endPos >= 0 && ba.at(endPos - 1) == '\\') + endPos = ba.indexOf('"', endPos + 1); + if (endPos < 0) + { + qWarning("Unfinished quoted string"); + return map; + } + // unquote + QByteArray value = ba.mid(pos, endPos - pos); + value.replace("\\\"", "\""); + value.replace("\\\\", "\\"); + map[key] = value; + // skip closing quote and comma + startIndex = endPos + 2; + } else { + // non-quoted string + int endPos = ba.indexOf(',', pos); + if (endPos < 0) + endPos = ba.size(); + map[key] = ba.mid(pos, endPos - pos); + // skip comma + startIndex = endPos + 1; + } + } + return map; +} + +QByteArray QXmppSaslDigestMd5::serializeMessage(const QMap<QByteArray, QByteArray> &map) +{ + QByteArray ba; + foreach (const QByteArray &key, map.keys()) + { + if (!ba.isEmpty()) + ba.append(','); + ba.append(key + "="); + QByteArray value = map[key]; + const char *separators = "()<>@,;:\\\"/[]?={} \t"; + bool quote = false; + for (const char *c = separators; *c; c++) + { + if (value.contains(*c)) + { + quote = true; + break; + } + } + if (quote) + { + value.replace("\\", "\\\\"); + value.replace("\"", "\\\""); + ba.append("\"" + value + "\""); + } + else + ba.append(value); + } + return ba; +} + diff --git a/src/base/QXmppSaslAuth.h b/src/base/QXmppSaslAuth.h new file mode 100644 index 00000000..409636cb --- /dev/null +++ b/src/base/QXmppSaslAuth.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPSASLAUTH_H +#define QXMPPSASLAUTH_H + +#include <QByteArray> +#include <QMap> + +class QXmppSaslDigestMd5 +{ +public: + QByteArray authzid() const; + void setAuthzid(const QByteArray &cnonce); + + QByteArray cnonce() const; + void setCnonce(const QByteArray &cnonce); + + QByteArray digestUri() const; + void setDigestUri(const QByteArray &digestUri); + + QByteArray nc() const; + void setNc(const QByteArray &nc); + + QByteArray nonce() const; + void setNonce(const QByteArray &nonce); + + QByteArray qop() const; + void setQop(const QByteArray &qop); + + void setSecret(const QByteArray &secret); + + QByteArray calculateDigest(const QByteArray &A2) const; + QByteArray calculateDigest(const QByteArray &A1, const QByteArray &A2) const; + static QByteArray generateNonce(); + + // message parsing and serialization + static QMap<QByteArray, QByteArray> parseMessage(const QByteArray &ba); + static QByteArray serializeMessage(const QMap<QByteArray, QByteArray> &map); + +private: + QByteArray m_authzid; + QByteArray m_cnonce; + QByteArray m_digestUri; + QByteArray m_nc; + QByteArray m_nonce; + QByteArray m_qop; + QByteArray m_secret; +}; + +#endif diff --git a/src/base/QXmppSessionIq.cpp b/src/base/QXmppSessionIq.cpp new file mode 100644 index 00000000..6672cc0e --- /dev/null +++ b/src/base/QXmppSessionIq.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> +#include <QXmlStreamWriter> + +#include "QXmppSessionIq.h" +#include "QXmppConstants.h" +#include "QXmppUtils.h" + +bool QXmppSessionIq::isSessionIq(const QDomElement &element) +{ + QDomElement sessionElement = element.firstChildElement("session"); + return (sessionElement.namespaceURI() == ns_session); +} + +void QXmppSessionIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("session");; + writer->writeAttribute("xmlns", ns_session); + writer->writeEndElement(); +} + diff --git a/src/base/QXmppSessionIq.h b/src/base/QXmppSessionIq.h new file mode 100644 index 00000000..2cbcd58c --- /dev/null +++ b/src/base/QXmppSessionIq.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPSESSIONIQ_H +#define QXMPPSESSIONIQ_H + +#include "QXmppIq.h" + +/// \brief The QXmppSessionIq class represents an IQ used for session +/// establishment as defined by RFC 5921. +/// +/// \ingroup Stanzas + +class QXmppSessionIq : public QXmppIq +{ +public: + /// \cond + static bool isSessionIq(const QDomElement &element); + /// \endcond + +private: + /// \cond + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond +}; + +#endif // QXMPPSESSION_H diff --git a/src/base/QXmppSocks.cpp b/src/base/QXmppSocks.cpp new file mode 100644 index 00000000..1c6f162c --- /dev/null +++ b/src/base/QXmppSocks.cpp @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDataStream> +#include <QEventLoop> +#include <QTcpServer> +#include <QTcpSocket> +#include <QTimer> + +#include "QXmppSocks.h" + +const static char SocksVersion = 5; + +enum AuthenticationMethod { + NoAuthentication = 0, + GSSAPI = 1, + UsernamePassword = 2, +}; + +enum Command { + ConnectCommand = 1, + BindCommand = 2, + AssociateCommand = 3, +}; + +enum AddressType { + IPv4Address = 1, + DomainName = 3, + IPv6Address = 4, +}; + +enum ReplyType { + Succeeded = 0, + SocksFailure = 1, + ConnectionNotAllowed = 2, + NetworkUnreachable = 3, + HostUnreachable = 4, + ConnectionRefused = 5, + TtlExpired = 6, + CommandNotSupported = 7, + AddressTypeNotSupported = 8, +}; + +enum State { + ConnectState = 0, + CommandState = 1, + ReadyState = 2, +}; + +static QByteArray encodeHostAndPort(quint8 type, const QByteArray &host, quint16 port) +{ + QByteArray buffer; + QDataStream stream(&buffer, QIODevice::WriteOnly); + // set host name + quint8 hostLength = host.size(); + stream << type; + stream << hostLength; + stream.writeRawData(host.constData(), hostLength); + // set port + stream << port; + return buffer; +} + +static bool parseHostAndPort(const QByteArray buffer, quint8 &type, QByteArray &host, quint16 &port) +{ + if (buffer.size() < 4) + return false; + + QDataStream stream(buffer); + // get host name + quint8 hostLength; + stream >> type; + stream >> hostLength; + if (buffer.size() < hostLength + 4) + { + qWarning("Invalid host length"); + return false; + } + host.resize(hostLength); + stream.readRawData(host.data(), hostLength); + // get port + stream >> port; + return true; +} + +QXmppSocksClient::QXmppSocksClient(const QHostAddress &proxyAddress, quint16 proxyPort, QObject *parent) + : QTcpSocket(parent), + m_proxyAddress(proxyAddress), + m_proxyPort(proxyPort), + m_step(ConnectState) +{ + connect(this, SIGNAL(connected()), this, SLOT(slotConnected())); + connect(this, SIGNAL(readyRead()), this, SLOT(slotReadyRead())); +} + +void QXmppSocksClient::connectToHost(const QString &hostName, quint16 hostPort) +{ + m_hostName = hostName; + m_hostPort = hostPort; + QTcpSocket::connectToHost(m_proxyAddress, m_proxyPort); +} + +void QXmppSocksClient::slotConnected() +{ + m_step = ConnectState; + + // disconnect from signal + disconnect(this, SIGNAL(connected()), this, SLOT(slotConnected())); + + // send connect to server + QByteArray buffer; + buffer.resize(3); + buffer[0] = SocksVersion; + buffer[1] = 0x01; // number of methods + buffer[2] = NoAuthentication; + write(buffer); +} + +void QXmppSocksClient::slotReadyRead() +{ + if (m_step == ConnectState) + { + m_step++; + + // receive connect to server response + QByteArray buffer = readAll(); + if (buffer.size() != 2 || buffer.at(0) != SocksVersion || buffer.at(1) != NoAuthentication) + { + qWarning("QXmppSocksClient received an invalid response during handshake"); + close(); + return; + } + + // send CONNECT command + buffer.resize(3); + buffer[0] = SocksVersion; + buffer[1] = ConnectCommand; + buffer[2] = 0x00; // reserved + buffer.append(encodeHostAndPort( + DomainName, + m_hostName.toAscii(), + m_hostPort)); + write(buffer); + + } else if (m_step == CommandState) { + m_step++; + + // disconnect from signal + disconnect(this, SIGNAL(readyRead()), this, SLOT(slotReadyRead())); + + // receive CONNECT response + QByteArray buffer = readAll(); + if (buffer.size() < 6 || + buffer.at(0) != SocksVersion || + buffer.at(1) != Succeeded || + buffer.at(2) != 0) + { + qWarning("QXmppSocksClient received an invalid response to CONNECT command"); + close(); + return; + } + + // parse host + quint8 hostType; + QByteArray hostName; + quint16 hostPort; + if (!parseHostAndPort(buffer.mid(3), hostType, hostName, hostPort)) + { + qWarning("QXmppSocksClient could not parse type/host/port"); + close(); + return; + } + // FIXME : what do we do with the resulting name / port? + + // notify of connection + emit ready(); + } +} + +bool QXmppSocksClient::waitForReady(int msecs) +{ + QEventLoop loop; + connect(this, SIGNAL(disconnected()), &loop, SLOT(quit())); + connect(this, SIGNAL(ready()), &loop, SLOT(quit())); + QTimer::singleShot(msecs, &loop, SLOT(quit())); + loop.exec(); + + if (m_step == ReadyState && isValid()) + return true; + else + return false; +} + +QXmppSocksServer::QXmppSocksServer(QObject *parent) + : QObject(parent) +{ + m_server = new QTcpServer(this); + connect(m_server, SIGNAL(newConnection()), this, SLOT(slotNewConnection())); +} + +void QXmppSocksServer::close() +{ + m_server->close(); +} + +bool QXmppSocksServer::listen(const QHostAddress &address, quint16 port) +{ + return m_server->listen(address, port); +} + +bool QXmppSocksServer::isListening() const +{ + return m_server->isListening(); +} + +QHostAddress QXmppSocksServer::serverAddress() const +{ + return m_server->serverAddress(); +} + +quint16 QXmppSocksServer::serverPort() const +{ + return m_server->serverPort(); +} + +void QXmppSocksServer::slotNewConnection() +{ + QTcpSocket *socket = m_server->nextPendingConnection(); + if (!socket) + return; + + // register socket + m_states.insert(socket, ConnectState); + connect(socket, SIGNAL(readyRead()), this, SLOT(slotReadyRead())); +} + +void QXmppSocksServer::slotReadyRead() +{ + QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender()); + if (!socket || !m_states.contains(socket)) + return; + + if (m_states.value(socket) == ConnectState) + { + m_states.insert(socket, CommandState); + + // receive connect to server request + QByteArray buffer = socket->readAll(); + if (buffer.size() < 3 || + buffer.at(0) != SocksVersion || + buffer.at(1) + 2 != buffer.size()) + { + qWarning("QXmppSocksServer received invalid handshake"); + socket->close(); + return; + } + + // check authentication method + bool foundMethod = false; + for (int i = 2; i < buffer.size(); i++) + { + if (buffer.at(i) == NoAuthentication) + { + foundMethod = true; + break; + } + } + if (!foundMethod) + { + qWarning("QXmppSocksServer received bad authentication method"); + socket->close(); + return; + } + + // send connect to server response + buffer.resize(2); + buffer[0] = SocksVersion; + buffer[1] = NoAuthentication; + socket->write(buffer); + + } else if (m_states.value(socket) == CommandState) { + m_states.insert(socket, ReadyState); + + // disconnect from signals + disconnect(socket, SIGNAL(readyRead()), this, SLOT(slotReadyRead())); + + // receive command + QByteArray buffer = socket->readAll(); + if (buffer.size() < 4 || + buffer.at(0) != SocksVersion || + buffer.at(1) != ConnectCommand || + buffer.at(2) != 0x00) + { + qWarning("QXmppSocksServer received an invalid command"); + socket->close(); + return; + } + + // parse host + quint8 hostType; + QByteArray hostName; + quint16 hostPort; + if (!parseHostAndPort(buffer.mid(3), hostType, hostName, hostPort)) + { + qWarning("QXmppSocksServer could not parse type/host/port"); + socket->close(); + return; + } + + // notify of connection + emit newConnection(socket, hostName, hostPort); + + // send response + buffer.resize(3); + buffer[0] = SocksVersion; + buffer[1] = Succeeded; + buffer[2] = 0x00; + buffer.append(encodeHostAndPort( + DomainName, + hostName, + hostPort)); + socket->write(buffer); + } +} + diff --git a/src/base/QXmppSocks.h b/src/base/QXmppSocks.h new file mode 100644 index 00000000..111055fe --- /dev/null +++ b/src/base/QXmppSocks.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPSOCKS_H +#define QXMPPSOCKS_H + +#include <QHostAddress> +#include <QTcpSocket> + +class QTcpServer; + +class QXmppSocksClient : public QTcpSocket +{ + Q_OBJECT + +public: + QXmppSocksClient(const QHostAddress &proxyAddress, quint16 proxyPort, QObject *parent=0); + void connectToHost(const QString &hostName, quint16 hostPort); + bool waitForReady(int msecs = 30000); + +signals: + void ready(); + +private slots: + void slotConnected(); + void slotReadyRead(); + +private: + QHostAddress m_proxyAddress; + quint16 m_proxyPort; + QString m_hostName; + quint16 m_hostPort; + int m_step; +}; + +class QXmppSocksServer : public QObject +{ + Q_OBJECT + +public: + QXmppSocksServer(QObject *parent=0); + void close(); + bool isListening() const; + bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0); + + QHostAddress serverAddress() const; + quint16 serverPort() const; + +signals: + void newConnection(QTcpSocket *socket, QString hostName, quint16 port); + +private slots: + void slotNewConnection(); + void slotReadyRead(); + +private: + QTcpServer *m_server; + QMap<QTcpSocket*, int> m_states; +}; + +#endif diff --git a/src/base/QXmppStanza.cpp b/src/base/QXmppStanza.cpp new file mode 100644 index 00000000..e8937b6f --- /dev/null +++ b/src/base/QXmppStanza.cpp @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#include "QXmppStanza.h" +#include "QXmppUtils.h" +#include "QXmppConstants.h" + +#include <QDomElement> +#include <QXmlStreamWriter> + +uint QXmppStanza::s_uniqeIdNo = 0; + +QXmppStanza::Error::Error(): + m_code(0), + m_type(static_cast<QXmppStanza::Error::Type>(-1)), + m_condition(static_cast<QXmppStanza::Error::Condition>(-1)) +{ +} + +QXmppStanza::Error::Error(Type type, Condition cond, const QString& text): + m_code(0), + m_type(type), + m_condition(cond), + m_text(text) +{ +} + +QXmppStanza::Error::Error(const QString& type, const QString& cond, + const QString& text): + m_code(0), + m_text(text) +{ + setTypeFromStr(type); + setConditionFromStr(cond); +} + +QString QXmppStanza::Error::text() const +{ + return m_text; +} + +void QXmppStanza::Error::setText(const QString& text) +{ + m_text = text; +} + +int QXmppStanza::Error::code() const +{ + return m_code; +} + +void QXmppStanza::Error::setCode(int code) +{ + m_code = code; +} + +QXmppStanza::Error::Condition QXmppStanza::Error::condition() const +{ + return m_condition; +} + +void QXmppStanza::Error::setCondition(QXmppStanza::Error::Condition cond) +{ + m_condition = cond; +} + +QXmppStanza::Error::Type QXmppStanza::Error::type() const +{ + return m_type; +} + +void QXmppStanza::Error::setType(QXmppStanza::Error::Type type) +{ + m_type = type; +} + +QString QXmppStanza::Error::getTypeStr() const +{ + switch(m_type) + { + case Cancel: + return "cancel"; + case Continue: + return "continue"; + case Modify: + return "modify"; + case Auth: + return "auth"; + case Wait: + return "wait"; + default: + return ""; + } +} + +QString QXmppStanza::Error::getConditionStr() const +{ + switch(m_condition) + { + case BadRequest: + return "bad-request"; + case Conflict: + return "conflict"; + case FeatureNotImplemented: + return "feature-not-implemented"; + case Forbidden: + return "forbidden"; + case Gone: + return "gone"; + case InternalServerError: + return "internal-server-error"; + case ItemNotFound: + return "item-not-found"; + case JidMalformed: + return "jid-malformed"; + case NotAcceptable: + return "not-acceptable"; + case NotAllowed: + return "not-allowed"; + case NotAuthorized: + return "not-authorized"; + case PaymentRequired: + return "payment-required"; + case RecipientUnavailable: + return "recipient-unavailable"; + case Redirect: + return "redirect"; + case RegistrationRequired: + return "registration-required"; + case RemoteServerNotFound: + return "remote-server-not-found"; + case RemoteServerTimeout: + return "remote-server-timeout"; + case ResourceConstraint: + return "resource-constraint"; + case ServiceUnavailable: + return "service-unavailable"; + case SubscriptionRequired: + return "subscription-required"; + case UndefinedCondition: + return "undefined-condition"; + case UnexpectedRequest: + return "unexpected-request"; + default: + return ""; + } +} + +void QXmppStanza::Error::setTypeFromStr(const QString& type) +{ + if(type == "cancel") + setType(Cancel); + else if(type == "continue") + setType(Continue); + else if(type == "modify") + setType(Modify); + else if(type == "auth") + setType(Auth); + else if(type == "wait") + setType(Wait); + else + setType(static_cast<QXmppStanza::Error::Type>(-1)); +} + +void QXmppStanza::Error::setConditionFromStr(const QString& type) +{ + if(type == "bad-request") + setCondition(BadRequest); + else if(type == "conflict") + setCondition(Conflict); + else if(type == "feature-not-implemented") + setCondition(FeatureNotImplemented); + else if(type == "forbidden") + setCondition(Forbidden); + else if(type == "gone") + setCondition(Gone); + else if(type == "internal-server-error") + setCondition(InternalServerError); + else if(type == "item-not-found") + setCondition(ItemNotFound); + else if(type == "jid-malformed") + setCondition(JidMalformed); + else if(type == "not-acceptable") + setCondition(NotAcceptable); + else if(type == "not-allowed") + setCondition(NotAllowed); + else if(type == "not-authorized") + setCondition(NotAuthorized); + else if(type == "payment-required") + setCondition(PaymentRequired); + else if(type == "recipient-unavailable") + setCondition(RecipientUnavailable); + else if(type == "redirect") + setCondition(Redirect); + else if(type == "registration-required") + setCondition(RegistrationRequired); + else if(type == "remote-server-not-found") + setCondition(RemoteServerNotFound); + else if(type == "remote-server-timeout") + setCondition(RemoteServerTimeout); + else if(type == "resource-constraint") + setCondition(ResourceConstraint); + else if(type == "service-unavailable") + setCondition(ServiceUnavailable); + else if(type == "subscription-required") + setCondition(SubscriptionRequired); + else if(type == "undefined-condition") + setCondition(UndefinedCondition); + else if(type == "unexpected-request") + setCondition(UnexpectedRequest); + else + setCondition(static_cast<QXmppStanza::Error::Condition>(-1)); +} + +bool QXmppStanza::Error::isValid() const +{ + return !(getTypeStr().isEmpty() && getConditionStr().isEmpty()); +} + +void QXmppStanza::Error::parse(const QDomElement &errorElement) +{ + setCode(errorElement.attribute("code").toInt()); + setTypeFromStr(errorElement.attribute("type")); + + QString text; + QString cond; + QDomElement element = errorElement.firstChildElement(); + while(!element.isNull()) + { + if(element.tagName() == "text") + text = element.text(); + else if(element.namespaceURI() == ns_stanza) + { + cond = element.tagName(); + } + element = element.nextSiblingElement(); + } + + setConditionFromStr(cond); + setText(text); +} + +void QXmppStanza::Error::toXml( QXmlStreamWriter *writer ) const +{ + QString cond = getConditionStr(); + QString type = getTypeStr(); + + if(cond.isEmpty() && type.isEmpty()) + return; + + writer->writeStartElement("error"); + helperToXmlAddAttribute(writer, "type", type); + + if (m_code > 0) + helperToXmlAddAttribute(writer, "code", QString::number(m_code)); + + if(!cond.isEmpty()) + { + writer->writeStartElement(cond); + writer->writeAttribute("xmlns", ns_stanza); + writer->writeEndElement(); + } + if(!m_text.isEmpty()) + { + writer->writeStartElement("text"); + writer->writeAttribute("xml:lang", "en"); + writer->writeAttribute("xmlns", ns_stanza); + writer->writeCharacters(m_text); + writer->writeEndElement(); + } + + writer->writeEndElement(); +} + +/// Constructs a QXmppStanza with the specified sender and recipient. +/// +/// \param from +/// \param to + +QXmppStanza::QXmppStanza(const QString& from, const QString& to) + : QXmppPacket(), + m_to(to), + m_from(from) +{ +} + +/// Destroys a QXmppStanza. + +QXmppStanza::~QXmppStanza() +{ +} + +/// Returns the stanza's recipient JID. +/// + +QString QXmppStanza::to() const +{ + return m_to; +} + +/// Sets the stanza's recipient JID. +/// +/// \param to + +void QXmppStanza::setTo(const QString& to) +{ + m_to = to; +} + +/// Returns the stanza's sender JID. + +QString QXmppStanza::from() const +{ + return m_from; +} + +/// Sets the stanza's sender JID. +/// +/// \param from + +void QXmppStanza::setFrom(const QString& from) +{ + m_from = from; +} + +/// Returns the stanza's identifier. + +QString QXmppStanza::id() const +{ + return m_id; +} + +/// Sets the stanza's identifier. +/// +/// \param id + +void QXmppStanza::setId(const QString& id) +{ + m_id = id; +} + +/// Returns the stanza's language. + +QString QXmppStanza::lang() const +{ + return m_lang; +} + +/// Sets the stanza's language. +/// +/// \param lang + +void QXmppStanza::setLang(const QString& lang) +{ + m_lang = lang; +} + +/// Returns the stanza's error. + +QXmppStanza::Error QXmppStanza::error() const +{ + return m_error; +} + +/// Sets the stanza's error. +/// +/// \param error + +void QXmppStanza::setError(const QXmppStanza::Error& error) +{ + m_error = error; +} + +/// Returns the stanza's "extensions". +/// +/// Extensions are XML elements which are not handled internally by QXmpp. + +QXmppElementList QXmppStanza::extensions() const +{ + return m_extensions; +} + +/// Sets the stanza's "extensions". +/// +/// \param extensions + +void QXmppStanza::setExtensions(const QXmppElementList &extensions) +{ + m_extensions = extensions; +} + +void QXmppStanza::generateAndSetNextId() +{ + // get back + ++s_uniqeIdNo; + m_id = "qxmpp" + QString::number(s_uniqeIdNo); +} + +bool QXmppStanza::isErrorStanza() const +{ + return m_error.isValid(); +} + +void QXmppStanza::parse(const QDomElement &element) +{ + m_from = element.attribute("from"); + m_to = element.attribute("to"); + m_id = element.attribute("id"); + m_lang = element.attribute("lang"); + + QDomElement errorElement = element.firstChildElement("error"); + if(!errorElement.isNull()) + m_error.parse(errorElement); +} + diff --git a/src/base/QXmppStanza.h b/src/base/QXmppStanza.h new file mode 100644 index 00000000..dc1c1346 --- /dev/null +++ b/src/base/QXmppStanza.h @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPSTANZA_H +#define QXMPPSTANZA_H + +#include "QXmppElement.h" +#include "QXmppPacket.h" +#include <QString> + +// forward declarations of QXmlStream* classes will not work on Mac, we need to +// include the whole header. +// See http://lists.trolltech.com/qt-interest/2008-07/thread00798-0.html +// for an explanation. +#include <QXmlStreamWriter> + +/// \defgroup Stanzas + +/// \brief The QXmppStanza class is the base class for all XMPP stanzas. +/// +/// \ingroup Stanzas + +class QXmppStanza : public QXmppPacket +{ +public: + class Error + { + public: + enum Type + { + Cancel, + Continue, + Modify, + Auth, + Wait + }; + + enum Condition + { + BadRequest, + Conflict, + FeatureNotImplemented, + Forbidden, + Gone, + InternalServerError, + ItemNotFound, + JidMalformed, + NotAcceptable, + NotAllowed, + NotAuthorized, + PaymentRequired, + RecipientUnavailable, + Redirect, + RegistrationRequired, + RemoteServerNotFound, + RemoteServerTimeout, + ResourceConstraint, + ServiceUnavailable, + SubscriptionRequired, + UndefinedCondition, + UnexpectedRequest + }; + + Error(); + Error(Type type, Condition cond, const QString& text=""); + Error(const QString& type, const QString& cond, const QString& text=""); + + int code() const; + void setCode(int code); + + QString text() const; + void setText(const QString& text); + + Condition condition() const; + void setCondition(Condition cond); + + void setType(Type type); + Type type() const; + + // FIXME : remove this once is gone + bool isValid() const; + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + private: + QString getConditionStr() const; + void setConditionFromStr(const QString& cond); + + QString getTypeStr() const; + void setTypeFromStr(const QString& type); + + int m_code; + Type m_type; + Condition m_condition; + QString m_text; + }; + + QXmppStanza(const QString& from = QString(), const QString& to = QString()); + ~QXmppStanza(); + + QString to() const; + void setTo(const QString&); + + QString from() const; + void setFrom(const QString&); + + QString id() const; + void setId(const QString&); + + QString lang() const; + void setLang(const QString&); + + QXmppStanza::Error error() const; + void setError(const QXmppStanza::Error& error); + + QXmppElementList extensions() const; + void setExtensions(const QXmppElementList &elements); + + /// \cond + // FIXME : why is this needed? + bool isErrorStanza() const; + +protected: + void generateAndSetNextId(); + void parse(const QDomElement &element); + /// \endcond + +private: + static uint s_uniqeIdNo; + QString m_to; + QString m_from; + QString m_id; + QString m_lang; + QXmppStanza::Error m_error; + QXmppElementList m_extensions; +}; + +#endif // QXMPPSTANZA_H diff --git a/src/base/QXmppStream.cpp b/src/base/QXmppStream.cpp new file mode 100644 index 00000000..d9f7ff88 --- /dev/null +++ b/src/base/QXmppStream.cpp @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#include "QXmppConstants.h" +#include "QXmppLogger.h" +#include "QXmppPacket.h" +#include "QXmppStream.h" +#include "QXmppUtils.h" + +#include <QBuffer> +#include <QDomDocument> +#include <QHostAddress> +#include <QRegExp> +#include <QSslSocket> +#include <QStringList> +#include <QTime> +#include <QXmlStreamWriter> + +static bool randomSeeded = false; +static const QByteArray streamRootElementEnd = "</stream:stream>"; + +class QXmppStreamPrivate +{ +public: + QXmppStreamPrivate(); + + QByteArray dataBuffer; + QSslSocket* socket; + + // stream state + QByteArray streamStart; +}; + +QXmppStreamPrivate::QXmppStreamPrivate() + : socket(0) +{ +} + +/// Constructs a base XMPP stream. +/// +/// \param parent + +QXmppStream::QXmppStream(QObject *parent) + : QXmppLoggable(parent), + d(new QXmppStreamPrivate) +{ + // Make sure the random number generator is seeded + if (!randomSeeded) + { + qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); + randomSeeded = true; + } +} + +/// Destroys a base XMPP stream. + +QXmppStream::~QXmppStream() +{ + delete d; +} + +/// Disconnects from the remote host. +/// + +void QXmppStream::disconnectFromHost() +{ + sendData(streamRootElementEnd); + if (d->socket) + { + d->socket->flush(); + d->socket->disconnectFromHost(); + } +} + +/// Handles a stream start event, which occurs when the underlying transport +/// becomes ready (socket connected, encryption started). +/// +/// If you redefine handleStart(), make sure to call the base class's method. + +void QXmppStream::handleStart() +{ + d->dataBuffer.clear(); + d->streamStart.clear(); +} + +/// Returns true if the stream is connected. +/// + +bool QXmppStream::isConnected() const +{ + return d->socket && + d->socket->state() == QAbstractSocket::ConnectedState; +} + +/// Sends raw data to the peer. +/// +/// \param data + +bool QXmppStream::sendData(const QByteArray &data) +{ + logSent(QString::fromUtf8(data)); + if (!d->socket || d->socket->state() != QAbstractSocket::ConnectedState) + return false; + return d->socket->write(data) == data.size(); +} + +/// Sends an XMPP packet to the peer. +/// +/// \param packet + +bool QXmppStream::sendPacket(const QXmppPacket &packet) +{ + // prepare packet + QByteArray data; + QXmlStreamWriter xmlStream(&data); + packet.toXml(&xmlStream); + + // send packet + return sendData(data); +} + +/// Returns the QSslSocket used for this stream. +/// + +QSslSocket *QXmppStream::socket() const +{ + return d->socket; +} + +/// Sets the QSslSocket used for this stream. +/// + +void QXmppStream::setSocket(QSslSocket *socket) +{ + bool check; + Q_UNUSED(check); + + d->socket = socket; + if (!d->socket) + return; + + // socket events + check = connect(socket, SIGNAL(connected()), + this, SLOT(_q_socketConnected())); + Q_ASSERT(check); + + check = connect(socket, SIGNAL(disconnected()), + this, SLOT(_q_socketDisconnected())); + Q_ASSERT(check); + + check = connect(socket, SIGNAL(encrypted()), + this, SLOT(_q_socketEncrypted())); + Q_ASSERT(check); + + check = connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(_q_socketError(QAbstractSocket::SocketError))); + + check = connect(socket, SIGNAL(readyRead()), + this, SLOT(_q_socketReadyRead())); + Q_ASSERT(check); + + // relay signals + check = connect(socket, SIGNAL(disconnected()), + this, SIGNAL(disconnected())); + Q_ASSERT(check); +} + +void QXmppStream::_q_socketConnected() +{ + info(QString("Socket connected to %1 %2").arg( + d->socket->peerAddress().toString(), + QString::number(d->socket->peerPort()))); + handleStart(); +} + +void QXmppStream::_q_socketDisconnected() +{ + info("Socket disconnected"); +} + +void QXmppStream::_q_socketEncrypted() +{ + debug("Socket encrypted"); + handleStart(); +} + +void QXmppStream::_q_socketError(QAbstractSocket::SocketError socketError) +{ + Q_UNUSED(socketError); + warning(QString("Socket error: " + socket()->errorString())); +} + +void QXmppStream::_q_socketReadyRead() +{ + d->dataBuffer.append(d->socket->readAll()); + + // FIXME : maybe these QRegExps could be static? + QRegExp startStreamRegex("^(<\\?xml.*\\?>)?\\s*<stream:stream.*>"); + startStreamRegex.setMinimal(true); + QRegExp endStreamRegex("</stream:stream>$"); + endStreamRegex.setMinimal(true); + + // check whether we need to add stream start / end elements + QByteArray completeXml = d->dataBuffer; + const QString strData = QString::fromUtf8(d->dataBuffer); + bool streamStart = false; + if (d->streamStart.isEmpty() && strData.contains(startStreamRegex)) + streamStart = true; + else + completeXml.prepend(d->streamStart); + if (!strData.contains(endStreamRegex)) + completeXml.append(streamRootElementEnd); + + // check whether we have a valid XML document + QDomDocument doc; + if (!doc.setContent(completeXml, true)) + return; + + // remove data from buffer + logReceived(strData); + d->dataBuffer.clear(); + if (streamStart) + d->streamStart = startStreamRegex.cap(0).toUtf8(); + + // process stream start + if (streamStart) + handleStream(doc.documentElement()); + + // process stanzas + QDomElement nodeRecv = doc.documentElement().firstChildElement(); + while (!nodeRecv.isNull()) { + handleStanza(nodeRecv); + nodeRecv = nodeRecv.nextSiblingElement(); + } +} + + diff --git a/src/base/QXmppStream.h b/src/base/QXmppStream.h new file mode 100644 index 00000000..bcbb7616 --- /dev/null +++ b/src/base/QXmppStream.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPSTREAM_H +#define QXMPPSTREAM_H + +#include <QAbstractSocket> +#include <QObject> +#include "QXmppLogger.h" + +class QDomElement; +class QSslSocket; +class QXmppPacket; +class QXmppStreamPrivate; + +/// \brief The QXmppStream class is the base class for all XMPP streams. +/// + +class QXmppStream : public QXmppLoggable +{ + Q_OBJECT + +public: + QXmppStream(QObject *parent); + ~QXmppStream(); + + virtual bool isConnected() const; + bool sendPacket(const QXmppPacket&); + +signals: + /// This signal is emitted when the stream is connected. + void connected(); + + /// This signal is emitted when the stream is disconnected. + void disconnected(); + +protected: + // Access to underlying socket + QSslSocket *socket() const; + void setSocket(QSslSocket *socket); + + // Overridable methods + virtual void handleStart(); + + /// Handles an incoming XMPP stanza. + /// + /// \param element + virtual void handleStanza(const QDomElement &element) = 0; + + /// Handles an incoming XMPP stream start. + /// + /// \param element + virtual void handleStream(const QDomElement &element) = 0; + +public slots: + virtual void disconnectFromHost(); + virtual bool sendData(const QByteArray&); + +private slots: + void _q_socketConnected(); + void _q_socketDisconnected(); + void _q_socketEncrypted(); + void _q_socketError(QAbstractSocket::SocketError error); + void _q_socketReadyRead(); + +private: + QXmppStreamPrivate * const d; +}; + +#endif // QXMPPSTREAM_H diff --git a/src/base/QXmppStreamFeatures.cpp b/src/base/QXmppStreamFeatures.cpp new file mode 100644 index 00000000..241f676f --- /dev/null +++ b/src/base/QXmppStreamFeatures.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> + +#include "QXmppConstants.h" +#include "QXmppStreamFeatures.h" + +QXmppStreamFeatures::QXmppStreamFeatures() + : m_bindMode(Disabled), + m_sessionMode(Disabled), + m_nonSaslAuthMode(Disabled), + m_tlsMode(Disabled) +{ +} + +QXmppStreamFeatures::Mode QXmppStreamFeatures::bindMode() const +{ + return m_bindMode; +} + +void QXmppStreamFeatures::setBindMode(QXmppStreamFeatures::Mode mode) +{ + m_bindMode = mode; +} + +QXmppStreamFeatures::Mode QXmppStreamFeatures::sessionMode() const +{ + return m_sessionMode; +} + +void QXmppStreamFeatures::setSessionMode(Mode mode) +{ + m_sessionMode = mode; +} + +QXmppStreamFeatures::Mode QXmppStreamFeatures::nonSaslAuthMode() const +{ + return m_nonSaslAuthMode; +} + +void QXmppStreamFeatures::setNonSaslAuthMode(QXmppStreamFeatures::Mode mode) +{ + m_nonSaslAuthMode = mode; +} + +QList<QXmppConfiguration::SASLAuthMechanism> QXmppStreamFeatures::authMechanisms() const +{ + return m_authMechanisms; +} + +void QXmppStreamFeatures::setAuthMechanisms(QList<QXmppConfiguration::SASLAuthMechanism> &mechanisms) +{ + m_authMechanisms = mechanisms; +} + +QList<QXmppConfiguration::CompressionMethod> QXmppStreamFeatures::compressionMethods() const +{ + return m_compressionMethods; +} + +void QXmppStreamFeatures::setCompressionMethods(QList<QXmppConfiguration::CompressionMethod> &methods) +{ + m_compressionMethods = methods; +} + +QXmppStreamFeatures::Mode QXmppStreamFeatures::tlsMode() const +{ + return m_tlsMode; +} + +void QXmppStreamFeatures::setTlsMode(QXmppStreamFeatures::Mode mode) +{ + m_tlsMode = mode; +} + +bool QXmppStreamFeatures::isStreamFeatures(const QDomElement &element) +{ + return element.namespaceURI() == ns_stream && + element.tagName() == "features"; +} + +static QXmppStreamFeatures::Mode readFeature(const QDomElement &element, const char *tagName, const char *tagNs) +{ + QDomElement subElement = element.firstChildElement(tagName); + if (subElement.namespaceURI() == tagNs) + { + if (!subElement.firstChildElement("required").isNull()) + return QXmppStreamFeatures::Required; + else + return QXmppStreamFeatures::Enabled; + } else { + return QXmppStreamFeatures::Disabled; + } +} + +void QXmppStreamFeatures::parse(const QDomElement &element) +{ + m_bindMode = readFeature(element, "bind", ns_bind); + m_sessionMode = readFeature(element, "session", ns_session); + m_nonSaslAuthMode = readFeature(element, "auth", ns_authFeature); + m_tlsMode = readFeature(element, "starttls", ns_tls); + + // parse advertised compression methods + QDomElement compression = element.firstChildElement("compression"); + if (compression.namespaceURI() == ns_compressFeature) + { + QDomElement subElement = compression.firstChildElement("method"); + while(!subElement.isNull()) + { + if (subElement.text() == QLatin1String("zlib")) + m_compressionMethods << QXmppConfiguration::ZlibCompression; + subElement = subElement.nextSiblingElement("method"); + } + } + + // parse advertised SASL Authentication mechanisms + QDomElement mechs = element.firstChildElement("mechanisms"); + if (mechs.namespaceURI() == ns_sasl) + { + QDomElement subElement = mechs.firstChildElement("mechanism"); + while(!subElement.isNull()) + { + if (subElement.text() == QLatin1String("PLAIN")) + m_authMechanisms << QXmppConfiguration::SASLPlain; + else if (subElement.text() == QLatin1String("DIGEST-MD5")) + m_authMechanisms << QXmppConfiguration::SASLDigestMD5; + else if (subElement.text() == QLatin1String("ANONYMOUS")) + m_authMechanisms << QXmppConfiguration::SASLAnonymous; + else if (subElement.text() == QLatin1String("X-FACEBOOK-PLATFORM")) + m_authMechanisms << QXmppConfiguration::SASLXFacebookPlatform; + subElement = subElement.nextSiblingElement("mechanism"); + } + } +} + +static void writeFeature(QXmlStreamWriter *writer, const char *tagName, const char *tagNs, QXmppStreamFeatures::Mode mode) +{ + if (mode != QXmppStreamFeatures::Disabled) + { + writer->writeStartElement(tagName); + writer->writeAttribute("xmlns", tagNs); + if (mode == QXmppStreamFeatures::Required) + writer->writeEmptyElement("required"); + writer->writeEndElement(); + } +} + +void QXmppStreamFeatures::toXml(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("stream:features"); + writeFeature(writer, "bind", ns_bind, m_bindMode); + writeFeature(writer, "session", ns_session, m_sessionMode); + writeFeature(writer, "auth", ns_authFeature, m_nonSaslAuthMode); + writeFeature(writer, "starttls", ns_tls, m_tlsMode); + + if (!m_compressionMethods.isEmpty()) + { + writer->writeStartElement("compression"); + writer->writeAttribute("xmlns", ns_compressFeature); + for (int i = 0; i < m_compressionMethods.size(); i++) + { + writer->writeStartElement("method"); + switch (m_compressionMethods[i]) + { + case QXmppConfiguration::ZlibCompression: + writer->writeCharacters("zlib"); + break; + } + writer->writeEndElement(); + } + writer->writeEndElement(); + } + if (!m_authMechanisms.isEmpty()) + { + writer->writeStartElement("mechanisms"); + writer->writeAttribute("xmlns", ns_sasl); + for (int i = 0; i < m_authMechanisms.size(); i++) + { + writer->writeStartElement("mechanism"); + switch (m_authMechanisms[i]) + { + case QXmppConfiguration::SASLPlain: + writer->writeCharacters("PLAIN"); + break; + case QXmppConfiguration::SASLDigestMD5: + writer->writeCharacters("DIGEST-MD5"); + break; + case QXmppConfiguration::SASLAnonymous: + writer->writeCharacters("ANONYMOUS"); + break; + case QXmppConfiguration::SASLXFacebookPlatform: + writer->writeCharacters("X-FACEBOOK-PLATFORM"); + break; + } + writer->writeEndElement(); + } + writer->writeEndElement(); + } + writer->writeEndElement(); +} + diff --git a/src/base/QXmppStreamFeatures.h b/src/base/QXmppStreamFeatures.h new file mode 100644 index 00000000..bfc42c64 --- /dev/null +++ b/src/base/QXmppStreamFeatures.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPSTREAMFEATURES_H +#define QXMPPSTREAMFEATURES_H + +#include "QXmppConfiguration.h" +#include "QXmppStanza.h" + +class QXmppStreamFeatures : public QXmppStanza +{ +public: + QXmppStreamFeatures(); + + enum Mode + { + Disabled = 0, + Enabled, + Required + }; + + Mode bindMode() const; + void setBindMode(Mode mode); + + Mode sessionMode() const; + void setSessionMode(Mode mode); + + Mode nonSaslAuthMode() const; + void setNonSaslAuthMode(Mode mode); + + QList<QXmppConfiguration::SASLAuthMechanism> authMechanisms() const; + void setAuthMechanisms(QList<QXmppConfiguration::SASLAuthMechanism> &mecanisms); + + QList<QXmppConfiguration::CompressionMethod> compressionMethods() const; + void setCompressionMethods(QList<QXmppConfiguration::CompressionMethod> &methods); + + Mode tlsMode() const; + void setTlsMode(Mode mode); + + /// \cond + void parse(const QDomElement &element); + void toXml(QXmlStreamWriter *writer) const; + /// \endcond + + static bool isStreamFeatures(const QDomElement &element); + +private: + Mode m_bindMode; + Mode m_sessionMode; + Mode m_nonSaslAuthMode; + Mode m_tlsMode; + QList<QXmppConfiguration::SASLAuthMechanism> m_authMechanisms; + QList<QXmppConfiguration::CompressionMethod> m_compressionMethods; +}; + +#endif diff --git a/src/base/QXmppStreamInitiationIq.cpp b/src/base/QXmppStreamInitiationIq.cpp new file mode 100644 index 00000000..32dd0c78 --- /dev/null +++ b/src/base/QXmppStreamInitiationIq.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> + +#include "QXmppConstants.h" +#include "QXmppStreamInitiationIq.h" +#include "QXmppUtils.h" + +QString QXmppStreamInitiationIq::mimeType() const +{ + return m_mimeType; +} + +void QXmppStreamInitiationIq::setMimeType(const QString &mimeType) +{ + m_mimeType = mimeType; +} + +QXmppStreamInitiationIq::Profile QXmppStreamInitiationIq::profile() const +{ + return m_profile; +} + +void QXmppStreamInitiationIq::setProfile(QXmppStreamInitiationIq::Profile profile) +{ + m_profile = profile; +} + +QXmppElementList QXmppStreamInitiationIq::siItems() const +{ + return m_siItems; +} + +QString QXmppStreamInitiationIq::siId() const +{ + return m_siId; +} + +void QXmppStreamInitiationIq::setSiId(const QString &id) +{ + m_siId = id; +} + +void QXmppStreamInitiationIq::setSiItems(const QXmppElementList &items) +{ + m_siItems = items; +} + +bool QXmppStreamInitiationIq::isStreamInitiationIq(const QDomElement &element) +{ + QDomElement siElement = element.firstChildElement("si"); + return (siElement.namespaceURI() == ns_stream_initiation); +} + +void QXmppStreamInitiationIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement siElement = element.firstChildElement("si"); + m_siId = siElement.attribute("id"); + m_mimeType = siElement.attribute("mime-type"); + if (siElement.attribute("profile") == ns_stream_initiation_file_transfer) + m_profile = FileTransfer; + else + m_profile = None; + + QDomElement itemElement = siElement.firstChildElement(); + while (!itemElement.isNull()) + { + m_siItems.append(QXmppElement(itemElement)); + itemElement = itemElement.nextSiblingElement(); + } +} + +void QXmppStreamInitiationIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("si"); + writer->writeAttribute("xmlns", ns_stream_initiation); + helperToXmlAddAttribute(writer, "id", m_siId); + helperToXmlAddAttribute(writer, "mime-type", m_mimeType); + if (m_profile == FileTransfer) + helperToXmlAddAttribute(writer, "profile", ns_stream_initiation_file_transfer); + foreach (const QXmppElement &item, m_siItems) + item.toXml(writer); + writer->writeEndElement(); +} + diff --git a/src/base/QXmppStreamInitiationIq.h b/src/base/QXmppStreamInitiationIq.h new file mode 100644 index 00000000..27739b46 --- /dev/null +++ b/src/base/QXmppStreamInitiationIq.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPSTREAMINITIATIONIQ_H +#define QXMPPSTREAMINITIATIONIQ_H + +#include <QDateTime> + +#include "QXmppIq.h" + +class QDomElement; +class QXmlStreamWriter; + +class QXmppStreamInitiationIq : public QXmppIq +{ +public: + enum Profile { + None = 0, + FileTransfer, + }; + + QString mimeType() const; + void setMimeType(const QString &mimeType); + + QXmppStreamInitiationIq::Profile profile() const; + void setProfile(QXmppStreamInitiationIq::Profile profile); + + QString siId() const; + void setSiId(const QString &id); + + QXmppElementList siItems() const; + void setSiItems(const QXmppElementList &items); + + static bool isStreamInitiationIq(const QDomElement &element); + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QString m_mimeType; + Profile m_profile; + QString m_siId; + QXmppElementList m_siItems; +}; + +#endif diff --git a/src/base/QXmppStun.cpp b/src/base/QXmppStun.cpp new file mode 100644 index 00000000..16cef928 --- /dev/null +++ b/src/base/QXmppStun.cpp @@ -0,0 +1,2581 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#define QXMPP_DEBUG_STUN + +#include <QCryptographicHash> +#include <QHostInfo> +#include <QNetworkInterface> +#include <QUdpSocket> +#include <QTimer> + +#include "QXmppStun.h" +#include "QXmppUtils.h" + +#define ID_SIZE 12 +#define STUN_RTO_INTERVAL 500 +#define STUN_RTO_MAX 7 + +static const quint32 STUN_MAGIC = 0x2112A442; +static const quint16 STUN_HEADER = 20; +static const quint8 STUN_IPV4 = 0x01; +static const quint8 STUN_IPV6 = 0x02; + +enum AttributeType { + MappedAddress = 0x0001, // RFC5389 + ChangeRequest = 0x0003, // RFC5389 + SourceAddress = 0x0004, // RFC5389 + ChangedAddress = 0x0005, // RFC5389 + Username = 0x0006, // RFC5389 + MessageIntegrity = 0x0008, // RFC5389 + ErrorCode = 0x0009, // RFC5389 + ChannelNumber = 0x000c, // RFC5766 : TURN + Lifetime = 0x000d, // RFC5766 : TURN + XorPeerAddress = 0x0012, // RFC5766 : TURN + DataAttr = 0x0013, // RFC5766 : TURN + Realm = 0x0014, // RFC5389 + Nonce = 0x0015, // RFC5389 + XorRelayedAddress= 0x0016, // RFC5766 : TURN + EvenPort = 0x0018, // RFC5766 : TURN + RequestedTransport=0x0019, // RFC5766 : TURN + XorMappedAddress = 0x0020, // RFC5389 + ReservationToken = 0x0022, // RFC5766 : TURN + Priority = 0x0024, // RFC5245 + UseCandidate = 0x0025, // RFC5245 + Software = 0x8022, // RFC5389 + Fingerprint = 0x8028, // RFC5389 + IceControlled = 0x8029, // RFC5245 + IceControlling = 0x802a, // RFC5245 + OtherAddress = 0x802c, // RFC5780 +}; + +// FIXME : we need to set local preference to discriminate between +// multiple IP addresses +static quint32 candidatePriority(const QXmppJingleCandidate &candidate, int localPref = 65535) +{ + int typePref; + switch (candidate.type()) + { + case QXmppJingleCandidate::HostType: + typePref = 126; + break; + case QXmppJingleCandidate::PeerReflexiveType: + typePref = 110; + break; + case QXmppJingleCandidate::ServerReflexiveType: + typePref = 100; + break; + default: + typePref = 0; + } + + return (1 << 24) * typePref + \ + (1 << 8) * localPref + \ + (256 - candidate.component()); +} + +static bool isIPv6LinkLocalAddress(const QHostAddress &addr) +{ + if (addr.protocol() != QAbstractSocket::IPv6Protocol) + return false; + Q_IPV6ADDR ipv6addr = addr.toIPv6Address(); + return (((ipv6addr[0] << 8) + ipv6addr[1]) & 0xffc0) == 0xfe80; +} + +static bool decodeAddress(QDataStream &stream, quint16 a_length, QHostAddress &address, quint16 &port, const QByteArray &xorId = QByteArray()) +{ + if (a_length < 4) + return false; + quint8 reserved, protocol; + quint16 rawPort; + stream >> reserved; + stream >> protocol; + stream >> rawPort; + if (xorId.isEmpty()) + port = rawPort; + else + port = rawPort ^ (STUN_MAGIC >> 16); + if (protocol == STUN_IPV4) + { + if (a_length != 8) + return false; + quint32 addr; + stream >> addr; + if (xorId.isEmpty()) + address = QHostAddress(addr); + else + address = QHostAddress(addr ^ STUN_MAGIC); + } else if (protocol == STUN_IPV6) { + if (a_length != 20) + return false; + Q_IPV6ADDR addr; + stream.readRawData((char*)&addr, sizeof(addr)); + if (!xorId.isEmpty()) + { + QByteArray xpad; + QDataStream(&xpad, QIODevice::WriteOnly) << STUN_MAGIC; + xpad += xorId; + for (int i = 0; i < 16; i++) + addr[i] ^= xpad[i]; + } + address = QHostAddress(addr); + } else { + return false; + } + return true; +} + +static void encodeAddress(QDataStream &stream, quint16 type, const QHostAddress &address, quint16 port, const QByteArray &xorId = QByteArray()) +{ + const quint8 reserved = 0; + if (address.protocol() == QAbstractSocket::IPv4Protocol) + { + stream << type; + stream << quint16(8); + stream << reserved; + stream << quint8(STUN_IPV4); + quint32 addr = address.toIPv4Address(); + if (!xorId.isEmpty()) + { + port ^= (STUN_MAGIC >> 16); + addr ^= STUN_MAGIC; + } + stream << port; + stream << addr; + } else if (address.protocol() == QAbstractSocket::IPv6Protocol) { + stream << type; + stream << quint16(20); + stream << reserved; + stream << quint8(STUN_IPV6); + Q_IPV6ADDR addr = address.toIPv6Address(); + if (!xorId.isEmpty()) + { + port ^= (STUN_MAGIC >> 16); + QByteArray xpad; + QDataStream(&xpad, QIODevice::WriteOnly) << STUN_MAGIC; + xpad += xorId; + for (int i = 0; i < 16; i++) + addr[i] ^= xpad[i]; + } + stream << port; + stream.writeRawData((char*)&addr, sizeof(addr)); + } else { + qWarning("Cannot write STUN attribute for unknown IP version"); + } +} + +static void addAddress(QDataStream &stream, quint16 type, const QHostAddress &host, quint16 port, const QByteArray &xorId = QByteArray()) +{ + if (port && !host.isNull() && + (host.protocol() == QAbstractSocket::IPv4Protocol || + host.protocol() == QAbstractSocket::IPv6Protocol)) + { + encodeAddress(stream, type, host, port, xorId); + } +} + +static void encodeString(QDataStream &stream, quint16 type, const QString &string) +{ + const QByteArray utf8string = string.toUtf8(); + stream << type; + stream << quint16(utf8string.size()); + stream.writeRawData(utf8string.data(), utf8string.size()); + if (utf8string.size() % 4) + { + const QByteArray padding(4 - (utf8string.size() % 4), 0); + stream.writeRawData(padding.data(), padding.size()); + } +} + +static void setBodyLength(QByteArray &buffer, qint16 length) +{ + QDataStream stream(&buffer, QIODevice::WriteOnly); + stream.device()->seek(2); + stream << length; +} + +/// Constructs a new QXmppStunMessage. + +QXmppStunMessage::QXmppStunMessage() + : errorCode(0), + changedPort(0), + mappedPort(0), + otherPort(0), + sourcePort(0), + xorMappedPort(0), + xorPeerPort(0), + xorRelayedPort(0), + useCandidate(false), + m_cookie(STUN_MAGIC), + m_type(0), + m_changeRequest(0), + m_channelNumber(0), + m_lifetime(0), + m_priority(0) +{ + m_id = QByteArray(ID_SIZE, 0); +} + +quint32 QXmppStunMessage::cookie() const +{ + return m_cookie; +} + +void QXmppStunMessage::setCookie(quint32 cookie) +{ + m_cookie = cookie; +} + +QByteArray QXmppStunMessage::id() const +{ + return m_id; +} + +void QXmppStunMessage::setId(const QByteArray &id) +{ + Q_ASSERT(id.size() == ID_SIZE); + m_id = id; +} + +quint16 QXmppStunMessage::messageClass() const +{ + return m_type & 0x0110; +} + +quint16 QXmppStunMessage::messageMethod() const +{ + return m_type & 0x3eef; +} + +quint16 QXmppStunMessage::type() const +{ + return m_type; +} + +void QXmppStunMessage::setType(quint16 type) +{ + m_type = type; +} + +/// Returns the CHANGE-REQUEST attribute, indicating whether to change +/// the IP and / or port from which the response is sent. + +quint32 QXmppStunMessage::changeRequest() const +{ + return m_changeRequest; +} + +/// Sets the CHANGE-REQUEST attribute, indicating whether to change +/// the IP and / or port from which the response is sent. +/// +/// \param changeRequest + +void QXmppStunMessage::setChangeRequest(quint32 changeRequest) +{ + m_changeRequest = changeRequest; + m_attributes << ChangeRequest; +} + +/// Returns the CHANNEL-NUMBER attribute. + +quint16 QXmppStunMessage::channelNumber() const +{ + return m_channelNumber; +} + +/// Sets the CHANNEL-NUMBER attribute. +/// +/// \param channelNumber + +void QXmppStunMessage::setChannelNumber(quint16 channelNumber) +{ + m_channelNumber = channelNumber; + m_attributes << ChannelNumber; +} + +/// Returns the DATA attribute. + +QByteArray QXmppStunMessage::data() const +{ + return m_data; +} + +/// Sets the DATA attribute. + +void QXmppStunMessage::setData(const QByteArray &data) +{ + m_data = data; + m_attributes << DataAttr; +} + +/// Returns the LIFETIME attribute, indicating the duration in seconds for +/// which the server will maintain an allocation. + +quint32 QXmppStunMessage::lifetime() const +{ + return m_lifetime; +} + +/// Sets the LIFETIME attribute, indicating the duration in seconds for +/// which the server will maintain an allocation. +/// +/// \param lifetime + +void QXmppStunMessage::setLifetime(quint32 lifetime) +{ + m_lifetime = lifetime; + m_attributes << Lifetime; +} + +/// Returns the NONCE attribute. + +QByteArray QXmppStunMessage::nonce() const +{ + return m_nonce; +} + +/// Sets the NONCE attribute. +/// +/// \param nonce + +void QXmppStunMessage::setNonce(const QByteArray &nonce) +{ + m_nonce = nonce; + m_attributes << Nonce; +} + +/// Returns the PRIORITY attribute, the priority that would be assigned to +/// a peer reflexive candidate discovered during the ICE check. + +quint32 QXmppStunMessage::priority() const +{ + return m_priority; +} + +/// Sets the PRIORITY attribute, the priority that would be assigned to +/// a peer reflexive candidate discovered during the ICE check. +/// +/// \param priority + +void QXmppStunMessage::setPriority(quint32 priority) +{ + m_priority = priority; + m_attributes << Priority; +} + +/// Returns the REALM attribute. + +QString QXmppStunMessage::realm() const +{ + return m_realm; +} + +/// Sets the REALM attribute. +/// +/// \param realm + +void QXmppStunMessage::setRealm(const QString &realm) +{ + m_realm = realm; + m_attributes << Realm; +} + +/// Returns the REQUESTED-TRANSPORT attribute. + +quint8 QXmppStunMessage::requestedTransport() const +{ + return m_requestedTransport; +} + +/// Sets the REQUESTED-TRANSPORT attribute. +/// +/// \param requestedTransport + +void QXmppStunMessage::setRequestedTransport(quint8 requestedTransport) +{ + m_requestedTransport = requestedTransport; + m_attributes << RequestedTransport; +} + +/// Returns the RESERVATION-TOKEN attribute. + +QByteArray QXmppStunMessage::reservationToken() const +{ + return m_reservationToken; +} + +/// Sets the RESERVATION-TOKEN attribute. +/// +/// \param reservationToken + +void QXmppStunMessage::setReservationToken(const QByteArray &reservationToken) +{ + m_reservationToken = reservationToken; + m_reservationToken.resize(8); + m_attributes << ReservationToken; +} + +/// Returns the SOFTWARE attribute, containing a textual description of the +/// software being used. + +QString QXmppStunMessage::software() const +{ + return m_software; +} + +/// Sets the SOFTWARE attribute, containing a textual description of the +/// software being used. +/// +/// \param software + +void QXmppStunMessage::setSoftware(const QString &software) +{ + m_software = software; + m_attributes << Software; +} + +/// Returns the USERNAME attribute, containing the username to use for +/// authentication. + +QString QXmppStunMessage::username() const +{ + return m_username; +} + +/// Sets the USERNAME attribute, containing the username to use for +/// authentication. +/// +/// \param username + +void QXmppStunMessage::setUsername(const QString &username) +{ + m_username = username; + m_attributes << Username; +} + +/// Decodes a QXmppStunMessage and checks its integrity using the given key. +/// +/// \param buffer +/// \param key +/// \param errors + +bool QXmppStunMessage::decode(const QByteArray &buffer, const QByteArray &key, QStringList *errors) +{ + QStringList silent; + if (!errors) + errors = &silent; + + if (buffer.size() < STUN_HEADER) + { + *errors << QLatin1String("Received a truncated STUN packet"); + return false; + } + + // parse STUN header + QDataStream stream(buffer); + quint16 length; + stream >> m_type; + stream >> length; + stream >> m_cookie; + stream.readRawData(m_id.data(), m_id.size()); + + if (length != buffer.size() - STUN_HEADER) + { + *errors << QLatin1String("Received an invalid STUN packet"); + return false; + } + + // parse STUN attributes + int done = 0; + bool after_integrity = false; + while (done < length) + { + quint16 a_type, a_length; + stream >> a_type; + stream >> a_length; + const int pad_length = 4 * ((a_length + 3) / 4) - a_length; + + // only FINGERPRINT is allowed after MESSAGE-INTEGRITY + if (after_integrity && a_type != Fingerprint) + { + *errors << QString("Skipping attribute %1 after MESSAGE-INTEGRITY").arg(QString::number(a_type)); + stream.skipRawData(a_length + pad_length); + done += 4 + a_length + pad_length; + continue; + } + + if (a_type == Priority) + { + // PRIORITY + if (a_length != sizeof(m_priority)) + return false; + stream >> m_priority; + m_attributes << Priority; + + } else if (a_type == ErrorCode) { + + // ERROR-CODE + if (a_length < 4) + return false; + quint16 reserved; + quint8 errorCodeHigh, errorCodeLow; + stream >> reserved; + stream >> errorCodeHigh; + stream >> errorCodeLow; + errorCode = errorCodeHigh * 100 + errorCodeLow; + QByteArray phrase(a_length - 4, 0); + stream.readRawData(phrase.data(), phrase.size()); + errorPhrase = QString::fromUtf8(phrase); + + } else if (a_type == UseCandidate) { + + // USE-CANDIDATE + if (a_length != 0) + return false; + useCandidate = true; + + } else if (a_type == ChannelNumber) { + + // CHANNEL-NUMBER + if (a_length != 4) + return false; + stream >> m_channelNumber; + stream.skipRawData(2); + m_attributes << ChannelNumber; + + } else if (a_type == DataAttr) { + + // DATA + m_data.resize(a_length); + stream.readRawData(m_data.data(), m_data.size()); + m_attributes << DataAttr; + + } else if (a_type == Lifetime) { + + // LIFETIME + if (a_length != sizeof(m_lifetime)) + return false; + stream >> m_lifetime; + m_attributes << Lifetime; + + } else if (a_type == Nonce) { + + // NONCE + m_nonce.resize(a_length); + stream.readRawData(m_nonce.data(), m_nonce.size()); + m_attributes << Nonce; + + } else if (a_type == Realm) { + + // REALM + QByteArray utf8Realm(a_length, 0); + stream.readRawData(utf8Realm.data(), utf8Realm.size()); + m_realm = QString::fromUtf8(utf8Realm); + m_attributes << Realm; + + } else if (a_type == RequestedTransport) { + + // REQUESTED-TRANSPORT + if (a_length != 4) + return false; + stream >> m_requestedTransport; + stream.skipRawData(3); + m_attributes << RequestedTransport; + + } else if (a_type == ReservationToken) { + + // RESERVATION-TOKEN + if (a_length != 8) + return false; + m_reservationToken.resize(a_length); + stream.readRawData(m_reservationToken.data(), m_reservationToken.size()); + m_attributes << ReservationToken; + + } else if (a_type == Software) { + + // SOFTWARE + QByteArray utf8Software(a_length, 0); + stream.readRawData(utf8Software.data(), utf8Software.size()); + m_software = QString::fromUtf8(utf8Software); + m_attributes << Software; + + } else if (a_type == Username) { + + // USERNAME + QByteArray utf8Username(a_length, 0); + stream.readRawData(utf8Username.data(), utf8Username.size()); + m_username = QString::fromUtf8(utf8Username); + m_attributes << Username; + + } else if (a_type == MappedAddress) { + + // MAPPED-ADDRESS + if (!decodeAddress(stream, a_length, mappedHost, mappedPort)) + { + *errors << QLatin1String("Bad MAPPED-ADDRESS"); + return false; + } + + } else if (a_type == ChangeRequest) { + + // CHANGE-REQUEST + if (a_length != sizeof(m_changeRequest)) + return false; + stream >> m_changeRequest; + m_attributes << ChangeRequest; + + } else if (a_type == SourceAddress) { + + // SOURCE-ADDRESS + if (!decodeAddress(stream, a_length, sourceHost, sourcePort)) + { + *errors << QLatin1String("Bad SOURCE-ADDRESS"); + return false; + } + + } else if (a_type == ChangedAddress) { + + // CHANGED-ADDRESS + if (!decodeAddress(stream, a_length, changedHost, changedPort)) + { + *errors << QLatin1String("Bad CHANGED-ADDRESS"); + return false; + } + + } else if (a_type == OtherAddress) { + + // OTHER-ADDRESS + if (!decodeAddress(stream, a_length, otherHost, otherPort)) + { + *errors << QLatin1String("Bad OTHER-ADDRESS"); + return false; + } + + } else if (a_type == XorMappedAddress) { + + // XOR-MAPPED-ADDRESS + if (!decodeAddress(stream, a_length, xorMappedHost, xorMappedPort, m_id)) + { + *errors << QLatin1String("Bad XOR-MAPPED-ADDRESS"); + return false; + } + + } else if (a_type == XorPeerAddress) { + + // XOR-PEER-ADDRESS + if (!decodeAddress(stream, a_length, xorPeerHost, xorPeerPort, m_id)) + { + *errors << QLatin1String("Bad XOR-PEER-ADDRESS"); + return false; + } + + } else if (a_type == XorRelayedAddress) { + + // XOR-RELAYED-ADDRESS + if (!decodeAddress(stream, a_length, xorRelayedHost, xorRelayedPort, m_id)) + { + *errors << QLatin1String("Bad XOR-RELAYED-ADDRESS"); + return false; + } + + } else if (a_type == MessageIntegrity) { + + // MESSAGE-INTEGRITY + if (a_length != 20) + return false; + QByteArray integrity(20, 0); + stream.readRawData(integrity.data(), integrity.size()); + + // check HMAC-SHA1 + if (!key.isEmpty()) + { + QByteArray copy = buffer.left(STUN_HEADER + done); + setBodyLength(copy, done + 24); + if (integrity != generateHmacSha1(key, copy)) + { + *errors << QLatin1String("Bad message integrity"); + return false; + } + } + + // from here onwards, only FINGERPRINT is allowed + after_integrity = true; + + } else if (a_type == Fingerprint) { + + // FINGERPRINT + if (a_length != 4) + return false; + quint32 fingerprint; + stream >> fingerprint; + + // check CRC32 + QByteArray copy = buffer.left(STUN_HEADER + done); + setBodyLength(copy, done + 8); + const quint32 expected = generateCrc32(copy) ^ 0x5354554eL; + if (fingerprint != expected) + { + *errors << QLatin1String("Bad fingerprint"); + return false; + } + + // stop parsing, no more attributes are allowed + return true; + + } else if (a_type == IceControlling) { + + /// ICE-CONTROLLING + if (a_length != 8) + return false; + iceControlling.resize(a_length); + stream.readRawData(iceControlling.data(), iceControlling.size()); + + } else if (a_type == IceControlled) { + + /// ICE-CONTROLLED + if (a_length != 8) + return false; + iceControlled.resize(a_length); + stream.readRawData(iceControlled.data(), iceControlled.size()); + + } else { + + // Unknown attribute + stream.skipRawData(a_length); + *errors << QString("Skipping unknown attribute %1").arg(QString::number(a_type)); + + } + stream.skipRawData(pad_length); + done += 4 + a_length + pad_length; + } + return true; +} + +/// Encodes the current QXmppStunMessage, optionally calculating the +/// message integrity attribute using the given key. +/// +/// \param key +/// \param addFingerprint + +QByteArray QXmppStunMessage::encode(const QByteArray &key, bool addFingerprint) const +{ + QByteArray buffer; + QDataStream stream(&buffer, QIODevice::WriteOnly); + + // encode STUN header + quint16 length = 0; + stream << m_type; + stream << length; + stream << m_cookie; + stream.writeRawData(m_id.data(), m_id.size()); + + // MAPPED-ADDRESS + addAddress(stream, MappedAddress, mappedHost, mappedPort); + + // CHANGE-REQUEST + if (m_attributes.contains(ChangeRequest)) { + stream << quint16(ChangeRequest); + stream << quint16(sizeof(m_changeRequest)); + stream << m_changeRequest; + } + + // SOURCE-ADDRESS + addAddress(stream, SourceAddress, sourceHost, sourcePort); + + // CHANGED-ADDRESS + addAddress(stream, ChangedAddress, changedHost, changedPort); + + // OTHER-ADDRESS + addAddress(stream, OtherAddress, otherHost, otherPort); + + // XOR-MAPPED-ADDRESS + addAddress(stream, XorMappedAddress, xorMappedHost, xorMappedPort, m_id); + + // XOR-PEER-ADDRESS + addAddress(stream, XorPeerAddress, xorPeerHost, xorPeerPort, m_id); + + // XOR-RELAYED-ADDRESS + addAddress(stream, XorRelayedAddress, xorRelayedHost, xorRelayedPort, m_id); + + // ERROR-CODE + if (errorCode) + { + const quint16 reserved = 0; + const quint8 errorCodeHigh = errorCode / 100; + const quint8 errorCodeLow = errorCode % 100; + const QByteArray phrase = errorPhrase.toUtf8(); + stream << quint16(ErrorCode); + stream << quint16(phrase.size() + 4); + stream << reserved; + stream << errorCodeHigh; + stream << errorCodeLow; + stream.writeRawData(phrase.data(), phrase.size()); + if (phrase.size() % 4) + { + const QByteArray padding(4 - (phrase.size() % 4), 0); + stream.writeRawData(padding.data(), padding.size()); + } + } + + // PRIORITY + if (m_attributes.contains(Priority)) + { + stream << quint16(Priority); + stream << quint16(sizeof(m_priority)); + stream << m_priority; + } + + // USE-CANDIDATE + if (useCandidate) + { + stream << quint16(UseCandidate); + stream << quint16(0); + } + + // CHANNEL-NUMBER + if (m_attributes.contains(ChannelNumber)) { + stream << quint16(ChannelNumber); + stream << quint16(4); + stream << m_channelNumber; + stream << quint16(0); + } + + // DATA + if (m_attributes.contains(DataAttr)) { + stream << quint16(DataAttr); + stream << quint16(m_data.size()); + stream.writeRawData(m_data.data(), m_data.size()); + if (m_data.size() % 4) { + const QByteArray padding(4 - (m_data.size() % 4), 0); + stream.writeRawData(padding.data(), padding.size()); + } + } + + // LIFETIME + if (m_attributes.contains(Lifetime)) { + stream << quint16(Lifetime); + stream << quint16(sizeof(m_lifetime)); + stream << m_lifetime; + } + + // NONCE + if (m_attributes.contains(Nonce)) { + stream << quint16(Nonce); + stream << quint16(m_nonce.size()); + stream.writeRawData(m_nonce.data(), m_nonce.size()); + } + + // REALM + if (m_attributes.contains(Realm)) + encodeString(stream, Realm, m_realm); + + // REQUESTED-TRANSPORT + if (m_attributes.contains(RequestedTransport)) { + const QByteArray reserved(3, 0); + stream << quint16(RequestedTransport); + stream << quint16(4); + stream << m_requestedTransport; + stream.writeRawData(reserved.data(), reserved.size()); + } + + // RESERVATION-TOKEN + if (m_attributes.contains(ReservationToken)) { + stream << quint16(ReservationToken); + stream << quint16(m_reservationToken.size()); + stream.writeRawData(m_reservationToken.data(), m_reservationToken.size()); + } + + // SOFTWARE + if (m_attributes.contains(Software)) + encodeString(stream, Software, m_software); + + // USERNAME + if (m_attributes.contains(Username)) + encodeString(stream, Username, m_username); + + // ICE-CONTROLLING or ICE-CONTROLLED + if (!iceControlling.isEmpty()) + { + stream << quint16(IceControlling); + stream << quint16(iceControlling.size()); + stream.writeRawData(iceControlling.data(), iceControlling.size()); + } else if (!iceControlled.isEmpty()) { + stream << quint16(IceControlled); + stream << quint16(iceControlled.size()); + stream.writeRawData(iceControlled.data(), iceControlled.size()); + } + + // set body length + setBodyLength(buffer, buffer.size() - STUN_HEADER); + + // MESSAGE-INTEGRITY + if (!key.isEmpty()) + { + setBodyLength(buffer, buffer.size() - STUN_HEADER + 24); + QByteArray integrity = generateHmacSha1(key, buffer); + stream << quint16(MessageIntegrity); + stream << quint16(integrity.size()); + stream.writeRawData(integrity.data(), integrity.size()); + } + + // FINGERPRINT + if (addFingerprint) + { + setBodyLength(buffer, buffer.size() - STUN_HEADER + 8); + quint32 fingerprint = generateCrc32(buffer) ^ 0x5354554eL; + stream << quint16(Fingerprint); + stream << quint16(sizeof(fingerprint)); + stream << fingerprint; + } + + return buffer; +} + +/// If the given packet looks like a STUN message, returns the message +/// type, otherwise returns 0. +/// +/// \param buffer +/// \param cookie +/// \param id + +quint16 QXmppStunMessage::peekType(const QByteArray &buffer, quint32 &cookie, QByteArray &id) +{ + if (buffer.size() < STUN_HEADER) + return 0; + + // parse STUN header + QDataStream stream(buffer); + quint16 type; + quint16 length; + stream >> type; + stream >> length; + stream >> cookie; + + if (length != buffer.size() - STUN_HEADER) + return 0; + + id.resize(ID_SIZE); + stream.readRawData(id.data(), id.size()); + return type; +} + +QString QXmppStunMessage::toString() const +{ + QStringList dumpLines; + QString typeName; + switch (messageMethod()) + { + case Binding: typeName = "Binding"; break; + case SharedSecret: typeName = "Shared Secret"; break; + case Allocate: typeName = "Allocate"; break; + case Refresh: typeName = "Refresh"; break; + case Send: typeName = "Send"; break; + case Data: typeName = "Data"; break; + case CreatePermission: typeName = "CreatePermission"; break; + case ChannelBind: typeName = "ChannelBind"; break; + default: typeName = "Unknown"; break; + } + switch (messageClass()) + { + case Request: typeName += " Request"; break; + case Indication: typeName += " Indication"; break; + case Response: typeName += " Response"; break; + case Error: typeName += " Error"; break; + default: break; + } + dumpLines << QString(" type %1 (%2)") + .arg(typeName) + .arg(QString::number(m_type)); + dumpLines << QString(" id %1").arg(QString::fromAscii(m_id.toHex())); + + // attributes + if (m_attributes.contains(ChannelNumber)) + dumpLines << QString(" * CHANNEL-NUMBER %1").arg(QString::number(m_channelNumber)); + if (errorCode) + dumpLines << QString(" * ERROR-CODE %1 %2") + .arg(QString::number(errorCode), errorPhrase); + if (m_attributes.contains(Lifetime)) + dumpLines << QString(" * LIFETIME %1").arg(QString::number(m_lifetime)); + if (m_attributes.contains(Nonce)) + dumpLines << QString(" * NONCE %1").arg(QString::fromLatin1(m_nonce)); + if (m_attributes.contains(Realm)) + dumpLines << QString(" * REALM %1").arg(m_realm); + if (m_attributes.contains(RequestedTransport)) + dumpLines << QString(" * REQUESTED-TRANSPORT 0x%1").arg(QString::number(m_requestedTransport, 16)); + if (m_attributes.contains(ReservationToken)) + dumpLines << QString(" * RESERVATION-TOKEN %1").arg(QString::fromAscii(m_reservationToken.toHex())); + if (m_attributes.contains(Software)) + dumpLines << QString(" * SOFTWARE %1").arg(m_software); + if (m_attributes.contains(Username)) + dumpLines << QString(" * USERNAME %1").arg(m_username); + if (mappedPort) + dumpLines << QString(" * MAPPED-ADDRESS %1 %2") + .arg(mappedHost.toString(), QString::number(mappedPort)); + if (m_attributes.contains(ChangeRequest)) + dumpLines << QString(" * CHANGE-REQUEST %1") + .arg(QString::number(m_changeRequest)); + if (sourcePort) + dumpLines << QString(" * SOURCE-ADDRESS %1 %2") + .arg(sourceHost.toString(), QString::number(sourcePort)); + if (changedPort) + dumpLines << QString(" * CHANGED-ADDRESS %1 %2") + .arg(changedHost.toString(), QString::number(changedPort)); + if (otherPort) + dumpLines << QString(" * OTHER-ADDRESS %1 %2") + .arg(otherHost.toString(), QString::number(otherPort)); + if (xorMappedPort) + dumpLines << QString(" * XOR-MAPPED-ADDRESS %1 %2") + .arg(xorMappedHost.toString(), QString::number(xorMappedPort)); + if (xorPeerPort) + dumpLines << QString(" * XOR-PEER-ADDRESS %1 %2") + .arg(xorPeerHost.toString(), QString::number(xorPeerPort)); + if (xorRelayedPort) + dumpLines << QString(" * XOR-RELAYED-ADDRESS %1 %2") + .arg(xorRelayedHost.toString(), QString::number(xorRelayedPort)); + if (m_attributes.contains(Priority)) + dumpLines << QString(" * PRIORITY %1").arg(QString::number(m_priority)); + if (!iceControlling.isEmpty()) + dumpLines << QString(" * ICE-CONTROLLING %1") + .arg(QString::fromAscii(iceControlling.toHex())); + if (!iceControlled.isEmpty()) + dumpLines << QString(" * ICE-CONTROLLED %1") + .arg(QString::fromAscii(iceControlled.toHex())); + + return dumpLines.join("\n"); +} + +/// Constructs a new QXmppStunTransaction. +/// +/// \param request +/// \param receiver + +QXmppStunTransaction::QXmppStunTransaction(const QXmppStunMessage &request, QObject *receiver) + : QXmppLoggable(receiver), + m_request(request), + m_tries(0) +{ + bool check; + Q_UNUSED(check); + + check = connect(this, SIGNAL(writeStun(QXmppStunMessage)), + receiver, SLOT(writeStun(QXmppStunMessage))); + Q_ASSERT(check); + + check = connect(this, SIGNAL(finished()), + receiver, SLOT(transactionFinished())); + Q_ASSERT(check); + + // RTO timer + m_retryTimer = new QTimer(this); + m_retryTimer->setSingleShot(true); + check = connect(m_retryTimer, SIGNAL(timeout()), + this, SLOT(retry())); + + // send packet immediately + m_tries++; + emit writeStun(m_request); + m_retryTimer->start(STUN_RTO_INTERVAL); +} + +void QXmppStunTransaction::readStun(const QXmppStunMessage &response) +{ + if (response.messageClass() == QXmppStunMessage::Error || + response.messageClass() == QXmppStunMessage::Response) { + m_response = response; + emit finished(); + } +} + +/// Returns the STUN request. + +QXmppStunMessage QXmppStunTransaction::request() const +{ + return m_request; +} + +/// Returns the STUN response. + +QXmppStunMessage QXmppStunTransaction::response() const +{ + return m_response; +} + +void QXmppStunTransaction::retry() +{ + if (m_tries >= STUN_RTO_MAX) { + m_response.setType(QXmppStunMessage::Error); + m_response.errorPhrase = QLatin1String("Request timed out"); + emit finished(); + return; + } + + // resend request + m_tries++; + emit writeStun(m_request); + m_retryTimer->start(2 * m_retryTimer->interval()); +} + +/// Constructs a new QXmppTurnAllocation. +/// +/// \param parent + +QXmppTurnAllocation::QXmppTurnAllocation(QObject *parent) + : QXmppLoggable(parent), + m_relayedPort(0), + m_turnPort(0), + m_channelNumber(0x4000), + m_lifetime(600), + m_state(UnconnectedState) +{ + bool check; + Q_UNUSED(check); + + socket = new QUdpSocket(this); + check = connect(socket, SIGNAL(readyRead()), + this, SLOT(readyRead())); + Q_ASSERT(check); + + m_timer = new QTimer(this); + m_timer->setSingleShot(true); + check = connect(m_timer, SIGNAL(timeout()), + this, SLOT(refresh())); + Q_ASSERT(check); + + // channels are valid 600s, we refresh every 500s + m_channelTimer = new QTimer(this); + m_channelTimer->setInterval(500 * 1000); + check = connect(m_channelTimer, SIGNAL(timeout()), + this, SLOT(refreshChannels())); + Q_ASSERT(check); +} + +/// Destroys the TURN allocation. + +QXmppTurnAllocation::~QXmppTurnAllocation() +{ + if (m_state == ConnectedState) + disconnectFromHost(); +} + +/// Allocates the TURN allocation. + +void QXmppTurnAllocation::connectToHost() +{ + if (m_state != UnconnectedState) + return; + + // start listening for UDP + if (socket->state() == QAbstractSocket::UnconnectedState) { + if (!socket->bind()) { + warning("Could not start listening for TURN"); + return; + } + } + + // send allocate request + QXmppStunMessage request; + request.setType(QXmppStunMessage::Allocate | QXmppStunMessage::Request); + request.setId(generateRandomBytes(12)); + request.setLifetime(m_lifetime); + request.setRequestedTransport(0x11); + m_transactions << new QXmppStunTransaction(request, this); + + // update state + setState(ConnectingState); +} + +/// Releases the TURN allocation. + +void QXmppTurnAllocation::disconnectFromHost() +{ + m_channelTimer->stop(); + m_timer->stop(); + + // clear channels and any outstanding transactions + m_channels.clear(); + foreach (QXmppStunTransaction *transaction, m_transactions) + delete transaction; + m_transactions.clear(); + + // end allocation + if (m_state == ConnectedState) { + QXmppStunMessage request; + request.setType(QXmppStunMessage::Refresh | QXmppStunMessage::Request); + request.setId(generateRandomBytes(12)); + request.setNonce(m_nonce); + request.setRealm(m_realm); + request.setUsername(m_username); + request.setLifetime(0); + m_transactions << new QXmppStunTransaction(request, this); + + setState(ClosingState); + } else { + setState(UnconnectedState); + } +} + +void QXmppTurnAllocation::readyRead() +{ + QByteArray buffer; + QHostAddress remoteHost; + quint16 remotePort; + while (socket->hasPendingDatagrams()) { + const qint64 size = socket->pendingDatagramSize(); + buffer.resize(size); + socket->readDatagram(buffer.data(), buffer.size(), &remoteHost, &remotePort); + handleDatagram(buffer, remoteHost, remotePort); + } +} + +void QXmppTurnAllocation::handleDatagram(const QByteArray &buffer, const QHostAddress &remoteHost, quint16 remotePort) +{ + // demultiplex channel data + if (buffer.size() >= 4 && (buffer[0] & 0xc0) == 0x40) { + QDataStream stream(buffer); + quint16 channel, length; + stream >> channel; + stream >> length; + if (m_state == ConnectedState && m_channels.contains(channel) && length <= buffer.size() - 4) { + emit datagramReceived(buffer.mid(4, length), m_channels[channel].first, + m_channels[channel].second); + } + return; + } + + // parse STUN message + QXmppStunMessage message; + QStringList errors; + if (!message.decode(buffer, QByteArray(), &errors)) { + foreach (const QString &error, errors) + warning(error); + return; + } + +#ifdef QXMPP_DEBUG_STUN + logReceived(QString("TURN packet from %1 port %2\n%3").arg( + remoteHost.toString(), + QString::number(remotePort), + message.toString())); +#endif + + // find transaction + foreach (QXmppStunTransaction *transaction, m_transactions) { + if (transaction->request().id() == message.id() && + transaction->request().messageMethod() == message.messageMethod()) { + transaction->readStun(message); + return; + } + } +} + +/// Refresh allocation. + +void QXmppTurnAllocation::refresh() +{ + QXmppStunMessage request; + request.setType(QXmppStunMessage::Refresh | QXmppStunMessage::Request); + request.setId(generateRandomBytes(12)); + request.setNonce(m_nonce); + request.setRealm(m_realm); + request.setUsername(m_username); + m_transactions << new QXmppStunTransaction(request, this); +} + +/// Refresh channel bindings. + +void QXmppTurnAllocation::refreshChannels() +{ + foreach (quint16 channel, m_channels.keys()) { + QXmppStunMessage request; + request.setType(QXmppStunMessage::ChannelBind | QXmppStunMessage::Request); + request.setId(generateRandomBytes(12)); + request.setNonce(m_nonce); + request.setRealm(m_realm); + request.setUsername(m_username); + request.setChannelNumber(channel); + request.xorPeerHost = m_channels[channel].first; + request.xorPeerPort = m_channels[channel].second; + m_transactions << new QXmppStunTransaction(request, this); + } +} + +/// Returns the relayed host address, i.e. the address on the server +/// used to communicate with peers. + +QHostAddress QXmppTurnAllocation::relayedHost() const +{ + return m_relayedHost; +} + +/// Returns the relayed port, i.e. the port on the server used to communicate +/// with peers. + +quint16 QXmppTurnAllocation::relayedPort() const +{ + return m_relayedPort; +} + +/// Sets the password used to authenticate with the TURN server. +/// +/// \param password + +void QXmppTurnAllocation::setPassword(const QString &password) +{ + m_password = password; +} + +/// Sets the TURN server to use. +/// +/// \param host The address of the TURN server. +/// \param port The port of the TURN server. + +void QXmppTurnAllocation::setServer(const QHostAddress &host, quint16 port) +{ + m_turnHost = host; + m_turnPort = port; +} + +/// Sets the \a user used for authentication with the TURN server. +/// +/// \param user + +void QXmppTurnAllocation::setUser(const QString &user) +{ + m_username = user; +} + +/// Returns the current state of the allocation. +/// + +QXmppTurnAllocation::AllocationState QXmppTurnAllocation::state() const +{ + return m_state; +} + +void QXmppTurnAllocation::setState(AllocationState state) +{ + if (state == m_state) + return; + m_state = state; + if (m_state == ConnectedState) { + emit connected(); + } else if (m_state == UnconnectedState) { + m_timer->stop(); + emit disconnected(); + } +} + +void QXmppTurnAllocation::transactionFinished() +{ + QXmppStunTransaction *transaction = qobject_cast<QXmppStunTransaction*>(sender()); + if (!transaction || !m_transactions.removeAll(transaction)) + return; + transaction->deleteLater(); + + // handle authentication + const QXmppStunMessage reply = transaction->response(); + if (reply.messageClass() == QXmppStunMessage::Error && + reply.errorCode == 401 && + (reply.nonce() != m_nonce && reply.realm() != m_realm)) + { + // update long-term credentials + m_nonce = reply.nonce(); + m_realm = reply.realm(); + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData((m_username + ":" + m_realm + ":" + m_password).toUtf8()); + m_key = hash.result(); + + // retry request + QXmppStunMessage request(transaction->request()); + request.setId(generateRandomBytes(12)); + request.setNonce(m_nonce); + request.setRealm(m_realm); + request.setUsername(m_username); + m_transactions << new QXmppStunTransaction(request, this); + return; + } + + const quint16 method = transaction->request().messageMethod(); + if (method == QXmppStunMessage::Allocate) { + + if (reply.messageClass() == QXmppStunMessage::Error) { + warning(QString("Allocation failed: %1 %2").arg( + QString::number(reply.errorCode), reply.errorPhrase)); + setState(UnconnectedState); + return; + } + if (reply.xorRelayedHost.isNull() || + reply.xorRelayedHost.protocol() != QAbstractSocket::IPv4Protocol || + !reply.xorRelayedPort) { + warning("Allocation did not yield a valid relayed address"); + setState(UnconnectedState); + return; + } + + // store relayed address + m_relayedHost = reply.xorRelayedHost; + m_relayedPort = reply.xorRelayedPort; + + // schedule refresh + m_lifetime = reply.lifetime(); + m_timer->start((m_lifetime - 60) * 1000); + + setState(ConnectedState); + + } else if (method == QXmppStunMessage::ChannelBind) { + + if (reply.messageClass() == QXmppStunMessage::Error) { + warning(QString("ChannelBind failed: %1 %2").arg( + QString::number(reply.errorCode), reply.errorPhrase)); + + // remove channel + m_channels.remove(transaction->request().channelNumber()); + if (m_channels.isEmpty()) + m_channelTimer->stop(); + return; + } + + } else if (method == QXmppStunMessage::Refresh) { + + if (reply.messageClass() == QXmppStunMessage::Error) { + warning(QString("Refresh failed: %1 %2").arg( + QString::number(reply.errorCode), reply.errorPhrase)); + setState(UnconnectedState); + return; + } + + if (m_state == ClosingState) { + setState(UnconnectedState); + return; + } + + // schedule refresh + m_lifetime = reply.lifetime(); + m_timer->start((m_lifetime - 60) * 1000); + + } +} + +qint64 QXmppTurnAllocation::writeDatagram(const QByteArray &data, const QHostAddress &host, quint16 port) +{ + if (m_state != ConnectedState) + return -1; + + const Address addr = qMakePair(host, port); + quint16 channel = m_channels.key(addr); + + if (!channel) { + channel = m_channelNumber++; + m_channels.insert(channel, addr); + + // bind channel + QXmppStunMessage request; + request.setType(QXmppStunMessage::ChannelBind | QXmppStunMessage::Request); + request.setId(generateRandomBytes(12)); + request.setNonce(m_nonce); + request.setRealm(m_realm); + request.setUsername(m_username); + request.setChannelNumber(channel); + request.xorPeerHost = host; + request.xorPeerPort = port; + m_transactions << new QXmppStunTransaction(request, this); + + // schedule refresh + if (!m_channelTimer->isActive()) + m_channelTimer->start(); + } + + // send data + QByteArray channelData; + channelData.reserve(4 + data.size()); + QDataStream stream(&channelData, QIODevice::WriteOnly); + stream << channel; + stream << quint16(data.size()); + stream.writeRawData(data.data(), data.size()); + if (socket->writeDatagram(channelData, m_turnHost, m_turnPort) == channelData.size()) + return data.size(); + else + return -1; +} + +void QXmppTurnAllocation::writeStun(const QXmppStunMessage &message) +{ + socket->writeDatagram(message.encode(m_key), m_turnHost, m_turnPort); +#ifdef QXMPP_DEBUG_STUN + logSent(QString("TURN packet to %1 port %2\n%3").arg( + m_turnHost.toString(), + QString::number(m_turnPort), + message.toString())); +#endif +} + +QXmppIceComponent::Pair::Pair(int component, bool controlling) + : checked(QIODevice::NotOpen), + socket(0), + m_component(component), + m_controlling(controlling) +{ + transaction = generateRandomBytes(ID_SIZE); +} + +quint64 QXmppIceComponent::Pair::priority() const +{ + QXmppJingleCandidate local; + local.setComponent(m_component); + local.setType(socket ? QXmppJingleCandidate::HostType : QXmppJingleCandidate::RelayedType); + local.setPriority(candidatePriority(local)); + + // see RFC 5245 - 5.7.2. Computing Pair Priority and Ordering Pairs + const quint32 G = m_controlling ? local.priority() : remote.priority(); + const quint32 D = m_controlling ? remote.priority() : local.priority(); + return (quint64(1) << 32) * qMin(G, D) + 2 * qMax(G, D) + (G > D ? 1 : 0); +} + +QString QXmppIceComponent::Pair::toString() const +{ + QString str = QString("%1 port %2").arg(remote.host().toString(), QString::number(remote.port())); + if (socket) + str += QString(" (local %1 port %2)").arg(socket->localAddress().toString(), QString::number(socket->localPort())); + else + str += QString(" (relayed)"); + if (!reflexive.host().isNull() && reflexive.port()) + str += QString(" (reflexive %1 port %2)").arg(reflexive.host().toString(), QString::number(reflexive.port())); + return str; +} + +/// Constructs a new QXmppIceComponent. +/// +/// \param controlling +/// \param parent + +QXmppIceComponent::QXmppIceComponent(QObject *parent) + : QXmppLoggable(parent), + m_component(0), + m_activePair(0), + m_fallbackPair(0), + m_iceControlling(false), + m_peerReflexivePriority(0), + m_stunPort(0), + m_stunTries(0), + m_turnConfigured(false) +{ + bool check; + Q_UNUSED(check); + + m_localUser = generateStanzaHash(4); + m_localPassword = generateStanzaHash(22); + + m_timer = new QTimer(this); + m_timer->setInterval(500); + check = connect(m_timer, SIGNAL(timeout()), + this, SLOT(checkCandidates())); + Q_ASSERT(check); + + m_stunTimer = new QTimer(this); + m_stunTimer->setInterval(500); + check = connect(m_stunTimer, SIGNAL(timeout()), + this, SLOT(checkStun())); + Q_ASSERT(check); + + m_turnAllocation = new QXmppTurnAllocation(this); + check = connect(m_turnAllocation, SIGNAL(connected()), + this, SLOT(turnConnected())); + Q_ASSERT(check); + check = connect(m_turnAllocation, SIGNAL(datagramReceived(QByteArray,QHostAddress,quint16)), + this, SLOT(handleDatagram(QByteArray,QHostAddress,quint16))); + Q_ASSERT(check); +} + +/// Destroys the QXmppIceComponent. + +QXmppIceComponent::~QXmppIceComponent() +{ + foreach (Pair *pair, m_pairs) + delete pair; +} + +/// Returns the component id for the current socket, e.g. 1 for RTP +/// and 2 for RTCP. + +int QXmppIceComponent::component() const +{ + return m_component; +} + +/// Sets the component id for the current socket, e.g. 1 for RTP +/// and 2 for RTCP. +/// +/// \param component + +void QXmppIceComponent::setComponent(int component) +{ + m_component = component; + + // calculate peer-reflexive candidate priority + // see RFC 5245 - 7.1.2.1. PRIORITY and USE-CANDIDATE + QXmppJingleCandidate reflexive; + reflexive.setComponent(m_component); + reflexive.setType(QXmppJingleCandidate::PeerReflexiveType); + m_peerReflexivePriority = candidatePriority(reflexive); + + setObjectName(QString("STUN(%1)").arg(QString::number(m_component))); +} + +void QXmppIceComponent::checkCandidates() +{ + debug("Checking remote candidates"); + foreach (Pair *pair, m_pairs) + { + if (m_remoteUser.isEmpty()) + continue; + + // send a binding request + QXmppStunMessage message; + message.setId(pair->transaction); + message.setType(QXmppStunMessage::Binding | QXmppStunMessage::Request); + message.setPriority(m_peerReflexivePriority); + message.setUsername(QString("%1:%2").arg(m_remoteUser, m_localUser)); + if (m_iceControlling) + { + message.iceControlling = QByteArray(8, 0); + message.useCandidate = true; + } else { + message.iceControlled = QByteArray(8, 0); + } + writeStun(message, pair); + } + +} + +void QXmppIceComponent::checkStun() +{ + if (m_stunHost.isNull() || !m_stunPort || m_stunTries > 10) { + m_stunTimer->stop(); + return; + } + + // Send a request to STUN server to determine server-reflexive candidate + foreach (QUdpSocket *socket, m_sockets) + { + QXmppStunMessage msg; + msg.setType(QXmppStunMessage::Binding | QXmppStunMessage::Request); + msg.setId(m_stunId); +#ifdef QXMPP_DEBUG_STUN + logSent(QString("STUN packet to %1 port %2\n%3").arg(m_stunHost.toString(), + QString::number(m_stunPort), msg.toString())); +#endif + socket->writeDatagram(msg.encode(), m_stunHost, m_stunPort); + } + m_stunTries++; +} + +/// Stops ICE connectivity checks and closes the underlying sockets. + +void QXmppIceComponent::close() +{ + foreach (QUdpSocket *socket, m_sockets) + socket->close(); + m_turnAllocation->disconnectFromHost(); + m_timer->stop(); + m_stunTimer->stop(); + m_activePair = 0; +} + +/// Starts ICE connectivity checks. + +void QXmppIceComponent::connectToHost() +{ + if (m_activePair) + return; + + checkCandidates(); + m_timer->start(); +} + +/// Returns true if ICE negotiation completed, false otherwise. + +bool QXmppIceComponent::isConnected() const +{ + return m_activePair != 0; +} + +void QXmppIceComponent::setIceControlling(bool controlling) +{ + m_iceControlling = controlling; +} + +/// Returns the list of local candidates. + +QList<QXmppJingleCandidate> QXmppIceComponent::localCandidates() const +{ + return m_localCandidates; +} + +/// Sets the local user fragment. +/// +/// \param user + +void QXmppIceComponent::setLocalUser(const QString &user) +{ + m_localUser = user; +} + +/// Sets the local password. +/// +/// \param password + +void QXmppIceComponent::setLocalPassword(const QString &password) +{ + m_localPassword = password; +} + +/// Adds a remote STUN candidate. + +bool QXmppIceComponent::addRemoteCandidate(const QXmppJingleCandidate &candidate) +{ + if (candidate.component() != m_component || + (candidate.type() != QXmppJingleCandidate::HostType && + candidate.type() != QXmppJingleCandidate::RelayedType && + candidate.type() != QXmppJingleCandidate::ServerReflexiveType) || + candidate.protocol() != "udp" || + (candidate.host().protocol() != QAbstractSocket::IPv4Protocol && + candidate.host().protocol() != QAbstractSocket::IPv6Protocol)) + return false; + + foreach (Pair *pair, m_pairs) + if (pair->remote.host() == candidate.host() && + pair->remote.port() == candidate.port()) + return false; + + foreach (QUdpSocket *socket, m_sockets) + { + // do not pair IPv4 with IPv6 or global with link-local addresses + if (socket->localAddress().protocol() != candidate.host().protocol() || + isIPv6LinkLocalAddress(socket->localAddress()) != isIPv6LinkLocalAddress(candidate.host())) + continue; + + Pair *pair = new Pair(m_component, m_iceControlling); + pair->remote = candidate; + if (isIPv6LinkLocalAddress(pair->remote.host())) + { + QHostAddress remoteHost = pair->remote.host(); + remoteHost.setScopeId(socket->localAddress().scopeId()); + pair->remote.setHost(remoteHost); + } + pair->socket = socket; + m_pairs << pair; + + if (!m_fallbackPair) + m_fallbackPair = pair; + } + + // only use relaying for IPv4 candidates + if (m_turnConfigured && candidate.host().protocol() == QAbstractSocket::IPv4Protocol) { + Pair *pair = new Pair(m_component, m_iceControlling); + pair->remote = candidate; + pair->socket = 0; + m_pairs << pair; + } + return true; +} + +/// Adds a discovered peer-reflexive STUN candidate. + +QXmppIceComponent::Pair *QXmppIceComponent::addRemoteCandidate(QUdpSocket *socket, const QHostAddress &host, quint16 port, quint32 priority) +{ + foreach (Pair *pair, m_pairs) + if (pair->remote.host() == host && + pair->remote.port() == port && + pair->socket == socket) + return pair; + + QXmppJingleCandidate candidate; + candidate.setComponent(m_component); + candidate.setHost(host); + candidate.setId(generateStanzaHash(10)); + candidate.setPort(port); + candidate.setPriority(priority); + candidate.setProtocol("udp"); + candidate.setType(QXmppJingleCandidate::PeerReflexiveType); + + Pair *pair = new Pair(m_component, m_iceControlling); + pair->remote = candidate; + pair->socket = socket; + m_pairs << pair; + + debug(QString("Added candidate %1").arg(pair->toString())); + return pair; +} + +/// Sets the remote user fragment. +/// +/// \param user + +void QXmppIceComponent::setRemoteUser(const QString &user) +{ + m_remoteUser = user; +} + +/// Sets the remote password. +/// +/// \param password + +void QXmppIceComponent::setRemotePassword(const QString &password) +{ + m_remotePassword = password; +} + +/// Sets the list of sockets to use for this component. +/// +/// \param sockets + +void QXmppIceComponent::setSockets(QList<QUdpSocket*> sockets) +{ + // clear previous candidates and sockets + m_localCandidates.clear(); + foreach (QUdpSocket *socket, m_sockets) + delete socket; + m_sockets.clear(); + + // store candidates + int foundation = 0; + foreach (QUdpSocket *socket, sockets) + { + socket->setParent(this); + connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead())); + + QXmppJingleCandidate candidate; + candidate.setComponent(m_component); + candidate.setFoundation(foundation++); + // remove scope ID from IPv6 non-link local addresses + QHostAddress addr(socket->localAddress()); + if (addr.protocol() == QAbstractSocket::IPv6Protocol && + !isIPv6LinkLocalAddress(addr)) { + addr.setScopeId(QString()); + } + candidate.setHost(addr); + candidate.setId(generateStanzaHash(10)); + candidate.setPort(socket->localPort()); + candidate.setProtocol("udp"); + candidate.setType(QXmppJingleCandidate::HostType); + candidate.setPriority(candidatePriority(candidate)); + + m_sockets << socket; + m_localCandidates << candidate; + } + + // start STUN checks + if (!m_stunHost.isNull() && m_stunPort) { + m_stunTries = 0; + checkStun(); + m_stunTimer->start(); + } + + // connect to TURN server + if (m_turnConfigured) + m_turnAllocation->connectToHost(); +} + +/// Sets the STUN server to use to determine server-reflexive addresses +/// and ports. +/// +/// \param host The address of the STUN server. +/// \param port The port of the STUN server. + +void QXmppIceComponent::setStunServer(const QHostAddress &host, quint16 port) +{ + m_stunHost = host; + m_stunPort = port; + m_stunId = generateRandomBytes(ID_SIZE); +} + +/// Sets the TURN server to use to relay packets in double-NAT configurations. +/// +/// \param host The address of the TURN server. +/// \param port The port of the TURN server. + +void QXmppIceComponent::setTurnServer(const QHostAddress &host, quint16 port) +{ + m_turnAllocation->setServer(host, port); + m_turnConfigured = !host.isNull() && port; +} + +/// Sets the \a user used for authentication with the TURN server. +/// +/// \param user + +void QXmppIceComponent::setTurnUser(const QString &user) +{ + m_turnAllocation->setUser(user); +} + +/// Sets the \a password used for authentication with the TURN server. +/// +/// \param password + +void QXmppIceComponent::setTurnPassword(const QString &password) +{ + m_turnAllocation->setPassword(password); +} + +void QXmppIceComponent::readyRead() +{ + QUdpSocket *socket = qobject_cast<QUdpSocket*>(sender()); + if (!socket) + return; + + QByteArray buffer; + QHostAddress remoteHost; + quint16 remotePort; + while (socket->hasPendingDatagrams()) { + const qint64 size = socket->pendingDatagramSize(); + buffer.resize(size); + socket->readDatagram(buffer.data(), buffer.size(), &remoteHost, &remotePort); + handleDatagram(buffer, remoteHost, remotePort, socket); + } +} + +void QXmppIceComponent::handleDatagram(const QByteArray &buffer, const QHostAddress &remoteHost, quint16 remotePort, QUdpSocket *socket) +{ + // if this is not a STUN message, emit it + quint32 messageCookie; + QByteArray messageId; + quint16 messageType = QXmppStunMessage::peekType(buffer, messageCookie, messageId); + if (!messageType || messageCookie != STUN_MAGIC) + { + // use this as an opportunity to flag a potential pair + foreach (Pair *pair, m_pairs) { + if (pair->remote.host() == remoteHost && + pair->remote.port() == remotePort) { + m_fallbackPair = pair; + break; + } + } + emit datagramReceived(buffer); + return; + } + + // determine password to use + QString messagePassword; + if (messageId != m_stunId) + { + messagePassword = (messageType & 0xFF00) ? m_remotePassword : m_localPassword; + if (messagePassword.isEmpty()) + return; + } + + // parse STUN message + QXmppStunMessage message; + QStringList errors; + if (!message.decode(buffer, messagePassword.toUtf8(), &errors)) + { + foreach (const QString &error, errors) + warning(error); + return; + } +#ifdef QXMPP_DEBUG_STUN + logReceived(QString("STUN packet from %1 port %2\n%3").arg( + remoteHost.toString(), + QString::number(remotePort), + message.toString())); +#endif + + // check how to handle message + if (message.id() == m_stunId) + { + m_stunTimer->stop(); + + // determine server-reflexive address + QHostAddress reflexiveHost; + quint16 reflexivePort = 0; + if (!message.xorMappedHost.isNull() && message.xorMappedPort != 0) + { + reflexiveHost = message.xorMappedHost; + reflexivePort = message.xorMappedPort; + } + else if (!message.mappedHost.isNull() && message.mappedPort != 0) + { + reflexiveHost = message.mappedHost; + reflexivePort = message.mappedPort; + } else { + warning("STUN server did not provide a reflexive address"); + return; + } + + // check whether this candidates is already known + foreach (const QXmppJingleCandidate &candidate, m_localCandidates) + { + if (candidate.host() == reflexiveHost && + candidate.port() == reflexivePort && + candidate.type() == QXmppJingleCandidate::ServerReflexiveType) + return; + } + + // add the new local candidate + debug(QString("Adding server-reflexive candidate %1 port %2").arg(reflexiveHost.toString(), QString::number(reflexivePort))); + QXmppJingleCandidate candidate; + candidate.setComponent(m_component); + candidate.setHost(reflexiveHost); + candidate.setId(generateStanzaHash(10)); + candidate.setPort(reflexivePort); + candidate.setProtocol("udp"); + candidate.setType(QXmppJingleCandidate::ServerReflexiveType); + candidate.setPriority(candidatePriority(candidate)); + m_localCandidates << candidate; + + emit localCandidatesChanged(); + return; + } + + // process message from peer + Pair *pair = 0; + if (message.type() == (QXmppStunMessage::Binding | QXmppStunMessage::Request)) + { + // add remote candidate + pair = addRemoteCandidate(socket, remoteHost, remotePort, message.priority()); + + // send a binding response + QXmppStunMessage response; + response.setId(message.id()); + response.setType(QXmppStunMessage::Binding | QXmppStunMessage::Response); + response.setUsername(message.username()); + response.xorMappedHost = pair->remote.host(); + response.xorMappedPort = pair->remote.port(); + writeStun(response, pair); + + // update state + if (m_iceControlling || message.useCandidate) + { + debug(QString("ICE reverse check complete %1").arg(pair->toString())); + pair->checked |= QIODevice::ReadOnly; + } + + if (!m_iceControlling && !m_activePair && !m_remoteUser.isEmpty()) + { + // send a triggered connectivity test + QXmppStunMessage message; + message.setId(pair->transaction); + message.setType(QXmppStunMessage::Binding | QXmppStunMessage::Request); + message.setPriority(m_peerReflexivePriority); + message.setUsername(QString("%1:%2").arg(m_remoteUser, m_localUser)); + message.iceControlled = QByteArray(8, 0); + writeStun(message, pair); + } + + } else if (message.type() == (QXmppStunMessage::Binding | QXmppStunMessage::Response)) { + + // find the pair for this transaction + foreach (Pair *ptr, m_pairs) + { + if (ptr->transaction == message.id()) + { + pair = ptr; + break; + } + } + if (!pair) + { + debug(QString("Unknown transaction %1").arg(QString::fromAscii(message.id().toHex()))); + return; + } + // store peer-reflexive address + pair->reflexive.setHost(message.xorMappedHost); + pair->reflexive.setPort(message.xorMappedPort); + +#if 0 + // send a binding indication + QXmppStunMessage indication; + indication.setId(generateRandomBytes(ID_SIZE)); + indication.setType(BindingIndication); + m_socket->writeStun(indication, pair); +#endif + + // outgoing media can flow + debug(QString("ICE forward check complete %1").arg(pair->toString())); + pair->checked |= QIODevice::WriteOnly; + } + + // signal completion + if (pair && pair->checked == QIODevice::ReadWrite) + { + m_timer->stop(); + if (!m_activePair || pair->priority() > m_activePair->priority()) { + info(QString("ICE pair selected %1 (priority: %2)").arg( + pair->toString(), QString::number(pair->priority()))); + const bool wasConnected = (m_activePair != 0); + m_activePair = pair; + if (!wasConnected) + emit connected(); + } + } +} + +void QXmppIceComponent::turnConnected() +{ + // add the new local candidate + debug(QString("Adding relayed candidate %1 port %2").arg( + m_turnAllocation->relayedHost().toString(), + QString::number(m_turnAllocation->relayedPort()))); + QXmppJingleCandidate candidate; + candidate.setComponent(m_component); + candidate.setHost(m_turnAllocation->relayedHost()); + candidate.setId(generateStanzaHash(10)); + candidate.setPort(m_turnAllocation->relayedPort()); + candidate.setProtocol("udp"); + candidate.setType(QXmppJingleCandidate::RelayedType); + candidate.setPriority(candidatePriority(candidate)); + m_localCandidates << candidate; + + emit localCandidatesChanged(); +} + +static QList<QUdpSocket*> reservePort(const QList<QHostAddress> &addresses, quint16 port, QObject *parent) +{ + QList<QUdpSocket*> sockets; + foreach (const QHostAddress &address, addresses) { + QUdpSocket *socket = new QUdpSocket(parent); + sockets << socket; + if (!socket->bind(address, port)) { + for (int i = 0; i < sockets.size(); ++i) + delete sockets[i]; + sockets.clear(); + break; + } + } + return sockets; +} + +/// Returns the list of local network addresses. + +QList<QHostAddress> QXmppIceComponent::discoverAddresses() +{ + QList<QHostAddress> addresses; + foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) + { + if (!(interface.flags() & QNetworkInterface::IsRunning) || + interface.flags() & QNetworkInterface::IsLoopBack) + continue; + + foreach (const QNetworkAddressEntry &entry, interface.addressEntries()) + { + QHostAddress ip = entry.ip(); + if ((ip.protocol() != QAbstractSocket::IPv4Protocol && + ip.protocol() != QAbstractSocket::IPv6Protocol) || + entry.netmask().isNull()) + continue; + +#ifdef Q_OS_MAC + // FIXME: on Mac OS X, sending IPv6 UDP packets fails + if (ip.protocol() == QAbstractSocket::IPv6Protocol) + continue; +#endif + + // FIXME: for now skip IPv6 link-local addresses, seems to upset + // clients such as empathy + if (isIPv6LinkLocalAddress(ip)) { + ip.setScopeId(interface.name()); + continue; + } + addresses << ip; + } + } + return addresses; +} + +/// Tries to bind \a count UDP sockets on each of the given \a addresses. +/// +/// The port numbers are chosen so that they are consecutive, starting at +/// an even port. This makes them suitable for RTP/RTCP sockets pairs. +/// +/// \param addresses The network address on which to bind the sockets. +/// \param count The number of ports to reserve. +/// \param parent The parent object for the sockets. + +QList<QUdpSocket*> QXmppIceComponent::reservePorts(const QList<QHostAddress> &addresses, int count, QObject *parent) +{ + QList<QUdpSocket*> sockets; + if (addresses.isEmpty() || !count) + return sockets; + + const int expectedSize = addresses.size() * count; + quint16 port = 49152; + while (sockets.size() != expectedSize) { + // reserve first port (even number) + if (port % 2) + port++; + QList<QUdpSocket*> socketChunk; + while (socketChunk.isEmpty() && port <= 65536 - count) { + socketChunk = reservePort(addresses, port, parent); + if (socketChunk.isEmpty()) + port += 2; + } + if (socketChunk.isEmpty()) + return sockets; + + // reserve other ports + sockets << socketChunk; + for (int i = 1; i < count; ++i) { + socketChunk = reservePort(addresses, ++port, parent); + if (socketChunk.isEmpty()) + break; + sockets << socketChunk; + } + + // cleanup if we failed + if (sockets.size() != expectedSize) { + for (int i = 0; i < sockets.size(); ++i) + delete sockets[i]; + sockets.clear(); + } + } + return sockets; +} + +/// Sends a data packet to the remote party. +/// +/// \param datagram + +qint64 QXmppIceComponent::sendDatagram(const QByteArray &datagram) +{ + Pair *pair = m_activePair ? m_activePair : m_fallbackPair; + if (!pair) + return -1; + if (pair->socket) + return pair->socket->writeDatagram(datagram, pair->remote.host(), pair->remote.port()); + else if (m_turnAllocation->state() == QXmppTurnAllocation::ConnectedState) + return m_turnAllocation->writeDatagram(datagram, pair->remote.host(), pair->remote.port()); + else + return -1; +} + +/// Sends a STUN packet to the remote party. + +qint64 QXmppIceComponent::writeStun(const QXmppStunMessage &message, QXmppIceComponent::Pair *pair) +{ + qint64 ret; + const QString messagePassword = (message.type() & 0xFF00) ? m_localPassword : m_remotePassword; + if (pair->socket) + ret = pair->socket->writeDatagram( + message.encode(messagePassword.toUtf8()), + pair->remote.host(), + pair->remote.port()); + else if (m_turnAllocation->state() == QXmppTurnAllocation::ConnectedState) + ret = m_turnAllocation->writeDatagram( + message.encode(messagePassword.toUtf8()), + pair->remote.host(), + pair->remote.port()); + else + return -1; +#ifdef QXMPP_DEBUG_STUN + logSent(QString("Sent to %1\n%2").arg(pair->toString(), message.toString())); +#endif + return ret; +} + +/// Constructs a new ICE connection. +/// +/// \param controlling +/// \param parent + +QXmppIceConnection::QXmppIceConnection(QObject *parent) + : QXmppLoggable(parent), + m_iceControlling(false), + m_stunPort(0) +{ + bool check; + + m_localUser = generateStanzaHash(4); + m_localPassword = generateStanzaHash(22); + + // timer to limit connection time to 30 seconds + m_connectTimer = new QTimer(this); + m_connectTimer->setInterval(30000); + m_connectTimer->setSingleShot(true); + check = connect(m_connectTimer, SIGNAL(timeout()), + this, SLOT(slotTimeout())); + Q_ASSERT(check); + Q_UNUSED(check); +} + +/// Returns the given component of this ICE connection. +/// +/// \param component + +QXmppIceComponent *QXmppIceConnection::component(int component) +{ + return m_components.value(component); +} + +/// Adds a component to this ICE connection, for instance 1 for RTP +/// or 2 for RTCP. +/// +/// \param component + +void QXmppIceConnection::addComponent(int component) +{ + bool check; + Q_UNUSED(check); + + if (m_components.contains(component)) + { + warning(QString("Already have component %1").arg(QString::number(component))); + return; + } + + QXmppIceComponent *socket = new QXmppIceComponent(this); + socket->setComponent(component); + socket->setIceControlling(m_iceControlling); + socket->setLocalUser(m_localUser); + socket->setLocalPassword(m_localPassword); + socket->setStunServer(m_stunHost, m_stunPort); + socket->setTurnServer(m_turnHost, m_turnPort); + socket->setTurnUser(m_turnUser); + socket->setTurnPassword(m_turnPassword); + + check = connect(socket, SIGNAL(localCandidatesChanged()), + this, SIGNAL(localCandidatesChanged())); + Q_ASSERT(check); + + check = connect(socket, SIGNAL(connected()), + this, SLOT(slotConnected())); + Q_ASSERT(check); + + m_components[component] = socket; +} + +/// Adds a candidate for one of the remote components. +/// +/// \param candidate + +void QXmppIceConnection::addRemoteCandidate(const QXmppJingleCandidate &candidate) +{ + QXmppIceComponent *socket = m_components.value(candidate.component()); + if (!socket) + { + warning(QString("Not adding candidate for unknown component %1").arg( + QString::number(candidate.component()))); + return; + } + socket->addRemoteCandidate(candidate); +} + +/// Binds the local sockets to the specified addresses. +/// +/// \param addresses The addresses on which to listen. + +bool QXmppIceConnection::bind(const QList<QHostAddress> &addresses) +{ + // reserve ports + QList<QUdpSocket*> sockets = QXmppIceComponent::reservePorts(addresses, m_components.size()); + if (sockets.isEmpty() && !addresses.isEmpty()) + return false; + + // assign sockets + QList<int> keys = m_components.keys(); + qSort(keys); + int s = 0; + foreach (int k, keys) { + m_components[k]->setSockets(sockets.mid(s, addresses.size())); + s += addresses.size(); + } + + return true; +} + +/// Closes the ICE connection. + +void QXmppIceConnection::close() +{ + m_connectTimer->stop(); + foreach (QXmppIceComponent *socket, m_components.values()) + socket->close(); +} + +/// Starts ICE connectivity checks. + +void QXmppIceConnection::connectToHost() +{ + if (isConnected() || m_connectTimer->isActive()) + return; + + foreach (QXmppIceComponent *socket, m_components.values()) + socket->connectToHost(); + m_connectTimer->start(); +} + + +/// Returns true if ICE negotiation completed, false otherwise. + +bool QXmppIceConnection::isConnected() const +{ + foreach (QXmppIceComponent *socket, m_components.values()) + if (!socket->isConnected()) + return false; + return true; +} + +void QXmppIceConnection::setIceControlling(bool controlling) +{ + m_iceControlling = controlling; + foreach (QXmppIceComponent *socket, m_components.values()) + socket->setIceControlling(controlling); +} + +/// Returns the list of local HOST CANDIDATES candidates by iterating +/// over the available network interfaces. + +QList<QXmppJingleCandidate> QXmppIceConnection::localCandidates() const +{ + QList<QXmppJingleCandidate> candidates; + foreach (QXmppIceComponent *socket, m_components.values()) + candidates += socket->localCandidates(); + return candidates; +} + +/// Returns the local user fragment. + +QString QXmppIceConnection::localUser() const +{ + return m_localUser; +} + +/// Sets the local user fragment. +/// +/// You do not usually need to call this as one is automatically generated. +/// +/// \param user + +void QXmppIceConnection::setLocalUser(const QString &user) +{ + m_localUser = user; + foreach (QXmppIceComponent *socket, m_components.values()) + socket->setLocalUser(user); +} + +/// Returns the local password. + +QString QXmppIceConnection::localPassword() const +{ + return m_localPassword; +} + +/// Sets the local password. +/// +/// You do not usually need to call this as one is automatically generated. +/// +/// \param password + +void QXmppIceConnection::setLocalPassword(const QString &password) +{ + m_localPassword = password; + foreach (QXmppIceComponent *socket, m_components.values()) + socket->setLocalPassword(password); +} + +/// Sets the remote user fragment. +/// +/// \param user + +void QXmppIceConnection::setRemoteUser(const QString &user) +{ + foreach (QXmppIceComponent *socket, m_components.values()) + socket->setRemoteUser(user); +} + +/// Sets the remote password. +/// +/// \param password + +void QXmppIceConnection::setRemotePassword(const QString &password) +{ + foreach (QXmppIceComponent *socket, m_components.values()) + socket->setRemotePassword(password); +} + +/// Sets the STUN server to use to determine server-reflexive addresses +/// and ports. +/// +/// \param host The address of the STUN server. +/// \param port The port of the STUN server. + +void QXmppIceConnection::setStunServer(const QHostAddress &host, quint16 port) +{ + m_stunHost = host; + m_stunPort = port; + foreach (QXmppIceComponent *socket, m_components.values()) + socket->setStunServer(host, port); +} + +/// Sets the TURN server to use to relay packets in double-NAT configurations. +/// +/// \param host The address of the TURN server. +/// \param port The port of the TURN server. + +void QXmppIceConnection::setTurnServer(const QHostAddress &host, quint16 port) +{ + m_turnHost = host; + m_turnPort = port; + foreach (QXmppIceComponent *socket, m_components.values()) + socket->setTurnServer(host, port); +} + +/// Sets the \a user used for authentication with the TURN server. +/// +/// \param user + +void QXmppIceConnection::setTurnUser(const QString &user) +{ + m_turnUser = user; + foreach (QXmppIceComponent *socket, m_components.values()) + socket->setTurnUser(user); +} + +/// Sets the \a password used for authentication with the TURN server. +/// +/// \param password + +void QXmppIceConnection::setTurnPassword(const QString &password) +{ + m_turnPassword = password; + foreach (QXmppIceComponent *socket, m_components.values()) + socket->setTurnPassword(password); +} + +void QXmppIceConnection::slotConnected() +{ + foreach (QXmppIceComponent *socket, m_components.values()) + if (!socket->isConnected()) + return; + info(QString("ICE negotiation completed")); + m_connectTimer->stop(); + emit connected(); +} + +void QXmppIceConnection::slotTimeout() +{ + warning(QString("ICE negotiation timed out")); + foreach (QXmppIceComponent *socket, m_components.values()) + socket->close(); + emit disconnected(); +} + diff --git a/src/base/QXmppStun.h b/src/base/QXmppStun.h new file mode 100644 index 00000000..1792d3cb --- /dev/null +++ b/src/base/QXmppStun.h @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPSTUN_H +#define QXMPPSTUN_H + +#include <QObject> + +#include "QXmppLogger.h" +#include "QXmppJingleIq.h" + +class QDataStream; +class QUdpSocket; +class QTimer; + +/// \internal +/// +/// The QXmppStunMessage class represents a STUN message. +/// + +class QXmppStunMessage +{ +public: + enum MethodType { + Binding = 0x1, + SharedSecret = 0x2, + Allocate = 0x3, + Refresh = 0x4, + Send = 0x6, + Data = 0x7, + CreatePermission = 0x8, + ChannelBind = 0x9, + }; + + enum ClassType { + Request = 0x000, + Indication = 0x010, + Response = 0x100, + Error = 0x110, + }; + + QXmppStunMessage(); + + quint32 cookie() const; + void setCookie(quint32 cookie); + + QByteArray id() const; + void setId(const QByteArray &id); + + quint16 messageClass() const; + quint16 messageMethod() const; + + quint16 type() const; + void setType(quint16 type); + + // attributes + + quint32 changeRequest() const; + void setChangeRequest(quint32 changeRequest); + + quint16 channelNumber() const; + void setChannelNumber(quint16 channelNumber); + + QByteArray data() const; + void setData(const QByteArray &data); + + quint32 lifetime() const; + void setLifetime(quint32 changeRequest); + + QByteArray nonce() const; + void setNonce(const QByteArray &nonce); + + quint32 priority() const; + void setPriority(quint32 priority); + + QString realm() const; + void setRealm(const QString &realm); + + QByteArray reservationToken() const; + void setReservationToken(const QByteArray &reservationToken); + + quint8 requestedTransport() const; + void setRequestedTransport(quint8 requestedTransport); + + QString software() const; + void setSoftware(const QString &software); + + QString username() const; + void setUsername(const QString &username); + + QByteArray encode(const QByteArray &key = QByteArray(), bool addFingerprint = true) const; + bool decode(const QByteArray &buffer, const QByteArray &key = QByteArray(), QStringList *errors = 0); + QString toString() const; + static quint16 peekType(const QByteArray &buffer, quint32 &cookie, QByteArray &id); + + // attributes + int errorCode; + QString errorPhrase; + QByteArray iceControlling; + QByteArray iceControlled; + QHostAddress changedHost; + quint16 changedPort; + QHostAddress mappedHost; + quint16 mappedPort; + QHostAddress otherHost; + quint16 otherPort; + QHostAddress sourceHost; + quint16 sourcePort; + QHostAddress xorMappedHost; + quint16 xorMappedPort; + QHostAddress xorPeerHost; + quint16 xorPeerPort; + QHostAddress xorRelayedHost; + quint16 xorRelayedPort; + bool useCandidate; + +private: + quint32 m_cookie; + QByteArray m_id; + quint16 m_type; + + // attributes + QSet<quint16> m_attributes; + quint32 m_changeRequest; + quint16 m_channelNumber; + QByteArray m_data; + quint32 m_lifetime; + QByteArray m_nonce; + quint32 m_priority; + QString m_realm; + quint8 m_requestedTransport; + QByteArray m_reservationToken; + QString m_software; + QString m_username; +}; + +/// \internal +/// +/// The QXmppStunTransaction class represents a STUN transaction. +/// + +class QXmppStunTransaction : public QXmppLoggable +{ + Q_OBJECT + +public: + QXmppStunTransaction(const QXmppStunMessage &request, QObject *parent); + QXmppStunMessage request() const; + QXmppStunMessage response() const; + +signals: + void finished(); + void writeStun(const QXmppStunMessage &request); + +public slots: + void readStun(const QXmppStunMessage &response); + +private slots: + void retry(); + +private: + QXmppStunMessage m_request; + QXmppStunMessage m_response; + QTimer *m_retryTimer; + int m_tries; +}; + +/// \internal +/// +/// The QXmppTurnAllocation class represents a TURN allocation as defined +/// by RFC 5766 Traversal Using Relays around NAT (TURN). +/// + +class QXmppTurnAllocation : public QXmppLoggable +{ + Q_OBJECT + +public: + enum AllocationState + { + UnconnectedState, + ConnectingState, + ConnectedState, + ClosingState, + }; + + QXmppTurnAllocation(QObject *parent = 0); + ~QXmppTurnAllocation(); + + QHostAddress relayedHost() const; + quint16 relayedPort() const; + AllocationState state() const; + + void setServer(const QHostAddress &host, quint16 port = 3478); + void setUser(const QString &user); + void setPassword(const QString &password); + + qint64 writeDatagram(const QByteArray &data, const QHostAddress &host, quint16 port); + +signals: + /// \brief This signal is emitted once TURN allocation succeeds. + void connected(); + + /// \brief This signal is emitted when a data packet is received. + void datagramReceived(const QByteArray &data, const QHostAddress &host, quint16 port); + + /// \brief This signal is emitted when TURN allocation fails. + void disconnected(); + +public slots: + void connectToHost(); + void disconnectFromHost(); + +private slots: + void readyRead(); + void refresh(); + void refreshChannels(); + void transactionFinished(); + void writeStun(const QXmppStunMessage &message); + +private: + void handleDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port); + void setState(AllocationState state); + + QUdpSocket *socket; + QTimer *m_timer; + QTimer *m_channelTimer; + QString m_password; + QString m_username; + QHostAddress m_relayedHost; + quint16 m_relayedPort; + QHostAddress m_turnHost; + quint16 m_turnPort; + + // channels + typedef QPair<QHostAddress, quint16> Address; + quint16 m_channelNumber; + QMap<quint16, Address> m_channels; + + // state + quint32 m_lifetime; + QByteArray m_key; + QString m_realm; + QByteArray m_nonce; + AllocationState m_state; + QList<QXmppStunTransaction*> m_transactions; +}; + +/// \brief The QXmppIceComponent class represents a piece of a media stream +/// requiring a single transport address, as defined by RFC 5245 +/// (Interactive Connectivity Establishment). + +class QXmppIceComponent : public QXmppLoggable +{ + Q_OBJECT + +public: + QXmppIceComponent(QObject *parent=0); + ~QXmppIceComponent(); + void setIceControlling(bool controlling); + void setStunServer(const QHostAddress &host, quint16 port); + void setTurnServer(const QHostAddress &host, quint16 port); + void setTurnUser(const QString &user); + void setTurnPassword(const QString &password); + + QList<QXmppJingleCandidate> localCandidates() const; + void setLocalUser(const QString &user); + void setLocalPassword(const QString &password); + + int component() const; + void setComponent(int component); + + bool addRemoteCandidate(const QXmppJingleCandidate &candidate); + void setRemoteUser(const QString &user); + void setRemotePassword(const QString &password); + + bool isConnected() const; + void setSockets(QList<QUdpSocket*> sockets); + + static QList<QHostAddress> discoverAddresses(); + static QList<QUdpSocket*> reservePorts(const QList<QHostAddress> &addresses, int count, QObject *parent = 0); + +public slots: + void close(); + void connectToHost(); + qint64 sendDatagram(const QByteArray &datagram); + +private slots: + void checkCandidates(); + void checkStun(); + void handleDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port, QUdpSocket *socket = 0); + void readyRead(); + void turnConnected(); + +signals: + /// \brief This signal is emitted once ICE negotiation succeeds. + void connected(); + + /// \brief This signal is emitted when a data packet is received. + void datagramReceived(const QByteArray &datagram); + + /// \brief This signal is emitted when the list of local candidates changes. + void localCandidatesChanged(); + +private: + class Pair { + public: + Pair(int component, bool controlling); + quint64 priority() const; + QString toString() const; + + QIODevice::OpenMode checked; + QXmppJingleCandidate remote; + QXmppJingleCandidate reflexive; + QByteArray transaction; + QUdpSocket *socket; + + private: + int m_component; + bool m_controlling; + }; + + Pair *addRemoteCandidate(QUdpSocket *socket, const QHostAddress &host, quint16 port, quint32 priority); + qint64 writeStun(const QXmppStunMessage &message, QXmppIceComponent::Pair *pair); + + int m_component; + + QList<QXmppJingleCandidate> m_localCandidates; + QString m_localUser; + QString m_localPassword; + + Pair *m_activePair; + Pair *m_fallbackPair; + bool m_iceControlling; + QList<Pair*> m_pairs; + quint32 m_peerReflexivePriority; + QString m_remoteUser; + QString m_remotePassword; + + QList<QUdpSocket*> m_sockets; + QTimer *m_timer; + + // STUN server + QByteArray m_stunId; + QHostAddress m_stunHost; + quint16 m_stunPort; + QTimer *m_stunTimer; + int m_stunTries; + + // TURN server + QXmppTurnAllocation *m_turnAllocation; + bool m_turnConfigured; +}; + +/// \brief The QXmppIceConnection class represents a set of UDP sockets +/// capable of performing Interactive Connectivity Establishment (RFC 5245). +/// + +class QXmppIceConnection : public QXmppLoggable +{ + Q_OBJECT + +public: + QXmppIceConnection(QObject *parent = 0); + + QXmppIceComponent *component(int component); + void addComponent(int component); + void setIceControlling(bool controlling); + + QList<QXmppJingleCandidate> localCandidates() const; + QString localUser() const; + void setLocalUser(const QString &user); + QString localPassword() const; + void setLocalPassword(const QString &password); + + void addRemoteCandidate(const QXmppJingleCandidate &candidate); + void setRemoteUser(const QString &user); + void setRemotePassword(const QString &password); + + void setStunServer(const QHostAddress &host, quint16 port = 3478); + void setTurnServer(const QHostAddress &host, quint16 port = 3478); + void setTurnUser(const QString &user); + void setTurnPassword(const QString &password); + + bool bind(const QList<QHostAddress> &addresses); + bool isConnected() const; + +signals: + /// \brief This signal is emitted once ICE negotiation succeeds. + void connected(); + + /// \brief This signal is emitted when ICE negotiation fails. + void disconnected(); + + /// \brief This signal is emitted when the list of local candidates changes. + void localCandidatesChanged(); + +public slots: + void close(); + void connectToHost(); + +private slots: + void slotConnected(); + void slotTimeout(); + +private: + QTimer *m_connectTimer; + bool m_iceControlling; + QMap<int, QXmppIceComponent*> m_components; + QString m_localUser; + QString m_localPassword; + QHostAddress m_stunHost; + quint16 m_stunPort; + QHostAddress m_turnHost; + quint16 m_turnPort; + QString m_turnUser; + QString m_turnPassword; +}; + +#endif diff --git a/src/base/QXmppUtils.cpp b/src/base/QXmppUtils.cpp new file mode 100644 index 00000000..1698582f --- /dev/null +++ b/src/base/QXmppUtils.cpp @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#include <QBuffer> +#include <QByteArray> +#include <QCryptographicHash> +#include <QDateTime> +#include <QDebug> +#include <QDomElement> +#include <QRegExp> +#include <QString> +#include <QStringList> +#include <QXmlStreamWriter> + +#include "QXmppUtils.h" +#include "QXmppLogger.h" + +// adapted from public domain source by Ross Williams and Eric Durbin +// FIXME : is this valid for big-endian machines? +static quint32 crctable[256] = +{ + 0x00000000L, 0x77073096L, 0xEE0E612CL, 0x990951BAL, + 0x076DC419L, 0x706AF48FL, 0xE963A535L, 0x9E6495A3L, + 0x0EDB8832L, 0x79DCB8A4L, 0xE0D5E91EL, 0x97D2D988L, + 0x09B64C2BL, 0x7EB17CBDL, 0xE7B82D07L, 0x90BF1D91L, + 0x1DB71064L, 0x6AB020F2L, 0xF3B97148L, 0x84BE41DEL, + 0x1ADAD47DL, 0x6DDDE4EBL, 0xF4D4B551L, 0x83D385C7L, + 0x136C9856L, 0x646BA8C0L, 0xFD62F97AL, 0x8A65C9ECL, + 0x14015C4FL, 0x63066CD9L, 0xFA0F3D63L, 0x8D080DF5L, + 0x3B6E20C8L, 0x4C69105EL, 0xD56041E4L, 0xA2677172L, + 0x3C03E4D1L, 0x4B04D447L, 0xD20D85FDL, 0xA50AB56BL, + 0x35B5A8FAL, 0x42B2986CL, 0xDBBBC9D6L, 0xACBCF940L, + 0x32D86CE3L, 0x45DF5C75L, 0xDCD60DCFL, 0xABD13D59L, + 0x26D930ACL, 0x51DE003AL, 0xC8D75180L, 0xBFD06116L, + 0x21B4F4B5L, 0x56B3C423L, 0xCFBA9599L, 0xB8BDA50FL, + 0x2802B89EL, 0x5F058808L, 0xC60CD9B2L, 0xB10BE924L, + 0x2F6F7C87L, 0x58684C11L, 0xC1611DABL, 0xB6662D3DL, + 0x76DC4190L, 0x01DB7106L, 0x98D220BCL, 0xEFD5102AL, + 0x71B18589L, 0x06B6B51FL, 0x9FBFE4A5L, 0xE8B8D433L, + 0x7807C9A2L, 0x0F00F934L, 0x9609A88EL, 0xE10E9818L, + 0x7F6A0DBBL, 0x086D3D2DL, 0x91646C97L, 0xE6635C01L, + 0x6B6B51F4L, 0x1C6C6162L, 0x856530D8L, 0xF262004EL, + 0x6C0695EDL, 0x1B01A57BL, 0x8208F4C1L, 0xF50FC457L, + 0x65B0D9C6L, 0x12B7E950L, 0x8BBEB8EAL, 0xFCB9887CL, + 0x62DD1DDFL, 0x15DA2D49L, 0x8CD37CF3L, 0xFBD44C65L, + 0x4DB26158L, 0x3AB551CEL, 0xA3BC0074L, 0xD4BB30E2L, + 0x4ADFA541L, 0x3DD895D7L, 0xA4D1C46DL, 0xD3D6F4FBL, + 0x4369E96AL, 0x346ED9FCL, 0xAD678846L, 0xDA60B8D0L, + 0x44042D73L, 0x33031DE5L, 0xAA0A4C5FL, 0xDD0D7CC9L, + 0x5005713CL, 0x270241AAL, 0xBE0B1010L, 0xC90C2086L, + 0x5768B525L, 0x206F85B3L, 0xB966D409L, 0xCE61E49FL, + 0x5EDEF90EL, 0x29D9C998L, 0xB0D09822L, 0xC7D7A8B4L, + 0x59B33D17L, 0x2EB40D81L, 0xB7BD5C3BL, 0xC0BA6CADL, + 0xEDB88320L, 0x9ABFB3B6L, 0x03B6E20CL, 0x74B1D29AL, + 0xEAD54739L, 0x9DD277AFL, 0x04DB2615L, 0x73DC1683L, + 0xE3630B12L, 0x94643B84L, 0x0D6D6A3EL, 0x7A6A5AA8L, + 0xE40ECF0BL, 0x9309FF9DL, 0x0A00AE27L, 0x7D079EB1L, + 0xF00F9344L, 0x8708A3D2L, 0x1E01F268L, 0x6906C2FEL, + 0xF762575DL, 0x806567CBL, 0x196C3671L, 0x6E6B06E7L, + 0xFED41B76L, 0x89D32BE0L, 0x10DA7A5AL, 0x67DD4ACCL, + 0xF9B9DF6FL, 0x8EBEEFF9L, 0x17B7BE43L, 0x60B08ED5L, + 0xD6D6A3E8L, 0xA1D1937EL, 0x38D8C2C4L, 0x4FDFF252L, + 0xD1BB67F1L, 0xA6BC5767L, 0x3FB506DDL, 0x48B2364BL, + 0xD80D2BDAL, 0xAF0A1B4CL, 0x36034AF6L, 0x41047A60L, + 0xDF60EFC3L, 0xA867DF55L, 0x316E8EEFL, 0x4669BE79L, + 0xCB61B38CL, 0xBC66831AL, 0x256FD2A0L, 0x5268E236L, + 0xCC0C7795L, 0xBB0B4703L, 0x220216B9L, 0x5505262FL, + 0xC5BA3BBEL, 0xB2BD0B28L, 0x2BB45A92L, 0x5CB36A04L, + 0xC2D7FFA7L, 0xB5D0CF31L, 0x2CD99E8BL, 0x5BDEAE1DL, + 0x9B64C2B0L, 0xEC63F226L, 0x756AA39CL, 0x026D930AL, + 0x9C0906A9L, 0xEB0E363FL, 0x72076785L, 0x05005713L, + 0x95BF4A82L, 0xE2B87A14L, 0x7BB12BAEL, 0x0CB61B38L, + 0x92D28E9BL, 0xE5D5BE0DL, 0x7CDCEFB7L, 0x0BDBDF21L, + 0x86D3D2D4L, 0xF1D4E242L, 0x68DDB3F8L, 0x1FDA836EL, + 0x81BE16CDL, 0xF6B9265BL, 0x6FB077E1L, 0x18B74777L, + 0x88085AE6L, 0xFF0F6A70L, 0x66063BCAL, 0x11010B5CL, + 0x8F659EFFL, 0xF862AE69L, 0x616BFFD3L, 0x166CCF45L, + 0xA00AE278L, 0xD70DD2EEL, 0x4E048354L, 0x3903B3C2L, + 0xA7672661L, 0xD06016F7L, 0x4969474DL, 0x3E6E77DBL, + 0xAED16A4AL, 0xD9D65ADCL, 0x40DF0B66L, 0x37D83BF0L, + 0xA9BCAE53L, 0xDEBB9EC5L, 0x47B2CF7FL, 0x30B5FFE9L, + 0xBDBDF21CL, 0xCABAC28AL, 0x53B39330L, 0x24B4A3A6L, + 0xBAD03605L, 0xCDD70693L, 0x54DE5729L, 0x23D967BFL, + 0xB3667A2EL, 0xC4614AB8L, 0x5D681B02L, 0x2A6F2B94L, + 0xB40BBE37L, 0xC30C8EA1L, 0x5A05DF1BL, 0x2D02EF8DL +}; + +QDateTime datetimeFromString(const QString &str) +{ + QRegExp tzRe("(Z|([+-])([0-9]{2}):([0-9]{2}))"); + int tzPos = tzRe.indexIn(str, 19); + if (str.size() < 20 || tzPos < 0) + return QDateTime(); + + // process date and time + QDateTime dt = QDateTime::fromString(str.left(19), "yyyy-MM-ddThh:mm:ss"); + dt.setTimeSpec(Qt::UTC); + + // process milliseconds + if (tzPos > 20 && str.at(19) == '.') + { + QString millis = (str.mid(20, tzPos - 20) + "000").left(3); + dt = dt.addMSecs(millis.toInt()); + } + + // process time zone + if (tzRe.cap(1) != "Z") + { + int offset = tzRe.cap(3).toInt() * 3600 + tzRe.cap(4).toInt() * 60; + if (tzRe.cap(2) == "+") + dt = dt.addSecs(-offset); + else + dt = dt.addSecs(offset); + } + return dt; +} + +QString datetimeToString(const QDateTime &dt) +{ + QDateTime utc = dt.toUTC(); + if (utc.time().msec()) + return utc.toString("yyyy-MM-ddThh:mm:ss.zzzZ"); + else + return utc.toString("yyyy-MM-ddThh:mm:ssZ"); +} + +/// Parses a timezone offset (in seconds) from a string. +/// +/// \param str +/// + +int timezoneOffsetFromString(const QString &str) +{ + QRegExp tzRe("(Z|([+-])([0-9]{2}):([0-9]{2}))"); + if (!tzRe.exactMatch(str)) + return 0; + + // No offset from UTC + if (tzRe.cap(1) == "Z") + return 0; + + // Calculate offset + const int offset = tzRe.cap(3).toInt() * 3600 + + tzRe.cap(4).toInt() * 60; + if (tzRe.cap(2) == "-") + return -offset; + else + return offset; +} + +/// Serializes a timezone offset (in seconds) to a string. +/// +/// \param secs + +QString timezoneOffsetToString(int secs) +{ + if (!secs) + return QString::fromLatin1("Z"); + + const QTime tzoTime = QTime(0, 0, 0).addSecs(qAbs(secs)); + return (secs < 0 ? "-" : "+") + tzoTime.toString("hh:mm"); +} + +QString jidToDomain(const QString &jid) +{ + return jidToBareJid(jid).split("@").last(); +} + +QString jidToResource(const QString& jid) +{ + const int pos = jid.indexOf(QChar('/')); + if (pos < 0) + return QString(); + return jid.mid(pos+1); +} + +QString jidToUser(const QString &jid) +{ + const int pos = jid.indexOf(QChar('@')); + if (pos < 0) + return QString(); + return jid.left(pos); +} + +QString jidToBareJid(const QString& jid) +{ + const int pos = jid.indexOf(QChar('/')); + if (pos < 0) + return jid; + return jid.left(pos); +} + +quint32 generateCrc32(const QByteArray &in) +{ + quint32 result = 0xffffffff; + for(int n = 0; n < in.size(); ++n) + result = (result >> 8) ^ (crctable[(result & 0xff) ^ (quint8)in[n]]); + return result ^= 0xffffffff; +} + +static QByteArray generateHmac(QCryptographicHash::Algorithm algorithm, const QByteArray &key, const QByteArray &text) +{ + QCryptographicHash hasher(algorithm); + + const int B = 64; + QByteArray kpad = key + QByteArray(B - key.size(), 0); + + QByteArray ba; + for (int i = 0; i < B; ++i) + ba += kpad[i] ^ 0x5c; + + QByteArray tmp; + for (int i = 0; i < B; ++i) + tmp += kpad[i] ^ 0x36; + hasher.addData(tmp); + hasher.addData(text); + ba += hasher.result(); + + hasher.reset(); + hasher.addData(ba); + return hasher.result(); +} + +QByteArray generateHmacMd5(const QByteArray &key, const QByteArray &text) +{ + return generateHmac(QCryptographicHash::Md5, key, text); +} + +QByteArray generateHmacSha1(const QByteArray &key, const QByteArray &text) +{ + return generateHmac(QCryptographicHash::Sha1, key, text); +} + +/// Generates a random integer x between 0 and N-1. +/// +/// \param N + +int generateRandomInteger(int N) +{ + Q_ASSERT(N > 0 && N <= RAND_MAX); + int val; + while (N <= (val = qrand() / (RAND_MAX/N))) {}; + return val; +} + +/// Returns a random byte array of the specified size. +/// +/// \param length + +QByteArray generateRandomBytes(int length) +{ + QByteArray bytes(length, 'm'); + for (int i = 0; i < length; ++i) + bytes[i] = (char)generateRandomInteger(256); + return bytes; +} + +/// Returns a random alphanumerical string of the specified size. +/// +/// \param length + +QString generateStanzaHash(int length) +{ + const QString somechars = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const int N = somechars.size(); + QString hashResult; + for ( int idx = 0; idx < length; ++idx ) + hashResult += somechars[generateRandomInteger(N)]; + return hashResult; +} + +void helperToXmlAddAttribute(QXmlStreamWriter* stream, const QString& name, + const QString& value) +{ + if(!value.isEmpty()) + stream->writeAttribute(name,value); +} + +void helperToXmlAddDomElement(QXmlStreamWriter* stream, const QDomElement& element, const QStringList &omitNamespaces) +{ + stream->writeStartElement(element.tagName()); + + /* attributes */ + QString xmlns = element.namespaceURI(); + if (!xmlns.isEmpty() && !omitNamespaces.contains(xmlns)) + stream->writeAttribute("xmlns", xmlns); + QDomNamedNodeMap attrs = element.attributes(); + for (int i = 0; i < attrs.size(); i++) + { + QDomAttr attr = attrs.item(i).toAttr(); + stream->writeAttribute(attr.name(), attr.value()); + } + + /* children */ + QDomNode childNode = element.firstChild(); + while (!childNode.isNull()) + { + if (childNode.isElement()) + { + helperToXmlAddDomElement(stream, childNode.toElement(), QStringList() << xmlns); + } else if (childNode.isText()) { + stream->writeCharacters(childNode.toText().data()); + } + childNode = childNode.nextSibling(); + } + stream->writeEndElement(); +} + +void helperToXmlAddTextElement(QXmlStreamWriter* stream, const QString& name, + const QString& value) +{ + if(!value.isEmpty()) + stream->writeTextElement( name, value); + else + stream->writeEmptyElement(name); +} + diff --git a/src/base/QXmppUtils.h b/src/base/QXmppUtils.h new file mode 100644 index 00000000..80d84240 --- /dev/null +++ b/src/base/QXmppUtils.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Authors: + * Manjeet Dahiya + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPUTILS_H +#define QXMPPUTILS_H + +// forward declarations of QXmlStream* classes will not work on Mac, we need to +// include the whole header. +// See http://lists.trolltech.com/qt-interest/2008-07/thread00798-0.html +// for an explanation. +#include <QXmlStreamWriter> + +class QByteArray; +class QDateTime; +class QDomElement; +class QString; +class QStringList; + +// XEP-0082: XMPP Date and Time Profiles +QDateTime datetimeFromString(const QString &str); +QString datetimeToString(const QDateTime &dt); +int timezoneOffsetFromString(const QString &str); +QString timezoneOffsetToString(int secs); + +QString jidToDomain(const QString& jid); +QString jidToResource(const QString& jid); +QString jidToUser(const QString& jid); +QString jidToBareJid(const QString& jid); + +quint32 generateCrc32(const QByteArray &input); +QByteArray generateHmacMd5(const QByteArray &key, const QByteArray &text); +QByteArray generateHmacSha1(const QByteArray &key, const QByteArray &text); +int generateRandomInteger(int N); +QByteArray generateRandomBytes(int length); +QString generateStanzaHash(int length=32); + +void helperToXmlAddAttribute(QXmlStreamWriter* stream, const QString& name, + const QString& value); +void helperToXmlAddDomElement(QXmlStreamWriter* stream, + const QDomElement& element, const QStringList &omitNamespaces); +void helperToXmlAddTextElement(QXmlStreamWriter* stream, const QString& name, + const QString& value); + +#endif // QXMPPUTILS_H diff --git a/src/base/QXmppVCardIq.cpp b/src/base/QXmppVCardIq.cpp new file mode 100644 index 00000000..aaaf4554 --- /dev/null +++ b/src/base/QXmppVCardIq.cpp @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#include <QBuffer> +#include <QXmlStreamWriter> + +#include "QXmppVCardIq.h" +#include "QXmppUtils.h" +#include "QXmppConstants.h" + +QString getImageType(const QByteArray &contents) +{ + if (contents.startsWith("\x89PNG\x0d\x0a\x1a\x0a")) + return "image/png"; + else if (contents.startsWith("\x8aMNG")) + return "video/x-mng"; + else if (contents.startsWith("GIF8")) + return "image/gif"; + else if (contents.startsWith("BM")) + return "image/bmp"; + else if (contents.contains("/* XPM */")) + return "image/x-xpm"; + else if (contents.contains("<?xml") && contents.contains("<svg")) + return "image/svg+xml"; + else if (contents.startsWith("\xFF\xD8\xFF\xE0")) + return "image/jpeg"; + return "image/unknown"; +} + +/// Constructs a QXmppVCardIq for the specified recipient. +/// +/// \param jid + +QXmppVCardIq::QXmppVCardIq(const QString& jid) : QXmppIq(QXmppIq::Get) +{ + // for self jid should be empty + setTo(jid); +} + +/// Returns the date of birth of the individual associated with the vCard. +/// + +QDate QXmppVCardIq::birthday() const +{ + return m_birthday; +} + +/// Sets the date of birth of the individual associated with the vCard. +/// +/// \param birthday + +void QXmppVCardIq::setBirthday(const QDate &birthday) +{ + m_birthday = birthday; +} + +/// Returns the email address. +/// + +QString QXmppVCardIq::email() const +{ + return m_email; +} + +/// Sets the email address. +/// +/// \param email + +void QXmppVCardIq::setEmail(const QString &email) +{ + m_email = email; +} + +/// Returns the first name. +/// + +QString QXmppVCardIq::firstName() const +{ + return m_firstName; +} + +/// Sets the first name. +/// +/// \param firstName + +void QXmppVCardIq::setFirstName(const QString &firstName) +{ + m_firstName = firstName; +} + +/// Returns the full name. +/// + +QString QXmppVCardIq::fullName() const +{ + return m_fullName; +} + +/// Sets the full name. +/// +/// \param fullName + +void QXmppVCardIq::setFullName(const QString &fullName) +{ + m_fullName = fullName; +} + +/// Returns the last name. +/// + +QString QXmppVCardIq::lastName() const +{ + return m_lastName; +} + +/// Sets the last name. +/// +/// \param lastName + +void QXmppVCardIq::setLastName(const QString &lastName) +{ + m_lastName = lastName; +} + +/// Returns the middle name. +/// + +QString QXmppVCardIq::middleName() const +{ + return m_middleName; +} + +/// Sets the middle name. +/// +/// \param middleName + +void QXmppVCardIq::setMiddleName(const QString &middleName) +{ + m_middleName = middleName; +} + +/// Returns the nickname. +/// + +QString QXmppVCardIq::nickName() const +{ + return m_nickName; +} + +/// Sets the nickname. +/// +/// \param nickName + +void QXmppVCardIq::setNickName(const QString &nickName) +{ + m_nickName = nickName; +} + +/// Returns the URL associated with the vCard. It can represent the user's +/// homepage or a location at which you can find real-time information about +/// the vCard. + +QString QXmppVCardIq::url() const +{ + return m_url; +} + +/// Sets the URL associated with the vCard. It can represent the user's +/// homepage or a location at which you can find real-time information about +/// the vCard. +/// +/// \param url + +void QXmppVCardIq::setUrl(const QString& url) +{ + m_url = url; +} + +/// Returns the photo's binary contents. +/// +/// If you want to use the photo as a QImage you can use: +/// +/// \code +/// QBuffer buffer; +/// buffer.setData(myCard.photo()); +/// buffer.open(QIODevice::ReadOnly); +/// QImageReader imageReader(&buffer); +/// QImage myImage = imageReader.read(); +/// \endcode + +QByteArray QXmppVCardIq::photo() const +{ + return m_photo; +} + +/// Sets the photo's binary contents. + +void QXmppVCardIq::setPhoto(const QByteArray& photo) +{ + m_photo = photo; +} + +/// Returns the photo's MIME type. + +QString QXmppVCardIq::photoType() const +{ + return m_photoType; +} + +/// Sets the photo's MIME type. + +void QXmppVCardIq::setPhotoType(const QString& photoType) +{ + m_photoType = photoType; +} + +bool QXmppVCardIq::isVCard(const QDomElement &nodeRecv) +{ + return nodeRecv.firstChildElement("vCard").namespaceURI() == ns_vcard; +} + +void QXmppVCardIq::parseElementFromChild(const QDomElement& nodeRecv) +{ + // vCard + QDomElement cardElement = nodeRecv.firstChildElement("vCard"); + m_birthday = QDate::fromString(cardElement.firstChildElement("BDAY").text(), "yyyy-MM-dd"); + QDomElement emailElement = cardElement.firstChildElement("EMAIL"); + m_email = emailElement.firstChildElement("USERID").text(); + m_fullName = cardElement.firstChildElement("FN").text(); + m_nickName = cardElement.firstChildElement("NICKNAME").text(); + QDomElement nameElement = cardElement.firstChildElement("N"); + m_firstName = nameElement.firstChildElement("GIVEN").text(); + m_lastName = nameElement.firstChildElement("FAMILY").text(); + m_middleName = nameElement.firstChildElement("MIDDLE").text(); + m_url = cardElement.firstChildElement("URL").text(); + QDomElement photoElement = cardElement.firstChildElement("PHOTO"); + QByteArray base64data = photoElement. + firstChildElement("BINVAL").text().toAscii(); + m_photo = QByteArray::fromBase64(base64data); + m_photoType = photoElement.firstChildElement("TYPE").text(); +} + +void QXmppVCardIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("vCard"); + writer->writeAttribute("xmlns", ns_vcard); + if (m_birthday.isValid()) + helperToXmlAddTextElement(writer, "BDAY", m_birthday.toString("yyyy-MM-dd")); + if (!m_email.isEmpty()) + { + writer->writeStartElement("EMAIL"); + writer->writeEmptyElement("INTERNET"); + helperToXmlAddTextElement(writer, "USERID", m_email); + writer->writeEndElement(); + } + if (!m_fullName.isEmpty()) + helperToXmlAddTextElement(writer, "FN", m_fullName); + if(!m_nickName.isEmpty()) + helperToXmlAddTextElement(writer, "NICKNAME", m_nickName); + if (!m_firstName.isEmpty() || + !m_lastName.isEmpty() || + !m_middleName.isEmpty()) + { + writer->writeStartElement("N"); + if (!m_firstName.isEmpty()) + helperToXmlAddTextElement(writer, "GIVEN", m_firstName); + if (!m_lastName.isEmpty()) + helperToXmlAddTextElement(writer, "FAMILY", m_lastName); + if (!m_middleName.isEmpty()) + helperToXmlAddTextElement(writer, "MIDDLE", m_middleName); + writer->writeEndElement(); + } + if (!m_url.isEmpty()) + helperToXmlAddTextElement(writer, "URL", m_url); + + if(!photo().isEmpty()) + { + writer->writeStartElement("PHOTO"); + QString photoType = m_photoType; + if (photoType.isEmpty()) + photoType = getImageType(m_photo); + helperToXmlAddTextElement(writer, "TYPE", photoType); + helperToXmlAddTextElement(writer, "BINVAL", m_photo.toBase64()); + writer->writeEndElement(); + } + + writer->writeEndElement(); +} + diff --git a/src/base/QXmppVCardIq.h b/src/base/QXmppVCardIq.h new file mode 100644 index 00000000..348c00fc --- /dev/null +++ b/src/base/QXmppVCardIq.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Manjeet Dahiya + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + + +#ifndef QXMPPVCARDIQ_H +#define QXMPPVCARDIQ_H + +#include "QXmppIq.h" +#include <QDate> +#include <QMap> +#include <QDomElement> + +class QImage; + +/// \brief Represents the XMPP vCard. +/// +/// The functions names are self explanatory. +/// Look at QXmppVCardManager and XEP-0054: vcard-temp for more details. +/// +/// There are many field of XMPP vCard which are not present in +/// this class. File a issue for the same. We will add the requested +/// field to this class. +/// + +class QXmppVCardIq : public QXmppIq +{ +public: + QXmppVCardIq(const QString& bareJid = ""); + + QDate birthday() const; + void setBirthday(const QDate &birthday); + + QString email() const; + void setEmail(const QString&); + + QString firstName() const; + void setFirstName(const QString&); + + QString fullName() const; + void setFullName(const QString&); + + QString lastName() const; + void setLastName(const QString&); + + QString middleName() const; + void setMiddleName(const QString&); + + QString nickName() const; + void setNickName(const QString&); + + QByteArray photo() const; + void setPhoto(const QByteArray&); + + QString photoType() const; + void setPhotoType(const QString &type); + + QString url() const; + void setUrl(const QString&); + + /// \cond + static bool isVCard(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement&); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QDate m_birthday; + QString m_email; + QString m_firstName; + QString m_fullName; + QString m_lastName; + QString m_middleName; + QString m_nickName; + QString m_url; + + // not as 64 base + QByteArray m_photo; + QString m_photoType; +}; + +#endif // QXMPPVCARDIQ_H diff --git a/src/base/QXmppVersionIq.cpp b/src/base/QXmppVersionIq.cpp new file mode 100644 index 00000000..358596b6 --- /dev/null +++ b/src/base/QXmppVersionIq.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#include <QDomElement> + +#include "QXmppConstants.h" +#include "QXmppUtils.h" +#include "QXmppVersionIq.h" + +/// Returns the name of the software. +/// + +QString QXmppVersionIq::name() const +{ + return m_name; +} + +/// Sets the name of the software. +/// +/// \param name + +void QXmppVersionIq::setName(const QString &name) +{ + m_name = name; +} + +/// Returns the operating system. +/// + +QString QXmppVersionIq::os() const +{ + return m_os; +} + +/// Sets the operating system. +/// +/// \param os + +void QXmppVersionIq::setOs(const QString &os) +{ + m_os = os; +} + +/// Returns the software version. +/// + +QString QXmppVersionIq::version() const +{ + return m_version; +} + +/// Sets the software version. +/// +/// \param version + +void QXmppVersionIq::setVersion(const QString &version) +{ + m_version = version; +} + +bool QXmppVersionIq::isVersionIq(const QDomElement &element) +{ + QDomElement queryElement = element.firstChildElement("query"); + return queryElement.namespaceURI() == ns_version; +} + +void QXmppVersionIq::parseElementFromChild(const QDomElement &element) +{ + QDomElement queryElement = element.firstChildElement("query"); + m_name = queryElement.firstChildElement("name").text(); + m_os = queryElement.firstChildElement("os").text(); + m_version = queryElement.firstChildElement("version").text(); +} + +void QXmppVersionIq::toXmlElementFromChild(QXmlStreamWriter *writer) const +{ + writer->writeStartElement("query"); + writer->writeAttribute("xmlns", ns_version); + + if (!m_name.isEmpty()) + helperToXmlAddTextElement(writer, "name", m_name); + + if (!m_os.isEmpty()) + helperToXmlAddTextElement(writer, "os", m_os); + + if (!m_version.isEmpty()) + helperToXmlAddTextElement(writer, "version", m_version); + + writer->writeEndElement(); +} + diff --git a/src/base/QXmppVersionIq.h b/src/base/QXmppVersionIq.h new file mode 100644 index 00000000..57a800e0 --- /dev/null +++ b/src/base/QXmppVersionIq.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2008-2011 The QXmpp developers + * + * Author: + * Jeremy Lainé + * + * Source: + * http://code.google.com/p/qxmpp + * + * This file is a part of QXmpp library. + * + * 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. + * + */ + +#ifndef QXMPPVERSIONIQ_H +#define QXMPPVERSIONIQ_H + +#include "QXmppIq.h" + +/// \brief The QXmppVersionIq class represents an IQ for conveying a software +/// version as defined by XEP-0092: Software Version. +/// +/// \ingroup Stanzas + +class QXmppVersionIq : public QXmppIq +{ +public: + QString name() const; + void setName(const QString &name); + + QString os() const; + void setOs(const QString &os); + + QString version() const; + void setVersion(const QString &version); + + /// \cond + static bool isVersionIq(const QDomElement &element); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement &element); + void toXmlElementFromChild(QXmlStreamWriter *writer) const; + /// \endcond + +private: + QString m_name; + QString m_os; + QString m_version; +}; + +#endif diff --git a/src/base/qdnslookup.cpp b/src/base/qdnslookup.cpp new file mode 100644 index 00000000..7b1e7c8f --- /dev/null +++ b/src/base/qdnslookup.cpp @@ -0,0 +1,989 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdnslookup.h" +#include "qdnslookup_p.h" + +#include <QCoreApplication> +#include <QDateTime> +#include <QMetaType> +#include <QThreadStorage> +#include <QUrl> + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QDnsLookupThreadPool, theDnsLookupThreadPool); +Q_GLOBAL_STATIC(QThreadStorage<bool *>, theDnsLookupSeedStorage); + +static bool qt_qdnsmailexchangerecord_less_than(const QDnsMailExchangeRecord &r1, const QDnsMailExchangeRecord &r2) +{ + // Lower numbers are more preferred than higher ones. + return r1.preference() < r2.preference(); +} + +/*! + Sorts a list of QDnsMailExchangeRecord objects according to RFC 5321. +*/ + +static void qt_qdnsmailexchangerecord_sort(QList<QDnsMailExchangeRecord> &records) +{ + // If we have no more than one result, we are done. + if (records.size() <= 1) + return; + + // Order the records by preference. + qSort(records.begin(), records.end(), qt_qdnsmailexchangerecord_less_than); + + int i = 0; + while (i < records.size()) { + + // Determine the slice of records with the current preference. + QList<QDnsMailExchangeRecord> slice; + const quint16 slicePreference = records[i].preference(); + for (int j = i; j < records.size(); ++j) { + if (records[j].preference() != slicePreference) + break; + slice << records[j]; + } + + // Randomize the slice of records. + while (!slice.isEmpty()) { + const unsigned int pos = qrand() % slice.size(); + records[i++] = slice.takeAt(pos); + } + } +} + +static bool qt_qdnsservicerecord_less_than(const QDnsServiceRecord &r1, const QDnsServiceRecord &r2) +{ + // Order by priority, or if the priorities are equal, + // put zero weight records first. + return r1.priority() < r2.priority() + || (r1.priority() == r2.priority() + && r1.weight() == 0 && r2.weight() > 0); +} + +/*! + Sorts a list of QDnsServiceRecord objects according to RFC 2782. +*/ + +static void qt_qdnsservicerecord_sort(QList<QDnsServiceRecord> &records) +{ + // If we have no more than one result, we are done. + if (records.size() <= 1) + return; + + // Order the records by priority, and for records with an equal + // priority, put records with a zero weight first. + qSort(records.begin(), records.end(), qt_qdnsservicerecord_less_than); + + int i = 0; + while (i < records.size()) { + + // Determine the slice of records with the current priority. + QList<QDnsServiceRecord> slice; + const quint16 slicePriority = records[i].priority(); + unsigned int sliceWeight = 0; + for (int j = i; j < records.size(); ++j) { + if (records[j].priority() != slicePriority) + break; + sliceWeight += records[j].weight(); + slice << records[j]; + } +#ifdef QDNSLOOKUP_DEBUG + qDebug("qt_qdnsservicerecord_sort() : priority %i (size: %i, total weight: %i)", + slicePriority, slice.size(), sliceWeight); +#endif + + // Order the slice of records. + while (!slice.isEmpty()) { + const unsigned int weightThreshold = qrand() % (sliceWeight + 1); + unsigned int summedWeight = 0; + for (int j = 0; j < slice.size(); ++j) { + summedWeight += slice[j].weight(); + if (summedWeight >= weightThreshold) { +#ifdef QDNSLOOKUP_DEBUG + qDebug("qt_qdnsservicerecord_sort() : adding %s %i (weight: %i)", + qPrintable(slice[j].target()), slice[j].port(), + slice[j].weight()); +#endif + // Adjust the slice weight and take the current record. + sliceWeight -= slice[j].weight(); + records[i++] = slice.takeAt(j); + break; + } + } + } + } +} + +/*! + \class QDnsLookup + \brief The QDnsLookup class represents a DNS lookup. + + \inmodule QtNetwork + \ingroup network + + QDnsLookup uses the mechanisms provided by the operating system to perform + DNS lookups. To perform a lookup you need to specify a \l name and \l type + then invoke the \l{QDnsLookup::lookup()}{lookup()} slot. The + \l{QDnsLookup::finished()}{finished()} signal will be emitted upon + completion. + + For example, you can determine which servers an XMPP chat client should + connect to for a given domain with: + + \snippet doc/src/snippets/code/src_network_kernel_qdnslookup.cpp 0 + + Once the request finishes you can handle the results with: + + \snippet doc/src/snippets/code/src_network_kernel_qdnslookup.cpp 1 + + \note If you simply want to find the IP address(es) associated with a host + name, or the host name associated with an IP address you should use + QHostInfo instead. +*/ + +/*! + \enum QDnsLookup::Error + + Indicates all possible error conditions found during the + processing of the DNS lookup. + + \value NoError no error condition. + + \value ResolverError there was an error initializing the system's + DNS resolver. + + \value OperationCancelledError the lookup was aborted using the abort() + method. + + \value InvalidRequestError the requested DNS lookup was invalid. + + \value InvalidReplyError the reply returned by the server was invalid. + + \value ServerFailureError the server encountered an internal failure + while processing the request (SERVFAIL). + + \value ServerRefusedError the server refused to process the request for + security or policy reasons (REFUSED). + + \value NotFoundError the requested domain name does not exist + (NXDOMAIN). +*/ + +/*! + \enum QDnsLookup::Type + + Indicates the type of DNS lookup that was performed. + + \value A IPv4 address records. + + \value AAAA IPv6 address records. + + \value ANY any records. + + \value CNAME canonical name records. + + \value MX mail exchange records. + + \value NS name server records. + + \value PTR pointer records. + + \value SRV service records. + + \value TXT text records. +*/ + +/*! + \fn void QDnsLookup::finished() + + This signal is emitted when the reply has finished processing. +*/ + +/*! + \fn void QDnsLookup::nameChanged(const QString &name) + + This signal is emitted when the lookup \l name changes. + \a name is the new lookup name. +*/ + +/*! + \fn void QDnsLookup::typeChanged(Type type) + + This signal is emitted when the lookup \l type changes. + \a type is the new lookup type. +*/ + +/*! + Constructs a QDnsLookup object and sets \a parent as the parent object. + + The \l type property will default to QDnsLookup::A. +*/ + +QDnsLookup::QDnsLookup(QObject *parent) + : QObject(parent) + , d_ptr(new QDnsLookupPrivate(this)) +{ + qRegisterMetaType<QDnsLookupReply>(); +} +/*! + Constructs a QDnsLookup object for the given \a type and \a name and sets + \a parent as the parent object. +*/ + +QDnsLookup::QDnsLookup(Type type, const QString &name, QObject *parent) + : QObject(parent) + , d_ptr(new QDnsLookupPrivate(this)) +{ + Q_D(QDnsLookup); + qRegisterMetaType<QDnsLookupReply>(); + d->name = name; + d->type = type; +} + +/*! + Destroys the QDnsLookup object. + + It is safe to delete a QDnsLookup object even if it is not finished, you + will simply never receive its results. +*/ + +QDnsLookup::~QDnsLookup() +{ +} + +/*! + \property QDnsLookup::error + \brief the type of error that occurred if the DNS lookup failed, or NoError. +*/ + +QDnsLookup::Error QDnsLookup::error() const +{ + return d_func()->reply.error; +} + +/*! + \property QDnsLookup::errorString + \brief a human-readable description of the error if the DNS lookup failed. +*/ + +QString QDnsLookup::errorString() const +{ + return d_func()->reply.errorString; +} + +/*! + \property QDnsLookup::finished + \brief whether the reply has finished or was aborted. +*/ + +bool QDnsLookup::isFinished() const +{ + return d_func()->isFinished; +} + +/*! + \property QDnsLookup::name + \brief the name to lookup. + + \note The name will be encoded using IDNA, which means it's unsuitable for + querying SRV records compatible with the DNS-SD specification. +*/ + +QString QDnsLookup::name() const +{ + return d_func()->name; +} + +void QDnsLookup::setName(const QString &name) +{ + Q_D(QDnsLookup); + if (name != d->name) { + d->name = name; + emit nameChanged(name); + } +} + +/*! + \property QDnsLookup::type + \brief the type of DNS lookup. +*/ + +QDnsLookup::Type QDnsLookup::type() const +{ + return d_func()->type; +} + +void QDnsLookup::setType(Type type) +{ + Q_D(QDnsLookup); + if (type != d->type) { + d->type = type; + emit typeChanged(type); + } +} + +/*! + Returns the list of canonical name records associated with this lookup. +*/ + +QList<QDnsDomainNameRecord> QDnsLookup::canonicalNameRecords() const +{ + return d_func()->reply.canonicalNameRecords; +} + +/*! + Returns the list of host address records associated with this lookup. +*/ + +QList<QDnsHostAddressRecord> QDnsLookup::hostAddressRecords() const +{ + return d_func()->reply.hostAddressRecords; +} + +/*! + Returns the list of mail exchange records associated with this lookup. + + The records are sorted according to + \l{http://www.rfc-editor.org/rfc/rfc5321.txt}{RFC 5321}, so if you use them + to connect to servers, you should try them in the order they are listed. +*/ + +QList<QDnsMailExchangeRecord> QDnsLookup::mailExchangeRecords() const +{ + return d_func()->reply.mailExchangeRecords; +} + +/*! + Returns the list of name server records associated with this lookup. +*/ + +QList<QDnsDomainNameRecord> QDnsLookup::nameServerRecords() const +{ + return d_func()->reply.nameServerRecords; +} + +/*! + Returns the list of pointer records associated with this lookup. +*/ + +QList<QDnsDomainNameRecord> QDnsLookup::pointerRecords() const +{ + return d_func()->reply.pointerRecords; +} + +/*! + Returns the list of service records associated with this lookup. + + The records are sorted according to + \l{http://www.rfc-editor.org/rfc/rfc2782.txt}{RFC 2782}, so if you use them + to connect to servers, you should try them in the order they are listed. +*/ + +QList<QDnsServiceRecord> QDnsLookup::serviceRecords() const +{ + return d_func()->reply.serviceRecords; +} + +/*! + Returns the list of text records associated with this lookup. +*/ + +QList<QDnsTextRecord> QDnsLookup::textRecords() const +{ + return d_func()->reply.textRecords; +} + +/*! + Aborts the DNS lookup operation. + + If the lookup is already finished, does nothing. +*/ + +void QDnsLookup::abort() +{ + Q_D(QDnsLookup); + if (d->runnable) { + d->runnable = 0; + d->reply = QDnsLookupReply(); + d->reply.error = QDnsLookup::OperationCancelledError; + d->reply.errorString = tr("Operation cancelled"); + d->isFinished = true; + emit finished(); + } +} + +/*! + Performs the DNS lookup. + + The \l{QDnsLookup::finished()}{finished()} signal is emitted upon completion. +*/ + +void QDnsLookup::lookup() +{ + Q_D(QDnsLookup); + d->isFinished = false; + d->reply = QDnsLookupReply(); + d->runnable = new QDnsLookupRunnable(d->type, QUrl::toAce(d->name)); + connect(d->runnable, SIGNAL(finished(QDnsLookupReply)), + this, SLOT(_q_lookupFinished(QDnsLookupReply)), + Qt::BlockingQueuedConnection); + theDnsLookupThreadPool()->start(d->runnable); +} + +/*! + \class QDnsDomainNameRecord + \brief The QDnsDomainNameRecord class stores information about a domain + name record. + + \inmodule QtNetwork + \ingroup network + + When performing a name server lookup, zero or more records will be returned. + Each record is represented by a QDnsDomainNameRecord instance. + + \sa QDnsLookup +*/ + +/*! + Constructs an empty domain name record object. +*/ + +QDnsDomainNameRecord::QDnsDomainNameRecord() + : d(new QDnsDomainNameRecordPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ + +QDnsDomainNameRecord::QDnsDomainNameRecord(const QDnsDomainNameRecord &other) + : d(other.d) +{ +} + +/*! + Destroys a domain name record. +*/ + +QDnsDomainNameRecord::~QDnsDomainNameRecord() +{ +} + +/*! + Returns the name for this record. +*/ + +QString QDnsDomainNameRecord::name() const +{ + return d->name; +} + +/*! + Returns the duration in seconds for which this record is valid. +*/ + +quint32 QDnsDomainNameRecord::timeToLive() const +{ + return d->timeToLive; +} + +/*! + Returns the value for this domain name record. +*/ + +QString QDnsDomainNameRecord::value() const +{ + return d->value; +} + +/*! + Assigns the data of the \a other object to this record object, + and returns a reference to it. +*/ + +QDnsDomainNameRecord &QDnsDomainNameRecord::operator=(const QDnsDomainNameRecord &other) +{ + d = other.d; + return *this; +} + +/*! + \class QDnsHostAddressRecord + \brief The QDnsHostAddressRecord class stores information about a host + address record. + + \inmodule QtNetwork + \ingroup network + + When performing an address lookup, zero or more records will be + returned. Each record is represented by a QDnsHostAddressRecord instance. + + \sa QDnsLookup +*/ + +/*! + Constructs an empty host address record object. +*/ + +QDnsHostAddressRecord::QDnsHostAddressRecord() + : d(new QDnsHostAddressRecordPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ + +QDnsHostAddressRecord::QDnsHostAddressRecord(const QDnsHostAddressRecord &other) + : d(other.d) +{ +} + +/*! + Destroys a host address record. +*/ + +QDnsHostAddressRecord::~QDnsHostAddressRecord() +{ +} + +/*! + Returns the name for this record. +*/ + +QString QDnsHostAddressRecord::name() const +{ + return d->name; +} + +/*! + Returns the duration in seconds for which this record is valid. +*/ + +quint32 QDnsHostAddressRecord::timeToLive() const +{ + return d->timeToLive; +} + +/*! + Returns the value for this host address record. +*/ + +QHostAddress QDnsHostAddressRecord::value() const +{ + return d->value; +} + +/*! + Assigns the data of the \a other object to this record object, + and returns a reference to it. +*/ + +QDnsHostAddressRecord &QDnsHostAddressRecord::operator=(const QDnsHostAddressRecord &other) +{ + d = other.d; + return *this; +} + +/*! + \class QDnsMailExchangeRecord + \brief The QDnsMailExchangeRecord class stores information about a DNS MX record. + + \inmodule QtNetwork + \ingroup network + + When performing a lookup on a service, zero or more records will be + returned. Each record is represented by a QDnsMailExchangeRecord instance. + + The meaning of the fields is defined in + \l{http://www.rfc-editor.org/rfc/rfc1035.txt}{RFC 1035}. + + \sa QDnsLookup +*/ + +/*! + Constructs an empty mail exchange record object. +*/ + +QDnsMailExchangeRecord::QDnsMailExchangeRecord() + : d(new QDnsMailExchangeRecordPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ + +QDnsMailExchangeRecord::QDnsMailExchangeRecord(const QDnsMailExchangeRecord &other) + : d(other.d) +{ +} + +/*! + Destroys a mail exchange record. +*/ + +QDnsMailExchangeRecord::~QDnsMailExchangeRecord() +{ +} + +/*! + Returns the domain name of the mail exchange for this record. +*/ + +QString QDnsMailExchangeRecord::exchange() const +{ + return d->exchange; +} + +/*! + Returns the name for this record. +*/ + +QString QDnsMailExchangeRecord::name() const +{ + return d->name; +} + +/*! + Returns the preference for this record. +*/ + +quint16 QDnsMailExchangeRecord::preference() const +{ + return d->preference; +} + +/*! + Returns the duration in seconds for which this record is valid. +*/ + +quint32 QDnsMailExchangeRecord::timeToLive() const +{ + return d->timeToLive; +} + +/*! + Assigns the data of the \a other object to this record object, + and returns a reference to it. +*/ + +QDnsMailExchangeRecord &QDnsMailExchangeRecord::operator=(const QDnsMailExchangeRecord &other) +{ + d = other.d; + return *this; +} + +/*! + \class QDnsServiceRecord + \brief The QDnsServiceRecord class stores information about a DNS SRV record. + + \inmodule QtNetwork + \ingroup network + + When performing a lookup on a service, zero or more records will be + returned. Each record is represented by a QDnsServiceRecord instance. + + The meaning of the fields is defined in + \l{http://www.rfc-editor.org/rfc/rfc2782.txt}{RFC 2782}. + + \sa QDnsLookup +*/ + +/*! + Constructs an empty service record object. +*/ + +QDnsServiceRecord::QDnsServiceRecord() + : d(new QDnsServiceRecordPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ + +QDnsServiceRecord::QDnsServiceRecord(const QDnsServiceRecord &other) + : d(other.d) +{ +} + +/*! + Destroys a service record. +*/ + +QDnsServiceRecord::~QDnsServiceRecord() +{ +} + +/*! + Returns the name for this record. +*/ + +QString QDnsServiceRecord::name() const +{ + return d->name; +} + +/*! + Returns the port on the target host for this service record. +*/ + +quint16 QDnsServiceRecord::port() const +{ + return d->port; +} + +/*! + Returns the priority for this service record. + + A client must attempt to contact the target host with the lowest-numbered + priority. +*/ + +quint16 QDnsServiceRecord::priority() const +{ + return d->priority; +} + +/*! + Returns the domain name of the target host for this service record. +*/ + +QString QDnsServiceRecord::target() const +{ + return d->target; +} + +/*! + Returns the duration in seconds for which this record is valid. +*/ + +quint32 QDnsServiceRecord::timeToLive() const +{ + return d->timeToLive; +} + +/*! + Returns the weight for this service record. + + The weight field specifies a relative weight for entries with the same + priority. Entries with higher weights should be selected with a higher + probability. +*/ + +quint16 QDnsServiceRecord::weight() const +{ + return d->weight; +} + +/*! + Assigns the data of the \a other object to this record object, + and returns a reference to it. +*/ + +QDnsServiceRecord &QDnsServiceRecord::operator=(const QDnsServiceRecord &other) +{ + d = other.d; + return *this; +} + +/*! + \class QDnsTextRecord + \brief The QDnsTextRecord class stores information about a DNS TXT record. + + \inmodule QtNetwork + \ingroup network + + When performing a text lookup, zero or more records will be + returned. Each record is represented by a QDnsTextRecord instance. + + The meaning of the fields is defined in + \l{http://www.rfc-editor.org/rfc/rfc1035.txt}{RFC 1035}. + + \sa QDnsLookup +*/ + +/*! + Constructs an empty text record object. +*/ + +QDnsTextRecord::QDnsTextRecord() + : d(new QDnsTextRecordPrivate) +{ +} + +/*! + Constructs a copy of \a other. +*/ + +QDnsTextRecord::QDnsTextRecord(const QDnsTextRecord &other) + : d(other.d) +{ +} + +/*! + Destroys a text record. +*/ + +QDnsTextRecord::~QDnsTextRecord() +{ +} + +/*! + Returns the name for this text record. +*/ + +QString QDnsTextRecord::name() const +{ + return d->name; +} + +/*! + Returns the duration in seconds for which this record is valid. +*/ + +quint32 QDnsTextRecord::timeToLive() const +{ + return d->timeToLive; +} + +/*! + Returns the values for this text record. +*/ + +QList<QByteArray> QDnsTextRecord::values() const +{ + return d->values; +} + +/*! + Assigns the data of the \a other object to this record object, + and returns a reference to it. +*/ + +QDnsTextRecord &QDnsTextRecord::operator=(const QDnsTextRecord &other) +{ + d = other.d; + return *this; +} + +void QDnsLookupPrivate::_q_lookupFinished(const QDnsLookupReply &_reply) +{ + Q_Q(QDnsLookup); + if (runnable == q->sender()) { +#ifdef QDNSLOOKUP_DEBUG + qDebug("DNS reply for %s: %i (%s)", qPrintable(name), _reply.error, qPrintable(_reply.errorString)); +#endif + reply = _reply; + runnable = 0; + isFinished = true; + emit q->finished(); + } +} + +void QDnsLookupRunnable::run() +{ + QDnsLookupReply reply; + + // Validate input. + if (requestName.isEmpty()) { + reply.error = QDnsLookup::InvalidRequestError; + reply.errorString = tr("Invalid domain name"); + emit finished(reply); + return; + } + + // Perform request. + query(requestType, requestName, &reply); + + // Sort results. + if (!theDnsLookupSeedStorage()->hasLocalData()) { + qsrand(QTime(0,0,0).msecsTo(QTime::currentTime()) ^ reinterpret_cast<quintptr>(this)); + theDnsLookupSeedStorage()->setLocalData(new bool(true)); + } + qt_qdnsmailexchangerecord_sort(reply.mailExchangeRecords); + qt_qdnsservicerecord_sort(reply.serviceRecords); + + emit finished(reply); +} + +QDnsLookupThreadPool::QDnsLookupThreadPool() + : signalsConnected(false) +{ + // Run up to 5 lookups in parallel. + setMaxThreadCount(5); +} + +void QDnsLookupThreadPool::start(QRunnable *runnable) +{ + // Ensure threads complete at application destruction. + if (!signalsConnected) { + QMutexLocker signalsLocker(&signalsMutex); + if (!signalsConnected) { + QCoreApplication *app = QCoreApplication::instance(); + if (!app) { + qWarning("QDnsLookup requires a QCoreApplication"); + delete runnable; + return; + } + + moveToThread(app->thread()); + connect(app, SIGNAL(destroyed()), + SLOT(_q_applicationDestroyed()), Qt::DirectConnection); + signalsConnected = true; + } + } + + QThreadPool::start(runnable); +} + +void QDnsLookupThreadPool::_q_applicationDestroyed() +{ + waitForDone(); + signalsConnected = false; +} + +QT_END_NAMESPACE diff --git a/src/base/qdnslookup.h b/src/base/qdnslookup.h new file mode 100644 index 00000000..6f913896 --- /dev/null +++ b/src/base/qdnslookup.h @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDNSLOOKUP_H +#define QDNSLOOKUP_H + +#include <QList> +#include <QObject> +#include <QSharedData> +#include <QSharedPointer> +#include <QString> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QHostAddress; +class QDnsLookupPrivate; +class QDnsDomainNameRecordPrivate; +class QDnsHostAddressRecordPrivate; +class QDnsMailExchangeRecordPrivate; +class QDnsServiceRecordPrivate; +class QDnsTextRecordPrivate; + +class QDnsDomainNameRecord +{ +public: + QDnsDomainNameRecord(); + QDnsDomainNameRecord(const QDnsDomainNameRecord &other); + ~QDnsDomainNameRecord(); + + QString name() const; + quint32 timeToLive() const; + QString value() const; + + QDnsDomainNameRecord &operator=(const QDnsDomainNameRecord &other); + +private: + QSharedDataPointer<QDnsDomainNameRecordPrivate> d; + friend class QDnsLookupRunnable; +}; + +class QDnsHostAddressRecord +{ +public: + QDnsHostAddressRecord(); + QDnsHostAddressRecord(const QDnsHostAddressRecord &other); + ~QDnsHostAddressRecord(); + + QString name() const; + quint32 timeToLive() const; + QHostAddress value() const; + + QDnsHostAddressRecord &operator=(const QDnsHostAddressRecord &other); + +private: + QSharedDataPointer<QDnsHostAddressRecordPrivate> d; + friend class QDnsLookupRunnable; +}; + +class QDnsMailExchangeRecord +{ +public: + QDnsMailExchangeRecord(); + QDnsMailExchangeRecord(const QDnsMailExchangeRecord &other); + ~QDnsMailExchangeRecord(); + + QString exchange() const; + QString name() const; + quint16 preference() const; + quint32 timeToLive() const; + + QDnsMailExchangeRecord &operator=(const QDnsMailExchangeRecord &other); + +private: + QSharedDataPointer<QDnsMailExchangeRecordPrivate> d; + friend class QDnsLookupRunnable; +}; + +class QDnsServiceRecord +{ +public: + QDnsServiceRecord(); + QDnsServiceRecord(const QDnsServiceRecord &other); + ~QDnsServiceRecord(); + + QString name() const; + quint16 port() const; + quint16 priority() const; + QString target() const; + quint32 timeToLive() const; + quint16 weight() const; + + QDnsServiceRecord &operator=(const QDnsServiceRecord &other); + +private: + QSharedDataPointer<QDnsServiceRecordPrivate> d; + friend class QDnsLookupRunnable; +}; + +class QDnsTextRecord +{ +public: + QDnsTextRecord(); + QDnsTextRecord(const QDnsTextRecord &other); + ~QDnsTextRecord(); + + QString name() const; + quint32 timeToLive() const; + QList<QByteArray> values() const; + + QDnsTextRecord &operator=(const QDnsTextRecord &other); + +private: + QSharedDataPointer<QDnsTextRecordPrivate> d; + friend class QDnsLookupRunnable; +}; + +class QDnsLookup : public QObject +{ + Q_OBJECT + Q_ENUMS(Error Type) + Q_PROPERTY(Error error READ error NOTIFY finished) + Q_PROPERTY(QString errorString READ errorString NOTIFY finished) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(Type type READ type WRITE setType NOTIFY typeChanged) + +public: + enum Error + { + NoError = 0, + ResolverError, + OperationCancelledError, + InvalidRequestError, + InvalidReplyError, + ServerFailureError, + ServerRefusedError, + NotFoundError + }; + + enum Type + { + A = 1, + AAAA = 28, + ANY = 255, + CNAME = 5, + MX = 15, + NS = 2, + PTR = 12, + SRV = 33, + TXT = 16 + }; + + QDnsLookup(QObject *parent = 0); + QDnsLookup(Type type, const QString &name, QObject *parent = 0); + ~QDnsLookup(); + + Error error() const; + QString errorString() const; + bool isFinished() const; + + QString name() const; + void setName(const QString &name); + + Type type() const; + void setType(QDnsLookup::Type); + + QList<QDnsDomainNameRecord> canonicalNameRecords() const; + QList<QDnsHostAddressRecord> hostAddressRecords() const; + QList<QDnsMailExchangeRecord> mailExchangeRecords() const; + QList<QDnsDomainNameRecord> nameServerRecords() const; + QList<QDnsDomainNameRecord> pointerRecords() const; + QList<QDnsServiceRecord> serviceRecords() const; + QList<QDnsTextRecord> textRecords() const; + + +public Q_SLOTS: + void abort(); + void lookup(); + +Q_SIGNALS: + void finished(); + void nameChanged(const QString &name); + void typeChanged(Type type); + +private: + QDnsLookupPrivate *d_ptr; + Q_DECLARE_PRIVATE(QDnsLookup) + Q_PRIVATE_SLOT(d_func(), void _q_lookupFinished(const QDnsLookupReply &reply)) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#include "qdnslookup_p.h" + +#endif // QDNSLOOKUP_H diff --git a/src/base/qdnslookup_p.h b/src/base/qdnslookup_p.h new file mode 100644 index 00000000..4797f345 --- /dev/null +++ b/src/base/qdnslookup_p.h @@ -0,0 +1,216 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDNSLOOKUP_P_H +#define QDNSLOOKUP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QDnsLookup class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QHostAddress> +#include <QMetaType> +#include <QMutex> +#include <QRunnable> +#include <QSharedPointer> +#include <QThreadPool> + +#include "qdnslookup.h" + +QT_BEGIN_NAMESPACE + +//#define QDNSLOOKUP_DEBUG + +class QDnsLookupRunnable; + +class QDnsLookupReply +{ +public: + QDnsLookupReply() + : error(QDnsLookup::NoError) + { } + + QDnsLookup::Error error; + QString errorString; + + QList<QDnsDomainNameRecord> canonicalNameRecords; + QList<QDnsHostAddressRecord> hostAddressRecords; + QList<QDnsMailExchangeRecord> mailExchangeRecords; + QList<QDnsDomainNameRecord> nameServerRecords; + QList<QDnsDomainNameRecord> pointerRecords; + QList<QDnsServiceRecord> serviceRecords; + QList<QDnsTextRecord> textRecords; +}; + +class QDnsLookupPrivate +{ +public: + QDnsLookupPrivate(QDnsLookup *qq) + : isFinished(false) + , type(QDnsLookup::A) + , runnable(0) + , q_ptr(qq) + { } + + void _q_lookupFinished(const QDnsLookupReply &reply); + + bool isFinished; + QString name; + QDnsLookup::Type type; + QDnsLookupReply reply; + QDnsLookupRunnable *runnable; + QDnsLookup *q_ptr; + + Q_DECLARE_PUBLIC(QDnsLookup) +}; + +class QDnsLookupRunnable : public QObject, public QRunnable +{ + Q_OBJECT + +public: + QDnsLookupRunnable(QDnsLookup::Type type, const QByteArray &name) + : requestType(type) + , requestName(name) + { } + void run(); + +signals: + void finished(const QDnsLookupReply &reply); + +private: + static void query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply); + QDnsLookup::Type requestType; + QByteArray requestName; +}; + +class QDnsLookupThreadPool : public QThreadPool +{ + Q_OBJECT + +public: + QDnsLookupThreadPool(); + void start(QRunnable *runnable); + +private slots: + void _q_applicationDestroyed(); + +private: + QMutex signalsMutex; + bool signalsConnected; +}; + +class QDnsRecordPrivate : public QSharedData +{ +public: + QDnsRecordPrivate() + : timeToLive(0) + { } + + QString name; + quint32 timeToLive; +}; + +class QDnsDomainNameRecordPrivate : public QDnsRecordPrivate +{ +public: + QDnsDomainNameRecordPrivate() + { } + + QString value; +}; + +class QDnsHostAddressRecordPrivate : public QDnsRecordPrivate +{ +public: + QDnsHostAddressRecordPrivate() + { } + + QHostAddress value; +}; + +class QDnsMailExchangeRecordPrivate : public QDnsRecordPrivate +{ +public: + QDnsMailExchangeRecordPrivate() + : preference(0) + { } + + QString exchange; + quint16 preference; +}; + +class QDnsServiceRecordPrivate : public QDnsRecordPrivate +{ +public: + QDnsServiceRecordPrivate() + : port(0), + priority(0), + weight(0) + { } + + QString target; + quint16 port; + quint16 priority; + quint16 weight; +}; + +class QDnsTextRecordPrivate : public QDnsRecordPrivate +{ +public: + QDnsTextRecordPrivate() + { } + + QList<QByteArray> values; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QDnsLookupReply) + +#endif // QDNSLOOKUP_P_H diff --git a/src/base/qdnslookup_stub.cpp b/src/base/qdnslookup_stub.cpp new file mode 100644 index 00000000..df9ed349 --- /dev/null +++ b/src/base/qdnslookup_stub.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdnslookup_p.h" + +QT_BEGIN_NAMESPACE + +void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply) +{ + Q_UNUSED(requestType); + Q_UNUSED(requestName); + reply->error = QDnsLookup::ResolverError; + reply->errorString = QLatin1String("QDnsLookup is not implemented for this platform"); +} + +QT_END_NAMESPACE diff --git a/src/base/qdnslookup_symbian.cpp b/src/base/qdnslookup_symbian.cpp new file mode 100644 index 00000000..bed5278e --- /dev/null +++ b/src/base/qdnslookup_symbian.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdnslookup_p.h" + +#include <QUrl> +#include <QMutex> +#include <QLibrary> + +#include <dns_qry.h> + +QT_BEGIN_NAMESPACE + +void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply) +{ + RHostResolver dnsResolver; + RSocketServ dnsSocket; + + // Initialise resolver. + TInt err = dnsSocket.Connect(); + err = dnsResolver.Open(dnsSocket, KAfInet, KProtocolInetUdp); + if (err != KErrNone) { + reply->error = QDnsLookup::ResolverError; + reply->errorString = QLatin1String("RHostResolver::Open failed"); + return; + } + + // Perform DNS query. + TDnsQueryBuf dnsQuery; + TDnsRespSRVBuf dnsResponse; + dnsQuery().SetClass(KDnsRRClassIN); + TPtrC8 queryPtr(reinterpret_cast<const TUint8*>(requestName.constData()), requestName.size()); + dnsQuery().SetData(queryPtr); + dnsQuery().SetType(requestType); + err = dnsResolver.Query(dnsQuery, dnsResponse); + if (err != KErrNone) { + reply->error = QDnsLookup::NotFoundError; + reply->errorString = QLatin1String("RHostResolver::Query failed"); + return; + } + + // Extract results. + while (err == KErrNone) { + const QByteArray aceName((const char*)dnsResponse().Target().Ptr(), + dnsResponse().Target().Length()); + + QDnsServiceRecord record; + record.d->name = QUrl::fromAce(requestName); + record.d->target = QUrl::fromAce(aceName); + record.d->port = dnsResponse().Port(); + record.d->priority = dnsResponse().Priority(); + record.d->timeToLive = dnsResponse().RRTtl(); + record.d->weight = dnsResponse().Weight(); + reply->serviceRecords.append(record); + + err = dnsResolver.QueryGetNext(dnsResponse); + } +} + +QT_END_NAMESPACE diff --git a/src/base/qdnslookup_unix.cpp b/src/base/qdnslookup_unix.cpp new file mode 100644 index 00000000..f4d1c64c --- /dev/null +++ b/src/base/qdnslookup_unix.cpp @@ -0,0 +1,323 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdnslookup_p.h" + +#include <QLibrary> +#include <QMutex> +#include <QScopedPointer> +#include <QUrl> + +#include <sys/types.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <arpa/nameser_compat.h> +#include <resolv.h> + +QT_BEGIN_NAMESPACE + +static QMutex local_res_mutex; +typedef int (*dn_expand_proto)(const unsigned char *, const unsigned char *, const unsigned char *, char *, int); +static dn_expand_proto local_dn_expand = 0; +typedef void (*res_nclose_proto)(res_state); +static res_nclose_proto local_res_nclose = 0; +typedef int (*res_ninit_proto)(res_state); +static res_ninit_proto local_res_ninit = 0; +typedef int (*res_nquery_proto)(res_state, const char *, int, int, unsigned char *, int); +static res_nquery_proto local_res_nquery = 0; + +// Custom deleter to close resolver state. + +struct QDnsLookupStateDeleter +{ + static inline void cleanup(struct __res_state *pointer) + { + local_res_nclose(pointer); + } +}; + +static void resolveLibrary() +{ + QLibrary lib(QLatin1String("resolv")); + if (!lib.load()) + return; + + local_dn_expand = dn_expand_proto(lib.resolve("__dn_expand")); + if (!local_dn_expand) + local_dn_expand = dn_expand_proto(lib.resolve("dn_expand")); + + local_res_nclose = res_nclose_proto(lib.resolve("__res_nclose")); + if (!local_res_nclose) + local_res_nclose = res_nclose_proto(lib.resolve("res_9_nclose")); + if (!local_res_nclose) + local_res_nclose = res_nclose_proto(lib.resolve("res_nclose")); + + local_res_ninit = res_ninit_proto(lib.resolve("__res_ninit")); + if (!local_res_ninit) + local_res_ninit = res_ninit_proto(lib.resolve("res_9_ninit")); + if (!local_res_ninit) + local_res_ninit = res_ninit_proto(lib.resolve("res_ninit")); + + local_res_nquery = res_nquery_proto(lib.resolve("__res_nquery")); + if (!local_res_nquery) + local_res_nquery = res_nquery_proto(lib.resolve("res_9_nquery")); + if (!local_res_nquery) + local_res_nquery = res_nquery_proto(lib.resolve("res_nquery")); +} + +void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply) +{ + // Load dn_expand, res_ninit and res_nquery on demand. + static volatile bool triedResolve = false; + if (!triedResolve) { + QMutexLocker locker(&local_res_mutex); + if (!triedResolve) { + resolveLibrary(); + triedResolve = true; + } + } + + // If dn_expand, res_ninit or res_nquery is missing, fail. + if (!local_dn_expand || !local_res_nclose || !local_res_ninit || !local_res_nquery) { + reply->error = QDnsLookup::ResolverError; + reply->errorString = tr("Resolver functions not found"); + return; + } + + // Initialize state. + struct __res_state state; + memset(&state, 0, sizeof(state)); + if (local_res_ninit(&state) < 0) { + reply->error = QDnsLookup::ResolverError; + reply->errorString = tr("Resolver initialization failed"); + return; + } +#ifdef QDNSLOOKUP_DEBUG + state.options |= RES_DEBUG; +#endif + QScopedPointer<struct __res_state, QDnsLookupStateDeleter> state_ptr(&state); + + // Perform DNS query. + unsigned char response[PACKETSZ]; + memset(response, 0, sizeof(response)); + const int responseLength = local_res_nquery(&state, requestName, C_IN, requestType, response, sizeof(response)); + + // Check the response header. + HEADER *header = (HEADER*)response; + const int answerCount = ntohs(header->ancount); + switch (header->rcode) { + case NOERROR: + break; + case FORMERR: + reply->error = QDnsLookup::InvalidRequestError; + reply->errorString = tr("Server could not process query"); + return; + case SERVFAIL: + reply->error = QDnsLookup::ServerFailureError; + reply->errorString = tr("Server failure"); + return; + case NXDOMAIN: + reply->error = QDnsLookup::NotFoundError; + reply->errorString = tr("Non existent domain"); + return; + case REFUSED: + reply->error = QDnsLookup::ServerRefusedError; + reply->errorString = tr("Server refused to answer"); + return; + default: + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid reply received"); + return; + } + + // Check the reply is valid. + if (responseLength < int(sizeof(HEADER))) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid reply received"); + return; + } + + // Skip the query host, type (2 bytes) and class (2 bytes). + char host[PACKETSZ], answer[PACKETSZ]; + unsigned char *p = response + sizeof(HEADER); + int status = local_dn_expand(response, response + responseLength, p, host, sizeof(host)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Could not expand domain name"); + return; + } + p += status + 4; + + // Extract results. + int answerIndex = 0; + while ((p < response + responseLength) && (answerIndex < answerCount)) { + status = local_dn_expand(response, response + responseLength, p, host, sizeof(host)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Could not expand domain name"); + return; + } + const QString name = QUrl::fromAce(host); + + p += status; + const quint16 type = (p[0] << 8) | p[1]; + p += 2; // RR type + p += 2; // RR class + const quint32 ttl = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + p += 4; + const quint16 size = (p[0] << 8) | p[1]; + p += 2; + + if (type == QDnsLookup::A) { + if (size != 4) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid IPv4 address record"); + return; + } + const quint32 addr = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; + QDnsHostAddressRecord record; + record.d->name = name; + record.d->timeToLive = ttl; + record.d->value = QHostAddress(addr); + reply->hostAddressRecords.append(record); + } else if (type == QDnsLookup::AAAA) { + if (size != 16) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid IPv6 address record"); + return; + } + QDnsHostAddressRecord record; + record.d->name = name; + record.d->timeToLive = ttl; + record.d->value = QHostAddress(p); + reply->hostAddressRecords.append(record); + } else if (type == QDnsLookup::CNAME) { + status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid canonical name record"); + return; + } + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ttl; + record.d->value = QUrl::fromAce(answer); + reply->canonicalNameRecords.append(record); + } else if (type == QDnsLookup::NS) { + status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid name server record"); + return; + } + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ttl; + record.d->value = QUrl::fromAce(answer); + reply->nameServerRecords.append(record); + } else if (type == QDnsLookup::PTR) { + status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid pointer record"); + return; + } + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ttl; + record.d->value = QUrl::fromAce(answer); + reply->pointerRecords.append(record); + } else if (type == QDnsLookup::MX) { + const quint16 preference = (p[0] << 8) | p[1]; + status = local_dn_expand(response, response + responseLength, p + 2, answer, sizeof(answer)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid mail exchange record"); + return; + } + QDnsMailExchangeRecord record; + record.d->exchange = QUrl::fromAce(answer); + record.d->name = name; + record.d->preference = preference; + record.d->timeToLive = ttl; + reply->mailExchangeRecords.append(record); + } else if (type == QDnsLookup::SRV) { + const quint16 priority = (p[0] << 8) | p[1]; + const quint16 weight = (p[2] << 8) | p[3]; + const quint16 port = (p[4] << 8) | p[5]; + status = local_dn_expand(response, response + responseLength, p + 6, answer, sizeof(answer)); + if (status < 0) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid service record"); + return; + } + QDnsServiceRecord record; + record.d->name = name; + record.d->target = QUrl::fromAce(answer); + record.d->port = port; + record.d->priority = priority; + record.d->timeToLive = ttl; + record.d->weight = weight; + reply->serviceRecords.append(record); + } else if (type == QDnsLookup::TXT) { + unsigned char *txt = p; + QDnsTextRecord record; + record.d->name = name; + record.d->timeToLive = ttl; + while (txt < p + size) { + const unsigned char length = *txt; + txt++; + if (txt + length > p + size) { + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid text record"); + return; + } + record.d->values << QByteArray((char*)txt, length); + txt += length; + } + reply->textRecords.append(record); + } + p += size; + answerIndex++; + } +} + +QT_END_NAMESPACE diff --git a/src/base/qdnslookup_win.cpp b/src/base/qdnslookup_win.cpp new file mode 100644 index 00000000..87d6955a --- /dev/null +++ b/src/base/qdnslookup_win.cpp @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdnslookup_p.h" + +#include <QUrl> +#include <QMutex> +#include <QLibrary> + +#include <windows.h> +#include <windns.h> +#include <winsock2.h> + +QT_BEGIN_NAMESPACE + +static QMutex local_dns_mutex; +typedef DNS_STATUS (*dns_query_utf8_proto)(PCSTR,WORD,DWORD,PIP4_ARRAY,PDNS_RECORD*,PVOID*); +static dns_query_utf8_proto local_dns_query_utf8 = 0; +typedef void (*dns_record_list_free_proto)(PDNS_RECORD,DNS_FREE_TYPE); +static dns_record_list_free_proto local_dns_record_list_free = 0; + +static void resolveLibrary() +{ + QLibrary lib(QLatin1String("dnsapi")); + if (!lib.load()) + return; + + local_dns_query_utf8 = (dns_query_utf8_proto) lib.resolve(QLatin1String("dnsapi"), "DnsQuery_UTF8"); + local_dns_record_list_free = (dns_record_list_free_proto) lib.resolve(QLatin1String("dnsapi"), "DnsRecordListFree"); +} + +void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, QDnsLookupReply *reply) +{ + // Load DnsQuery_UTF8 and DnsRecordListFree on demand. + static volatile bool triedResolve = false; + if (!triedResolve) { + QMutexLocker locker(&local_dns_mutex); + if (!triedResolve) { + resolveLibrary(); + triedResolve = true; + } + } + + // If DnsQuery_UTF8 or DnsRecordListFree is missing, fail. + if (!local_dns_query_utf8 || !local_dns_record_list_free) { + reply->error = QDnsLookup::ResolverError, + reply->errorString = tr("Resolver functions not found"); + return; + } + + // Perform DNS query. + PDNS_RECORD dns_records; + const DNS_STATUS status = local_dns_query_utf8(requestName, requestType, DNS_QUERY_STANDARD, NULL, &dns_records, NULL); + switch (status) { + case ERROR_SUCCESS: + break; + case DNS_ERROR_RCODE_FORMAT_ERROR: + reply->error = QDnsLookup::InvalidRequestError; + reply->errorString = tr("Server could not process query"); + return; + case DNS_ERROR_RCODE_SERVER_FAILURE: + reply->error = QDnsLookup::ServerFailureError; + reply->errorString = tr("Server failure"); + return; + case DNS_ERROR_RCODE_NAME_ERROR: + reply->error = QDnsLookup::NotFoundError; + reply->errorString = tr("Non existent domain"); + return; + case DNS_ERROR_RCODE_REFUSED: + reply->error = QDnsLookup::ServerRefusedError; + reply->errorString = tr("Server refused to answer"); + return; + default: + reply->error = QDnsLookup::InvalidReplyError; + reply->errorString = tr("Invalid reply received"); + return; + } + + // Extract results. + for (PDNS_RECORD ptr = dns_records; ptr != NULL; ptr = ptr->pNext) { + const QString name = QUrl::fromAce((char*)ptr->pName); + if (ptr->wType == QDnsLookup::A) { + QDnsHostAddressRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = QHostAddress(ntohl(ptr->Data.A.IpAddress)); + reply->hostAddressRecords.append(record); + } else if (ptr->wType == QDnsLookup::AAAA) { + Q_IPV6ADDR addr; + memcpy(&addr, &ptr->Data.AAAA.Ip6Address, sizeof(Q_IPV6ADDR)); + + QDnsHostAddressRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = QHostAddress(addr); + reply->hostAddressRecords.append(record); + } else if (ptr->wType == QDnsLookup::CNAME) { + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = QUrl::fromAce((char*)ptr->Data.Cname.pNameHost); + reply->canonicalNameRecords.append(record); + } else if (ptr->wType == QDnsLookup::MX) { + QDnsMailExchangeRecord record; + record.d->name = name; + record.d->exchange = QUrl::fromAce((char*)ptr->Data.Mx.pNameExchange); + record.d->preference = ptr->Data.Mx.wPreference; + record.d->timeToLive = ptr->dwTtl; + reply->mailExchangeRecords.append(record); + } else if (ptr->wType == QDnsLookup::NS) { + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = QUrl::fromAce((char*)ptr->Data.Ns.pNameHost); + reply->nameServerRecords.append(record); + } else if (ptr->wType == QDnsLookup::PTR) { + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = QUrl::fromAce((char*)ptr->Data.Ptr.pNameHost); + reply->pointerRecords.append(record); + } else if (ptr->wType == QDnsLookup::SRV) { + QDnsServiceRecord record; + record.d->name = name; + record.d->target = QUrl::fromAce((char*)ptr->Data.Srv.pNameTarget); + record.d->port = ptr->Data.Srv.wPort; + record.d->priority = ptr->Data.Srv.wPriority; + record.d->timeToLive = ptr->dwTtl; + record.d->weight = ptr->Data.Srv.wWeight; + reply->serviceRecords.append(record); + } else if (ptr->wType == QDnsLookup::TXT) { + QDnsTextRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + for (unsigned int i = 0; i < ptr->Data.Txt.dwStringCount; ++i) { + record.d->values << QByteArray((char*)ptr->Data.Txt.pStringArray[i]); + } + reply->textRecords.append(record); + } + } + + local_dns_record_list_free(dns_records, DnsFreeRecordList); +} + +QT_END_NAMESPACE |
