#include "stdafx.h" #include "OnlineUpdater.h" #include "../MiscHelpers/Common/Common.h" #include "../MiscHelpers/Common/OtherFunctions.h" #include "SandMan.h" #include "Windows/SettingsWindow.h" #include #include #include #include "../MiscHelpers/Common/CheckableMessageBox.h" #include #include "../../SandboxieTools/UpdUtil/UpdUtil.h" #include #include "Helpers/WinAdmin.h" #include #include #ifdef QT_NO_SSL #error Qt requires Open SSL support for the updater to work #endif #ifdef _DEBUG // mess with a dummy installation when debugging #undef VERSION_MJR #define VERSION_MJR 1 #undef VERSION_MIN #define VERSION_MIN 9 #undef VERSION_REV #define VERSION_REV 7 #undef VERSION_UPD #define VERSION_UPD 0 #define DUMMY_PATH "C:\\Projects\\Sandboxie\\SandboxieTools\\x64\\Debug\\Test" #endif DWORD GetIdleTime() // in seconds { LASTINPUTINFO lastInPut; GetLastInputInfo(&lastInPut); return (GetTickCount() - lastInPut.dwTime) / 1000; } COnlineUpdater::COnlineUpdater(QObject* parent) : QObject(parent) { m_IgnoredUpdates = theConf->GetStringList("Options/IgnoredUpdates"); m_RequestManager = NULL; m_pUpdaterUtil = NULL; LoadState(); } void COnlineUpdater::StartJob(CUpdatesJob* pJob, const QUrl& Url) { if (m_RequestManager == NULL) m_RequestManager = new CNetworkAccessManager(30 * 1000, this); QNetworkRequest Request = QNetworkRequest(Url); //Request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); Request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); //Request.setRawHeader("Accept-Encoding", "gzip"); QNetworkReply* pReply = m_RequestManager->get(Request); connect(pReply, SIGNAL(finished()), this, SLOT(OnRequestFinished())); connect(pReply, SIGNAL(downloadProgress(qint64, qint64)), pJob, SLOT(OnDownloadProgress(qint64, qint64))); connect(pJob->m_pProgress.data(), &CSbieProgress::Canceled, pReply, &QNetworkReply::abort); m_JobQueue.insert(pReply, pJob); } void COnlineUpdater::OnRequestFinished() { QNetworkReply* pReply = qobject_cast(sender()); CUpdatesJob* pJob = m_JobQueue.take(pReply); if (pJob) { pJob->Finish(pReply); pJob->deleteLater(); } pReply->deleteLater(); } quint64 COnlineUpdater::GetRandID() { quint64 RandID = 0; theAPI->GetSecureParam("RandID", &RandID, sizeof(RandID)); if (!RandID) { RandID = QRandomGenerator64::global()->generate(); theAPI->SetSecureParam("RandID", &RandID, sizeof(RandID)); } return RandID; } SB_PROGRESS COnlineUpdater::GetUpdates(QObject* receiver, const char* member, const QVariantMap& Params) { QUrlQuery Query; Query.addQueryItem("action", "update"); Query.addQueryItem("software", "sandboxie-plus"); //QString Branch = theConf->GetString("Options/ReleaseBranch"); //if (!Branch.isEmpty()) // Query.addQueryItem("branch", Branch); //Query.addQueryItem("version", theGUI->GetVersion()); //Query.addQueryItem("version", QString::number(VERSION_MJR) + "." + QString::number(VERSION_MIN) + "." + QString::number(VERSION_REV) + "." + QString::number(VERSION_UPD)); #ifdef INSIDER_BUILD Query.addQueryItem("version", QString(__DATE__)); #else Query.addQueryItem("version", QString::number(VERSION_MJR) + "." + QString::number(VERSION_MIN) + "." + QString::number(VERSION_REV)); #endif Query.addQueryItem("system", "windows-" + QSysInfo::kernelVersion() + "-" + QSysInfo::currentCpuArchitecture()); Query.addQueryItem("language", QLocale::system().name()); QString UpdateKey = GetArguments(g_Certificate, L'\n', L':').value("UPDATEKEY"); //if (UpdateKey.isEmpty()) // UpdateKey = theAPI->GetGlobalSettings()->GetText("UpdateKey"); // theConf->GetString("Options/UpdateKey"); //if (UpdateKey.isEmpty()) // UpdateKey = "00000000000000000000000000000000"; if (!UpdateKey.isEmpty()) UpdateKey += "-"; quint64 RandID = COnlineUpdater::GetRandID(); quint32 Hash = theAPI->GetUserSettings()->GetName().mid(13).toInt(NULL, 16); quint64 HashID = RandID ^ (quint64((Hash & 0xFFFF) ^ ((Hash >> 16) & 0xFFFF)) << 48); // fold the hash in half and xor it with the first 16 bit of RandID UpdateKey += QString::number(HashID, 16).rightJustified(16, '0').toUpper(); Query.addQueryItem("update_key", UpdateKey); if (Params.contains("channel")) Query.addQueryItem("channel", Params["channel"].toString()); else { QString ReleaseChannel = theConf->GetString("Options/ReleaseChannel", "stable"); Query.addQueryItem("channel", ReleaseChannel); } Query.addQueryItem("auto", Params["manual"].toBool() ? "0" : "1"); if (!Params["manual"].toBool()) { int UpdateInterval = theConf->GetInt("Options/UpdateInterval", UPDATE_INTERVAL); // in seconds Query.addQueryItem("interval", QString::number(UpdateInterval)); } #ifdef _DEBUG QString Test = Query.toString(); #endif QUrl Url("https://sandboxie-plus.com/update.php"); Url.setQuery(Query); CUpdatesJob* pJob = new CGetUpdatesJob(Params, this); StartJob(pJob, Url); QObject::connect(pJob, SIGNAL(UpdateData(const QVariantMap&, const QVariantMap&)), receiver, member, Qt::QueuedConnection); return SB_PROGRESS(OP_ASYNC, pJob->m_pProgress); } void CGetUpdatesJob::Finish(QNetworkReply* pReply) { QByteArray Reply = pReply->readAll(); m_pProgress->Finish(SB_OK); QVariantMap Data = QJsonDocument::fromJson(Reply).toVariant().toMap(); emit UpdateData(Data, m_Params); } SB_PROGRESS COnlineUpdater::DownloadFile(const QString& Url, QObject* receiver, const char* member, const QVariantMap& Params) { CUpdatesJob* pJob = new CGetFileJob(Params, this); StartJob(pJob, Url); QObject::connect(pJob, SIGNAL(Download(const QString&, const QVariantMap&)), receiver, member, Qt::QueuedConnection); return SB_PROGRESS(OP_ASYNC, pJob->m_pProgress); } void CGetFileJob::Finish(QNetworkReply* pReply) { quint64 Size = pReply->bytesAvailable(); m_pProgress->SetProgress(-1); QString FilePath = m_Params["path"].toString(); if (FilePath.isEmpty()) { QString Name = pReply->request().url().fileName(); if (Name.isEmpty()) Name = "unnamed_download.tmp"; FilePath = ((COnlineUpdater*)parent())->GetUpdateDir(true) + "/" + Name; } QFile File(FilePath); if (File.open(QFile::WriteOnly)) { while (pReply->bytesAvailable() > 0) File.write(pReply->read(4096)); File.flush(); QDateTime Date = m_Params["setDate"].toDateTime(); if(Date.isValid()) File.setFileTime(Date, QFileDevice::FileModificationTime); File.close(); } m_pProgress->Finish(SB_OK); if (File.size() != Size) { QMessageBox::critical(theGUI, "Sandboxie-Plus", tr("Failed to download file from: %1").arg(pReply->request().url().toString())); return; } emit Download(FilePath, m_Params); } SB_PROGRESS COnlineUpdater::GetSupportCert(const QString& Serial, QObject* receiver, const char* member, const QVariantMap& Params) { QString UpdateKey = Params["key"].toString(); QUrlQuery Query; if (!Serial.isEmpty()) { Query.addQueryItem("SN", Serial); if (Serial.length() > 5 && Serial.at(4).toUpper() == 'N') { wchar_t uuid_str[40]; theAPI->GetDriverInfo(-2, uuid_str, sizeof(uuid_str)); Query.addQueryItem("HwId", QString::fromWCharArray(uuid_str)); } } if(!UpdateKey.isEmpty()) Query.addQueryItem("UpdateKey", UpdateKey); #ifdef _DEBUG QString Test = Query.toString(); #endif QUrl Url("https://sandboxie-plus.com/get_cert.php"); Url.setQuery(Query); CUpdatesJob* pJob = new CGetCertJob(Params, this); StartJob(pJob, Url); QObject::connect(pJob, SIGNAL(Certificate(const QByteArray&, const QVariantMap&)), receiver, member, Qt::QueuedConnection); return SB_PROGRESS(OP_ASYNC, pJob->m_pProgress); } void CGetCertJob::Finish(QNetworkReply* pReply) { QByteArray Reply = pReply->readAll(); m_pProgress->Finish(SB_OK); if (Reply.left(1) == "{") { // error QVariantMap Data = QJsonDocument::fromJson(Reply).toVariant().toMap(); Reply.clear(); m_Params["error"] = Data["errorMsg"].toString(); } emit Certificate(Reply, m_Params); } ///////////////////////////////////////////////////////////////////////////////////////////////// // Update Handling // void COnlineUpdater::LoadState() { m_CheckMode = eInit; int iUpdate = 0; QString UpdateStr = ParseVersionStr(theConf->GetString("Updater/PendingUpdate"), &iUpdate); if (!IsVersionNewer(UpdateStr) && (UpdateStr != GetCurrentVersion() || iUpdate <= GetCurrentUpdate())) theConf->SetValue("Updater/PendingUpdate", ""); // it seems update has been applied bool bIsInstallerReady = false; QString FilePath = theConf->GetString("Updater/InstallerPath"); if (!FilePath.isEmpty() && QFile::exists(FilePath)) { QString ReleaseStr = ParseVersionStr(theConf->GetString("Updater/InstallerVersion")); if (IsVersionNewer(ReleaseStr)) { bIsInstallerReady = true; } } QString OnNewRelease = GetOnNewReleaseOption(); bool bCanRunInstaller = OnNewRelease == "install"; bool bIsUpdateReady = false; QVariantMap Update = QJsonDocument::fromJson(ReadFileAsString(GetUpdateDir() + "/" UPDATE_FILE).toUtf8()).toVariant().toMap(); if (!Update.isEmpty()) { int iUpdate = 0; QString UpdateStr = ParseVersionStr(theConf->GetString("Updater/UpdateVersion"), &iUpdate); if (IsVersionNewer(UpdateStr) || (UpdateStr == GetCurrentVersion() && iUpdate > GetCurrentUpdate())) { if (ScanUpdateFiles(Update) == eNone) // check if this update has already been applied theConf->SetValue("Updater/CurrentUpdate", MakeVersionStr(Update)); // cache result else bIsUpdateReady = true; } } QString OnNewUpdate = GetOnNewUpdateOption(); bool bCanApplyUpdate = OnNewUpdate == "install"; if (bIsInstallerReady && bCanRunInstaller) m_CheckMode = ePendingInstall; else if (bIsUpdateReady && bCanApplyUpdate) m_CheckMode = ePendingUpdate; } QString COnlineUpdater::GetOnNewUpdateOption() const { QString OnNewUpdate = theConf->GetString("Options/OnNewUpdate", "ignore"); QString ReleaseChannel = theConf->GetString("Options/ReleaseChannel", "stable"); if (ReleaseChannel != "preview" && (!g_CertInfo.active || g_CertInfo.expired)) // without active cert, allow revisions for preview channel return "ignore"; // this service requires a valid certificate return OnNewUpdate; } QString COnlineUpdater::GetOnNewReleaseOption() const { QString OnNewRelease = theConf->GetString("Options/OnNewRelease", "download"); if (OnNewRelease == "install" || OnNewRelease == "download") { QString ReleaseChannel = theConf->GetString("Options/ReleaseChannel", "stable"); if (ReleaseChannel != "preview" && (!g_CertInfo.active || g_CertInfo.expired)) // without active cert, allow automated updates only for preview channel return "notify"; // this service requires a valid certificate } //if ((g_CertInfo.active && g_CertInfo.expired) && OnNewRelease == "install") // return "download"; // disable auto update on an active but expired personal certificate return OnNewRelease; } bool COnlineUpdater::ShowCertWarningIfNeeded() { // // This function checks if this installation uses a expired personal // certificate which is active for the current build // in which case it shows a warning that updating to the latest build // will deactivate the certificate // if (!(g_CertInfo.active && g_CertInfo.expired)) return true; QString Message = tr("Your Sandboxie-Plus supporter certificate is expired, however for the current build you are using it remains active, when you update to a newer build exclusive supporter features will be disabled.\n\n" "Do you still want to update?"); int Ret = QMessageBox("Sandboxie-Plus", Message, QMessageBox::Warning, QMessageBox::Yes, QMessageBox::No | QMessageBox::Escape | QMessageBox::Default, QMessageBox::Cancel, theGUI).exec(); if (Ret == QMessageBox::Cancel) { QTimer::singleShot(10, this, [=] { theConf->DelValue("Updater/InstallerPath"); theConf->DelValue("Updater/UpdateVersion"); theGUI->UpdateLabel(); }); } return Ret == QMessageBox::Yes; } void COnlineUpdater::Process() { int iCheckUpdates = theConf->GetInt("Options/CheckForUpdates", 2); if (iCheckUpdates != 0) { time_t NextUpdateCheck = theConf->GetUInt64("Options/NextCheckForUpdates", 0); if (NextUpdateCheck == 0) // no check made yet theConf->SetValue("Options/NextCheckForUpdates", QDateTime::currentDateTime().addDays(7).toSecsSinceEpoch()); else if(QDateTime::currentDateTime().toSecsSinceEpoch() >= NextUpdateCheck) { if (iCheckUpdates == 2) { bool bCheck = false; iCheckUpdates = CCheckableMessageBox::question(theGUI, "Sandboxie-Plus", tr("Do you want to check if there is a new version of Sandboxie-Plus?") , tr("Don't show this message again."), &bCheck, QDialogButtonBox::Yes | QDialogButtonBox::No, QDialogButtonBox::Yes, QMessageBox::Information) == QDialogButtonBox::Ok ? 1 : 0; if (bCheck) theConf->SetValue("Options/CheckForUpdates", iCheckUpdates); } if (iCheckUpdates == 0) // no clicked on prompt theConf->SetValue("Options/NextCheckForUpdates", QDateTime::currentDateTime().addDays(7).toSecsSinceEpoch()); else { // schedule next check in 12 h in case this one fails theConf->SetValue("Options/NextCheckForUpdates", QDateTime::currentDateTime().addSecs(12 * 60 * 60).toSecsSinceEpoch()); CheckForUpdates(false); } } } if (m_CheckMode == ePendingUpdate || m_CheckMode == ePendingInstall) { // When auto install/apply is active wait for the user to be idle #ifndef _DEBUG if(GetIdleTime() > theConf->GetInt("Options/UpdateIdleTime", 30*60)) // default 30 minutes #endif // and wait for no processes running in the boxes if (theAPI->IsConnected() && theAPI->GetAllProcesses().isEmpty()) { if (m_CheckMode == ePendingUpdate) ApplyUpdate(true); else if (m_CheckMode == ePendingInstall) RunInstaller(true); m_CheckMode = eInit; } } } void COnlineUpdater::CheckForUpdates(bool bManual) { if (m_CheckMode == eManual || m_CheckMode == eAuto) return; // already in progress #ifdef _DEBUG if (QApplication::keyboardModifiers() & Qt::ControlModifier) bManual = false; #endif // clean up old check result m_UpdateData.clear(); m_CheckMode = bManual ? eManual : eAuto; QVariantMap Params; SB_PROGRESS Status = GetUpdates(this, SLOT(OnUpdateData(const QVariantMap&, const QVariantMap&)), Params); if (bManual && Status.GetStatus() == OP_ASYNC) { theGUI->AddAsyncOp(Status.GetValue()); Status.GetValue()->ShowMessage(tr("Checking for updates...")); } } void COnlineUpdater::OnUpdateData(const QVariantMap& Data, const QVariantMap& Params) { if (Data.isEmpty() || Data["error"].toBool()) { QString Error = Data.isEmpty() ? tr("server not reachable") : Data["errorMsg"].toString(); theGUI->OnLogMessage(tr("Failed to check for updates, error: %1").arg(Error), m_CheckMode != eManual); if (m_CheckMode == eManual) QMessageBox::critical(theGUI, "Sandboxie-Plus", tr("Failed to check for updates, error: %1").arg(Error)); m_CheckMode = eInit; return; } bool bNothing = true; bool bAuto = m_CheckMode != eManual; if (HandleUserMessage(Data)) bNothing = false; m_UpdateData = Data; m_LastUpdate = QDateTime::currentDateTime(); bool PendingUpdate = HandleUpdate(); theGUI->UpdateLabel(); if (PendingUpdate) { bNothing = false; } if (bAuto) { int UpdateInterval = theConf->GetInt("Options/UpdateInterval", UPDATE_INTERVAL); // in seconds theConf->SetValue("Options/NextCheckForUpdates", QDateTime::currentDateTime().addSecs(UpdateInterval).toSecsSinceEpoch()); } else if (bNothing) { QMessageBox::information(theGUI, "Sandboxie-Plus", tr("No new updates found, your Sandboxie-Plus is up-to-date.\n" "\nNote: The update check is often behind the latest GitHub release to ensure that only tested updates are offered.")); } } bool COnlineUpdater::HandleUpdate() { QString PendingUpdate; QString OnNewRelease = GetOnNewReleaseOption(); bool bNewRelease = false; QVariantMap Release = m_UpdateData["release"].toMap(); QString ReleaseStr = Release["version"].toString(); if (IsVersionNewer(ReleaseStr)) { if (m_CheckMode == eManual || !m_IgnoredUpdates.contains(ReleaseStr)) { PendingUpdate = ReleaseStr; bNewRelease = true; } } QString OnNewUpdate = GetOnNewUpdateOption(); bool bNewUpdate = false; QVariantMap Update = m_UpdateData["update"].toMap(); QString UpdateStr = Update["version"].toString(); bool bNewer; if ((bNewer = IsVersionNewer(UpdateStr)) || UpdateStr == GetCurrentVersion()) { int iUpdate = Update["update"].toInt(); if (iUpdate) UpdateStr += QChar('a' + (iUpdate - 1)); if (bNewer || iUpdate > GetCurrentUpdate()) { if (ScanUpdateFiles(Update) == eNone) // check if this update has already been applied theConf->SetValue("Updater/CurrentUpdate", MakeVersionStr(Update)); // cache result else if (OnNewUpdate != "ignore") { if(PendingUpdate.isEmpty()) PendingUpdate = UpdateStr; if (m_CheckMode == eManual || !m_IgnoredUpdates.contains(UpdateStr)) { bNewUpdate = true; } } } } theConf->SetValue("Updater/PendingUpdate", PendingUpdate); // // special case: updates allowed be to installed, but releases only allowed to be downloaded // solution: apply updates silently, then prompt to install new release, else prioritize installing new releases over updating the existing one // bool bAllowAuto = g_CertInfo.active && !g_CertInfo.expired; // To use automatic updates a valid certificate is required bool bCanRunInstaller = (m_CheckMode == eAuto && OnNewRelease == "install"); bool bIsInstallerReady = false; if (bNewRelease) { if (theConf->GetString("Updater/InstallerVersion") == MakeVersionStr(Release)) { QString FilePath = theConf->GetString("Updater/InstallerPath"); bIsInstallerReady = (!FilePath.isEmpty() && QFile::exists(FilePath)); } if (!bIsInstallerReady) { // clear when not up to date theConf->DelValue("Updater/InstallerVersion"); if ((bCanRunInstaller || (m_CheckMode == eAuto && OnNewRelease == "download")) || AskDownload(Release, bAllowAuto)) { if (DownloadInstaller(Release, m_CheckMode == eManual)) return true; } } } bool bCanApplyUpdate = (m_CheckMode == eAuto && OnNewUpdate == "install"); if (bNewUpdate) { if ((!bNewRelease || (bCanApplyUpdate && !bCanRunInstaller))) { bool bIsUpdateReady = false; if (theConf->GetString("Updater/UpdateVersion") == MakeVersionStr(Update)) bIsUpdateReady = QFile::exists(GetUpdateDir() + "/" UPDATE_FILE); if (!bIsUpdateReady) { // clear when not up to date theConf->DelValue("Updater/UpdateVersion"); if ((bCanApplyUpdate || (m_CheckMode == eAuto && OnNewUpdate == "download")) || AskDownload(Update, true)) { if (DownloadUpdate(Update, m_CheckMode == eManual)) return true; } } else if (m_CheckMode == eManual) { if (ApplyUpdate(false)) return true; } else if (bCanApplyUpdate) m_CheckMode = ePendingUpdate; } } if (bIsInstallerReady) { if (m_CheckMode == eManual) { if (RunInstaller(false)) return true; } else if(bCanRunInstaller) m_CheckMode = ePendingInstall; } if (m_CheckMode != ePendingUpdate && m_CheckMode != ePendingInstall) m_CheckMode = eInit; return bNewRelease || bNewUpdate; } bool COnlineUpdater::AskDownload(const QVariantMap& Data, bool bAuto) { QString VersionStr = MakeVersionStr(Data); QString UpdateMsg = Data["infoMsg"].toString(); QString UpdateUrl = Data["infoUrl"].toString(); QString FullMessage = !UpdateMsg.isEmpty() ? UpdateMsg : tr("

There is a new version of Sandboxie-Plus available.
New version: %1

").arg(VersionStr); QVariantMap Installer = Data["installer"].toMap(); QString DownloadUrl = Installer["downloadUrl"].toString(); enum EAction { eNone = 0, eDownload, eNotify, } Action = eNone; if (bAuto && !DownloadUrl.isEmpty()) { Action = eDownload; FullMessage += tr("

Do you want to download the installer?

"); } else if (bAuto && Data.contains("files")) { Action = eDownload; FullMessage += tr("

Do you want to download the updates?

"); } else if (!UpdateUrl.isEmpty()) { Action = eNotify; FullMessage += tr("

Do you want to go to the download page?

").arg(UpdateUrl); } CCheckableMessageBox mb(theGUI); mb.setWindowTitle("Sandboxie-Plus"); QIcon ico(QLatin1String(":/SandMan.png")); mb.setIconPixmap(ico.pixmap(64, 64)); //mb.setTextFormat(Qt::RichText); mb.setText(FullMessage); mb.setCheckBoxText(tr("Don't show this update anymore.")); mb.setCheckBoxVisible(m_CheckMode != eManual); if (Action != eNone) { mb.setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); mb.setDefaultButton(QDialogButtonBox::Yes); } else mb.setStandardButtons(QDialogButtonBox::Ok); mb.exec(); if (mb.clickedStandardButton() == QDialogButtonBox::Yes) { if (Action == eDownload) { m_CheckMode = eManual; return true; } else QDesktopServices::openUrl(UpdateUrl); } else { if (mb.clickedStandardButton() == QDialogButtonBox::Cancel) { theConf->SetValue("Updater/PendingUpdate", ""); theGUI->UpdateLabel(); } if (mb.isChecked()) theConf->SetValue("Options/IgnoredUpdates", m_IgnoredUpdates << VersionStr); } return false; } COnlineUpdater::EUpdateScope COnlineUpdater::GetFileScope(const QString& Path) { static const WCHAR CoreFiles[] = SCOPE_CORE_FILES; static const WCHAR LangFiles[] = SCOPE_LANG_FILES; static const WCHAR TmplFiles[] = SCOPE_TMPL_FILES; auto WildMatch = [Path](const WCHAR* pFiles) { for (const WCHAR* pFile = pFiles; *pFile; pFile += wcslen(pFile) + 1) { QString WC = QRegularExpression::wildcardToRegularExpression(QString::fromWCharArray(pFile)); QRegularExpression RegExp(WC, QRegularExpression::CaseInsensitiveOption); if (RegExp.match(Path).hasMatch()) return true; } return false; }; if (WildMatch(CoreFiles)) return eCore; if (WildMatch(TmplFiles) || WildMatch(LangFiles)) return eMeta; // unknown files are handled the same as known Plus files return eFull; } COnlineUpdater::EUpdateScope COnlineUpdater::ScanUpdateFiles(const QVariantMap& Update) { QString AppDir = QApplication::applicationDirPath(); #ifdef DUMMY_PATH AppDir = DUMMY_PATH; #endif EUpdateScope Scope = eNone; foreach(const QVariant vFile, Update["files"].toList()) { QVariantMap File = vFile.toMap(); QCryptographicHash qHash(QCryptographicHash::Sha256); QFile qFile(AppDir + "\\" + File["path"].toString()); if (qFile.open(QFile::ReadOnly)) { qHash.addData(&qFile); qFile.close(); } if (qHash.result() == QByteArray::fromHex(File["hash"].toByteArray())) continue; // file did not change EUpdateScope CurScope = GetFileScope(File["path"].toString()); if (Scope < CurScope) Scope = CurScope; } return Scope; } bool COnlineUpdater::DownloadUpdate(const QVariantMap& Update, bool bAndApply) { QJsonDocument doc(QJsonValue::fromVariant(Update).toObject()); WriteStringToFile(GetUpdateDir(true) + "/" UPDATE_FILE, doc.toJson()); theConf->DelValue("Updater/UpdateVersion"); QStringList Params; Params.append("update"); Params.append("sandboxie-plus"); Params.append("/step:prepare"); Params.append("/embedded"); Params.append("/temp:" + GetUpdateDir().replace("/", "\\")); #ifdef DUMMY_PATH Params.append("/path:" DUMMY_PATH); #endif m_pUpdaterUtil = new QProcess(this); m_pUpdaterUtil->setProperty("apply", bAndApply); m_pUpdaterUtil->setProperty("version", MakeVersionStr(Update)); m_pUpdaterUtil->setProgram(QApplication::applicationDirPath() + "/UpdUtil.exe"); m_pUpdaterUtil->setArguments(Params); connect(m_pUpdaterUtil, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(OnPrepareFinished(int, QProcess::ExitStatus))); connect(m_pUpdaterUtil, SIGNAL(readyReadStandardOutput()), this, SLOT(OnPrepareOutput())); connect(m_pUpdaterUtil, SIGNAL(readyReadStandardError()), this, SLOT(OnPrepareError())); m_pUpdaterUtil->start(); if (m_pUpdaterUtil->state() != QProcess::Running) return false; m_pUpdateProgress = CSbieProgressPtr(new CSbieProgress()); connect(m_pUpdateProgress.data(), &CSbieProgress::Canceled, this, [&]() { if (m_pUpdaterUtil && m_pUpdaterUtil->state() == QProcess::Running) m_pUpdaterUtil->terminate(); }); theGUI->AddAsyncOp(m_pUpdateProgress); m_pUpdateProgress->ShowMessage(tr("Downloading updates...")); return true; } void COnlineUpdater::OnPrepareOutput() { QProcess* pProcess = (QProcess*)sender(); QByteArray Text = pProcess->readAllStandardOutput(); qDebug() << "UPD-OUT:\t" << Text; if (!m_pUpdateProgress.isNull()) m_pUpdateProgress->ShowMessage(Text.trimmed()); } void COnlineUpdater::OnPrepareError() { QProcess* pProcess = (QProcess*)sender(); QByteArray Text = pProcess->readAllStandardOutput(); qDebug() << "UPD-ERR:\t" << Text; } QString GetUpdErrorStr(int exitCode) { switch (exitCode) { case ERROR_INVALID: return COnlineUpdater::tr("invalid parameter"); case ERROR_GET: return COnlineUpdater::tr("failed to download updated information"); case ERROR_LOAD: return COnlineUpdater::tr("failed to load updated json file"); case ERROR_DOWNLOAD: return COnlineUpdater::tr("failed to download a particular file"); case ERROR_SCAN: return COnlineUpdater::tr("failed to scan existing installation"); case ERROR_SIGN: return COnlineUpdater::tr("updated signature is invalid !!!"); case ERROR_HASH: return COnlineUpdater::tr("downloaded file is corrupted"); case ERROR_INTERNAL: return COnlineUpdater::tr("internal error"); default: return COnlineUpdater::tr("unknown error"); } } void COnlineUpdater::OnPrepareFinished(int exitCode, QProcess::ExitStatus exitStatus) { QProcess* pProcess = (QProcess*)sender(); if (pProcess != m_pUpdaterUtil) { pProcess->deleteLater(); return; } bool bAndApply = pProcess->property("apply").toBool(); QString VersionStr = pProcess->property("version").toString(); m_pUpdaterUtil->deleteLater(); m_pUpdaterUtil = NULL; if (m_pUpdateProgress.isNull()) return; // canceled m_pUpdateProgress->Finish(SB_OK); m_pUpdateProgress.clear(); if (exitCode < 0) { QMessageBox::critical(theGUI, "Sandboxie-Plus", tr("Failed to download updates from server, error %1").arg(GetUpdErrorStr(exitCode))); return; // failed } theConf->SetValue("Updater/UpdateVersion", VersionStr); if (bAndApply) ApplyUpdate(false); else { HandleUpdate(); theGUI->UpdateLabel(); } } bool COnlineUpdater::ApplyUpdate(bool bSilent) { if (!ShowCertWarningIfNeeded()) return false; if (!bSilent) { QString Message = tr("

Updates for Sandboxie-Plus have been downloaded.

Do you want to apply these updates? If any programs are running sandboxed, they will be terminated.

"); int Ret = QMessageBox("Sandboxie-Plus", Message, QMessageBox::Information, QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape, QMessageBox::Cancel, theGUI).exec(); if (Ret == QMessageBox::Cancel) { theConf->DelValue("Updater/UpdateVersion"); theGUI->UpdateLabel(); } if (Ret != QMessageBox::Yes) return false; } QVariantMap Update = QJsonDocument::fromJson(ReadFileAsString(GetUpdateDir() + "/" UPDATE_FILE).toUtf8()).toVariant().toMap(); EUpdateScope Scope = ScanUpdateFiles(Update); if (Scope == eNone) return true; // nothing to do if(Scope != eMeta) theAPI->TerminateAll(); QStringList Params; Params.append("update"); Params.append("sandboxie-plus"); Params.append("/step:apply"); if(Scope == eMeta) Params.append("/scope:meta"); else Params.append("/restart"); #ifndef _DEBUG Params.append("/embedded"); #else Params.append("/pause"); #endif Params.append("/temp:" + GetUpdateDir().replace("/", "\\")); #ifdef DUMMY_PATH Params.append("/path:" DUMMY_PATH); #endif if (Scope == eFull) Params.append("/open:sandman.exe"); SB_RESULT(int) status = RunUpdater(Params, true, Scope != eFull); if (!status.IsError()) { if(bSilent) theConf->DelValue("Updater/UpdateVersion"); if (Scope == eMeta) theAPI->ReloadConfig(); else if (Scope == eFull) QApplication::quit(); else theGUI->ConnectSbie(); return true; } return false; } SB_RESULT(int) COnlineUpdater::RunUpdater(const QStringList& Params, bool bSilent, bool Wait) { if (bSilent) { SB_RESULT(int) Result = theAPI->RunUpdateUtility(Params, 2, Wait); if (!Result.IsError()) return Result; // else fallback to ShellExecuteEx if (theConf->GetBool("Options/UpdateNoFallback", false)) return Result; } std::wstring wFile = QString(QApplication::applicationDirPath() + "/UpdUtil.exe").replace("/", "\\").toStdWString(); std::wstring wParams; foreach(const QString & Param, Params) { if (!wParams.empty()) wParams.push_back(L' '); wParams += L"\"" + Param.toStdWString() + L"\""; } int ExitCode = RunElevated(wFile, wParams, Wait ? INFINITE : 0); if (ExitCode == STATUS_PENDING && !Wait) ExitCode = 0; return CSbieResult(ExitCode); } bool COnlineUpdater::DownloadInstaller(const QVariantMap& Release, bool bAndRun) { if (m_RequestManager == NULL) m_RequestManager = new CNetworkAccessManager(30 * 1000, this); QVariantMap Installer = Release["installer"].toMap(); QString DownloadUrl = Installer["downloadUrl"].toString(); if (DownloadUrl.isEmpty()) return false; // clean up old installer if present QString FilePath = theConf->GetString("Updater/InstallerPath"); if (!FilePath.isEmpty()) { QFile::remove(FilePath); QFile::remove(FilePath + ".sig"); theConf->DelValue("Updater/InstallerPath"); } QVariantMap Params; Params["run"] = bAndRun; Params["version"] = MakeVersionStr(Release); Params["signature"] = Installer["signature"]; SB_PROGRESS Status = DownloadFile(DownloadUrl, this, SLOT(OnInstallerDownload(const QString&, const QVariantMap&)), Params); if (Status.GetStatus() == OP_ASYNC) { theGUI->AddAsyncOp(Status.GetValue()); Status.GetValue()->ShowMessage(tr("Downloading installer...")); } return true; } void COnlineUpdater::OnInstallerDownload(const QString& Path, const QVariantMap& Params) { bool bAndRun = Params["run"].toBool(); QString VersionStr = Params["version"].toString(); QByteArray Signature = Params["signature"].toByteArray(); QFile SigFile(Path + ".sig"); if (SigFile.open(QFile::WriteOnly)) { SigFile.write(QByteArray::fromBase64(Signature)); SigFile.close(); } theConf->SetValue("Updater/InstallerVersion", VersionStr); theConf->SetValue("Updater/InstallerPath", Path); if (bAndRun) RunInstaller(false); else { HandleUpdate(); theGUI->UpdateLabel(); } } bool COnlineUpdater::RunInstaller(bool bSilent) { if (!ShowCertWarningIfNeeded()) return false; QString FilePath = theConf->GetString("Updater/InstallerPath"); if (FilePath.isEmpty() || !QFile::exists(FilePath)) { theConf->DelValue("Updater/InstallerPath"); theConf->DelValue("Updater/InstallerVersion"); return false; } if (!bSilent) { QString Message = tr("

A new Sandboxie-Plus installer has been downloaded to the following location:

%1

Do you want to begin the installation? If any programs are running sandboxed, they will be terminated.

") .arg(FilePath).arg("File:///" + Split2(FilePath, "/", true).first); int Ret = QMessageBox("Sandboxie-Plus", Message, QMessageBox::Information, QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape, QMessageBox::Cancel, theGUI).exec(); if (Ret == QMessageBox::Cancel) { QFile::remove(FilePath); QFile::remove(FilePath + ".sig"); theConf->DelValue("Updater/InstallerPath"); theGUI->UpdateLabel(); } if (Ret != QMessageBox::Yes) return false; } theAPI->TerminateAll(); if (RunInstaller2(FilePath, true)) { if (bSilent) theConf->DelValue("Updater/InstallerVersion"); QApplication::quit(); return true; } return false; } bool COnlineUpdater::RunInstaller2(const QString& FilePath, bool bSilent) { if (bSilent && !theGUI->IsFullyPortable()) { QStringList Params; Params.append("run_setup"); Params.append(QString(FilePath).replace("/", "\\")); #ifndef _DEBUG_ Params.append("/embedded"); #else Params.append("/pause"); #endif SB_RESULT(int) Result = theAPI->RunUpdateUtility(Params, 1); if (!Result.IsError()) return true; // else fallback to ShellExecuteEx if (theConf->GetBool("Options/UpdateNoFallback", false)) return false; } std::wstring wFile = QString(FilePath).replace("/", "\\").toStdWString(); std::wstring wParams; if(theGUI->IsFullyPortable()) wParams = L"/PORTABLE=1"; #ifndef _DEBUG else wParams = L"/SILENT"; #endif return RunElevated(wFile, wParams) == 0; } bool COnlineUpdater::HandleUserMessage(const QVariantMap& Data) { QString UserMsg = Data["userMsg"].toString(); if (!UserMsg.isEmpty()) { QString MsgHash = QCryptographicHash::hash(Data["userMsg"].toByteArray(), QCryptographicHash::Md5).toHex().left(8); if (!m_IgnoredUpdates.contains(MsgHash)) { QString FullMessage = UserMsg; QString InfoUrl = Data["infoUrl"].toString(); if (!InfoUrl.isEmpty()) FullMessage += tr("

Do you want to go to the info page?

").arg(InfoUrl); CCheckableMessageBox mb(theGUI); mb.setWindowTitle("Sandboxie-Plus"); QIcon ico(QLatin1String(":/SandMan.png")); mb.setIconPixmap(ico.pixmap(64, 64)); //mb.setTextFormat(Qt::RichText); mb.setText(UserMsg); mb.setCheckBoxText(tr("Don't show this announcement in the future.")); if (!InfoUrl.isEmpty()) { mb.setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No); mb.setDefaultButton(QDialogButtonBox::Yes); } else mb.setStandardButtons(QDialogButtonBox::Ok); mb.exec(); if (mb.isChecked()) theConf->SetValue("Options/IgnoredUpdates", m_IgnoredUpdates << MsgHash); if (mb.clickedStandardButton() == QDialogButtonBox::Yes) { QDesktopServices::openUrl(InfoUrl); } return true; } } return false; } QString COnlineUpdater::GetUpdateDir(bool bCreate) { QString TempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); if (TempDir.right(1) != "/") TempDir += "/"; TempDir += "sandboxie-updater"; // Note: must not end with a / if(bCreate) QDir().mkpath(TempDir); return TempDir; } QString COnlineUpdater::MakeVersionStr(const QVariantMap& Data) { QString Str = Data["version"].toString(); int iUpdate = Data["update"].toInt(); if (iUpdate) Str += QChar('a' + (iUpdate - 1)); return Str; } QString COnlineUpdater::ParseVersionStr(const QString& Str, int* pUpdate) { int pos = Str.indexOf(QRegularExpression("[a-zA-Z]")); if (pos == -1) return Str; QString Ver = Str.left(pos); if (pUpdate) { QString Tmp = Str.mid(pos); *pUpdate = Tmp[0].toLatin1() - 'a' + 1; } return Ver; } QString COnlineUpdater::GetCurrentVersion() { return QString::number(VERSION_MJR) + "." + QString::number(VERSION_MIN) + "." + QString::number(VERSION_REV); } int COnlineUpdater::GetCurrentUpdate() { int iUpdate = 0; QString Version = ParseVersionStr(theConf->GetString("Updater/CurrentUpdate", 0), &iUpdate); if(Version != GetCurrentVersion() || iUpdate < VERSION_UPD) iUpdate = VERSION_UPD; return iUpdate; } quint32 COnlineUpdater::CurrentVersion() { //quint8 myVersion[4] = { VERSION_UPD, VERSION_REV, VERSION_MIN, VERSION_MJR }; // ntohl quint8 myVersion[4] = { 0, VERSION_REV, VERSION_MIN, VERSION_MJR }; // ntohl quint32 MyVersion = *(quint32*)&myVersion; return MyVersion; } quint32 COnlineUpdater::VersionToInt(const QString& VersionStr) { quint32 Version = 0; QStringList Nums = VersionStr.split("."); for (int i = 0, Bits = 24; i < Nums.count() && Bits >= 0; i++, Bits -= 8) Version |= (Nums[i].toInt() & 0xFF) << Bits; return Version; } bool COnlineUpdater::IsVersionNewer(const QString& VersionStr) { if (VersionStr.isEmpty()) return false; #ifdef INSIDER_BUILD QString sVersion = VersionStr; if (sVersion[4] == ' ') sVersion[4] = '0'; QDateTime VersionDate = QDateTime::fromString(sVersion, "MMM dd yyyy"); sVersion = QString(__DATE__); if (sVersion[4] == ' ') sVersion[4] = '0'; QDateTime BuildDate = QDateTime::fromString(sVersion, "MMM dd yyyy"); return (VersionDate > BuildDate); #else return VersionToInt(VersionStr) > CurrentVersion(); #endif }