1 // Copyright 2005-2019 The Mumble Developers. All rights reserved.
2 // Use of this source code is governed by a BSD-style license
3 // that can be found in the LICENSE file at the root of the
4 // Mumble source tree or at <https://www.mumble.info/LICENSE>.
5
6 #include "mumble_pch.hpp"
7
8 #include "VersionCheck.h"
9
10 #include "MainWindow.h"
11 #include "WebFetch.h"
12
13 // We define a global macro called 'g'. This can lead to issues when included code uses 'g' as a type or parameter name (like protobuf 3.7 does). As such, for now, we have to make this our last include.
14 #include "Global.h"
15
VersionCheck(bool autocheck,QObject * p,bool focus)16 VersionCheck::VersionCheck(bool autocheck, QObject *p, bool focus) : QObject(p) {
17 QUrl url;
18 url.setPath(focus ? QLatin1String("/v1/banner") : QLatin1String("/v1/version-check"));
19
20 QList<QPair<QString, QString> > queryItems;
21 queryItems << qMakePair(QString::fromLatin1("ver"), QString::fromLatin1(QUrl::toPercentEncoding(QLatin1String(MUMBLE_RELEASE))));
22 #if defined(Q_OS_WIN)
23 # if defined(Q_OS_WIN64)
24 queryItems << qMakePair(QString::fromLatin1("os"), QString::fromLatin1("WinX64"));
25 # else
26 queryItems << qMakePair(QString::fromLatin1("os"), QString::fromLatin1("Win32"));
27 # endif
28 #elif defined(Q_OS_MAC)
29 # if defined(USE_MAC_UNIVERSAL)
30 queryItems << qMakePair(QString::fromLatin1("os"), QString::fromLatin1("MacOSX-Universal"));
31 # else
32 queryItems << qMakePair(QString::fromLatin1("os"), QString::fromLatin1("MacOSX"));
33 # endif
34 #else
35 queryItems << qMakePair(QString::fromLatin1("os"), QString::fromLatin1("Unix"));
36 #endif
37 if (! g.s.bUsage)
38 queryItems << qMakePair(QString::fromLatin1("nousage"), QString::fromLatin1("1"));
39 if (autocheck)
40 queryItems << qMakePair(QString::fromLatin1("auto"), QString::fromLatin1("1"));
41
42 queryItems << qMakePair(QString::fromLatin1("locale"), g.s.qsLanguage.isEmpty() ? QLocale::system().name() : g.s.qsLanguage);
43
44 QFile f(qApp->applicationFilePath());
45 if (! f.open(QIODevice::ReadOnly)) {
46 qWarning("VersionCheck: Failed to open binary");
47 } else {
48 QByteArray a = f.readAll();
49 if (a.size() < 1) {
50 qWarning("VersionCheck: suspiciously small binary");
51 } else {
52 QCryptographicHash qch(QCryptographicHash::Sha1);
53 qch.addData(a);
54 queryItems << qMakePair(QString::fromLatin1("sha1"), QString::fromLatin1(qch.result().toHex()));
55 }
56 }
57
58 #if QT_VERSION >= 0x050000
59 QUrlQuery query;
60 query.setQueryItems(queryItems);
61 url.setQuery(query);
62 #else
63 for (int i = 0; i < queryItems.size(); i++) {
64 const QPair<QString, QString> &queryPair = queryItems.at(i);
65 url.addQueryItem(queryPair.first, queryPair.second);
66 }
67 #endif
68 WebFetch::fetch(QLatin1String("update"), url, this, SLOT(fetched(QByteArray,QUrl)));
69 }
70
fetched(QByteArray a,QUrl url)71 void VersionCheck::fetched(QByteArray a, QUrl url) {
72 if (! a.isNull()) {
73 if (! a.isEmpty()) {
74 #ifdef SNAPSHOT_BUILD
75 if (url.path() == QLatin1String("/v1/banner")) {
76 g.mw->msgBox(QString::fromUtf8(a));
77 } else if (url.path() == QLatin1String("/v1/version-check")) {
78 #ifndef Q_OS_WIN
79 g.mw->msgBox(QString::fromUtf8(a));
80 #else
81 QDomDocument qdd;
82 qdd.setContent(a);
83
84 QDomElement elem = qdd.firstChildElement(QLatin1String("p"));
85 elem = elem.firstChildElement(QLatin1String("a"));
86
87 QUrl fetch = QUrl(elem.attribute(QLatin1String("href")));
88 fetch.setHost(QString());
89 fetch.setScheme(QString());
90 if (! fetch.isValid()) {
91 g.mw->msgBox(QString::fromUtf8(a));
92 } else {
93 QString filename = g.qdBasePath.absoluteFilePath(QLatin1String("Snapshots/") + QFileInfo(fetch.path()).fileName());
94
95 QFile qf(filename);
96 if (qf.exists()) {
97 std::wstring native = QDir::toNativeSeparators(filename).toStdWString();
98
99 WINTRUST_FILE_INFO file;
100 ZeroMemory(&file, sizeof(file));
101 file.cbStruct = sizeof(file);
102 file.pcwszFilePath = native.c_str();
103
104 WINTRUST_DATA data;
105 ZeroMemory(&data, sizeof(data));
106 data.cbStruct = sizeof(data);
107 data.dwUIChoice = WTD_UI_NONE;
108 data.fdwRevocationChecks = WTD_REVOKE_NONE;
109 data.dwUnionChoice = WTD_CHOICE_FILE;
110 data.pFile = &file;
111 data.dwProvFlags = WTD_SAFER_FLAG | WTD_USE_DEFAULT_OSVER_CHECK;
112 data.dwUIContext = WTD_UICONTEXT_INSTALL;
113
114 static GUID guid = WINTRUST_ACTION_GENERIC_VERIFY_V2;
115
116 LONG ts = WinVerifyTrust(0, &guid , &data);
117
118 if (ts == 0) {
119 if (QMessageBox::question(g.mw,
120 tr("Upgrade Mumble"),
121 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?"),
122 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) {
123 SHELLEXECUTEINFOW execinfo;
124 std::wstring filenative = filename.toStdWString();
125 std::wstring dirnative = QDir::toNativeSeparators(QDir::tempPath()).toStdWString();
126 ZeroMemory(&execinfo, sizeof(execinfo));
127 execinfo.cbSize = sizeof(execinfo);
128 execinfo.lpFile = filenative.c_str();
129 execinfo.lpDirectory = dirnative.c_str();
130 execinfo.nShow = SW_NORMAL;
131
132 if (ShellExecuteExW(&execinfo)) {
133 g.mw->bSuppressAskOnQuit = true;
134 qApp->closeAllWindows();
135 } else {
136 g.mw->msgBox(tr("Failed to launch snapshot installer."));
137 }
138 }
139
140 } else {
141 g.mw->msgBox(tr("Corrupt download of new version detected. Automatically removed."));
142 qf.remove();
143 }
144
145 // Delete all but the N most recent snapshots
146 size_t numberOfSnapshotsToKeep = 1;
147
148 QDir snapdir(g.qdBasePath.absolutePath() + QLatin1String("/Snapshots/"),
149 QString(),
150 QDir::Name,
151 QDir::Files);
152
153 foreach(const QFileInfo fileInfo, snapdir.entryInfoList(QStringList(), QDir::NoFilter, QDir::Time)) {
154 if (numberOfSnapshotsToKeep) {
155 --numberOfSnapshotsToKeep;
156 continue;
157 }
158
159 qWarning() << "Purging old snapshot" << fileInfo.fileName();
160 QFile file(fileInfo.absoluteFilePath());
161 file.remove();
162 }
163 } else {
164 g.mw->msgBox(tr("Downloading new snapshot from %1 to %2").arg(Qt::escape(fetch.toString()), Qt::escape(filename)));
165 WebFetch::fetch(QLatin1String("dl"), fetch, this, SLOT(fetched(QByteArray,QUrl)));
166 return;
167 }
168 }
169 } else {
170 QString filename = g.qdBasePath.absoluteFilePath(QLatin1String("Snapshots/") + QFileInfo(url.path()).fileName());
171
172 QFile qf(filename);
173 if (qf.open(QIODevice::WriteOnly)) {
174 qf.write(a);
175 qf.close();
176 new VersionCheck(true, g.mw);
177 } else {
178 g.mw->msgBox(tr("Failed to write new version to disk."));
179 }
180 #endif
181 }
182 #else
183 Q_UNUSED(url);
184 g.mw->msgBox(QString::fromUtf8(a));
185 #endif
186 }
187 } else {
188 g.mw->msgBox(tr("Mumble failed to retrieve version information from the central server."));
189 }
190
191 deleteLater();
192 }
193