1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4 
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "core/update_checker.h"
9 
10 #include "platform/platform_specific.h"
11 #include "base/platform/base_platform_info.h"
12 #include "base/platform/base_platform_file_utilities.h"
13 #include "base/timer.h"
14 #include "base/bytes.h"
15 #include "base/unixtime.h"
16 #include "base/qt_adapters.h"
17 #include "storage/localstorage.h"
18 #include "core/application.h"
19 #include "core/changelogs.h"
20 #include "core/click_handler_types.h"
21 #include "mainwindow.h"
22 #include "main/main_account.h"
23 #include "main/main_session.h"
24 #include "main/main_domain.h"
25 #include "info/info_memento.h"
26 #include "info/settings/info_settings_widget.h"
27 #include "window/window_session_controller.h"
28 #include "settings/settings_intro.h"
29 #include "ui/layers/box_content.h"
30 #include "app.h"
31 
32 #include <QtCore/QJsonDocument>
33 #include <QtCore/QJsonObject>
34 
35 extern "C" {
36 #include <openssl/rsa.h>
37 #include <openssl/pem.h>
38 #include <openssl/bio.h>
39 #include <openssl/err.h>
40 } // extern "C"
41 
42 #ifndef TDESKTOP_DISABLE_AUTOUPDATE
43 #if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win
44 #include <LzmaLib.h>
45 #else // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
46 #include <lzma.h>
47 #endif // else of Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
48 #endif // !TDESKTOP_DISABLE_AUTOUPDATE
49 
50 #ifdef Q_OS_UNIX
51 #include <unistd.h>
52 #endif // Q_OS_UNIX
53 
54 namespace Core {
55 namespace {
56 
57 constexpr auto kUpdaterTimeout = 10 * crl::time(1000);
58 constexpr auto kMaxResponseSize = 1024 * 1024;
59 
60 #ifdef TDESKTOP_DISABLE_AUTOUPDATE
61 bool UpdaterIsDisabled = true;
62 #else // TDESKTOP_DISABLE_AUTOUPDATE
63 bool UpdaterIsDisabled = false;
64 #endif // TDESKTOP_DISABLE_AUTOUPDATE
65 
66 std::weak_ptr<Updater> UpdaterInstance;
67 
68 using Progress = UpdateChecker::Progress;
69 using State = UpdateChecker::State;
70 
71 #ifdef Q_OS_WIN
72 using VersionInt = DWORD;
73 using VersionChar = WCHAR;
74 #else // Q_OS_WIN
75 using VersionInt = int;
76 using VersionChar = wchar_t;
77 #endif // Q_OS_WIN
78 
79 using Loader = MTP::AbstractDedicatedLoader;
80 
81 struct BIODeleter {
operator ()Core::__anona3063f7d0111::BIODeleter82 	void operator()(BIO *value) {
83 		BIO_free(value);
84 	}
85 };
86 
MakeBIO(const void * buf,int len)87 inline auto MakeBIO(const void *buf, int len) {
88 	return std::unique_ptr<BIO, BIODeleter>{
89 		BIO_new_mem_buf(buf, len),
90 	};
91 }
92 
93 class Checker : public base::has_weak_ptr {
94 public:
95 	Checker(bool testing);
96 
97 	virtual void start() = 0;
98 
99 	rpl::producer<std::shared_ptr<Loader>> ready() const;
100 	rpl::producer<> failed() const;
101 
102 	rpl::lifetime &lifetime();
103 
104 	virtual ~Checker() = default;
105 
106 protected:
107 	bool testing() const;
108 	void done(std::shared_ptr<Loader> result);
109 	void fail();
110 
111 private:
112 	bool _testing = false;
113 	rpl::event_stream<std::shared_ptr<Loader>> _ready;
114 	rpl::event_stream<> _failed;
115 
116 	rpl::lifetime _lifetime;
117 
118 };
119 
120 struct Implementation {
121 	std::unique_ptr<Checker> checker;
122 	std::shared_ptr<Loader> loader;
123 	bool failed = false;
124 
125 };
126 
127 class HttpChecker : public Checker {
128 public:
129 	HttpChecker(bool testing);
130 
131 	void start() override;
132 
133 	~HttpChecker();
134 
135 private:
136 	void gotResponse();
137 	void gotFailure(QNetworkReply::NetworkError e);
138 	void clearSentRequest();
139 	bool handleResponse(const QByteArray &response);
140 	std::optional<QString> parseOldResponse(
141 		const QByteArray &response) const;
142 	std::optional<QString> parseResponse(const QByteArray &response) const;
143 	QString validateLatestUrl(
144 		uint64 availableVersion,
145 		bool isAvailableAlpha,
146 		QString url) const;
147 
148 	std::unique_ptr<QNetworkAccessManager> _manager;
149 	QNetworkReply *_reply = nullptr;
150 
151 };
152 
153 class HttpLoaderActor;
154 
155 class HttpLoader : public Loader {
156 public:
157 	HttpLoader(const QString &url);
158 
159 	~HttpLoader();
160 
161 private:
162 	void startLoading() override;
163 
164 	friend class HttpLoaderActor;
165 
166 	QString _url;
167 	std::unique_ptr<QThread> _thread;
168 	HttpLoaderActor *_actor = nullptr;
169 
170 };
171 
172 class HttpLoaderActor : public QObject {
173 public:
174 	HttpLoaderActor(
175 		not_null<HttpLoader*> parent,
176 		not_null<QThread*> thread,
177 		const QString &url);
178 
179 private:
180 	void start();
181 	void sendRequest();
182 
183 	void gotMetaData();
184 	void partFinished(qint64 got, qint64 total);
185 	void partFailed(QNetworkReply::NetworkError e);
186 
187 	not_null<HttpLoader*> _parent;
188 	QString _url;
189 	QNetworkAccessManager _manager;
190 	std::unique_ptr<QNetworkReply> _reply;
191 
192 };
193 
194 class MtpChecker : public Checker {
195 public:
196 	MtpChecker(base::weak_ptr<Main::Session> session, bool testing);
197 
198 	void start() override;
199 
200 private:
201 	using FileLocation = MTP::DedicatedLoader::Location;
202 
203 	using Checker::fail;
204 	Fn<void(const MTP::Error &error)> failHandler();
205 
206 	void gotMessage(const MTPmessages_Messages &result);
207 	std::optional<FileLocation> parseMessage(
208 		const MTPmessages_Messages &result) const;
209 	std::optional<FileLocation> parseText(const QByteArray &text) const;
210 	FileLocation validateLatestLocation(
211 		uint64 availableVersion,
212 		const FileLocation &location) const;
213 
214 	MTP::WeakInstance _mtp;
215 
216 };
217 
GetUpdaterInstance()218 std::shared_ptr<Updater> GetUpdaterInstance() {
219 	if (const auto result = UpdaterInstance.lock()) {
220 		return result;
221 	}
222 	const auto result = std::make_shared<Updater>();
223 	UpdaterInstance = result;
224 	return result;
225 }
226 
UpdatesFolder()227 QString UpdatesFolder() {
228 	return cWorkingDir() + qsl("tupdates");
229 }
230 
ClearAll()231 void ClearAll() {
232 	base::Platform::DeleteDirectory(UpdatesFolder());
233 }
234 
FindUpdateFile()235 QString FindUpdateFile() {
236 	QDir updates(UpdatesFolder());
237 	if (!updates.exists()) {
238 		return QString();
239 	}
240 	const auto list = updates.entryInfoList(QDir::Files);
241 	for (const auto &info : list) {
242 		if (QRegularExpression(
243 			"^("
244 			"tupdate|"
245 			"tx64upd|"
246 			"tmacupd|"
247 			"tarmacupd|"
248 			"tlinuxupd|"
249 			")\\d+(_[a-z\\d]+)?$",
250 			QRegularExpression::CaseInsensitiveOption
251 		).match(info.fileName()).hasMatch()) {
252 			return info.absoluteFilePath();
253 		}
254 	}
255 	return QString();
256 }
257 
ExtractFilename(const QString & url)258 QString ExtractFilename(const QString &url) {
259 	const auto expression = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)"));
260 	if (const auto match = expression.match(url); match.hasMatch()) {
261 		return match.captured(1).replace(
262 			QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")),
263 			QString());
264 	}
265 	return QString();
266 }
267 
UnpackUpdate(const QString & filepath)268 bool UnpackUpdate(const QString &filepath) {
269 #ifndef TDESKTOP_DISABLE_AUTOUPDATE
270 	QFile input(filepath);
271 	if (!input.open(QIODevice::ReadOnly)) {
272 		LOG(("Update Error: cant read updates file!"));
273 		return false;
274 	}
275 
276 #if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win
277 	const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
278 #else // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
279 	const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header
280 #endif // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
281 
282 	QByteArray compressed = input.readAll();
283 	int32 compressedLen = compressed.size() - hSize;
284 	if (compressedLen <= 0) {
285 		LOG(("Update Error: bad compressed size: %1").arg(compressed.size()));
286 		return false;
287 	}
288 	input.close();
289 
290 	QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready");
291 	base::Platform::DeleteDirectory(tempDirPath);
292 
293 	QDir tempDir(tempDirPath);
294 	if (tempDir.exists() || QFile(readyFilePath).exists()) {
295 		LOG(("Update Error: cant clear tupdates/temp dir!"));
296 		return false;
297 	}
298 
299 	uchar sha1Buffer[20];
300 	bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen);
301 	if (!goodSha1) {
302 		LOG(("Update Error: bad SHA1 hash of update file!"));
303 		return false;
304 	}
305 
306 	RSA *pbKey = [] {
307 		const auto bio = MakeBIO(
308 			const_cast<char*>(
309 				AppBetaVersion
310 					? UpdatesPublicBetaKey
311 					: UpdatesPublicKey),
312 			-1);
313 		return PEM_read_bio_RSAPublicKey(bio.get(), 0, 0, 0);
314 	}();
315 	if (!pbKey) {
316 		LOG(("Update Error: cant read public rsa key!"));
317 		return false;
318 	}
319 	if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature
320 		RSA_free(pbKey);
321 
322 		// try other public key, if we update from beta to stable or vice versa
323 		pbKey = [] {
324 			const auto bio = MakeBIO(
325 				const_cast<char*>(
326 					AppBetaVersion
327 						? UpdatesPublicKey
328 						: UpdatesPublicBetaKey),
329 				-1);
330 			return PEM_read_bio_RSAPublicKey(bio.get(), 0, 0, 0);
331 		}();
332 		if (!pbKey) {
333 			LOG(("Update Error: cant read public rsa key!"));
334 			return false;
335 		}
336 		if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature
337 			RSA_free(pbKey);
338 			LOG(("Update Error: bad RSA signature of update file!"));
339 			return false;
340 		}
341 	}
342 	RSA_free(pbKey);
343 
344 	QByteArray uncompressed;
345 
346 	int32 uncompressedLen;
347 	memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen);
348 	uncompressed.resize(uncompressedLen);
349 
350 	size_t resultLen = uncompressed.size();
351 #if defined Q_OS_WIN && !defined DESKTOP_APP_USE_PACKAGED // use Lzma SDK for win
352 	SizeT srcLen = compressedLen;
353 	int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE);
354 	if (uncompressRes != SZ_OK) {
355 		LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes));
356 		return false;
357 	}
358 #else // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
359 	lzma_stream stream = LZMA_STREAM_INIT;
360 
361 	lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED);
362 	if (ret != LZMA_OK) {
363 		const char *msg;
364 		switch (ret) {
365 		case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
366 		case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break;
367 		case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break;
368 		default: msg = "Unknown error, possibly a bug"; break;
369 		}
370 		LOG(("Error initializing the decoder: %1 (error code %2)").arg(msg).arg(ret));
371 		return false;
372 	}
373 
374 	stream.avail_in = compressedLen;
375 	stream.next_in = (uint8_t*)(compressed.constData() + hSize);
376 	stream.avail_out = resultLen;
377 	stream.next_out = (uint8_t*)uncompressed.data();
378 
379 	lzma_ret res = lzma_code(&stream, LZMA_FINISH);
380 	if (stream.avail_in) {
381 		LOG(("Error in decompression, %1 bytes left in _in of %2 whole.").arg(stream.avail_in).arg(compressedLen));
382 		return false;
383 	} else if (stream.avail_out) {
384 		LOG(("Error in decompression, %1 bytes free left in _out of %2 whole.").arg(stream.avail_out).arg(resultLen));
385 		return false;
386 	}
387 	lzma_end(&stream);
388 	if (res != LZMA_OK && res != LZMA_STREAM_END) {
389 		const char *msg;
390 		switch (res) {
391 		case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
392 		case LZMA_FORMAT_ERROR: msg = "The input data is not in the .xz format"; break;
393 		case LZMA_OPTIONS_ERROR: msg = "Unsupported compression options"; break;
394 		case LZMA_DATA_ERROR: msg = "Compressed file is corrupt"; break;
395 		case LZMA_BUF_ERROR: msg = "Compressed data is truncated or otherwise corrupt"; break;
396 		default: msg = "Unknown error, possibly a bug"; break;
397 		}
398 		LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res));
399 		return false;
400 	}
401 #endif // Q_OS_WIN && !DESKTOP_APP_USE_PACKAGED
402 
403 	tempDir.mkdir(tempDir.absolutePath());
404 
405 	quint32 version;
406 	{
407 		QDataStream stream(uncompressed);
408 		stream.setVersion(QDataStream::Qt_5_1);
409 
410 		stream >> version;
411 		if (stream.status() != QDataStream::Ok) {
412 			LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status()));
413 			return false;
414 		}
415 
416 		quint64 alphaVersion = 0;
417 		if (version == 0x7FFFFFFF) { // alpha version
418 			stream >> alphaVersion;
419 			if (stream.status() != QDataStream::Ok) {
420 				LOG(("Update Error: cant read alpha version from downloaded stream, status: %1").arg(stream.status()));
421 				return false;
422 			}
423 			if (!cAlphaVersion() || alphaVersion <= cAlphaVersion()) {
424 				LOG(("Update Error: downloaded alpha version %1 is not greater, than mine %2").arg(alphaVersion).arg(cAlphaVersion()));
425 				return false;
426 			}
427 		} else if (int32(version) <= AppVersion) {
428 			LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion));
429 			return false;
430 		}
431 
432 		quint32 filesCount;
433 		stream >> filesCount;
434 		if (stream.status() != QDataStream::Ok) {
435 			LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status()));
436 			return false;
437 		}
438 		if (!filesCount) {
439 			LOG(("Update Error: update is empty!"));
440 			return false;
441 		}
442 		for (uint32 i = 0; i < filesCount; ++i) {
443 			QString relativeName;
444 			quint32 fileSize;
445 			QByteArray fileInnerData;
446 			bool executable = false;
447 
448 			stream >> relativeName >> fileSize >> fileInnerData;
449 #ifdef Q_OS_UNIX
450 			stream >> executable;
451 #endif // Q_OS_UNIX
452 			if (stream.status() != QDataStream::Ok) {
453 				LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status()));
454 				return false;
455 			}
456 			if (fileSize != quint32(fileInnerData.size())) {
457 				LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size()));
458 				return false;
459 			}
460 
461 			QFile f(tempDirPath + '/' + relativeName);
462 			if (!QDir().mkpath(QFileInfo(f).absolutePath())) {
463 				LOG(("Update Error: cant mkpath for file '%1'").arg(tempDirPath + '/' + relativeName));
464 				return false;
465 			}
466 			if (!f.open(QIODevice::WriteOnly)) {
467 				LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName));
468 				return false;
469 			}
470 			auto writtenBytes = f.write(fileInnerData);
471 			if (writtenBytes != fileSize) {
472 				f.close();
473 				LOG(("Update Error: cant write file '%1', desiredSize: %2, write result: %3").arg(tempDirPath + '/' + relativeName).arg(fileSize).arg(writtenBytes));
474 				return false;
475 			}
476 			f.close();
477 			if (executable) {
478 				QFileDevice::Permissions p = f.permissions();
479 				p |= QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther;
480 				f.setPermissions(p);
481 			}
482 		}
483 
484 		// create tdata/version file
485 		tempDir.mkdir(QDir(tempDirPath + qsl("/tdata")).absolutePath());
486 		std::wstring versionString = FormatVersionDisplay(version).toStdWString();
487 
488 		const auto versionNum = VersionInt(version);
489 		const auto versionLen = VersionInt(versionString.size() * sizeof(VersionChar));
490 		VersionChar versionStr[32];
491 		memcpy(versionStr, versionString.c_str(), versionLen);
492 
493 		QFile fVersion(tempDirPath + qsl("/tdata/version"));
494 		if (!fVersion.open(QIODevice::WriteOnly)) {
495 			LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/version")));
496 			return false;
497 		}
498 		fVersion.write((const char*)&versionNum, sizeof(VersionInt));
499 		if (versionNum == 0x7FFFFFFF) { // alpha version
500 			fVersion.write((const char*)&alphaVersion, sizeof(quint64));
501 		} else {
502 			fVersion.write((const char*)&versionLen, sizeof(VersionInt));
503 			fVersion.write((const char*)&versionStr[0], versionLen);
504 		}
505 		fVersion.close();
506 	}
507 
508 	QFile readyFile(readyFilePath);
509 	if (readyFile.open(QIODevice::WriteOnly)) {
510 		if (readyFile.write("1", 1)) {
511 			readyFile.close();
512 		} else {
513 			LOG(("Update Error: cant write ready file '%1'").arg(readyFilePath));
514 			return false;
515 		}
516 	} else {
517 		LOG(("Update Error: cant create ready file '%1'").arg(readyFilePath));
518 		return false;
519 	}
520 	input.remove();
521 
522 	return true;
523 #else // !TDESKTOP_DISABLE_AUTOUPDATE
524 	return false;
525 #endif // TDESKTOP_DISABLE_AUTOUPDATE
526 }
527 
528 template <typename Callback>
ParseCommonMap(const QByteArray & json,bool testing,Callback && callback)529 bool ParseCommonMap(
530 		const QByteArray &json,
531 		bool testing,
532 		Callback &&callback) {
533 	auto error = QJsonParseError{ 0, QJsonParseError::NoError };
534 	const auto document = QJsonDocument::fromJson(json, &error);
535 	if (error.error != QJsonParseError::NoError) {
536 		LOG(("Update Error: MTP failed to parse JSON, error: %1"
537 			).arg(error.errorString()));
538 		return false;
539 	} else if (!document.isObject()) {
540 		LOG(("Update Error: MTP not an object received in JSON."));
541 		return false;
542 	}
543 	const auto platforms = document.object();
544 	const auto platform = Platform::AutoUpdateKey();
545 	const auto it = platforms.constFind(platform);
546 	if (it == platforms.constEnd()) {
547 		LOG(("Update Error: MTP platform '%1' not found in response."
548 			).arg(platform));
549 		return false;
550 	} else if (!(*it).isObject()) {
551 		LOG(("Update Error: MTP not an object found for platform '%1'."
552 			).arg(platform));
553 		return false;
554 	}
555 	const auto types = (*it).toObject();
556 	const auto list = [&]() -> std::vector<QString> {
557 		if (cAlphaVersion()) {
558 			return { "alpha", "beta", "stable" };
559 		} else if (cInstallBetaVersion()) {
560 			return { "beta", "stable" };
561 		}
562 		return { "stable" };
563 	}();
564 	auto bestIsAvailableAlpha = false;
565 	auto bestAvailableVersion = 0ULL;
566 	for (const auto &type : list) {
567 		const auto it = types.constFind(type);
568 		if (it == types.constEnd()) {
569 			continue;
570 		} else if (!(*it).isObject()) {
571 			LOG(("Update Error: Not an object found for '%1:%2'."
572 				).arg(platform).arg(type));
573 			return false;
574 		}
575 		const auto map = (*it).toObject();
576 		const auto key = testing ? "testing" : "released";
577 		const auto version = map.constFind(key);
578 		if (version == map.constEnd()) {
579 			continue;
580 		}
581 		const auto isAvailableAlpha = (type == "alpha");
582 		const auto availableVersion = [&] {
583 			if ((*version).isString()) {
584 				const auto string = (*version).toString();
585 				if (const auto index = string.indexOf(':'); index > 0) {
586 					return base::StringViewMid(string, 0, index).toULongLong();
587 				}
588 				return string.toULongLong();
589 			} else if ((*version).isDouble()) {
590 				return uint64(base::SafeRound((*version).toDouble()));
591 			}
592 			return 0ULL;
593 		}();
594 		if (!availableVersion) {
595 			LOG(("Update Error: Version is not valid for '%1:%2:%3'."
596 				).arg(platform).arg(type).arg(key));
597 			return false;
598 		}
599 		const auto compare = isAvailableAlpha
600 			? availableVersion
601 			: availableVersion * 1000;
602 		const auto bestCompare = bestIsAvailableAlpha
603 			? bestAvailableVersion
604 			: bestAvailableVersion * 1000;
605 		if (compare > bestCompare) {
606 			bestAvailableVersion = availableVersion;
607 			bestIsAvailableAlpha = isAvailableAlpha;
608 			if (!callback(availableVersion, isAvailableAlpha, map)) {
609 				return false;
610 			}
611 		}
612 	}
613 	if (!bestAvailableVersion) {
614 		LOG(("Update Error: No valid entry found for platform '%1'."
615 			).arg(platform));
616 		return false;
617 	}
618 	return true;
619 }
620 
Checker(bool testing)621 Checker::Checker(bool testing) : _testing(testing) {
622 }
623 
ready() const624 rpl::producer<std::shared_ptr<Loader>> Checker::ready() const {
625 	return _ready.events();
626 }
627 
failed() const628 rpl::producer<> Checker::failed() const {
629 	return _failed.events();
630 }
631 
testing() const632 bool Checker::testing() const {
633 	return _testing;
634 }
635 
done(std::shared_ptr<Loader> result)636 void Checker::done(std::shared_ptr<Loader> result) {
637 	_ready.fire(std::move(result));
638 }
639 
fail()640 void Checker::fail() {
641 	_failed.fire({});
642 }
643 
lifetime()644 rpl::lifetime &Checker::lifetime() {
645 	return _lifetime;
646 }
647 
HttpChecker(bool testing)648 HttpChecker::HttpChecker(bool testing) : Checker(testing) {
649 }
650 
start()651 void HttpChecker::start() {
652 	const auto updaterVersion = Platform::AutoUpdateVersion();
653 	const auto path = Local::readAutoupdatePrefix()
654 		+ qstr("/current")
655 		+ (updaterVersion > 1 ? QString::number(updaterVersion) : QString());
656 	auto url = QUrl(path);
657 	DEBUG_LOG(("Update Info: requesting update state"));
658 	const auto request = QNetworkRequest(url);
659 	_manager = std::make_unique<QNetworkAccessManager>();
660 	_reply = _manager->get(request);
661 	_reply->connect(_reply, &QNetworkReply::finished, [=] {
662 		gotResponse();
663 	});
664 	_reply->connect(_reply, base::QNetworkReply_error, [=](auto e) {
665 		gotFailure(e);
666 	});
667 }
668 
gotResponse()669 void HttpChecker::gotResponse() {
670 	if (!_reply) {
671 		return;
672 	}
673 
674 	cSetLastUpdateCheck(base::unixtime::now());
675 	const auto response = _reply->readAll();
676 	clearSentRequest();
677 
678 	if (response.size() >= kMaxResponseSize || !handleResponse(response)) {
679 		LOG(("Update Error: Bad update map size: %1").arg(response.size()));
680 		gotFailure(QNetworkReply::UnknownContentError);
681 	}
682 }
683 
handleResponse(const QByteArray & response)684 bool HttpChecker::handleResponse(const QByteArray &response) {
685 	const auto handle = [&](const QString &url) {
686 		done(url.isEmpty() ? nullptr : std::make_shared<HttpLoader>(url));
687 		return true;
688 	};
689 	if (const auto url = parseOldResponse(response)) {
690 		return handle(*url);
691 	} else if (const auto url = parseResponse(response)) {
692 		return handle(*url);
693 	}
694 	return false;
695 }
696 
clearSentRequest()697 void HttpChecker::clearSentRequest() {
698 	const auto reply = base::take(_reply);
699 	if (!reply) {
700 		return;
701 	}
702 	reply->disconnect(reply, &QNetworkReply::finished, nullptr, nullptr);
703 	reply->disconnect(reply, base::QNetworkReply_error, nullptr, nullptr);
704 	reply->abort();
705 	reply->deleteLater();
706 	_manager = nullptr;
707 }
708 
gotFailure(QNetworkReply::NetworkError e)709 void HttpChecker::gotFailure(QNetworkReply::NetworkError e) {
710 	LOG(("Update Error: "
711 		"could not get current version %1").arg(e));
712 	if (const auto reply = base::take(_reply)) {
713 		reply->deleteLater();
714 	}
715 
716 	fail();
717 }
718 
parseOldResponse(const QByteArray & response) const719 std::optional<QString> HttpChecker::parseOldResponse(
720 		const QByteArray &response) const {
721 	const auto string = QString::fromLatin1(response);
722 	const auto old = QRegularExpression(
723 		qsl("^\\s*(\\d+)\\s*:\\s*([\\x21-\\x7f]+)\\s*$")
724 	).match(string);
725 	if (!old.hasMatch()) {
726 		return std::nullopt;
727 	}
728 	const auto availableVersion = old.captured(1).toULongLong();
729 	const auto url = old.captured(2);
730 	const auto isAvailableAlpha = url.startsWith(qstr("beta_"));
731 	return validateLatestUrl(
732 		availableVersion,
733 		isAvailableAlpha,
734 		isAvailableAlpha ? url.mid(5) + "_{signature}" : url);
735 }
736 
parseResponse(const QByteArray & response) const737 std::optional<QString> HttpChecker::parseResponse(
738 		const QByteArray &response) const {
739 	auto bestAvailableVersion = 0ULL;
740 	auto bestIsAvailableAlpha = false;
741 	auto bestLink = QString();
742 	const auto accumulate = [&](
743 			uint64 version,
744 			bool isAlpha,
745 			const QJsonObject &map) {
746 		bestAvailableVersion = version;
747 		bestIsAvailableAlpha = isAlpha;
748 		const auto link = map.constFind("link");
749 		if (link == map.constEnd()) {
750 			LOG(("Update Error: Link not found for version %1."
751 				).arg(version));
752 			return false;
753 		} else if (!(*link).isString()) {
754 			LOG(("Update Error: Link is not a string for version %1."
755 				).arg(version));
756 			return false;
757 		}
758 		bestLink = (*link).toString();
759 		return true;
760 	};
761 	const auto result = ParseCommonMap(response, testing(), accumulate);
762 	if (!result) {
763 		return std::nullopt;
764 	}
765 	return validateLatestUrl(
766 		bestAvailableVersion,
767 		bestIsAvailableAlpha,
768 		Local::readAutoupdatePrefix() + bestLink);
769 }
770 
validateLatestUrl(uint64 availableVersion,bool isAvailableAlpha,QString url) const771 QString HttpChecker::validateLatestUrl(
772 		uint64 availableVersion,
773 		bool isAvailableAlpha,
774 		QString url) const {
775 	const auto myVersion = isAvailableAlpha
776 		? cAlphaVersion()
777 		: uint64(AppVersion);
778 	const auto validVersion = (cAlphaVersion() || !isAvailableAlpha);
779 	if (!validVersion || availableVersion <= myVersion) {
780 		return QString();
781 	}
782 	const auto versionUrl = url.replace(
783 		"{version}",
784 		QString::number(availableVersion));
785 	const auto finalUrl = isAvailableAlpha
786 		? QString(versionUrl).replace(
787 			"{signature}",
788 			countAlphaVersionSignature(availableVersion))
789 		: versionUrl;
790 	return finalUrl;
791 }
792 
~HttpChecker()793 HttpChecker::~HttpChecker() {
794 	clearSentRequest();
795 }
796 
HttpLoader(const QString & url)797 HttpLoader::HttpLoader(const QString &url)
798 : Loader(UpdatesFolder() + '/' + ExtractFilename(url), kChunkSize)
799 , _url(url) {
800 }
801 
startLoading()802 void HttpLoader::startLoading() {
803 	LOG(("Update Info: Loading using HTTP from '%1'.").arg(_url));
804 
805 	_thread = std::make_unique<QThread>();
806 	_actor = new HttpLoaderActor(this, _thread.get(), _url);
807 	_thread->start();
808 }
809 
~HttpLoader()810 HttpLoader::~HttpLoader() {
811 	if (const auto thread = base::take(_thread)) {
812 		if (const auto actor = base::take(_actor)) {
813 			QObject::connect(
814 				thread.get(),
815 				&QThread::finished,
816 				actor,
817 				&QObject::deleteLater);
818 		}
819 		thread->quit();
820 		thread->wait();
821 	}
822 }
823 
HttpLoaderActor(not_null<HttpLoader * > parent,not_null<QThread * > thread,const QString & url)824 HttpLoaderActor::HttpLoaderActor(
825 		not_null<HttpLoader*> parent,
826 		not_null<QThread*> thread,
827 		const QString &url)
828 : _parent(parent) {
829 	_url = url;
830 	moveToThread(thread);
831 	_manager.moveToThread(thread);
832 
833 	connect(thread, &QThread::started, this, [=] { start(); });
834 }
835 
start()836 void HttpLoaderActor::start() {
837 	sendRequest();
838 }
839 
sendRequest()840 void HttpLoaderActor::sendRequest() {
841 	auto request = QNetworkRequest(_url);
842 	const auto rangeHeaderValue = "bytes="
843 		+ QByteArray::number(_parent->alreadySize())
844 		+ "-";
845 	request.setRawHeader("Range", rangeHeaderValue);
846 	request.setAttribute(
847 		QNetworkRequest::HttpPipeliningAllowedAttribute,
848 		true);
849 	_reply.reset(_manager.get(request));
850 	connect(
851 		_reply.get(),
852 		&QNetworkReply::downloadProgress,
853 		this,
854 		&HttpLoaderActor::partFinished);
855 	connect(
856 		_reply.get(),
857 		base::QNetworkReply_error,
858 		this,
859 		&HttpLoaderActor::partFailed);
860 	connect(
861 		_reply.get(),
862 		&QNetworkReply::metaDataChanged,
863 		this,
864 		&HttpLoaderActor::gotMetaData);
865 }
866 
gotMetaData()867 void HttpLoaderActor::gotMetaData() {
868 	const auto pairs = _reply->rawHeaderPairs();
869 	for (const auto &pair : pairs) {
870 		if (QString::fromUtf8(pair.first).toLower() == "content-range") {
871 			const auto m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(pair.second));
872 			if (m.hasMatch()) {
873 				_parent->writeChunk({}, m.captured(1).toInt());
874 			}
875 		}
876 	}
877 }
878 
partFinished(qint64 got,qint64 total)879 void HttpLoaderActor::partFinished(qint64 got, qint64 total) {
880 	if (!_reply) return;
881 
882 	const auto statusCode = _reply->attribute(
883 		QNetworkRequest::HttpStatusCodeAttribute);
884 	if (statusCode.isValid()) {
885 		const auto status = statusCode.toInt();
886 		if (status != 200 && status != 206 && status != 416) {
887 			LOG(("Update Error: "
888 				"Bad HTTP status received in partFinished(): %1"
889 				).arg(status));
890 			_parent->threadSafeFailed();
891 			return;
892 		}
893 	}
894 
895 	DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total));
896 
897 	const auto data = _reply->readAll();
898 	_parent->writeChunk(bytes::make_span(data), total);
899 }
900 
partFailed(QNetworkReply::NetworkError e)901 void HttpLoaderActor::partFailed(QNetworkReply::NetworkError e) {
902 	if (!_reply) return;
903 
904 	const auto statusCode = _reply->attribute(
905 		QNetworkRequest::HttpStatusCodeAttribute);
906 	_reply.release()->deleteLater();
907 	if (statusCode.isValid()) {
908 		const auto status = statusCode.toInt();
909 		if (status == 416) { // Requested range not satisfiable
910 			_parent->writeChunk({}, _parent->alreadySize());
911 			return;
912 		}
913 	}
914 	LOG(("Update Error: failed to download part after %1, error %2"
915 		).arg(_parent->alreadySize()
916 		).arg(e));
917 	_parent->threadSafeFailed();
918 }
919 
MtpChecker(base::weak_ptr<Main::Session> session,bool testing)920 MtpChecker::MtpChecker(
921 	base::weak_ptr<Main::Session> session,
922 	bool testing)
923 : Checker(testing)
924 , _mtp(session) {
925 }
926 
start()927 void MtpChecker::start() {
928 	if (!_mtp.valid()) {
929 		LOG(("Update Info: MTP is unavailable."));
930 		crl::on_main(this, [=] { fail(); });
931 		return;
932 	}
933 	const auto updaterVersion = Platform::AutoUpdateVersion();
934 	const auto feed = "tdhbcfeed"
935 		+ (updaterVersion > 1 ? QString::number(updaterVersion) : QString());
936 	MTP::ResolveChannel(&_mtp, feed, [=](
937 			const MTPInputChannel &channel) {
938 		_mtp.send(
939 			MTPmessages_GetHistory(
940 				MTP_inputPeerChannel(
941 					channel.c_inputChannel().vchannel_id(),
942 					channel.c_inputChannel().vaccess_hash()),
943 				MTP_int(0),  // offset_id
944 				MTP_int(0),  // offset_date
945 				MTP_int(0),  // add_offset
946 				MTP_int(1),  // limit
947 				MTP_int(0),  // max_id
948 				MTP_int(0),  // min_id
949 				MTP_long(0)), // hash
950 			[=](const MTPmessages_Messages &result) { gotMessage(result); },
951 			failHandler());
952 	}, [=] { fail(); });
953 }
954 
gotMessage(const MTPmessages_Messages & result)955 void MtpChecker::gotMessage(const MTPmessages_Messages &result) {
956 	const auto location = parseMessage(result);
957 	if (!location) {
958 		fail();
959 		return;
960 	} else if (location->username.isEmpty()) {
961 		done(nullptr);
962 		return;
963 	}
964 	const auto ready = [=](std::unique_ptr<MTP::DedicatedLoader> loader) {
965 		if (loader) {
966 			done(std::move(loader));
967 		} else {
968 			fail();
969 		}
970 	};
971 	MTP::StartDedicatedLoader(&_mtp, *location, UpdatesFolder(), ready);
972 }
973 
parseMessage(const MTPmessages_Messages & result) const974 auto MtpChecker::parseMessage(const MTPmessages_Messages &result) const
975 -> std::optional<FileLocation> {
976 	const auto message = MTP::GetMessagesElement(result);
977 	if (!message || message->type() != mtpc_message) {
978 		LOG(("Update Error: MTP feed message not found."));
979 		return std::nullopt;
980 	}
981 	return parseText(message->c_message().vmessage().v);
982 }
983 
parseText(const QByteArray & text) const984 auto MtpChecker::parseText(const QByteArray &text) const
985 -> std::optional<FileLocation> {
986 	auto bestAvailableVersion = 0ULL;
987 	auto bestLocation = FileLocation();
988 	const auto accumulate = [&](
989 			uint64 version,
990 			bool isAlpha,
991 			const QJsonObject &map) {
992 		if (isAlpha) {
993 			LOG(("Update Error: MTP closed alpha found."));
994 			return false;
995 		}
996 		bestAvailableVersion = version;
997 		const auto key = testing() ? "testing" : "released";
998 		const auto entry = map.constFind(key);
999 		if (entry == map.constEnd()) {
1000 			LOG(("Update Error: MTP entry not found for version %1."
1001 				).arg(version));
1002 			return false;
1003 		} else if (!(*entry).isString()) {
1004 			LOG(("Update Error: MTP entry is not a string for version %1."
1005 				).arg(version));
1006 			return false;
1007 		}
1008 		const auto full = (*entry).toString();
1009 		const auto start = full.indexOf(':');
1010 		const auto post = full.indexOf('#');
1011 		if (start <= 0 || post < start) {
1012 			LOG(("Update Error: MTP entry '%1' is bad for version %2."
1013 				).arg(full
1014 				).arg(version));
1015 			return false;
1016 		}
1017 		bestLocation.username = full.mid(start + 1, post - start - 1);
1018 		bestLocation.postId = base::StringViewMid(full, post + 1).toInt();
1019 		if (bestLocation.username.isEmpty() || !bestLocation.postId) {
1020 			LOG(("Update Error: MTP entry '%1' is bad for version %2."
1021 				).arg(full
1022 				).arg(version));
1023 			return false;
1024 		}
1025 		return true;
1026 	};
1027 	const auto result = ParseCommonMap(text, testing(), accumulate);
1028 	if (!result) {
1029 		return std::nullopt;
1030 	}
1031 	return validateLatestLocation(bestAvailableVersion, bestLocation);
1032 }
1033 
validateLatestLocation(uint64 availableVersion,const FileLocation & location) const1034 auto MtpChecker::validateLatestLocation(
1035 		uint64 availableVersion,
1036 		const FileLocation &location) const -> FileLocation {
1037 	const auto myVersion = uint64(AppVersion);
1038 	return (availableVersion <= myVersion) ? FileLocation() : location;
1039 }
1040 
failHandler()1041 Fn<void(const MTP::Error &error)> MtpChecker::failHandler() {
1042 	return [=](const MTP::Error &error) {
1043 		LOG(("Update Error: MTP check failed with '%1'"
1044 			).arg(QString::number(error.code()) + ':' + error.type()));
1045 		fail();
1046 	};
1047 }
1048 
1049 } // namespace
1050 
UpdaterDisabled()1051 bool UpdaterDisabled() {
1052 	return UpdaterIsDisabled;
1053 }
1054 
SetUpdaterDisabledAtStartup()1055 void SetUpdaterDisabledAtStartup() {
1056 	Expects(UpdaterInstance.lock() == nullptr);
1057 
1058 	UpdaterIsDisabled = true;
1059 }
1060 
1061 class Updater : public base::has_weak_ptr {
1062 public:
1063 	Updater();
1064 
1065 	rpl::producer<> checking() const;
1066 	rpl::producer<> isLatest() const;
1067 	rpl::producer<Progress> progress() const;
1068 	rpl::producer<> failed() const;
1069 	rpl::producer<> ready() const;
1070 
1071 	void start(bool forceWait);
1072 	void stop();
1073 	void test();
1074 
1075 	State state() const;
1076 	int already() const;
1077 	int size() const;
1078 
1079 	void setMtproto(base::weak_ptr<Main::Session> session);
1080 
1081 	~Updater();
1082 
1083 private:
1084 	enum class Action {
1085 		Waiting,
1086 		Checking,
1087 		Loading,
1088 		Unpacking,
1089 		Ready,
1090 	};
1091 	void check();
1092 	void startImplementation(
1093 		not_null<Implementation*> which,
1094 		std::unique_ptr<Checker> checker);
1095 	bool tryLoaders();
1096 	void handleTimeout();
1097 	void checkerDone(
1098 		not_null<Implementation*> which,
1099 		std::shared_ptr<Loader> loader);
1100 	void checkerFail(not_null<Implementation*> which);
1101 
1102 	void finalize(QString filepath);
1103 	void unpackDone(bool ready);
1104 	void handleChecking();
1105 	void handleProgress();
1106 	void handleLatest();
1107 	void handleFailed();
1108 	void handleReady();
1109 	void scheduleNext();
1110 
1111 	bool _testing = false;
1112 	Action _action = Action::Waiting;
1113 	base::Timer _timer;
1114 	base::Timer _retryTimer;
1115 	rpl::event_stream<> _checking;
1116 	rpl::event_stream<> _isLatest;
1117 	rpl::event_stream<Progress> _progress;
1118 	rpl::event_stream<> _failed;
1119 	rpl::event_stream<> _ready;
1120 	Implementation _httpImplementation;
1121 	Implementation _mtpImplementation;
1122 	std::shared_ptr<Loader> _activeLoader;
1123 	bool _usingMtprotoLoader = (cAlphaVersion() != 0);
1124 	base::weak_ptr<Main::Session> _session;
1125 
1126 	rpl::lifetime _lifetime;
1127 
1128 };
1129 
Updater()1130 Updater::Updater()
1131 : _timer([=] { check(); })
__anona3063f7d1302null1132 , _retryTimer([=] { handleTimeout(); }) {
__anona3063f7d1402null1133 	checking() | rpl::start_with_next([=] {
1134 		handleChecking();
1135 	}, _lifetime);
__anona3063f7d1502null1136 	progress() | rpl::start_with_next([=] {
1137 		handleProgress();
1138 	}, _lifetime);
__anona3063f7d1602null1139 	failed() | rpl::start_with_next([=] {
1140 		handleFailed();
1141 	}, _lifetime);
__anona3063f7d1702null1142 	ready() | rpl::start_with_next([=] {
1143 		handleReady();
1144 	}, _lifetime);
__anona3063f7d1802null1145 	isLatest() | rpl::start_with_next([=] {
1146 		handleLatest();
1147 	}, _lifetime);
1148 }
1149 
checking() const1150 rpl::producer<> Updater::checking() const {
1151 	return _checking.events();
1152 }
1153 
isLatest() const1154 rpl::producer<> Updater::isLatest() const {
1155 	return _isLatest.events();
1156 }
1157 
progress() const1158 auto Updater::progress() const
1159 -> rpl::producer<Progress> {
1160 	return _progress.events();
1161 }
1162 
failed() const1163 rpl::producer<> Updater::failed() const {
1164 	return _failed.events();
1165 }
1166 
ready() const1167 rpl::producer<> Updater::ready() const {
1168 	return _ready.events();
1169 }
1170 
check()1171 void Updater::check() {
1172 	start(false);
1173 }
1174 
handleReady()1175 void Updater::handleReady() {
1176 	stop();
1177 	_action = Action::Ready;
1178 	if (!App::quitting()) {
1179 		cSetLastUpdateCheck(base::unixtime::now());
1180 		Local::writeSettings();
1181 	}
1182 }
1183 
handleFailed()1184 void Updater::handleFailed() {
1185 	scheduleNext();
1186 }
1187 
handleLatest()1188 void Updater::handleLatest() {
1189 	if (const auto update = FindUpdateFile(); !update.isEmpty()) {
1190 		QFile(update).remove();
1191 	}
1192 	scheduleNext();
1193 }
1194 
handleChecking()1195 void Updater::handleChecking() {
1196 	_action = Action::Checking;
1197 	_retryTimer.callOnce(kUpdaterTimeout);
1198 }
1199 
handleProgress()1200 void Updater::handleProgress() {
1201 	_retryTimer.callOnce(kUpdaterTimeout);
1202 }
1203 
scheduleNext()1204 void Updater::scheduleNext() {
1205 	stop();
1206 	if (!App::quitting()) {
1207 		cSetLastUpdateCheck(base::unixtime::now());
1208 		Local::writeSettings();
1209 		start(true);
1210 	}
1211 }
1212 
state() const1213 auto Updater::state() const -> State {
1214 	if (_action == Action::Ready) {
1215 		return State::Ready;
1216 	} else if (_action == Action::Loading) {
1217 		return State::Download;
1218 	}
1219 	return State::None;
1220 }
1221 
size() const1222 int Updater::size() const {
1223 	return _activeLoader ? _activeLoader->totalSize() : 0;
1224 }
1225 
already() const1226 int Updater::already() const {
1227 	return _activeLoader ? _activeLoader->alreadySize() : 0;
1228 }
1229 
stop()1230 void Updater::stop() {
1231 	_httpImplementation = Implementation();
1232 	_mtpImplementation = Implementation();
1233 	_activeLoader = nullptr;
1234 	_action = Action::Waiting;
1235 }
1236 
start(bool forceWait)1237 void Updater::start(bool forceWait) {
1238 	if (cExeName().isEmpty()) {
1239 		return;
1240 	}
1241 
1242 	_timer.cancel();
1243 	if (!cAutoUpdate() || _action != Action::Waiting) {
1244 		return;
1245 	}
1246 
1247 	_retryTimer.cancel();
1248 	const auto constDelay = cAlphaVersion() ? 600 : UpdateDelayConstPart;
1249 	const auto randDelay = cAlphaVersion() ? 300 : UpdateDelayRandPart;
1250 	const auto updateInSecs = cLastUpdateCheck()
1251 		+ constDelay
1252 		+ int(rand() % randDelay)
1253 		- base::unixtime::now();
1254 	auto sendRequest = (updateInSecs <= 0)
1255 		|| (updateInSecs > constDelay + randDelay);
1256 	if (!sendRequest && !forceWait) {
1257 		if (!FindUpdateFile().isEmpty()) {
1258 			sendRequest = true;
1259 		}
1260 	}
1261 	if (cManyInstance() && !Logs::DebugEnabled()) {
1262 		// Only main instance is updating.
1263 		return;
1264 	}
1265 
1266 	if (sendRequest) {
1267 		startImplementation(
1268 			&_httpImplementation,
1269 			std::make_unique<HttpChecker>(_testing));
1270 		startImplementation(
1271 			&_mtpImplementation,
1272 			std::make_unique<MtpChecker>(_session, _testing));
1273 
1274 		_checking.fire({});
1275 	} else {
1276 		_timer.callOnce((updateInSecs + 5) * crl::time(1000));
1277 	}
1278 }
1279 
startImplementation(not_null<Implementation * > which,std::unique_ptr<Checker> checker)1280 void Updater::startImplementation(
1281 		not_null<Implementation*> which,
1282 		std::unique_ptr<Checker> checker) {
1283 	if (!checker) {
1284 		class EmptyChecker : public Checker {
1285 		public:
1286 			EmptyChecker() : Checker(false) {
1287 			}
1288 
1289 			void start() override {
1290 				crl::on_main(this, [=] { fail(); });
1291 			}
1292 
1293 		};
1294 		checker = std::make_unique<EmptyChecker>();
1295 	}
1296 
1297 	checker->ready(
1298 	) | rpl::start_with_next([=](std::shared_ptr<Loader> &&loader) {
1299 		checkerDone(which, std::move(loader));
1300 	}, checker->lifetime());
1301 	checker->failed(
1302 	) | rpl::start_with_next([=] {
1303 		checkerFail(which);
1304 	}, checker->lifetime());
1305 
1306 	*which = Implementation{ std::move(checker) };
1307 
1308 	crl::on_main(which->checker.get(), [=] {
1309 		which->checker->start();
1310 	});
1311 }
1312 
checkerDone(not_null<Implementation * > which,std::shared_ptr<Loader> loader)1313 void Updater::checkerDone(
1314 		not_null<Implementation*> which,
1315 		std::shared_ptr<Loader> loader) {
1316 	which->checker = nullptr;
1317 	which->loader = std::move(loader);
1318 
1319 	tryLoaders();
1320 }
1321 
checkerFail(not_null<Implementation * > which)1322 void Updater::checkerFail(not_null<Implementation*> which) {
1323 	which->checker = nullptr;
1324 	which->failed = true;
1325 
1326 	tryLoaders();
1327 }
1328 
test()1329 void Updater::test() {
1330 	_testing = true;
1331 	cSetLastUpdateCheck(0);
1332 	start(false);
1333 }
1334 
setMtproto(base::weak_ptr<Main::Session> session)1335 void Updater::setMtproto(base::weak_ptr<Main::Session> session) {
1336 	_session = session;
1337 }
1338 
handleTimeout()1339 void Updater::handleTimeout() {
1340 	if (_action == Action::Checking) {
1341 		const auto reset = [&](Implementation &which) {
1342 			if (base::take(which.checker)) {
1343 				which.failed = true;
1344 			}
1345 		};
1346 		reset(_httpImplementation);
1347 		reset(_mtpImplementation);
1348 		if (!tryLoaders()) {
1349 			cSetLastUpdateCheck(0);
1350 			_timer.callOnce(kUpdaterTimeout);
1351 		}
1352 	} else if (_action == Action::Loading) {
1353 		_failed.fire({});
1354 	}
1355 }
1356 
tryLoaders()1357 bool Updater::tryLoaders() {
1358 	if (_httpImplementation.checker || _mtpImplementation.checker) {
1359 		// Some checkers didn't finish yet.
1360 		return true;
1361 	}
1362 	_retryTimer.cancel();
1363 
1364 	const auto tryOne = [&](Implementation &which) {
1365 		_activeLoader = std::move(which.loader);
1366 		if (const auto loader = _activeLoader.get()) {
1367 			_action = Action::Loading;
1368 
1369 			loader->progress(
1370 			) | rpl::start_to_stream(_progress, loader->lifetime());
1371 			loader->ready(
1372 			) | rpl::start_with_next([=](QString &&filepath) {
1373 				finalize(std::move(filepath));
1374 			}, loader->lifetime());
1375 			loader->failed(
1376 			) | rpl::start_with_next([=] {
1377 				_failed.fire({});
1378 			}, loader->lifetime());
1379 
1380 			_retryTimer.callOnce(kUpdaterTimeout);
1381 			loader->wipeFolder();
1382 			loader->start();
1383 		} else {
1384 			_isLatest.fire({});
1385 		}
1386 	};
1387 	if (_mtpImplementation.failed && _httpImplementation.failed) {
1388 		_failed.fire({});
1389 		return false;
1390 	} else if (!_mtpImplementation.loader) {
1391 		tryOne(_httpImplementation);
1392 	} else if (!_httpImplementation.loader) {
1393 		tryOne(_mtpImplementation);
1394 	} else {
1395 		tryOne(_usingMtprotoLoader
1396 			? _mtpImplementation
1397 			: _httpImplementation);
1398 		_usingMtprotoLoader = !_usingMtprotoLoader;
1399 	}
1400 	return true;
1401 }
1402 
finalize(QString filepath)1403 void Updater::finalize(QString filepath) {
1404 	if (_action != Action::Loading) {
1405 		return;
1406 	}
1407 	_retryTimer.cancel();
1408 	_activeLoader = nullptr;
1409 	_action = Action::Unpacking;
1410 	crl::async([=] {
1411 		const auto ready = UnpackUpdate(filepath);
1412 		crl::on_main([=] {
1413 			GetUpdaterInstance()->unpackDone(ready);
1414 		});
1415 	});
1416 }
1417 
unpackDone(bool ready)1418 void Updater::unpackDone(bool ready) {
1419 	if (ready) {
1420 		_ready.fire({});
1421 	} else {
1422 		ClearAll();
1423 		_failed.fire({});
1424 	}
1425 }
1426 
~Updater()1427 Updater::~Updater() {
1428 	stop();
1429 }
1430 
UpdateChecker()1431 UpdateChecker::UpdateChecker()
1432 : _updater(GetUpdaterInstance()) {
1433 	if (IsAppLaunched() && Core::App().domain().started()) {
1434 		if (const auto session = Core::App().activeAccount().maybeSession()) {
1435 			_updater->setMtproto(session);
1436 		}
1437 	}
1438 }
1439 
checking() const1440 rpl::producer<> UpdateChecker::checking() const {
1441 	return _updater->checking();
1442 }
1443 
isLatest() const1444 rpl::producer<> UpdateChecker::isLatest() const {
1445 	return _updater->isLatest();
1446 }
1447 
progress() const1448 auto UpdateChecker::progress() const
1449 -> rpl::producer<Progress> {
1450 	return _updater->progress();
1451 }
1452 
failed() const1453 rpl::producer<> UpdateChecker::failed() const {
1454 	return _updater->failed();
1455 }
1456 
ready() const1457 rpl::producer<> UpdateChecker::ready() const {
1458 	return _updater->ready();
1459 }
1460 
start(bool forceWait)1461 void UpdateChecker::start(bool forceWait) {
1462 	_updater->start(forceWait);
1463 }
1464 
test()1465 void UpdateChecker::test() {
1466 	_updater->test();
1467 }
1468 
setMtproto(base::weak_ptr<Main::Session> session)1469 void UpdateChecker::setMtproto(base::weak_ptr<Main::Session> session) {
1470 	_updater->setMtproto(session);
1471 }
1472 
stop()1473 void UpdateChecker::stop() {
1474 	_updater->stop();
1475 }
1476 
state() const1477 auto UpdateChecker::state() const
1478 -> State {
1479 	return _updater->state();
1480 }
1481 
already() const1482 int UpdateChecker::already() const {
1483 	return _updater->already();
1484 }
1485 
size() const1486 int UpdateChecker::size() const {
1487 	return _updater->size();
1488 }
1489 
1490 //QString winapiErrorWrap() {
1491 //	WCHAR errMsg[2048];
1492 //	DWORD errorCode = GetLastError();
1493 //	LPTSTR errorText = NULL, errorTextDefault = L"(Unknown error)";
1494 //	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorText, 0, 0);
1495 //	if (!errorText) {
1496 //		errorText = errorTextDefault;
1497 //	}
1498 //	StringCbPrintf(errMsg, sizeof(errMsg), L"Error code: %d, error message: %s", errorCode, errorText);
1499 //	if (errorText != errorTextDefault) {
1500 //		LocalFree(errorText);
1501 //	}
1502 //	return QString::fromWCharArray(errMsg);
1503 //}
1504 
checkReadyUpdate()1505 bool checkReadyUpdate() {
1506 	QString readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"), readyPath = cWorkingDir() + qsl("tupdates/temp");
1507 	if (!QFile(readyFilePath).exists() || cExeName().isEmpty()) {
1508 		if (QDir(cWorkingDir() + qsl("tupdates/ready")).exists() || QDir(cWorkingDir() + qsl("tupdates/temp")).exists()) {
1509 			ClearAll();
1510 		}
1511 		return false;
1512 	}
1513 
1514 	// check ready version
1515 	QString versionPath = readyPath + qsl("/tdata/version");
1516 	{
1517 		QFile fVersion(versionPath);
1518 		if (!fVersion.open(QIODevice::ReadOnly)) {
1519 			LOG(("Update Error: cant read version file '%1'").arg(versionPath));
1520 			ClearAll();
1521 			return false;
1522 		}
1523 		auto versionNum = VersionInt();
1524 		if (fVersion.read((char*)&versionNum, sizeof(VersionInt)) != sizeof(VersionInt)) {
1525 			LOG(("Update Error: cant read version from file '%1'").arg(versionPath));
1526 			ClearAll();
1527 			return false;
1528 		}
1529 		if (versionNum == 0x7FFFFFFF) { // alpha version
1530 			quint64 alphaVersion = 0;
1531 			if (fVersion.read((char*)&alphaVersion, sizeof(quint64)) != sizeof(quint64)) {
1532 				LOG(("Update Error: cant read alpha version from file '%1'").arg(versionPath));
1533 				ClearAll();
1534 				return false;
1535 			}
1536 			if (!cAlphaVersion() || alphaVersion <= cAlphaVersion()) {
1537 				LOG(("Update Error: cant install alpha version %1 having alpha version %2").arg(alphaVersion).arg(cAlphaVersion()));
1538 				ClearAll();
1539 				return false;
1540 			}
1541 		} else if (versionNum <= AppVersion) {
1542 			LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion));
1543 			ClearAll();
1544 			return false;
1545 		}
1546 		fVersion.close();
1547 	}
1548 
1549 #ifdef Q_OS_WIN
1550 	QString curUpdater = (cExeDir() + qsl("Updater.exe"));
1551 	QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Updater.exe"));
1552 #elif defined Q_OS_MAC // Q_OS_WIN
1553 	QString curUpdater = (cExeDir() + cExeName() + qsl("/Contents/Frameworks/Updater"));
1554 	QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Telegram.app/Contents/Frameworks/Updater"));
1555 #elif defined Q_OS_UNIX // Q_OS_MAC
1556 	QString curUpdater = (cExeDir() + qsl("Updater"));
1557 	QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Updater"));
1558 #endif // Q_OS_UNIX
1559 	if (!updater.exists()) {
1560 		QFileInfo current(curUpdater);
1561 		if (!current.exists()) {
1562 			ClearAll();
1563 			return false;
1564 		}
1565 		if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) {
1566 			ClearAll();
1567 			return false;
1568 		}
1569 	}
1570 #ifdef Q_OS_WIN
1571 	if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) {
1572 		DWORD errorCode = GetLastError();
1573 		if (errorCode == ERROR_ACCESS_DENIED) { // we are in write-protected dir, like Program Files
1574 			cSetWriteProtected(true);
1575 			return true;
1576 		} else {
1577 			ClearAll();
1578 			return false;
1579 		}
1580 	}
1581 	if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) {
1582 		ClearAll();
1583 		return false;
1584 	}
1585 #elif defined Q_OS_MAC // Q_OS_WIN
1586 	QDir().mkpath(QFileInfo(curUpdater).absolutePath());
1587 	DEBUG_LOG(("Update Info: moving %1 to %2...").arg(updater.absoluteFilePath()).arg(curUpdater));
1588 	if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) {
1589 		ClearAll();
1590 		return false;
1591 	}
1592 #elif defined Q_OS_UNIX // Q_OS_MAC
1593 	// if the files in the directory are owned by user, while the directory is not,
1594 	// update will still fail since it's not possible to remove files
1595 	if (QFile::exists(curUpdater)
1596 		&& unlink(QFile::encodeName(curUpdater).constData())) {
1597 		if (errno == EACCES) {
1598 			DEBUG_LOG(("Update Info: "
1599 				"could not unlink current Updater, access denied."));
1600 			cSetWriteProtected(true);
1601 			return true;
1602 		} else {
1603 			DEBUG_LOG(("Update Error: could not unlink current Updater."));
1604 			ClearAll();
1605 			return false;
1606 		}
1607 	}
1608 	if (!linuxMoveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) {
1609 		if (errno == EACCES) {
1610 			DEBUG_LOG(("Update Info: "
1611 				"could not copy new Updater, access denied."));
1612 			cSetWriteProtected(true);
1613 			return true;
1614 		} else {
1615 			DEBUG_LOG(("Update Error: could not copy new Updater."));
1616 			ClearAll();
1617 			return false;
1618 		}
1619 	}
1620 #endif // Q_OS_UNIX
1621 
1622 #ifdef Q_OS_MAC
1623 	base::Platform::RemoveQuarantine(QFileInfo(curUpdater).absolutePath());
1624 	base::Platform::RemoveQuarantine(updater.absolutePath());
1625 #endif // Q_OS_MAC
1626 
1627 	return true;
1628 }
1629 
UpdateApplication()1630 void UpdateApplication() {
1631 	if (UpdaterDisabled()) {
1632 		const auto url = [&] {
1633 #ifdef OS_WIN_STORE
1634 			return "https://www.microsoft.com/en-us/store/p/telegram-desktop/9nztwsqntd0s";
1635 #elif defined OS_MAC_STORE // OS_WIN_STORE
1636 			return "https://itunes.apple.com/ae/app/telegram-desktop/id946399090";
1637 #elif defined Q_OS_UNIX && !defined Q_OS_MAC // OS_WIN_STORE || OS_MAC_STORE
1638 			if (Platform::InFlatpak()) {
1639 				return "https://flathub.org/apps/details/org.telegram.desktop";
1640 			} else if (Platform::InSnap()) {
1641 				return "https://snapcraft.io/telegram-desktop";
1642 			}
1643 			return "https://desktop.telegram.org";
1644 #else // OS_WIN_STORE || OS_MAC_STORE || (defined Q_OS_UNIX && !defined Q_OS_MAC)
1645 			return "https://desktop.telegram.org";
1646 #endif // OS_WIN_STORE || OS_MAC_STORE || (defined Q_OS_UNIX && !defined Q_OS_MAC)
1647 		}();
1648 		UrlClickHandler::Open(url);
1649 	} else {
1650 		cSetAutoUpdate(true);
1651 		if (const auto window = App::wnd()) {
1652 			if (const auto controller = window->sessionController()) {
1653 				controller->showSection(
1654 					std::make_shared<Info::Memento>(
1655 						Info::Settings::Tag{ controller->session().user() },
1656 						Info::Section::SettingsType::Advanced),
1657 					Window::SectionShow());
1658 			} else {
1659 				window->showSpecialLayer(
1660 					Box<::Settings::LayerWidget>(&window->controller()),
1661 					anim::type::normal);
1662 			}
1663 			window->showFromTray();
1664 		}
1665 		cSetLastUpdateCheck(0);
1666 		Core::UpdateChecker().start();
1667 	}
1668 }
1669 
countAlphaVersionSignature(uint64 version)1670 QString countAlphaVersionSignature(uint64 version) { // duplicated in packer.cpp
1671 	if (cAlphaPrivateKey().isEmpty()) {
1672 		LOG(("Error: Trying to count alpha version signature without alpha private key!"));
1673 		return QString();
1674 	}
1675 
1676 	QByteArray signedData = (qstr("TelegramBeta_") + QString::number(version, 16).toLower()).toUtf8();
1677 
1678 	static const int32 shaSize = 20, keySize = 128;
1679 
1680 	uchar sha1Buffer[shaSize];
1681 	hashSha1(signedData.constData(), signedData.size(), sha1Buffer); // count sha1
1682 
1683 	uint32 siglen = 0;
1684 
1685 	RSA *prKey = [] {
1686 		const auto bio = MakeBIO(
1687 			const_cast<char*>(cAlphaPrivateKey().constData()),
1688 			-1);
1689 		return PEM_read_bio_RSAPrivateKey(bio.get(), 0, 0, 0);
1690 	}();
1691 	if (!prKey) {
1692 		LOG(("Error: Could not read alpha private key!"));
1693 		return QString();
1694 	}
1695 	if (RSA_size(prKey) != keySize) {
1696 		LOG(("Error: Bad alpha private key size: %1").arg(RSA_size(prKey)));
1697 		RSA_free(prKey);
1698 		return QString();
1699 	}
1700 	QByteArray signature;
1701 	signature.resize(keySize);
1702 	if (RSA_sign(NID_sha1, (const uchar*)(sha1Buffer), shaSize, (uchar*)(signature.data()), &siglen, prKey) != 1) { // count signature
1703 		LOG(("Error: Counting alpha version signature failed!"));
1704 		RSA_free(prKey);
1705 		return QString();
1706 	}
1707 	RSA_free(prKey);
1708 
1709 	if (siglen != keySize) {
1710 		LOG(("Error: Bad alpha version signature length: %1").arg(siglen));
1711 		return QString();
1712 	}
1713 
1714 	signature = signature.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
1715 	signature = signature.replace('-', '8').replace('_', 'B');
1716 	return QString::fromUtf8(signature.mid(19, 32));
1717 }
1718 
1719 } // namespace Core
1720