qt: Fix filename handling in cryptoconfig
[gpgme.git] / lang / qt / src / qgpgmenewcryptoconfig.cpp
1 /*
2     qgpgmenewcryptoconfig.cpp
3
4     This file is part of qgpgme, the Qt API binding for gpgme
5     Copyright (c) 2010 Klarälvdalens Datakonsult AB
6     Copyright (c) 2016 by Bundesamt für Sicherheit in der Informationstechnik
7     Software engineering by Intevation GmbH
8
9     QGpgME is free software; you can redistribute it and/or
10     modify it under the terms of the GNU General Public License as
11     published by the Free Software Foundation; either version 2 of the
12     License, or (at your option) any later version.
13
14     QGpgME is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17     General Public License for more details.
18
19     You should have received a copy of the GNU General Public License
20     along with this program; if not, write to the Free Software
21     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22
23     In addition, as a special exception, the copyright holders give
24     permission to link the code of this program with any edition of
25     the Qt library by Trolltech AS, Norway (or with modified versions
26     of Qt that use the same license as Qt), and distribute linked
27     combinations including the two.  You must obey the GNU General
28     Public License in all respects for all of the code used other than
29     Qt.  If you modify this file, you may extend this exception to
30     your version of the file, but you are not obligated to do so.  If
31     you do not wish to do so, delete this exception statement from
32     your version.
33 */
34
35 #ifdef HAVE_CONFIG_H
36  #include "config.h"
37 #endif
38
39 #include "qgpgmenewcryptoconfig.h"
40
41 #include <QDebug>
42 #include "gpgme_backend_debug.h"
43
44 #include <QFile>
45 #include <QDir>
46
47 #include "global.h"
48 #include "error.h"
49
50
51 #include <sstream>
52 #include <string>
53 #include <functional>
54 #include <cassert>
55 #include <functional>
56
57 using namespace QGpgME;
58 using namespace GpgME;
59 using namespace GpgME::Configuration;
60
61 namespace
62 {
63 struct Select1St {
64     template <typename U, typename V>
65     const U &operator()(const std::pair<U, V> &p) const
66     {
67         return p.first;
68     }
69     template <typename U, typename V>
70     const U &operator()(const QPair<U, V> &p) const
71     {
72         return p.first;
73     }
74 };
75 }
76
77 // Just for the Q_ASSERT in the dtor. Not thread-safe, but who would
78 // have 2 threads talking to gpgconf anyway? :)
79 static bool s_duringClear = false;
80
81 QGpgMENewCryptoConfig::QGpgMENewCryptoConfig()
82     :  m_parsed(false)
83 {
84 }
85
86 QGpgMENewCryptoConfig::~QGpgMENewCryptoConfig()
87 {
88     clear();
89 }
90
91 void QGpgMENewCryptoConfig::reloadConfiguration(bool)
92 {
93     clear();
94
95     Error error;
96     const std::vector<Component> components = Component::load(error);
97 #ifndef NDEBUG
98     {
99         std::stringstream ss;
100         ss << "error: " << error
101            << "components:\n";
102         std::copy(components.begin(), components.end(),
103                   std::ostream_iterator<Component>(ss, "\n"));
104         qCDebug(GPGPME_BACKEND_LOG) << ss.str().c_str();
105     }
106 #endif
107 #if 0
108     TODO port?
109     if (error && showErrors) {
110         const QString wmsg = i18n("<qt>Failed to execute gpgconf:<p>%1</p></qt>", QString::fromLocal8Bit(error.asString()));
111         qCWarning(GPGPME_BACKEND_LOG) << wmsg; // to see it from test_cryptoconfig.cpp
112         KMessageBox::error(0, wmsg);
113     }
114 #endif
115     Q_FOREACH(const Component & c, components) {
116         const std::shared_ptr<QGpgMENewCryptoConfigComponent> comp(new QGpgMENewCryptoConfigComponent);
117         comp->setComponent(c);
118         m_componentsByName[ comp->name() ] = comp;
119     }
120     m_parsed = true;
121 }
122
123 QStringList QGpgMENewCryptoConfig::componentList() const
124 {
125     if (!m_parsed) {
126         const_cast<QGpgMENewCryptoConfig *>(this)->reloadConfiguration(true);
127     }
128     QStringList result;
129     std::transform(m_componentsByName.begin(), m_componentsByName.end(),
130                    std::back_inserter(result),
131                    mem_fn(&QGpgMENewCryptoConfigComponent::name));
132     return result;
133 }
134
135 QGpgMENewCryptoConfigComponent *QGpgMENewCryptoConfig::component(const QString &name) const
136 {
137     if (!m_parsed) {
138         const_cast<QGpgMENewCryptoConfig *>(this)->reloadConfiguration(false);
139     }
140     return m_componentsByName.value(name).get();
141 }
142
143 void QGpgMENewCryptoConfig::sync(bool runtime)
144 {
145     Q_FOREACH(const std::shared_ptr<QGpgMENewCryptoConfigComponent> &c, m_componentsByName)
146     c->sync(runtime);
147 }
148
149 void QGpgMENewCryptoConfig::clear()
150 {
151     s_duringClear = true;
152     m_componentsByName.clear();
153     s_duringClear = false;
154     m_parsed = false; // next call to componentList/component will need to run gpgconf again
155 }
156
157 ////
158
159 QGpgMENewCryptoConfigComponent::QGpgMENewCryptoConfigComponent()
160     : CryptoConfigComponent(),
161       m_component()
162 {
163
164 }
165
166 void QGpgMENewCryptoConfigComponent::setComponent(const Component &component)
167 {
168     m_component = component;
169     m_groupsByName.clear();
170
171     std::shared_ptr<QGpgMENewCryptoConfigGroup> group;
172
173     const std::vector<Option> options = m_component.options();
174     Q_FOREACH(const Option & o, options)
175     if (o.flags() & Group) {
176         if (group) {
177             m_groupsByName[group->name()] = group;
178         }
179         group.reset(new QGpgMENewCryptoConfigGroup(shared_from_this(), o));
180     } else if (group) {
181         const std::shared_ptr<QGpgMENewCryptoConfigEntry> entry(new QGpgMENewCryptoConfigEntry(group, o));
182         const QString name = entry->name();
183         group->m_entryNames.push_back(name);
184         group->m_entriesByName[name] = entry;
185     } else {
186         qCWarning(GPGPME_BACKEND_LOG) << "found no group for entry" << o.name() << "of component" << name();
187     }
188     if (group) {
189         m_groupsByName[group->name()] = group;
190     }
191
192 }
193
194 QGpgMENewCryptoConfigComponent::~QGpgMENewCryptoConfigComponent() {}
195
196 QString QGpgMENewCryptoConfigComponent::name() const
197 {
198     return QString::fromUtf8(m_component.name());
199 }
200
201 QString QGpgMENewCryptoConfigComponent::description() const
202 {
203     return QString::fromUtf8(m_component.description());
204 }
205
206 QStringList QGpgMENewCryptoConfigComponent::groupList() const
207 {
208     QStringList result;
209     result.reserve(m_groupsByName.size());
210     std::transform(m_groupsByName.begin(), m_groupsByName.end(),
211                    std::back_inserter(result),
212                    std::mem_fn(&QGpgMENewCryptoConfigGroup::name));
213     return result;
214 }
215
216 QGpgMENewCryptoConfigGroup *QGpgMENewCryptoConfigComponent::group(const QString &name) const
217 {
218     return m_groupsByName.value(name).get();
219 }
220
221 void QGpgMENewCryptoConfigComponent::sync(bool runtime)
222 {
223     Q_UNUSED(runtime) // runtime is always set by engine_gpgconf
224     if (const Error err = m_component.save()) {
225         qCWarning(GPGPME_BACKEND_LOG) << ":"
226             << "Error from gpgconf while saving configuration: %1"
227             << QString::fromLocal8Bit(err.asString());
228     }
229 }
230
231 ////
232
233 QGpgMENewCryptoConfigGroup::QGpgMENewCryptoConfigGroup(const std::shared_ptr<QGpgMENewCryptoConfigComponent> &comp, const Option &option)
234     : CryptoConfigGroup(),
235       m_component(comp),
236       m_option(option)
237 {
238 }
239
240 QGpgMENewCryptoConfigGroup::~QGpgMENewCryptoConfigGroup() {}
241
242 QString QGpgMENewCryptoConfigGroup::name() const
243 {
244     return QString::fromUtf8(m_option.name());
245 }
246
247 QString QGpgMENewCryptoConfigGroup::description() const
248 {
249     return QString::fromUtf8(m_option.description());
250 }
251
252 QString QGpgMENewCryptoConfigGroup::path() const
253 {
254     if (const std::shared_ptr<QGpgMENewCryptoConfigComponent> c = m_component.lock()) {
255         return c->name() + QLatin1Char('/') + name();
256     } else {
257         return QString();
258     }
259 }
260
261 CryptoConfigEntry::Level QGpgMENewCryptoConfigGroup::level() const
262 {
263     // two casts to make SunCC happy:
264     return static_cast<CryptoConfigEntry::Level>(static_cast<unsigned int>(m_option.level()));
265 }
266
267 QStringList QGpgMENewCryptoConfigGroup::entryList() const
268 {
269     return m_entryNames;
270 }
271
272 QGpgMENewCryptoConfigEntry *QGpgMENewCryptoConfigGroup::entry(const QString &name) const
273 {
274     return m_entriesByName.value(name).get();
275 }
276
277 static QString urlpart_encode(const QString &str)
278 {
279     QString enc(str);
280     enc.replace(QLatin1Char('%'), QStringLiteral("%25"));   // first!
281     enc.replace(QLatin1Char(':'), QStringLiteral("%3a"));
282     //qCDebug(GPGPME_BACKEND_LOG) <<"  urlpart_encode:" << str <<" ->" << enc;
283     return enc;
284 }
285
286 static QString urlpart_decode(const QString &str)
287 {
288     return QUrl::fromPercentEncoding(str.toLatin1());
289 }
290
291 // gpgconf arg type number -> NewCryptoConfigEntry arg type enum mapping
292 static QGpgME::CryptoConfigEntry::ArgType knownArgType(int argType, bool &ok)
293 {
294     ok = true;
295     switch (argType) {
296     case 0: // none
297         return QGpgME::CryptoConfigEntry::ArgType_None;
298     case 1: // string
299         return QGpgME::CryptoConfigEntry::ArgType_String;
300     case 2: // int32
301         return QGpgME::CryptoConfigEntry::ArgType_Int;
302     case 3: // uint32
303         return QGpgME::CryptoConfigEntry::ArgType_UInt;
304     case 32: // pathname
305         return QGpgME::CryptoConfigEntry::ArgType_Path;
306     case 33: // ldap server
307         return QGpgME::CryptoConfigEntry::ArgType_LDAPURL;
308     default:
309         ok = false;
310         return QGpgME::CryptoConfigEntry::ArgType_None;
311     }
312 }
313
314 QGpgMENewCryptoConfigEntry::QGpgMENewCryptoConfigEntry(const std::shared_ptr<QGpgMENewCryptoConfigGroup> &group, const Option &option)
315     : m_group(group), m_option(option)
316 {
317 }
318
319 #if 0
320 QVariant QGpgMENewCryptoConfigEntry::stringToValue(const QString &str, bool unescape) const
321 {
322     const bool isString = isStringType();
323
324     if (isList()) {
325         if (argType() == ArgType_None) {
326             bool ok = true;
327             const QVariant v = str.isEmpty() ? 0U : str.toUInt(&ok);
328             if (!ok) {
329                 qCWarning(GPGPME_BACKEND_LOG) << "list-of-none should have an unsigned int as value:" << str;
330             }
331             return v;
332         }
333         QList<QVariant> lst;
334         QStringList items = str.split(',', QString::SkipEmptyParts);
335         for (QStringList::const_iterator valit = items.constBegin(); valit != items.constEnd(); ++valit) {
336             QString val = *valit;
337             if (isString) {
338                 if (val.isEmpty()) {
339                     lst << QVariant(QString());
340                     continue;
341                 } else if (unescape) {
342                     if (val[0] != '"') { // see README.gpgconf
343                         qCWarning(GPGPME_BACKEND_LOG) << "String value should start with '\"' :" << val;
344                     }
345                     val = val.mid(1);
346                 }
347             }
348             lst << QVariant(unescape ? gpgconf_unescape(val) : val);
349         }
350         return lst;
351     } else { // not a list
352         QString val(str);
353         if (isString) {
354             if (val.isEmpty()) {
355                 return QVariant(QString());    // not set  [ok with lists too?]
356             } else if (unescape) {
357                 if (val[0] != '"') { // see README.gpgconf
358                     qCWarning(GPGPME_BACKEND_LOG) << "String value should start with '\"' :" << val;
359                 }
360                 val = val.mid(1);
361             }
362         }
363         return QVariant(unescape ? gpgconf_unescape(val) : val);
364     }
365 }
366 #endif
367
368 QGpgMENewCryptoConfigEntry::~QGpgMENewCryptoConfigEntry()
369 {
370 #ifndef NDEBUG
371     if (!s_duringClear && m_option.dirty())
372         qCWarning(GPGPME_BACKEND_LOG) << "Deleting a QGpgMENewCryptoConfigEntry that was modified (" << m_option.description() << ")"
373                                       << "You forgot to call sync() (to commit) or clear() (to discard)";
374 #endif
375 }
376
377 QString QGpgMENewCryptoConfigEntry::name() const
378 {
379     return QString::fromUtf8(m_option.name());
380 }
381
382 QString QGpgMENewCryptoConfigEntry::description() const
383 {
384     return QString::fromUtf8(m_option.description());
385 }
386
387 QString QGpgMENewCryptoConfigEntry::path() const
388 {
389     if (const std::shared_ptr<QGpgMENewCryptoConfigGroup> g = m_group.lock()) {
390         return g->path() + QLatin1Char('/') + name();
391     } else {
392         return QString();
393     }
394 }
395
396 bool QGpgMENewCryptoConfigEntry::isOptional() const
397 {
398     return m_option.flags() & Optional;
399 }
400
401 bool QGpgMENewCryptoConfigEntry::isReadOnly() const
402 {
403     return m_option.flags() & NoChange;
404 }
405
406 bool QGpgMENewCryptoConfigEntry::isList() const
407 {
408     return m_option.flags() & List;
409 }
410
411 bool QGpgMENewCryptoConfigEntry::isRuntime() const
412 {
413     return m_option.flags() & Runtime;
414 }
415
416 CryptoConfigEntry::Level QGpgMENewCryptoConfigEntry::level() const
417 {
418     // two casts to make SunCC happy:
419     return static_cast<Level>(static_cast<unsigned int>(m_option.level()));
420 }
421
422 CryptoConfigEntry::ArgType QGpgMENewCryptoConfigEntry::argType() const
423 {
424     bool ok = false;
425     const ArgType type = knownArgType(m_option.type(), ok);
426     if (ok) {
427         return type;
428     } else {
429         return knownArgType(m_option.alternateType(), ok);
430     }
431 }
432
433 bool QGpgMENewCryptoConfigEntry::isSet() const
434 {
435     return m_option.set();
436 }
437
438 bool QGpgMENewCryptoConfigEntry::boolValue() const
439 {
440     Q_ASSERT(m_option.alternateType() == NoType);
441     Q_ASSERT(!isList());
442     return m_option.currentValue().boolValue();
443 }
444
445 QString QGpgMENewCryptoConfigEntry::stringValue() const
446 {
447     //return toString( false );
448     Q_ASSERT(m_option.alternateType() == StringType);
449     Q_ASSERT(!isList());
450     return QString::fromUtf8(m_option.currentValue().stringValue());
451 }
452
453 int QGpgMENewCryptoConfigEntry::intValue() const
454 {
455     Q_ASSERT(m_option.alternateType() == IntegerType);
456     Q_ASSERT(!isList());
457     return m_option.currentValue().intValue();
458 }
459
460 unsigned int QGpgMENewCryptoConfigEntry::uintValue() const
461 {
462     Q_ASSERT(m_option.alternateType() == UnsignedIntegerType);
463     Q_ASSERT(!isList());
464     return m_option.currentValue().uintValue();
465 }
466
467 static QUrl parseURL(int mRealArgType, const QString &str)
468 {
469     if (mRealArgType == 33) {   // LDAP server
470         // The format is HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN
471         QStringList items = str.split(QLatin1Char(':'));
472         if (items.count() == 5) {
473             QStringList::const_iterator it = items.constBegin();
474             QUrl url;
475             url.setScheme(QStringLiteral("ldap"));
476             url.setHost(urlpart_decode(*it++));
477
478             bool ok;
479             const int port = (*it++).toInt(&ok);
480             if (ok) {
481                 url.setPort(port);
482             } else if (!it->isEmpty()) {
483                 qCWarning(GPGPME_BACKEND_LOG) << "parseURL: malformed LDAP server port, ignoring: \"" << *it << "\"";
484             }
485
486             const QString userName = urlpart_decode(*it++);
487             if (!userName.isEmpty()) {
488                 url.setUserName(userName);
489             }
490             const QString passWord = urlpart_decode(*it++);
491             if (!passWord.isEmpty()) {
492                 url.setPassword(passWord);
493             }
494             url.setQuery(urlpart_decode(*it));
495             return url;
496         } else {
497             qCWarning(GPGPME_BACKEND_LOG) << "parseURL: malformed LDAP server:" << str;
498         }
499     }
500     // other URLs : assume wellformed URL syntax.
501     return QUrl(str);
502 }
503
504 // The opposite of parseURL
505 static QString splitURL(int mRealArgType, const QUrl &url)
506 {
507     if (mRealArgType == 33) {   // LDAP server
508         // The format is HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN
509         Q_ASSERT(url.scheme() == QLatin1String("ldap"));
510         return urlpart_encode(url.host()) + QLatin1Char(':') +
511                (url.port() != -1 ? QString::number(url.port()) : QString()) + QLatin1Char(':') +     // -1 is used for default ports, omit
512                urlpart_encode(url.userName()) + QLatin1Char(':') +
513                urlpart_encode(url.password()) + QLatin1Char(':') +
514                urlpart_encode(url.query());
515     }
516     return url.path();
517 }
518
519 QUrl QGpgMENewCryptoConfigEntry::urlValue() const
520 {
521     const Type type = m_option.type();
522     Q_ASSERT(type == FilenameType || type == LdapServerType);
523     Q_ASSERT(!isList());
524     if (type == FilenameType) {
525         QUrl url = QUrl::fromLocalFile(m_option.currentValue().stringValue());
526         return url;
527     }
528     return parseURL(type, stringValue());
529 }
530
531 unsigned int QGpgMENewCryptoConfigEntry::numberOfTimesSet() const
532 {
533     Q_ASSERT(m_option.alternateType() == NoType);
534     Q_ASSERT(isList());
535     return m_option.currentValue().uintValue();
536 }
537
538 std::vector<int> QGpgMENewCryptoConfigEntry::intValueList() const
539 {
540     Q_ASSERT(m_option.alternateType() == IntegerType);
541     Q_ASSERT(isList());
542     return m_option.currentValue().intValues();
543 }
544
545 std::vector<unsigned int> QGpgMENewCryptoConfigEntry::uintValueList() const
546 {
547     Q_ASSERT(m_option.alternateType() == UnsignedIntegerType);
548     Q_ASSERT(isList());
549     return m_option.currentValue().uintValues();
550 }
551
552 QStringList QGpgMENewCryptoConfigEntry::stringValueList() const
553 {
554     Q_ASSERT(isList());
555     const Argument arg = m_option.currentValue();
556     const std::vector<const char *> values = arg.stringValues();
557     QStringList ret;
558     for(const char *value: values) {
559         ret << QString::fromUtf8(value);
560     }
561     return ret;
562 }
563
564 QList<QUrl> QGpgMENewCryptoConfigEntry::urlValueList() const
565 {
566     const Type type = m_option.type();
567     Q_ASSERT(type == FilenameType || type == LdapServerType);
568     Q_ASSERT(isList());
569     const Argument arg = m_option.currentValue();
570     const std::vector<const char *> values = arg.stringValues();
571     QList<QUrl> ret;
572     Q_FOREACH(const char *value, values)
573     if (type == FilenameType) {
574         QUrl url;
575         url.setPath(QFile::decodeName(value));
576         ret << url;
577     } else {
578         ret << parseURL(type, QString::fromUtf8(value));
579     }
580     return ret;
581 }
582
583 void QGpgMENewCryptoConfigEntry::resetToDefault()
584 {
585     m_option.resetToDefaultValue();
586 }
587
588 void QGpgMENewCryptoConfigEntry::setBoolValue(bool b)
589 {
590     Q_ASSERT(m_option.alternateType() == NoType);
591     Q_ASSERT(!isList());
592     // A "no arg" option is either set or not set.
593     // Being set means createNoneArgument(), being unset means resetToDefault()
594     m_option.setNewValue(m_option.createNoneArgument(b));
595 }
596
597 void QGpgMENewCryptoConfigEntry::setStringValue(const QString &str)
598 {
599     Q_ASSERT(m_option.alternateType() == StringType);
600     Q_ASSERT(!isList());
601     const Type type = m_option.type();
602     // When setting a string to empty (and there's no default), we need to act like resetToDefault
603     // Otherwise we try e.g. "ocsp-responder:0:" and gpgconf answers:
604     // "gpgconf: argument required for option ocsp-responder"
605     if (str.isEmpty() && !isOptional()) {
606         m_option.resetToDefaultValue();
607     } else if (type == FilenameType) {
608         m_option.setNewValue(m_option.createStringArgument(QFile::encodeName(str).constData()));
609     } else {
610         m_option.setNewValue(m_option.createStringArgument(str.toUtf8().constData()));
611     }
612 }
613
614 void QGpgMENewCryptoConfigEntry::setIntValue(int i)
615 {
616     Q_ASSERT(m_option.alternateType() == IntegerType);
617     Q_ASSERT(!isList());
618     m_option.setNewValue(m_option.createIntArgument(i));
619 }
620
621 void QGpgMENewCryptoConfigEntry::setUIntValue(unsigned int i)
622 {
623     Q_ASSERT(m_option.alternateType() == UnsignedIntegerType);
624     Q_ASSERT(!isList());
625     m_option.setNewValue(m_option.createUIntArgument(i));
626 }
627
628 void QGpgMENewCryptoConfigEntry::setURLValue(const QUrl &url)
629 {
630     const Type type = m_option.type();
631     Q_ASSERT(type == FilenameType || type == LdapServerType);
632     Q_ASSERT(!isList());
633     const QString str = splitURL(type, url);
634     // cf. setStringValue()
635     if (str.isEmpty() && !isOptional()) {
636         m_option.resetToDefaultValue();
637     } else if (type == FilenameType) {
638         m_option.setNewValue(m_option.createStringArgument(QDir::toNativeSeparators(url.toLocalFile()).toUtf8().constData()));
639     } else {
640         m_option.setNewValue(m_option.createStringArgument(str.toUtf8().constData()));
641     }
642 }
643
644 void QGpgMENewCryptoConfigEntry::setNumberOfTimesSet(unsigned int i)
645 {
646     Q_ASSERT(m_option.alternateType() == NoType);
647     Q_ASSERT(isList());
648     m_option.setNewValue(m_option.createNoneListArgument(i));
649 }
650
651 void QGpgMENewCryptoConfigEntry::setIntValueList(const std::vector<int> &lst)
652 {
653     Q_ASSERT(m_option.alternateType() == IntegerType);
654     Q_ASSERT(isList());
655     m_option.setNewValue(m_option.createIntListArgument(lst));
656 }
657
658 void QGpgMENewCryptoConfigEntry::setUIntValueList(const std::vector<unsigned int> &lst)
659 {
660     Q_ASSERT(m_option.alternateType() == UnsignedIntegerType);
661     Q_ASSERT(isList());
662     m_option.setNewValue(m_option.createUIntListArgument(lst));
663 }
664
665 void QGpgMENewCryptoConfigEntry::setURLValueList(const QList<QUrl> &urls)
666 {
667     const Type type = m_option.type();
668     Q_ASSERT(m_option.alternateType() == StringType);
669     Q_ASSERT(isList());
670     std::vector<std::string> values;
671     values.reserve(urls.size());
672     Q_FOREACH (const QUrl &url, urls)
673         if (type == FilenameType) {
674             values.push_back(QFile::encodeName(url.path()).constData());
675         } else {
676             values.push_back(splitURL(type, url).toUtf8().constData());
677         }
678     m_option.setNewValue(m_option.createStringListArgument(values));
679 }
680
681 bool QGpgMENewCryptoConfigEntry::isDirty() const
682 {
683     return m_option.dirty();
684 }
685
686 #if 0
687 QString QGpgMENewCryptoConfigEntry::toString(bool escape) const
688 {
689     // Basically the opposite of stringToValue
690     if (isStringType()) {
691         if (mValue.isNull()) {
692             return QString();
693         } else if (isList()) { // string list
694             QStringList lst = mValue.toStringList();
695             if (escape) {
696                 for (QStringList::iterator it = lst.begin(); it != lst.end(); ++it) {
697                     if (!(*it).isNull()) {
698                         *it = gpgconf_escape(*it).prepend("\"");
699                     }
700                 }
701             }
702             QString res = lst.join(",");
703             //qCDebug(GPGPME_BACKEND_LOG) <<"toString:" << res;
704             return res;
705         } else { // normal string
706             QString res = mValue.toString();
707             if (escape) {
708                 res = gpgconf_escape(res).prepend("\"");
709             }
710             return res;
711         }
712     }
713     if (!isList()) { // non-list non-string
714         if (mArgType == ArgType_None) {
715             return mValue.toBool() ? QString::fromLatin1("1") : QString();
716         } else { // some int
717             Q_ASSERT(mArgType == ArgType_Int || mArgType == ArgType_UInt);
718             return mValue.toString(); // int to string conversion
719         }
720     }
721
722     // Lists (of other types than strings)
723     if (mArgType == ArgType_None) {
724         return QString::number(numberOfTimesSet());
725     }
726     QStringList ret;
727     QList<QVariant> lst = mValue.toList();
728     for (QList<QVariant>::const_iterator it = lst.constBegin(); it != lst.constEnd(); ++it) {
729         ret << (*it).toString(); // QVariant does the conversion
730     }
731     return ret.join(",");
732 }
733
734 QString QGpgMENewCryptoConfigEntry::outputString() const
735 {
736     Q_ASSERT(mSet);
737     return toString(true);
738 }
739
740 bool QGpgMENewCryptoConfigEntry::isStringType() const
741 {
742     return (mArgType == QGpgME::NewCryptoConfigEntry::ArgType_String
743             || mArgType == QGpgME::NewCryptoConfigEntry::ArgType_Path
744             || mArgType == QGpgME::NewCryptoConfigEntry::ArgType_URL
745             || mArgType == QGpgME::NewCryptoConfigEntry::ArgType_LDAPURL);
746 }
747
748 void QGpgMENewCryptoConfigEntry::setDirty(bool b)
749 {
750     mDirty = b;
751 }
752 #endif