// SPDX-FileCopyrightText: 2010 Jeremy Lainé // SPDX-FileCopyrightText: 2019 Linus Jahn // // SPDX-License-Identifier: LGPL-2.1-or-later #include "QXmppDataForm.h" #include "QXmppConstants_p.h" #include "QXmppDataFormBase.h" #include "QXmppUtils.h" #include #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 }, }; std::optional fieldTypeFromString(const QString &type) { struct field_type *ptr; for (ptr = field_types; ptr->str; ptr++) { if (type == ptr->str) { return ptr->type; } } return {}; } QString fieldTypeToString(QXmppDataForm::Field::Type type) { struct field_type *ptr; for (ptr = field_types; ptr->str; ptr++) { if (type == ptr->type) { return ptr->str; } } return {}; } std::optional formTypeFromString(const QString &type) { if (type == "form") { return QXmppDataForm::Form; } if (type == "submit") { return QXmppDataForm::Submit; } if (type == "cancel") { return QXmppDataForm::Cancel; } if (type == "result") { return QXmppDataForm::Result; } return {}; } QString formTypeToString(QXmppDataForm::Type type) { switch (type) { case QXmppDataForm::Form: return "form"; case QXmppDataForm::Submit: return "submit"; case QXmppDataForm::Cancel: return "cancel"; case QXmppDataForm::Result: return "result"; default: return {}; } } class QXmppDataFormMediaSourcePrivate : public QSharedData { public: QUrl uri; QMimeType contentType; }; /// /// \class QXmppDataForm::MediaSource /// /// The QXmppDataForm::MediaSource class represents a link to one of possibly /// multiple sources for a media element from \xep{0221, Data Forms Media /// Element} consisting of a MIME type and a QUrl. /// /// \since QXmpp 1.1 /// /// /// Default constructor /// QXmppDataForm::MediaSource::MediaSource() : d(new QXmppDataFormMediaSourcePrivate) { } /// /// Constructs a MediaSource and sets its uri and contentType. /// /// \param uri /// \param contentType /// QXmppDataForm::MediaSource::MediaSource(const QUrl &uri, const QMimeType &contentType) : d(new QXmppDataFormMediaSourcePrivate) { d->uri = uri; d->contentType = contentType; } /// Default copy-constructor QXmppDataForm::MediaSource::MediaSource(const QXmppDataForm::MediaSource &) = default; /// Default move-constructor QXmppDataForm::MediaSource::MediaSource(QXmppDataForm::MediaSource &&) = default; QXmppDataForm::MediaSource::~MediaSource() = default; /// Default assignment operator QXmppDataForm::MediaSource &QXmppDataForm::MediaSource::operator=(const QXmppDataForm::MediaSource &) = default; /// Default move-assignment operator QXmppDataForm::MediaSource &QXmppDataForm::MediaSource::operator=(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; }; /// /// \class QXmppDataForm::Media /// /// The QXmppDataForm::Media class represents a media field as defined by /// \xep{0221, Data Forms Media Element}. /// /// \deprecated This class is deprecated since QXmpp 1.1. /// /// /// 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: QString description; QString key; QString label; QList> options; bool required = false; QXmppDataForm::Field::Type type = QXmppDataForm::Field::TextSingleField; QVariant value; QSize mediaSize; QVector mediaSources; }; /// /// \class QXmppDataForm::Field /// /// The QXmppDataForm::Field class represents a data form field as defined by /// \xep{0004, Data Forms}. /// /// /// 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) = default; /// Default move constructor. QXmppDataForm::Field::Field(QXmppDataForm::Field &&) = default; /// Destroys the form field. QXmppDataForm::Field::~Field() = default; /// Assigns \a other to this field. QXmppDataForm::Field &QXmppDataForm::Field::operator=(const QXmppDataForm::Field &other) = default; /// Default move-assignment operator. QXmppDataForm::Field &QXmppDataForm::Field::operator=(QXmppDataForm::Field &&) = default; /// /// 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: QString instructions; QList fields; QString title; QXmppDataForm::Type type = QXmppDataForm::None; }; /// /// \class QXmppDataForm /// /// The QXmppDataForm class represents a data form as defined by \xep{0004, /// Data Forms}. /// /// /// Constructs a QXmppDataForm with the specified attributes. /// /// \since QXmpp 1.3 /// 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 data form from any type based on QXmppDataFormBase. /// /// \since QXmpp 1.5 /// QXmppDataForm::QXmppDataForm(const QXmppDataFormBase &based) { *this = based.toDataForm(); } /// Constructs a copy of \a other. QXmppDataForm::QXmppDataForm(const QXmppDataForm &other) = default; /// Default move constructor. QXmppDataForm::QXmppDataForm(QXmppDataForm &&other) = default; /// Destroys the form. QXmppDataForm::~QXmppDataForm() = default; /// Assigns \a other to this form. QXmppDataForm &QXmppDataForm::operator=(const QXmppDataForm &other) = default; /// Default move-assignment operator. QXmppDataForm &QXmppDataForm::operator=(QXmppDataForm &&) = default; /// /// 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; } /// /// Searches for a hidden field called 'FORM_TYPE' and returns its value. /// /// \returns The string value of the field or a null string if the field /// couldn't be found. /// /// \since QXmpp 1.5 /// QString QXmppDataForm::formType() const { const auto formTypeItr = std::find_if(d->fields.begin(), d->fields.end(), [](const QXmppDataForm::Field &field) { return field.type() == QXmppDataForm::Field::HiddenField && field.key() == QStringLiteral("FORM_TYPE"); }); if (formTypeItr != d->fields.end()) { return formTypeItr->value().toString(); } return {}; } /// /// 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 */ if (const auto type = formTypeFromString(element.attribute("type"))) { d->type = *type; } else { qWarning() << "Unknown form type" << element.attribute("type"); return; } /* form properties */ d->title = element.firstChildElement("title").text(); d->instructions = element.firstChildElement("instructions").text(); for (auto fieldElement = element.firstChildElement("field"); !fieldElement.isNull(); fieldElement = fieldElement.nextSiblingElement("field")) { QXmppDataForm::Field field; /* field type */ field.setType(fieldTypeFromString(fieldElement.attribute("type")).value_or(Field::TextSingleField)); /* field attributes */ field.setLabel(fieldElement.attribute("label")); field.setKey(fieldElement.attribute("var")); /* field value(s) */ switch (field.type()) { case Field::BooleanField: { const auto valueStr = fieldElement.firstChildElement("value").text(); field.setValue(valueStr == "1" || valueStr == "true"); break; } case Field::ListMultiField: case Field::JidMultiField: case Field::TextMultiField: { QStringList values; for (auto element = fieldElement.firstChildElement("value"); !element.isNull(); element = element.nextSiblingElement("value")) { values << element.text(); } field.setValue(values); break; } default: field.setValue(fieldElement.firstChildElement("value").text()); } /* field media */ if (const auto mediaElement = fieldElement.firstChildElement("media"); mediaElement.namespaceURI() == ns_media_element) { field.mediaSize().setHeight(mediaElement.attribute("height", "-1").toInt()); field.mediaSize().setWidth(mediaElement.attribute("width", "-1").toInt()); QMimeDatabase database; for (auto element = mediaElement.firstChildElement(QStringLiteral("uri")); !element.isNull(); element = element.nextSiblingElement(QStringLiteral("uri"))) { field.mediaSources() << MediaSource( QUrl(element.text()), database.mimeTypeForName(element.attribute(QStringLiteral("type")))); } } /* field options */ switch (field.type()) { case Field::ListMultiField: case Field::ListSingleField: { QList> options; for (auto element = fieldElement.firstChildElement("option"); !element.isNull(); element = element.nextSiblingElement("option")) { options << qMakePair(element.attribute("label"), element.firstChildElement("value").text()); } field.setOptions(options); } default: break; } /* other properties */ field.setDescription(fieldElement.firstChildElement("description").text()); field.setRequired(!fieldElement.firstChildElement("required").isNull()); d->fields.append(field); } } void QXmppDataForm::toXml(QXmlStreamWriter *writer) const { if (isNull()) { return; } writer->writeStartElement("x"); writer->writeDefaultNamespace(ns_data); /* form type */ writer->writeAttribute("type", formTypeToString(d->type)); /* form properties */ if (!d->title.isEmpty()) { writer->writeTextElement("title", d->title); } if (!d->instructions.isEmpty()) { writer->writeTextElement("instructions", d->instructions); } for (const auto &field : d->fields) { writer->writeStartElement("field"); /* field type */ writer->writeAttribute("type", fieldTypeToString(field.type())); /* field attributes */ helperToXmlAddAttribute(writer, "label", field.label()); helperToXmlAddAttribute(writer, "var", field.key()); /* field value(s) */ switch (field.type()) { case Field::BooleanField: helperToXmlAddTextElement(writer, "value", field.value().toBool() ? "1" : "0"); break; case Field::ListMultiField: case Field::JidMultiField: case Field::TextMultiField: { const auto values = field.value().toStringList(); for (const QString &value : values) { helperToXmlAddTextElement(writer, "value", value); } break; } default: if (const auto value = field.value().toString(); !value.isEmpty()) { helperToXmlAddTextElement(writer, "value", value); } } /* field media */ if (!field.mediaSources().isEmpty()) { writer->writeStartElement("media"); writer->writeDefaultNamespace(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 auto 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 */ switch (field.type()) { case Field::ListMultiField: case Field::ListSingleField: { const auto options = field.options(); for (const auto &option : options) { writer->writeStartElement("option"); helperToXmlAddAttribute(writer, "label", option.first); helperToXmlAddTextElement(writer, "value", option.second); writer->writeEndElement(); } } default: break; } /* other properties */ if (!field.description().isEmpty()) { helperToXmlAddTextElement(writer, "description", field.description()); } if (field.isRequired()) { helperToXmlAddTextElement(writer, "required", ""); } writer->writeEndElement(); } writer->writeEndElement(); } /// \endcond