/* * Created by victoria on 2021-05-12. */ #include "qomemo.h" #include static bool elementMatches(const QXmppElement &element, const QString &tagName, const QString &xmlns = QStringLiteral("")) { if (element.tagName() != tagName) { qWarning() << "tag name: expected = " << tagName << ", got = " << element.tagName(); return false; } if (!xmlns.isEmpty() && element.attribute("xmlns") != xmlns) { qWarning() << "xmlns: expected = " << xmlns << ", got = " << element.attribute("xmlns"); return false; } return true; } static QXmppElement createElement(const QString &tagName, const QString &xmlns = QStringLiteral("")) { QXmppElement el{}; el.setTagName(tagName); if (!xmlns.isEmpty()) el.setAttribute("xmlns", xmlns); return el; } static QXmppElement createValue(const QString &value) { auto el = createElement("value"); el.setValue(value); return el; } static QXmppElement createField(const QString &key, const QString &value, bool hidden = false) { auto field = createElement("field"); field.setAttribute("var", key); if (hidden) field.setAttribute("type", "hidden"); field.appendChild(createValue(value)); return field; } static QXmppElement createOpenPublishOptions(const QString &maxItems = QStringLiteral("")) { auto formType = createField( "FORM_TYPE", "http://jabber.org/protocol/pubsub#publish-options", true); auto accessModel = createField("pubsub#access_model", "open"); auto x = createElement("x", "jabber:x:data"); x.setAttribute("type", "submit"); x.appendChild(formType); x.appendChild(accessModel); if (!maxItems.isEmpty()) x.appendChild(createField("pubsub#max_items", maxItems)); auto publishOptions = createElement("publish-options"); publishOptions.appendChild(x); return publishOptions; } QXmppElement QOmemo::DeviceList::toXml() const { auto element = createElement("devices", "urn:xmpp:omemo:1"); for (const auto &d : devices) { element.appendChild(d.toXml()); } return element; } QXmppIq QOmemo::DeviceList::toIq() const { QXmppIq iq{}; iq.setType(QXmppIq::Set); auto item = createElement("item"); item.setAttribute("id", "current"); item.appendChild(toXml()); auto publish = createElement("publish"); publish.setAttribute("node", "urn:xmpp:omemo:1:devices"); publish.appendChild(item); auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub"); pubSub.appendChild(publish); pubSub.appendChild(createOpenPublishOptions()); iq.extensions().push_back(pubSub); return iq; } void QOmemo::DeviceList::fromXml(const QXmppElement &element) { if (!elementMatches(element, "devices", "urn:xmpp:omemo:1")) return; devices.clear(); auto deviceElement = element.firstChildElement("device"); while (!deviceElement.isNull()) { Device device{}; device.fromXml(deviceElement); devices.push_back(device); deviceElement = deviceElement.nextSiblingElement("device"); } } QXmppElement QOmemo::Device::toXml() const { auto result = createElement("device"); result.setAttribute("id", QString::number(id)); if (!label.isEmpty()) { result.setAttribute("label", label); } return result; } void QOmemo::Device::fromXml(const QXmppElement &element) { if (!elementMatches(element, "device")) return; id = element.attribute("id").toInt(); label = element.attribute("label"); } QXmppElement QOmemo::PreKey::toXml() const { auto pk = createElement("pk"); pk.setAttribute("id", QString::number(id)); // TODO: Base64 pk.setValue(data); return pk; } void QOmemo::PreKey::fromXml(const QXmppElement &element) { if (!elementMatches(element, "pk")) return; id = element.attribute("id").toInt(); // TODO: Base64 data = element.value(); } QXmppElement QOmemo::Bundle::toXml() const { auto spkNode = createElement("spk"); spkNode.setAttribute("id", QString::number(spkId)); spkNode.setValue(spk); auto spksNode = createElement("spks"); spksNode.setValue(spks); auto ikNode = createElement("ik"); ikNode.setValue(ik); auto prekeysNode = createElement("prekeys"); for (const auto &pk : prekeys) { prekeysNode.appendChild(pk.toXml()); } auto result = createElement("bundle", "urn:xmpp:omemo:1"); result.appendChild(spkNode); result.appendChild(spksNode); result.appendChild(ikNode); result.appendChild(prekeysNode); return result; } QXmppIq QOmemo::Bundle::toIq() const { QXmppIq iq{}; iq.setType(QXmppIq::Set); auto item = createElement("item"); item.setAttribute("id", QString::number(deviceId)); item.appendChild(toXml()); auto publish = createElement("publish"); publish.setAttribute("node", "urn:xmpp:omemo:1:bundles"); publish.appendChild(item); auto pubSub = createElement("pubsub", "http://jabber.org/protocol/pubsub"); pubSub.appendChild(publish); pubSub.appendChild(createOpenPublishOptions("max")); iq.extensions().push_back(pubSub); return iq; } void QOmemo::Bundle::fromXml(const QXmppElement &element) { if (!elementMatches(element, "bundle", "urn:xmpp:omemo:1")) return; auto spkNode = element.firstChildElement("spk"); if (spkNode.isNull()) { qWarning() << "'bundle': missing 'spk'"; return; } spk = spkNode.value(); spkId = spkNode.attribute("id").toInt(); auto spksNode = element.firstChildElement("spks"); if (spksNode.isNull()) { qWarning() << "'bundle': missing 'spks'"; return; } spks = spksNode.value(); auto ikNode = element.firstChildElement("ik"); if (ikNode.isNull()) { qWarning() << "'bundle': missing 'ik'"; return; } ik = ikNode.value(); auto prekeysNode = element.firstChildElement("prekeys"); auto pkNode = prekeysNode.firstChildElement("pk"); prekeys.clear(); while (!pkNode.isNull()) { PreKey pk{}; pk.fromXml(pkNode); prekeys.push_back(pk); } }