/* $Id: UIDownloaderGuestAdditions.cpp $ */ /** @file * VBox Qt GUI - UIDownloaderGuestAdditions class implementation. */ /* * Copyright (C) 2006-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ /* Qt includes: */ #include #include #include #include /* GUI includes: */ #include "QIFileDialog.h" #include "UICommon.h" #include "UIDownloaderGuestAdditions.h" #include "UIMessageCenter.h" #include "UIModalWindowManager.h" #include "UINetworkReply.h" #include "UINotificationCenter.h" #include "UIVersion.h" /* Other VBox includes: */ #include UIDownloaderGuestAdditions::UIDownloaderGuestAdditions() { /* Get version number and adjust it for test and trunk builds. The server only has official releases. */ const QString strVersion = UIVersion(uiCommon().vboxVersionStringNormalized()).effectiveReleasedVersion().toString(); /* Prepare source/target: */ const QString strSourceName = QString("%1_%2.iso").arg(GUI_GuestAdditionsName, strVersion); const QString strSourcePath = QString("https://download.virtualbox.org/virtualbox/%1/").arg(strVersion); const QString strSource = strSourcePath + strSourceName; const QString strPathSHA256SumsFile = QString("https://www.virtualbox.org/download/hashes/%1/SHA256SUMS").arg(strVersion); const QString strTarget = QDir(uiCommon().homeFolder()).absoluteFilePath(QString("%1.tmp").arg(strSourceName)); /* Set source/target: */ setSource(strSource); setTarget(strTarget); setPathSHA256SumsFile(strPathSHA256SumsFile); } QString UIDownloaderGuestAdditions::description() const { return UIDownloader::description().arg(tr("VirtualBox Guest Additions")); } bool UIDownloaderGuestAdditions::askForDownloadingConfirmation(UINetworkReply *pReply) { return msgCenter().confirmDownloadGuestAdditions(source().toString(), pReply->header(UINetworkReply::ContentLengthHeader).toInt()); } void UIDownloaderGuestAdditions::handleDownloadedObject(UINetworkReply *pReply) { m_receivedData = pReply->readAll(); } void UIDownloaderGuestAdditions::handleVerifiedObject(UINetworkReply *pReply) { /* Try to verify the SHA-256 checksum: */ QString strCalculatedSumm; bool fSuccess = false; do { /* Read received data into the buffer: */ const QByteArray receivedData(pReply->readAll()); /* Make sure it's not empty: */ if (receivedData.isEmpty()) break; /* Parse buffer contents to dictionary: */ #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) const QStringList dictionary(QString(receivedData).split("\n", Qt::SkipEmptyParts)); #else const QStringList dictionary(QString(receivedData).split("\n", QString::SkipEmptyParts)); #endif /* Make sure it's not empty: */ if (dictionary.isEmpty()) break; /* Parse each record to tags, look for the required one: */ foreach (const QString &strRecord, dictionary) { const QString strFileName = strRecord.section(" *", 1); const QString strDownloadedSumm = strRecord.section(" *", 0, 0); if (strFileName == source().fileName()) { /* Calc the SHA-256 on the bytes, creating a string: */ uint8_t abHash[RTSHA256_HASH_SIZE]; RTSha256(m_receivedData.constData(), m_receivedData.length(), abHash); char szDigest[RTSHA256_DIGEST_LEN + 1]; int rc = RTSha256ToString(abHash, szDigest, sizeof(szDigest)); if (RT_FAILURE(rc)) { AssertRC(rc); szDigest[0] = '\0'; } strCalculatedSumm = szDigest; //printf("Downloaded SHA-256 summ: [%s]\n", strDownloadedSumm.toUtf8().constData()); //printf("Calculated SHA-256 summ: [%s]\n", strCalculatedSumm.toUtf8().constData()); /* Make sure checksum is valid: */ fSuccess = strDownloadedSumm == strCalculatedSumm; break; } } } while (false); /* If SHA-256 checksum verification failed: */ if (!fSuccess) { /* Warn the user about additions-image was downloaded and saved but checksum is invalid: */ UINotificationMessage::cannotValidateGuestAdditionsSHA256Sum(source().toString(), QDir::toNativeSeparators(target())); return; } /* Make sure temporary file exists. If we have * reached this place, it's already written and verified. */ const QString strTempFilename = target(); if (!QFile::exists(strTempFilename)) { /* But still we are providing a failsafe. * Since we can try to write it again. */ QFile file(strTempFilename); if (!file.open(QIODevice::WriteOnly)) AssertFailedReturnVoid(); file.write(m_receivedData); } /* Rename temporary file to target one. This can require a number * of tries to let user choose the place to save file to. */ QString strNetTarget = target(); strNetTarget.remove(QRegularExpression("\\.tmp$")); setTarget(strNetTarget); while (true) { /* Make sure target file doesn't exist: */ bool fTargetFileExists = QFile::exists(target()); if (fTargetFileExists) { /* We should ask user about file rewriting (or exit otherwise): */ if (!msgCenter().confirmOverridingFile(QDir::toNativeSeparators(target()))) break; /* And remove file if rewriting confirmed: */ if (QFile::remove(target())) fTargetFileExists = false; } /* Try to rename temporary file to target one (would fail if target file still exists): */ const bool fFileRenamed = !fTargetFileExists && QFile::rename(strTempFilename, target()); /* If file renamed: */ if (fFileRenamed) { /* Warn the user about additions-image downloaded and saved, propose to mount it (and/or exit in any case): */ if (msgCenter().proposeMountGuestAdditions(source().toString(), QDir::toNativeSeparators(target()))) emit sigDownloadFinished(target()); break; } else { /* Warn the user about additions-image was downloaded but was NOT saved: */ msgCenter().cannotSaveGuestAdditions(source().toString(), QDir::toNativeSeparators(target())); /* Ask the user for another location for the additions-image file: */ const QString strTarget = QIFileDialog::getExistingDirectory(QFileInfo(target()).absolutePath(), windowManager().mainWindowShown(), tr("Select folder to save Guest Additions image to"), true); /* Check if user had really set a new target (and exit in opposite case): */ if (!strTarget.isNull()) setTarget(QDir(strTarget).absoluteFilePath(QFileInfo(target()).fileName())); else break; } } }