/* * Copyright (C) 2008-2021 The QXmpp developers * * Author: * Jeremy Lainé * 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 "QXmppDataForm.h" #include "QXmppConstants_p.h" #include "QXmppUtils.h" #include #include #include #include #include #include #include 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(-1), nullptr }, }; class QXmppDataFormMediaSourcePrivate : public QSharedData { public: QUrl uri; QMimeType contentType; }; QXmppDataForm::MediaSource::MediaSource() : d(new QXmppDataFormMediaSourcePrivate) { } QXmppDataForm::MediaSource::MediaSource(const QUrl &uri, const QMimeType &contentType) : d(new QXmppDataFormMediaSourcePrivate) { d->uri = uri; d->contentType = contentType; } QXmppDataForm::MediaSource::MediaSource(const QXmppDataForm::MediaSource &) = default; QXmppDataForm::MediaSource::~MediaSource() = default; QXmppDataForm::MediaSource &QXmppDataForm::MediaSource::operator=(const QXmppDataForm::MediaSource &) = default; /// Returns the media URI as QUrl. This can be i.e. a \c http:// URL or a /// \c cid: Bits of Binary URI. QUrl QXmppDataForm::MediaSource::uri() const { return d->uri; } /// Sets the URI. void QXmppDataForm::MediaSource::setUri(const QUrl &uri) { d->uri = uri; } /// Returns the content type of the source QMimeType QXmppDataForm::MediaSource::contentType() const { return d->contentType; } /// Sets the content type of the media source. void QXmppDataForm::MediaSource::setContentType(const QMimeType &contentType) { d->contentType = contentType; } /// Returns true if two media sources are identical. bool QXmppDataForm::MediaSource::operator==(const QXmppDataForm::MediaSource &other) const { return d->uri == other.uri() && d->contentType == other.contentType(); } class QXmppDataFormMediaPrivate : public QSharedData { public: QSize size; QList> uris; }; /// Constructs an empty QXmppDataForm::Media. /// /// \deprecated This class is deprecated since QXmpp 1.1. QXmppDataForm::Media::Media() : d(new QXmppDataFormMediaPrivate) { } /// Constructs a copy of \a other. /// /// \deprecated This class is deprecated since QXmpp 1.1. QXmppDataForm::Media::Media(const QXmppDataForm::Media &other) = default; /// Destroys the media. /// /// \deprecated This class is deprecated since QXmpp 1.1. QXmppDataForm::Media::~Media() = default; /// Assigns \a other to this media. /// /// \deprecated This class is deprecated since QXmpp 1.1. QXmppDataForm::Media &QXmppDataForm::Media::operator=(const QXmppDataForm::Media &other) = default; /// Returns media's height. /// /// \deprecated This method is deprecated since QXmpp 1.1. Use /// \c QXmppDataForm::Field::mediaSize().height() instead. int QXmppDataForm::Media::height() const { return d->size.height(); } /// Sets media's \a height. /// /// \deprecated This method is deprecated since QXmpp 1.1. Use /// \c QXmppDataForm::Field::mediaSize().setHeight() instead. void QXmppDataForm::Media::setHeight(int height) { d->size.setHeight(height); } /// Returns media's width. /// /// \deprecated This method is deprecated since QXmpp 1.1. Use /// \c QXmppDataForm::Field::mediaSize().width() instead. int QXmppDataForm::Media::width() const { return d->size.width(); } /// Sets media's \a width. /// /// \deprecated This method is deprecated since QXmpp 1.1. Use /// \c QXmppDataForm::Field::mediaSize().setWidth() instead. void QXmppDataForm::Media::setWidth(int width) { d->size.setWidth(width); } /// Returns media's uris. /// /// \deprecated This method is deprecated since QXmpp 1.1. Use /// \c QXmppDataForm::Field::mediaSources() instead. QList> QXmppDataForm::Media::uris() const { return d->uris; } /// Sets media's \a uris. /// /// \deprecated This method is deprecated since QXmpp 1.1. Use /// \c QXmppDataForm::Media::setMediaSources() instead. void QXmppDataForm::Media::setUris(const QList> &uris) { d->uris = uris; } /// Returns true if no media tag present. bool QXmppDataForm::Media::isNull() const { return d->uris.isEmpty(); } class QXmppDataFormFieldPrivate : public QSharedData { public: QXmppDataFormFieldPrivate(); QString description; QString key; QString label; QList> options; bool required; QXmppDataForm::Field::Type type; QVariant value; QSize mediaSize; QVector mediaSources; }; QXmppDataFormFieldPrivate::QXmppDataFormFieldPrivate() : required(false), type(QXmppDataForm::Field::TextSingleField) { } /// Constructs a QXmppDataForm::Field of the specified \a type. QXmppDataForm::Field::Field(QXmppDataForm::Field::Type type) : d(new QXmppDataFormFieldPrivate) { d->type = type; } /// /// Constructs a QXmppDataForm::Field with the specified attributes. /// /// \since QXmpp 1.3 /// QXmppDataForm::Field::Field(QXmppDataForm::Field::Type type, const QString &key, const QVariant &value, bool isRequired, const QString &label, const QString &description, const QList> &options) : d(new QXmppDataFormFieldPrivate) { d->type = type; d->key = key; d->value = value; d->required = isRequired; d->label = label; d->description = description; d->options = options; } /// Constructs a copy of \a other. QXmppDataForm::Field::Field(const QXmppDataForm::Field &other) : d(other.d) { } /// Destroys the form field. QXmppDataForm::Field::~Field() { } /// Assigns \a other to this field. QXmppDataForm::Field &QXmppDataForm::Field::operator=(const QXmppDataForm::Field &other) { d = other.d; return *this; } /// Returns the field's description. QString QXmppDataForm::Field::description() const { return d->description; } /// Sets the field's description. /// /// \param description void QXmppDataForm::Field::setDescription(const QString &description) { d->description = description; } /// Returns the field's key. QString QXmppDataForm::Field::key() const { return d->key; } /// Sets the field's key. /// /// \param key void QXmppDataForm::Field::setKey(const QString &key) { d->key = key; } /// Returns the field's label. QString QXmppDataForm::Field::label() const { return d->label; } /// Sets the field's label. /// /// \param label void QXmppDataForm::Field::setLabel(const QString &label) { d->label = label; } /// /// Returns the field's media. /// /// \deprecated This method is deprecated since QXmpp 1.1. Use /// \c QXmppDataForm::Field::mediaSources() or /// \c QXmppDataForm::Field::mediaSize() instead. /// QXmppDataForm::Media QXmppDataForm::Field::media() const { QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED Media media; QList> pairUris; pairUris.reserve(d->mediaSources.size()); for (const auto &source : std::as_const(d->mediaSources)) { pairUris << qMakePair( source.contentType().name(), source.uri().toString()); } media.setHeight(d->mediaSize.height()); media.setWidth(d->mediaSize.width()); media.setUris(pairUris); return media; QT_WARNING_POP } /// Sets the field's \a media. /// /// \deprecated This method is deprecated since QXmpp 1.1. Use /// \c QXmppDataForm::Field::setMediaSources() or /// \c QXmppDataForm::Field::setMediaSize() instead. void QXmppDataForm::Field::setMedia(const QXmppDataForm::Media &media) { QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED const QList> &uris = media.uris(); QVector sources; sources.reserve(uris.size()); for (const auto &pairUri : uris) { sources << QXmppDataForm::MediaSource( QUrl(pairUri.second), QMimeDatabase().mimeTypeForName(pairUri.first)); } d->mediaSources = sources; d->mediaSize = QSize(media.width(), media.height()); QT_WARNING_POP } /// Returns the field's options. QList> QXmppDataForm::Field::options() const { return d->options; } /// Sets the field's options. /// /// \param options void QXmppDataForm::Field::setOptions(const QList> &options) { d->options = options; } /// Returns true if the field is required, false otherwise. bool QXmppDataForm::Field::isRequired() const { return d->required; } /// Set to true if the field is required, false otherwise. /// /// \param required void QXmppDataForm::Field::setRequired(bool required) { d->required = required; } /// Returns the field's type. QXmppDataForm::Field::Type QXmppDataForm::Field::type() const { return d->type; } /// Sets the field's type. /// /// \param type void QXmppDataForm::Field::setType(QXmppDataForm::Field::Type type) { d->type = type; } /// Returns the field's value. QVariant QXmppDataForm::Field::value() const { return d->value; } /// Sets the field's value. /// /// \param value void QXmppDataForm::Field::setValue(const QVariant &value) { d->value = value; } /// Returns the size of the attached media according to \xep{0221}: Data Forms /// Media Element. /// /// \since QXmpp 1.1 QSize QXmppDataForm::Field::mediaSize() const { return d->mediaSize; } /// Returns the size of the attached media according to \xep{0221}: Data Forms /// Media Element. /// /// \since QXmpp 1.1 QSize &QXmppDataForm::Field::mediaSize() { return d->mediaSize; } /// Sets the size of the attached media according to \xep{0221}: Data Forms Media /// Element. /// /// \since QXmpp 1.1 void QXmppDataForm::Field::setMediaSize(const QSize &size) { d->mediaSize = size; } /// Returns the sources for the attached media according to \xep{0221}: Data /// Forms Media Element. /// /// \since QXmpp 1.1 QVector QXmppDataForm::Field::mediaSources() const { return d->mediaSources; } /// Returns the sources for the attached media according to \xep{0221}: Data /// Forms Media Element. /// /// \since QXmpp 1.1 QVector &QXmppDataForm::Field::mediaSources() { return d->mediaSources; } /// Sets the sources to the attached media of the field according to \xep{0221}: /// Data Forms Media Element. /// /// \since QXmpp 1.1 void QXmppDataForm::Field::setMediaSources(const QVector &mediaSources) { d->mediaSources = mediaSources; } /// Returns true if the other field is identical to this one. /// /// \since QXmpp 1.1 bool QXmppDataForm::Field::operator==(const QXmppDataForm::Field &other) const { return d->description == other.description() && d->key == other.key() && d->label == other.label() && d->options == other.options() && d->required == other.isRequired() && d->type == other.type() && d->value == other.value() && d->mediaSources == other.mediaSources() && d->mediaSize == other.mediaSize(); } class QXmppDataFormPrivate : public QSharedData { public: QXmppDataFormPrivate(); QString instructions; QList fields; QString title; QXmppDataForm::Type type; }; QXmppDataFormPrivate::QXmppDataFormPrivate() : type(QXmppDataForm::None) { } /// Constructs a QXmppDataForm of the specified \a type. QXmppDataForm::QXmppDataForm(QXmppDataForm::Type type) : d(new QXmppDataFormPrivate) { d->type = type; } /// /// Constructs a QXmppDataForm with the specified attributes. /// /// \since QXmpp 1.3 /// QXmppDataForm::QXmppDataForm(QXmppDataForm::Type type, const QList &fields, const QString &title, const QString &instructions) : d(new QXmppDataFormPrivate) { d->type = type; d->fields = fields; d->title = title; d->instructions = instructions; } /// Constructs a copy of \a other. QXmppDataForm::QXmppDataForm(const QXmppDataForm &other) : d(other.d) { } /// Destroys the form. QXmppDataForm::~QXmppDataForm() { } /// Assigns \a other to this form. QXmppDataForm &QXmppDataForm::operator=(const QXmppDataForm &other) { d = other.d; return *this; } /// Returns the form's fields. QList QXmppDataForm::fields() const { return d->fields; } /// Returns the form's fields by reference. QList &QXmppDataForm::fields() { return d->fields; } /// Sets the form's fields. /// /// \param fields void QXmppDataForm::setFields(const QList &fields) { d->fields = fields; } /// Returns the form's instructions. QString QXmppDataForm::instructions() const { return d->instructions; } /// Sets the form's instructions. /// /// \param instructions void QXmppDataForm::setInstructions(const QString &instructions) { d->instructions = instructions; } /// Returns the form's title. QString QXmppDataForm::title() const { return d->title; } /// Sets the form's title. /// /// \param title void QXmppDataForm::setTitle(const QString &title) { d->title = title; } /// Returns the form's type. QXmppDataForm::Type QXmppDataForm::type() const { return d->type; } /// Sets the form's type. /// /// \param type void QXmppDataForm::setType(QXmppDataForm::Type type) { d->type = type; } /// Returns true if the form has an unknown type. bool QXmppDataForm::isNull() const { return d->type == QXmppDataForm::None; } /// \cond void QXmppDataForm::parse(const QDomElement &element) { if (element.isNull()) return; /* form type */ const QString typeStr = element.attribute("type"); if (typeStr == "form") d->type = QXmppDataForm::Form; else if (typeStr == "submit") d->type = QXmppDataForm::Submit; else if (typeStr == "cancel") d->type = QXmppDataForm::Cancel; else if (typeStr == "result") d->type = QXmppDataForm::Result; else { qWarning() << "Unknown form type" << typeStr; return; } /* form properties */ d->title = element.firstChildElement("title").text(); d->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 media */ QDomElement mediaElement = fieldElement.firstChildElement("media"); if (!mediaElement.isNull() && mediaElement.namespaceURI() == ns_media_element) { field.mediaSize().setHeight(mediaElement.attribute("height", "-1").toInt()); field.mediaSize().setWidth(mediaElement.attribute("width", "-1").toInt()); QDomElement uriElement = mediaElement.firstChildElement(QStringLiteral("uri")); while (!uriElement.isNull()) { field.mediaSources() << MediaSource( QUrl(uriElement.text()), QMimeDatabase().mimeTypeForName( uriElement.attribute(QStringLiteral("type")))); uriElement = uriElement.nextSiblingElement(QStringLiteral("uri")); } } /* field options */ if (type == QXmppDataForm::Field::ListMultiField || type == QXmppDataForm::Field::ListSingleField) { QList> options; QDomElement optionElement = fieldElement.firstChildElement("option"); while (!optionElement.isNull()) { options.append(QPair(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()); d->fields.append(field); fieldElement = fieldElement.nextSiblingElement("field"); } } void QXmppDataForm::toXml(QXmlStreamWriter *writer) const { if (isNull()) return; writer->writeStartElement("x"); writer->writeDefaultNamespace(ns_data); /* form type */ QString typeStr; if (d->type == QXmppDataForm::Form) typeStr = "form"; else if (d->type == QXmppDataForm::Submit) typeStr = "submit"; else if (d->type == QXmppDataForm::Cancel) typeStr = "cancel"; else if (d->type == QXmppDataForm::Result) typeStr = "result"; writer->writeAttribute("type", typeStr); /* form properties */ if (!d->title.isEmpty()) writer->writeTextElement("title", d->title); if (!d->instructions.isEmpty()) writer->writeTextElement("instructions", d->instructions); for (const QXmppDataForm::Field &field : d->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; } } writer->writeAttribute("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) { for (const QString &value : field.value().toStringList()) helperToXmlAddTextElement(writer, "value", value); } else if (!field.value().toString().isEmpty()) { helperToXmlAddTextElement(writer, "value", field.value().toString()); } /* field media */ if (!field.mediaSources().isEmpty()) { writer->writeStartElement("media"); helperToXmlAddAttribute(writer, "xmlns", ns_media_element); // media width and height if (field.mediaSize().width() > 0) helperToXmlAddAttribute( writer, QStringLiteral("width"), QString::number(field.mediaSize().width())); if (field.mediaSize().height() > 0) helperToXmlAddAttribute( writer, QStringLiteral("height"), QString::number(field.mediaSize().height())); const QVector &sources = field.mediaSources(); for (const auto &source : sources) { writer->writeStartElement(QStringLiteral("uri")); helperToXmlAddAttribute(writer, QStringLiteral("type"), source.contentType().name()); writer->writeCharacters(source.uri().toString()); writer->writeEndElement(); } writer->writeEndElement(); } /* field options */ if (type == QXmppDataForm::Field::ListMultiField || type == QXmppDataForm::Field::ListSingleField) { QPair option; for (const auto &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(); } /// \endcond