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