diff --git a/common/lc_blenderpreferences.cpp b/common/lc_blenderpreferences.cpp index 17bf5cf3..2a8396eb 100644 --- a/common/lc_blenderpreferences.cpp +++ b/common/lc_blenderpreferences.cpp @@ -50,6 +50,7 @@ const QLatin1String LineEnding("\r\n"); #define LC_BLENDER_ADDON_API_STR LC_BLENDER_ADDON_REPO_API_STR "/blenderldrawrender/" #define LC_BLENDER_ADDON_LATEST_URL LC_BLENDER_ADDON_API_STR "releases/latest" #define LC_BLENDER_ADDON_URL LC_BLENDER_ADDON_STR "releases/latest/download/" LC_BLENDER_ADDON_FILE +#define LC_BLENDER_ADDON_SHA_HASH_URL LC_BLENDER_ADDON_URL ".sha256" #define LC_THEME_DARK_PALETTE_MIDLIGHT "#3E3E3E" // 62, 62, 62, 255 #define LC_THEME_DEFAULT_PALETTE_LIGHT "#AEADAC" // 174, 173, 172, 255 @@ -1329,7 +1330,7 @@ bool lcBlenderPreferences::GetBlenderAddon(const QString& BlenderDir) const QString BlenderAddonFile = QDir::toNativeSeparators(QString("%1/%2").arg(BlenderDir).arg(LC_BLENDER_ADDON_FILE)); const QString AddonVersionFile = QDir::toNativeSeparators(QString("%1/%2/__version__.py").arg(BlenderAddonDir).arg(LC_BLENDER_ADDON_FOLDER_STR)); bool ExtractedAddon = QFileInfo(AddonVersionFile).isReadable(); - bool BlenderAddonExists = ExtractedAddon || QFileInfo(BlenderAddonFile).isReadable(); + bool BlenderAddonValidated = ExtractedAddon || QFileInfo(BlenderAddonFile).isReadable(); QString AddonStatus = tr("Installing Blender addon..."); AddonEnc AddonAction = ADDON_DOWNLOAD; QString LocalVersion, OnlineVersion; @@ -1443,7 +1444,7 @@ bool lcBlenderPreferences::GetBlenderAddon(const QString& BlenderDir) return true; // Reload existing archive }; - if (BlenderAddonExists) + if (BlenderAddonValidated) { if (GetBlenderAddonVersionMatch()) { @@ -1487,26 +1488,32 @@ bool lcBlenderPreferences::GetBlenderAddon(const QString& BlenderDir) } } - if (QFileInfo(BlenderAddonDir).exists()) + auto RemoveOldBlenderAddon = [&] (const QString& OldBlenderAddonFile) { - bool Result = true; - QDir Dir(BlenderAddonDir); - for (QFileInfo const& FileInfo : Dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks, QDir::DirsFirst)) + if (QFileInfo(BlenderAddonDir).exists()) { - if (FileInfo.isDir()) - Result &= QDir(FileInfo.absoluteFilePath()).removeRecursively(); - else - Result &= QFile::remove(FileInfo.absoluteFilePath()); - } + bool Result = true; + QDir Dir(BlenderAddonDir); + for (QFileInfo const& FileInfo : Dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks, QDir::DirsFirst)) + { + if (FileInfo.isDir()) + Result &= QDir(FileInfo.absoluteFilePath()).removeRecursively(); + else + Result &= QFile::remove(FileInfo.absoluteFilePath()); + } - Result &= Dir.rmdir(BlenderAddonDir); - if (!Result) - ShowMessage(tr("Failed to remove Blender addon: %1").arg(BlenderAddonDir), tr("Remove Existing Addon"), QString(), QString(), MBB_OK, QMessageBox::Warning); - } + if (QFileInfo(OldBlenderAddonFile).exists()) + Result &= QFile::remove(OldBlenderAddonFile); + + Result &= Dir.rmdir(BlenderAddonDir); + if (!Result) + ShowMessage(tr("Failed to properly remove Blender addon: %1").arg(BlenderAddonDir), tr("Remove Existing Addon"), QString(), QString(), MBB_OK, QMessageBox::Warning); + } + }; if (AddonAction == ADDON_DOWNLOAD) { - BlenderAddonExists = false; + BlenderAddonValidated = false; lcHttpManager* HttpManager = new lcHttpManager(gAddonPreferences); connect(HttpManager, SIGNAL(DownloadFinished(lcHttpReply*)), gAddonPreferences, SLOT(DownloadFinished(lcHttpReply*))); gAddonPreferences->mHttpReply = HttpManager->DownloadFile(QLatin1String(LC_BLENDER_ADDON_URL)); @@ -1514,36 +1521,90 @@ bool lcBlenderPreferences::GetBlenderAddon(const QString& BlenderDir) QApplication::processEvents(); if (!gAddonPreferences->mData.isEmpty()) { + const QString OldBlenderAddonFile = QString("%1.hold").arg(BlenderAddonFile); if (QFileInfo(BlenderAddonFile).exists()) { - QDir Dir(BlenderDir); - if (!Dir.remove(BlenderAddonFile)) - ShowMessage(tr("Failed to remove Blender addon archive:
%1").arg(BlenderAddonFile)); + if (!QFile::rename(BlenderAddonFile, OldBlenderAddonFile)) + ShowMessage(tr("Failed to rename existing Blender addon archive %1.").arg(BlenderAddonFile)); } + + QString ArchiveFileName, OldArchiveFileName = QFileInfo(OldBlenderAddonFile).fileName(); QFile File(BlenderAddonFile); if (File.open(QIODevice::WriteOnly)) { File.write(gAddonPreferences->mData); File.close(); - BlenderAddonExists = true; - gAddonPreferences->mData.clear(); + if (File.open(QIODevice::ReadOnly)) + { + QCryptographicHash Sha256Hash(QCryptographicHash::Sha256); + qint64 DataSize = File.size(); + const qint64 BufferSize = Q_INT64_C(1000); + char Buf[BufferSize]; + int BytesRead; + int ReadSize = qMin(DataSize, BufferSize); + while (ReadSize > 0 && (BytesRead = File.read(Buf, ReadSize)) > 0) + { + DataSize -= BytesRead; + Sha256Hash.addData(Buf, BytesRead); + ReadSize = qMin(DataSize, BufferSize); + } + File.close(); + const QString HexCalculated = Sha256Hash.result().toHex(); + + gAddonPreferences->mData.clear(); + gAddonPreferences->mHttpReply = HttpManager->DownloadFile(QLatin1String(LC_BLENDER_ADDON_SHA_HASH_URL)); + while (gAddonPreferences->mHttpReply) + QApplication::processEvents(); + if (!gAddonPreferences->mData.isEmpty()) + { + const QStringList HexReceived = QString(gAddonPreferences->mData).trimmed().split(" ", SkipEmptyParts); + if (HexReceived.first() == HexCalculated) + { + ArchiveFileName = QFileInfo(BlenderAddonFile).fileName(); + if (ArchiveFileName == HexReceived.last()) + { + RemoveOldBlenderAddon(OldBlenderAddonFile); + BlenderAddonValidated = true; + } + else + ShowMessage(tr("Failed to validate Blender addon file name
Downloaded:%1
Received:%2").arg(ArchiveFileName, HexReceived.last())); + } + else + ShowMessage(tr("Failed to validate Blender addon SHA hash
Calculated:%1
Received:%2").arg(HexCalculated, HexReceived.first())); + gAddonPreferences->mData.clear(); + } + else + ShowMessage(tr("Failed to receive SHA hash for Blender addon %1.sha256").arg(LC_BLENDER_ADDON_FILE)); + } + else + ShowMessage(tr("Failed to read Blender addon archive:
%1:
%2").arg(BlenderAddonFile).arg(File.errorString())); } else - ShowMessage(tr("Failed to open Blender addon file:
%1:
%2").arg(BlenderAddonFile).arg(File.errorString())); + ShowMessage(tr("Failed to write Blender addon archive:
%1:
%2").arg(BlenderAddonFile).arg(File.errorString())); + + if (!BlenderAddonValidated) + { + if (QFileInfo(BlenderAddonFile).exists()) + if (!QFile::remove(BlenderAddonFile)) + ShowMessage(tr("Failed to remove invalid Blender addon archive:
%1").arg(BlenderAddonFile)); + if (QFileInfo(OldBlenderAddonFile).exists()) + if (!QFile::rename(OldBlenderAddonFile, BlenderAddonFile)) + ShowMessage(tr("Failed to restore Blender addon archive:
%1 from %2").arg(ArchiveFileName, OldArchiveFileName)); + } } else ShowMessage(tr("Failed to download Blender addon archive:
%1").arg(BlenderAddonFile)); - if (!BlenderAddonExists) + if (!BlenderAddonValidated) { AddonStatus = tr("Download addon failed."); gAddonPreferences->StatusUpdate(true, true, AddonStatus); } } - else if (!BlenderAddonExists) + else if (!BlenderAddonValidated) ShowMessage(tr("Blender addon archive %1 was not found").arg(BlenderAddonFile)); - return BlenderAddonExists; + return BlenderAddonValidated; } void lcBlenderPreferences::StatusUpdate(bool Addon, bool Error, const QString& Message)