aboutsummaryrefslogtreecommitdiffstats
path: root/lang/qt/src/qgpgmecryptoconfig.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lang/qt/src/qgpgmecryptoconfig.cpp948
1 files changed, 948 insertions, 0 deletions
diff --git a/lang/qt/src/qgpgmecryptoconfig.cpp b/lang/qt/src/qgpgmecryptoconfig.cpp
new file mode 100644
index 00000000..fe3e54f3
--- /dev/null
+++ b/lang/qt/src/qgpgmecryptoconfig.cpp
@@ -0,0 +1,948 @@
+/*
+ qgpgmecryptoconfig.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
+
+ Libkleopatra 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.
+
+ Libkleopatra 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.
+*/
+
+#include "qgpgmecryptoconfig.h"
+
+#include <QList>
+#include <QByteArray>
+#include <errno.h>
+#include "gpgme_backend_debug.h"
+
+#include "engineinfo.h"
+#include "global.h"
+
+#include <cassert>
+#include <QTemporaryFile>
+#include <QFile>
+#include <cstdlib>
+#include <iterator>
+#include <QStandardPaths>
+
+// Just for the Q_ASSERT in the dtor. Not thread-safe, but who would
+// have 2 threads talking to gpgconf anyway? :)
+static bool s_duringClear = false;
+
+static const int GPGCONF_FLAG_GROUP = 1;
+static const int GPGCONF_FLAG_OPTIONAL = 2;
+static const int GPGCONF_FLAG_LIST = 4;
+static const int GPGCONF_FLAG_RUNTIME = 8;
+static const int GPGCONF_FLAG_DEFAULT = 16; // fixed default value available
+//static const int GPGCONF_FLAG_DEFAULT_DESC = 32; // runtime default value available
+//static const int GPGCONF_FLAG_NOARG_DESC = 64; // option with optional arg; special meaning if no arg set
+static const int GPGCONF_FLAG_NO_CHANGE = 128; // readonly
+// Change size of mFlags bitfield if adding new values here
+
+QString QGpgMECryptoConfig::gpgConfPath()
+{
+ const GpgME::EngineInfo info = GpgME::engineInfo(GpgME::GpgConfEngine);
+ return info.fileName() ? QFile::decodeName(info.fileName()) : QStandardPaths::findExecutable(QStringLiteral("gpgconf"));
+}
+
+QGpgMECryptoConfig::QGpgMECryptoConfig()
+ : mParsed(false)
+{
+}
+
+QGpgMECryptoConfig::~QGpgMECryptoConfig()
+{
+ clear();
+}
+
+void QGpgMECryptoConfig::runGpgConf(bool showErrors)
+{
+ // Run gpgconf --list-components to make the list of components
+ KProcess process;
+
+ process << gpgConfPath();
+ process << QStringLiteral("--list-components");
+
+ connect(&process, &KProcess::readyReadStandardOutput, this, &QGpgMECryptoConfig::slotCollectStdOut);
+
+ // run the process:
+ int rc = 0;
+ process.setOutputChannelMode(KProcess::OnlyStdoutChannel);
+ process.start();
+ if (!process.waitForFinished()) {
+ rc = -2;
+ } else if (process.exitStatus() == QProcess::NormalExit) {
+ rc = process.exitCode();
+ } else {
+ rc = -1;
+ }
+
+ // handle errors, if any (and if requested)
+ if (showErrors && rc != 0) {
+ QString reason;
+ if (rc == -1) {
+ reason = i18n("program terminated unexpectedly");
+ } else if (rc == -2) {
+ reason = i18n("program not found or cannot be started");
+ } else {
+ reason = QString::fromLocal8Bit(strerror(rc)); // XXX errno as an exit code?
+ }
+ QString wmsg = i18n("<qt>Failed to execute gpgconf:<p>%1</p></qt>", reason);
+ qCWarning(GPGPME_BACKEND_LOG) << wmsg; // to see it from test_cryptoconfig.cpp
+ KMessageBox::error(0, wmsg);
+ }
+ mParsed = true;
+}
+
+void QGpgMECryptoConfig::slotCollectStdOut()
+{
+ assert(qobject_cast<KProcess *>(QObject::sender()));
+ KProcess *const proc = static_cast<KProcess *>(QObject::sender());
+ while (proc->canReadLine()) {
+ QString line = QString::fromUtf8(proc->readLine());
+ if (line.endsWith(QLatin1Char('\n'))) {
+ line.chop(1);
+ }
+ if (line.endsWith(QLatin1Char('\r'))) {
+ line.chop(1);
+ }
+ //qCDebug(GPGPME_BACKEND_LOG) <<"GOT LINE:" << line;
+ // Format: NAME:DESCRIPTION
+ const QStringList lst = line.split(QLatin1Char(':'));
+ if (lst.count() >= 2) {
+ const std::pair<QString, QGpgMECryptoConfigComponent *> pair(lst[0], new QGpgMECryptoConfigComponent(this, lst[0], lst[1]));
+ mComponentsNaturalOrder.push_back(pair);
+ mComponentsByName[pair.first] = pair.second;
+ } else {
+ qCWarning(GPGPME_BACKEND_LOG) << "Parse error on gpgconf --list-components output:" << line;
+ }
+ }
+}
+
+namespace
+{
+struct Select1St {
+ template <typename U, typename V>
+ const U &operator()(const std::pair<U, V> &p) const
+ {
+ return p.first;
+ }
+ template <typename U, typename V>
+ const U &operator()(const QPair<U, V> &p) const
+ {
+ return p.first;
+ }
+};
+}
+
+QStringList QGpgMECryptoConfig::componentList() const
+{
+ if (!mParsed) {
+ const_cast<QGpgMECryptoConfig *>(this)->runGpgConf(true);
+ }
+ QStringList result;
+ std::transform(mComponentsNaturalOrder.begin(), mComponentsNaturalOrder.end(),
+ std::back_inserter(result), Select1St());
+ return result;
+}
+
+QGpgME::CryptoConfigComponent *QGpgMECryptoConfig::component(const QString &name) const
+{
+ if (!mParsed) {
+ const_cast<QGpgMECryptoConfig *>(this)->runGpgConf(false);
+ }
+ return mComponentsByName.value(name);
+}
+
+void QGpgMECryptoConfig::sync(bool runtime)
+{
+ Q_FOREACH (QGpgMECryptoConfigComponent *it, mComponentsByName) {
+ it->sync(runtime);
+ }
+}
+
+void QGpgMECryptoConfig::clear()
+{
+ s_duringClear = true;
+ mComponentsNaturalOrder.clear();
+ qDeleteAll(mComponentsByName);
+ mComponentsByName.clear();
+ s_duringClear = false;
+ mParsed = false; // next call to componentList/component will need to run gpgconf again
+}
+
+////
+
+QGpgMECryptoConfigComponent::QGpgMECryptoConfigComponent(QGpgMECryptoConfig *, const QString &name, const QString &description)
+ : mName(name), mDescription(description)
+{
+ runGpgConf();
+}
+
+QGpgMECryptoConfigComponent::~QGpgMECryptoConfigComponent()
+{
+ mGroupsNaturalOrder.clear();
+ qDeleteAll(mGroupsByName);
+ mGroupsByName.clear();
+}
+
+void QGpgMECryptoConfigComponent::runGpgConf()
+{
+ const QString gpgconf = QGpgMECryptoConfig::gpgConfPath();
+ if (gpgconf.isEmpty()) {
+ qCWarning(GPGPME_BACKEND_LOG) << "Can't get path to gpgconf executable...";
+ return;
+ }
+
+ // Run gpgconf --list-options <component>, and create all groups and entries for that component
+ KProcess proc;
+ proc << gpgconf;
+ proc << QStringLiteral("--list-options");
+ proc << mName;
+
+ //qCDebug(GPGPME_BACKEND_LOG) <<"Running gpgconf --list-options" << mName;
+
+ connect(&proc, &KProcess::readyReadStandardOutput, this, &QGpgMECryptoConfigComponent::slotCollectStdOut);
+ mCurrentGroup = 0;
+
+ // run the process:
+ int rc = 0;
+ proc.setOutputChannelMode(KProcess::OnlyStdoutChannel);
+ proc.start();
+ if (!proc.waitForFinished()) {
+ rc = -2;
+ } else if (proc.exitStatus() == QProcess::NormalExit) {
+ rc = proc.exitCode();
+ } else {
+ rc = -1;
+ }
+
+ if (rc != 0) { // can happen when using the wrong version of gpg...
+ qCWarning(GPGPME_BACKEND_LOG) << "Running 'gpgconf --list-options" << mName << "' failed." << strerror(rc) << ", but try that command to see the real output";
+ } else {
+ if (mCurrentGroup && !mCurrentGroup->mEntriesNaturalOrder.empty()) { // only add non-empty groups
+ mGroupsByName.insert(mCurrentGroupName, mCurrentGroup);
+ mGroupsNaturalOrder.push_back(std::make_pair(mCurrentGroupName, mCurrentGroup));
+ }
+ }
+}
+
+void QGpgMECryptoConfigComponent::slotCollectStdOut()
+{
+ assert(qobject_cast<KProcess *>(QObject::sender()));
+ KProcess *const proc = static_cast<KProcess *>(QObject::sender());
+ while (proc->canReadLine()) {
+ QString line = QString::fromUtf8(proc->readLine());
+ if (line.endsWith(QLatin1Char('\n'))) {
+ line.chop(1);
+ }
+ if (line.endsWith(QLatin1Char('\r'))) {
+ line.chop(1);
+ }
+ //qCDebug(GPGPME_BACKEND_LOG) <<"GOT LINE:" << line;
+ // Format: NAME:FLAGS:LEVEL:DESCRIPTION:TYPE:ALT-TYPE:ARGNAME:DEFAULT:ARGDEF:VALUE
+ const QStringList lst = line.split(QLatin1Char(':'));
+ if (lst.count() >= 10) {
+ const int flags = lst[1].toInt();
+ const int level = lst[2].toInt();
+ if (level > 2) { // invisible or internal -> skip it;
+ continue;
+ }
+ if (flags & GPGCONF_FLAG_GROUP) {
+ if (mCurrentGroup && !mCurrentGroup->mEntriesNaturalOrder.empty()) { // only add non-empty groups
+ mGroupsByName.insert(mCurrentGroupName, mCurrentGroup);
+ mGroupsNaturalOrder.push_back(std::make_pair(mCurrentGroupName, mCurrentGroup));
+ }
+ //else
+ // qCDebug(GPGPME_BACKEND_LOG) <<"Discarding empty group" << mCurrentGroupName;
+ mCurrentGroup = new QGpgMECryptoConfigGroup(this, lst[0], lst[3], level);
+ mCurrentGroupName = lst[0];
+ } else {
+ // normal entry
+ if (!mCurrentGroup) { // first toplevel entry -> create toplevel group
+ mCurrentGroup = new QGpgMECryptoConfigGroup(this, QStringLiteral("<nogroup>"), QString(), 0);
+ mCurrentGroupName = QStringLiteral("<nogroup>");
+ }
+ const QString &name = lst[0];
+ QGpgMECryptoConfigEntry *value = new QGpgMECryptoConfigEntry(mCurrentGroup, lst);
+ mCurrentGroup->mEntriesByName.insert(name, value);
+ mCurrentGroup->mEntriesNaturalOrder.push_back(std::make_pair(name, value));
+ }
+ } else {
+ // This happens on lines like
+ // dirmngr[31465]: error opening `/home/dfaure/.gnupg/dirmngr_ldapservers.conf': No such file or directory
+ // so let's not bother the user with it.
+ //qCWarning(GPGPME_BACKEND_LOG) <<"Parse error on gpgconf --list-options output:" << line;
+ }
+ }
+}
+
+QStringList QGpgMECryptoConfigComponent::groupList() const
+{
+ QStringList result;
+ std::transform(mGroupsNaturalOrder.begin(), mGroupsNaturalOrder.end(),
+ std::back_inserter(result), Select1St());
+ return result;
+}
+
+QGpgME::CryptoConfigGroup *QGpgMECryptoConfigComponent::group(const QString &name) const
+{
+ return mGroupsByName.value(name);
+}
+
+void QGpgMECryptoConfigComponent::sync(bool runtime)
+{
+ QTemporaryFile tmpFile;
+ tmpFile.open();
+
+ QList<QGpgMECryptoConfigEntry *> dirtyEntries;
+
+ // Collect all dirty entries
+ const QList<QString> keylist = mGroupsByName.uniqueKeys();
+ Q_FOREACH (const QString &key, keylist) {
+ const QHash<QString, QGpgMECryptoConfigEntry *> entry = mGroupsByName[key]->mEntriesByName;
+ const QList<QString> keylistentry = entry.uniqueKeys();
+ Q_FOREACH (const QString &keyentry, keylistentry) {
+ if (entry[keyentry]->isDirty()) {
+ // OK, we can set it.currentKey() to it.current()->outputString()
+ QString line = keyentry;
+ if (entry[keyentry]->isSet()) { // set option
+ line += QLatin1String(":0:");
+ line += entry[keyentry]->outputString();
+ } else { // unset option
+ line += QLatin1String(":16:");
+ }
+#ifdef Q_OS_WIN
+ line += QLatin1Char('\r');
+#endif
+ line += QLatin1Char('\n');
+ const QByteArray line8bit = line.toUtf8(); // encode with utf8, and K3ProcIO uses utf8 when reading.
+ tmpFile.write(line8bit);
+ dirtyEntries.append(entry[keyentry]);
+
+ }
+ }
+ }
+
+ tmpFile.flush();
+ if (dirtyEntries.isEmpty()) {
+ return;
+ }
+
+ // Call gpgconf --change-options <component>
+ const QString gpgconf = QGpgMECryptoConfig::gpgConfPath();
+ QString commandLine = gpgconf.isEmpty()
+ ? QStringLiteral("gpgconf")
+ : KShell::quoteArg(gpgconf);
+ if (runtime) {
+ commandLine += QLatin1String(" --runtime");
+ }
+ commandLine += QLatin1String(" --change-options ");
+ commandLine += KShell::quoteArg(mName);
+ commandLine += QLatin1String(" < ");
+ commandLine += KShell::quoteArg(tmpFile.fileName());
+
+ //qCDebug(GPGPME_BACKEND_LOG) << commandLine;
+ //system( QCString( "cat " ) + tmpFile.name().toLatin1() ); // DEBUG
+
+ KProcess proc;
+ proc.setShellCommand(commandLine);
+
+ // run the process:
+ int rc = proc.execute();
+
+ if (rc == -2) {
+ QString wmsg = i18n("Could not start gpgconf.\nCheck that gpgconf is in the PATH and that it can be started.");
+ qCWarning(GPGPME_BACKEND_LOG) << wmsg;
+ KMessageBox::error(0, wmsg);
+ } else if (rc != 0) { // Happens due to bugs in gpgconf (e.g. issues 104/115)
+ QString wmsg = i18n("Error from gpgconf while saving configuration: %1", QString::fromLocal8Bit(strerror(rc)));
+ qCWarning(GPGPME_BACKEND_LOG) << ":" << strerror(rc);
+ KMessageBox::error(0, wmsg);
+ } else {
+ QList<QGpgMECryptoConfigEntry *>::const_iterator it = dirtyEntries.constBegin();
+ for (; it != dirtyEntries.constEnd(); ++it) {
+ (*it)->setDirty(false);
+ }
+ }
+}
+
+////
+
+QGpgMECryptoConfigGroup::QGpgMECryptoConfigGroup(QGpgMECryptoConfigComponent *comp, const QString &name, const QString &description, int level)
+ :
+ mComponent(comp),
+ mName(name),
+ mDescription(description),
+ mLevel(static_cast<QGpgME::CryptoConfigEntry::Level>(level))
+{
+}
+
+QGpgMECryptoConfigGroup::~QGpgMECryptoConfigGroup()
+{
+ mEntriesNaturalOrder.clear();
+ qDeleteAll(mEntriesByName);
+ mEntriesByName.clear();
+}
+
+QStringList QGpgMECryptoConfigGroup::entryList() const
+{
+ QStringList result;
+ std::transform(mEntriesNaturalOrder.begin(), mEntriesNaturalOrder.end(),
+ std::back_inserter(result), Select1St());
+ return result;
+}
+
+QGpgME::CryptoConfigEntry *QGpgMECryptoConfigGroup::entry(const QString &name) const
+{
+ return mEntriesByName.value(name);
+}
+
+////
+
+static QString gpgconf_unescape(const QString &str, bool handleComma = true)
+{
+ /* See gpgconf_escape */
+ QString dec(str);
+ dec.replace(QStringLiteral("%25"), QStringLiteral("%"));
+ dec.replace(QStringLiteral("%3a"), QStringLiteral(":"));
+ if (handleComma) {
+ dec.replace(QStringLiteral("%2c"), QStringLiteral(","));
+ }
+ return dec;
+}
+
+static QString gpgconf_escape(const QString &str, bool handleComma = true)
+{
+ /* Gpgconf does not really percent encode. It just
+ * encodes , % and : characters. It expects all other
+ * chars to be UTF-8 encoded.
+ * Except in the Base-DN part where a , may not be percent
+ * escaped.
+ */
+ QString esc(str);
+ esc.replace(QLatin1Char('%'), QStringLiteral("%25"));
+ esc.replace(QLatin1Char(':'), QStringLiteral("%3a"));
+ if (handleComma) {
+ esc.replace(QLatin1Char(','), QStringLiteral("%2c"));
+ }
+ return esc;
+}
+
+static QString urlpart_escape(const QString &str)
+{
+ /* We need to double escape here, as a username or password
+ * or an LDAP Base-DN may contain : or , and in that
+ * case we would break gpgconf's format if we only escaped
+ * the : once. As an escaped : is used internaly to split
+ * the parts of an url. */
+
+ return gpgconf_escape(gpgconf_escape(str, false), false);
+}
+
+static QString urlpart_unescape(const QString &str)
+{
+ /* See urlpart_escape */
+ return gpgconf_unescape(gpgconf_unescape(str, false), false);
+}
+
+// gpgconf arg type number -> CryptoConfigEntry arg type enum mapping
+static QGpgME::CryptoConfigEntry::ArgType knownArgType(int argType, bool &ok)
+{
+ ok = true;
+ switch (argType) {
+ case 0: // none
+ return QGpgME::CryptoConfigEntry::ArgType_None;
+ case 1: // string
+ return QGpgME::CryptoConfigEntry::ArgType_String;
+ case 2: // int32
+ return QGpgME::CryptoConfigEntry::ArgType_Int;
+ case 3: // uint32
+ return QGpgME::CryptoConfigEntry::ArgType_UInt;
+ case 32: // pathname
+ return QGpgME::CryptoConfigEntry::ArgType_Path;
+ case 33: // ldap server
+ return QGpgME::CryptoConfigEntry::ArgType_LDAPURL;
+ default:
+ ok = false;
+ return QGpgME::CryptoConfigEntry::ArgType_None;
+ }
+}
+
+QGpgMECryptoConfigEntry::QGpgMECryptoConfigEntry(QGpgMECryptoConfigGroup *group, const QStringList &parsedLine)
+ : mGroup(group)
+{
+ // Format: NAME:FLAGS:LEVEL:DESCRIPTION:TYPE:ALT-TYPE:ARGNAME:DEFAULT:ARGDEF:VALUE
+ assert(parsedLine.count() >= 10); // called checked for it already
+ QStringList::const_iterator it = parsedLine.constBegin();
+ mName = *it++;
+ mFlags = (*it++).toInt();
+ mLevel = (*it++).toInt();
+ mDescription = *it++;
+ bool ok;
+ // we keep the real (int) arg type, since it influences the parsing (e.g. for ldap urls)
+ mRealArgType = (*it++).toInt();
+ mArgType = knownArgType(mRealArgType, ok);
+ if (!ok && !(*it).isEmpty()) {
+ // use ALT-TYPE
+ mRealArgType = (*it).toInt();
+ mArgType = knownArgType(mRealArgType, ok);
+ }
+ if (!ok) {
+ qCWarning(GPGPME_BACKEND_LOG) << "Unsupported datatype:" << parsedLine[4] << " :" << *it << " for" << parsedLine[0];
+ }
+ ++it; // done with alt-type
+ ++it; // skip argname (not useful in GUIs)
+
+ mSet = false;
+ QString value;
+ if (mFlags & GPGCONF_FLAG_DEFAULT) {
+ value = *it; // get default value
+ mDefaultValue = stringToValue(value, true);
+ }
+ ++it; // done with DEFAULT
+ ++it; // ### skip ARGDEF for now. It's only for options with an "optional arg"
+ //qCDebug(GPGPME_BACKEND_LOG) <<"Entry" << parsedLine[0] <<" val=" << *it;
+
+ if (!(*it).isEmpty()) { // a real value was set
+ mSet = true;
+ value = *it;
+ mValue = stringToValue(value, true);
+ } else {
+ mValue = mDefaultValue;
+ }
+
+ mDirty = false;
+}
+
+QVariant QGpgMECryptoConfigEntry::stringToValue(const QString &str, bool unescape) const
+{
+ const bool isString = isStringType();
+
+ if (isList()) {
+ if (argType() == ArgType_None) {
+ bool ok = true;
+ const QVariant v = str.isEmpty() ? 0U : str.toUInt(&ok);
+ if (!ok) {
+ qCWarning(GPGPME_BACKEND_LOG) << "list-of-none should have an unsigned int as value:" << str;
+ }
+ return v;
+ }
+ QList<QVariant> lst;
+ QStringList items = str.split(QLatin1Char(','), QString::SkipEmptyParts);
+ for (QStringList::const_iterator valit = items.constBegin(); valit != items.constEnd(); ++valit) {
+ QString val = *valit;
+ if (isString) {
+ if (val.isEmpty()) {
+ lst << QVariant(QString());
+ continue;
+ } else if (unescape) {
+ if (val[0] != QLatin1Char('"')) { // see README.gpgconf
+ qCWarning(GPGPME_BACKEND_LOG) << "String value should start with '\"' :" << val;
+ }
+ val = val.mid(1);
+ }
+ }
+ lst << QVariant(unescape ? gpgconf_unescape(val) : val);
+ }
+ return lst;
+ } else { // not a list
+ QString val(str);
+ if (isString) {
+ if (val.isEmpty()) {
+ return QVariant(QString()); // not set [ok with lists too?]
+ } else if (unescape) {
+ if (val[0] != QLatin1Char('"')) { // see README.gpgconf
+ qCWarning(GPGPME_BACKEND_LOG) << "String value should start with '\"' :" << val;
+ }
+ val = val.mid(1);
+ }
+ }
+ return QVariant(unescape ? gpgconf_unescape(val) : val);
+ }
+}
+
+QGpgMECryptoConfigEntry::~QGpgMECryptoConfigEntry()
+{
+#ifndef NDEBUG
+ if (!s_duringClear && mDirty)
+ qCWarning(GPGPME_BACKEND_LOG) << "Deleting a QGpgMECryptoConfigEntry that was modified (" << mDescription << ")"
+ << "You forgot to call sync() (to commit) or clear() (to discard)";
+#endif
+}
+
+bool QGpgMECryptoConfigEntry::isOptional() const
+{
+ return mFlags & GPGCONF_FLAG_OPTIONAL;
+}
+
+bool QGpgMECryptoConfigEntry::isReadOnly() const
+{
+ return mFlags & GPGCONF_FLAG_NO_CHANGE;
+}
+
+bool QGpgMECryptoConfigEntry::isList() const
+{
+ return mFlags & GPGCONF_FLAG_LIST;
+}
+
+bool QGpgMECryptoConfigEntry::isRuntime() const
+{
+ return mFlags & GPGCONF_FLAG_RUNTIME;
+}
+
+bool QGpgMECryptoConfigEntry::isSet() const
+{
+ return mSet;
+}
+
+bool QGpgMECryptoConfigEntry::boolValue() const
+{
+ Q_ASSERT(mArgType == ArgType_None);
+ Q_ASSERT(!isList());
+ return mValue.toBool();
+}
+
+QString QGpgMECryptoConfigEntry::stringValue() const
+{
+ return toString(false);
+}
+
+int QGpgMECryptoConfigEntry::intValue() const
+{
+ Q_ASSERT(mArgType == ArgType_Int);
+ Q_ASSERT(!isList());
+ return mValue.toInt();
+}
+
+unsigned int QGpgMECryptoConfigEntry::uintValue() const
+{
+ Q_ASSERT(mArgType == ArgType_UInt);
+ Q_ASSERT(!isList());
+ return mValue.toUInt();
+}
+
+static QUrl parseURL(int mRealArgType, const QString &str)
+{
+ if (mRealArgType == 33) { // LDAP server
+ // The format is HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN
+ QStringList items = str.split(QLatin1Char(':'));
+ if (items.count() == 5) {
+ QStringList::const_iterator it = items.constBegin();
+ QUrl url;
+ url.setScheme(QStringLiteral("ldap"));
+ url.setHost(gpgconf_unescape(*it++));
+
+ bool ok;
+ const int port = (*it++).toInt(&ok);
+ if (ok) {
+ url.setPort(port);
+ } else if (!it->isEmpty()) {
+ qCWarning(GPGPME_BACKEND_LOG) << "parseURL: malformed LDAP server port, ignoring: \"" << *it << "\"";
+ }
+
+ const QString userName = urlpart_unescape(*it++);
+ if (!userName.isEmpty()) {
+ url.setUserName(userName);
+ }
+ const QString passWord = urlpart_unescape(*it++);
+ if (!passWord.isEmpty()) {
+ url.setPassword(passWord);
+ }
+ url.setQuery(urlpart_unescape(*it));
+ return url;
+ } else {
+ qCWarning(GPGPME_BACKEND_LOG) << "parseURL: malformed LDAP server:" << str;
+ }
+ }
+ // other URLs : assume wellformed URL syntax.
+ return QUrl(str);
+}
+
+// The opposite of parseURL
+static QString splitURL(int mRealArgType, const QUrl &url)
+{
+ if (mRealArgType == 33) { // LDAP server
+ // The format is HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN
+ Q_ASSERT(url.scheme() == QLatin1String("ldap"));
+ return gpgconf_escape(url.host()) + QLatin1Char(':') +
+ (url.port() != -1 ? QString::number(url.port()) : QString()) + QLatin1Char(':') + // -1 is used for default ports, omit
+ urlpart_escape(url.userName()) + QLatin1Char(':') +
+ urlpart_escape(url.password()) + QLatin1Char(':') +
+ urlpart_escape(url.query());
+ }
+ return url.path();
+}
+
+QUrl QGpgMECryptoConfigEntry::urlValue() const
+{
+ Q_ASSERT(mArgType == ArgType_Path || mArgType == ArgType_LDAPURL);
+ Q_ASSERT(!isList());
+ QString str = mValue.toString();
+ if (mArgType == ArgType_Path) {
+ QUrl url = QUrl::fromUserInput(str, QString(), QUrl::AssumeLocalFile);
+ return url;
+ }
+ return parseURL(mRealArgType, str);
+}
+
+unsigned int QGpgMECryptoConfigEntry::numberOfTimesSet() const
+{
+ Q_ASSERT(mArgType == ArgType_None);
+ Q_ASSERT(isList());
+ return mValue.toUInt();
+}
+
+std::vector<int> QGpgMECryptoConfigEntry::intValueList() const
+{
+ Q_ASSERT(mArgType == ArgType_Int);
+ Q_ASSERT(isList());
+ std::vector<int> ret;
+ QList<QVariant> lst = mValue.toList();
+ ret.reserve(lst.size());
+ for (QList<QVariant>::const_iterator it = lst.constBegin(); it != lst.constEnd(); ++it) {
+ ret.push_back((*it).toInt());
+ }
+ return ret;
+}
+
+std::vector<unsigned int> QGpgMECryptoConfigEntry::uintValueList() const
+{
+ Q_ASSERT(mArgType == ArgType_UInt);
+ Q_ASSERT(isList());
+ std::vector<unsigned int> ret;
+ QList<QVariant> lst = mValue.toList();
+ ret.reserve(lst.size());
+ for (QList<QVariant>::const_iterator it = lst.constBegin(); it != lst.constEnd(); ++it) {
+ ret.push_back((*it).toUInt());
+ }
+ return ret;
+}
+
+QList<QUrl> QGpgMECryptoConfigEntry::urlValueList() const
+{
+ Q_ASSERT(mArgType == ArgType_Path || mArgType == ArgType_LDAPURL);
+ Q_ASSERT(isList());
+ QStringList lst = mValue.toStringList();
+
+ QList<QUrl> ret;
+ for (QStringList::const_iterator it = lst.constBegin(); it != lst.constEnd(); ++it) {
+ if (mArgType == ArgType_Path) {
+ QUrl url = QUrl::fromUserInput(*it, QString(), QUrl::AssumeLocalFile);
+ } else {
+ ret << parseURL(mRealArgType, *it);
+ }
+ }
+ return ret;
+}
+
+void QGpgMECryptoConfigEntry::resetToDefault()
+{
+ mSet = false;
+ mDirty = true;
+ if (mFlags & GPGCONF_FLAG_DEFAULT) {
+ mValue = mDefaultValue;
+ } else if (mArgType == ArgType_None) {
+ if (isList()) {
+ mValue = 0U;
+ } else {
+ mValue = false;
+ }
+ }
+}
+
+void QGpgMECryptoConfigEntry::setBoolValue(bool b)
+{
+ Q_ASSERT(mArgType == ArgType_None);
+ Q_ASSERT(!isList());
+ // A "no arg" option is either set or not set.
+ // Being set means mSet==true + mValue==true, being unset means resetToDefault(), i.e. both false
+ mValue = b;
+ mSet = b;
+ mDirty = true;
+}
+
+void QGpgMECryptoConfigEntry::setStringValue(const QString &str)
+{
+ mValue = stringToValue(str, false);
+ // When setting a string to empty (and there's no default), we need to act like resetToDefault
+ // Otherwise we try e.g. "ocsp-responder:0:" and gpgconf answers:
+ // "gpgconf: argument required for option ocsp-responder"
+ if (str.isEmpty() && !isOptional()) {
+ mSet = false;
+ } else {
+ mSet = true;
+ }
+ mDirty = true;
+}
+
+void QGpgMECryptoConfigEntry::setIntValue(int i)
+{
+ Q_ASSERT(mArgType == ArgType_Int);
+ Q_ASSERT(!isList());
+ mValue = i;
+ mSet = true;
+ mDirty = true;
+}
+
+void QGpgMECryptoConfigEntry::setUIntValue(unsigned int i)
+{
+ mValue = i;
+ mSet = true;
+ mDirty = true;
+}
+
+void QGpgMECryptoConfigEntry::setURLValue(const QUrl &url)
+{
+ QString str = splitURL(mRealArgType, url);
+ if (str.isEmpty() && !isOptional()) {
+ mSet = false;
+ } else {
+ mSet = true;
+ }
+ mValue = str;
+ mDirty = true;
+}
+
+void QGpgMECryptoConfigEntry::setNumberOfTimesSet(unsigned int i)
+{
+ Q_ASSERT(mArgType == ArgType_None);
+ Q_ASSERT(isList());
+ mValue = i;
+ mSet = i > 0;
+ mDirty = true;
+}
+
+void QGpgMECryptoConfigEntry::setIntValueList(const std::vector<int> &lst)
+{
+ QList<QVariant> ret;
+ for (std::vector<int>::const_iterator it = lst.begin(); it != lst.end(); ++it) {
+ ret << QVariant(*it);
+ }
+ mValue = ret;
+ if (ret.isEmpty() && !isOptional()) {
+ mSet = false;
+ } else {
+ mSet = true;
+ }
+ mDirty = true;
+}
+
+void QGpgMECryptoConfigEntry::setUIntValueList(const std::vector<unsigned int> &lst)
+{
+ QList<QVariant> ret;
+ for (std::vector<unsigned int>::const_iterator it = lst.begin(); it != lst.end(); ++it) {
+ ret << QVariant(*it);
+ }
+ if (ret.isEmpty() && !isOptional()) {
+ mSet = false;
+ } else {
+ mSet = true;
+ }
+ mValue = ret;
+ mDirty = true;
+}
+
+void QGpgMECryptoConfigEntry::setURLValueList(const QList<QUrl> &urls)
+{
+ QStringList lst;
+ for (QList<QUrl>::const_iterator it = urls.constBegin(); it != urls.constEnd(); ++it) {
+ lst << splitURL(mRealArgType, *it);
+ }
+ mValue = lst;
+ if (lst.isEmpty() && !isOptional()) {
+ mSet = false;
+ } else {
+ mSet = true;
+ }
+ mDirty = true;
+}
+
+QString QGpgMECryptoConfigEntry::toString(bool escape) const
+{
+ // Basically the opposite of stringToValue
+ if (isStringType()) {
+ if (mValue.isNull()) {
+ return QString();
+ } else if (isList()) { // string list
+ QStringList lst = mValue.toStringList();
+ if (escape) {
+ for (QStringList::iterator it = lst.begin(); it != lst.end(); ++it) {
+ if (!(*it).isNull()) {
+ *it = gpgconf_escape(*it).prepend(QLatin1String("\""));
+ }
+ }
+ }
+ const QString res = lst.join(QStringLiteral(","));
+ //qCDebug(GPGPME_BACKEND_LOG) <<"toString:" << res;
+ return res;
+ } else { // normal string
+ QString res = mValue.toString();
+ if (escape) {
+ res = gpgconf_escape(res).prepend(QLatin1String("\""));
+ }
+ return res;
+ }
+ }
+ if (!isList()) { // non-list non-string
+ if (mArgType == ArgType_None) {
+ return mValue.toBool() ? QStringLiteral("1") : QString();
+ } else { // some int
+ Q_ASSERT(mArgType == ArgType_Int || mArgType == ArgType_UInt);
+ return mValue.toString(); // int to string conversion
+ }
+ }
+
+ // Lists (of other types than strings)
+ if (mArgType == ArgType_None) {
+ return QString::number(numberOfTimesSet());
+ }
+
+ QStringList ret;
+ QList<QVariant> lst = mValue.toList();
+ for (QList<QVariant>::const_iterator it = lst.constBegin(); it != lst.constEnd(); ++it) {
+ ret << (*it).toString(); // QVariant does the conversion
+ }
+ return ret.join(QStringLiteral(","));
+}
+
+QString QGpgMECryptoConfigEntry::outputString() const
+{
+ Q_ASSERT(mSet);
+ return toString(true);
+}
+
+bool QGpgMECryptoConfigEntry::isStringType() const
+{
+ return (mArgType == QGpgME::CryptoConfigEntry::ArgType_String
+ || mArgType == QGpgME::CryptoConfigEntry::ArgType_Path
+ || mArgType == QGpgME::CryptoConfigEntry::ArgType_LDAPURL);
+}
+
+void QGpgMECryptoConfigEntry::setDirty(bool b)
+{
+ mDirty = b;
+}