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 }