diff --git a/NEWS b/NEWS index 2b71a6aa..0274f9c9 100644 --- a/NEWS +++ b/NEWS @@ -3,10 +3,14 @@ Noteworthy changes in version 1.7.2 (unreleased) * The module of the Python bindings has been renamed to 'gpg'. + * qt: Added Distinguished Name parser from libkleo + * Interface changes relative to the 1.7.1 release: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ gpgme_set_sender NEW. gpgme_get_sender NEW. + qt: DN NEW. + qt: DN::Attribute NEW. Noteworthy changes in version 1.7.1 (2016-10-18) diff --git a/lang/qt/src/Makefile.am b/lang/qt/src/Makefile.am index f7610fd6..0dd18fe7 100644 --- a/lang/qt/src/Makefile.am +++ b/lang/qt/src/Makefile.am @@ -35,7 +35,8 @@ qgpgme_sources = \ qgpgmeverifyopaquejob.cpp threadedjobmixin.cpp \ qgpgmekeyformailboxjob.cpp gpgme_backend_debug.cpp \ qgpgmetofupolicyjob.cpp \ - defaultkeygenerationjob.cpp qgpgmewkspublishjob.cpp + defaultkeygenerationjob.cpp qgpgmewkspublishjob.cpp \ + dn.cpp # If you add one here make sure that you also add one in camelcase qgpgme_headers= \ @@ -73,7 +74,8 @@ qgpgme_headers= \ verifydetachedjob.h \ defaultkeygenerationjob.h \ tofupolicyjob.h \ - wkspublishjob.h + wkspublishjob.h \ + dn.h camelcase_headers= \ AddUserIDJob \ @@ -84,6 +86,7 @@ camelcase_headers= \ DataProvider \ DecryptJob \ DecryptVerifyJob \ + DN \ DownloadJob \ EncryptJob \ ExportJob \ diff --git a/lang/qt/src/dn.cpp b/lang/qt/src/dn.cpp new file mode 100644 index 00000000..0f81a4c3 --- /dev/null +++ b/lang/qt/src/dn.cpp @@ -0,0 +1,495 @@ +/* + dn.cpp + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2004 Klarälvdalens Datakonsult AB + Copyright (c) 2016 Intevation GmbH + + 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 + +#include "dn.h" + +static const struct { + const char *name; + const char *oid; +} oidmap[] = { + // 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" }, + { "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" }, +}; +static const unsigned int numOidMaps = sizeof oidmap / sizeof * 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 +{ +struct DnPair { + char *key; + char *value; +}; +} + +// copied from CryptPlug and adapted to work on DN::Attribute::List: + +#define digitp(p) (*(p) >= '0' && *(p) <= '9') +#define hexdigitp(a) (digitp (a) \ + || (*(a) >= 'A' && *(a) <= 'F') \ + || (*(a) >= 'a' && *(a) <= 'f')) +#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ + *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) +#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) + +static char * +trim_trailing_spaces(char *string) +{ + char *p, *mark; + + for (mark = NULL, p = string; *p; p++) { + if (isspace(*p)) { + if (!mark) { + mark = p; + } + } else { + mark = NULL; + } + } + if (mark) { + *mark = '\0'; + } + + return 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 const unsigned char * +parse_dn_part(DnPair *array, const unsigned char *string) +{ + const unsigned char *s, *s1; + size_t n; + char *p; + + /* parse attributeType */ + for (s = string + 1; *s && *s != '='; s++) + ; + if (!*s) { + return NULL; /* error */ + } + n = s - string; + if (!n) { + return NULL; /* empty key */ + } + p = (char *)malloc(n + 1); + + memcpy(p, string, n); + p[n] = 0; + trim_trailing_spaces((char *)p); + // map OIDs to their names: + for (unsigned int i = 0; i < numOidMaps; ++i) + if (!strcasecmp((char *)p, oidmap[i].oid)) { + free(p); + p = strdup(oidmap[i].name); + break; + } + array->key = p; + string = s + 1; + + if (*string == '#') { + /* hexstring */ + string++; + for (s = string; hexdigitp(s); s++) { + s++; + } + n = s - string; + if (!n || (n & 1)) { + return NULL; /* empty or odd number of digits */ + } + n /= 2; + array->value = p = (char *)malloc(n + 1); + + for (s1 = string; n; s1 += 2, n--) { + *p++ = xtoi_2(s1); + } + *p = 0; + } else { + /* regular v3 quoted string */ + for (n = 0, s = string; *s; s++) { + if (*s == '\\') { + /* pair */ + s++; + if (*s == ',' || *s == '=' || *s == '+' + || *s == '<' || *s == '>' || *s == '#' || *s == ';' + || *s == '\\' || *s == '\"' || *s == ' ') { + n++; + } else if (hexdigitp(s) && hexdigitp(s + 1)) { + s++; + n++; + } else { + return NULL; /* invalid escape sequence */ + } + } else if (*s == '\"') { + return NULL; /* invalid encoding */ + } else if (*s == ',' || *s == '=' || *s == '+' + || *s == '<' || *s == '>' || *s == '#' || *s == ';') { + break; + } else { + n++; + } + } + + array->value = p = (char *)malloc(n + 1); + + for (s = string; n; s++, n--) { + if (*s == '\\') { + s++; + if (hexdigitp(s)) { + *p++ = xtoi_2(s); + s++; + } else { + *p++ = *s; + } + } else { + *p++ = *s; + } + } + *p = 0; + } + return s; +} + +/* 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 QGpgME::DN::Attribute::List +parse_dn(const unsigned char *string) +{ + if (!string) { + return QVector(); + } + + QVector result; + while (*string) { + while (*string == ' ') { + string++; + } + if (!*string) { + break; /* ready */ + } + + DnPair pair = { 0, 0 }; + string = parse_dn_part(&pair, string); + if (!string) { + goto failure; + } + if (pair.key && pair.value) + result.push_back(QGpgME::DN::Attribute(QString::fromUtf8(pair.key), + QString::fromUtf8(pair.value))); + free(pair.key); + free(pair.value); + + while (*string == ' ') { + string++; + } + if (*string && *string != ',' && *string != ';' && *string != '+') { + goto failure; /* invalid delimiter */ + } + if (*string) { + string++; + } + } + return result; + +failure: + return QVector(); +} + +static QVector +parse_dn(const QString &dn) +{ + return parse_dn((const unsigned char *)dn.toUtf8().data()); +} + +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 QString +serialise(const QVector &dn, const QString &sep) +{ + QStringList result; + for (QVector::const_iterator it = dn.begin(); it != dn.end(); ++it) + if (!(*it).name().isEmpty() && !(*it).value().isEmpty()) { + result.push_back((*it).name().trimmed() + QLatin1Char('=') + dn_escape((*it).value().trimmed())); + } + return result.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); +} + +QGpgME::DN::DN(const char *utf8DN) +{ + d = new Private(); + d->ref(); + if (utf8DN) { + d->attributes = parse_dn((const unsigned char *)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(); +} + +// 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::const_iterator it = d->attributes.constBegin(); + it != d->attributes.constEnd(); ++it) + if ((*it).name() == attrUpper) { + return (*it).value(); + } + return QString(); +} + +static QVector 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; +} diff --git a/lang/qt/src/dn.h b/lang/qt/src/dn.h new file mode 100644 index 00000000..17b1c302 --- /dev/null +++ b/lang/qt/src/dn.h @@ -0,0 +1,136 @@ +/* + dn.h + + This file is part of qgpgme, the Qt API binding for gpgme + Copyright (c) 2004 Klarälvdalens Datakonsult AB + Copyright (c) 2016 Intevation GmbH + + 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. +*/ +#ifndef QGPGME_DN_H +#define QGPGME_DN_H + +#include "qgpgme_export.h" + +#include +#include + +#include + +namespace QGpgME +{ + +/** + @short DN parser and reorderer +*/ +class QGPGME_EXPORT DN +{ +public: + class Attribute; + typedef QVector AttributeList; + typedef AttributeList::const_iterator const_iterator; + + DN(); + explicit DN(const QString &dn); + explicit DN(const char *utf8DN); + DN(const DN &other); + ~DN(); + + const DN &operator=(const DN &other); + + /** @return the value in rfc-2253-escaped form */ + static QString escape(const QString &value); + + /** @return the DN in a reordered form, according to the settings in + the [DN] group of the application's config file */ + QString prettyDN() const; + /** @return the DN in the original form */ + QString dn() const; + /** + \overload + Uses \a sep as separator (default: ,) + */ + QString dn(const QString &sep) const; + + QString operator[](const QString &attr) const; + + void append(const Attribute &attr); + + const_iterator begin() const; + const_iterator end() const; + + /** Set the order in which prettyDN will reorder the Attirbutes. */ + void setAttributeOrder(const QStringList &order) const; + + /** Get the used attribute order. */ + const QStringList & attributeOrder() const; + +private: + void detach(); +private: + class Private; + Private *d; +}; + +class QGPGME_EXPORT DN::Attribute +{ +public: + typedef DN::AttributeList List; + + explicit Attribute(const QString &name = QString(), const QString &value = QString()) + : mName(name.toUpper()), mValue(value) {} + Attribute(const Attribute &other) + : mName(other.name()), mValue(other.value()) {} + + const Attribute &operator=(const Attribute &other) + { + if (this != &other) { + mName = other.name(); + mValue = other.value(); + } + return *this; + } + + const QString &name() const + { + return mName; + } + const QString &value() const + { + return mValue; + } + + void setValue(const QString &value) + { + mValue = value; + } + +private: + QString mName; + QString mValue; +}; +} // namespace QGpgME +#endif // QGPGME_DN_H