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