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 "configfile.h"
18 #include "theme.h"
19 #include "version.h"
20 #include "common/utility.h"
21 #include "common/asserts.h"
22 #include "version.h"
23 
24 #include "creds/abstractcredentials.h"
25 #include "creds/keychainchunk.h"
26 
27 #include "csync_exclude.h"
28 
29 #ifndef TOKEN_AUTH_ONLY
30 #include <QWidget>
31 #include <QHeaderView>
32 #endif
33 
34 #include <QCoreApplication>
35 #include <QDir>
36 #include <QFile>
37 #include <QFileInfo>
38 #include <QLoggingCategory>
39 #include <QSettings>
40 #include <QNetworkProxy>
41 #include <QStandardPaths>
42 
43 #define QTLEGACY (QT_VERSION < QT_VERSION_CHECK(5,9,0))
44 
45 #if !(QTLEGACY)
46 #include <QOperatingSystemVersion>
47 #endif
48 
49 #define DEFAULT_REMOTE_POLL_INTERVAL 30000 // default remote poll time in milliseconds
50 #define DEFAULT_MAX_LOG_LINES 20000
51 
52 namespace {
53 static constexpr char showMainDialogAsNormalWindowC[] = "showMainDialogAsNormalWindow";
54 }
55 
56 namespace OCC {
57 
58 namespace chrono = std::chrono;
59 
60 Q_LOGGING_CATEGORY(lcConfigFile, "nextcloud.sync.configfile", QtInfoMsg)
61 
62 //static const char caCertsKeyC[] = "CaCertificates"; only used from account.cpp
63 static const char remotePollIntervalC[] = "remotePollInterval";
64 static const char forceSyncIntervalC[] = "forceSyncInterval";
65 static const char fullLocalDiscoveryIntervalC[] = "fullLocalDiscoveryInterval";
66 static const char notificationRefreshIntervalC[] = "notificationRefreshInterval";
67 static const char monoIconsC[] = "monoIcons";
68 static const char promptDeleteC[] = "promptDeleteAllFiles";
69 static const char crashReporterC[] = "crashReporter";
70 static const char optionalServerNotificationsC[] = "optionalServerNotifications";
71 static const char showInExplorerNavigationPaneC[] = "showInExplorerNavigationPane";
72 static const char skipUpdateCheckC[] = "skipUpdateCheck";
73 static const char autoUpdateCheckC[] = "autoUpdateCheck";
74 static const char updateCheckIntervalC[] = "updateCheckInterval";
75 static const char updateSegmentC[] = "updateSegment";
76 static const char updateChannelC[] = "updateChannel";
77 static const char geometryC[] = "geometry";
78 static const char timeoutC[] = "timeout";
79 static const char chunkSizeC[] = "chunkSize";
80 static const char minChunkSizeC[] = "minChunkSize";
81 static const char maxChunkSizeC[] = "maxChunkSize";
82 static const char targetChunkUploadDurationC[] = "targetChunkUploadDuration";
83 static const char automaticLogDirC[] = "logToTemporaryLogDir";
84 static const char logDirC[] = "logDir";
85 static const char logDebugC[] = "logDebug";
86 static const char logExpireC[] = "logExpire";
87 static const char logFlushC[] = "logFlush";
88 static const char showExperimentalOptionsC[] = "showExperimentalOptions";
89 static const char clientVersionC[] = "clientVersion";
90 
91 static const char proxyHostC[] = "Proxy/host";
92 static const char proxyTypeC[] = "Proxy/type";
93 static const char proxyPortC[] = "Proxy/port";
94 static const char proxyUserC[] = "Proxy/user";
95 static const char proxyPassC[] = "Proxy/pass";
96 static const char proxyNeedsAuthC[] = "Proxy/needsAuth";
97 
98 static const char useUploadLimitC[] = "BWLimit/useUploadLimit";
99 static const char useDownloadLimitC[] = "BWLimit/useDownloadLimit";
100 static const char uploadLimitC[] = "BWLimit/uploadLimit";
101 static const char downloadLimitC[] = "BWLimit/downloadLimit";
102 
103 static const char newBigFolderSizeLimitC[] = "newBigFolderSizeLimit";
104 static const char useNewBigFolderSizeLimitC[] = "useNewBigFolderSizeLimit";
105 static const char confirmExternalStorageC[] = "confirmExternalStorage";
106 static const char moveToTrashC[] = "moveToTrash";
107 
108 const char certPath[] = "http_certificatePath";
109 const char certPasswd[] = "http_certificatePasswd";
110 QString ConfigFile::_confDir = QString();
111 bool ConfigFile::_askedUser = false;
112 
millisecondsValue(const QSettings & setting,const char * key,chrono::milliseconds defaultValue)113 static chrono::milliseconds millisecondsValue(const QSettings &setting, const char *key,
114     chrono::milliseconds defaultValue)
115 {
116     return chrono::milliseconds(setting.value(QLatin1String(key), qlonglong(defaultValue.count())).toLongLong());
117 }
118 
copy_dir_recursive(QString from_dir,QString to_dir)119 bool copy_dir_recursive(QString from_dir, QString to_dir)
120 {
121     QDir dir;
122     dir.setPath(from_dir);
123 
124     from_dir += QDir::separator();
125     to_dir += QDir::separator();
126 
127     foreach (QString copy_file, dir.entryList(QDir::Files)) {
128         QString from = from_dir + copy_file;
129         QString to = to_dir + copy_file;
130 
131         if (QFile::copy(from, to) == false) {
132             return false;
133         }
134     }
135 
136     foreach (QString copy_dir, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
137         QString from = from_dir + copy_dir;
138         QString to = to_dir + copy_dir;
139 
140         if (dir.mkpath(to) == false) {
141             return false;
142         }
143 
144         if (copy_dir_recursive(from, to) == false) {
145             return false;
146         }
147     }
148 
149     return true;
150 }
151 
ConfigFile()152 ConfigFile::ConfigFile()
153 {
154     // QDesktopServices uses the application name to create a config path
155     qApp->setApplicationName(Theme::instance()->appNameGUI());
156 
157     QSettings::setDefaultFormat(QSettings::IniFormat);
158 
159     const QString config = configFile();
160 
161 
162     QSettings settings(config, QSettings::IniFormat);
163     settings.beginGroup(defaultConnection());
164 }
165 
setConfDir(const QString & value)166 bool ConfigFile::setConfDir(const QString &value)
167 {
168     QString dirPath = value;
169     if (dirPath.isEmpty())
170         return false;
171 
172     QFileInfo fi(dirPath);
173     if (!fi.exists()) {
174         QDir().mkpath(dirPath);
175         fi.setFile(dirPath);
176     }
177     if (fi.exists() && fi.isDir()) {
178         dirPath = fi.absoluteFilePath();
179         qCInfo(lcConfigFile) << "Using custom config dir " << dirPath;
180         _confDir = dirPath;
181         return true;
182     }
183     return false;
184 }
185 
optionalServerNotifications() const186 bool ConfigFile::optionalServerNotifications() const
187 {
188     QSettings settings(configFile(), QSettings::IniFormat);
189     return settings.value(QLatin1String(optionalServerNotificationsC), true).toBool();
190 }
191 
showInExplorerNavigationPane() const192 bool ConfigFile::showInExplorerNavigationPane() const
193 {
194     const bool defaultValue =
195 #ifdef Q_OS_WIN
196     #if QTLEGACY
197         (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS10);
198     #else
199         QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10;
200     #endif
201 #else
202         false
203 #endif
204         ;
205     QSettings settings(configFile(), QSettings::IniFormat);
206     return settings.value(QLatin1String(showInExplorerNavigationPaneC), defaultValue).toBool();
207 }
208 
setShowInExplorerNavigationPane(bool show)209 void ConfigFile::setShowInExplorerNavigationPane(bool show)
210 {
211     QSettings settings(configFile(), QSettings::IniFormat);
212     settings.setValue(QLatin1String(showInExplorerNavigationPaneC), show);
213     settings.sync();
214 }
215 
timeout() const216 int ConfigFile::timeout() const
217 {
218     QSettings settings(configFile(), QSettings::IniFormat);
219     return settings.value(QLatin1String(timeoutC), 300).toInt(); // default to 5 min
220 }
221 
chunkSize() const222 qint64 ConfigFile::chunkSize() const
223 {
224     QSettings settings(configFile(), QSettings::IniFormat);
225     return settings.value(QLatin1String(chunkSizeC), 10 * 1000 * 1000).toLongLong(); // default to 10 MB
226 }
227 
maxChunkSize() const228 qint64 ConfigFile::maxChunkSize() const
229 {
230     QSettings settings(configFile(), QSettings::IniFormat);
231     return settings.value(QLatin1String(maxChunkSizeC), 1000 * 1000 * 1000).toLongLong(); // default to 1000 MB
232 }
233 
minChunkSize() const234 qint64 ConfigFile::minChunkSize() const
235 {
236     QSettings settings(configFile(), QSettings::IniFormat);
237     return settings.value(QLatin1String(minChunkSizeC), 1000 * 1000).toLongLong(); // default to 1 MB
238 }
239 
targetChunkUploadDuration() const240 chrono::milliseconds ConfigFile::targetChunkUploadDuration() const
241 {
242     QSettings settings(configFile(), QSettings::IniFormat);
243     return millisecondsValue(settings, targetChunkUploadDurationC, chrono::minutes(1));
244 }
245 
setOptionalServerNotifications(bool show)246 void ConfigFile::setOptionalServerNotifications(bool show)
247 {
248     QSettings settings(configFile(), QSettings::IniFormat);
249     settings.setValue(QLatin1String(optionalServerNotificationsC), show);
250     settings.sync();
251 }
252 
saveGeometry(QWidget * w)253 void ConfigFile::saveGeometry(QWidget *w)
254 {
255 #ifndef TOKEN_AUTH_ONLY
256     ASSERT(!w->objectName().isNull());
257     QSettings settings(configFile(), QSettings::IniFormat);
258     settings.beginGroup(w->objectName());
259     settings.setValue(QLatin1String(geometryC), w->saveGeometry());
260     settings.sync();
261 #endif
262 }
263 
restoreGeometry(QWidget * w)264 void ConfigFile::restoreGeometry(QWidget *w)
265 {
266 #ifndef TOKEN_AUTH_ONLY
267     w->restoreGeometry(getValue(geometryC, w->objectName()).toByteArray());
268 #endif
269 }
270 
saveGeometryHeader(QHeaderView * header)271 void ConfigFile::saveGeometryHeader(QHeaderView *header)
272 {
273 #ifndef TOKEN_AUTH_ONLY
274     if (!header)
275         return;
276     ASSERT(!header->objectName().isEmpty());
277 
278     QSettings settings(configFile(), QSettings::IniFormat);
279     settings.beginGroup(header->objectName());
280     settings.setValue(QLatin1String(geometryC), header->saveState());
281     settings.sync();
282 #endif
283 }
284 
restoreGeometryHeader(QHeaderView * header)285 void ConfigFile::restoreGeometryHeader(QHeaderView *header)
286 {
287 #ifndef TOKEN_AUTH_ONLY
288     if (!header)
289         return;
290     ASSERT(!header->objectName().isNull());
291 
292     QSettings settings(configFile(), QSettings::IniFormat);
293     settings.beginGroup(header->objectName());
294     header->restoreState(settings.value(geometryC).toByteArray());
295 #endif
296 }
297 
getPolicySetting(const QString & setting,const QVariant & defaultValue) const298 QVariant ConfigFile::getPolicySetting(const QString &setting, const QVariant &defaultValue) const
299 {
300     if (Utility::isWindows()) {
301         // check for policies first and return immediately if a value is found.
302         QSettings userPolicy(QString::fromLatin1(R"(HKEY_CURRENT_USER\Software\Policies\%1\%2)")
303                                  .arg(APPLICATION_VENDOR, Theme::instance()->appNameGUI()),
304             QSettings::NativeFormat);
305         if (userPolicy.contains(setting)) {
306             return userPolicy.value(setting);
307         }
308 
309         QSettings machinePolicy(QString::fromLatin1(R"(HKEY_LOCAL_MACHINE\Software\Policies\%1\%2)")
310                                     .arg(APPLICATION_VENDOR, Theme::instance()->appNameGUI()),
311             QSettings::NativeFormat);
312         if (machinePolicy.contains(setting)) {
313             return machinePolicy.value(setting);
314         }
315     }
316     return defaultValue;
317 }
318 
configPath() const319 QString ConfigFile::configPath() const
320 {
321     if (_confDir.isEmpty()) {
322         if (!Utility::isWindows()) {
323             // On Unix, use the AppConfigLocation for the settings, that's configurable with the XDG_CONFIG_HOME env variable.
324             _confDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
325         } else {
326             // On Windows, use AppDataLocation, that's where the roaming data is and where we should store the config file
327              auto newLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
328 
329              // Check if this is the first time loading the new location
330              if (!QFileInfo(newLocation).isDir()) {
331                  // Migrate data to the new locations
332                  auto oldLocation = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
333 
334                  // Only migrate if the old location exists.
335                  if (QFileInfo(oldLocation).isDir()) {
336                      QDir().mkpath(newLocation);
337                      copy_dir_recursive(oldLocation, newLocation);
338                  }
339              }
340             _confDir = newLocation;
341         }
342     }
343     QString dir = _confDir;
344 
345     if (!dir.endsWith(QLatin1Char('/')))
346         dir.append(QLatin1Char('/'));
347     return dir;
348 }
349 
350 static const QLatin1String exclFile("sync-exclude.lst");
351 
excludeFile(Scope scope) const352 QString ConfigFile::excludeFile(Scope scope) const
353 {
354     // prefer sync-exclude.lst, but if it does not exist, check for
355     // exclude.lst for compatibility reasons in the user writeable
356     // directories.
357     QFileInfo fi;
358 
359     switch (scope) {
360     case UserScope:
361         fi.setFile(configPath(), exclFile);
362 
363         if (!fi.isReadable()) {
364             fi.setFile(configPath(), QLatin1String("exclude.lst"));
365         }
366         if (!fi.isReadable()) {
367             fi.setFile(configPath(), exclFile);
368         }
369         return fi.absoluteFilePath();
370     case SystemScope:
371         return ConfigFile::excludeFileFromSystem();
372     }
373 
374     ASSERT(false);
375     return QString();
376 }
377 
excludeFileFromSystem()378 QString ConfigFile::excludeFileFromSystem()
379 {
380     QFileInfo fi;
381 #ifdef Q_OS_WIN
382     fi.setFile(QCoreApplication::applicationDirPath(), exclFile);
383 #endif
384 #ifdef Q_OS_UNIX
385     fi.setFile(QString(SYSCONFDIR "/" + Theme::instance()->appName()), exclFile);
386     if (!fi.exists()) {
387         // Prefer to return the preferred path! Only use the fallback location
388         // if the other path does not exist and the fallback is valid.
389         QFileInfo nextToBinary(QCoreApplication::applicationDirPath(), exclFile);
390         if (nextToBinary.exists()) {
391             fi = nextToBinary;
392         } else {
393             // For AppImage, the file might reside under a temporary mount path
394             QDir d(QCoreApplication::applicationDirPath()); // supposed to be /tmp/mount.xyz/usr/bin
395             d.cdUp(); // go out of bin
396             d.cdUp(); // go out of usr
397             if (!d.isRoot()) { // it is really a mountpoint
398                 if (d.cd("etc") && d.cd(Theme::instance()->appName())) {
399                     QFileInfo inMountDir(d, exclFile);
400                     if (inMountDir.exists()) {
401                         fi = inMountDir;
402                     }
403                 };
404             }
405         }
406     }
407 #endif
408 #ifdef Q_OS_MAC
409     // exec path is inside the bundle
410     fi.setFile(QCoreApplication::applicationDirPath(),
411         QLatin1String("../Resources/") + exclFile);
412 #endif
413 
414     return fi.absoluteFilePath();
415 }
416 
backup() const417 QString ConfigFile::backup() const
418 {
419     QString baseFile = configFile();
420     auto versionString = clientVersionString();
421     if (!versionString.isEmpty())
422         versionString.prepend('_');
423     QString backupFile =
424         QString("%1.backup_%2%3")
425             .arg(baseFile)
426             .arg(QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss"))
427             .arg(versionString);
428 
429     // If this exact file already exists it's most likely that a backup was
430     // already done. (two backup calls directly after each other, potentially
431     // even with source alterations in between!)
432     if (!QFile::exists(backupFile)) {
433         QFile f(baseFile);
434         f.copy(backupFile);
435     }
436     return backupFile;
437 }
438 
configFile() const439 QString ConfigFile::configFile() const
440 {
441     return configPath() + Theme::instance()->configFileName();
442 }
443 
exists()444 bool ConfigFile::exists()
445 {
446     QFile file(configFile());
447     return file.exists();
448 }
449 
defaultConnection() const450 QString ConfigFile::defaultConnection() const
451 {
452     return Theme::instance()->appName();
453 }
454 
storeData(const QString & group,const QString & key,const QVariant & value)455 void ConfigFile::storeData(const QString &group, const QString &key, const QVariant &value)
456 {
457     const QString con(group.isEmpty() ? defaultConnection() : group);
458     QSettings settings(configFile(), QSettings::IniFormat);
459 
460     settings.beginGroup(con);
461     settings.setValue(key, value);
462     settings.sync();
463 }
464 
retrieveData(const QString & group,const QString & key) const465 QVariant ConfigFile::retrieveData(const QString &group, const QString &key) const
466 {
467     const QString con(group.isEmpty() ? defaultConnection() : group);
468     QSettings settings(configFile(), QSettings::IniFormat);
469 
470     settings.beginGroup(con);
471     return settings.value(key);
472 }
473 
removeData(const QString & group,const QString & key)474 void ConfigFile::removeData(const QString &group, const QString &key)
475 {
476     const QString con(group.isEmpty() ? defaultConnection() : group);
477     QSettings settings(configFile(), QSettings::IniFormat);
478 
479     settings.beginGroup(con);
480     settings.remove(key);
481 }
482 
dataExists(const QString & group,const QString & key) const483 bool ConfigFile::dataExists(const QString &group, const QString &key) const
484 {
485     const QString con(group.isEmpty() ? defaultConnection() : group);
486     QSettings settings(configFile(), QSettings::IniFormat);
487 
488     settings.beginGroup(con);
489     return settings.contains(key);
490 }
491 
remotePollInterval(const QString & connection) const492 chrono::milliseconds ConfigFile::remotePollInterval(const QString &connection) const
493 {
494     QString con(connection);
495     if (connection.isEmpty())
496         con = defaultConnection();
497 
498     QSettings settings(configFile(), QSettings::IniFormat);
499     settings.beginGroup(con);
500 
501     auto defaultPollInterval = chrono::milliseconds(DEFAULT_REMOTE_POLL_INTERVAL);
502     auto remoteInterval = millisecondsValue(settings, remotePollIntervalC, defaultPollInterval);
503     if (remoteInterval < chrono::seconds(5)) {
504         qCWarning(lcConfigFile) << "Remote Interval is less than 5 seconds, reverting to" << DEFAULT_REMOTE_POLL_INTERVAL;
505         remoteInterval = defaultPollInterval;
506     }
507     return remoteInterval;
508 }
509 
setRemotePollInterval(chrono::milliseconds interval,const QString & connection)510 void ConfigFile::setRemotePollInterval(chrono::milliseconds interval, const QString &connection)
511 {
512     QString con(connection);
513     if (connection.isEmpty())
514         con = defaultConnection();
515 
516     if (interval < chrono::seconds(5)) {
517         qCWarning(lcConfigFile) << "Remote Poll interval of " << interval.count() << " is below five seconds.";
518         return;
519     }
520     QSettings settings(configFile(), QSettings::IniFormat);
521     settings.beginGroup(con);
522     settings.setValue(QLatin1String(remotePollIntervalC), qlonglong(interval.count()));
523     settings.sync();
524 }
525 
forceSyncInterval(const QString & connection) const526 chrono::milliseconds ConfigFile::forceSyncInterval(const QString &connection) const
527 {
528     auto pollInterval = remotePollInterval(connection);
529 
530     QString con(connection);
531     if (connection.isEmpty())
532         con = defaultConnection();
533     QSettings settings(configFile(), QSettings::IniFormat);
534     settings.beginGroup(con);
535 
536     auto defaultInterval = chrono::hours(2);
537     auto interval = millisecondsValue(settings, forceSyncIntervalC, defaultInterval);
538     if (interval < pollInterval) {
539         qCWarning(lcConfigFile) << "Force sync interval is less than the remote poll inteval, reverting to" << pollInterval.count();
540         interval = pollInterval;
541     }
542     return interval;
543 }
544 
fullLocalDiscoveryInterval() const545 chrono::milliseconds OCC::ConfigFile::fullLocalDiscoveryInterval() const
546 {
547     QSettings settings(configFile(), QSettings::IniFormat);
548     settings.beginGroup(defaultConnection());
549     return millisecondsValue(settings, fullLocalDiscoveryIntervalC, chrono::hours(1));
550 }
551 
notificationRefreshInterval(const QString & connection) const552 chrono::milliseconds ConfigFile::notificationRefreshInterval(const QString &connection) const
553 {
554     QString con(connection);
555     if (connection.isEmpty())
556         con = defaultConnection();
557     QSettings settings(configFile(), QSettings::IniFormat);
558     settings.beginGroup(con);
559 
560     auto defaultInterval = chrono::minutes(5);
561     auto interval = millisecondsValue(settings, notificationRefreshIntervalC, defaultInterval);
562     if (interval < chrono::minutes(1)) {
563         qCWarning(lcConfigFile) << "Notification refresh interval smaller than one minute, setting to one minute";
564         interval = chrono::minutes(1);
565     }
566     return interval;
567 }
568 
updateCheckInterval(const QString & connection) const569 chrono::milliseconds ConfigFile::updateCheckInterval(const QString &connection) const
570 {
571     QString con(connection);
572     if (connection.isEmpty())
573         con = defaultConnection();
574     QSettings settings(configFile(), QSettings::IniFormat);
575     settings.beginGroup(con);
576 
577     auto defaultInterval = chrono::hours(10);
578     auto interval = millisecondsValue(settings, updateCheckIntervalC, defaultInterval);
579 
580     auto minInterval = chrono::minutes(5);
581     if (interval < minInterval) {
582         qCWarning(lcConfigFile) << "Update check interval less than five minutes, resetting to 5 minutes";
583         interval = minInterval;
584     }
585     return interval;
586 }
587 
skipUpdateCheck(const QString & connection) const588 bool ConfigFile::skipUpdateCheck(const QString &connection) const
589 {
590     QString con(connection);
591     if (connection.isEmpty())
592         con = defaultConnection();
593 
594 #if 0
595     QVariant fallback = getValue(QLatin1String(skipUpdateCheckC), con, false);
596 #else
597     QVariant fallback = getValue(QLatin1String(skipUpdateCheckC), con, true);
598 #endif
599     fallback = getValue(QLatin1String(skipUpdateCheckC), QString(), fallback);
600 
601     QVariant value = getPolicySetting(QLatin1String(skipUpdateCheckC), fallback);
602 #if 0
603     return value.toBool();
604 #else
605     if ( !value.toBool() )
606         qDebug() << "FreeBSD package disabled the UpdateCheck mechanism.";
607 
608     return true;
609 #endif
610 }
611 
setSkipUpdateCheck(bool skip,const QString & connection)612 void ConfigFile::setSkipUpdateCheck(bool skip, const QString &connection)
613 {
614     QString con(connection);
615     if (connection.isEmpty())
616         con = defaultConnection();
617 
618     QSettings settings(configFile(), QSettings::IniFormat);
619     settings.beginGroup(con);
620 
621     settings.setValue(QLatin1String(skipUpdateCheckC), QVariant(skip));
622     settings.sync();
623 }
624 
autoUpdateCheck(const QString & connection) const625 bool ConfigFile::autoUpdateCheck(const QString &connection) const
626 {
627     QString con(connection);
628     if (connection.isEmpty())
629         con = defaultConnection();
630 
631     QVariant fallback = getValue(QLatin1String(autoUpdateCheckC), con, true);
632     fallback = getValue(QLatin1String(autoUpdateCheckC), QString(), fallback);
633 
634     QVariant value = getPolicySetting(QLatin1String(autoUpdateCheckC), fallback);
635     return value.toBool();
636 }
637 
setAutoUpdateCheck(bool autoCheck,const QString & connection)638 void ConfigFile::setAutoUpdateCheck(bool autoCheck, const QString &connection)
639 {
640     QString con(connection);
641     if (connection.isEmpty())
642         con = defaultConnection();
643 
644     QSettings settings(configFile(), QSettings::IniFormat);
645     settings.beginGroup(con);
646 
647     settings.setValue(QLatin1String(autoUpdateCheckC), QVariant(autoCheck));
648     settings.sync();
649 }
650 
updateSegment() const651 int ConfigFile::updateSegment() const
652 {
653     QSettings settings(configFile(), QSettings::IniFormat);
654     int segment = settings.value(QLatin1String(updateSegmentC), -1).toInt();
655 
656     // Invalid? (Unset at the very first launch)
657     if(segment < 0 || segment > 99) {
658         // Save valid segment value, normally has to be done only once.
659         segment = Utility::rand() % 99;
660         settings.setValue(QLatin1String(updateSegmentC), segment);
661     }
662 
663     return segment;
664 }
665 
updateChannel() const666 QString ConfigFile::updateChannel() const
667 {
668     QString defaultUpdateChannel = QStringLiteral("stable");
669     QString suffix = QString::fromLatin1(MIRALL_STRINGIFY(MIRALL_VERSION_SUFFIX));
670     if (suffix.startsWith("daily")
671         || suffix.startsWith("nightly")
672         || suffix.startsWith("alpha")
673         || suffix.startsWith("rc")
674         || suffix.startsWith("beta")) {
675         defaultUpdateChannel = QStringLiteral("beta");
676     }
677 
678     QSettings settings(configFile(), QSettings::IniFormat);
679     return settings.value(QLatin1String(updateChannelC), defaultUpdateChannel).toString();
680 }
681 
setUpdateChannel(const QString & channel)682 void ConfigFile::setUpdateChannel(const QString &channel)
683 {
684     QSettings settings(configFile(), QSettings::IniFormat);
685     settings.setValue(QLatin1String(updateChannelC), channel);
686 }
687 
setProxyType(int proxyType,const QString & host,int port,bool needsAuth,const QString & user,const QString & pass)688 void ConfigFile::setProxyType(int proxyType,
689     const QString &host,
690     int port, bool needsAuth,
691     const QString &user,
692     const QString &pass)
693 {
694     QSettings settings(configFile(), QSettings::IniFormat);
695 
696     settings.setValue(QLatin1String(proxyTypeC), proxyType);
697 
698     if (proxyType == QNetworkProxy::HttpProxy || proxyType == QNetworkProxy::Socks5Proxy) {
699         settings.setValue(QLatin1String(proxyHostC), host);
700         settings.setValue(QLatin1String(proxyPortC), port);
701         settings.setValue(QLatin1String(proxyNeedsAuthC), needsAuth);
702         settings.setValue(QLatin1String(proxyUserC), user);
703 
704         if (pass.isEmpty()) {
705             // Security: Don't keep password in config file
706             settings.remove(QLatin1String(proxyPassC));
707 
708             // Delete password from keychain
709             auto job = new KeychainChunk::DeleteJob(keychainProxyPasswordKey());
710             job->exec();
711         } else {
712             // Write password to keychain
713             auto job = new KeychainChunk::WriteJob(keychainProxyPasswordKey(), pass.toUtf8());
714             if (job->exec()) {
715                 // Security: Don't keep password in config file
716                 settings.remove(QLatin1String(proxyPassC));
717             }
718         }
719     }
720     settings.sync();
721 }
722 
getValue(const QString & param,const QString & group,const QVariant & defaultValue) const723 QVariant ConfigFile::getValue(const QString &param, const QString &group,
724     const QVariant &defaultValue) const
725 {
726     QVariant systemSetting;
727     if (Utility::isMac()) {
728         QSettings systemSettings(QLatin1String("/Library/Preferences/" APPLICATION_REV_DOMAIN ".plist"), QSettings::NativeFormat);
729         if (!group.isEmpty()) {
730             systemSettings.beginGroup(group);
731         }
732         systemSetting = systemSettings.value(param, defaultValue);
733     } else if (Utility::isUnix()) {
734         QSettings systemSettings(QString(SYSCONFDIR "/%1/%1.conf").arg(Theme::instance()->appName()), QSettings::NativeFormat);
735         if (!group.isEmpty()) {
736             systemSettings.beginGroup(group);
737         }
738         systemSetting = systemSettings.value(param, defaultValue);
739     } else { // Windows
740         QSettings systemSettings(QString::fromLatin1(R"(HKEY_LOCAL_MACHINE\Software\%1\%2)")
741                                      .arg(APPLICATION_VENDOR, Theme::instance()->appNameGUI()),
742             QSettings::NativeFormat);
743         if (!group.isEmpty()) {
744             systemSettings.beginGroup(group);
745         }
746         systemSetting = systemSettings.value(param, defaultValue);
747     }
748 
749     QSettings settings(configFile(), QSettings::IniFormat);
750     if (!group.isEmpty())
751         settings.beginGroup(group);
752 
753     return settings.value(param, systemSetting);
754 }
755 
setValue(const QString & key,const QVariant & value)756 void ConfigFile::setValue(const QString &key, const QVariant &value)
757 {
758     QSettings settings(configFile(), QSettings::IniFormat);
759 
760     settings.setValue(key, value);
761 }
762 
proxyType() const763 int ConfigFile::proxyType() const
764 {
765     if (Theme::instance()->forceSystemNetworkProxy()) {
766         return QNetworkProxy::DefaultProxy;
767     }
768     return getValue(QLatin1String(proxyTypeC)).toInt();
769 }
770 
proxyHostName() const771 QString ConfigFile::proxyHostName() const
772 {
773     return getValue(QLatin1String(proxyHostC)).toString();
774 }
775 
proxyPort() const776 int ConfigFile::proxyPort() const
777 {
778     return getValue(QLatin1String(proxyPortC)).toInt();
779 }
780 
proxyNeedsAuth() const781 bool ConfigFile::proxyNeedsAuth() const
782 {
783     return getValue(QLatin1String(proxyNeedsAuthC)).toBool();
784 }
785 
proxyUser() const786 QString ConfigFile::proxyUser() const
787 {
788     return getValue(QLatin1String(proxyUserC)).toString();
789 }
790 
proxyPassword() const791 QString ConfigFile::proxyPassword() const
792 {
793     QByteArray passEncoded = getValue(proxyPassC).toByteArray();
794     auto pass = QString::fromUtf8(QByteArray::fromBase64(passEncoded));
795     passEncoded.clear();
796 
797     const auto key = keychainProxyPasswordKey();
798 
799     if (!pass.isEmpty()) {
800         // Security: Migrate password from config file to keychain
801         auto job = new KeychainChunk::WriteJob(key, pass.toUtf8());
802         if (job->exec()) {
803             QSettings settings(configFile(), QSettings::IniFormat);
804             settings.remove(QLatin1String(proxyPassC));
805             qCInfo(lcConfigFile()) << "Migrated proxy password to keychain";
806         }
807     } else {
808         // Read password from keychain
809         auto job = new KeychainChunk::ReadJob(key);
810         if (job->exec()) {
811             pass = job->textData();
812         }
813     }
814 
815     return pass;
816 }
817 
keychainProxyPasswordKey() const818 QString ConfigFile::keychainProxyPasswordKey() const
819 {
820     return QString::fromLatin1("proxy-password");
821 }
822 
useUploadLimit() const823 int ConfigFile::useUploadLimit() const
824 {
825     return getValue(useUploadLimitC, QString(), 0).toInt();
826 }
827 
useDownloadLimit() const828 int ConfigFile::useDownloadLimit() const
829 {
830     return getValue(useDownloadLimitC, QString(), 0).toInt();
831 }
832 
setUseUploadLimit(int val)833 void ConfigFile::setUseUploadLimit(int val)
834 {
835     setValue(useUploadLimitC, val);
836 }
837 
setUseDownloadLimit(int val)838 void ConfigFile::setUseDownloadLimit(int val)
839 {
840     setValue(useDownloadLimitC, val);
841 }
842 
uploadLimit() const843 int ConfigFile::uploadLimit() const
844 {
845     return getValue(uploadLimitC, QString(), 10).toInt();
846 }
847 
downloadLimit() const848 int ConfigFile::downloadLimit() const
849 {
850     return getValue(downloadLimitC, QString(), 80).toInt();
851 }
852 
setUploadLimit(int kbytes)853 void ConfigFile::setUploadLimit(int kbytes)
854 {
855     setValue(uploadLimitC, kbytes);
856 }
857 
setDownloadLimit(int kbytes)858 void ConfigFile::setDownloadLimit(int kbytes)
859 {
860     setValue(downloadLimitC, kbytes);
861 }
862 
newBigFolderSizeLimit() const863 QPair<bool, qint64> ConfigFile::newBigFolderSizeLimit() const
864 {
865     auto defaultValue = Theme::instance()->newBigFolderSizeLimit();
866     const auto fallback = getValue(newBigFolderSizeLimitC, QString(), defaultValue).toLongLong();
867     const auto value = getPolicySetting(QLatin1String(newBigFolderSizeLimitC), fallback).toLongLong();
868     const bool use = value >= 0 && useNewBigFolderSizeLimit();
869     return qMakePair(use, qMax<qint64>(0, value));
870 }
871 
setNewBigFolderSizeLimit(bool isChecked,qint64 mbytes)872 void ConfigFile::setNewBigFolderSizeLimit(bool isChecked, qint64 mbytes)
873 {
874     setValue(newBigFolderSizeLimitC, mbytes);
875     setValue(useNewBigFolderSizeLimitC, isChecked);
876 }
877 
confirmExternalStorage() const878 bool ConfigFile::confirmExternalStorage() const
879 {
880     const auto fallback = getValue(confirmExternalStorageC, QString(), true);
881     return getPolicySetting(QLatin1String(confirmExternalStorageC), fallback).toBool();
882 }
883 
useNewBigFolderSizeLimit() const884 bool ConfigFile::useNewBigFolderSizeLimit() const
885 {
886     const auto fallback = getValue(useNewBigFolderSizeLimitC, QString(), true);
887     return getPolicySetting(QLatin1String(useNewBigFolderSizeLimitC), fallback).toBool();
888 }
889 
setConfirmExternalStorage(bool isChecked)890 void ConfigFile::setConfirmExternalStorage(bool isChecked)
891 {
892     setValue(confirmExternalStorageC, isChecked);
893 }
894 
moveToTrash() const895 bool ConfigFile::moveToTrash() const
896 {
897     return getValue(moveToTrashC, QString(), false).toBool();
898 }
899 
setMoveToTrash(bool isChecked)900 void ConfigFile::setMoveToTrash(bool isChecked)
901 {
902     setValue(moveToTrashC, isChecked);
903 }
904 
showMainDialogAsNormalWindow() const905 bool ConfigFile::showMainDialogAsNormalWindow() const {
906     return getValue(showMainDialogAsNormalWindowC, {}, false).toBool();
907 }
908 
promptDeleteFiles() const909 bool ConfigFile::promptDeleteFiles() const
910 {
911     QSettings settings(configFile(), QSettings::IniFormat);
912     return settings.value(QLatin1String(promptDeleteC), false).toBool();
913 }
914 
setPromptDeleteFiles(bool promptDeleteFiles)915 void ConfigFile::setPromptDeleteFiles(bool promptDeleteFiles)
916 {
917     QSettings settings(configFile(), QSettings::IniFormat);
918     settings.setValue(QLatin1String(promptDeleteC), promptDeleteFiles);
919 }
920 
monoIcons() const921 bool ConfigFile::monoIcons() const
922 {
923     QSettings settings(configFile(), QSettings::IniFormat);
924     bool monoDefault = false; // On Mac we want bw by default
925 #ifdef Q_OS_MAC
926     // OEM themes are not obliged to ship mono icons
927     monoDefault = QByteArrayLiteral("Nextcloud") == QByteArrayLiteral(APPLICATION_NAME);
928 #endif
929     return settings.value(QLatin1String(monoIconsC), monoDefault).toBool();
930 }
931 
setMonoIcons(bool useMonoIcons)932 void ConfigFile::setMonoIcons(bool useMonoIcons)
933 {
934     QSettings settings(configFile(), QSettings::IniFormat);
935     settings.setValue(QLatin1String(monoIconsC), useMonoIcons);
936 }
937 
crashReporter() const938 bool ConfigFile::crashReporter() const
939 {
940     QSettings settings(configFile(), QSettings::IniFormat);
941     const auto fallback = settings.value(QLatin1String(crashReporterC), true);
942     return getPolicySetting(QLatin1String(crashReporterC), fallback).toBool();
943 }
944 
setCrashReporter(bool enabled)945 void ConfigFile::setCrashReporter(bool enabled)
946 {
947     QSettings settings(configFile(), QSettings::IniFormat);
948     settings.setValue(QLatin1String(crashReporterC), enabled);
949 }
950 
automaticLogDir() const951 bool ConfigFile::automaticLogDir() const
952 {
953     QSettings settings(configFile(), QSettings::IniFormat);
954     return settings.value(QLatin1String(automaticLogDirC), false).toBool();
955 }
956 
setAutomaticLogDir(bool enabled)957 void ConfigFile::setAutomaticLogDir(bool enabled)
958 {
959     QSettings settings(configFile(), QSettings::IniFormat);
960     settings.setValue(QLatin1String(automaticLogDirC), enabled);
961 }
962 
logDir() const963 QString ConfigFile::logDir() const
964 {
965     const auto defaultLogDir = QString(configPath() + QStringLiteral("/logs"));
966     QSettings settings(configFile(), QSettings::IniFormat);
967     return settings.value(QLatin1String(logDirC), defaultLogDir).toString();
968 }
969 
setLogDir(const QString & dir)970 void ConfigFile::setLogDir(const QString &dir)
971 {
972     QSettings settings(configFile(), QSettings::IniFormat);
973     settings.setValue(QLatin1String(logDirC), dir);
974 }
975 
logDebug() const976 bool ConfigFile::logDebug() const
977 {
978     QSettings settings(configFile(), QSettings::IniFormat);
979     return settings.value(QLatin1String(logDebugC), true).toBool();
980 }
981 
setLogDebug(bool enabled)982 void ConfigFile::setLogDebug(bool enabled)
983 {
984     QSettings settings(configFile(), QSettings::IniFormat);
985     settings.setValue(QLatin1String(logDebugC), enabled);
986 }
987 
logExpire() const988 int ConfigFile::logExpire() const
989 {
990     QSettings settings(configFile(), QSettings::IniFormat);
991     return settings.value(QLatin1String(logExpireC), 24).toInt();
992 }
993 
setLogExpire(int hours)994 void ConfigFile::setLogExpire(int hours)
995 {
996     QSettings settings(configFile(), QSettings::IniFormat);
997     settings.setValue(QLatin1String(logExpireC), hours);
998 }
999 
logFlush() const1000 bool ConfigFile::logFlush() const
1001 {
1002     QSettings settings(configFile(), QSettings::IniFormat);
1003     return settings.value(QLatin1String(logFlushC), false).toBool();
1004 }
1005 
setLogFlush(bool enabled)1006 void ConfigFile::setLogFlush(bool enabled)
1007 {
1008     QSettings settings(configFile(), QSettings::IniFormat);
1009     settings.setValue(QLatin1String(logFlushC), enabled);
1010 }
1011 
showExperimentalOptions() const1012 bool ConfigFile::showExperimentalOptions() const
1013 {
1014     QSettings settings(configFile(), QSettings::IniFormat);
1015     return settings.value(QLatin1String(showExperimentalOptionsC), false).toBool();
1016 }
1017 
certificatePath() const1018 QString ConfigFile::certificatePath() const
1019 {
1020     return retrieveData(QString(), QLatin1String(certPath)).toString();
1021 }
1022 
setCertificatePath(const QString & cPath)1023 void ConfigFile::setCertificatePath(const QString &cPath)
1024 {
1025     QSettings settings(configFile(), QSettings::IniFormat);
1026     settings.setValue(QLatin1String(certPath), cPath);
1027     settings.sync();
1028 }
1029 
certificatePasswd() const1030 QString ConfigFile::certificatePasswd() const
1031 {
1032     return retrieveData(QString(), QLatin1String(certPasswd)).toString();
1033 }
1034 
setCertificatePasswd(const QString & cPasswd)1035 void ConfigFile::setCertificatePasswd(const QString &cPasswd)
1036 {
1037     QSettings settings(configFile(), QSettings::IniFormat);
1038     settings.setValue(QLatin1String(certPasswd), cPasswd);
1039     settings.sync();
1040 }
1041 
clientVersionString() const1042 QString ConfigFile::clientVersionString() const
1043 {
1044     QSettings settings(configFile(), QSettings::IniFormat);
1045     return settings.value(QLatin1String(clientVersionC), QString()).toString();
1046 }
1047 
setClientVersionString(const QString & version)1048 void ConfigFile::setClientVersionString(const QString &version)
1049 {
1050     QSettings settings(configFile(), QSettings::IniFormat);
1051     settings.setValue(QLatin1String(clientVersionC), version);
1052 }
1053 
Q_GLOBAL_STATIC(QString,g_configFileName)1054 Q_GLOBAL_STATIC(QString, g_configFileName)
1055 
1056 std::unique_ptr<QSettings> ConfigFile::settingsWithGroup(const QString &group, QObject *parent)
1057 {
1058     if (g_configFileName()->isEmpty()) {
1059         // cache file name
1060         ConfigFile cfg;
1061         *g_configFileName() = cfg.configFile();
1062     }
1063     std::unique_ptr<QSettings> settings(new QSettings(*g_configFileName(), QSettings::IniFormat, parent));
1064     settings->beginGroup(group);
1065     return settings;
1066 }
1067 
setupDefaultExcludeFilePaths(ExcludedFiles & excludedFiles)1068 void ConfigFile::setupDefaultExcludeFilePaths(ExcludedFiles &excludedFiles)
1069 {
1070     ConfigFile cfg;
1071     QString systemList = cfg.excludeFile(ConfigFile::SystemScope);
1072     QString userList = cfg.excludeFile(ConfigFile::UserScope);
1073 
1074     if (!QFile::exists(userList)) {
1075         qCInfo(lcConfigFile) << "User defined ignore list does not exist:" << userList;
1076         if (!QFile::copy(systemList, userList)) {
1077             qCInfo(lcConfigFile) << "Could not copy over default list to:" << userList;
1078         }
1079     }
1080 
1081     if (!QFile::exists(userList)) {
1082         qCInfo(lcConfigFile) << "Adding system ignore list to csync:" << systemList;
1083         excludedFiles.addExcludeFilePath(systemList);
1084     } else {
1085         qCInfo(lcConfigFile) << "Adding user defined ignore list to csync:" << userList;
1086         excludedFiles.addExcludeFilePath(userList);
1087     }
1088 }
1089 }
1090