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 ¶m, 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