File indexing completed on 2024-06-02 05:12:11
0001 /* 0002 SPDX-FileCopyrightText: 2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0003 SPDX-FileCopyrightText: 2021 Dawid Wróbel <me@dawidwrobel.com> 0004 SPDX-FileCopyrightText: 2022 Thomas Baumgart <tbaumgart@kde.org> 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "xmlstorage.h" 0009 0010 #include <memory> 0011 #include <config-kmymoney.h> 0012 0013 // ---------------------------------------------------------------------------- 0014 // QT Includes 0015 0016 #include <QTimer> 0017 #include <QFile> 0018 #include <QTemporaryFile> 0019 #include <QFileDialog> 0020 #include <QRegularExpression> 0021 #include <QRegularExpressionMatch> 0022 0023 // ---------------------------------------------------------------------------- 0024 // KDE Includes 0025 0026 #include <KPluginFactory> 0027 #include <KActionCollection> 0028 #include <KLocalizedString> 0029 #include <KMessageBox> 0030 #include <KCompressionDevice> 0031 #include <KIO/StoredTransferJob> 0032 #include <KBackup> 0033 0034 // ---------------------------------------------------------------------------- 0035 // Project Includes 0036 0037 #include "appinterface.h" 0038 #include "icons.h" 0039 #include "kgpgfile.h" 0040 #include "kgpgkeyselectiondlg.h" 0041 #include "kmymoneyenums.h" 0042 #include "kmymoneysettings.h" 0043 #include "kmymoneyutils.h" 0044 #include "mymoneyanonwriter.h" 0045 #include "mymoneyexception.h" 0046 #include "mymoneyfile.h" 0047 #include "mymoneystoragebin.h" 0048 #include "mymoneyxmlreader.h" 0049 #include "mymoneyxmlwriter.h" 0050 #include "viewinterface.h" 0051 0052 #include "kmmyesno.h" 0053 0054 using namespace Icons; 0055 0056 static constexpr KCompressionDevice::CompressionType const& COMPRESSION_TYPE = KCompressionDevice::GZip; 0057 // static constexpr char recoveryKeyId[] = "0xD2B08440"; 0058 static constexpr char recoveryKeyId[] = "59B0F826D2B08440"; 0059 0060 // define the default period to warn about an expiring recoverkey to 30 days 0061 // but allows to override this setting during build time 0062 #ifndef RECOVER_KEY_EXPIRATION_WARNING 0063 #define RECOVER_KEY_EXPIRATION_WARNING 30 0064 #endif 0065 0066 XMLStorage::XMLStorage(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args) : 0067 KMyMoneyPlugin::Plugin(parent, metaData, args) 0068 { 0069 // For information, announce that we have been loaded. 0070 qDebug("Plugins: xmlstorage loaded"); 0071 checkRecoveryKeyValidity(); 0072 } 0073 0074 XMLStorage::~XMLStorage() 0075 { 0076 qDebug("Plugins: xmlstorage unloaded"); 0077 } 0078 0079 bool XMLStorage::open(const QUrl &url) 0080 { 0081 fileUrl.clear(); 0082 0083 if (url.scheme() == QLatin1String("sql")) 0084 return false; 0085 0086 QString fileName; 0087 auto downloadedFile = false; 0088 if (url.isLocalFile()) { 0089 fileName = url.toLocalFile(); 0090 } else { 0091 fileName = KMyMoneyUtils::downloadFile(url); 0092 downloadedFile = true; 0093 } 0094 0095 if (!KMyMoneyUtils::fileExists(QUrl::fromLocalFile(fileName))) 0096 throw MYMONEYEXCEPTION(QString::fromLatin1("Error opening the file.\n" 0097 "Requested file: '%1'.\n" 0098 "Downloaded file: '%2'").arg(qPrintable(url.url()), fileName)); 0099 0100 0101 QFile file(fileName); 0102 if (!file.open(QIODevice::ReadOnly)) 0103 throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot read the file: %1").arg(fileName)); 0104 0105 QByteArray qbaFileHeader(2, '\0'); 0106 const auto sFileToShort = QString::fromLatin1("File %1 is too short.").arg(fileName); 0107 if (file.read(qbaFileHeader.data(), 2) != 2) 0108 throw MYMONEYEXCEPTION(sFileToShort); 0109 0110 file.close(); 0111 0112 // There's a problem with the KFilterDev and KGPGFile classes: 0113 // One supports the at(n) member but not ungetch() together with 0114 // read() and the other does not provide an at(n) method but 0115 // supports read() that considers the ungetch() buffer. QFile 0116 // supports everything so this is not a problem. We solve the problem 0117 // for now by keeping track of which method can be used. 0118 auto haveAt = true; 0119 auto isEncrypted = false; 0120 0121 QIODevice* qfile = nullptr; 0122 QString sFileHeader(qbaFileHeader); 0123 if (sFileHeader == QString("\037\213")) { // gzipped? 0124 qfile = new KCompressionDevice(fileName, COMPRESSION_TYPE); 0125 } else if (sFileHeader == QString("--") || // PGP ASCII armored? 0126 sFileHeader == QString("\205\001") || // PGP binary? 0127 sFileHeader == QString("\205\002")) { // PGP binary? 0128 #ifdef ENABLE_GPG 0129 if (KGPGFile::GPGAvailable()) { 0130 qfile = new KGPGFile(fileName); 0131 haveAt = false; 0132 isEncrypted = true; 0133 } else 0134 #endif 0135 { 0136 throw MYMONEYEXCEPTION(QString::fromLatin1("GPG is not available for decryption of file <b>%1</b>").arg(fileName)); 0137 } 0138 } else { 0139 // we can't use file directly, as we delete qfile later on 0140 qfile = new QFile(file.fileName()); 0141 } 0142 0143 if (!qfile->open(QIODevice::ReadOnly)) { 0144 delete qfile; 0145 throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot read the file: %1").arg(fileName)); 0146 } 0147 0148 qbaFileHeader.resize(8); 0149 if (qfile->read(qbaFileHeader.data(), 8) != 8) 0150 throw MYMONEYEXCEPTION(sFileToShort); 0151 0152 if (haveAt) 0153 qfile->seek(0); 0154 else 0155 ungetString(qfile, qbaFileHeader.data(), 8); 0156 0157 // Ok, we got the first block of 8 bytes. Read in the two 0158 // unsigned long int's by preserving endianness. This is 0159 // achieved by reading them through a QDataStream object 0160 qint32 magic0, magic1; 0161 QDataStream s(&qbaFileHeader, QIODevice::ReadOnly); 0162 s >> magic0; 0163 s >> magic1; 0164 0165 // If both magic numbers match (we actually read in the 0166 // text 'KMyMoney' then we assume a binary file and 0167 // construct a reader for it. Otherwise, we construct 0168 // an XML reader object. 0169 // 0170 // The expression magic0 < 30 is only used to create 0171 // a binary reader if we assume an old binary file. This 0172 // should be removed at some point. An alternative is to 0173 // check the beginning of the file against an pattern 0174 // of the XML file (e.g. '?<xml' ). 0175 if ((magic0 == MAGIC_0_50 && magic1 == MAGIC_0_51) || 0176 magic0 < 30) { 0177 // we do not support this file format anymore 0178 throw MYMONEYEXCEPTION(QString::fromLatin1("<qt>File <b>%1</b> contains the old binary format used by KMyMoney. Please use an older version of KMyMoney (0.8.x) that still supports this format to convert it to the new XML based format.</qt>").arg(fileName)); 0179 } 0180 0181 // Scan the first 70 bytes to see if we find something 0182 // we know. For now, we support our own XML format and 0183 // GNUCash XML format. If the file is smaller, then it 0184 // contains no valid data and we reject it anyway. 0185 qbaFileHeader.resize(70); 0186 if (qfile->read(qbaFileHeader.data(), 70) != 70) 0187 throw MYMONEYEXCEPTION(sFileToShort); 0188 0189 if (haveAt) 0190 qfile->seek(0); 0191 else 0192 ungetString(qfile, qbaFileHeader.data(), 70); 0193 0194 static const QRegularExpression kmyexp(QLatin1String("<!DOCTYPE KMYMONEY-FILE>")); 0195 QByteArray txt(qbaFileHeader, 70); 0196 const auto docType(kmyexp.match(txt)); 0197 if (!docType.hasMatch()) 0198 return false; 0199 0200 MyMoneyXmlReader reader; 0201 reader.setFile(MyMoneyFile::instance()); 0202 reader.read(qfile); 0203 0204 qfile->close(); 0205 delete qfile; 0206 0207 // if a temporary file was downloaded, then it will be removed 0208 // with the next call. Otherwise, it stays untouched on the local 0209 // filesystem. 0210 if (downloadedFile) 0211 QFile::remove(fileName); 0212 0213 // make sure we setup the encryption key correctly 0214 if (isEncrypted) { 0215 if (MyMoneyFile::instance()->value("kmm-encryption-key").isEmpty()) { 0216 // encapsulate transactions to the engine to be able to commit/rollback 0217 MyMoneyFileTransaction ft; 0218 MyMoneyFile::instance()->setValue("kmm-encryption-key", KMyMoneySettings::gpgRecipientList().join(",")); 0219 ft.commit(); 0220 } 0221 } 0222 0223 fileUrl = url; 0224 //write the directory used for this file as the default one for next time. 0225 appInterface()->writeLastUsedDir(url.toDisplayString(QUrl::RemoveFilename | QUrl::PreferLocalFile | QUrl::StripTrailingSlash)); 0226 0227 return true; 0228 } 0229 0230 QUrl XMLStorage::openUrl() const 0231 { 0232 return fileUrl; 0233 } 0234 0235 bool XMLStorage::save(const QUrl &url) 0236 { 0237 QString filename = url.toLocalFile(); 0238 0239 if (!appInterface()->fileOpen()) { 0240 KMessageBox::error(nullptr, i18n("Tried to access a file when it has not been opened")); 0241 return false; 0242 } 0243 0244 std::unique_ptr<MyMoneyXmlWriter> storageWriter; 0245 0246 // If this file ends in ".ANON.XML" then this should be written using the 0247 // anonymous writer. 0248 bool plaintext = filename.right(4).toLower() == ".xml"; 0249 if (filename.right(9).toLower() == ".anon.xml") 0250 storageWriter = std::make_unique<MyMoneyAnonWriter>(); 0251 else 0252 storageWriter = std::make_unique<MyMoneyXmlWriter>(); 0253 0254 QString keyList; 0255 if (!appInterface()->filenameURL().isEmpty()) 0256 keyList = MyMoneyFile::instance()->value("kmm-encryption-key"); 0257 if (keyList.isEmpty()) 0258 keyList = m_encryptionKeys; 0259 0260 // actually, url should be the parameter to this function 0261 // but for now, this would involve too many changes 0262 auto rc = true; 0263 try { 0264 if (! url.isValid()) { 0265 throw MYMONEYEXCEPTION(QString::fromLatin1("Malformed URL '%1'").arg(url.url())); 0266 } 0267 0268 if (url.isLocalFile()) { 0269 filename = url.toLocalFile(); 0270 try { 0271 const unsigned int nbak = KMyMoneySettings::autoBackupCopies(); 0272 if (nbak) { 0273 KBackup::numberedBackupFile(filename, QString(), QStringLiteral("~"), nbak); 0274 } 0275 saveToLocalFile(filename, storageWriter.get(), plaintext, keyList); 0276 } catch (const MyMoneyException &e) { 0277 qWarning("Unable to write changes to: %s\nReason: %s", qPrintable(filename), e.what()); 0278 throw; 0279 } 0280 } else { 0281 // obtain a temporary name for the local destination 0282 // using QTemporaryFile. As long as the object is 0283 // not destroyed, the file remains opened, which causes 0284 // problems on MS-Windows if you want to e.g. rename it. 0285 // Since we just need the name at this point, we simply 0286 // create the object, take the name (which is only available 0287 // once the file is opened) and destroy the object (which 0288 // closes the file on the filesystem) to avoid such problems. 0289 const auto tmpfile = new QTemporaryFile; 0290 tmpfile->open(); 0291 const auto fileName = tmpfile->fileName(); 0292 delete tmpfile; 0293 0294 saveToLocalFile(fileName, storageWriter.get(), plaintext, keyList); 0295 0296 Q_CONSTEXPR int permission = -1; 0297 QFile file(fileName); 0298 file.open(QIODevice::ReadOnly); 0299 KIO::StoredTransferJob *putjob = KIO::storedPut(file.readAll(), url, permission, KIO::JobFlag::Overwrite); 0300 if (!putjob->exec()) { 0301 throw MYMONEYEXCEPTION(QString::fromLatin1("Unable to upload to '%1'.<br />%2").arg(url.toDisplayString(), putjob->errorString())); 0302 } 0303 file.close(); 0304 } 0305 } catch (const MyMoneyException &e) { 0306 KMessageBox::error(nullptr, QString::fromLatin1(e.what())); 0307 MyMoneyFile::instance()->setDirty(); 0308 rc = false; 0309 } 0310 return rc; 0311 } 0312 0313 bool XMLStorage::saveAs() 0314 { 0315 auto rc = false; 0316 QStringList m_additionalGpgKeys; 0317 m_encryptionKeys.clear(); 0318 0319 QString selectedKeyName; 0320 #ifdef ENABLE_GPG 0321 if (KGPGFile::GPGAvailable() && KMyMoneySettings::writeDataEncrypted()) { 0322 // fill the secret key list and combo box 0323 QStringList keyList; 0324 KGPGFile::secretKeyList(keyList); 0325 0326 QPointer<KGpgKeySelectionDlg> dlg = new KGpgKeySelectionDlg(nullptr); 0327 dlg->setSecretKeys(keyList, KMyMoneySettings::gpgRecipient()); 0328 dlg->setAdditionalKeys(KMyMoneySettings::gpgRecipientList()); 0329 rc = dlg->exec(); 0330 if ((rc == QDialog::Accepted) && (dlg != 0)) { 0331 m_additionalGpgKeys = dlg->additionalKeys(); 0332 selectedKeyName = dlg->secretKey(); 0333 } 0334 delete dlg; 0335 if (rc != QDialog::Accepted) { 0336 return rc; 0337 } 0338 } 0339 #endif 0340 0341 QString prevDir; // don't prompt file name if not a native file 0342 if (appInterface()->isNativeFile()) 0343 prevDir = appInterface()->readLastUsedDir(); 0344 0345 QPointer<QFileDialog> dlg = 0346 new QFileDialog(nullptr, 0347 i18n("Save As"), 0348 prevDir, 0349 QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.kmy"), i18nc("KMyMoney (Filefilter)", "KMyMoney files")) 0350 + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.anon.xml"), i18nc("Anonymous (Filefilter)", "Anonymous files")) 0351 + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.xml"), i18nc("XML (Filefilter)", "XML files")) 0352 + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*"), i18nc("All files (Filefilter)", "All files"))); 0353 dlg->setAcceptMode(QFileDialog::AcceptSave); 0354 connect(dlg, &QFileDialog::filterSelected, this, [&](const QString txt) { 0355 // for some reason, txt sometimes contains the filter expression only 0356 // e.g. "*.xml" and in some others it contains the full text with 0357 // the filter expression appended in parenthesis e.g. 0358 // "KMyMoney files (*.xml)". The following logic extracts the 0359 // filter and sets the default suffix based on it. 0360 QRegularExpression filter(QStringLiteral("\\*\\.(?<extension>[a-z\\.]+)")); 0361 const auto match = filter.match(txt); 0362 if (match.hasMatch()) { 0363 dlg->setDefaultSuffix(match.captured(QStringLiteral("extension"))); 0364 } else { 0365 dlg->setDefaultSuffix(QString()); 0366 } 0367 0368 }); 0369 0370 if (dlg->exec() == QDialog::Accepted && dlg != 0) { 0371 QUrl newURL = dlg->selectedUrls().first(); 0372 if (!newURL.fileName().isEmpty()) { 0373 QString newName = newURL.toDisplayString(QUrl::PreferLocalFile); 0374 0375 // append extension if not present 0376 if (!newName.endsWith(QLatin1String(".kmy"), Qt::CaseInsensitive) && 0377 !newName.endsWith(QLatin1String(".xml"), Qt::CaseInsensitive)) 0378 newName.append(QLatin1String(".kmy")); 0379 newURL = QUrl::fromUserInput(newName); 0380 0381 // If this is the anonymous file export, just save it, don't actually take the 0382 // name, or remember it! Don't even try to encrypt it 0383 if (newName.endsWith(QLatin1String(".anon.xml"), Qt::CaseInsensitive)) 0384 rc = save(newURL); 0385 else { 0386 appInterface()->writeFilenameURL(newURL); 0387 static const QRegularExpression keyExp(QLatin1String(".* \\((.*)\\)")); 0388 const auto key(keyExp.match(selectedKeyName)); 0389 if (key.hasMatch()) { 0390 m_encryptionKeys = key.captured(1); 0391 if (!m_additionalGpgKeys.isEmpty()) { 0392 if (!m_encryptionKeys.isEmpty()) 0393 m_encryptionKeys.append(QLatin1Char(',')); 0394 m_encryptionKeys.append(m_additionalGpgKeys.join(QLatin1Char(','))); 0395 } 0396 } 0397 // clear out any existing keys so that the new ones will be used 0398 MyMoneyFileTransaction ft; 0399 try { 0400 MyMoneyFile::instance()->deletePair("kmm-encryption-key"); 0401 ft.commit(); 0402 } catch (MyMoneyException&) { 0403 ; // do nothing 0404 } 0405 rc = save(newURL); 0406 0407 appInterface()->addToRecentFiles(newURL); 0408 //write the directory used for this file as the default one for next time. 0409 appInterface()->writeLastUsedDir(newURL.toDisplayString(QUrl::RemoveFilename | QUrl::PreferLocalFile | QUrl::StripTrailingSlash)); 0410 appInterface()->writeLastUsedFile(newName); 0411 } 0412 } 0413 } 0414 (*appInterface()->progressCallback())(0,0, i18nc("Application is ready to use", "Ready.")); 0415 delete dlg; 0416 return rc; 0417 } 0418 0419 eKMyMoney::StorageType XMLStorage::storageType() const 0420 { 0421 return eKMyMoney::StorageType::XML; 0422 } 0423 0424 QString XMLStorage::fileExtension() const 0425 { 0426 return i18n("KMyMoney files (*.kmy *.xml)"); 0427 } 0428 0429 void XMLStorage::ungetString(QIODevice *qfile, char *buf, int len) 0430 { 0431 buf = &buf[len-1]; 0432 while (len--) { 0433 qfile->ungetChar(*buf--); 0434 } 0435 } 0436 0437 void XMLStorage::saveToLocalFile(const QString& localFile, MyMoneyXmlWriter* pWriter, bool plaintext, const QString& keyList) 0438 { 0439 #ifndef ENABLE_GPG 0440 Q_UNUSED(keyList) 0441 #else 0442 // Check GPG encryption 0443 bool encryptFile = true; 0444 bool encryptRecover = false; 0445 if (!keyList.isEmpty()) { 0446 if (!KGPGFile::GPGAvailable()) { 0447 KMessageBox::error(nullptr, i18n("GPG does not seem to be installed on your system. Please make sure that GPG can be found using the standard search path. This time, encryption is disabled."), i18n("GPG not found")); 0448 encryptFile = false; 0449 } else { 0450 if (KMyMoneySettings::encryptRecover()) { 0451 encryptRecover = true; 0452 if (!KGPGFile::keyAvailable(QString(recoveryKeyId))) { 0453 KMessageBox::error(nullptr, i18n("<p>You have selected to encrypt your data also with the KMyMoney recover key, but the key with id</p><p><center><b>%1</b></center></p><p>has not been found in your keyring at this time. Please make sure to import this key into your keyring. You can find it on the <a href=\"https://kmymoney.org/\">KMyMoney web-site</a>. This time your data will not be encrypted with the KMyMoney recover key.</p>", QString(recoveryKeyId)), i18n("GPG Key not found")); 0454 encryptRecover = false; 0455 } 0456 } 0457 0458 const auto keys = keyList.split(',', Qt::SkipEmptyParts); 0459 for (const QString& key : qAsConst(keys)) { 0460 if (!KGPGFile::keyAvailable(key)) { 0461 KMessageBox::error(nullptr, i18n("<p>You have specified to encrypt your data for the user-id</p><p><center><b>%1</b>.</center></p><p>Unfortunately, a valid key for this user-id was not found in your keyring. Please make sure to import a valid key for this user-id. This time, encryption is disabled.</p>", key), i18n("GPG Key not found")); 0462 encryptFile = false; 0463 break; 0464 } 0465 } 0466 0467 if (encryptFile == true) { 0468 QString msg = i18n("<p>You have configured to save your data in encrypted form using GPG. Make sure you understand that you might lose all your data if you encrypt it, but cannot decrypt it later on. If unsure, answer <b>No</b>.</p>"); 0469 if (KMessageBox::questionTwoActions(nullptr, msg, i18n("Store GPG encrypted"), KMMYesNo::yes(), KMMYesNo::no(), "StoreEncrypted") 0470 == KMessageBox::SecondaryAction) { 0471 encryptFile = false; 0472 } 0473 } 0474 } 0475 } 0476 #endif 0477 // Permissions to apply to new file 0478 QFileDevice::Permissions fmode = QFileDevice::ReadUser | QFileDevice::WriteUser; 0479 0480 // Create a temporary file if needed 0481 QString writeFile = localFile; 0482 if (QFile::exists(localFile)) { 0483 QTemporaryFile tmpFile(writeFile); 0484 tmpFile.open(); 0485 writeFile = tmpFile.fileName(); 0486 // Since file is going to be replaced, stash the original permissions so they can be restored 0487 fmode = QFile::permissions(localFile); 0488 } 0489 0490 QSignalBlocker blockMyMoneyFile(MyMoneyFile::instance()); 0491 0492 MyMoneyFileTransaction ft; 0493 MyMoneyFile::instance()->deletePair("kmm-encryption-key"); 0494 std::unique_ptr<QIODevice> device; 0495 0496 #ifdef ENABLE_GPG 0497 if (!keyList.isEmpty() && encryptFile && !plaintext) { 0498 std::unique_ptr<KGPGFile> kgpg = std::unique_ptr<KGPGFile>(new KGPGFile{writeFile}); 0499 if (kgpg) { 0500 const auto keys = keyList.split(',', Qt::SkipEmptyParts); 0501 for (const QString& key : qAsConst(keys)) { 0502 kgpg->addRecipient(key.toLatin1()); 0503 } 0504 0505 if (encryptRecover) { 0506 kgpg->addRecipient(recoveryKeyId); 0507 } 0508 MyMoneyFile::instance()->setValue("kmm-encryption-key", keyList); 0509 device = std::unique_ptr<decltype(device)::element_type>(kgpg.release()); 0510 } 0511 } else 0512 #endif 0513 { 0514 QFile *file = new QFile(writeFile); 0515 // The second parameter of KCompressionDevice means that KCompressionDevice will delete the QFile object 0516 device = std::unique_ptr<decltype(device)::element_type>(new KCompressionDevice{file, true, (plaintext) ? KCompressionDevice::None : COMPRESSION_TYPE}); 0517 } 0518 0519 ft.commit(); 0520 0521 if (!device || !device->open(QIODevice::WriteOnly)) { 0522 throw MYMONEYEXCEPTION(QString::fromLatin1("Unable to open file '%1' for writing.").arg(localFile)); 0523 } 0524 0525 pWriter->setFile(MyMoneyFile::instance()); 0526 const auto xmlWrittenOk = pWriter->write(device.get()); 0527 device->close(); 0528 0529 if (!xmlWrittenOk) { 0530 throw MYMONEYEXCEPTION(QString::fromLatin1("XML write failure while writing to '%1'").arg(localFile)); 0531 } 0532 0533 // Check for errors if possible, only possible for KGPGFile 0534 QFileDevice *fileDevice = qobject_cast<QFileDevice*>(device.get()); 0535 if (fileDevice && fileDevice->error() != QFileDevice::NoError) { 0536 throw MYMONEYEXCEPTION(QString::fromLatin1("Failure while writing to '%1'").arg(localFile)); 0537 } 0538 0539 if (writeFile != localFile) { 0540 // This simple comparison is possible because the strings are equal if no temporary file was created. 0541 // If a temporary file was created, it is made in a way that the name is definitely different. So no 0542 // symlinks etc. have to be evaluated. 0543 0544 // on Windows QTemporaryFile does not release file handle even after close() 0545 // so QFile::rename(writeFile, localFile) will fail since Windows does not allow moving files in use 0546 // as a workaround QFile::copy is used instead of QFile::rename below 0547 // writeFile (i.e. tmpFile) will be deleted by QTemporaryFile dtor when it falls out of scope 0548 if (!QFile::remove(localFile)) { 0549 throw MYMONEYEXCEPTION(QString::fromLatin1("Failure while removing '%1'").arg(localFile)); 0550 } 0551 if (!QFile::rename(writeFile, localFile)) { 0552 throw MYMONEYEXCEPTION(QString::fromLatin1("Failure while renaming '%1' to '%2'").arg(writeFile, localFile)); 0553 } 0554 } 0555 QFile::setPermissions(localFile, fmode); 0556 } 0557 0558 void XMLStorage::checkRecoveryKeyValidity() 0559 { 0560 #ifdef ENABLE_GPG 0561 // check if the recovery key is still valid or expires soon 0562 0563 if (KMyMoneySettings::writeDataEncrypted() && KMyMoneySettings::encryptRecover()) { 0564 if (KGPGFile::GPGAvailable()) { 0565 KGPGFile file; 0566 QDateTime expirationDate = file.keyExpires(QLatin1String(recoveryKeyId)); 0567 if (expirationDate.isValid() && QDateTime::currentDateTime().daysTo(expirationDate) <= RECOVER_KEY_EXPIRATION_WARNING) { 0568 bool skipMessage = false; 0569 0570 //get global config object for our app. 0571 KSharedConfigPtr kconfig = KSharedConfig::openConfig(); 0572 KConfigGroup grp; 0573 QDate lastWarned; 0574 if (kconfig) { 0575 grp = kconfig->group("General Options"); 0576 lastWarned = grp.readEntry("LastRecoverKeyExpirationWarning", QDate()); 0577 if (QDate::currentDate() == lastWarned) { 0578 skipMessage = true; 0579 } 0580 } 0581 if (!skipMessage) { 0582 if (kconfig) { 0583 grp.writeEntry("LastRecoverKeyExpirationWarning", QDate::currentDate()); 0584 } 0585 KMessageBox::information(nullptr, i18np("You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 day. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", "You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 days. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", QDateTime::currentDateTime().daysTo(expirationDate)), i18n("Recover key expires soon")); 0586 } 0587 } 0588 } 0589 } 0590 #endif 0591 } 0592 0593 K_PLUGIN_CLASS_WITH_JSON(XMLStorage, "xmlstorage.json") 0594 0595 #include "xmlstorage.moc"