File indexing completed on 2024-11-24 04:53:15
0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net> 0002 0003 This file is part of the Trojita Qt IMAP e-mail client, 0004 http://trojita.flaska.net/ 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public License as 0008 published by the Free Software Foundation; either version 2 of 0009 the License or (at your option) version 3 or any later version 0010 accepted by the membership of KDE e.V. (or its successor approved 0011 by the membership of KDE e.V.), which shall act as a proxy 0012 defined in Section 14 of version 3 of the license. 0013 0014 This program is distributed in the hope that it will be useful, 0015 but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0017 GNU General Public License for more details. 0018 0019 You should have received a copy of the GNU General Public License 0020 along with this program. If not, see <http://www.gnu.org/licenses/>. 0021 */ 0022 #include "Utils.h" 0023 #include <QAbstractProxyModel> 0024 #include <QDir> 0025 #include <QFile> 0026 #include <QFileInfo> 0027 #include <QGuiApplication> 0028 #include <QLocale> 0029 #include <QProcess> 0030 #include <QSettings> 0031 #include <QSslCertificate> 0032 #include <QSslKey> 0033 #include <QSysInfo> 0034 0035 #include "Common/Paths.h" 0036 #include "Common/SettingsNames.h" 0037 #include "Imap/Model/NetworkPolicy.h" 0038 0039 #ifdef TROJITA_MOBILITY_SYSTEMINFO 0040 #include <QSystemDeviceInfo> 0041 #endif 0042 0043 namespace Imap { 0044 namespace Mailbox { 0045 0046 QString persistentLogFileName() 0047 { 0048 QString logFileName = Common::writablePath(Common::LOCATION_CACHE); 0049 if (logFileName.isEmpty()) { 0050 logFileName = QDir::homePath() + QLatin1String("/.trojita-connection-log"); 0051 } else { 0052 QDir().mkpath(logFileName); 0053 logFileName += QLatin1String("/trojita-connection-log"); 0054 } 0055 return logFileName; 0056 } 0057 0058 QString systemPlatformVersion() 0059 { 0060 QString os = QLatin1String("" // clazy:exclude=qstring-allocations 0061 #ifdef Q_OS_AIX 0062 "AIX" 0063 #endif 0064 #ifdef Q_OS_BSD4 0065 #ifndef Q_OS_MAC 0066 "AnyBSD4.4" 0067 #endif 0068 #endif 0069 #ifdef Q_OS_BSDI 0070 "BSD/OS" 0071 #endif 0072 #ifdef Q_OS_CYGWIN 0073 "Cygwin" 0074 #endif 0075 #ifdef Q_OS_DGUX 0076 "DG/UX" 0077 #endif 0078 #ifdef Q_OS_DYNIX 0079 "DYNIX/ptx" 0080 #endif 0081 #ifdef Q_OS_FREEBSD 0082 "FreeBSD" 0083 #endif 0084 #ifdef Q_OS_HPUX 0085 "HP-UX" 0086 #endif 0087 #ifdef Q_OS_HURD 0088 "Hurd" 0089 #endif 0090 #ifdef Q_OS_IRIX 0091 "Irix" 0092 #endif 0093 #ifdef Q_OS_LINUX 0094 "Linux" 0095 #endif 0096 #ifdef Q_OS_LYNX 0097 "LynxOS" 0098 #endif 0099 #ifdef Q_OS_MAC 0100 "MacOS" 0101 #endif 0102 #ifdef Q_OS_MSDOS 0103 "MSDOS" 0104 #endif 0105 #ifdef Q_OS_NETBSD 0106 "NetBSD" 0107 #endif 0108 #ifdef Q_OS_OS2 0109 "OS/2" 0110 #endif 0111 #ifdef Q_OS_OPENBSD 0112 "OpenBSD" 0113 #endif 0114 #ifdef Q_OS_OS2EMX 0115 "OS2EMX" 0116 #endif 0117 #ifdef Q_OS_OSF 0118 "HPTru64UNIX" 0119 #endif 0120 #ifdef Q_OS_QNX 0121 "QNXNeutrino" 0122 #endif 0123 #ifdef Q_OS_RELIANT 0124 "ReliantUNIX" 0125 #endif 0126 #ifdef Q_OS_SCO 0127 "SCOOpenServer5" 0128 #endif 0129 #ifdef Q_OS_SOLARIS 0130 "Solaris" 0131 #endif 0132 #ifdef Q_OS_SYMBIAN 0133 "Symbian" 0134 #endif 0135 #ifdef Q_OS_ULTRIX 0136 "Ultrix" 0137 #endif 0138 #ifdef Q_OS_UNIXWARE 0139 "Unixware" 0140 #endif 0141 #ifdef Q_OS_WIN32 0142 "Windows" 0143 #endif 0144 #ifdef Q_OS_WINCE 0145 "WinCE" 0146 #endif 0147 ); 0148 #ifdef Q_OS_UNIX 0149 if (os.isEmpty()) { 0150 os = QStringLiteral("Unix"); 0151 } 0152 #endif 0153 0154 static QString platformVersion; 0155 #ifdef TROJITA_MOBILITY_SYSTEMINFO 0156 if (platformVersion.isEmpty()) { 0157 QtMobility::QSystemDeviceInfo device; 0158 if (device.productName().isEmpty()) { 0159 platformVersion = device.manufacturer() + QLatin1String(" ") + device.model(); 0160 } else { 0161 platformVersion = QString::fromAscii("%1 %2 (%3)").arg(device.manufacturer(), device.productName(), device.model()); 0162 } 0163 } 0164 #endif 0165 if (platformVersion.isEmpty()) { 0166 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) 0167 switch (QSysInfo::WindowsVersion) { 0168 case QSysInfo::WV_32s: 0169 platformVersion = QLatin1String("3.1"); 0170 break; 0171 case QSysInfo::WV_95: 0172 platformVersion = QLatin1String("95"); 0173 break; 0174 case QSysInfo::WV_98: 0175 platformVersion = QLatin1String("98"); 0176 break; 0177 case QSysInfo::WV_Me: 0178 platformVersion = QLatin1String("Me"); 0179 break; 0180 case QSysInfo::WV_NT: 0181 platformVersion = QLatin1String("NT"); 0182 break; 0183 case QSysInfo::WV_2000: 0184 platformVersion = QLatin1String("2000"); 0185 break; 0186 case QSysInfo::WV_XP: 0187 platformVersion = QLatin1String("XP"); 0188 break; 0189 case QSysInfo::WV_2003: 0190 platformVersion = QLatin1String("2003"); 0191 break; 0192 case QSysInfo::WV_VISTA: 0193 platformVersion = QLatin1String("Vista"); 0194 break; 0195 case QSysInfo::WV_WINDOWS7: 0196 platformVersion = QLatin1String("7"); 0197 break; 0198 case QSysInfo::WV_WINDOWS8: 0199 platformVersion = QLatin1String("8"); 0200 break; 0201 case QSysInfo::WV_WINDOWS8_1: 0202 platformVersion = QLatin1String("8.1"); 0203 break; 0204 #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) 0205 case QSysInfo::WV_WINDOWS10: 0206 platformVersion = QLatin1String("10"); 0207 break; 0208 #endif 0209 case QSysInfo::WV_CE: 0210 platformVersion = QLatin1String("CE"); 0211 break; 0212 case QSysInfo::WV_CENET: 0213 platformVersion = QLatin1String("CE.NET"); 0214 break; 0215 case QSysInfo::WV_CE_5: 0216 platformVersion = QLatin1String("CE5.x"); 0217 break; 0218 case QSysInfo::WV_CE_6: 0219 platformVersion = QLatin1String("CE6.x"); 0220 break; 0221 case QSysInfo::WV_DOS_based: 0222 platformVersion = QLatin1String("DOS-based"); 0223 break; 0224 case QSysInfo::WV_NT_based: 0225 platformVersion = QLatin1String("NT-based"); 0226 break; 0227 case QSysInfo::WV_CE_based: 0228 platformVersion = QLatin1String("CE-based"); 0229 break; 0230 #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) 0231 case QSysInfo::WV_None: 0232 platformVersion = QLatin1String("non-Windows"); 0233 break; 0234 #endif 0235 } 0236 #endif 0237 #ifdef Q_OS_MAC 0238 switch (QSysInfo::MacintoshVersion) { 0239 case QSysInfo::MV_9: 0240 platformVersion = QLatin1String("9.0"); 0241 break; 0242 case QSysInfo::MV_10_0: 0243 platformVersion = QLatin1String("X 10.0"); 0244 break; 0245 case QSysInfo::MV_10_1: 0246 platformVersion = QLatin1String("X 10.1"); 0247 break; 0248 case QSysInfo::MV_10_2: 0249 platformVersion = QLatin1String("X 10.2"); 0250 break; 0251 case QSysInfo::MV_10_3: 0252 platformVersion = QLatin1String("X 10.3"); 0253 break; 0254 case QSysInfo::MV_10_4: 0255 platformVersion = QLatin1String("X 10.4"); 0256 break; 0257 case QSysInfo::MV_10_5: 0258 platformVersion = QLatin1String("X 10.5"); 0259 break; 0260 case QSysInfo::MV_10_6: 0261 platformVersion = QLatin1String("X 10.6"); 0262 break; 0263 case QSysInfo::MV_10_7: 0264 platformVersion = QLatin1String("X 10.7"); 0265 break; 0266 case QSysInfo::MV_10_8: 0267 platformVersion = QLatin1String("X 10.8"); 0268 break; 0269 case QSysInfo::MV_10_9: 0270 platformVersion = QLatin1String("X 10.9"); 0271 break; 0272 #if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) 0273 case QSysInfo::MV_10_10: 0274 platformVersion = QLatin1String("X 10.10"); 0275 break; 0276 #endif 0277 case QSysInfo::MV_IOS: 0278 platformVersion = QLatin1String("iOS"); 0279 break; 0280 case QSysInfo::MV_IOS_4_3: 0281 platformVersion = QLatin1String("iOS 4.3"); 0282 break; 0283 case QSysInfo::MV_IOS_5_0: 0284 platformVersion = QLatin1String("iOS 5.0"); 0285 break; 0286 case QSysInfo::MV_IOS_5_1: 0287 platformVersion = QLatin1String("iOS 5.1"); 0288 break; 0289 case QSysInfo::MV_IOS_6_0: 0290 platformVersion = QLatin1String("iOS 6.0"); 0291 break; 0292 case QSysInfo::MV_IOS_6_1: 0293 platformVersion = QLatin1String("iOS 6.1"); 0294 break; 0295 case QSysInfo::MV_IOS_7_0: 0296 platformVersion = QLatin1String("iOS 7.0"); 0297 break; 0298 case QSysInfo::MV_IOS_7_1: 0299 platformVersion = QLatin1String("iOS 7.1"); 0300 break; 0301 #if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) 0302 case QSysInfo::MV_IOS_8_0: 0303 platformVersion = QLatin1String("iOS 8.0"); 0304 break; 0305 #endif 0306 case QSysInfo::MV_Unknown: 0307 platformVersion = QLatin1String("iOS (unknown)"); 0308 break; 0309 #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) 0310 case QSysInfo::MV_None: 0311 platformVersion = QLatin1String("non-Mac"); 0312 break; 0313 #endif 0314 } 0315 #endif 0316 if (platformVersion.isEmpty()) { 0317 // try to call the lsb_release 0318 QProcess *proc = new QProcess(0); 0319 proc->start(QStringLiteral("lsb_release"), QStringList() << QStringLiteral("-s") << QStringLiteral("-d")); 0320 proc->waitForFinished(); 0321 platformVersion = QString::fromLocal8Bit(proc->readAll()).trimmed().replace(QLatin1Char('"'), QString()).replace(QLatin1Char(';'), QLatin1Char(',')); 0322 proc->deleteLater(); 0323 } 0324 } 0325 return QStringLiteral("Qt/%1; %2; %3; %4").arg(QString::fromUtf8(qVersion()), QGuiApplication::platformName(), os, platformVersion); 0326 } 0327 0328 } 0329 0330 /** @short Return current date in the RFC2822 format 0331 0332 This function will return RFC2822-formatted current date with proper timezone information and all the glory details. 0333 It's rather surprising how hard is to do that in Qt -- why is there no accessor for the timezone info which is *already stored* 0334 in QDateTime? 0335 */ 0336 QString formatDateTimeWithTimeZoneAtEnd(const QDateTime &now, const QString &format) 0337 { 0338 // At first, try to find out the offset of the current timezone. That's the part which sucks. 0339 // If there's a more Qt-ish way of doing that, please let me know. 0340 // that's right, both of these command are actually needed 0341 QDateTime nowUtc = now.toUTC(); 0342 nowUtc.setTimeSpec(Qt::LocalTime); 0343 0344 // Got to cast to a signed type to prevent unsigned underflow here. Also go to 64bits because otherwise there'd 0345 // a problem when the value is out-of-range for an int32. 0346 int minutesDifference = (now.toSecsSinceEpoch() - nowUtc.toSecsSinceEpoch()) / 60; 0347 int tzOffsetHours = qAbs(minutesDifference) / 60; 0348 int tzOffsetMinutes = qAbs(minutesDifference) % 60; 0349 // The rest is just a piece of cake now 0350 0351 return QLocale(QStringLiteral("C")).toString(now, format) + 0352 QLatin1String(minutesDifference >= 0 ? "+" : "-") + 0353 QString::number(tzOffsetHours).rightJustified(2, QLatin1Char('0')) + 0354 QString::number(tzOffsetMinutes).rightJustified(2, QLatin1Char('0')); 0355 } 0356 0357 /** @short Return current date in the RFC2822 format */ 0358 QString dateTimeToRfc2822(const QDateTime &now) 0359 { 0360 return formatDateTimeWithTimeZoneAtEnd(now, QStringLiteral("ddd, dd MMM yyyy hh:mm:ss ")); 0361 } 0362 0363 /** @short Return current date in the RFC3501's INTERNALDATE format */ 0364 QString dateTimeToInternalDate(const QDateTime &now) 0365 { 0366 return formatDateTimeWithTimeZoneAtEnd(now, QStringLiteral("dd-MMM-yyyy hh:mm:ss ")); 0367 } 0368 0369 /** @short Migrate old application settings to the new format */ 0370 void migrateSettings(QSettings *settings) 0371 { 0372 using Common::SettingsNames; 0373 0374 // Process the obsolete settings about the "cache backend". This has been changed to "offline stuff" after v0.3. 0375 if (settings->value(SettingsNames::cacheMetadataKey).toString() == SettingsNames::cacheMetadataMemory) { 0376 settings->setValue(SettingsNames::cacheOfflineKey, SettingsNames::cacheOfflineNone); 0377 settings->remove(SettingsNames::cacheMetadataKey); 0378 0379 // Also remove the older values used for cache lifetime management which were not used, but set to zero by default 0380 settings->remove(QStringLiteral("offline.sync")); 0381 settings->remove(QStringLiteral("offline.sync.days")); 0382 settings->remove(QStringLiteral("offline.sync.messages")); 0383 } 0384 0385 // Migrate the "last known certificate" from the full PEM format to just the pubkey 0386 QByteArray lastKnownCertPem = settings->value(SettingsNames::obsImapSslPemCertificate).toByteArray(); 0387 if (!lastKnownCertPem.isEmpty()) { 0388 QList<QSslCertificate> oldChain = QSslCertificate::fromData(lastKnownCertPem, QSsl::Pem); 0389 if (!oldChain.isEmpty()) { 0390 settings->setValue(SettingsNames::imapSslPemPubKey, oldChain[0].publicKey().toPem()); 0391 } 0392 } 0393 settings->remove(SettingsNames::obsImapSslPemCertificate); 0394 0395 // Migration of the sender identities 0396 bool needsIdentityMigration = settings->beginReadArray(SettingsNames::identitiesKey) == 0; 0397 settings->endArray(); 0398 if (needsIdentityMigration) { 0399 QString realName = settings->value(SettingsNames::obsRealNameKey).toString(); 0400 QString email = settings->value(SettingsNames::obsAddressKey).toString(); 0401 if (!realName.isEmpty() || !email.isEmpty()) { 0402 settings->beginWriteArray(SettingsNames::identitiesKey); 0403 settings->setArrayIndex(0); 0404 settings->setValue(SettingsNames::realNameKey, realName); 0405 settings->setValue(SettingsNames::addressKey, email); 0406 settings->endArray(); 0407 settings->remove(Common::SettingsNames::obsRealNameKey); 0408 settings->remove(Common::SettingsNames::obsAddressKey); 0409 } 0410 } 0411 0412 QVariant offlineSetting = settings->value(SettingsNames::obsImapStartOffline); 0413 if (offlineSetting.isValid()) { 0414 settings->setValue(SettingsNames::imapStartMode, offlineSetting.toBool() ? Common::SettingsNames::netOffline : Common::SettingsNames::netOnline); 0415 settings->remove(SettingsNames::obsImapStartOffline); 0416 } 0417 0418 const QString obsImapEnableId = QStringLiteral("imap.enableId"); 0419 auto enableId = settings->value(obsImapEnableId); 0420 if (enableId.isValid() && enableId.toBool() == false && !settings->contains(Common::SettingsNames::interopRevealVersions)) { 0421 settings->setValue(Common::SettingsNames::interopRevealVersions, QVariant(false)); 0422 settings->remove(obsImapEnableId); 0423 } 0424 } 0425 0426 /** @short Return the matching QModelIndex after stripping all proxy layers */ 0427 QModelIndex deproxifiedIndex(const QModelIndex& index) 0428 { 0429 QModelIndex res = index; 0430 while (const QAbstractProxyModel *proxy = qobject_cast<const QAbstractProxyModel *>(res.model())) { 0431 res = proxy->mapToSource(res); 0432 } 0433 return res; 0434 } 0435 0436 /** @short Recursively removes a directory and all its contents 0437 0438 This by some crazy voodoo unintentional 'magic', is almost identical 0439 to a solution by John Schember, which was pointed out by jkt. 0440 0441 http://john.nachtimwald.com/2010/06/08/qt-remove-directory-and-its-contents/ 0442 */ 0443 bool removeRecursively(const QString &dirName) 0444 { 0445 bool result = true; 0446 QDir dir = dirName; 0447 0448 if (dir.exists(dirName)) { 0449 Q_FOREACH(const QFileInfo &fileInfo, 0450 dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, 0451 QDir::DirsFirst)) { 0452 if (fileInfo.isDir()) { 0453 result = removeRecursively(fileInfo.absoluteFilePath()); 0454 } else { 0455 result = QFile::remove(fileInfo.absoluteFilePath()); 0456 } 0457 if (!result) { 0458 return result; 0459 } 0460 } 0461 result = dir.rmdir(dirName); 0462 } 0463 return result; 0464 } 0465 0466 }