Add QGpgME code from libkleo
[gpgme.git] / lang / qt / src / qgpgmecryptoconfig.cpp
1 /*
2     qgpgmecryptoconfig.cpp
3
4     This file is part of qgpgme, the Qt API binding for gpgme
5     Copyright (c) 2004 Klarälvdalens Datakonsult AB
6     Copyright (c) 2016 Intevation GmbH
7
8     Libkleopatra is free software; you can redistribute it and/or
9     modify it under the terms of the GNU General Public License as
10     published by the Free Software Foundation; either version 2 of the
11     License, or (at your option) any later version.
12
13     Libkleopatra is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16     General Public License for more details.
17
18     You should have received a copy of the GNU General Public License
19     along with this program; if not, write to the Free Software
20     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21
22     In addition, as a special exception, the copyright holders give
23     permission to link the code of this program with any edition of
24     the Qt library by Trolltech AS, Norway (or with modified versions
25     of Qt that use the same license as Qt), and distribute linked
26     combinations including the two.  You must obey the GNU General
27     Public License in all respects for all of the code used other than
28     Qt.  If you modify this file, you may extend this exception to
29     your version of the file, but you are not obligated to do so.  If
30     you do not wish to do so, delete this exception statement from
31     your version.
32 */
33
34 #include "qgpgmecryptoconfig.h"
35
36 #include <QList>
37 #include <QByteArray>
38 #include <errno.h>
39 #include "gpgme_backend_debug.h"
40
41 #include "engineinfo.h"
42 #include "global.h"
43
44 #include <cassert>
45 #include <QTemporaryFile>
46 #include <QFile>
47 #include <cstdlib>
48 #include <iterator>
49 #include <QStandardPaths>
50
51 // Just for the Q_ASSERT in the dtor. Not thread-safe, but who would
52 // have 2 threads talking to gpgconf anyway? :)
53 static bool s_duringClear = false;
54
55 static const int GPGCONF_FLAG_GROUP = 1;
56 static const int GPGCONF_FLAG_OPTIONAL = 2;
57 static const int GPGCONF_FLAG_LIST = 4;
58 static const int GPGCONF_FLAG_RUNTIME = 8;
59 static const int GPGCONF_FLAG_DEFAULT = 16; // fixed default value available
60 //static const int GPGCONF_FLAG_DEFAULT_DESC = 32; // runtime default value available
61 //static const int GPGCONF_FLAG_NOARG_DESC = 64; // option with optional arg; special meaning if no arg set
62 static const int GPGCONF_FLAG_NO_CHANGE = 128; // readonly
63 // Change size of mFlags bitfield if adding new values here
64
65 QString QGpgMECryptoConfig::gpgConfPath()
66 {
67     const GpgME::EngineInfo info = GpgME::engineInfo(GpgME::GpgConfEngine);
68     return info.fileName() ? QFile::decodeName(info.fileName()) : QStandardPaths::findExecutable(QStringLiteral("gpgconf"));
69 }
70
71 QGpgMECryptoConfig::QGpgMECryptoConfig()
72     :  mParsed(false)
73 {
74 }
75
76 QGpgMECryptoConfig::~QGpgMECryptoConfig()
77 {
78     clear();
79 }
80
81 void QGpgMECryptoConfig::runGpgConf(bool showErrors)
82 {
83     // Run gpgconf --list-components to make the list of components
84     KProcess process;
85
86     process << gpgConfPath();
87     process << QStringLiteral("--list-components");
88
89     connect(&process, &KProcess::readyReadStandardOutput, this, &QGpgMECryptoConfig::slotCollectStdOut);
90
91     // run the process:
92     int rc = 0;
93     process.setOutputChannelMode(KProcess::OnlyStdoutChannel);
94     process.start();
95     if (!process.waitForFinished()) {
96         rc = -2;
97     } else if (process.exitStatus() == QProcess::NormalExit) {
98         rc = process.exitCode();
99     } else {
100         rc = -1;
101     }
102
103     // handle errors, if any (and if requested)
104     if (showErrors && rc != 0) {
105         QString reason;
106         if (rc == -1) {
107             reason = i18n("program terminated unexpectedly");
108         } else if (rc == -2) {
109             reason = i18n("program not found or cannot be started");
110         } else {
111             reason = QString::fromLocal8Bit(strerror(rc));    // XXX errno as an exit code?
112         }
113         QString wmsg = i18n("<qt>Failed to execute gpgconf:<p>%1</p></qt>", reason);
114         qCWarning(GPGPME_BACKEND_LOG) << wmsg; // to see it from test_cryptoconfig.cpp
115         KMessageBox::error(0, wmsg);
116     }
117     mParsed = true;
118 }
119
120 void QGpgMECryptoConfig::slotCollectStdOut()
121 {
122     assert(qobject_cast<KProcess *>(QObject::sender()));
123     KProcess *const proc = static_cast<KProcess *>(QObject::sender());
124     while (proc->canReadLine()) {
125         QString line = QString::fromUtf8(proc->readLine());
126         if (line.endsWith(QLatin1Char('\n'))) {
127             line.chop(1);
128         }
129         if (line.endsWith(QLatin1Char('\r'))) {
130             line.chop(1);
131         }
132         //qCDebug(GPGPME_BACKEND_LOG) <<"GOT LINE:" << line;
133         // Format: NAME:DESCRIPTION
134         const QStringList lst = line.split(QLatin1Char(':'));
135         if (lst.count() >= 2) {
136             const std::pair<QString, QGpgMECryptoConfigComponent *> pair(lst[0], new QGpgMECryptoConfigComponent(this, lst[0], lst[1]));
137             mComponentsNaturalOrder.push_back(pair);
138             mComponentsByName[pair.first] = pair.second;
139         } else {
140             qCWarning(GPGPME_BACKEND_LOG) << "Parse error on gpgconf --list-components output:" << line;
141         }
142     }
143 }
144
145 namespace
146 {
147 struct Select1St {
148     template <typename U, typename V>
149     const U &operator()(const std::pair<U, V> &p) const
150     {
151         return p.first;
152     }
153     template <typename U, typename V>
154     const U &operator()(const QPair<U, V> &p) const
155     {
156         return p.first;
157     }
158 };
159 }
160
161 QStringList QGpgMECryptoConfig::componentList() const
162 {
163     if (!mParsed) {
164         const_cast<QGpgMECryptoConfig *>(this)->runGpgConf(true);
165     }
166     QStringList result;
167     std::transform(mComponentsNaturalOrder.begin(), mComponentsNaturalOrder.end(),
168                    std::back_inserter(result), Select1St());
169     return result;
170 }
171
172 QGpgME::CryptoConfigComponent *QGpgMECryptoConfig::component(const QString &name) const
173 {
174     if (!mParsed) {
175         const_cast<QGpgMECryptoConfig *>(this)->runGpgConf(false);
176     }
177     return mComponentsByName.value(name);
178 }
179
180 void QGpgMECryptoConfig::sync(bool runtime)
181 {
182     Q_FOREACH (QGpgMECryptoConfigComponent *it, mComponentsByName) {
183         it->sync(runtime);
184     }
185 }
186
187 void QGpgMECryptoConfig::clear()
188 {
189     s_duringClear = true;
190     mComponentsNaturalOrder.clear();
191     qDeleteAll(mComponentsByName);
192     mComponentsByName.clear();
193     s_duringClear = false;
194     mParsed = false; // next call to componentList/component will need to run gpgconf again
195 }
196
197 ////
198
199 QGpgMECryptoConfigComponent::QGpgMECryptoConfigComponent(QGpgMECryptoConfig *, const QString &name, const QString &description)
200     : mName(name), mDescription(description)
201 {
202     runGpgConf();
203 }
204
205 QGpgMECryptoConfigComponent::~QGpgMECryptoConfigComponent()
206 {
207     mGroupsNaturalOrder.clear();
208     qDeleteAll(mGroupsByName);
209     mGroupsByName.clear();
210 }
211
212 void QGpgMECryptoConfigComponent::runGpgConf()
213 {
214     const QString gpgconf = QGpgMECryptoConfig::gpgConfPath();
215     if (gpgconf.isEmpty()) {
216         qCWarning(GPGPME_BACKEND_LOG) << "Can't get path to gpgconf executable...";
217         return;
218     }
219
220     // Run gpgconf --list-options <component>, and create all groups and entries for that component
221     KProcess proc;
222     proc << gpgconf;
223     proc << QStringLiteral("--list-options");
224     proc << mName;
225
226     //qCDebug(GPGPME_BACKEND_LOG) <<"Running gpgconf --list-options" << mName;
227
228     connect(&proc, &KProcess::readyReadStandardOutput, this, &QGpgMECryptoConfigComponent::slotCollectStdOut);
229     mCurrentGroup = 0;
230
231     // run the process:
232     int rc = 0;
233     proc.setOutputChannelMode(KProcess::OnlyStdoutChannel);
234     proc.start();
235     if (!proc.waitForFinished()) {
236         rc = -2;
237     } else if (proc.exitStatus() == QProcess::NormalExit) {
238         rc = proc.exitCode();
239     } else {
240         rc = -1;
241     }
242
243     if (rc != 0) { // can happen when using the wrong version of gpg...
244         qCWarning(GPGPME_BACKEND_LOG) << "Running 'gpgconf --list-options" << mName << "' failed." << strerror(rc) << ", but try that command to see the real output";
245     } else {
246         if (mCurrentGroup && !mCurrentGroup->mEntriesNaturalOrder.empty()) {   // only add non-empty groups
247             mGroupsByName.insert(mCurrentGroupName, mCurrentGroup);
248             mGroupsNaturalOrder.push_back(std::make_pair(mCurrentGroupName, mCurrentGroup));
249         }
250     }
251 }
252
253 void QGpgMECryptoConfigComponent::slotCollectStdOut()
254 {
255     assert(qobject_cast<KProcess *>(QObject::sender()));
256     KProcess *const proc = static_cast<KProcess *>(QObject::sender());
257     while (proc->canReadLine()) {
258         QString line = QString::fromUtf8(proc->readLine());
259         if (line.endsWith(QLatin1Char('\n'))) {
260             line.chop(1);
261         }
262         if (line.endsWith(QLatin1Char('\r'))) {
263             line.chop(1);
264         }
265         //qCDebug(GPGPME_BACKEND_LOG) <<"GOT LINE:" << line;
266         // Format: NAME:FLAGS:LEVEL:DESCRIPTION:TYPE:ALT-TYPE:ARGNAME:DEFAULT:ARGDEF:VALUE
267         const QStringList lst = line.split(QLatin1Char(':'));
268         if (lst.count() >= 10) {
269             const int flags = lst[1].toInt();
270             const int level = lst[2].toInt();
271             if (level > 2) { // invisible or internal -> skip it;
272                 continue;
273             }
274             if (flags & GPGCONF_FLAG_GROUP) {
275                 if (mCurrentGroup && !mCurrentGroup->mEntriesNaturalOrder.empty()) {   // only add non-empty groups
276                     mGroupsByName.insert(mCurrentGroupName, mCurrentGroup);
277                     mGroupsNaturalOrder.push_back(std::make_pair(mCurrentGroupName, mCurrentGroup));
278                 }
279                 //else
280                 //  qCDebug(GPGPME_BACKEND_LOG) <<"Discarding empty group" << mCurrentGroupName;
281                 mCurrentGroup = new QGpgMECryptoConfigGroup(this, lst[0], lst[3], level);
282                 mCurrentGroupName = lst[0];
283             } else {
284                 // normal entry
285                 if (!mCurrentGroup) {    // first toplevel entry -> create toplevel group
286                     mCurrentGroup = new QGpgMECryptoConfigGroup(this, QStringLiteral("<nogroup>"), QString(), 0);
287                     mCurrentGroupName = QStringLiteral("<nogroup>");
288                 }
289                 const QString &name = lst[0];
290                 QGpgMECryptoConfigEntry *value = new QGpgMECryptoConfigEntry(mCurrentGroup, lst);
291                 mCurrentGroup->mEntriesByName.insert(name, value);
292                 mCurrentGroup->mEntriesNaturalOrder.push_back(std::make_pair(name, value));
293             }
294         } else {
295             // This happens on lines like
296             // dirmngr[31465]: error opening `/home/dfaure/.gnupg/dirmngr_ldapservers.conf': No such file or directory
297             // so let's not bother the user with it.
298             //qCWarning(GPGPME_BACKEND_LOG) <<"Parse error on gpgconf --list-options output:" << line;
299         }
300     }
301 }
302
303 QStringList QGpgMECryptoConfigComponent::groupList() const
304 {
305     QStringList result;
306     std::transform(mGroupsNaturalOrder.begin(), mGroupsNaturalOrder.end(),
307                    std::back_inserter(result), Select1St());
308     return result;
309 }
310
311 QGpgME::CryptoConfigGroup *QGpgMECryptoConfigComponent::group(const QString &name) const
312 {
313     return mGroupsByName.value(name);
314 }
315
316 void QGpgMECryptoConfigComponent::sync(bool runtime)
317 {
318     QTemporaryFile tmpFile;
319     tmpFile.open();
320
321     QList<QGpgMECryptoConfigEntry *> dirtyEntries;
322
323     // Collect all dirty entries
324     const QList<QString> keylist = mGroupsByName.uniqueKeys();
325     Q_FOREACH (const QString &key, keylist) {
326         const QHash<QString, QGpgMECryptoConfigEntry *> entry = mGroupsByName[key]->mEntriesByName;
327         const QList<QString> keylistentry = entry.uniqueKeys();
328         Q_FOREACH (const QString &keyentry, keylistentry) {
329             if (entry[keyentry]->isDirty()) {
330                 // OK, we can set it.currentKey() to it.current()->outputString()
331                 QString line = keyentry;
332                 if (entry[keyentry]->isSet()) {   // set option
333                     line += QLatin1String(":0:");
334                     line += entry[keyentry]->outputString();
335                 } else {                       // unset option
336                     line += QLatin1String(":16:");
337                 }
338 #ifdef Q_OS_WIN
339                 line += QLatin1Char('\r');
340 #endif
341                 line += QLatin1Char('\n');
342                 const QByteArray line8bit = line.toUtf8(); // encode with utf8, and K3ProcIO uses utf8 when reading.
343                 tmpFile.write(line8bit);
344                 dirtyEntries.append(entry[keyentry]);
345
346             }
347         }
348     }
349
350     tmpFile.flush();
351     if (dirtyEntries.isEmpty()) {
352         return;
353     }
354
355     // Call gpgconf --change-options <component>
356     const QString gpgconf = QGpgMECryptoConfig::gpgConfPath();
357     QString commandLine = gpgconf.isEmpty()
358                           ? QStringLiteral("gpgconf")
359                           : KShell::quoteArg(gpgconf);
360     if (runtime) {
361         commandLine += QLatin1String(" --runtime");
362     }
363     commandLine += QLatin1String(" --change-options ");
364     commandLine += KShell::quoteArg(mName);
365     commandLine += QLatin1String(" < ");
366     commandLine += KShell::quoteArg(tmpFile.fileName());
367
368     //qCDebug(GPGPME_BACKEND_LOG) << commandLine;
369     //system( QCString( "cat " ) + tmpFile.name().toLatin1() ); // DEBUG
370
371     KProcess proc;
372     proc.setShellCommand(commandLine);
373
374     // run the process:
375     int rc = proc.execute();
376
377     if (rc == -2) {
378         QString wmsg = i18n("Could not start gpgconf.\nCheck that gpgconf is in the PATH and that it can be started.");
379         qCWarning(GPGPME_BACKEND_LOG) << wmsg;
380         KMessageBox::error(0, wmsg);
381     } else if (rc != 0) { // Happens due to bugs in gpgconf (e.g. issues 104/115)
382         QString wmsg = i18n("Error from gpgconf while saving configuration: %1", QString::fromLocal8Bit(strerror(rc)));
383         qCWarning(GPGPME_BACKEND_LOG) << ":" << strerror(rc);
384         KMessageBox::error(0, wmsg);
385     } else {
386         QList<QGpgMECryptoConfigEntry *>::const_iterator it = dirtyEntries.constBegin();
387         for (; it != dirtyEntries.constEnd(); ++it) {
388             (*it)->setDirty(false);
389         }
390     }
391 }
392
393 ////
394
395 QGpgMECryptoConfigGroup::QGpgMECryptoConfigGroup(QGpgMECryptoConfigComponent *comp, const QString &name, const QString &description, int level)
396     :
397     mComponent(comp),
398     mName(name),
399     mDescription(description),
400     mLevel(static_cast<QGpgME::CryptoConfigEntry::Level>(level))
401 {
402 }
403
404 QGpgMECryptoConfigGroup::~QGpgMECryptoConfigGroup()
405 {
406     mEntriesNaturalOrder.clear();
407     qDeleteAll(mEntriesByName);
408     mEntriesByName.clear();
409 }
410
411 QStringList QGpgMECryptoConfigGroup::entryList() const
412 {
413     QStringList result;
414     std::transform(mEntriesNaturalOrder.begin(), mEntriesNaturalOrder.end(),
415                    std::back_inserter(result), Select1St());
416     return result;
417 }
418
419 QGpgME::CryptoConfigEntry *QGpgMECryptoConfigGroup::entry(const QString &name) const
420 {
421     return mEntriesByName.value(name);
422 }
423
424 ////
425
426 static QString gpgconf_unescape(const QString &str, bool handleComma = true)
427 {
428     /* See gpgconf_escape */
429     QString dec(str);
430     dec.replace(QStringLiteral("%25"), QStringLiteral("%"));
431     dec.replace(QStringLiteral("%3a"), QStringLiteral(":"));
432     if (handleComma) {
433         dec.replace(QStringLiteral("%2c"), QStringLiteral(","));
434     }
435     return dec;
436 }
437
438 static QString gpgconf_escape(const QString &str, bool handleComma = true)
439 {
440     /* Gpgconf does not really percent encode. It just
441      * encodes , % and : characters. It expects all other
442      * chars to be UTF-8 encoded.
443      * Except in the Base-DN part where a , may not be percent
444      * escaped.
445      */
446     QString esc(str);
447     esc.replace(QLatin1Char('%'), QStringLiteral("%25"));
448     esc.replace(QLatin1Char(':'), QStringLiteral("%3a"));
449     if (handleComma) {
450         esc.replace(QLatin1Char(','), QStringLiteral("%2c"));
451     }
452     return esc;
453 }
454
455 static QString urlpart_escape(const QString &str)
456 {
457     /* We need to double escape here, as a username or password
458      * or an LDAP Base-DN may contain : or , and in that
459      * case we would break gpgconf's format if we only escaped
460      * the : once. As an escaped : is used internaly to split
461      * the parts of an url. */
462
463     return gpgconf_escape(gpgconf_escape(str, false), false);
464 }
465
466 static QString urlpart_unescape(const QString &str)
467 {
468     /* See urlpart_escape */
469     return gpgconf_unescape(gpgconf_unescape(str, false), false);
470 }
471
472 // gpgconf arg type number -> CryptoConfigEntry arg type enum mapping
473 static QGpgME::CryptoConfigEntry::ArgType knownArgType(int argType, bool &ok)
474 {
475     ok = true;
476     switch (argType) {
477     case 0: // none
478         return QGpgME::CryptoConfigEntry::ArgType_None;
479     case 1: // string
480         return QGpgME::CryptoConfigEntry::ArgType_String;
481     case 2: // int32
482         return QGpgME::CryptoConfigEntry::ArgType_Int;
483     case 3: // uint32
484         return QGpgME::CryptoConfigEntry::ArgType_UInt;
485     case 32: // pathname
486         return QGpgME::CryptoConfigEntry::ArgType_Path;
487     case 33: // ldap server
488         return QGpgME::CryptoConfigEntry::ArgType_LDAPURL;
489     default:
490         ok = false;
491         return QGpgME::CryptoConfigEntry::ArgType_None;
492     }
493 }
494
495 QGpgMECryptoConfigEntry::QGpgMECryptoConfigEntry(QGpgMECryptoConfigGroup *group, const QStringList &parsedLine)
496     : mGroup(group)
497 {
498     // Format: NAME:FLAGS:LEVEL:DESCRIPTION:TYPE:ALT-TYPE:ARGNAME:DEFAULT:ARGDEF:VALUE
499     assert(parsedLine.count() >= 10);   // called checked for it already
500     QStringList::const_iterator it = parsedLine.constBegin();
501     mName = *it++;
502     mFlags = (*it++).toInt();
503     mLevel = (*it++).toInt();
504     mDescription = *it++;
505     bool ok;
506     // we keep the real (int) arg type, since it influences the parsing (e.g. for ldap urls)
507     mRealArgType = (*it++).toInt();
508     mArgType = knownArgType(mRealArgType, ok);
509     if (!ok && !(*it).isEmpty()) {
510         // use ALT-TYPE
511         mRealArgType = (*it).toInt();
512         mArgType = knownArgType(mRealArgType, ok);
513     }
514     if (!ok) {
515         qCWarning(GPGPME_BACKEND_LOG) << "Unsupported datatype:" << parsedLine[4] << " :" << *it << " for" << parsedLine[0];
516     }
517     ++it; // done with alt-type
518     ++it; // skip argname (not useful in GUIs)
519
520     mSet = false;
521     QString value;
522     if (mFlags & GPGCONF_FLAG_DEFAULT) {
523         value = *it; // get default value
524         mDefaultValue = stringToValue(value, true);
525     }
526     ++it; // done with DEFAULT
527     ++it; // ### skip ARGDEF for now. It's only for options with an "optional arg"
528     //qCDebug(GPGPME_BACKEND_LOG) <<"Entry" << parsedLine[0] <<" val=" << *it;
529
530     if (!(*it).isEmpty()) {    // a real value was set
531         mSet = true;
532         value = *it;
533         mValue = stringToValue(value, true);
534     } else {
535         mValue = mDefaultValue;
536     }
537
538     mDirty = false;
539 }
540
541 QVariant QGpgMECryptoConfigEntry::stringToValue(const QString &str, bool unescape) const
542 {
543     const bool isString = isStringType();
544
545     if (isList()) {
546         if (argType() == ArgType_None) {
547             bool ok = true;
548             const QVariant v = str.isEmpty() ? 0U : str.toUInt(&ok);
549             if (!ok) {
550                 qCWarning(GPGPME_BACKEND_LOG) << "list-of-none should have an unsigned int as value:" << str;
551             }
552             return v;
553         }
554         QList<QVariant> lst;
555         QStringList items = str.split(QLatin1Char(','), QString::SkipEmptyParts);
556         for (QStringList::const_iterator valit = items.constBegin(); valit != items.constEnd(); ++valit) {
557             QString val = *valit;
558             if (isString) {
559                 if (val.isEmpty()) {
560                     lst << QVariant(QString());
561                     continue;
562                 } else if (unescape) {
563                     if (val[0] != QLatin1Char('"')) { // see README.gpgconf
564                         qCWarning(GPGPME_BACKEND_LOG) << "String value should start with '\"' :" << val;
565                     }
566                     val = val.mid(1);
567                 }
568             }
569             lst << QVariant(unescape ? gpgconf_unescape(val) : val);
570         }
571         return lst;
572     } else { // not a list
573         QString val(str);
574         if (isString) {
575             if (val.isEmpty()) {
576                 return QVariant(QString());    // not set  [ok with lists too?]
577             } else if (unescape) {
578                 if (val[0] != QLatin1Char('"')) { // see README.gpgconf
579                     qCWarning(GPGPME_BACKEND_LOG) << "String value should start with '\"' :" << val;
580                 }
581                 val = val.mid(1);
582             }
583         }
584         return QVariant(unescape ? gpgconf_unescape(val) : val);
585     }
586 }
587
588 QGpgMECryptoConfigEntry::~QGpgMECryptoConfigEntry()
589 {
590 #ifndef NDEBUG
591     if (!s_duringClear && mDirty)
592         qCWarning(GPGPME_BACKEND_LOG) << "Deleting a QGpgMECryptoConfigEntry that was modified (" << mDescription << ")"
593                                       << "You forgot to call sync() (to commit) or clear() (to discard)";
594 #endif
595 }
596
597 bool QGpgMECryptoConfigEntry::isOptional() const
598 {
599     return mFlags & GPGCONF_FLAG_OPTIONAL;
600 }
601
602 bool QGpgMECryptoConfigEntry::isReadOnly() const
603 {
604     return mFlags & GPGCONF_FLAG_NO_CHANGE;
605 }
606
607 bool QGpgMECryptoConfigEntry::isList() const
608 {
609     return mFlags & GPGCONF_FLAG_LIST;
610 }
611
612 bool QGpgMECryptoConfigEntry::isRuntime() const
613 {
614     return mFlags & GPGCONF_FLAG_RUNTIME;
615 }
616
617 bool QGpgMECryptoConfigEntry::isSet() const
618 {
619     return mSet;
620 }
621
622 bool QGpgMECryptoConfigEntry::boolValue() const
623 {
624     Q_ASSERT(mArgType == ArgType_None);
625     Q_ASSERT(!isList());
626     return mValue.toBool();
627 }
628
629 QString QGpgMECryptoConfigEntry::stringValue() const
630 {
631     return toString(false);
632 }
633
634 int QGpgMECryptoConfigEntry::intValue() const
635 {
636     Q_ASSERT(mArgType == ArgType_Int);
637     Q_ASSERT(!isList());
638     return mValue.toInt();
639 }
640
641 unsigned int QGpgMECryptoConfigEntry::uintValue() const
642 {
643     Q_ASSERT(mArgType == ArgType_UInt);
644     Q_ASSERT(!isList());
645     return mValue.toUInt();
646 }
647
648 static QUrl parseURL(int mRealArgType, const QString &str)
649 {
650     if (mRealArgType == 33) {   // LDAP server
651         // The format is HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN
652         QStringList items = str.split(QLatin1Char(':'));
653         if (items.count() == 5) {
654             QStringList::const_iterator it = items.constBegin();
655             QUrl url;
656             url.setScheme(QStringLiteral("ldap"));
657             url.setHost(gpgconf_unescape(*it++));
658
659             bool ok;
660             const int port = (*it++).toInt(&ok);
661             if (ok) {
662                 url.setPort(port);
663             } else if (!it->isEmpty()) {
664                 qCWarning(GPGPME_BACKEND_LOG) << "parseURL: malformed LDAP server port, ignoring: \"" << *it << "\"";
665             }
666
667             const QString userName = urlpart_unescape(*it++);
668             if (!userName.isEmpty()) {
669                 url.setUserName(userName);
670             }
671             const QString passWord = urlpart_unescape(*it++);
672             if (!passWord.isEmpty()) {
673                 url.setPassword(passWord);
674             }
675             url.setQuery(urlpart_unescape(*it));
676             return url;
677         } else {
678             qCWarning(GPGPME_BACKEND_LOG) << "parseURL: malformed LDAP server:" << str;
679         }
680     }
681     // other URLs : assume wellformed URL syntax.
682     return QUrl(str);
683 }
684
685 // The opposite of parseURL
686 static QString splitURL(int mRealArgType, const QUrl &url)
687 {
688     if (mRealArgType == 33) {   // LDAP server
689         // The format is HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN
690         Q_ASSERT(url.scheme() == QLatin1String("ldap"));
691         return gpgconf_escape(url.host()) + QLatin1Char(':') +
692                (url.port() != -1 ? QString::number(url.port()) : QString()) + QLatin1Char(':') +     // -1 is used for default ports, omit
693                urlpart_escape(url.userName()) + QLatin1Char(':') +
694                urlpart_escape(url.password()) + QLatin1Char(':') +
695                urlpart_escape(url.query());
696     }
697     return url.path();
698 }
699
700 QUrl QGpgMECryptoConfigEntry::urlValue() const
701 {
702     Q_ASSERT(mArgType == ArgType_Path || mArgType == ArgType_LDAPURL);
703     Q_ASSERT(!isList());
704     QString str = mValue.toString();
705     if (mArgType == ArgType_Path) {
706         QUrl url = QUrl::fromUserInput(str, QString(), QUrl::AssumeLocalFile);
707         return url;
708     }
709     return parseURL(mRealArgType, str);
710 }
711
712 unsigned int QGpgMECryptoConfigEntry::numberOfTimesSet() const
713 {
714     Q_ASSERT(mArgType == ArgType_None);
715     Q_ASSERT(isList());
716     return mValue.toUInt();
717 }
718
719 std::vector<int> QGpgMECryptoConfigEntry::intValueList() const
720 {
721     Q_ASSERT(mArgType == ArgType_Int);
722     Q_ASSERT(isList());
723     std::vector<int> ret;
724     QList<QVariant> lst = mValue.toList();
725     ret.reserve(lst.size());
726     for (QList<QVariant>::const_iterator it = lst.constBegin(); it != lst.constEnd(); ++it) {
727         ret.push_back((*it).toInt());
728     }
729     return ret;
730 }
731
732 std::vector<unsigned int> QGpgMECryptoConfigEntry::uintValueList() const
733 {
734     Q_ASSERT(mArgType == ArgType_UInt);
735     Q_ASSERT(isList());
736     std::vector<unsigned int> ret;
737     QList<QVariant> lst = mValue.toList();
738     ret.reserve(lst.size());
739     for (QList<QVariant>::const_iterator it = lst.constBegin(); it != lst.constEnd(); ++it) {
740         ret.push_back((*it).toUInt());
741     }
742     return ret;
743 }
744
745 QList<QUrl> QGpgMECryptoConfigEntry::urlValueList() const
746 {
747     Q_ASSERT(mArgType == ArgType_Path || mArgType == ArgType_LDAPURL);
748     Q_ASSERT(isList());
749     QStringList lst = mValue.toStringList();
750
751     QList<QUrl> ret;
752     for (QStringList::const_iterator it = lst.constBegin(); it != lst.constEnd(); ++it) {
753         if (mArgType == ArgType_Path) {
754             QUrl url = QUrl::fromUserInput(*it, QString(), QUrl::AssumeLocalFile);
755         } else {
756             ret << parseURL(mRealArgType, *it);
757         }
758     }
759     return ret;
760 }
761
762 void QGpgMECryptoConfigEntry::resetToDefault()
763 {
764     mSet = false;
765     mDirty = true;
766     if (mFlags & GPGCONF_FLAG_DEFAULT) {
767         mValue = mDefaultValue;
768     } else if (mArgType == ArgType_None) {
769         if (isList()) {
770             mValue = 0U;
771         } else {
772             mValue = false;
773         }
774     }
775 }
776
777 void QGpgMECryptoConfigEntry::setBoolValue(bool b)
778 {
779     Q_ASSERT(mArgType == ArgType_None);
780     Q_ASSERT(!isList());
781     // A "no arg" option is either set or not set.
782     // Being set means mSet==true + mValue==true, being unset means resetToDefault(), i.e. both false
783     mValue = b;
784     mSet = b;
785     mDirty = true;
786 }
787
788 void QGpgMECryptoConfigEntry::setStringValue(const QString &str)
789 {
790     mValue = stringToValue(str, false);
791     // When setting a string to empty (and there's no default), we need to act like resetToDefault
792     // Otherwise we try e.g. "ocsp-responder:0:" and gpgconf answers:
793     // "gpgconf: argument required for option ocsp-responder"
794     if (str.isEmpty() && !isOptional()) {
795         mSet = false;
796     } else {
797         mSet = true;
798     }
799     mDirty = true;
800 }
801
802 void QGpgMECryptoConfigEntry::setIntValue(int i)
803 {
804     Q_ASSERT(mArgType == ArgType_Int);
805     Q_ASSERT(!isList());
806     mValue = i;
807     mSet = true;
808     mDirty = true;
809 }
810
811 void QGpgMECryptoConfigEntry::setUIntValue(unsigned int i)
812 {
813     mValue = i;
814     mSet = true;
815     mDirty = true;
816 }
817
818 void QGpgMECryptoConfigEntry::setURLValue(const QUrl &url)
819 {
820     QString str = splitURL(mRealArgType, url);
821     if (str.isEmpty() && !isOptional()) {
822         mSet = false;
823     } else {
824         mSet = true;
825     }
826     mValue = str;
827     mDirty = true;
828 }
829
830 void QGpgMECryptoConfigEntry::setNumberOfTimesSet(unsigned int i)
831 {
832     Q_ASSERT(mArgType == ArgType_None);
833     Q_ASSERT(isList());
834     mValue = i;
835     mSet = i > 0;
836     mDirty = true;
837 }
838
839 void QGpgMECryptoConfigEntry::setIntValueList(const std::vector<int> &lst)
840 {
841     QList<QVariant> ret;
842     for (std::vector<int>::const_iterator it = lst.begin(); it != lst.end(); ++it) {
843         ret << QVariant(*it);
844     }
845     mValue = ret;
846     if (ret.isEmpty() && !isOptional()) {
847         mSet = false;
848     } else {
849         mSet = true;
850     }
851     mDirty = true;
852 }
853
854 void QGpgMECryptoConfigEntry::setUIntValueList(const std::vector<unsigned int> &lst)
855 {
856     QList<QVariant> ret;
857     for (std::vector<unsigned int>::const_iterator it = lst.begin(); it != lst.end(); ++it) {
858         ret << QVariant(*it);
859     }
860     if (ret.isEmpty() && !isOptional()) {
861         mSet = false;
862     } else {
863         mSet = true;
864     }
865     mValue = ret;
866     mDirty = true;
867 }
868
869 void QGpgMECryptoConfigEntry::setURLValueList(const QList<QUrl> &urls)
870 {
871     QStringList lst;
872     for (QList<QUrl>::const_iterator it = urls.constBegin(); it != urls.constEnd(); ++it) {
873         lst << splitURL(mRealArgType, *it);
874     }
875     mValue = lst;
876     if (lst.isEmpty() && !isOptional()) {
877         mSet = false;
878     } else {
879         mSet = true;
880     }
881     mDirty = true;
882 }
883
884 QString QGpgMECryptoConfigEntry::toString(bool escape) const
885 {
886     // Basically the opposite of stringToValue
887     if (isStringType()) {
888         if (mValue.isNull()) {
889             return QString();
890         } else if (isList()) { // string list
891             QStringList lst = mValue.toStringList();
892             if (escape) {
893                 for (QStringList::iterator it = lst.begin(); it != lst.end(); ++it) {
894                     if (!(*it).isNull()) {
895                         *it = gpgconf_escape(*it).prepend(QLatin1String("\""));
896                     }
897                 }
898             }
899             const QString res = lst.join(QStringLiteral(","));
900             //qCDebug(GPGPME_BACKEND_LOG) <<"toString:" << res;
901             return res;
902         } else { // normal string
903             QString res = mValue.toString();
904             if (escape) {
905                 res = gpgconf_escape(res).prepend(QLatin1String("\""));
906             }
907             return res;
908         }
909     }
910     if (!isList()) { // non-list non-string
911         if (mArgType == ArgType_None) {
912             return mValue.toBool() ? QStringLiteral("1") : QString();
913         } else { // some int
914             Q_ASSERT(mArgType == ArgType_Int || mArgType == ArgType_UInt);
915             return mValue.toString(); // int to string conversion
916         }
917     }
918
919     // Lists (of other types than strings)
920     if (mArgType == ArgType_None) {
921         return QString::number(numberOfTimesSet());
922     }
923
924     QStringList ret;
925     QList<QVariant> lst = mValue.toList();
926     for (QList<QVariant>::const_iterator it = lst.constBegin(); it != lst.constEnd(); ++it) {
927         ret << (*it).toString(); // QVariant does the conversion
928     }
929     return ret.join(QStringLiteral(","));
930 }
931
932 QString QGpgMECryptoConfigEntry::outputString() const
933 {
934     Q_ASSERT(mSet);
935     return toString(true);
936 }
937
938 bool QGpgMECryptoConfigEntry::isStringType() const
939 {
940     return (mArgType == QGpgME::CryptoConfigEntry::ArgType_String
941             || mArgType == QGpgME::CryptoConfigEntry::ArgType_Path
942             || mArgType == QGpgME::CryptoConfigEntry::ArgType_LDAPURL);
943 }
944
945 void QGpgMECryptoConfigEntry::setDirty(bool b)
946 {
947     mDirty = b;
948 }