mumble-voip_mumble/src/mumble/VersionCheck.cpp

215 lines
7.3 KiB
C++

// Copyright 2007-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "VersionCheck.h"
#include "MainWindow.h"
#include "Utils.h"
#include "WebFetch.h"
#include "Global.h"
#ifdef Q_OS_WIN
# include "win.h"
#endif
#include <QtCore/QUrlQuery>
#include <QtWidgets/QMessageBox>
#include <QtXml/QDomDocument>
#include <QtConcurrent/QtConcurrent>
#ifdef Q_OS_WIN
# include <shellapi.h>
# include <softpub.h>
#endif
VersionCheck::VersionCheck(bool autocheck, QObject *p, bool focus) : QObject(p), m_preparationWatcher() {
connect(&m_preparationWatcher, &QFutureWatcher< void >::finished, this, &VersionCheck::performRequest);
QFuture< void > future = QtConcurrent::run([this, autocheck, focus]() {
m_requestURL.setPath(focus ? QLatin1String("/v1/banner") : QLatin1String("/v1/version-check"));
QList< QPair< QString, QString > > queryItems;
queryItems << qMakePair(QString::fromLatin1("ver"),
QString::fromLatin1(QUrl::toPercentEncoding(Version::getRelease())));
#if defined(Q_OS_WIN)
# if defined(Q_OS_WIN64)
queryItems << qMakePair(QString::fromLatin1("os"), QString::fromLatin1("WinX64"));
# else
queryItems << qMakePair(QString::fromLatin1("os"), QString::fromLatin1("Win32"));
# endif
#elif defined(Q_OS_MAC)
# if defined(USE_MAC_UNIVERSAL)
queryItems << qMakePair(QString::fromLatin1("os"), QString::fromLatin1("MacOSX-Universal"));
# else
queryItems << qMakePair(QString::fromLatin1("os"), QString::fromLatin1("MacOSX"));
# endif
#else
queryItems << qMakePair(QString::fromLatin1("os"), QString::fromLatin1("Unix"));
#endif
if (!Global::get().s.bUsage)
queryItems << qMakePair(QString::fromLatin1("nousage"), QString::fromLatin1("1"));
if (autocheck)
queryItems << qMakePair(QString::fromLatin1("auto"), QString::fromLatin1("1"));
queryItems << qMakePair(QString::fromLatin1("locale"), Global::get().s.qsLanguage.isEmpty()
? QLocale::system().name()
: Global::get().s.qsLanguage);
QFile f(qApp->applicationFilePath());
if (!f.open(QIODevice::ReadOnly)) {
qWarning("VersionCheck: Failed to open binary");
} else {
QByteArray a = f.readAll();
if (a.size() < 1) {
qWarning("VersionCheck: suspiciously small binary");
} else {
QCryptographicHash qch(QCryptographicHash::Sha1);
qch.addData(a);
queryItems << qMakePair(QString::fromLatin1("sha1"), QString::fromLatin1(qch.result().toHex()));
}
}
QUrlQuery query;
query.setQueryItems(queryItems);
m_requestURL.setQuery(query);
});
m_preparationWatcher.setFuture(future);
}
void VersionCheck::performRequest() {
WebFetch::fetch(QLatin1String("update"), m_requestURL, this, SLOT(fetched(QByteArray, QUrl)));
}
void VersionCheck::fetched(QByteArray a, QUrl url) {
if (!a.isNull()) {
if (!a.isEmpty()) {
#ifdef SNAPSHOT_BUILD
if (url.path() == QLatin1String("/v1/banner")) {
Global::get().mw->msgBox(QString::fromUtf8(a));
} else if (url.path() == QLatin1String("/v1/version-check")) {
# ifndef Q_OS_WIN
Global::get().mw->msgBox(QString::fromUtf8(a));
# else
QDomDocument qdd;
qdd.setContent(a);
QDomElement elem = qdd.firstChildElement(QLatin1String("p"));
elem = elem.firstChildElement(QLatin1String("a"));
QUrl fetch = QUrl(elem.attribute(QLatin1String("href")));
fetch.setHost(QString());
fetch.setScheme(QString());
if (!fetch.isValid()) {
Global::get().mw->msgBox(QString::fromUtf8(a));
} else {
QString filename = Global::get().qdBasePath.absoluteFilePath(QLatin1String("Snapshots/")
+ QFileInfo(fetch.path()).fileName());
QFile qf(filename);
if (qf.exists()) {
std::wstring native = QDir::toNativeSeparators(filename).toStdWString();
WINTRUST_FILE_INFO file;
ZeroMemory(&file, sizeof(file));
file.cbStruct = sizeof(file);
file.pcwszFilePath = native.c_str();
WINTRUST_DATA data;
ZeroMemory(&data, sizeof(data));
data.cbStruct = sizeof(data);
data.dwUIChoice = WTD_UI_NONE;
data.fdwRevocationChecks = WTD_REVOKE_NONE;
data.dwUnionChoice = WTD_CHOICE_FILE;
data.pFile = &file;
data.dwProvFlags = WTD_SAFER_FLAG | WTD_USE_DEFAULT_OSVER_CHECK;
data.dwUIContext = WTD_UICONTEXT_INSTALL;
static GUID guid = WINTRUST_ACTION_GENERIC_VERIFY_V2;
LONG ts = WinVerifyTrust(0, &guid, &data);
if (ts == 0) {
if (QMessageBox::question(
Global::get().mw, tr("Upgrade Mumble"),
tr("A new version of Mumble has been detected and automatically downloaded. It is "
"recommended that you either upgrade to this version, or downgrade to the "
"latest stable release. Do you want to launch the installer now?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)
== QMessageBox::Yes) {
SHELLEXECUTEINFOW execinfo;
std::wstring filenative = filename.toStdWString();
std::wstring dirnative = QDir::toNativeSeparators(QDir::tempPath()).toStdWString();
ZeroMemory(&execinfo, sizeof(execinfo));
execinfo.cbSize = sizeof(execinfo);
execinfo.lpFile = filenative.c_str();
execinfo.lpDirectory = dirnative.c_str();
execinfo.nShow = SW_NORMAL;
if (ShellExecuteExW(&execinfo)) {
Global::get().mw->forceQuit = true;
qApp->closeAllWindows();
} else {
Global::get().mw->msgBox(tr("Failed to launch snapshot installer."));
}
}
} else {
Global::get().mw->msgBox(
tr("Corrupt download of new version detected. Automatically removed."));
qf.remove();
}
// Delete all but the N most recent snapshots
size_t numberOfSnapshotsToKeep = 1;
QDir snapdir(Global::get().qdBasePath.absolutePath() + QLatin1String("/Snapshots/"), QString(),
QDir::Name, QDir::Files);
foreach (const QFileInfo fileInfo,
snapdir.entryInfoList(QStringList(), QDir::NoFilter, QDir::Time)) {
if (numberOfSnapshotsToKeep) {
--numberOfSnapshotsToKeep;
continue;
}
qWarning() << "Purging old snapshot" << fileInfo.fileName();
QFile file(fileInfo.absoluteFilePath());
file.remove();
}
} else {
Global::get().mw->msgBox(tr("Downloading new snapshot from %1 to %2")
.arg(fetch.toString().toHtmlEscaped(), filename.toHtmlEscaped()));
WebFetch::fetch(QLatin1String("dl"), fetch, this, SLOT(fetched(QByteArray, QUrl)));
return;
}
}
} else {
QString filename = Global::get().qdBasePath.absoluteFilePath(QLatin1String("Snapshots/")
+ QFileInfo(url.path()).fileName());
QFile qf(filename);
if (qf.open(QIODevice::WriteOnly)) {
qf.write(a);
qf.close();
new VersionCheck(true, Global::get().mw);
} else {
Global::get().mw->msgBox(tr("Failed to write new version to disk."));
}
# endif
}
#else
Q_UNUSED(url);
Global::get().mw->msgBox(QString::fromUtf8(a));
#endif
}
} else {
Global::get().mw->msgBox(tr("Mumble failed to retrieve version information from the central server."));
}
deleteLater();
}