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 "storage/localstorage.h"
9 //
10 #include "storage/serialize_common.h"
11 #include "storage/storage_account.h"
12 #include "storage/details/storage_file_utilities.h"
13 #include "storage/details/storage_settings_scheme.h"
14 #include "data/data_session.h"
15 #include "data/data_document.h"
16 #include "data/data_document_media.h"
17 #include "base/platform/base_platform_info.h"
18 #include "base/random.h"
19 #include "ui/effects/animation_value.h"
20 #include "core/update_checker.h"
21 #include "core/file_location.h"
22 #include "core/application.h"
23 #include "media/audio/media_audio.h"
24 #include "mtproto/mtproto_config.h"
25 #include "mtproto/mtproto_dc_options.h"
26 #include "main/main_domain.h"
27 #include "main/main_account.h"
28 #include "main/main_session.h"
29 #include "window/themes/window_theme.h"
30 #include "lang/lang_instance.h"
31
32 #include <QtCore/QDirIterator>
33
34 #ifndef Q_OS_WIN
35 #include <unistd.h>
36 #endif // Q_OS_WIN
37
38 //extern "C" {
39 //#include <openssl/evp.h>
40 //} // extern "C"
41
42 namespace Local {
43 namespace {
44
45 constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024;
46 constexpr auto kFileLoaderQueueStopTimeout = crl::time(5000);
47
48 constexpr auto kSavedBackgroundFormat = QImage::Format_ARGB32_Premultiplied;
49 constexpr auto kWallPaperLegacySerializeTagId = int32(-111);
50 constexpr auto kWallPaperSerializeTagId = int32(-112);
51 constexpr auto kWallPaperSidesLimit = 10'000;
52
53 const auto kThemeNewPathRelativeTag = qstr("special://new_tag");
54
55 using namespace Storage::details;
56 using Storage::FileKey;
57
58 using Database = Storage::Cache::Database;
59
60 QString _basePath, _userBasePath, _userDbPath;
61
62 TaskQueue *_localLoader = nullptr;
63
64 QByteArray _settingsSalt;
65
66 auto OldKey = MTP::AuthKeyPtr();
67 auto SettingsKey = MTP::AuthKeyPtr();
68
69 FileKey _themeKeyDay = 0;
70 FileKey _themeKeyNight = 0;
71
72 // Theme key legacy may be read in start() with settings.
73 // But it should be moved to keyDay or keyNight inside InitialLoadTheme()
74 // and never used after.
75 FileKey _themeKeyLegacy = 0;
76 FileKey _langPackKey = 0;
77 FileKey _languagesKey = 0;
78
79 FileKey _backgroundKeyDay = 0;
80 FileKey _backgroundKeyNight = 0;
81 bool _useGlobalBackgroundKeys = false;
82 bool _backgroundCanWrite = true;
83
84 int32 _oldSettingsVersion = 0;
85 bool _settingsRewriteNeeded = false;
86 bool _settingsWriteAllowed = false;
87
88 enum class WriteMapWhen {
89 Now,
90 Fast,
91 Soon,
92 };
93
CheckStreamStatus(QDataStream & stream)94 bool CheckStreamStatus(QDataStream &stream) {
95 if (stream.status() != QDataStream::Ok) {
96 LOG(("Bad data stream status: %1").arg(stream.status()));
97 return false;
98 }
99 return true;
100 }
101
LookupFallbackConfig()102 [[nodiscard]] const MTP::Config &LookupFallbackConfig() {
103 static const auto lookupConfig = [](not_null<Main::Account*> account) {
104 const auto mtp = &account->mtp();
105 const auto production = MTP::Environment::Production;
106 return (mtp->environment() == production)
107 ? &mtp->config()
108 : nullptr;
109 };
110 const auto &app = Core::App();
111 const auto &domain = app.domain();
112 if (!domain.started()) {
113 return app.fallbackProductionConfig();
114 }
115 if (const auto result = lookupConfig(&app.activeAccount())) {
116 return *result;
117 }
118 for (const auto &[_, account] : domain.accounts()) {
119 if (const auto result = lookupConfig(account.get())) {
120 return *result;
121 }
122 }
123 return app.fallbackProductionConfig();
124 }
125
applyReadContext(ReadSettingsContext && context)126 void applyReadContext(ReadSettingsContext &&context) {
127 ApplyReadFallbackConfig(context);
128
129 DEBUG_LOG(("Theme: applying context, legacy: %1, day: %2, night: %3"
130 ).arg(context.themeKeyLegacy
131 ).arg(context.themeKeyDay
132 ).arg(context.themeKeyNight));
133 _themeKeyLegacy = context.themeKeyLegacy;
134 _themeKeyDay = context.themeKeyDay;
135 _themeKeyNight = context.themeKeyNight;
136 _backgroundKeyDay = context.backgroundKeyDay;
137 _backgroundKeyNight = context.backgroundKeyNight;
138 _useGlobalBackgroundKeys = context.backgroundKeysRead;
139 _langPackKey = context.langPackKey;
140 _languagesKey = context.languagesKey;
141 }
142
_readOldSettings(bool remove,ReadSettingsContext & context)143 bool _readOldSettings(bool remove, ReadSettingsContext &context) {
144 bool result = false;
145 QFile file(cWorkingDir() + qsl("tdata/config"));
146 if (file.open(QIODevice::ReadOnly)) {
147 LOG(("App Info: reading old config..."));
148 QDataStream stream(&file);
149 stream.setVersion(QDataStream::Qt_5_1);
150
151 qint32 version = 0;
152 while (!stream.atEnd()) {
153 quint32 blockId;
154 stream >> blockId;
155 if (!CheckStreamStatus(stream)) break;
156
157 if (blockId == dbiVersion) {
158 stream >> version;
159 if (!CheckStreamStatus(stream)) break;
160
161 if (version > AppVersion) break;
162 } else if (!ReadSetting(blockId, stream, version, context)) {
163 break;
164 }
165 }
166 file.close();
167 result = true;
168 }
169 if (remove) file.remove();
170 return result;
171 }
172
_readOldUserSettingsFields(QIODevice * device,qint32 & version,ReadSettingsContext & context)173 void _readOldUserSettingsFields(
174 QIODevice *device,
175 qint32 &version,
176 ReadSettingsContext &context) {
177 QDataStream stream(device);
178 stream.setVersion(QDataStream::Qt_5_1);
179
180 while (!stream.atEnd()) {
181 quint32 blockId;
182 stream >> blockId;
183 if (!CheckStreamStatus(stream)) {
184 break;
185 }
186
187 if (blockId == dbiVersion) {
188 stream >> version;
189 if (!CheckStreamStatus(stream)) {
190 break;
191 }
192
193 if (version > AppVersion) return;
194 } else if (blockId == dbiEncryptedWithSalt) {
195 QByteArray salt, data, decrypted;
196 stream >> salt >> data;
197 if (!CheckStreamStatus(stream)) {
198 break;
199 }
200
201 if (salt.size() != 32) {
202 LOG(("App Error: bad salt in old user config encrypted part, size: %1").arg(salt.size()));
203 continue;
204 }
205
206 OldKey = CreateLegacyLocalKey(QByteArray(), salt);
207
208 if (data.size() <= 16 || (data.size() & 0x0F)) {
209 LOG(("App Error: bad encrypted part size in old user config: %1").arg(data.size()));
210 continue;
211 }
212 uint32 fullDataLen = data.size() - 16;
213 decrypted.resize(fullDataLen);
214 const char *dataKey = data.constData(), *encrypted = data.constData() + 16;
215 aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, OldKey, dataKey);
216 uchar sha1Buffer[20];
217 if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) {
218 LOG(("App Error: bad decrypt key, data from old user config not decrypted"));
219 continue;
220 }
221 uint32 dataLen = *(const uint32*)decrypted.constData();
222 if (dataLen > uint32(decrypted.size()) || dataLen <= fullDataLen - 16 || dataLen < 4) {
223 LOG(("App Error: bad decrypted part size in old user config: %1, fullDataLen: %2, decrypted size: %3").arg(dataLen).arg(fullDataLen).arg(decrypted.size()));
224 continue;
225 }
226 decrypted.resize(dataLen);
227 QBuffer decryptedStream(&decrypted);
228 decryptedStream.open(QIODevice::ReadOnly);
229 decryptedStream.seek(4); // skip size
230 LOG(("App Info: reading encrypted old user config..."));
231
232 _readOldUserSettingsFields(&decryptedStream, version, context);
233 } else if (!ReadSetting(blockId, stream, version, context)) {
234 return;
235 }
236 }
237 }
238
_readOldUserSettings(bool remove,ReadSettingsContext & context)239 bool _readOldUserSettings(bool remove, ReadSettingsContext &context) {
240 bool result = false;
241 // We dropped old test authorizations when migrated to multi auth.
242 //const auto testPrefix = (cTestMode() ? qsl("_test") : QString());
243 const auto testPrefix = QString();
244 QFile file(cWorkingDir() + cDataFile() + testPrefix + qsl("_config"));
245 if (file.open(QIODevice::ReadOnly)) {
246 LOG(("App Info: reading old user config..."));
247 qint32 version = 0;
248 _readOldUserSettingsFields(&file, version, context);
249 file.close();
250 result = true;
251 }
252 if (remove) file.remove();
253 return result;
254 }
255
_readOldMtpDataFields(QIODevice * device,qint32 & version,ReadSettingsContext & context)256 void _readOldMtpDataFields(
257 QIODevice *device,
258 qint32 &version,
259 ReadSettingsContext &context) {
260 QDataStream stream(device);
261 stream.setVersion(QDataStream::Qt_5_1);
262
263 while (!stream.atEnd()) {
264 quint32 blockId;
265 stream >> blockId;
266 if (!CheckStreamStatus(stream)) {
267 break;
268 }
269
270 if (blockId == dbiVersion) {
271 stream >> version;
272 if (!CheckStreamStatus(stream)) {
273 break;
274 }
275
276 if (version > AppVersion) return;
277 } else if (blockId == dbiEncrypted) {
278 QByteArray data, decrypted;
279 stream >> data;
280 if (!CheckStreamStatus(stream)) {
281 break;
282 }
283
284 if (!OldKey) {
285 LOG(("MTP Error: reading old encrypted keys without old key!"));
286 continue;
287 }
288
289 if (data.size() <= 16 || (data.size() & 0x0F)) {
290 LOG(("MTP Error: bad encrypted part size in old keys: %1").arg(data.size()));
291 continue;
292 }
293 uint32 fullDataLen = data.size() - 16;
294 decrypted.resize(fullDataLen);
295 const char *dataKey = data.constData(), *encrypted = data.constData() + 16;
296 aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, OldKey, dataKey);
297 uchar sha1Buffer[20];
298 if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) {
299 LOG(("MTP Error: bad decrypt key, data from old keys not decrypted"));
300 continue;
301 }
302 uint32 dataLen = *(const uint32*)decrypted.constData();
303 if (dataLen > uint32(decrypted.size()) || dataLen <= fullDataLen - 16 || dataLen < 4) {
304 LOG(("MTP Error: bad decrypted part size in old keys: %1, fullDataLen: %2, decrypted size: %3").arg(dataLen).arg(fullDataLen).arg(decrypted.size()));
305 continue;
306 }
307 decrypted.resize(dataLen);
308 QBuffer decryptedStream(&decrypted);
309 decryptedStream.open(QIODevice::ReadOnly);
310 decryptedStream.seek(4); // skip size
311 LOG(("App Info: reading encrypted old keys..."));
312
313 _readOldMtpDataFields(&decryptedStream, version, context);
314 } else if (!ReadSetting(blockId, stream, version, context)) {
315 return;
316 }
317 }
318 }
319
_readOldMtpData(bool remove,ReadSettingsContext & context)320 bool _readOldMtpData(bool remove, ReadSettingsContext &context) {
321 bool result = false;
322 // We dropped old test authorizations when migrated to multi auth.
323 //const auto testPostfix = (cTestMode() ? qsl("_test") : QString());
324 const auto testPostfix = QString();
325 QFile file(cWorkingDir() + cDataFile() + testPostfix);
326 if (file.open(QIODevice::ReadOnly)) {
327 LOG(("App Info: reading old keys..."));
328 qint32 version = 0;
329 _readOldMtpDataFields(&file, version, context);
330 file.close();
331 result = true;
332 }
333 if (remove) file.remove();
334 return result;
335 }
336
337 } // namespace
338
sync()339 void sync() {
340 Storage::details::Sync();
341 }
342
finish()343 void finish() {
344 delete base::take(_localLoader);
345 Storage::details::Finish();
346 }
347
348 void InitialLoadTheme();
349 bool ApplyDefaultNightMode();
350 void readLangPack();
351
start()352 void start() {
353 Expects(_basePath.isEmpty());
354
355 _localLoader = new TaskQueue(kFileLoaderQueueStopTimeout);
356
357 _basePath = cWorkingDir() + qsl("tdata/");
358 if (!QDir().exists(_basePath)) QDir().mkpath(_basePath);
359
360 ReadSettingsContext context;
361 FileReadDescriptor settingsData;
362 // We dropped old test authorizations when migrated to multi auth.
363 //const auto name = cTestMode() ? qsl("settings_test") : qsl("settings");
364 const auto name = u"settings"_q;
365 if (!ReadFile(settingsData, name, _basePath)) {
366 _readOldSettings(true, context);
367 _readOldUserSettings(false, context); // needed further in _readUserSettings
368 _readOldMtpData(false, context); // needed further in _readMtpData
369 applyReadContext(std::move(context));
370
371 _settingsRewriteNeeded = true;
372 ApplyDefaultNightMode();
373 return;
374 }
375 LOG(("App Info: reading settings..."));
376
377 QByteArray salt, settingsEncrypted;
378 settingsData.stream >> salt >> settingsEncrypted;
379 if (!CheckStreamStatus(settingsData.stream)) {
380 return writeSettings();
381 }
382
383 if (salt.size() != LocalEncryptSaltSize) {
384 LOG(("App Error: bad salt in settings file, size: %1").arg(salt.size()));
385 return writeSettings();
386 }
387 SettingsKey = CreateLegacyLocalKey(QByteArray(), salt);
388
389 EncryptedDescriptor settings;
390 if (!DecryptLocal(settings, settingsEncrypted, SettingsKey)) {
391 LOG(("App Error: could not decrypt settings from settings file..."));
392 return writeSettings();
393 }
394
395 LOG(("App Info: reading encrypted settings..."));
396 while (!settings.stream.atEnd()) {
397 quint32 blockId;
398 settings.stream >> blockId;
399 if (!CheckStreamStatus(settings.stream)) {
400 return writeSettings();
401 }
402
403 if (!ReadSetting(blockId, settings.stream, settingsData.version, context)) {
404 return writeSettings();
405 }
406 }
407
408 _oldSettingsVersion = settingsData.version;
409 _settingsSalt = salt;
410
411 applyReadContext(std::move(context));
412 if (context.legacyRead) {
413 writeSettings();
414 }
415
416 InitialLoadTheme();
417
418 if (context.tileRead && _useGlobalBackgroundKeys) {
419 Window::Theme::Background()->setTileDayValue(context.tileDay);
420 Window::Theme::Background()->setTileNightValue(context.tileNight);
421 }
422
423 readLangPack();
424 }
425
writeSettings()426 void writeSettings() {
427 if (!_settingsWriteAllowed) {
428 _settingsRewriteNeeded = true;
429
430 // We need to generate SettingsKey anyway,
431 // for the moveLegacyBackground to work.
432 if (SettingsKey) {
433 return;
434 }
435 }
436 if (_basePath.isEmpty()) {
437 LOG(("App Error: _basePath is empty in writeSettings()"));
438 return;
439 }
440
441 if (!QDir().exists(_basePath)) QDir().mkpath(_basePath);
442
443 // We dropped old test authorizations when migrated to multi auth.
444 //const auto name = cTestMode() ? qsl("settings_test") : qsl("settings");
445 const auto name = u"settings"_q;
446 FileWriteDescriptor settings(name, _basePath);
447 if (_settingsSalt.isEmpty() || !SettingsKey) {
448 _settingsSalt.resize(LocalEncryptSaltSize);
449 base::RandomFill(_settingsSalt.data(), _settingsSalt.size());
450 SettingsKey = CreateLegacyLocalKey(QByteArray(), _settingsSalt);
451 }
452 settings.writeData(_settingsSalt);
453
454 if (!_settingsWriteAllowed) {
455 EncryptedDescriptor data(0);
456 settings.writeEncrypted(data, SettingsKey);
457 return;
458 }
459 const auto configSerialized = LookupFallbackConfig().serialize();
460 const auto applicationSettings = Core::App().settings().serialize();
461
462 quint32 size = 9 * (sizeof(quint32) + sizeof(qint32));
463 size += sizeof(quint32) + Serialize::bytearraySize(configSerialized);
464 size += sizeof(quint32) + Serialize::bytearraySize(applicationSettings);
465 size += sizeof(quint32) + Serialize::stringSize(cDialogLastPath());
466
467 // Theme keys and night mode.
468 size += sizeof(quint32) + sizeof(quint64) * 2 + sizeof(quint32);
469 size += sizeof(quint32) + sizeof(quint64) * 2;
470 if (_langPackKey) {
471 size += sizeof(quint32) + sizeof(quint64);
472 }
473 size += sizeof(quint32) + sizeof(qint32) * 8;
474
475 EncryptedDescriptor data(size);
476 data.stream << quint32(dbiAutoStart) << qint32(cAutoStart());
477 data.stream << quint32(dbiStartMinimized) << qint32(cStartMinimized());
478 data.stream << quint32(dbiSendToMenu) << qint32(cSendToMenu());
479 data.stream << quint32(dbiSeenTrayTooltip) << qint32(cSeenTrayTooltip());
480 data.stream << quint32(dbiAutoUpdate) << qint32(cAutoUpdate());
481 data.stream << quint32(dbiLastUpdateCheck) << qint32(cLastUpdateCheck());
482 data.stream << quint32(dbiScalePercent) << qint32(cConfigScale());
483 data.stream << quint32(dbiFallbackProductionConfig) << configSerialized;
484 data.stream << quint32(dbiApplicationSettings) << applicationSettings;
485 data.stream << quint32(dbiDialogLastPath) << cDialogLastPath();
486 data.stream << quint32(dbiAnimationsDisabled) << qint32(anim::Disabled() ? 1 : 0);
487
488 data.stream
489 << quint32(dbiThemeKey)
490 << quint64(_themeKeyDay)
491 << quint64(_themeKeyNight)
492 << quint32(Window::Theme::IsNightMode() ? 1 : 0);
493 if (_useGlobalBackgroundKeys) {
494 data.stream
495 << quint32(dbiBackgroundKey)
496 << quint64(_backgroundKeyDay)
497 << quint64(_backgroundKeyNight);
498 data.stream
499 << quint32(dbiTileBackground)
500 << qint32(Window::Theme::Background()->tileDay() ? 1 : 0)
501 << qint32(Window::Theme::Background()->tileNight() ? 1 : 0);
502 }
503 if (_langPackKey) {
504 data.stream << quint32(dbiLangPackKey) << quint64(_langPackKey);
505 }
506 if (_languagesKey) {
507 data.stream << quint32(dbiLanguagesKey) << quint64(_languagesKey);
508 }
509
510 settings.writeEncrypted(data, SettingsKey);
511 }
512
rewriteSettingsIfNeeded()513 void rewriteSettingsIfNeeded() {
514 if (_settingsWriteAllowed) {
515 return;
516 }
517 _settingsWriteAllowed = true;
518 if (_oldSettingsVersion < AppVersion || _settingsRewriteNeeded) {
519 writeSettings();
520 }
521 }
522
AutoupdatePrefix(const QString & replaceWith={})523 const QString &AutoupdatePrefix(const QString &replaceWith = {}) {
524 Expects(!Core::UpdaterDisabled());
525
526 static auto value = QString();
527 if (!replaceWith.isEmpty()) {
528 value = replaceWith;
529 }
530 return value;
531 }
532
autoupdatePrefixFile()533 QString autoupdatePrefixFile() {
534 Expects(!Core::UpdaterDisabled());
535
536 return cWorkingDir() + "tdata/prefix";
537 }
538
readAutoupdatePrefixRaw()539 const QString &readAutoupdatePrefixRaw() {
540 Expects(!Core::UpdaterDisabled());
541
542 const auto &result = AutoupdatePrefix();
543 if (!result.isEmpty()) {
544 return result;
545 }
546 QFile f(autoupdatePrefixFile());
547 if (f.open(QIODevice::ReadOnly)) {
548 const auto value = QString::fromUtf8(f.readAll());
549 if (!value.isEmpty()) {
550 return AutoupdatePrefix(value);
551 }
552 }
553 return AutoupdatePrefix("https://td.telegram.org");
554 }
555
writeAutoupdatePrefix(const QString & prefix)556 void writeAutoupdatePrefix(const QString &prefix) {
557 if (Core::UpdaterDisabled()) {
558 return;
559 }
560
561 const auto current = readAutoupdatePrefixRaw();
562 if (current != prefix) {
563 AutoupdatePrefix(prefix);
564 QFile f(autoupdatePrefixFile());
565 if (f.open(QIODevice::WriteOnly)) {
566 f.write(prefix.toUtf8());
567 f.close();
568 }
569 if (cAutoUpdate()) {
570 Core::UpdateChecker checker;
571 checker.start();
572 }
573 }
574 }
575
readAutoupdatePrefix()576 QString readAutoupdatePrefix() {
577 Expects(!Core::UpdaterDisabled());
578
579 auto result = readAutoupdatePrefixRaw();
580 return result.replace(QRegularExpression("/+$"), QString());
581 }
582
writeBackground(const Data::WallPaper & paper,const QImage & image)583 void writeBackground(const Data::WallPaper &paper, const QImage &image) {
584 Expects(_settingsWriteAllowed);
585
586 if (!_backgroundCanWrite) {
587 return;
588 }
589
590 _useGlobalBackgroundKeys = true;
591 auto &backgroundKey = Window::Theme::IsNightMode()
592 ? _backgroundKeyNight
593 : _backgroundKeyDay;
594 auto imageData = QByteArray();
595 if (!image.isNull()) {
596 const auto width = qint32(image.width());
597 const auto height = qint32(image.height());
598 const auto perpixel = (image.depth() >> 3);
599 const auto srcperline = image.bytesPerLine();
600 const auto srcsize = srcperline * height;
601 const auto dstperline = width * perpixel;
602 const auto dstsize = dstperline * height;
603 const auto copy = (image.format() != kSavedBackgroundFormat)
604 ? image.convertToFormat(kSavedBackgroundFormat)
605 : image;
606 imageData.resize(2 * sizeof(qint32) + dstsize);
607
608 auto dst = bytes::make_detached_span(imageData);
609 bytes::copy(dst, bytes::object_as_span(&width));
610 dst = dst.subspan(sizeof(qint32));
611 bytes::copy(dst, bytes::object_as_span(&height));
612 dst = dst.subspan(sizeof(qint32));
613 const auto src = bytes::make_span(image.constBits(), srcsize);
614 if (srcsize == dstsize) {
615 bytes::copy(dst, src);
616 } else {
617 for (auto y = 0; y != height; ++y) {
618 bytes::copy(dst, src.subspan(y * srcperline, dstperline));
619 dst = dst.subspan(dstperline);
620 }
621 }
622 }
623 if (!backgroundKey) {
624 backgroundKey = GenerateKey(_basePath);
625 writeSettings();
626 }
627 const auto serialized = paper.serialize();
628 quint32 size = sizeof(qint32)
629 + Serialize::bytearraySize(serialized)
630 + Serialize::bytearraySize(imageData);
631 EncryptedDescriptor data(size);
632 data.stream
633 << qint32(kWallPaperSerializeTagId)
634 << serialized
635 << imageData;
636
637 FileWriteDescriptor file(backgroundKey, _basePath);
638 file.writeEncrypted(data, SettingsKey);
639 }
640
readBackground()641 bool readBackground() {
642 FileReadDescriptor bg;
643 auto &backgroundKey = Window::Theme::IsNightMode()
644 ? _backgroundKeyNight
645 : _backgroundKeyDay;
646 if (!ReadEncryptedFile(bg, backgroundKey, _basePath, SettingsKey)) {
647 if (backgroundKey) {
648 ClearKey(backgroundKey, _basePath);
649 backgroundKey = 0;
650 writeSettings();
651 }
652 return false;
653 }
654
655 qint32 legacyId = 0;
656 bg.stream >> legacyId;
657 const auto paper = [&] {
658 if (legacyId == kWallPaperLegacySerializeTagId) {
659 quint64 id = 0;
660 quint64 accessHash = 0;
661 quint32 flags = 0;
662 QString slug;
663 bg.stream
664 >> id
665 >> accessHash
666 >> flags
667 >> slug;
668 return Data::WallPaper::FromLegacySerialized(
669 id,
670 accessHash,
671 flags,
672 slug);
673 } else if (legacyId == kWallPaperSerializeTagId) {
674 QByteArray serialized;
675 bg.stream >> serialized;
676 return Data::WallPaper::FromSerialized(serialized);
677 } else {
678 return Data::WallPaper::FromLegacyId(legacyId);
679 }
680 }();
681 if (bg.stream.status() != QDataStream::Ok || !paper) {
682 return false;
683 }
684
685 QByteArray imageData;
686 bg.stream >> imageData;
687 const auto isOldEmptyImage = (bg.stream.status() != QDataStream::Ok);
688 if (isOldEmptyImage
689 || Data::IsLegacy1DefaultWallPaper(*paper)
690 || (Data::IsLegacy2DefaultWallPaper(*paper) && bg.version < 3000000)
691 || (Data::IsLegacy3DefaultWallPaper(*paper) && bg.version < 3000000)
692 || (Data::IsLegacy4DefaultWallPaper(*paper) && bg.version < 3000000)
693 || Data::IsDefaultWallPaper(*paper)) {
694 _backgroundCanWrite = false;
695 if (isOldEmptyImage || bg.version < 3000000) {
696 Window::Theme::Background()->set(Data::DefaultWallPaper());
697 } else {
698 Window::Theme::Background()->set(*paper);
699 }
700 _backgroundCanWrite = true;
701 return true;
702 } else if (Data::IsThemeWallPaper(*paper) && imageData.isEmpty()) {
703 _backgroundCanWrite = false;
704 Window::Theme::Background()->set(*paper);
705 _backgroundCanWrite = true;
706 return true;
707 }
708 auto image = QImage();
709 if (legacyId == kWallPaperSerializeTagId) {
710 const auto perpixel = 4;
711 auto src = bytes::make_span(imageData);
712 auto width = qint32();
713 auto height = qint32();
714 if (src.size() > 2 * sizeof(qint32)) {
715 bytes::copy(
716 bytes::object_as_span(&width),
717 src.subspan(0, sizeof(qint32)));
718 src = src.subspan(sizeof(qint32));
719 bytes::copy(
720 bytes::object_as_span(&height),
721 src.subspan(0, sizeof(qint32)));
722 src = src.subspan(sizeof(qint32));
723 if (width + height <= kWallPaperSidesLimit
724 && src.size() == width * height * perpixel) {
725 image = QImage(
726 width,
727 height,
728 QImage::Format_ARGB32_Premultiplied);
729 if (!image.isNull()) {
730 const auto srcperline = width * perpixel;
731 const auto srcsize = srcperline * height;
732 const auto dstperline = image.bytesPerLine();
733 const auto dstsize = dstperline * height;
734 Assert(srcsize == dstsize);
735 bytes::copy(
736 bytes::make_span(image.bits(), dstsize),
737 src);
738 }
739 }
740 }
741 } else {
742 auto buffer = QBuffer(&imageData);
743 auto reader = QImageReader(&buffer);
744 reader.setAutoTransform(true);
745 if (!reader.read(&image)) {
746 image = QImage();
747 }
748 }
749 if (!image.isNull() || !paper->backgroundColors().empty()) {
750 _backgroundCanWrite = false;
751 Window::Theme::Background()->set(*paper, std::move(image));
752 _backgroundCanWrite = true;
753 return true;
754 }
755 return false;
756 }
757
moveLegacyBackground(const QString & fromBasePath,const MTP::AuthKeyPtr & fromLocalKey,uint64 legacyBackgroundKeyDay,uint64 legacyBackgroundKeyNight)758 void moveLegacyBackground(
759 const QString &fromBasePath,
760 const MTP::AuthKeyPtr &fromLocalKey,
761 uint64 legacyBackgroundKeyDay,
762 uint64 legacyBackgroundKeyNight) {
763 if (_useGlobalBackgroundKeys
764 || (!legacyBackgroundKeyDay && !legacyBackgroundKeyNight)) {
765 return;
766 }
767 const auto move = [&](uint64 from, FileKey &to) {
768 if (!from || to) {
769 return;
770 }
771 to = GenerateKey(_basePath);
772 FileReadDescriptor read;
773 if (!ReadEncryptedFile(read, from, fromBasePath, fromLocalKey)) {
774 return;
775 }
776 EncryptedDescriptor data;
777 data.data = read.data;
778 FileWriteDescriptor write(to, _basePath);
779 write.writeEncrypted(data, SettingsKey);
780 };
781 move(legacyBackgroundKeyDay, _backgroundKeyDay);
782 move(legacyBackgroundKeyNight, _backgroundKeyNight);
783 _useGlobalBackgroundKeys = true;
784 _settingsRewriteNeeded = true;
785 }
786
reset()787 void reset() {
788 if (_localLoader) {
789 _localLoader->stop();
790 }
791
792 Window::Theme::Background()->reset();
793 _oldSettingsVersion = 0;
794 Core::App().settings().resetOnLastLogout();
795 writeSettings();
796 }
797
oldSettingsVersion()798 int32 oldSettingsVersion() {
799 return _oldSettingsVersion;
800 }
801
802 class CountWaveformTask : public Task {
803 public:
CountWaveformTask(not_null<Data::DocumentMedia * > media)804 CountWaveformTask(not_null<Data::DocumentMedia*> media)
805 : _doc(media->owner())
806 , _loc(_doc->location(true))
807 , _data(media->bytes())
808 , _wavemax(0) {
809 if (_data.isEmpty() && !_loc.accessEnable()) {
810 _doc = nullptr;
811 }
812 }
process()813 void process() override {
814 if (!_doc) return;
815
816 _waveform = audioCountWaveform(_loc, _data);
817 _wavemax = _waveform.empty()
818 ? char(0)
819 : *ranges::max_element(_waveform);
820 }
finish()821 void finish() override {
822 if (const auto voice = _doc ? _doc->voice() : nullptr) {
823 if (!_waveform.isEmpty()) {
824 voice->waveform = _waveform;
825 voice->wavemax = _wavemax;
826 }
827 if (voice->waveform.isEmpty()) {
828 voice->waveform.resize(1);
829 voice->waveform[0] = -2;
830 voice->wavemax = 0;
831 } else if (voice->waveform[0] < 0) {
832 voice->waveform[0] = -2;
833 voice->wavemax = 0;
834 }
835 _doc->owner().requestDocumentViewRepaint(_doc);
836 }
837 }
~CountWaveformTask()838 ~CountWaveformTask() {
839 if (_data.isEmpty() && _doc) {
840 _loc.accessDisable();
841 }
842 }
843
844 protected:
845 DocumentData *_doc = nullptr;
846 Core::FileLocation _loc;
847 QByteArray _data;
848 VoiceWaveform _waveform;
849 char _wavemax;
850
851 };
852
countVoiceWaveform(not_null<Data::DocumentMedia * > media)853 void countVoiceWaveform(not_null<Data::DocumentMedia*> media) {
854 const auto document = media->owner();
855 if (const auto voice = document->voice()) {
856 if (_localLoader) {
857 voice->waveform.resize(1 + sizeof(TaskId));
858 voice->waveform[0] = -1; // counting
859 TaskId taskId = _localLoader->addTask(
860 std::make_unique<CountWaveformTask>(media));
861 memcpy(voice->waveform.data() + 1, &taskId, sizeof(taskId));
862 }
863 }
864 }
865
cancelTask(TaskId id)866 void cancelTask(TaskId id) {
867 if (_localLoader) {
868 _localLoader->cancelTask(id);
869 }
870 }
871
readThemeUsingKey(FileKey key)872 Window::Theme::Saved readThemeUsingKey(FileKey key) {
873 using namespace Window::Theme;
874
875 FileReadDescriptor theme;
876 if (!ReadEncryptedFile(theme, key, _basePath, SettingsKey)) {
877 DEBUG_LOG(("Theme: Could not read file for key: %1").arg(key));
878 return {};
879 }
880
881 auto tag = QString();
882 auto result = Saved();
883 auto &object = result.object;
884 auto &cache = result.cache;
885 auto field1 = qint32();
886 auto field2 = quint32();
887 theme.stream >> object.content;
888 theme.stream >> tag >> object.pathAbsolute;
889 if (tag == kThemeNewPathRelativeTag) {
890 theme.stream
891 >> object.pathRelative
892 >> object.cloud.id
893 >> object.cloud.accessHash
894 >> object.cloud.slug
895 >> object.cloud.title
896 >> object.cloud.documentId
897 >> field1;
898 } else {
899 object.pathRelative = tag;
900 }
901 if (theme.stream.status() != QDataStream::Ok) {
902 DEBUG_LOG(("Theme: Bad status for key: %1, tag: %2"
903 ).arg(key
904 ).arg(tag));
905 return {};
906 }
907
908 auto ignoreCache = false;
909 if (!object.cloud.id) {
910 auto file = QFile(object.pathRelative);
911 if (object.pathRelative.isEmpty() || !file.exists()) {
912 file.setFileName(object.pathAbsolute);
913 }
914 if (!file.fileName().isEmpty()
915 && file.exists()
916 && file.open(QIODevice::ReadOnly)) {
917 if (file.size() > kThemeFileSizeLimit) {
918 LOG(("Error: theme file too large: %1 "
919 "(should be less than 5 MB, got %2)"
920 ).arg(file.fileName()
921 ).arg(file.size()));
922 return {};
923 }
924 auto fileContent = file.readAll();
925 file.close();
926 if (object.content != fileContent) {
927 object.content = fileContent;
928 ignoreCache = true;
929 }
930 }
931 }
932 int32 cachePaletteChecksum = 0;
933 int32 cacheContentChecksum = 0;
934 QByteArray cacheColors;
935 QByteArray cacheBackground;
936 theme.stream
937 >> cachePaletteChecksum
938 >> cacheContentChecksum
939 >> cacheColors
940 >> cacheBackground
941 >> field2;
942 if (!ignoreCache) {
943 if (theme.stream.status() != QDataStream::Ok) {
944 DEBUG_LOG(("Theme: Bad status for cache, key: %1, tag: %2"
945 ).arg(key
946 ).arg(tag));
947 return {};
948 }
949 cache.paletteChecksum = cachePaletteChecksum;
950 cache.contentChecksum = cacheContentChecksum;
951 cache.colors = std::move(cacheColors);
952 cache.background = std::move(cacheBackground);
953 cache.tiled = ((field2 & quint32(0xFF)) == 1);
954 }
955 if (tag == kThemeNewPathRelativeTag) {
956 object.cloud.createdBy = UserId(
957 ((quint64(field2) >> 8) << 32) | quint64(quint32(field1)));
958 }
959 return result;
960 }
961
InitialLoadThemeUsingKey(FileKey key)962 std::optional<QString> InitialLoadThemeUsingKey(FileKey key) {
963 auto read = readThemeUsingKey(key);
964 const auto result = read.object.pathAbsolute;
965 if (read.object.content.isEmpty()) {
966 DEBUG_LOG(("Theme: Could not read content for key: %1").arg(key));
967 }
968 if (read.object.content.isEmpty()
969 || !Window::Theme::Initialize(std::move(read))) {
970 DEBUG_LOG(("Theme: Could not initialized for key: %1").arg(key));
971 return std::nullopt;
972 }
973 return result;
974 }
975
writeTheme(const Window::Theme::Saved & saved)976 void writeTheme(const Window::Theme::Saved &saved) {
977 using namespace Window::Theme;
978
979 if (_themeKeyLegacy) {
980 DEBUG_LOG(("Theme: skipping write, because legacy: %1"
981 ).arg(_themeKeyLegacy));
982 return;
983 }
984 auto &themeKey = IsNightMode()
985 ? _themeKeyNight
986 : _themeKeyDay;
987 DEBUG_LOG(("Theme: writing (night: %1), key_day: %2, key_night: %3"
988 ).arg(Logs::b(IsNightMode())
989 ).arg(_themeKeyDay
990 ).arg(_themeKeyNight));
991 if (saved.object.content.isEmpty()) {
992 if (themeKey) {
993 if (IsNightMode()) {
994 DEBUG_LOG(("Theme: cleared for night mode."));
995 SetNightModeValue(false);
996 }
997 ClearKey(themeKey, _basePath);
998 themeKey = 0;
999 writeSettings();
1000 }
1001 return;
1002 }
1003
1004 if (!themeKey) {
1005 themeKey = GenerateKey(_basePath);
1006 writeSettings();
1007 }
1008
1009 const auto &object = saved.object;
1010 const auto &cache = saved.cache;
1011 const auto tag = QString(kThemeNewPathRelativeTag);
1012 quint32 size = Serialize::bytearraySize(object.content)
1013 + Serialize::stringSize(tag)
1014 + Serialize::stringSize(object.pathAbsolute)
1015 + Serialize::stringSize(object.pathRelative)
1016 + sizeof(uint64) * 3
1017 + Serialize::stringSize(object.cloud.slug)
1018 + Serialize::stringSize(object.cloud.title)
1019 + sizeof(qint32)
1020 + sizeof(qint32) * 2
1021 + Serialize::bytearraySize(cache.colors)
1022 + Serialize::bytearraySize(cache.background)
1023 + sizeof(quint32);
1024 const auto bareCreatedById = object.cloud.createdBy.bare;
1025 Assert((bareCreatedById & PeerId::kChatTypeMask) == bareCreatedById);
1026 const auto field1 = qint32(quint32(bareCreatedById & 0xFFFFFFFFULL));
1027 const auto field2 = quint32(cache.tiled ? 1 : 0)
1028 | (quint32(bareCreatedById >> 32) << 8);
1029 EncryptedDescriptor data(size);
1030 data.stream
1031 << object.content
1032 << tag
1033 << object.pathAbsolute
1034 << object.pathRelative
1035 << object.cloud.id
1036 << object.cloud.accessHash
1037 << object.cloud.slug
1038 << object.cloud.title
1039 << object.cloud.documentId
1040 << field1
1041 << cache.paletteChecksum
1042 << cache.contentChecksum
1043 << cache.colors
1044 << cache.background
1045 << field2;
1046
1047 FileWriteDescriptor file(themeKey, _basePath);
1048 file.writeEncrypted(data, SettingsKey);
1049 }
1050
clearTheme()1051 void clearTheme() {
1052 writeTheme(Window::Theme::Saved());
1053 }
1054
InitialLoadTheme()1055 void InitialLoadTheme() {
1056 const auto key = (_themeKeyLegacy != 0)
1057 ? _themeKeyLegacy
1058 : (Window::Theme::IsNightMode()
1059 ? _themeKeyNight
1060 : _themeKeyDay);
1061 DEBUG_LOG(("Theme: initial load (night: %1), "
1062 "key_legacy: %2, key_day: %3, key_night: %4"
1063 ).arg(Logs::b(Window::Theme::IsNightMode())
1064 ).arg(_themeKeyLegacy
1065 ).arg(_themeKeyDay
1066 ).arg(_themeKeyNight));
1067 if (!key) {
1068 if (Window::Theme::IsNightMode()) {
1069 DEBUG_LOG(("Theme: zero key for night mode."));
1070 Window::Theme::SetNightModeValue(false);
1071 }
1072 return;
1073 } else if (const auto path = InitialLoadThemeUsingKey(key)) {
1074 DEBUG_LOG(("Theme: loaded with result: %1").arg(*path));
1075 if (_themeKeyLegacy) {
1076 Window::Theme::SetNightModeValue(*path
1077 == Window::Theme::NightThemePath());
1078 (Window::Theme::IsNightMode()
1079 ? _themeKeyNight
1080 : _themeKeyDay) = base::take(_themeKeyLegacy);
1081 DEBUG_LOG(("Theme: now (night: %1), "
1082 "key_legacy: %2, key_day: %3, key_night: %4 (path: %5)"
1083 ).arg(Logs::b(Window::Theme::IsNightMode())
1084 ).arg(_themeKeyLegacy
1085 ).arg(_themeKeyDay
1086 ).arg(_themeKeyNight
1087 ).arg(*path));
1088 }
1089 } else {
1090 DEBUG_LOG(("Theme: could not load, clearing.."));
1091 clearTheme();
1092 }
1093 }
1094
ApplyDefaultNightMode()1095 bool ApplyDefaultNightMode() {
1096 const auto NightByDefault = Platform::IsMacStoreBuild();
1097 if (!NightByDefault
1098 || Window::Theme::IsNightMode()
1099 || _themeKeyDay
1100 || _themeKeyNight
1101 || _themeKeyLegacy) {
1102 return false;
1103 }
1104 Core::App().startSettingsAndBackground();
1105 Window::Theme::ToggleNightMode();
1106 Window::Theme::KeepApplied();
1107 return true;
1108 }
1109
readThemeAfterSwitch()1110 Window::Theme::Saved readThemeAfterSwitch() {
1111 const auto key = Window::Theme::IsNightMode()
1112 ? _themeKeyNight
1113 : _themeKeyDay;
1114 return readThemeUsingKey(key);
1115 }
1116
readLangPack()1117 void readLangPack() {
1118 FileReadDescriptor langpack;
1119 if (!_langPackKey || !ReadEncryptedFile(langpack, _langPackKey, _basePath, SettingsKey)) {
1120 return;
1121 }
1122 auto data = QByteArray();
1123 langpack.stream >> data;
1124 if (langpack.stream.status() == QDataStream::Ok) {
1125 Lang::GetInstance().fillFromSerialized(data, langpack.version);
1126 }
1127 }
1128
writeLangPack()1129 void writeLangPack() {
1130 auto langpack = Lang::GetInstance().serialize();
1131 if (!_langPackKey) {
1132 _langPackKey = GenerateKey(_basePath);
1133 writeSettings();
1134 }
1135
1136 EncryptedDescriptor data(Serialize::bytearraySize(langpack));
1137 data.stream << langpack;
1138
1139 FileWriteDescriptor file(_langPackKey, _basePath);
1140 file.writeEncrypted(data, SettingsKey);
1141 }
1142
saveRecentLanguages(const std::vector<Lang::Language> & list)1143 void saveRecentLanguages(const std::vector<Lang::Language> &list) {
1144 if (list.empty()) {
1145 if (_languagesKey) {
1146 ClearKey(_languagesKey, _basePath);
1147 _languagesKey = 0;
1148 writeSettings();
1149 }
1150 return;
1151 }
1152
1153 auto size = sizeof(qint32);
1154 for (const auto &language : list) {
1155 size += Serialize::stringSize(language.id)
1156 + Serialize::stringSize(language.pluralId)
1157 + Serialize::stringSize(language.baseId)
1158 + Serialize::stringSize(language.name)
1159 + Serialize::stringSize(language.nativeName);
1160 }
1161 if (!_languagesKey) {
1162 _languagesKey = GenerateKey(_basePath);
1163 writeSettings();
1164 }
1165
1166 EncryptedDescriptor data(size);
1167 data.stream << qint32(list.size());
1168 for (const auto &language : list) {
1169 data.stream
1170 << language.id
1171 << language.pluralId
1172 << language.baseId
1173 << language.name
1174 << language.nativeName;
1175 }
1176
1177 FileWriteDescriptor file(_languagesKey, _basePath);
1178 file.writeEncrypted(data, SettingsKey);
1179 }
1180
pushRecentLanguage(const Lang::Language & language)1181 void pushRecentLanguage(const Lang::Language &language) {
1182 if (language.id.startsWith('#')) {
1183 return;
1184 }
1185 auto list = readRecentLanguages();
1186 list.erase(
1187 ranges::remove_if(
1188 list,
1189 [&](const Lang::Language &v) { return (v.id == language.id); }),
1190 end(list));
1191 list.insert(list.begin(), language);
1192
1193 saveRecentLanguages(list);
1194 }
1195
removeRecentLanguage(const QString & id)1196 void removeRecentLanguage(const QString &id) {
1197 auto list = readRecentLanguages();
1198 list.erase(
1199 ranges::remove_if(
1200 list,
1201 [&](const Lang::Language &v) { return (v.id == id); }),
1202 end(list));
1203
1204 saveRecentLanguages(list);
1205 }
1206
readRecentLanguages()1207 std::vector<Lang::Language> readRecentLanguages() {
1208 FileReadDescriptor languages;
1209 if (!_languagesKey || !ReadEncryptedFile(languages, _languagesKey, _basePath, SettingsKey)) {
1210 return {};
1211 }
1212 qint32 count = 0;
1213 languages.stream >> count;
1214 if (count <= 0) {
1215 return {};
1216 }
1217 auto result = std::vector<Lang::Language>();
1218 result.reserve(count);
1219 for (auto i = 0; i != count; ++i) {
1220 auto language = Lang::Language();
1221 languages.stream
1222 >> language.id
1223 >> language.pluralId
1224 >> language.baseId
1225 >> language.name
1226 >> language.nativeName;
1227 result.push_back(language);
1228 }
1229 if (languages.stream.status() != QDataStream::Ok) {
1230 return {};
1231 }
1232 return result;
1233 }
1234
ReadThemeContent()1235 Window::Theme::Object ReadThemeContent() {
1236 using namespace Window::Theme;
1237
1238 auto &themeKey = IsNightMode() ? _themeKeyNight : _themeKeyDay;
1239 if (!themeKey) {
1240 return Object();
1241 }
1242
1243 FileReadDescriptor theme;
1244 if (!ReadEncryptedFile(theme, themeKey, _basePath, SettingsKey)) {
1245 return Object();
1246 }
1247
1248 QByteArray content;
1249 QString pathRelative, pathAbsolute;
1250 theme.stream >> content >> pathRelative >> pathAbsolute;
1251 if (theme.stream.status() != QDataStream::Ok) {
1252 return Object();
1253 }
1254
1255 auto result = Object();
1256 result.pathAbsolute = pathAbsolute;
1257 result.content = content;
1258 return result;
1259 }
1260
incrementRecentHashtag(RecentHashtagPack & recent,const QString & tag)1261 void incrementRecentHashtag(RecentHashtagPack &recent, const QString &tag) {
1262 auto i = recent.begin(), e = recent.end();
1263 for (; i != e; ++i) {
1264 if (i->first == tag) {
1265 ++i->second;
1266 if (qAbs(i->second) > 0x4000) {
1267 for (auto j = recent.begin(); j != e; ++j) {
1268 if (j->second > 1) {
1269 j->second /= 2;
1270 } else if (j->second > 0) {
1271 j->second = 1;
1272 }
1273 }
1274 }
1275 for (; i != recent.begin(); --i) {
1276 if (qAbs((i - 1)->second) > qAbs(i->second)) {
1277 break;
1278 }
1279 qSwap(*i, *(i - 1));
1280 }
1281 break;
1282 }
1283 }
1284 if (i == e) {
1285 while (recent.size() >= 64) recent.pop_back();
1286 recent.push_back(qMakePair(tag, 1));
1287 for (i = recent.end() - 1; i != recent.begin(); --i) {
1288 if ((i - 1)->second > i->second) {
1289 break;
1290 }
1291 qSwap(*i, *(i - 1));
1292 }
1293 }
1294 }
1295
readOldMtpData(bool remove,ReadSettingsContext & context)1296 bool readOldMtpData(bool remove, ReadSettingsContext &context) {
1297 return _readOldMtpData(remove, context);
1298 }
1299
readOldUserSettings(bool remove,ReadSettingsContext & context)1300 bool readOldUserSettings(bool remove, ReadSettingsContext &context) {
1301 return _readOldUserSettings(remove, context);
1302 }
1303
1304 } // namespace Local
1305