diff --git a/src/dn.cpp b/src/dn.cpp index 01aa632..0a407c9 100644 --- a/src/dn.cpp +++ b/src/dn.cpp @@ -1,619 +1,621 @@ /* dn.cpp This file is part of qgpgme, the Qt API binding for gpgme Copyright (c) 2004 Klarälvdalens Datakonsult AB Copyright (c) 2016 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Copyright (c) 2023-2025 g10 Code GmbH Software engineering by Ingo Klöcker <dev@ingo-kloecker.de> Software engineering by Sune Vuorela <sune@vuorela.dk> QGpgME is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. QGpgME 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif // Note: part of this file is also duplicated in kde:okular.git and freedesktop:poppler.git #include "dn.h" #include <QByteArray> #include <gpg-error.h> +#include <optional> +#include <string_view> #include <vector> +using namespace std::literals; + static const std::vector<std::pair<std::string_view, std::string_view>> &oidmap() { static const std::vector<std::pair<std::string_view, std::string_view>> oidmap_ = { // clang-format off - // keep them ordered by oid: - { "SP", "ST" }, // hack to show the Sphinx-required/desired SP for - // StateOrProvince, otherwise known as ST or even S - {"NameDistinguisher", "0.2.262.1.10.7.20" }, - {"EMAIL", "1.2.840.113549.1.9.1"}, - {"CN", "2.5.4.3" }, - {"SN", "2.5.4.4" }, - {"SerialNumber", "2.5.4.5" }, - {"T", "2.5.4.12" }, - {"D", "2.5.4.13" }, - {"BC", "2.5.4.15" }, - {"ADDR", "2.5.4.16" }, - {"PC", "2.5.4.17" }, - {"GN", "2.5.4.42" }, - {"Pseudo", "2.5.4.65" }, + { "SP", "ST" }, // hack to show the Sphinx-required/desired SP for + // StateOrProvince, otherwise known as ST or even S + // keep them ordered by oid: + {"NameDistinguisher", "0.2.262.1.10.7.20" }, + {"EMAIL", "1.2.840.113549.1.9.1"}, + {"CN", "2.5.4.3" }, + {"SN", "2.5.4.4" }, + {"SerialNumber", "2.5.4.5" }, + {"T", "2.5.4.12" }, + {"D", "2.5.4.13" }, + {"BC", "2.5.4.15" }, + {"ADDR", "2.5.4.16" }, + {"PC", "2.5.4.17" }, + {"GN", "2.5.4.42" }, + {"Pseudo", "2.5.4.65" }, // clang-format on }; return oidmap_; } class QGpgME::DN::Private { public: Private() : mRefCount(0) {} Private(const Private &other) : attributes(other.attributes), reorderedAttributes(other.reorderedAttributes), order{"CN", "L", "_X_", "OU", "O", "C"}, mRefCount(0) { } int ref() { return ++mRefCount; } int unref() { if (--mRefCount <= 0) { delete this; return 0; } else { return mRefCount; } } int refCount() const { return mRefCount; } DN::Attribute::List attributes; DN::Attribute::List reorderedAttributes; QStringList order; private: int mRefCount; }; namespace detail { static std::string_view removeLeadingSpaces(std::string_view view) { auto pos = view.find_first_not_of(' '); - if (pos > view.size()) { + if (pos == std::string_view::npos) { return {}; } return view.substr(pos); } static std::string_view removeTrailingSpaces(std::string_view view) { auto pos = view.find_last_not_of(' '); - if (pos > view.size()) { + if (pos == std::string_view::npos) { return {}; } return view.substr(0, pos + 1); } static unsigned char xtoi(unsigned char c) { if (c <= '9') { return c - '0'; } if (c <= 'F') { return c - 'A' + 10; } return c - 'a' + 10; } static unsigned char xtoi(unsigned char first, unsigned char second) { return 16 * xtoi(first) + xtoi(second); } + // Parses a hex string into actual content static std::optional<std::string> parseHexString(std::string_view view) { - auto size = view.size(); + const auto size = view.size(); if (size == 0 || (size % 2 == 1)) { - return std::nullopt; + return {}; } // It is only supposed to be called with actual hex strings // but this is just to be extra sure - auto endHex = view.find_first_not_of("1234567890abcdefABCDEF"); + auto endHex = view.find_first_not_of("1234567890abcdefABCDEF"sv); if (endHex != std::string_view::npos) { return {}; } std::string result; result.reserve(size / 2); - for (size_t i = 0; i < (view.size() - 1); i += 2) { + for (size_t i = 0; i < (size - 1); i += 2) { result.push_back(xtoi(view[i], view[i + 1])); } return result; } static std::string_view attributeNameForOID(std::string_view oid) { - if (oid.substr(0, 4) == std::string_view {"OID."} || oid.substr(0, 4) == std::string_view {"oid."}) { // c++20 has starts_with. we don't have that yet. + if (oid.substr(0, 4) == "OID."sv || oid.substr(0, 4) == "oid."sv) { // c++20 has starts_with. we don't have that yet. oid.remove_prefix(4); } for (const auto &m : oidmap()) { if (oid == m.second) { return m.first; } } return {}; } -/* Parse a DN and return an array-ized one. This is not a validating - parser and it does not support any old-stylish syntax; gpgme is - expected to return only rfc2253 compatible strings. */ +/* Parse a DN attribute key/value pair and return the remaining unparsed string and the + parsed attribute. This is not a validating parser and it does not support any old-stylish + syntax; gpgme is expected to return only rfc2253 compatible strings. */ static std::pair<std::optional<std::string_view>, std::pair<std::string, std::string>> parse_dn_part(std::string_view stringv) { std::pair<std::string, std::string> dnPair; auto separatorPos = stringv.find_first_of('='); if (separatorPos == 0 || separatorPos == std::string_view::npos) { return {}; /* empty key */ } std::string_view key = stringv.substr(0, separatorPos); key = removeTrailingSpaces(key); // map OIDs to their names: if (auto name = attributeNameForOID(key); !name.empty()) { key = name; } dnPair.first = std::string {key}; stringv = removeLeadingSpaces(stringv.substr(separatorPos + 1)); if (stringv.empty()) { return {}; } if (stringv.front() == '#') { /* hexstring */ stringv.remove_prefix(1); - auto endHex = stringv.find_first_not_of("1234567890abcdefABCDEF"); - if (!endHex || (endHex % 2 == 1)) { - return {}; /* empty or odd number of digits */ - } + auto endHex = stringv.find_first_not_of("1234567890abcdefABCDEF"sv); auto value = parseHexString(stringv.substr(0, endHex)); if (!value.has_value()) { return {}; } - stringv = stringv.substr(endHex); + stringv.remove_prefix(endHex); dnPair.second = value.value(); } else if (stringv.front() == '"') { stringv.remove_prefix(1); std::string value; bool stop = false; while (!stringv.empty() && !stop) { switch (stringv.front()) { case '\\': { if (stringv.size() < 2) { return {}; } if (stringv[1] == '"') { value.push_back('"'); stringv.remove_prefix(2); } else { // it is a bit unclear in rfc2253 if escaped hex chars should - // be decoded inside quotes. Let's just forward the verbatim + // be decoded inside quotes. Let's just forward them verbatim // for now value.push_back(stringv.front()); value.push_back(stringv[1]); stringv.remove_prefix(2); } break; } case '"': { stop = true; stringv.remove_prefix(1); break; } default: { value.push_back(stringv.front()); stringv.remove_prefix(1); } } } if (!stop) { // we have reached end of string, but never an actual ", so error out return {}; } dnPair.second = value; } else { std::string value; bool stop = false; bool lastAddedEscapedSpace = false; while (!stringv.empty() && !stop) { switch (stringv.front()) { - case '\\': //_escaping + case '\\': // escaping { stringv.remove_prefix(1); if (stringv.empty()) { return {}; } switch (stringv.front()) { case ',': case '=': case '+': case '<': case '>': case '#': case ';': case '\\': case '"': case ' ': { if (stringv.front() == ' ') { lastAddedEscapedSpace = true; } else { lastAddedEscapedSpace = false; } value.push_back(stringv.front()); stringv.remove_prefix(1); break; } default: { if (stringv.size() < 2) { // this should be double hex-ish, but isn't. return {}; } if (std::isxdigit(stringv.front()) && std::isxdigit(stringv[1])) { lastAddedEscapedSpace = false; value.push_back(xtoi(stringv.front(), stringv[1])); stringv.remove_prefix(2); break; } else { // invalid escape return {}; } } } break; } case '"': // unescaped " in the middle; not allowed return {}; case ',': case '=': case '+': case '<': case '>': case '#': case ';': { stop = true; - break; // + break; } default: lastAddedEscapedSpace = false; value.push_back(stringv.front()); stringv.remove_prefix(1); } } if (lastAddedEscapedSpace) { dnPair.second = value; } else { - dnPair.second = std::string {removeTrailingSpaces(value)}; + dnPair.second = std::string{removeTrailingSpaces(value)}; } } return {stringv, dnPair}; } } using Result = std::vector<std::pair<std::string, std::string>>; /* Parse a DN and return an array-ized one. This is not a validating parser and it does not support any old-stylish syntax; gpgme is expected to return only rfc2253 compatible strings. */ static Result parseString(std::string_view string) { Result result; while (!string.empty()) { string = detail::removeLeadingSpaces(string); if (string.empty()) { break; } auto [partResult, dnPair] = detail::parse_dn_part(string); if (!partResult.has_value()) { return {}; } - string = partResult.value(); if (dnPair.first.size() && dnPair.second.size()) { result.emplace_back(std::move(dnPair)); } - string = detail::removeLeadingSpaces(string); + string = detail::removeLeadingSpaces(partResult.value()); if (string.empty()) { break; } switch (string.front()) { case ',': case ';': case '+': string.remove_prefix(1); break; default: // some unexpected characters here return {}; } } return result; } QGpgME::DN::AttributeList parse_dn(std::string_view view) { - auto parsed = parseString(view); + const auto parsed = parseString(view); QGpgME::DN::AttributeList list; list.reserve(parsed.size()); - for (auto& item : parsed) { - list.append(QGpgME::DN::Attribute(QString::fromStdString(item.first), QString::fromStdString(item.second))); + for (auto &item : parsed) { + list.append(QGpgME::DN::Attribute(QString::fromStdString(item.first), + QString::fromStdString(item.second))); } return list; } static QString dn_escape(const QString &s) { QString result; for (unsigned int i = 0, end = s.length(); i != end; ++i) { const QChar ch = s[i]; switch (ch.unicode()) { case ',': case '+': case '"': case '\\': case '<': case '>': case ';': result += QLatin1Char('\\'); // fall through default: result += ch; } } return result; } static QStringList listAttributes(const QVector<QGpgME::DN::Attribute> &dn) { QStringList result; result.reserve(dn.size()); for (const auto &attribute : dn) { if (!attribute.name().isEmpty() && !attribute.value().isEmpty()) { result.push_back(attribute.name().trimmed() + QLatin1Char('=') + dn_escape(attribute.value().trimmed())); } } return result; } static QString serialise(const QVector<QGpgME::DN::Attribute> &dn, const QString &sep) { return listAttributes(dn).join(sep); } static QGpgME::DN::Attribute::List reorder_dn(const QGpgME::DN::Attribute::List &dn, const QStringList &attrOrder) { QGpgME::DN::Attribute::List unknownEntries; QGpgME::DN::Attribute::List result; unknownEntries.reserve(dn.size()); result.reserve(dn.size()); // find all unknown entries in their order of appearance for (QGpgME::DN::const_iterator it = dn.begin(); it != dn.end(); ++it) if (!attrOrder.contains((*it).name())) { unknownEntries.push_back(*it); } // process the known attrs in the desired order for (QStringList::const_iterator oit = attrOrder.begin(); oit != attrOrder.end(); ++oit) if (*oit == QLatin1String("_X_")) { // insert the unknown attrs std::copy(unknownEntries.begin(), unknownEntries.end(), std::back_inserter(result)); unknownEntries.clear(); // don't produce dup's } else { for (QGpgME::DN::const_iterator dnit = dn.begin(); dnit != dn.end(); ++dnit) if ((*dnit).name() == *oit) { result.push_back(*dnit); } } return result; } // // // class DN // // QGpgME::DN::DN() { d = new Private(); d->ref(); } QGpgME::DN::DN(const QString &dn) { d = new Private(); d->ref(); d->attributes = parse_dn(dn.toStdString()); } QGpgME::DN::DN(const char *utf8DN) { d = new Private(); d->ref(); if (utf8DN) { d->attributes = parse_dn(std::string_view(utf8DN, strlen(utf8DN))); } } QGpgME::DN::DN(const DN &other) : d(other.d) { if (d) { d->ref(); } } QGpgME::DN::~DN() { if (d) { d->unref(); } } const QGpgME::DN &QGpgME::DN::operator=(const DN &that) { if (this->d == that.d) { return *this; } if (that.d) { that.d->ref(); } if (this->d) { this->d->unref(); } this->d = that.d; return *this; } QString QGpgME::DN::prettyDN() const { if (!d) { return QString(); } if (d->reorderedAttributes.empty()) { d->reorderedAttributes = reorder_dn(d->attributes, d->order); } return serialise(d->reorderedAttributes, QStringLiteral(",")); } QString QGpgME::DN::dn() const { return d ? serialise(d->attributes, QStringLiteral(",")) : QString(); } QString QGpgME::DN::dn(const QString &sep) const { return d ? serialise(d->attributes, sep) : QString(); } QStringList QGpgME::DN::prettyAttributes() const { if (!d) { return {}; } if (d->reorderedAttributes.empty()) { d->reorderedAttributes = reorder_dn(d->attributes, d->order); } return listAttributes(d->reorderedAttributes); } // static QString QGpgME::DN::escape(const QString &value) { return dn_escape(value); } void QGpgME::DN::detach() { if (!d) { d = new QGpgME::DN::Private(); d->ref(); } else if (d->refCount() > 1) { QGpgME::DN::Private *d_save = d; d = new QGpgME::DN::Private(*d); d->ref(); d_save->unref(); } } void QGpgME::DN::append(const Attribute &attr) { detach(); d->attributes.push_back(attr); d->reorderedAttributes.clear(); } QString QGpgME::DN::operator[](const QString &attr) const { if (!d) { return QString(); } const QString attrUpper = attr.toUpper(); for (QVector<Attribute>::const_iterator it = d->attributes.constBegin(); it != d->attributes.constEnd(); ++it) if ((*it).name() == attrUpper) { return (*it).value(); } return QString(); } static QVector<QGpgME::DN::Attribute> empty; QGpgME::DN::const_iterator QGpgME::DN::begin() const { return d ? d->attributes.constBegin() : empty.constBegin(); } QGpgME::DN::const_iterator QGpgME::DN::end() const { return d ? d->attributes.constEnd() : empty.constEnd(); } void QGpgME::DN::setAttributeOrder (const QStringList &order) const { d->order = order; } const QStringList & QGpgME::DN::attributeOrder () const { return d->order; }