diff --git a/doc/xep.doc b/doc/xep.doc index 94e02f60b..58be0cb1b 100644 --- a/doc/xep.doc +++ b/doc/xep.doc @@ -45,5 +45,7 @@ Ongoing: - XEP-0009: Jabber-RPC (API is not finalized yet) - XEP-0060: Publish-Subscribe (Only basic IQ implemented) - XEP-0077: In-Band Registration (Only basic IQ implemented) +- XEP-0369: Mediated Information eXchange (MIX) (Only IQ queries implemented) +- XEP-0405: Mediated Information eXchange (MIX): Participant Server Requirements (Only IQ queries implemented) */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 976f069e4..2e48bffa8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,6 +25,7 @@ set(INSTALL_HEADER_FILES base/QXmppLogger.h base/QXmppMamIq.h base/QXmppMessage.h + base/QXmppMixIq.h base/QXmppMucIq.h base/QXmppNonSASLAuth.h base/QXmppPingIq.h @@ -99,6 +100,7 @@ set(SOURCE_FILES base/QXmppLogger.cpp base/QXmppMamIq.cpp base/QXmppMessage.cpp + base/QXmppMixIq.cpp base/QXmppMucIq.cpp base/QXmppNonSASLAuth.cpp base/QXmppPingIq.cpp diff --git a/src/base/QXmppConstants.cpp b/src/base/QXmppConstants.cpp index 3254dd617..a412e4bde 100644 --- a/src/base/QXmppConstants.cpp +++ b/src/base/QXmppConstants.cpp @@ -130,3 +130,15 @@ const char* ns_mam = "urn:xmpp:mam:1"; const char* ns_chat_markers = "urn:xmpp:chat-markers:0"; // XEP-0352: Client State Indication const char* ns_csi = "urn:xmpp:csi:0"; +// XEP-0369: Mediated Information eXchange (MIX) +const char* ns_mix = "urn:xmpp:mix:core:0"; +const char* ns_mix_create_channel = "urn:xmpp:mix:core:0#create-channel"; +const char* ns_mix_searchable = "urn:xmpp:mix:core:0#searchable"; +const char* ns_mix_node_messages = "urn:xmpp:mix:nodes:messages"; +const char* ns_mix_node_participants = "urn:xmpp:mix:nodes:participants"; +const char* ns_mix_node_presence = "urn:xmpp:mix:nodes:presence"; +const char* ns_mix_node_config = "urn:xmpp:mix:nodes:config"; +const char* ns_mix_node_info = "urn:xmpp:mix:nodes:info"; +// XEP-0405: Mediated Information eXchange (MIX): Participant Server Requirements +const char* ns_mix_pam = "urn:xmpp:mix:pam:0"; +const char* ns_mix_roster = "urn:xmpp:mix:roster:0"; diff --git a/src/base/QXmppConstants_p.h b/src/base/QXmppConstants_p.h index 17495b334..2bceba435 100644 --- a/src/base/QXmppConstants_p.h +++ b/src/base/QXmppConstants_p.h @@ -142,5 +142,17 @@ extern const char* ns_mam; extern const char* ns_chat_markers; // XEP-0352: Client State Indication extern const char* ns_csi; +// XEP-0369: Mediated Information eXchange (MIX) +extern const char* ns_mix; +extern const char* ns_mix_create_channel; +extern const char* ns_mix_searchable; +extern const char* ns_mix_node_messages; +extern const char* ns_mix_node_participants; +extern const char* ns_mix_node_presence; +extern const char* ns_mix_node_config; +extern const char* ns_mix_node_info; +// XEP-0405: Mediated Information eXchange (MIX): Participant Server Requirements +extern const char* ns_mix_pam; +extern const char* ns_mix_roster; #endif // QXMPPCONSTANTS_H diff --git a/src/base/QXmppMixIq.cpp b/src/base/QXmppMixIq.cpp new file mode 100644 index 000000000..77ada26a9 --- /dev/null +++ b/src/base/QXmppMixIq.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2008-2019 The QXmpp developers + * + * Author: + * Linus Jahn + * + * Source: + * https://github.com/qxmpp-project/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 "QXmppMixIq.h" +#include "QXmppDataForm.h" +#include "QXmppConstants_p.h" +#include "QXmppUtils.h" +#include + +static const QStringList MIX_ACTION_TYPES = QStringList() << "" + << "client-join" << "client-leave" << "join" << "leave" + << "update-subscription" << "setnick" << "create" << "destroy"; + +/// Returns the channel JID. It also contains a participant id for Join/ +/// ClientJoin results. + +QString QXmppMixIq::jid() const +{ + return m_jid; +} + +/// Sets the channel JID. For results of Join/ClientJoin queries this also +/// needs to contain a participant id. + +void QXmppMixIq::setJid(const QString& jid) +{ + m_jid = jid; +} + +/// Returns the channel name (the name part of the channel JID). This may still +/// be empty, if a JID was set. + +QString QXmppMixIq::channelName() const +{ + return m_channelName; +} + +/// Sets the channel name for creating/destroying specific channels. When you +/// create a new channel, this can also be left empty to let the server +/// generate a name. + +void QXmppMixIq::setChannelName(const QString& channelName) +{ + m_channelName = channelName; +} + +/// Returns the list of nodes to subscribe to. + +QStringList QXmppMixIq::nodes() const +{ + return m_nodes; +} + +/// Sets the nodes to subscribe to. Note that for UpdateSubscription queries +/// you only need to include the new subscriptions. + +void QXmppMixIq::setNodes(const QStringList& nodes) +{ + m_nodes = nodes; +} + +/// Returns the user's nickname in the channel. + +QString QXmppMixIq::nick() const +{ + return m_nick; +} + +/// Sets the nickname for the channel. + +void QXmppMixIq::setNick(const QString& nick) +{ + m_nick = nick; +} + +/// Returns the MIX channel action type. + +QXmppMixIq::Type QXmppMixIq::actionType() const +{ + return m_actionType; +} + +/// Sets the channel action. + +void QXmppMixIq::setActionType(QXmppMixIq::Type type) +{ + m_actionType = type; +} + +/// \cond +bool QXmppMixIq::isMixIq(const QDomElement& element) +{ + const QDomElement& child = element.firstChildElement(); + return !child.isNull() && (child.attribute("xmlns") == ns_mix + || child.attribute("xmlns") == ns_mix_pam); +} + +void QXmppMixIq::parseElementFromChild(const QDomElement& element) +{ + QDomElement child = element.firstChildElement(); + // determine action type + m_actionType = (QXmppMixIq::Type) MIX_ACTION_TYPES.indexOf(child.tagName()); + + if (child.namespaceURI() == ns_mix_pam) { + if (child.hasAttribute("channel")) + m_jid = child.attribute("channel"); + + child = child.firstChildElement(); + } + + if (child.isNull() || child.namespaceURI() != ns_mix) + return; + + if (child.hasAttribute("jid")) + m_jid = child.attribute("jid"); + if (child.hasAttribute("channel")) + m_channelName = child.attribute("channel"); + + QDomElement subChild = child.firstChildElement(); + while (!subChild.isNull()) { + if (subChild.tagName() == "subscribe") + m_nodes << subChild.attribute("node"); + else if (subChild.tagName() == "nick") + m_nick = subChild.text(); + + subChild = subChild.nextSiblingElement(); + } +} + +void QXmppMixIq::toXmlElementFromChild(QXmlStreamWriter* writer) const +{ + writer->writeStartElement(MIX_ACTION_TYPES.at(m_actionType)); + if (m_actionType == ClientJoin || m_actionType == ClientLeave) { + writer->writeAttribute("xmlns", ns_mix_pam); + if (type() == Set) + helperToXmlAddAttribute(writer, "channel", m_jid); + + if (m_actionType == ClientJoin) + writer->writeStartElement("join"); + else if (m_actionType == ClientLeave) + writer->writeStartElement("leave"); + } + + writer->writeAttribute("xmlns", ns_mix); + helperToXmlAddAttribute(writer, "channel", m_channelName); + if (type() == Result) + helperToXmlAddAttribute(writer, "jid", m_jid); + + for (auto node : m_nodes) { + writer->writeStartElement("subscribe"); + writer->writeAttribute("node", node); + writer->writeEndElement(); + } + if (!m_nick.isEmpty()) + writer->writeTextElement("nick", m_nick); + + writer->writeEndElement(); + if (m_actionType == ClientJoin || m_actionType == ClientLeave) + writer->writeEndElement(); +} +/// \endcond diff --git a/src/base/QXmppMixIq.h b/src/base/QXmppMixIq.h new file mode 100644 index 000000000..8863d4263 --- /dev/null +++ b/src/base/QXmppMixIq.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2008-2019 The QXmpp developers + * + * Author: + * Linus Jahn + * + * Source: + * https://github.com/qxmpp-project/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 QXMPPMIXIQ_H +#define QXMPPMIXIQ_H + +#include "QXmppIq.h" + +class QXMPP_EXPORT QXmppMixIq : public QXmppIq +{ +public: + enum Type { + None, + ClientJoin, + ClientLeave, + Join, + Leave, + UpdateSubscription, + SetNick, + Create, + Destroy + }; + + QXmppMixIq::Type actionType() const; + void setActionType(QXmppMixIq::Type); + + QString jid() const; + void setJid(const QString&); + + QString channelName() const; + void setChannelName(const QString&); + + QStringList nodes() const; + void setNodes(const QStringList&); + + QString nick() const; + void setNick(const QString&); + + /// \cond + static bool isMixIq(const QDomElement&); + /// \endcond + +protected: + /// \cond + void parseElementFromChild(const QDomElement&); + void toXmlElementFromChild(QXmlStreamWriter*) const; + /// \endcond + +private: + QString m_jid; + QString m_channelName; + QStringList m_nodes; + QString m_nick; + QXmppMixIq::Type m_actionType = QXmppMixIq::None; +}; + +#endif // QXMPPMIXIQ_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0b64c7466..d1cfd242f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,6 +27,7 @@ add_simple_test(qxmppiq) add_simple_test(qxmppjingleiq) add_simple_test(qxmppmammanager) add_simple_test(qxmppmessage) +add_simple_test(qxmppmixiq) add_simple_test(qxmppnonsaslauthiq) add_simple_test(qxmpppresence) add_simple_test(qxmpppubsubiq) diff --git a/tests/qxmppmixiq/tst_qxmppmixiq.cpp b/tests/qxmppmixiq/tst_qxmppmixiq.cpp new file mode 100644 index 000000000..501989a83 --- /dev/null +++ b/tests/qxmppmixiq/tst_qxmppmixiq.cpp @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2008-2019 The QXmpp developers + * + * Author: + * Linus Jahn + * + * Source: + * https://github.com/qxmpp-project/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 + +#include "QXmppMixIq.h" +#include "util.h" + +Q_DECLARE_METATYPE(QXmppIq::Type); +Q_DECLARE_METATYPE(QXmppMixIq::Type); + +class tst_QXmppMixIq : public QObject +{ + Q_OBJECT + +private slots: + void testBase_data(); + void testBase(); + void testDefaults(); + void testSetters(); +}; + +void tst_QXmppMixIq::testBase_data() +{ + QByteArray joinC2sSetXml( + "" + "" + "" + "" + "" + "" + "" + "third witch" + "" + "" + "" + ); + QByteArray joinS2sSetXml( + "" + "" + "" + "" + "" + "" + "stpeter" + "" + "" + ); + QByteArray joinS2sResultXml( + "" + "" + "" + "" + "" + "" + "third witch" + "" + "" + ); + QByteArray joinC2sResultXml( + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ); + QByteArray leaveC2sSetXml( + "" + "" + "" + "" + "" + ); + QByteArray leaveS2sSetXml( + "" + "" + "" + ); + QByteArray leaveS2sResultXml( + "" + "" + "" + ); + QByteArray leaveC2sResultXml( + "" + "" + "" + "" + "" + ); + QByteArray updateSubscriptionSetXml( + "" + "" + "" + "" + "" + ); + QByteArray updateSubscriptionResultXml( + "" + "" + "" + "" + "" + ); + QByteArray setNickSetXml( + "" + "" + "thirdwitch" + "" + "" + ); + QByteArray setNickResultXml( + "" + "" + "thirdwitch" + "" + "" + ); + QByteArray createXml( + "" + "" + "" + ); + QByteArray createWithoutNameXml( + "" + "" + "" + ); + QByteArray destroyXml( + "" + "" + "" + ); + + QStringList emptyNodes; + QStringList defaultNodes; + defaultNodes << "urn:xmpp:mix:nodes:messages" << "urn:xmpp:mix:nodes:presence" + << "urn:xmpp:mix:nodes:participants" << "urn:xmpp:mix:nodes:info"; + + QTest::addColumn("xml"); + QTest::addColumn("type"); + QTest::addColumn("actionType"); + QTest::addColumn("jid"); + QTest::addColumn("channelName"); + QTest::addColumn("nodes"); + QTest::addColumn("nick"); + + QTest::newRow("join-c2s-set") + << joinC2sSetXml + << QXmppIq::Set + << QXmppMixIq::ClientJoin + << "coven@mix.shakespeare.example" + << "" + << defaultNodes + << "third witch"; + QTest::newRow("join-s2s-set") + << joinS2sSetXml + << QXmppIq::Set + << QXmppMixIq::Join + << "" + << "" + << defaultNodes + << "stpeter"; + QTest::newRow("join-s2s-result") + << joinS2sResultXml + << QXmppIq::Result + << QXmppMixIq::Join + << "123456#coven@mix.shakespeare.example" + << "" + << defaultNodes + << "third witch"; + QTest::newRow("join-c2s-result") + << joinC2sResultXml + << QXmppIq::Result + << QXmppMixIq::ClientJoin + << "123456#coven@mix.shakespeare.example" + << "" + << defaultNodes + << ""; + QTest::newRow("leave-c2s-set") + << leaveC2sSetXml + << QXmppIq::Set + << QXmppMixIq::ClientLeave + << "coven@mix.shakespeare.example" + << "" << emptyNodes << ""; + QTest::newRow("leave-s2s-set") + << leaveS2sSetXml + << QXmppIq::Set + << QXmppMixIq::Leave + << "" << "" << emptyNodes << ""; + QTest::newRow("leave-s2s-result") + << leaveS2sResultXml + << QXmppIq::Result + << QXmppMixIq::Leave + << "" << "" << emptyNodes << ""; + QTest::newRow("leave-c2s-result") + << leaveC2sResultXml + << QXmppIq::Result + << QXmppMixIq::ClientLeave + << "" << "" << emptyNodes << ""; + QTest::newRow("update-subscription-set") + << updateSubscriptionSetXml + << QXmppIq::Set + << QXmppMixIq::UpdateSubscription + << "" + << "" + << (QStringList() << "urn:xmpp:mix:nodes:messages") + << ""; + QTest::newRow("update-subscription-result") + << updateSubscriptionResultXml + << QXmppIq::Result + << QXmppMixIq::UpdateSubscription + << "hag66@shakespeare.example" + << "" + << (QStringList() << "urn:xmpp:mix:nodes:messages") + << ""; + QTest::newRow("setnick-set") + << setNickSetXml + << QXmppIq::Set + << QXmppMixIq::SetNick + << "" << "" << emptyNodes + << "thirdwitch"; + QTest::newRow("setnick-result") + << setNickResultXml + << QXmppIq::Result + << QXmppMixIq::SetNick + << "" << "" << emptyNodes + << "thirdwitch"; + QTest::newRow("create") + << createXml + << QXmppIq::Set + << QXmppMixIq::Create + << "" << "coven" << emptyNodes << ""; + QTest::newRow("create-without-name") + << createWithoutNameXml + << QXmppIq::Set + << QXmppMixIq::Create + << "" << "" << emptyNodes << ""; + QTest::newRow("destroy") + << destroyXml + << QXmppIq::Set + << QXmppMixIq::Destroy + << "" << "coven" << emptyNodes << ""; +} + +void tst_QXmppMixIq::testBase() +{ + QFETCH(QByteArray, xml); + QFETCH(QXmppIq::Type, type); + QFETCH(QXmppMixIq::Type, actionType); + QFETCH(QString, jid); + QFETCH(QString, channelName); + QFETCH(QStringList, nodes); + QFETCH(QString, nick); + + QXmppMixIq iq; + parsePacket(iq, xml); + QCOMPARE(iq.type(), type); + QCOMPARE(iq.actionType(), actionType); + QCOMPARE(iq.jid(), jid); + QCOMPARE(iq.channelName(), channelName); + QCOMPARE(iq.nodes(), nodes); + QCOMPARE(iq.nick(), nick); + serializePacket(iq, xml); +} + +void tst_QXmppMixIq::testDefaults() +{ + QXmppMixIq iq; + QCOMPARE(iq.actionType(), QXmppMixIq::None); + QCOMPARE(iq.jid(), ""); + QCOMPARE(iq.channelName(), ""); + QCOMPARE(iq.nodes(), QStringList()); + QCOMPARE(iq.nick(), ""); +} + +void tst_QXmppMixIq::testSetters() +{ + QXmppMixIq iq; + iq.setActionType(QXmppMixIq::Join); + QCOMPARE(iq.actionType(), QXmppMixIq::Join); + iq.setJid("interestingnews@mix.example.com"); + QCOMPARE(iq.jid(), "interestingnews@mix.example.com"); + iq.setChannelName("interestingnews"); + QCOMPARE(iq.channelName(), "interestingnews"); + iq.setNodes(QStringList() << "com:example:mix:node:custom"); + QCOMPARE(iq.nodes(), QStringList() << "com:example:mix:node:custom"); + iq.setNick("SMUDO"); + QCOMPARE(iq.nick(), "SMUDO"); +} + +QTEST_MAIN(tst_QXmppMixIq) +#include "tst_qxmppmixiq.moc"