1 /*
2  * Copyright (C) by Klaas Freitag <freitag@owncloud.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12  * for more details.
13  */
14 
15 #include "config.h"
16 
17 #include "common/utility.h"
18 #include "common/asserts.h"
19 #include "configfile.h"
20 #include "logger.h"
21 #include "theme.h"
22 #include "version.h"
23 
24 #include "creds/abstractcredentials.h"
25 
26 #include "csync_exclude.h"
27 
28 #ifndef TOKEN_AUTH_ONLY
29 #include <QWidget>
30 #include <QHeaderView>
31 #endif
32 
33 #include <QCoreApplication>
34 #include <QDir>
35 #include <QFile>
36 #include <QFileInfo>
37 #include <QLoggingCategory>
38 #include <QSettings>
39 #include <QNetworkProxy>
40 #include <QOperatingSystemVersion>
41 #include <QStandardPaths>
42 
43 #define DEFAULT_REMOTE_POLL_INTERVAL 30000 // default remote poll time in milliseconds
44 #define DEFAULT_MAX_LOG_LINES 20000
45 
46 namespace OCC {
47 
48 namespace chrono = std::chrono;
49 
50 Q_LOGGING_CATEGORY(lcConfigFile, "sync.configfile", QtInfoMsg)
51 namespace  {
logHttpC()52 const QString logHttpC() { return QStringLiteral("logHttp"); }
remotePollIntervalC()53 const QString remotePollIntervalC() { return QStringLiteral("remotePollInterval"); }
54 //const QString caCertsKeyC() { return QStringLiteral("CaCertificates"); } only used from account.cpp
forceSyncIntervalC()55 const QString forceSyncIntervalC() { return QStringLiteral("forceSyncInterval"); }
fullLocalDiscoveryIntervalC()56 const QString fullLocalDiscoveryIntervalC() { return QStringLiteral("fullLocalDiscoveryInterval"); }
notificationRefreshIntervalC()57 const QString notificationRefreshIntervalC() { return QStringLiteral("notificationRefreshInterval"); }
monoIconsC()58 const QString monoIconsC() { return QStringLiteral("monoIcons"); }
promptDeleteC()59 const QString promptDeleteC() { return QStringLiteral("promptDeleteAllFiles"); }
crashReporterC()60 const QString crashReporterC() { return QStringLiteral("crashReporter"); }
optionalDesktopNoficationsC()61 const QString optionalDesktopNoficationsC() { return QStringLiteral("optionalDesktopNotifications"); }
showInExplorerNavigationPaneC()62 const QString showInExplorerNavigationPaneC() { return QStringLiteral("showInExplorerNavigationPane"); }
skipUpdateCheckC()63 const QString skipUpdateCheckC() { return QStringLiteral("skipUpdateCheck"); }
updateCheckIntervalC()64 const QString updateCheckIntervalC() { return QStringLiteral("updateCheckInterval"); }
updateChannelC()65 const QString updateChannelC() { return QStringLiteral("updateChannel"); }
geometryC()66 const QString geometryC() { return QStringLiteral("geometry"); }
timeoutC()67 const QString timeoutC() { return QStringLiteral("timeout"); }
chunkSizeC()68 const QString chunkSizeC() { return QStringLiteral("chunkSize"); }
minChunkSizeC()69 const QString minChunkSizeC() { return QStringLiteral("minChunkSize"); }
maxChunkSizeC()70 const QString maxChunkSizeC() { return QStringLiteral("maxChunkSize"); }
targetChunkUploadDurationC()71 const QString targetChunkUploadDurationC() { return QStringLiteral("targetChunkUploadDuration"); }
automaticLogDirC()72 const QString automaticLogDirC() { return QStringLiteral("logToTemporaryLogDir"); }
deleteOldLogsAfterHoursC()73 const QString deleteOldLogsAfterHoursC() { return QStringLiteral("temporaryLogDirDeleteOldLogsAfterHours"); }
showExperimentalOptionsC()74 const QString showExperimentalOptionsC() { return QStringLiteral("showExperimentalOptions"); }
clientVersionC()75 const QString clientVersionC() { return QStringLiteral("clientVersion"); }
76 
proxyHostC()77 const QString proxyHostC() { return QStringLiteral("Proxy/host"); }
proxyTypeC()78 const QString proxyTypeC() { return QStringLiteral("Proxy/type"); }
proxyPortC()79 const QString proxyPortC() { return QStringLiteral("Proxy/port"); }
proxyUserC()80 const QString proxyUserC() { return QStringLiteral("Proxy/user"); }
proxyPassC()81 const QString proxyPassC() { return QStringLiteral("Proxy/pass"); }
proxyNeedsAuthC()82 const QString proxyNeedsAuthC() { return QStringLiteral("Proxy/needsAuth"); }
83 
useUploadLimitC()84 const QString useUploadLimitC() { return QStringLiteral("BWLimit/useUploadLimit"); }
useDownloadLimitC()85 const QString useDownloadLimitC() { return QStringLiteral("BWLimit/useDownloadLimit"); }
uploadLimitC()86 const QString uploadLimitC() { return QStringLiteral("BWLimit/uploadLimit"); }
downloadLimitC()87 const QString downloadLimitC() { return QStringLiteral("BWLimit/downloadLimit"); }
88 
newBigFolderSizeLimitC()89 const QString newBigFolderSizeLimitC() { return QStringLiteral("newBigFolderSizeLimit"); }
useNewBigFolderSizeLimitC()90 const QString useNewBigFolderSizeLimitC() { return QStringLiteral("useNewBigFolderSizeLimit"); }
confirmExternalStorageC()91 const QString confirmExternalStorageC() { return QStringLiteral("confirmExternalStorage"); }
moveToTrashC()92 const QString moveToTrashC() { return QStringLiteral("moveToTrash"); }
93 
certPath()94 const QString certPath() { return QStringLiteral("http_certificatePath"); }
certPasswd()95 const QString certPasswd() { return QStringLiteral("http_certificatePasswd"); }
96 }
97 
98 QString ConfigFile::_confDir = QString();
99 bool ConfigFile::_askedUser = false;
100 
millisecondsValue(const QSettings & setting,const QString & key,chrono::milliseconds defaultValue)101 static chrono::milliseconds millisecondsValue(const QSettings &setting, const QString &key,
102     chrono::milliseconds defaultValue)
103 {
104     return chrono::milliseconds(setting.value(key, qlonglong(defaultValue.count())).toLongLong());
105 }
106 
ConfigFile()107 ConfigFile::ConfigFile()
108 {
109     // QDesktopServices uses the application name to create a config path
110     // TODO: we use multiple calls to setApplicationName with different arguments...
111     qApp->setApplicationName(Theme::instance()->appNameGUI());
112 
113     QSettings::setDefaultFormat(QSettings::IniFormat);
114 
115     const QString config = configFile();
116 
117 
118     QSettings settings(config, QSettings::IniFormat);
119     settings.beginGroup(defaultConnection());
120 
121     // run init only once
122     static bool init = [this]() {
123         setLogHttp(logHttp());
124         return false;
125     }();
126     Q_UNUSED(init);
127 }
128 
setConfDir(const QString & value)129 bool ConfigFile::setConfDir(const QString &value)
130 {
131     QString dirPath = value;
132     if (dirPath.isEmpty())
133         return false;
134 
135     QFileInfo fi(dirPath);
136     if (!fi.exists()) {
137         QDir().mkpath(dirPath);
138         fi.setFile(dirPath);
139     }
140     if (fi.exists() && fi.isDir()) {
141         dirPath = fi.absoluteFilePath();
142         qCInfo(lcConfigFile) << "Using custom config dir " << dirPath;
143         _confDir = dirPath;
144         return true;
145     }
146     return false;
147 }
148 
optionalDesktopNotifications() const149 bool ConfigFile::optionalDesktopNotifications() const
150 {
151     QSettings settings(configFile(), QSettings::IniFormat);
152     return settings.value(optionalDesktopNoficationsC(), true).toBool();
153 }
154 
showInExplorerNavigationPane() const155 bool ConfigFile::showInExplorerNavigationPane() const
156 {
157     QSettings settings(configFile(), QSettings::IniFormat);
158     return settings.value(showInExplorerNavigationPaneC(), QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10).toBool();
159 }
160 
setShowInExplorerNavigationPane(bool show)161 void ConfigFile::setShowInExplorerNavigationPane(bool show)
162 {
163     QSettings settings(configFile(), QSettings::IniFormat);
164     settings.setValue(showInExplorerNavigationPaneC(), show);
165     settings.sync();
166 }
167 
timeout() const168 int ConfigFile::timeout() const
169 {
170     QSettings settings(configFile(), QSettings::IniFormat);
171     return settings.value(timeoutC(), 300).toInt(); // default to 5 min
172 }
173 
chunkSize() const174 qint64 ConfigFile::chunkSize() const
175 {
176     QSettings settings(configFile(), QSettings::IniFormat);
177     return settings.value(chunkSizeC(), 10 * 1000 * 1000).toLongLong(); // default to 10 MB
178 }
179 
maxChunkSize() const180 qint64 ConfigFile::maxChunkSize() const
181 {
182     QSettings settings(configFile(), QSettings::IniFormat);
183     return settings.value(maxChunkSizeC(), 100 * 1000 * 1000).toLongLong(); // default to 100 MB
184 }
185 
minChunkSize() const186 qint64 ConfigFile::minChunkSize() const
187 {
188     QSettings settings(configFile(), QSettings::IniFormat);
189     return settings.value(minChunkSizeC(), 1000 * 1000).toLongLong(); // default to 1 MB
190 }
191 
targetChunkUploadDuration() const192 chrono::milliseconds ConfigFile::targetChunkUploadDuration() const
193 {
194     QSettings settings(configFile(), QSettings::IniFormat);
195     return millisecondsValue(settings, targetChunkUploadDurationC(), chrono::minutes(1));
196 }
197 
setOptionalDesktopNotifications(bool show)198 void ConfigFile::setOptionalDesktopNotifications(bool show)
199 {
200     QSettings settings(configFile(), QSettings::IniFormat);
201     settings.setValue(optionalDesktopNoficationsC(), show);
202     settings.sync();
203 }
204 
saveGeometry(QWidget * w)205 void ConfigFile::saveGeometry(QWidget *w)
206 {
207 #ifndef TOKEN_AUTH_ONLY
208     OC_ASSERT(!w->objectName().isNull());
209     QSettings settings(configFile(), QSettings::IniFormat);
210     settings.beginGroup(w->objectName());
211     settings.setValue(geometryC(), w->saveGeometry());
212     settings.sync();
213 #endif
214 }
215 
restoreGeometry(QWidget * w)216 void ConfigFile::restoreGeometry(QWidget *w)
217 {
218 #ifndef TOKEN_AUTH_ONLY
219     w->restoreGeometry(getValue(geometryC(), w->objectName()).toByteArray());
220 #endif
221 }
222 
saveGeometryHeader(QHeaderView * header)223 void ConfigFile::saveGeometryHeader(QHeaderView *header)
224 {
225 #ifndef TOKEN_AUTH_ONLY
226     if (!header)
227         return;
228     OC_ASSERT(!header->objectName().isEmpty());
229 
230     QSettings settings(configFile(), QSettings::IniFormat);
231     settings.beginGroup(header->objectName());
232     settings.setValue(geometryC(), header->saveState());
233     settings.sync();
234 #endif
235 }
236 
restoreGeometryHeader(QHeaderView * header)237 void ConfigFile::restoreGeometryHeader(QHeaderView *header)
238 {
239 #ifndef TOKEN_AUTH_ONLY
240     if (!header)
241         return;
242     OC_ASSERT(!header->objectName().isNull());
243 
244     QSettings settings(configFile(), QSettings::IniFormat);
245     settings.beginGroup(header->objectName());
246     header->restoreState(settings.value(geometryC()).toByteArray());
247 #endif
248 }
249 
getPolicySetting(const QString & setting,const QVariant & defaultValue) const250 QVariant ConfigFile::getPolicySetting(const QString &setting, const QVariant &defaultValue) const
251 {
252     if (Utility::isWindows()) {
253         // check for policies first and return immediately if a value is found.
254         QSettings userPolicy(QStringLiteral("HKEY_CURRENT_USER\\Software\\Policies\\%1\\%2")
255                                  .arg(QStringLiteral(APPLICATION_VENDOR), Theme::instance()->appNameGUI()),
256             QSettings::NativeFormat);
257         if (userPolicy.contains(setting)) {
258             return userPolicy.value(setting);
259         }
260 
261         QSettings machinePolicy(QStringLiteral("HKEY_LOCAL_MACHINE\\Software\\Policies\\%1\\%2")
262                                     .arg(QStringLiteral(APPLICATION_VENDOR), Theme::instance()->appNameGUI()),
263             QSettings::NativeFormat);
264         if (machinePolicy.contains(setting)) {
265             return machinePolicy.value(setting);
266         }
267     }
268     return defaultValue;
269 }
270 
configPath() const271 QString ConfigFile::configPath() const
272 {
273     if (_confDir.isEmpty()) {
274         // On Unix, use the AppConfigLocation for the settings, that's configurable with the XDG_CONFIG_HOME env variable.
275         // On Windows, use AppDataLocation, that's where the roaming data is and where we should store the config file
276         _confDir = QStandardPaths::writableLocation(Utility::isWindows() ? QStandardPaths::AppDataLocation : QStandardPaths::AppConfigLocation);
277     }
278     QString dir = _confDir;
279 
280     if (!dir.endsWith(QLatin1Char('/')))
281         dir.append(QLatin1Char('/'));
282     return dir;
283 }
284 
285 static const QLatin1String exclFile("sync-exclude.lst");
286 
excludeFile(Scope scope) const287 QString ConfigFile::excludeFile(Scope scope) const
288 {
289     // prefer sync-exclude.lst, but if it does not exist, check for
290     // exclude.lst for compatibility reasons in the user writeable
291     // directories.
292     QFileInfo fi;
293 
294     switch (scope) {
295     case UserScope:
296         fi.setFile(configPath(), exclFile);
297 
298         if (!fi.isReadable()) {
299             fi.setFile(configPath(), QStringLiteral("exclude.lst"));
300         }
301         if (!fi.isReadable()) {
302             fi.setFile(configPath(), exclFile);
303         }
304         return fi.absoluteFilePath();
305     case SystemScope:
306         return ConfigFile::excludeFileFromSystem();
307     }
308 
309     OC_ASSERT(false);
310     return QString();
311 }
312 
excludeFileFromSystem()313 QString ConfigFile::excludeFileFromSystem()
314 {
315     QFileInfo fi;
316 #ifdef Q_OS_WIN
317     fi.setFile(QCoreApplication::applicationDirPath(), exclFile);
318 #endif
319 #ifdef Q_OS_UNIX
320     fi.setFile(QStringLiteral(SYSCONFDIR "/%1").arg(Theme::instance()->appName()), exclFile);
321     if (!fi.exists()) {
322         // Prefer to return the preferred path! Only use the fallback location
323         // if the other path does not exist and the fallback is valid.
324         QFileInfo nextToBinary(QCoreApplication::applicationDirPath(), exclFile);
325         if (nextToBinary.exists()) {
326             fi = nextToBinary;
327         } else {
328             // For AppImage, the file might reside under a temporary mount path
329             QDir d(QCoreApplication::applicationDirPath()); // supposed to be /tmp/mount.xyz/usr/bin
330             d.cdUp(); // go out of bin
331             d.cdUp(); // go out of usr
332             if (!d.isRoot()) { // it is really a mountpoint
333                 if (d.cd(QStringLiteral("etc")) && d.cd(Theme::instance()->appName())) {
334                     QFileInfo inMountDir(d, exclFile);
335                     if (inMountDir.exists()) {
336                         fi = inMountDir;
337                     }
338                 };
339             }
340         }
341     }
342 #endif
343 #ifdef Q_OS_MAC
344     // exec path is inside the bundle
345     fi.setFile(QCoreApplication::applicationDirPath(),
346         QLatin1String("../Resources/") + exclFile);
347 #endif
348 
349     return fi.absoluteFilePath();
350 }
351 
backup() const352 QString ConfigFile::backup() const
353 {
354     QString baseFile = configFile();
355     auto versionString = clientVersionString();
356     if (!versionString.isEmpty())
357         versionString.prepend(QLatin1Char('_'));
358     QString backupFile =
359         QStringLiteral("%1.backup_%2%3")
360             .arg(baseFile)
361             .arg(QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMdd_HHmmss")))
362             .arg(versionString);
363 
364     // If this exact file already exists it's most likely that a backup was
365     // already done. (two backup calls directly after each other, potentially
366     // even with source alterations in between!)
367     if (!QFile::exists(backupFile)) {
368         QFile f(baseFile);
369         f.copy(backupFile);
370     }
371     return backupFile;
372 }
373 
configFile() const374 QString ConfigFile::configFile() const
375 {
376     return configPath() + Theme::instance()->configFileName();
377 }
378 
exists()379 bool ConfigFile::exists()
380 {
381     QFile file(configFile());
382     return file.exists();
383 }
384 
defaultConnection() const385 QString ConfigFile::defaultConnection() const
386 {
387     return Theme::instance()->appName();
388 }
389 
storeData(const QString & group,const QString & key,const QVariant & value)390 void ConfigFile::storeData(const QString &group, const QString &key, const QVariant &value)
391 {
392     const QString con(group.isEmpty() ? defaultConnection() : group);
393     QSettings settings(configFile(), QSettings::IniFormat);
394 
395     settings.beginGroup(con);
396     settings.setValue(key, value);
397     settings.sync();
398 }
399 
retrieveData(const QString & group,const QString & key) const400 QVariant ConfigFile::retrieveData(const QString &group, const QString &key) const
401 {
402     const QString con(group.isEmpty() ? defaultConnection() : group);
403     QSettings settings(configFile(), QSettings::IniFormat);
404 
405     settings.beginGroup(con);
406     return settings.value(key);
407 }
408 
removeData(const QString & group,const QString & key)409 void ConfigFile::removeData(const QString &group, const QString &key)
410 {
411     const QString con(group.isEmpty() ? defaultConnection() : group);
412     QSettings settings(configFile(), QSettings::IniFormat);
413 
414     settings.beginGroup(con);
415     settings.remove(key);
416 }
417 
dataExists(const QString & group,const QString & key) const418 bool ConfigFile::dataExists(const QString &group, const QString &key) const
419 {
420     const QString con(group.isEmpty() ? defaultConnection() : group);
421     QSettings settings(configFile(), QSettings::IniFormat);
422 
423     settings.beginGroup(con);
424     return settings.contains(key);
425 }
426 
remotePollInterval(const QString & connection) const427 chrono::milliseconds ConfigFile::remotePollInterval(const QString &connection) const
428 {
429     QString con(connection);
430     if (connection.isEmpty())
431         con = defaultConnection();
432 
433     QSettings settings(configFile(), QSettings::IniFormat);
434     settings.beginGroup(con);
435 
436     auto defaultPollInterval = chrono::milliseconds(DEFAULT_REMOTE_POLL_INTERVAL);
437     auto remoteInterval = millisecondsValue(settings, remotePollIntervalC(), defaultPollInterval);
438     if (remoteInterval < chrono::seconds(5)) {
439         qCWarning(lcConfigFile) << "Remote Interval is less than 5 seconds, reverting to" << DEFAULT_REMOTE_POLL_INTERVAL;
440         remoteInterval = defaultPollInterval;
441     }
442     return remoteInterval;
443 }
444 
setRemotePollInterval(chrono::milliseconds interval,const QString & connection)445 void ConfigFile::setRemotePollInterval(chrono::milliseconds interval, const QString &connection)
446 {
447     QString con(connection);
448     if (connection.isEmpty())
449         con = defaultConnection();
450 
451     if (interval < chrono::seconds(5)) {
452         qCWarning(lcConfigFile) << "Remote Poll interval of " << interval.count() << " is below five seconds.";
453         return;
454     }
455     QSettings settings(configFile(), QSettings::IniFormat);
456     settings.beginGroup(con);
457     settings.setValue(remotePollIntervalC(), qlonglong(interval.count()));
458     settings.sync();
459 }
460 
forceSyncInterval(const QString & connection) const461 chrono::milliseconds ConfigFile::forceSyncInterval(const QString &connection) const
462 {
463     auto pollInterval = remotePollInterval(connection);
464 
465     QString con(connection);
466     if (connection.isEmpty())
467         con = defaultConnection();
468     QSettings settings(configFile(), QSettings::IniFormat);
469     settings.beginGroup(con);
470 
471     auto defaultInterval = chrono::hours(2);
472     auto interval = millisecondsValue(settings, forceSyncIntervalC(), defaultInterval);
473     if (interval < pollInterval) {
474         qCWarning(lcConfigFile) << "Force sync interval is less than the remote poll inteval, reverting to" << pollInterval.count();
475         interval = pollInterval;
476     }
477     return interval;
478 }
479 
fullLocalDiscoveryInterval() const480 chrono::milliseconds OCC::ConfigFile::fullLocalDiscoveryInterval() const
481 {
482     QSettings settings(configFile(), QSettings::IniFormat);
483     settings.beginGroup(defaultConnection());
484     return millisecondsValue(settings, fullLocalDiscoveryIntervalC(), chrono::hours(1));
485 }
486 
notificationRefreshInterval(const QString & connection) const487 chrono::milliseconds ConfigFile::notificationRefreshInterval(const QString &connection) const
488 {
489     QString con(connection);
490     if (connection.isEmpty())
491         con = defaultConnection();
492     QSettings settings(configFile(), QSettings::IniFormat);
493     settings.beginGroup(con);
494 
495     auto defaultInterval = chrono::minutes(5);
496     auto interval = millisecondsValue(settings, notificationRefreshIntervalC(), defaultInterval);
497     if (interval < chrono::minutes(1)) {
498         qCWarning(lcConfigFile) << "Notification refresh interval smaller than one minute, setting to one minute";
499         interval = chrono::minutes(1);
500     }
501     return interval;
502 }
503 
updateCheckInterval(const QString & connection) const504 chrono::milliseconds ConfigFile::updateCheckInterval(const QString &connection) const
505 {
506     QString con(connection);
507     if (connection.isEmpty())
508         con = defaultConnection();
509     QSettings settings(configFile(), QSettings::IniFormat);
510     settings.beginGroup(con);
511 
512     auto defaultInterval = chrono::hours(10);
513     auto interval = millisecondsValue(settings, updateCheckIntervalC(), defaultInterval);
514 
515     auto minInterval = chrono::minutes(5);
516     if (interval < minInterval) {
517         qCWarning(lcConfigFile) << "Update check interval less than five minutes, resetting to 5 minutes";
518         interval = minInterval;
519     }
520     return interval;
521 }
522 
skipUpdateCheck(const QString & connection) const523 bool ConfigFile::skipUpdateCheck(const QString &connection) const
524 {
525     QString con(connection);
526     if (connection.isEmpty())
527         con = defaultConnection();
528 
529 #if 0
530     QVariant fallback = getValue(skipUpdateCheckC(), con, false);
531 #else
532     QVariant fallback = getValue(skipUpdateCheckC(), con, true);
533 #endif
534     fallback = getValue(skipUpdateCheckC(), QString(), fallback);
535 
536     QVariant value = getPolicySetting(skipUpdateCheckC(), fallback);
537 #if 0
538     return value.toBool();
539 #else
540     if ( !value.toBool() )
541         qDebug() << "FreeBSD package disabled the UpdateCheck mechanism.";
542 
543     return true;
544 #endif
545 }
546 
setSkipUpdateCheck(bool skip,const QString & connection)547 void ConfigFile::setSkipUpdateCheck(bool skip, const QString &connection)
548 {
549     QString con(connection);
550     if (connection.isEmpty())
551         con = defaultConnection();
552 
553     QSettings settings(configFile(), QSettings::IniFormat);
554     settings.beginGroup(con);
555 
556     settings.setValue(skipUpdateCheckC(), QVariant(skip));
557     settings.sync();
558 }
559 
updateChannel() const560 QString ConfigFile::updateChannel() const
561 {
562     QString defaultUpdateChannel = QStringLiteral("stable");
563     QString suffix = QString::fromLatin1(MIRALL_STRINGIFY(MIRALL_VERSION_SUFFIX));
564     if (suffix.startsWith(QLatin1String("daily"))
565         || suffix.startsWith(QLatin1String("nightly"))
566         || suffix.startsWith(QLatin1String("alpha"))
567         || suffix.startsWith(QLatin1String("rc"))
568         || suffix.startsWith(QLatin1String("beta"))) {
569         defaultUpdateChannel = QStringLiteral("beta");
570     }
571 
572     QSettings settings(configFile(), QSettings::IniFormat);
573     return settings.value(updateChannelC(), defaultUpdateChannel).toString();
574 }
575 
setUpdateChannel(const QString & channel)576 void ConfigFile::setUpdateChannel(const QString &channel)
577 {
578     QSettings settings(configFile(), QSettings::IniFormat);
579     settings.setValue(updateChannelC(), channel);
580 }
581 
setProxyType(int proxyType,const QString & host,int port,bool needsAuth,const QString & user,const QString & pass)582 void ConfigFile::setProxyType(int proxyType,
583     const QString &host,
584     int port, bool needsAuth,
585     const QString &user,
586     const QString &pass)
587 {
588     QSettings settings(configFile(), QSettings::IniFormat);
589 
590     settings.setValue(proxyTypeC(), proxyType);
591 
592     if (proxyType == QNetworkProxy::HttpProxy || proxyType == QNetworkProxy::Socks5Proxy) {
593         settings.setValue(proxyHostC(), host);
594         settings.setValue(proxyPortC(), port);
595         settings.setValue(proxyNeedsAuthC(), needsAuth);
596         settings.setValue(proxyUserC(), user);
597         settings.setValue(proxyPassC(), pass.toUtf8().toBase64());
598     }
599     settings.sync();
600 }
601 
getValue(const QString & param,const QString & group,const QVariant & defaultValue) const602 QVariant ConfigFile::getValue(const QString &param, const QString &group,
603     const QVariant &defaultValue) const
604 {
605     QVariant systemSetting;
606     if (Utility::isMac()) {
607         QSettings systemSettings(QStringLiteral("/Library/Preferences/" APPLICATION_REV_DOMAIN ".plist"), QSettings::NativeFormat);
608         if (!group.isEmpty()) {
609             systemSettings.beginGroup(group);
610         }
611         systemSetting = systemSettings.value(param, defaultValue);
612     } else if (Utility::isUnix()) {
613         QSettings systemSettings(QStringLiteral(SYSCONFDIR "/%1/%1.conf").arg(Theme::instance()->appName()), QSettings::NativeFormat);
614         if (!group.isEmpty()) {
615             systemSettings.beginGroup(group);
616         }
617         systemSetting = systemSettings.value(param, defaultValue);
618     } else { // Windows
619         QSettings systemSettings(QStringLiteral("HKEY_LOCAL_MACHINE\\Software\\" APPLICATION_VENDOR "\\%1")
620                                      .arg(Theme::instance()->appNameGUI()),
621             QSettings::NativeFormat);
622         if (!group.isEmpty()) {
623             systemSettings.beginGroup(group);
624         }
625         systemSetting = systemSettings.value(param, defaultValue);
626     }
627 
628     QSettings settings(configFile(), QSettings::IniFormat);
629     if (!group.isEmpty())
630         settings.beginGroup(group);
631 
632     return settings.value(param, systemSetting);
633 }
634 
setValue(const QString & key,const QVariant & value)635 void ConfigFile::setValue(const QString &key, const QVariant &value)
636 {
637     QSettings settings(configFile(), QSettings::IniFormat);
638 
639     settings.setValue(key, value);
640 }
641 
proxyType() const642 int ConfigFile::proxyType() const
643 {
644     if (Theme::instance()->forceSystemNetworkProxy()) {
645         return QNetworkProxy::DefaultProxy;
646     }
647     return getValue(proxyTypeC()).toInt();
648 }
649 
proxyHostName() const650 QString ConfigFile::proxyHostName() const
651 {
652     return getValue(proxyHostC()).toString();
653 }
654 
proxyPort() const655 int ConfigFile::proxyPort() const
656 {
657     return getValue(proxyPortC()).toInt();
658 }
659 
proxyNeedsAuth() const660 bool ConfigFile::proxyNeedsAuth() const
661 {
662     return getValue(proxyNeedsAuthC()).toBool();
663 }
664 
proxyUser() const665 QString ConfigFile::proxyUser() const
666 {
667     return getValue(proxyUserC()).toString();
668 }
669 
proxyPassword() const670 QString ConfigFile::proxyPassword() const
671 {
672     QByteArray pass = getValue(proxyPassC()).toByteArray();
673     return QString::fromUtf8(QByteArray::fromBase64(pass));
674 }
675 
useUploadLimit() const676 int ConfigFile::useUploadLimit() const
677 {
678     return getValue(useUploadLimitC(), QString(), 0).toInt();
679 }
680 
useDownloadLimit() const681 int ConfigFile::useDownloadLimit() const
682 {
683     return getValue(useDownloadLimitC(), QString(), 0).toInt();
684 }
685 
setUseUploadLimit(int val)686 void ConfigFile::setUseUploadLimit(int val)
687 {
688     setValue(useUploadLimitC(), val);
689 }
690 
setUseDownloadLimit(int val)691 void ConfigFile::setUseDownloadLimit(int val)
692 {
693     setValue(useDownloadLimitC(), val);
694 }
695 
uploadLimit() const696 int ConfigFile::uploadLimit() const
697 {
698     return getValue(uploadLimitC(), QString(), 10).toInt();
699 }
700 
downloadLimit() const701 int ConfigFile::downloadLimit() const
702 {
703     return getValue(downloadLimitC(), QString(), 80).toInt();
704 }
705 
setUploadLimit(int kbytes)706 void ConfigFile::setUploadLimit(int kbytes)
707 {
708     setValue(uploadLimitC(), kbytes);
709 }
710 
setDownloadLimit(int kbytes)711 void ConfigFile::setDownloadLimit(int kbytes)
712 {
713     setValue(downloadLimitC(), kbytes);
714 }
715 
newBigFolderSizeLimit() const716 QPair<bool, qint64> ConfigFile::newBigFolderSizeLimit() const
717 {
718     auto defaultValue = Theme::instance()->newBigFolderSizeLimit();
719     qint64 value = getValue(newBigFolderSizeLimitC(), QString(), defaultValue).toLongLong();
720     bool use = value >= 0 && getValue(useNewBigFolderSizeLimitC(), QString(), true).toBool();
721     return qMakePair(use, qMax<qint64>(0, value));
722 }
723 
setNewBigFolderSizeLimit(bool isChecked,qint64 mbytes)724 void ConfigFile::setNewBigFolderSizeLimit(bool isChecked, qint64 mbytes)
725 {
726     setValue(newBigFolderSizeLimitC(), mbytes);
727     setValue(useNewBigFolderSizeLimitC(), isChecked);
728 }
729 
confirmExternalStorage() const730 bool ConfigFile::confirmExternalStorage() const
731 {
732     return getValue(confirmExternalStorageC(), QString(), true).toBool();
733 }
734 
setConfirmExternalStorage(bool isChecked)735 void ConfigFile::setConfirmExternalStorage(bool isChecked)
736 {
737     setValue(confirmExternalStorageC(), isChecked);
738 }
739 
moveToTrash() const740 bool ConfigFile::moveToTrash() const
741 {
742     return getValue(moveToTrashC(), QString(), false).toBool();
743 }
744 
setMoveToTrash(bool isChecked)745 void ConfigFile::setMoveToTrash(bool isChecked)
746 {
747     setValue(moveToTrashC(), isChecked);
748 }
749 
promptDeleteFiles() const750 bool ConfigFile::promptDeleteFiles() const
751 {
752     QSettings settings(configFile(), QSettings::IniFormat);
753     return settings.value(promptDeleteC(), true).toBool();
754 }
755 
setPromptDeleteFiles(bool promptDeleteFiles)756 void ConfigFile::setPromptDeleteFiles(bool promptDeleteFiles)
757 {
758     QSettings settings(configFile(), QSettings::IniFormat);
759     settings.setValue(promptDeleteC(), promptDeleteFiles);
760 }
761 
monoIcons() const762 bool ConfigFile::monoIcons() const
763 {
764     QSettings settings(configFile(), QSettings::IniFormat);
765     bool monoDefault = false; // On Mac we want bw by default
766 #ifdef Q_OS_MAC
767     // OEM themes are not obliged to ship mono icons
768     monoDefault = (0 == (strcmp("ownCloud", APPLICATION_NAME)));
769 #endif
770     return settings.value(monoIconsC(), monoDefault).toBool();
771 }
772 
setMonoIcons(bool useMonoIcons)773 void ConfigFile::setMonoIcons(bool useMonoIcons)
774 {
775     QSettings settings(configFile(), QSettings::IniFormat);
776     settings.setValue(monoIconsC(), useMonoIcons);
777 }
778 
crashReporter() const779 bool ConfigFile::crashReporter() const
780 {
781     QSettings settings(configFile(), QSettings::IniFormat);
782     return settings.value(crashReporterC(), true).toBool();
783 }
784 
setCrashReporter(bool enabled)785 void ConfigFile::setCrashReporter(bool enabled)
786 {
787     QSettings settings(configFile(), QSettings::IniFormat);
788     settings.setValue(crashReporterC(), enabled);
789 }
790 
automaticLogDir() const791 bool ConfigFile::automaticLogDir() const
792 {
793     QSettings settings(configFile(), QSettings::IniFormat);
794     return settings.value(automaticLogDirC(), false).toBool();
795 }
796 
setAutomaticLogDir(bool enabled)797 void ConfigFile::setAutomaticLogDir(bool enabled)
798 {
799     QSettings settings(configFile(), QSettings::IniFormat);
800     settings.setValue(automaticLogDirC(), enabled);
801 }
802 
automaticDeleteOldLogsAge() const803 Optional<chrono::hours> ConfigFile::automaticDeleteOldLogsAge() const
804 {
805     QSettings settings(configFile(), QSettings::IniFormat);
806     auto value = settings.value(deleteOldLogsAfterHoursC());
807     if (!value.isValid())
808         return chrono::hours(4);
809     auto hours = value.toInt();
810     if (hours <= 0)
811         return {};
812     return chrono::hours(hours);
813 }
814 
setAutomaticDeleteOldLogsAge(Optional<chrono::hours> expireTime)815 void ConfigFile::setAutomaticDeleteOldLogsAge(Optional<chrono::hours> expireTime)
816 {
817     QSettings settings(configFile(), QSettings::IniFormat);
818     if (!expireTime) {
819         settings.setValue(deleteOldLogsAfterHoursC(), -1);
820     } else {
821         settings.setValue(deleteOldLogsAfterHoursC(), QVariant::fromValue(expireTime->count()));
822     }
823 }
824 
setLogHttp(bool b)825 void ConfigFile::setLogHttp(bool b)
826 {
827     QSettings settings(configFile(), QSettings::IniFormat);
828     settings.setValue(logHttpC(), b);
829     const QSet<QString> rule = { QStringLiteral("sync.httplogger=true") };
830     if (b) {
831         Logger::instance()->addLogRule(rule);
832     } else {
833         Logger::instance()->removeLogRule(rule);
834     }
835 }
836 
logHttp() const837 bool ConfigFile::logHttp() const
838 {
839     QSettings settings(configFile(), QSettings::IniFormat);
840     return settings.value(logHttpC(), false).toBool();
841 }
842 
showExperimentalOptions() const843 bool ConfigFile::showExperimentalOptions() const
844 {
845     QSettings settings(configFile(), QSettings::IniFormat);
846     return settings.value(showExperimentalOptionsC(), false).toBool();
847 }
848 
certificatePath() const849 QString ConfigFile::certificatePath() const
850 {
851     return retrieveData(QString(), certPath()).toString();
852 }
853 
setCertificatePath(const QString & cPath)854 void ConfigFile::setCertificatePath(const QString &cPath)
855 {
856     QSettings settings(configFile(), QSettings::IniFormat);
857     settings.setValue(certPath(), cPath);
858     settings.sync();
859 }
860 
certificatePasswd() const861 QString ConfigFile::certificatePasswd() const
862 {
863     return retrieveData(QString(), certPasswd()).toString();
864 }
865 
setCertificatePasswd(const QString & cPasswd)866 void ConfigFile::setCertificatePasswd(const QString &cPasswd)
867 {
868     QSettings settings(configFile(), QSettings::IniFormat);
869     settings.setValue(certPasswd(), cPasswd);
870     settings.sync();
871 }
872 
clientVersionString() const873 QString ConfigFile::clientVersionString() const
874 {
875     QSettings settings(configFile(), QSettings::IniFormat);
876     return settings.value(clientVersionC(), QString()).toString();
877 }
878 
setClientVersionString(const QString & version)879 void ConfigFile::setClientVersionString(const QString &version)
880 {
881     QSettings settings(configFile(), QSettings::IniFormat);
882     settings.setValue(clientVersionC(), version);
883 }
884 
Q_GLOBAL_STATIC(QString,g_configFileName)885 Q_GLOBAL_STATIC(QString, g_configFileName)
886 
887 std::unique_ptr<QSettings> ConfigFile::settingsWithGroup(const QString &group, QObject *parent)
888 {
889     if (g_configFileName()->isEmpty()) {
890         // cache file name
891         ConfigFile cfg;
892         *g_configFileName() = cfg.configFile();
893     }
894     std::unique_ptr<QSettings> settings(new QSettings(*g_configFileName(), QSettings::IniFormat, parent));
895     settings->beginGroup(group);
896     return settings;
897 }
898 
setupDefaultExcludeFilePaths(ExcludedFiles & excludedFiles)899 void ConfigFile::setupDefaultExcludeFilePaths(ExcludedFiles &excludedFiles)
900 {
901     ConfigFile cfg;
902     QString systemList = cfg.excludeFile(ConfigFile::SystemScope);
903     qCInfo(lcConfigFile) << "Adding system ignore list to csync:" << systemList;
904     excludedFiles.addExcludeFilePath(systemList);
905 
906     QString userList = cfg.excludeFile(ConfigFile::UserScope);
907     if (QFile::exists(userList)) {
908         qCInfo(lcConfigFile) << "Adding user defined ignore list to csync:" << userList;
909         excludedFiles.addExcludeFilePath(userList);
910     }
911 }
912 }
913