// Copyright 2007-2021 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 . #include "VersionCheck.h" #include "MainWindow.h" #include "Utils.h" #include "WebFetch.h" #include "Global.h" #ifdef Q_OS_WIN # include "win.h" #endif #include #include #include #include #ifdef Q_OS_WIN # include # include #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(QLatin1String(MUMBLE_RELEASE)))); #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->bSuppressAskOnQuit = 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(); }