/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "storage/storage_account.h" #include "storage/localstorage.h" #include "storage/storage_domain.h" #include "storage/storage_encryption.h" #include "storage/storage_clear_legacy.h" #include "storage/cache/storage_cache_types.h" #include "storage/details/storage_file_utilities.h" #include "storage/details/storage_settings_scheme.h" #include "storage/serialize_common.h" #include "storage/serialize_peer.h" #include "storage/serialize_document.h" #include "main/main_account.h" #include "main/main_session.h" #include "mtproto/mtproto_config.h" #include "mtproto/mtproto_dc_options.h" #include "mtproto/mtp_instance.h" #include "history/history.h" #include "core/application.h" #include "data/stickers/data_stickers.h" #include "data/data_session.h" #include "data/data_document.h" #include "data/data_user.h" #include "data/data_drafts.h" #include "export/export_settings.h" #include "window/themes/window_theme.h" namespace Storage { namespace { using namespace details; using Database = Cache::Database; constexpr auto kDelayedWriteTimeout = crl::time(1000); constexpr auto kStickersVersionTag = quint32(-1); constexpr auto kStickersSerializeVersion = 1; constexpr auto kMaxSavedStickerSetsCount = 1000; constexpr auto kDefaultStickerInstallDate = TimeId(1); constexpr auto kSinglePeerTypeUser = qint32(1); constexpr auto kSinglePeerTypeChat = qint32(2); constexpr auto kSinglePeerTypeChannel = qint32(3); constexpr auto kSinglePeerTypeSelf = qint32(4); constexpr auto kSinglePeerTypeEmpty = qint32(0); enum { // Local Storage Keys lskUserMap = 0x00, lskDraft = 0x01, // data: PeerId peer lskDraftPosition = 0x02, // data: PeerId peer lskLegacyImages = 0x03, // legacy lskLocations = 0x04, // no data lskLegacyStickerImages = 0x05, // legacy lskLegacyAudios = 0x06, // legacy lskRecentStickersOld = 0x07, // no data lskBackgroundOldOld = 0x08, // no data lskUserSettings = 0x09, // no data lskRecentHashtagsAndBots = 0x0a, // no data lskStickersOld = 0x0b, // no data lskSavedPeersOld = 0x0c, // no data lskReportSpamStatusesOld = 0x0d, // no data lskSavedGifsOld = 0x0e, // no data lskSavedGifs = 0x0f, // no data lskStickersKeys = 0x10, // no data lskTrustedBots = 0x11, // no data lskFavedStickers = 0x12, // no data lskExportSettings = 0x13, // no data lskBackgroundOld = 0x14, // no data lskSelfSerialized = 0x15, // serialized self }; [[nodiscard]] FileKey ComputeDataNameKey(const QString &dataName) { // We dropped old test authorizations when migrated to multi auth. //const auto testAddition = (cTestMode() ? qsl(":/test/") : QString()); const auto testAddition = QString(); const auto dataNameUtf8 = (dataName + testAddition).toUtf8(); FileKey dataNameHash[2] = { 0 }; hashMd5(dataNameUtf8.constData(), dataNameUtf8.size(), dataNameHash); return dataNameHash[0]; } [[nodiscard]] QString BaseGlobalPath() { return cWorkingDir() + qsl("tdata/"); } [[nodiscard]] QString ComputeDatabasePath(const QString &dataName) { return BaseGlobalPath() + "user_" + dataName // We dropped old test authorizations when migrated to multi auth. //+ (cTestMode() ? "[test]" : "") + '/'; } [[nodiscard]] QString LegacyTempDirectory() { return cWorkingDir() + qsl("tdata/tdld/"); } } // namespace Account::Account(not_null owner, const QString &dataName) : _owner(owner) , _dataName(dataName) , _dataNameKey(ComputeDataNameKey(dataName)) , _basePath(BaseGlobalPath() + ToFilePart(_dataNameKey) + QChar('/')) , _tempPath(BaseGlobalPath() + "temp_" + _dataName + QChar('/')) , _databasePath(ComputeDatabasePath(dataName)) , _cacheTotalSizeLimit(Database::Settings().totalSizeLimit) , _cacheBigFileTotalSizeLimit(Database::Settings().totalSizeLimit) , _cacheTotalTimeLimit(Database::Settings().totalTimeLimit) , _cacheBigFileTotalTimeLimit(Database::Settings().totalTimeLimit) , _writeMapTimer([=] { writeMap(); }) , _writeLocationsTimer([=] { writeLocations(); }) { } Account::~Account() { if (_localKey && _mapChanged) { writeMap(); } } QString Account::tempDirectory() const { return _tempPath; } StartResult Account::legacyStart(const QByteArray &passcode) { const auto result = readMapWith(MTP::AuthKeyPtr(), passcode); if (result == ReadMapResult::Failed) { Assert(_localKey == nullptr); } else if (result == ReadMapResult::IncorrectPasscode) { return StartResult::IncorrectPasscodeLegacy; } clearLegacyFiles(); return StartResult::Success; } std::unique_ptr Account::start(MTP::AuthKeyPtr localKey) { Expects(localKey != nullptr); _localKey = std::move(localKey); readMapWith(_localKey); clearLegacyFiles(); return readMtpConfig(); } void Account::startAdded(MTP::AuthKeyPtr localKey) { Expects(localKey != nullptr); _localKey = std::move(localKey); clearLegacyFiles(); } void Account::clearLegacyFiles() { const auto weak = base::make_weak(_owner.get()); ClearLegacyFiles(_basePath, [weak, this]( FnMut&&)> then) { crl::on_main(weak, [this, then = std::move(then)]() mutable { then(collectGoodNames()); }); }); } base::flat_set Account::collectGoodNames() const { const auto keys = { _locationsKey, _settingsKey, _installedStickersKey, _featuredStickersKey, _recentStickersKey, _favedStickersKey, _archivedStickersKey, _recentStickersKeyOld, _savedGifsKey, _legacyBackgroundKeyNight, _legacyBackgroundKeyDay, _recentHashtagsAndBotsKey, _exportSettingsKey, _trustedBotsKey, }; auto result = base::flat_set{ "map0", "map1", "maps", "configs", }; const auto push = [&](FileKey key) { if (!key) { return; } auto name = ToFilePart(key) + '0'; result.emplace(name); name[name.size() - 1] = '1'; result.emplace(name); name[name.size() - 1] = 's'; result.emplace(name); }; for (const auto &[key, value] : _draftsMap) { push(value); } for (const auto &[key, value] : _draftCursorsMap) { push(value); } for (const auto &value : keys) { push(value); } return result; } Account::ReadMapResult Account::readMapWith( MTP::AuthKeyPtr localKey, const QByteArray &legacyPasscode) { auto ms = crl::now(); FileReadDescriptor mapData; if (!ReadFile(mapData, qsl("map"), _basePath)) { return ReadMapResult::Failed; } LOG(("App Info: reading map...")); QByteArray legacySalt, legacyKeyEncrypted, mapEncrypted; mapData.stream >> legacySalt >> legacyKeyEncrypted >> mapEncrypted; if (!CheckStreamStatus(mapData.stream)) { return ReadMapResult::Failed; } if (!localKey) { if (legacySalt.size() != LocalEncryptSaltSize) { LOG(("App Error: bad salt in map file, size: %1").arg(legacySalt.size())); return ReadMapResult::Failed; } auto legacyPasscodeKey = CreateLegacyLocalKey(legacyPasscode, legacySalt); EncryptedDescriptor keyData; if (!DecryptLocal(keyData, legacyKeyEncrypted, legacyPasscodeKey)) { LOG(("App Info: could not decrypt pass-protected key from map file, maybe bad password...")); return ReadMapResult::IncorrectPasscode; } auto key = Serialize::read(keyData.stream); if (keyData.stream.status() != QDataStream::Ok || !keyData.stream.atEnd()) { LOG(("App Error: could not read pass-protected key from map file")); return ReadMapResult::Failed; } localKey = std::make_shared(key); } EncryptedDescriptor map; if (!DecryptLocal(map, mapEncrypted, localKey)) { LOG(("App Error: could not decrypt map.")); return ReadMapResult::Failed; } LOG(("App Info: reading encrypted map...")); QByteArray selfSerialized; base::flat_map draftsMap; base::flat_map draftCursorsMap; base::flat_map draftsNotReadMap; quint64 locationsKey = 0, reportSpamStatusesKey = 0, trustedBotsKey = 0; quint64 recentStickersKeyOld = 0; quint64 installedStickersKey = 0, featuredStickersKey = 0, recentStickersKey = 0, favedStickersKey = 0, archivedStickersKey = 0; quint64 savedGifsKey = 0; quint64 legacyBackgroundKeyDay = 0, legacyBackgroundKeyNight = 0; quint64 userSettingsKey = 0, recentHashtagsAndBotsKey = 0, exportSettingsKey = 0; while (!map.stream.atEnd()) { quint32 keyType; map.stream >> keyType; switch (keyType) { case lskDraft: { quint32 count = 0; map.stream >> count; for (quint32 i = 0; i < count; ++i) { FileKey key; quint64 p; map.stream >> key >> p; draftsMap.emplace(p, key); draftsNotReadMap.emplace(p, true); } } break; case lskSelfSerialized: { map.stream >> selfSerialized; } break; case lskDraftPosition: { quint32 count = 0; map.stream >> count; for (quint32 i = 0; i < count; ++i) { FileKey key; quint64 p; map.stream >> key >> p; draftCursorsMap.emplace(p, key); } } break; case lskLegacyImages: case lskLegacyStickerImages: case lskLegacyAudios: { quint32 count = 0; map.stream >> count; for (quint32 i = 0; i < count; ++i) { FileKey key; quint64 first, second; qint32 size; map.stream >> key >> first >> second >> size; // Just ignore the key, it will be removed as a leaked one. } } break; case lskLocations: { map.stream >> locationsKey; } break; case lskReportSpamStatusesOld: { map.stream >> reportSpamStatusesKey; ClearKey(reportSpamStatusesKey, _basePath); } break; case lskTrustedBots: { map.stream >> trustedBotsKey; } break; case lskRecentStickersOld: { map.stream >> recentStickersKeyOld; } break; case lskBackgroundOldOld: { map.stream >> (Window::Theme::IsNightMode() ? legacyBackgroundKeyNight : legacyBackgroundKeyDay); } break; case lskBackgroundOld: { map.stream >> legacyBackgroundKeyDay >> legacyBackgroundKeyNight; } break; case lskUserSettings: { map.stream >> userSettingsKey; } break; case lskRecentHashtagsAndBots: { map.stream >> recentHashtagsAndBotsKey; } break; case lskStickersOld: { map.stream >> installedStickersKey; } break; case lskStickersKeys: { map.stream >> installedStickersKey >> featuredStickersKey >> recentStickersKey >> archivedStickersKey; } break; case lskFavedStickers: { map.stream >> favedStickersKey; } break; case lskSavedGifsOld: { quint64 key; map.stream >> key; } break; case lskSavedGifs: { map.stream >> savedGifsKey; } break; case lskSavedPeersOld: { quint64 key; map.stream >> key; } break; case lskExportSettings: { map.stream >> exportSettingsKey; } break; default: LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType)); return ReadMapResult::Failed; } if (!CheckStreamStatus(map.stream)) { return ReadMapResult::Failed; } } _localKey = std::move(localKey); _draftsMap = draftsMap; _draftCursorsMap = draftCursorsMap; _draftsNotReadMap = draftsNotReadMap; _locationsKey = locationsKey; _trustedBotsKey = trustedBotsKey; _recentStickersKeyOld = recentStickersKeyOld; _installedStickersKey = installedStickersKey; _featuredStickersKey = featuredStickersKey; _recentStickersKey = recentStickersKey; _favedStickersKey = favedStickersKey; _archivedStickersKey = archivedStickersKey; _savedGifsKey = savedGifsKey; _legacyBackgroundKeyDay = legacyBackgroundKeyDay; _legacyBackgroundKeyNight = legacyBackgroundKeyNight; _settingsKey = userSettingsKey; _recentHashtagsAndBotsKey = recentHashtagsAndBotsKey; _exportSettingsKey = exportSettingsKey; _oldMapVersion = mapData.version; if (_oldMapVersion < AppVersion) { writeMapDelayed(); } else { _mapChanged = false; } if (_locationsKey) { readLocations(); } if (_legacyBackgroundKeyDay || _legacyBackgroundKeyNight) { Local::moveLegacyBackground( _basePath, _localKey, _legacyBackgroundKeyDay, _legacyBackgroundKeyNight); } auto stored = readSessionSettings(); readMtpData(); DEBUG_LOG(("selfSerialized set: %1").arg(selfSerialized.size())); _owner->setSessionFromStorage( std::move(stored), std::move(selfSerialized), _oldMapVersion); LOG(("Map read time: %1").arg(crl::now() - ms)); return ReadMapResult::Success; } void Account::writeMapDelayed() { _mapChanged = true; _writeMapTimer.callOnce(kDelayedWriteTimeout); } void Account::writeMapQueued() { _mapChanged = true; crl::on_main(_owner, [=] { writeMap(); }); } void Account::writeMap() { Expects(_localKey != nullptr); _writeMapTimer.cancel(); if (!_mapChanged) { return; } _mapChanged = false; if (!QDir().exists(_basePath)) { QDir().mkpath(_basePath); } FileWriteDescriptor map(u"map"_q, _basePath); map.writeData(QByteArray()); map.writeData(QByteArray()); uint32 mapSize = 0; const auto self = [&] { if (!_owner->sessionExists()) { DEBUG_LOG(("AuthSelf Warning: Session does not exist.")); return QByteArray(); } const auto self = _owner->session().user(); if (self->phone().isEmpty()) { DEBUG_LOG(("AuthSelf Error: Phone is empty.")); return QByteArray(); } auto result = QByteArray(); result.reserve(Serialize::peerSize(self) + Serialize::stringSize(self->about())); { QBuffer buffer(&result); buffer.open(QIODevice::WriteOnly); QDataStream stream(&buffer); Serialize::writePeer(stream, self); stream << self->about(); } return result; }(); if (!self.isEmpty()) mapSize += sizeof(quint32) + Serialize::bytearraySize(self); if (!_draftsMap.empty()) mapSize += sizeof(quint32) * 2 + _draftsMap.size() * sizeof(quint64) * 2; if (!_draftCursorsMap.empty()) mapSize += sizeof(quint32) * 2 + _draftCursorsMap.size() * sizeof(quint64) * 2; if (_locationsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_trustedBotsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_recentStickersKeyOld) mapSize += sizeof(quint32) + sizeof(quint64); if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { mapSize += sizeof(quint32) + 4 * sizeof(quint64); } if (_favedStickersKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_savedGifsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_settingsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_recentHashtagsAndBotsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_exportSettingsKey) mapSize += sizeof(quint32) + sizeof(quint64); EncryptedDescriptor mapData(mapSize); if (!self.isEmpty()) { mapData.stream << quint32(lskSelfSerialized) << self; } if (!_draftsMap.empty()) { mapData.stream << quint32(lskDraft) << quint32(_draftsMap.size()); for (const auto &[key, value] : _draftsMap) { mapData.stream << quint64(value) << quint64(key); } } if (!_draftCursorsMap.empty()) { mapData.stream << quint32(lskDraftPosition) << quint32(_draftCursorsMap.size()); for (const auto &[key, value] : _draftCursorsMap) { mapData.stream << quint64(value) << quint64(key); } } if (_locationsKey) { mapData.stream << quint32(lskLocations) << quint64(_locationsKey); } if (_trustedBotsKey) { mapData.stream << quint32(lskTrustedBots) << quint64(_trustedBotsKey); } if (_recentStickersKeyOld) { mapData.stream << quint32(lskRecentStickersOld) << quint64(_recentStickersKeyOld); } if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) { mapData.stream << quint32(lskStickersKeys); mapData.stream << quint64(_installedStickersKey) << quint64(_featuredStickersKey) << quint64(_recentStickersKey) << quint64(_archivedStickersKey); } if (_favedStickersKey) { mapData.stream << quint32(lskFavedStickers) << quint64(_favedStickersKey); } if (_savedGifsKey) { mapData.stream << quint32(lskSavedGifs) << quint64(_savedGifsKey); } if (_settingsKey) { mapData.stream << quint32(lskUserSettings) << quint64(_settingsKey); } if (_recentHashtagsAndBotsKey) { mapData.stream << quint32(lskRecentHashtagsAndBots) << quint64(_recentHashtagsAndBotsKey); } if (_exportSettingsKey) { mapData.stream << quint32(lskExportSettings) << quint64(_exportSettingsKey); } map.writeEncrypted(mapData, _localKey); _mapChanged = false; } void Account::reset() { auto names = collectGoodNames(); _draftsMap.clear(); _draftCursorsMap.clear(); _draftsNotReadMap.clear(); _locationsKey = _trustedBotsKey = 0; _recentStickersKeyOld = 0; _installedStickersKey = 0; _featuredStickersKey = 0; _recentStickersKey = 0; _favedStickersKey = 0; _archivedStickersKey = 0; _savedGifsKey = 0; _legacyBackgroundKeyDay = _legacyBackgroundKeyNight = 0; _settingsKey = _recentHashtagsAndBotsKey = _exportSettingsKey = 0; _oldMapVersion = 0; _fileLocations.clear(); _fileLocationPairs.clear(); _fileLocationAliases.clear(); _cacheTotalSizeLimit = Database::Settings().totalSizeLimit; _cacheTotalTimeLimit = Database::Settings().totalTimeLimit; _cacheBigFileTotalSizeLimit = Database::Settings().totalSizeLimit; _cacheBigFileTotalTimeLimit = Database::Settings().totalTimeLimit; _mapChanged = true; writeMap(); writeMtpData(); crl::async([base = _basePath, temp = _tempPath, names = std::move(names)] { for (const auto &name : names) { if (!name.endsWith(qstr("map0")) && !name.endsWith(qstr("map1")) && !name.endsWith(qstr("maps")) && !name.endsWith(qstr("configs"))) { QFile::remove(base + name); } } QDir(LegacyTempDirectory()).removeRecursively(); QDir(temp).removeRecursively(); }); } void Account::writeLocations() { _writeLocationsTimer.cancel(); if (!_locationsChanged) { return; } _locationsChanged = false; if (_fileLocations.isEmpty()) { if (_locationsKey) { ClearKey(_locationsKey, _basePath); _locationsKey = 0; writeMapDelayed(); } } else { if (!_locationsKey) { _locationsKey = GenerateKey(_basePath); writeMapQueued(); } quint32 size = 0; for (auto i = _fileLocations.cbegin(), e = _fileLocations.cend(); i != e; ++i) { // location + type + namelen + name size += sizeof(quint64) * 2 + sizeof(quint32) + Serialize::stringSize(i.value().name()); if (AppVersion > 9013) { // bookmark size += Serialize::bytearraySize(i.value().bookmark()); } // date + size size += Serialize::dateTimeSize() + sizeof(quint32); } //end mark size += sizeof(quint64) * 2 + sizeof(quint32) + Serialize::stringSize(QString()); if (AppVersion > 9013) { size += Serialize::bytearraySize(QByteArray()); } size += Serialize::dateTimeSize() + sizeof(quint32); size += sizeof(quint32); // aliases count for (auto i = _fileLocationAliases.cbegin(), e = _fileLocationAliases.cend(); i != e; ++i) { // alias + location size += sizeof(quint64) * 2 + sizeof(quint64) * 2; } EncryptedDescriptor data(size); auto legacyTypeField = 0; for (auto i = _fileLocations.cbegin(); i != _fileLocations.cend(); ++i) { data.stream << quint64(i.key().first) << quint64(i.key().second) << quint32(legacyTypeField) << i.value().name(); if (AppVersion > 9013) { data.stream << i.value().bookmark(); } data.stream << i.value().modified << quint32(i.value().size); } data.stream << quint64(0) << quint64(0) << quint32(0) << QString(); if (AppVersion > 9013) { data.stream << QByteArray(); } data.stream << QDateTime::currentDateTime() << quint32(0); data.stream << quint32(_fileLocationAliases.size()); for (auto i = _fileLocationAliases.cbegin(), e = _fileLocationAliases.cend(); i != e; ++i) { data.stream << quint64(i.key().first) << quint64(i.key().second) << quint64(i.value().first) << quint64(i.value().second); } FileWriteDescriptor file(_locationsKey, _basePath); file.writeEncrypted(data, _localKey); } } void Account::writeLocationsQueued() { _locationsChanged = true; crl::on_main(_owner, [=] { writeLocations(); }); } void Account::writeLocationsDelayed() { _locationsChanged = true; _writeLocationsTimer.callOnce(kDelayedWriteTimeout); } void Account::readLocations() { FileReadDescriptor locations; if (!ReadEncryptedFile(locations, _locationsKey, _basePath, _localKey)) { ClearKey(_locationsKey, _basePath); _locationsKey = 0; writeMapDelayed(); return; } bool endMarkFound = false; while (!locations.stream.atEnd()) { quint64 first, second; QByteArray bookmark; FileLocation loc; quint32 legacyTypeField = 0; locations.stream >> first >> second >> legacyTypeField >> loc.fname; if (locations.version > 9013) { locations.stream >> bookmark; } locations.stream >> loc.modified >> loc.size; loc.setBookmark(bookmark); if (!first && !second && !legacyTypeField && loc.fname.isEmpty() && !loc.size) { // end mark endMarkFound = true; break; } MediaKey key(first, second); _fileLocations.insert(key, loc); if (!loc.inMediaCache()) { _fileLocationPairs.insert(loc.fname, { key, loc }); } } if (endMarkFound) { quint32 cnt; locations.stream >> cnt; for (quint32 i = 0; i < cnt; ++i) { quint64 kfirst, ksecond, vfirst, vsecond; locations.stream >> kfirst >> ksecond >> vfirst >> vsecond; _fileLocationAliases.insert(MediaKey(kfirst, ksecond), MediaKey(vfirst, vsecond)); } if (!locations.stream.atEnd()) { quint32 webLocationsCount; locations.stream >> webLocationsCount; for (quint32 i = 0; i < webLocationsCount; ++i) { QString url; quint64 key; qint32 size; locations.stream >> url >> key >> size; ClearKey(key, _basePath); } } } } void Account::writeSessionSettings() { writeSessionSettings(nullptr); } void Account::writeSessionSettings(Main::SessionSettings *stored) { if (_readingUserSettings) { LOG(("App Error: attempt to write settings while reading them!")); return; } LOG(("App Info: writing encrypted user settings...")); if (!_settingsKey) { _settingsKey = GenerateKey(_basePath); writeMapQueued(); } auto recentEmojiPreloadData = cRecentEmojiPreload(); if (recentEmojiPreloadData.isEmpty()) { recentEmojiPreloadData.reserve(GetRecentEmoji().size()); for (auto &item : GetRecentEmoji()) { recentEmojiPreloadData.push_back(qMakePair(item.first->id(), item.second)); } } auto userDataInstance = stored ? stored : _owner->getSessionSettings(); auto userData = userDataInstance ? userDataInstance->serialize() : QByteArray(); auto recentStickers = cRecentStickersPreload(); if (recentStickers.isEmpty() && _owner->sessionExists()) { const auto &stickers = _owner->session().data().stickers(); recentStickers.reserve(stickers.getRecentPack().size()); for (const auto &pair : std::as_const(stickers.getRecentPack())) { recentStickers.push_back(qMakePair(pair.first->id, pair.second)); } } uint32 size = 24 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32); size += sizeof(quint32) + sizeof(qint32); for (auto &item : recentEmojiPreloadData) { size += Serialize::stringSize(item.first) + sizeof(item.second); } size += sizeof(quint32) + sizeof(qint32) + cEmojiVariants().size() * (sizeof(uint32) + sizeof(uint64)); size += sizeof(quint32) + sizeof(qint32) + recentStickers.size() * (sizeof(uint64) + sizeof(ushort)); size += sizeof(quint32) + 3 * sizeof(qint32); size += sizeof(quint32) + 2 * sizeof(qint32); size += sizeof(quint32) + sizeof(qint64) + sizeof(qint32); if (!userData.isEmpty()) { size += sizeof(quint32) + Serialize::bytearraySize(userData); } EncryptedDescriptor data(size); data.stream << quint32(dbiUseExternalVideoPlayer) << qint32(cUseExternalVideoPlayer()); data.stream << quint32(dbiCacheSettings) << qint64(_cacheTotalSizeLimit) << qint32(_cacheTotalTimeLimit) << qint64(_cacheBigFileTotalSizeLimit) << qint32(_cacheBigFileTotalTimeLimit); if (!userData.isEmpty()) { data.stream << quint32(dbiSessionSettings) << userData; } data.stream << quint32(dbiRecentEmoji) << recentEmojiPreloadData; data.stream << quint32(dbiEmojiVariants) << cEmojiVariants(); data.stream << quint32(dbiRecentStickers) << recentStickers; FileWriteDescriptor file(_settingsKey, _basePath); file.writeEncrypted(data, _localKey); } ReadSettingsContext Account::prepareReadSettingsContext() const { return ReadSettingsContext{ .legacyHasCustomDayBackground = (_legacyBackgroundKeyDay != 0) }; } std::unique_ptr Account::readSessionSettings() { ReadSettingsContext context; FileReadDescriptor userSettings; if (!ReadEncryptedFile(userSettings, _settingsKey, _basePath, _localKey)) { LOG(("App Info: could not read encrypted user settings...")); Local::readOldUserSettings(true, context); auto result = applyReadContext(std::move(context)); writeSessionSettings(result.get()); return result; } LOG(("App Info: reading encrypted user settings...")); _readingUserSettings = true; while (!userSettings.stream.atEnd()) { quint32 blockId; userSettings.stream >> blockId; if (!CheckStreamStatus(userSettings.stream)) { _readingUserSettings = false; writeSessionSettings(); return nullptr; } if (!ReadSetting(blockId, userSettings.stream, userSettings.version, context)) { _readingUserSettings = false; writeSessionSettings(); return nullptr; } } _readingUserSettings = false; LOG(("App Info: encrypted user settings read.")); auto result = applyReadContext(std::move(context)); if (context.legacyRead) { writeSessionSettings(result.get()); } return result; } std::unique_ptr Account::applyReadContext( ReadSettingsContext &&context) { ApplyReadFallbackConfig(context); if (context.cacheTotalSizeLimit) { _cacheTotalSizeLimit = context.cacheTotalSizeLimit; _cacheTotalTimeLimit = context.cacheTotalTimeLimit; _cacheBigFileTotalSizeLimit = context.cacheBigFileTotalSizeLimit; _cacheBigFileTotalTimeLimit = context.cacheBigFileTotalTimeLimit; const auto &normal = Database::Settings(); Assert(_cacheTotalSizeLimit > normal.maxDataSize); Assert(_cacheBigFileTotalSizeLimit > normal.maxDataSize); } if (!context.mtpAuthorization.isEmpty()) { _owner->setMtpAuthorization(context.mtpAuthorization); } else { for (auto &key : context.mtpLegacyKeys) { _owner->setLegacyMtpKey(std::move(key)); } if (context.mtpLegacyMainDcId) { _owner->setMtpMainDcId(context.mtpLegacyMainDcId); _owner->setSessionUserId(context.mtpLegacyUserId); } } if (context.tileRead) { Window::Theme::Background()->setTileDayValue(context.tileDay); Window::Theme::Background()->setTileNightValue(context.tileNight); } return std::move(context.sessionSettingsStorage); } void Account::writeMtpData() { Expects(_localKey != nullptr); const auto serialized = _owner->serializeMtpAuthorization(); const auto size = sizeof(quint32) + Serialize::bytearraySize(serialized); FileWriteDescriptor mtp(ToFilePart(_dataNameKey), BaseGlobalPath()); EncryptedDescriptor data(size); data.stream << quint32(dbiMtpAuthorization) << serialized; mtp.writeEncrypted(data, _localKey); } void Account::readMtpData() { auto context = prepareReadSettingsContext(); FileReadDescriptor mtp; if (!ReadEncryptedFile(mtp, ToFilePart(_dataNameKey), BaseGlobalPath(), _localKey)) { if (_localKey) { Local::readOldMtpData(true, context); applyReadContext(std::move(context)); writeMtpData(); } return; } LOG(("App Info: reading encrypted mtp data...")); while (!mtp.stream.atEnd()) { quint32 blockId; mtp.stream >> blockId; if (!CheckStreamStatus(mtp.stream)) { return writeMtpData(); } if (!ReadSetting(blockId, mtp.stream, mtp.version, context)) { return writeMtpData(); } } applyReadContext(std::move(context)); } void Account::writeMtpConfig() { Expects(_localKey != nullptr); const auto serialized = _owner->mtp().config().serialize(); const auto size = Serialize::bytearraySize(serialized); FileWriteDescriptor file(u"config"_q, _basePath); EncryptedDescriptor data(size); data.stream << serialized; file.writeEncrypted(data, _localKey); } std::unique_ptr Account::readMtpConfig() { Expects(_localKey != nullptr); FileReadDescriptor file; if (!ReadEncryptedFile(file, "config", _basePath, _localKey)) { return nullptr; } LOG(("App Info: reading encrypted mtp config...")); auto serialized = QByteArray(); file.stream >> serialized; if (!CheckStreamStatus(file.stream)) { return nullptr; } return MTP::Config::FromSerialized(serialized); } void Account::writeDrafts(not_null history) { Storage::MessageDraft storedLocalDraft, storedEditDraft; MessageCursor localCursor, editCursor; if (const auto localDraft = history->localDraft()) { if (_owner->session().supportMode() || !Data::draftsAreEqual(localDraft, history->cloudDraft())) { storedLocalDraft = Storage::MessageDraft{ localDraft->msgId, localDraft->textWithTags, localDraft->previewCancelled }; localCursor = localDraft->cursor; } } if (const auto editDraft = history->editDraft()) { storedEditDraft = Storage::MessageDraft{ editDraft->msgId, editDraft->textWithTags, editDraft->previewCancelled }; editCursor = editDraft->cursor; } writeDrafts( history->peer->id, storedLocalDraft, storedEditDraft); writeDraftCursors(history->peer->id, localCursor, editCursor); } void Account::writeDrafts( const PeerId &peer, const MessageDraft &localDraft, const MessageDraft &editDraft) { if (localDraft.msgId <= 0 && localDraft.textWithTags.text.isEmpty() && editDraft.msgId <= 0) { auto i = _draftsMap.find(peer); if (i != _draftsMap.cend()) { ClearKey(i->second, _basePath); _draftsMap.erase(i); writeMapDelayed(); } _draftsNotReadMap.remove(peer); } else { auto i = _draftsMap.find(peer); if (i == _draftsMap.cend()) { i = _draftsMap.emplace(peer, GenerateKey(_basePath)).first; writeMapQueued(); } auto msgTags = TextUtilities::SerializeTags( localDraft.textWithTags.tags); auto editTags = TextUtilities::SerializeTags( editDraft.textWithTags.tags); int size = sizeof(quint64); size += Serialize::stringSize(localDraft.textWithTags.text) + Serialize::bytearraySize(msgTags) + 2 * sizeof(qint32); size += Serialize::stringSize(editDraft.textWithTags.text) + Serialize::bytearraySize(editTags) + 2 * sizeof(qint32); EncryptedDescriptor data(size); data.stream << quint64(peer); data.stream << localDraft.textWithTags.text << msgTags; data.stream << qint32(localDraft.msgId) << qint32(localDraft.previewCancelled ? 1 : 0); data.stream << editDraft.textWithTags.text << editTags; data.stream << qint32(editDraft.msgId) << qint32(editDraft.previewCancelled ? 1 : 0); FileWriteDescriptor file(i->second, _basePath); file.writeEncrypted(data, _localKey); _draftsNotReadMap.remove(peer); } } void Account::clearDraftCursors(const PeerId &peer) { const auto i = _draftCursorsMap.find(peer); if (i != _draftCursorsMap.cend()) { ClearKey(i->second, _basePath); _draftCursorsMap.erase(i); writeMapDelayed(); } } void Account::readDraftCursors( const PeerId &peer, MessageCursor &localCursor, MessageCursor &editCursor) { const auto j = _draftCursorsMap.find(peer); if (j == _draftCursorsMap.cend()) { return; } FileReadDescriptor draft; if (!ReadEncryptedFile(draft, j->second, _basePath, _localKey)) { clearDraftCursors(peer); return; } quint64 draftPeer; qint32 localPosition = 0, localAnchor = 0, localScroll = QFIXED_MAX; qint32 editPosition = 0, editAnchor = 0, editScroll = QFIXED_MAX; draft.stream >> draftPeer >> localPosition >> localAnchor >> localScroll; if (!draft.stream.atEnd()) { draft.stream >> editPosition >> editAnchor >> editScroll; } if (draftPeer != peer) { clearDraftCursors(peer); return; } localCursor = MessageCursor(localPosition, localAnchor, localScroll); editCursor = MessageCursor(editPosition, editAnchor, editScroll); } void Account::readDraftsWithCursors(not_null history) { PeerId peer = history->peer->id; if (!_draftsNotReadMap.remove(peer)) { clearDraftCursors(peer); return; } const auto j = _draftsMap.find(peer); if (j == _draftsMap.cend()) { clearDraftCursors(peer); return; } FileReadDescriptor draft; if (!ReadEncryptedFile(draft, j->second, _basePath, _localKey)) { ClearKey(j->second, _basePath); _draftsMap.erase(j); clearDraftCursors(peer); return; } quint64 draftPeer = 0; TextWithTags msgData, editData; QByteArray msgTagsSerialized, editTagsSerialized; qint32 msgReplyTo = 0, msgPreviewCancelled = 0, editMsgId = 0, editPreviewCancelled = 0; draft.stream >> draftPeer >> msgData.text; if (draft.version >= 9048) { draft.stream >> msgTagsSerialized; } if (draft.version >= 7021) { draft.stream >> msgReplyTo; if (draft.version >= 8001) { draft.stream >> msgPreviewCancelled; if (!draft.stream.atEnd()) { draft.stream >> editData.text; if (draft.version >= 9048) { draft.stream >> editTagsSerialized; } draft.stream >> editMsgId >> editPreviewCancelled; } } } if (draftPeer != peer) { ClearKey(j->second, _basePath); _draftsMap.erase(j); clearDraftCursors(peer); return; } msgData.tags = TextUtilities::DeserializeTags( msgTagsSerialized, msgData.text.size()); editData.tags = TextUtilities::DeserializeTags( editTagsSerialized, editData.text.size()); MessageCursor msgCursor, editCursor; readDraftCursors(peer, msgCursor, editCursor); if (!history->localDraft()) { if (msgData.text.isEmpty() && !msgReplyTo) { history->clearLocalDraft(); } else { history->setLocalDraft(std::make_unique( msgData, msgReplyTo, msgCursor, msgPreviewCancelled)); } } if (!editMsgId) { history->clearEditDraft(); } else { history->setEditDraft(std::make_unique( editData, editMsgId, editCursor, editPreviewCancelled)); } } void Account::writeDraftCursors( const PeerId &peer, const MessageCursor &msgCursor, const MessageCursor &editCursor) { if (msgCursor == MessageCursor() && editCursor == MessageCursor()) { clearDraftCursors(peer); } else { auto i = _draftCursorsMap.find(peer); if (i == _draftCursorsMap.cend()) { i = _draftCursorsMap.emplace(peer, GenerateKey(_basePath)).first; writeMapQueued(); } EncryptedDescriptor data(sizeof(quint64) + sizeof(qint32) * 3); data.stream << quint64(peer) << qint32(msgCursor.position) << qint32(msgCursor.anchor) << qint32(msgCursor.scroll); data.stream << qint32(editCursor.position) << qint32(editCursor.anchor) << qint32(editCursor.scroll); FileWriteDescriptor file(i->second, _basePath); file.writeEncrypted(data, _localKey); } } bool Account::hasDraftCursors(const PeerId &peer) { return _draftCursorsMap.contains(peer); } bool Account::hasDraft(const PeerId &peer) { return _draftsMap.contains(peer); } void Account::writeFileLocation(MediaKey location, const FileLocation &local) { if (local.fname.isEmpty()) { return; } if (!local.inMediaCache()) { const auto aliasIt = _fileLocationAliases.constFind(location); if (aliasIt != _fileLocationAliases.cend()) { location = aliasIt.value(); } const auto i = _fileLocationPairs.find(local.fname); if (i != _fileLocationPairs.cend()) { if (i.value().second == local) { if (i.value().first != location) { _fileLocationAliases.insert(location, i.value().first); writeLocationsQueued(); } return; } if (i.value().first != location) { for (auto j = _fileLocations.find(i.value().first), e = _fileLocations.end(); (j != e) && (j.key() == i.value().first); ++j) { if (j.value() == i.value().second) { _fileLocations.erase(j); break; } } _fileLocationPairs.erase(i); } } _fileLocationPairs.insert(local.fname, { location, local }); } else { for (auto i = _fileLocations.find(location); (i != _fileLocations.end()) && (i.key() == location);) { if (i.value().inMediaCache() || i.value().check()) { return; } i = _fileLocations.erase(i); } } _fileLocations.insert(location, local); writeLocationsQueued(); } void Account::removeFileLocation(MediaKey location) { auto i = _fileLocations.find(location); if (i == _fileLocations.end()) { return; } while (i != _fileLocations.end() && (i.key() == location)) { i = _fileLocations.erase(i); } writeLocationsQueued(); } FileLocation Account::readFileLocation(MediaKey location) { const auto aliasIt = _fileLocationAliases.constFind(location); if (aliasIt != _fileLocationAliases.cend()) { location = aliasIt.value(); } for (auto i = _fileLocations.find(location); (i != _fileLocations.end()) && (i.key() == location);) { if (!i.value().inMediaCache() && !i.value().check()) { _fileLocationPairs.remove(i.value().fname); i = _fileLocations.erase(i); writeLocationsDelayed(); continue; } return i.value(); } return FileLocation(); } EncryptionKey Account::cacheKey() const { Expects(_localKey != nullptr); return EncryptionKey(bytes::make_vector(_localKey->data())); } EncryptionKey Account::cacheBigFileKey() const { return cacheKey(); } QString Account::cachePath() const { Expects(!_databasePath.isEmpty()); return _databasePath + "cache"; } Cache::Database::Settings Account::cacheSettings() const { auto result = Cache::Database::Settings(); result.clearOnWrongKey = true; result.totalSizeLimit = _cacheTotalSizeLimit; result.totalTimeLimit = _cacheTotalTimeLimit; result.maxDataSize = kMaxFileInMemory; return result; } void Account::updateCacheSettings( Cache::Database::SettingsUpdate &update, Cache::Database::SettingsUpdate &updateBig) { Expects(update.totalSizeLimit > Database::Settings().maxDataSize); Expects(update.totalTimeLimit >= 0); Expects(updateBig.totalSizeLimit > Database::Settings().maxDataSize); Expects(updateBig.totalTimeLimit >= 0); if (_cacheTotalSizeLimit == update.totalSizeLimit && _cacheTotalTimeLimit == update.totalTimeLimit && _cacheBigFileTotalSizeLimit == updateBig.totalSizeLimit && _cacheBigFileTotalTimeLimit == updateBig.totalTimeLimit) { return; } _cacheTotalSizeLimit = update.totalSizeLimit; _cacheTotalTimeLimit = update.totalTimeLimit; _cacheBigFileTotalSizeLimit = updateBig.totalSizeLimit; _cacheBigFileTotalTimeLimit = updateBig.totalTimeLimit; writeSessionSettings(); } QString Account::cacheBigFilePath() const { Expects(!_databasePath.isEmpty()); return _databasePath + "media_cache"; } Cache::Database::Settings Account::cacheBigFileSettings() const { auto result = Cache::Database::Settings(); result.clearOnWrongKey = true; result.totalSizeLimit = _cacheBigFileTotalSizeLimit; result.totalTimeLimit = _cacheBigFileTotalTimeLimit; result.maxDataSize = kMaxFileInMemory; return result; } void Account::writeStickerSet( QDataStream &stream, const Data::StickersSet &set) { const auto writeInfo = [&](int count) { stream << quint64(set.id) << quint64(set.access) << set.title << set.shortName << qint32(count) << qint32(set.hash) << qint32(set.flags) << qint32(set.installDate); Serialize::writeImageLocation(stream, set.thumbnailLocation()); }; if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { writeInfo(-set.count); return; } else if (set.stickers.isEmpty()) { return; } writeInfo(set.stickers.size()); for (const auto &sticker : set.stickers) { Serialize::Document::writeToStream(stream, sticker); } stream << qint32(set.dates.size()); if (!set.dates.empty()) { Assert(set.dates.size() == set.stickers.size()); for (const auto date : set.dates) { stream << qint32(date); } } stream << qint32(set.emoji.size()); for (auto j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) { stream << j.key()->id() << qint32(j->size()); for (const auto sticker : *j) { stream << quint64(sticker->id); } } } // In generic method _writeStickerSets() we look through all the sets and call a // callback on each set to see, if we write it, skip it or abort the whole write. enum class StickerSetCheckResult { Write, Skip, Abort, }; // CheckSet is a functor on Data::StickersSet, which returns a StickerSetCheckResult. template void Account::writeStickerSets( FileKey &stickersKey, CheckSet checkSet, const Data::StickersSetsOrder &order) { const auto &sets = _owner->session().data().stickers().sets(); if (sets.empty()) { if (stickersKey) { ClearKey(stickersKey, _basePath); stickersKey = 0; writeMapDelayed(); } return; } // versionTag + version + count quint32 size = sizeof(quint32) + sizeof(qint32) + sizeof(qint32); int32 setsCount = 0; for (const auto &[id, set] : sets) { const auto raw = set.get(); auto result = checkSet(*raw); if (result == StickerSetCheckResult::Abort) { return; } else if (result == StickerSetCheckResult::Skip) { continue; } // id + access + title + shortName + stickersCount + hash + flags + installDate size += sizeof(quint64) * 2 + Serialize::stringSize(raw->title) + Serialize::stringSize(raw->shortName) + sizeof(qint32) * 4 + Serialize::imageLocationSize(raw->thumbnailLocation()); if (raw->flags & MTPDstickerSet_ClientFlag::f_not_loaded) { continue; } for (const auto sticker : raw->stickers) { size += Serialize::Document::sizeInStream(sticker); } size += sizeof(qint32); // datesCount if (!raw->dates.empty()) { Assert(raw->stickers.size() == raw->dates.size()); size += raw->dates.size() * sizeof(qint32); } size += sizeof(qint32); // emojiCount for (auto j = raw->emoji.cbegin(), e = raw->emoji.cend(); j != e; ++j) { size += Serialize::stringSize(j.key()->id()) + sizeof(qint32) + (j->size() * sizeof(quint64)); } ++setsCount; } if (!setsCount && order.isEmpty()) { if (stickersKey) { ClearKey(stickersKey, _basePath); stickersKey = 0; writeMapDelayed(); } return; } size += sizeof(qint32) + (order.size() * sizeof(quint64)); if (!stickersKey) { stickersKey = GenerateKey(_basePath); writeMapQueued(); } EncryptedDescriptor data(size); data.stream << quint32(kStickersVersionTag) << qint32(kStickersSerializeVersion) << qint32(setsCount); for (const auto &[id, set] : sets) { auto result = checkSet(*set); if (result == StickerSetCheckResult::Abort) { return; } else if (result == StickerSetCheckResult::Skip) { continue; } writeStickerSet(data.stream, *set); } data.stream << order; FileWriteDescriptor file(stickersKey, _basePath); file.writeEncrypted(data, _localKey); } void Account::readStickerSets( FileKey &stickersKey, Data::StickersSetsOrder *outOrder, MTPDstickerSet::Flags readingFlags) { FileReadDescriptor stickers; if (!ReadEncryptedFile(stickers, stickersKey, _basePath, _localKey)) { ClearKey(stickersKey, _basePath); stickersKey = 0; writeMapDelayed(); return; } const auto failed = [&] { ClearKey(stickersKey, _basePath); stickersKey = 0; }; auto &sets = _owner->session().data().stickers().setsRef(); if (outOrder) outOrder->clear(); quint32 versionTag = 0; qint32 version = 0; stickers.stream >> versionTag >> version; if (versionTag != kStickersVersionTag || version != kStickersSerializeVersion) { // Old data, without sticker set thumbnails. return failed(); } qint32 count = 0; stickers.stream >> count; if (!CheckStreamStatus(stickers.stream) || (count < 0) || (count > kMaxSavedStickerSetsCount)) { return failed(); } for (auto i = 0; i != count; ++i) { using LocationType = StorageFileLocation::Type; quint64 setId = 0, setAccess = 0; QString setTitle, setShortName; qint32 scnt = 0; qint32 setInstallDate = 0; qint32 setHash = 0; MTPDstickerSet::Flags setFlags = 0; qint32 setFlagsValue = 0; ImageLocation setThumbnail; stickers.stream >> setId >> setAccess >> setTitle >> setShortName >> scnt >> setHash >> setFlagsValue >> setInstallDate; const auto thumbnail = Serialize::readImageLocation( stickers.version, stickers.stream); if (!thumbnail || !CheckStreamStatus(stickers.stream)) { return failed(); } else if (thumbnail->valid() && thumbnail->isLegacy()) { setThumbnail = thumbnail->convertToModern( LocationType::StickerSetThumb, setId, setAccess); } else { setThumbnail = *thumbnail; } setFlags = MTPDstickerSet::Flags::from_raw(setFlagsValue); if (setId == Data::Stickers::DefaultSetId) { setTitle = tr::lng_stickers_default_set(tr::now); setFlags |= MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special; } else if (setId == Data::Stickers::CustomSetId) { setTitle = qsl("Custom stickers"); setFlags |= MTPDstickerSet_ClientFlag::f_special; } else if (setId == Data::Stickers::CloudRecentSetId) { setTitle = tr::lng_recent_stickers(tr::now); setFlags |= MTPDstickerSet_ClientFlag::f_special; } else if (setId == Data::Stickers::FavedSetId) { setTitle = Lang::Hard::FavedSetTitle(); setFlags |= MTPDstickerSet_ClientFlag::f_special; } else if (!setId) { continue; } auto it = sets.find(setId); if (it == sets.cend()) { // We will set this flags from order lists when reading those stickers. setFlags &= ~(MTPDstickerSet::Flag::f_installed_date | MTPDstickerSet_ClientFlag::f_featured); it = sets.emplace(setId, std::make_unique( &_owner->session().data(), setId, setAccess, setTitle, setShortName, 0, setHash, MTPDstickerSet::Flags(setFlags), setInstallDate)).first; it->second->setThumbnail( ImageWithLocation{ .location = setThumbnail }); } const auto set = it->second.get(); auto inputSet = MTP_inputStickerSetID(MTP_long(set->id), MTP_long(set->access)); const auto fillStickers = set->stickers.isEmpty(); if (scnt < 0) { // disabled not loaded set if (!set->count || fillStickers) { set->count = -scnt; } continue; } if (fillStickers) { set->stickers.reserve(scnt); set->count = 0; } Serialize::Document::StickerSetInfo info(setId, setAccess, setShortName); base::flat_set read; for (int32 j = 0; j < scnt; ++j) { auto document = Serialize::Document::readStickerFromStream( &_owner->session(), stickers.version, stickers.stream, info); if (!CheckStreamStatus(stickers.stream)) { return failed(); } else if (!document || !document->sticker() || read.contains(document->id)) { continue; } read.emplace(document->id); if (fillStickers) { set->stickers.push_back(document); if (!(set->flags & MTPDstickerSet_ClientFlag::f_special)) { if (document->sticker()->set.type() != mtpc_inputStickerSetID) { document->sticker()->set = inputSet; } } ++set->count; } } qint32 datesCount = 0; stickers.stream >> datesCount; if (datesCount > 0) { if (datesCount != scnt) { return failed(); } const auto fillDates = (set->id == Data::Stickers::CloudRecentSetId) && (set->stickers.size() == datesCount); if (fillDates) { set->dates.clear(); set->dates.reserve(datesCount); } for (auto i = 0; i != datesCount; ++i) { qint32 date = 0; stickers.stream >> date; if (fillDates) { set->dates.push_back(TimeId(date)); } } } qint32 emojiCount = 0; stickers.stream >> emojiCount; if (!CheckStreamStatus(stickers.stream) || emojiCount < 0) { return failed(); } for (int32 j = 0; j < emojiCount; ++j) { QString emojiString; qint32 stickersCount; stickers.stream >> emojiString >> stickersCount; Data::StickersPack pack; pack.reserve(stickersCount); for (int32 k = 0; k < stickersCount; ++k) { quint64 id; stickers.stream >> id; const auto doc = _owner->session().data().document(id); if (!doc->sticker()) continue; pack.push_back(doc); } if (fillStickers) { if (auto emoji = Ui::Emoji::Find(emojiString)) { emoji = emoji->original(); set->emoji.insert(emoji, pack); } } } } // Read orders of installed and featured stickers. if (outOrder) { auto outOrderCount = quint32(); stickers.stream >> outOrderCount; if (!CheckStreamStatus(stickers.stream) || outOrderCount > 1000) { return failed(); } outOrder->reserve(outOrderCount); for (auto i = 0; i != outOrderCount; ++i) { auto value = uint64(); stickers.stream >> value; if (!CheckStreamStatus(stickers.stream)) { outOrder->clear(); return failed(); } outOrder->push_back(value); } } if (!CheckStreamStatus(stickers.stream)) { return failed(); } // Set flags that we dropped above from the order. if (readingFlags && outOrder) { for (const auto setId : std::as_const(*outOrder)) { auto it = sets.find(setId); if (it != sets.cend()) { const auto set = it->second.get(); set->flags |= readingFlags; if ((readingFlags == MTPDstickerSet::Flag::f_installed_date) && !set->installDate) { set->installDate = kDefaultStickerInstallDate; } } } } } void Account::writeInstalledStickers() { writeStickerSets(_installedStickersKey, [](const Data::StickersSet &set) { if (set.id == Data::Stickers::CloudRecentSetId || set.id == Data::Stickers::FavedSetId) { // separate files for them return StickerSetCheckResult::Skip; } else if (set.flags & MTPDstickerSet_ClientFlag::f_special) { if (set.stickers.isEmpty()) { // all other special are "installed" return StickerSetCheckResult::Skip; } } else if (!(set.flags & MTPDstickerSet::Flag::f_installed_date) || (set.flags & MTPDstickerSet::Flag::f_archived)) { return StickerSetCheckResult::Skip; } else if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { // waiting to receive return StickerSetCheckResult::Abort; } else if (set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write; }, _owner->session().data().stickers().setsOrder()); } void Account::writeFeaturedStickers() { writeStickerSets(_featuredStickersKey, [](const Data::StickersSet &set) { if (set.id == Data::Stickers::CloudRecentSetId || set.id == Data::Stickers::FavedSetId) { // separate files for them return StickerSetCheckResult::Skip; } else if (set.flags & MTPDstickerSet_ClientFlag::f_special) { return StickerSetCheckResult::Skip; } else if (!(set.flags & MTPDstickerSet_ClientFlag::f_featured)) { return StickerSetCheckResult::Skip; } else if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { // waiting to receive return StickerSetCheckResult::Abort; } else if (set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write; }, _owner->session().data().stickers().featuredSetsOrder()); } void Account::writeRecentStickers() { writeStickerSets(_recentStickersKey, [](const Data::StickersSet &set) { if (set.id != Data::Stickers::CloudRecentSetId || set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write; }, Data::StickersSetsOrder()); } void Account::writeFavedStickers() { writeStickerSets(_favedStickersKey, [](const Data::StickersSet &set) { if (set.id != Data::Stickers::FavedSetId || set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write; }, Data::StickersSetsOrder()); } void Account::writeArchivedStickers() { writeStickerSets(_archivedStickersKey, [](const Data::StickersSet &set) { if (!(set.flags & MTPDstickerSet::Flag::f_archived) || set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write; }, _owner->session().data().stickers().archivedSetsOrder()); } void Account::importOldRecentStickers() { if (!_recentStickersKeyOld) { return; } FileReadDescriptor stickers; if (!ReadEncryptedFile(stickers, _recentStickersKeyOld, _basePath, _localKey)) { ClearKey(_recentStickersKeyOld, _basePath); _recentStickersKeyOld = 0; writeMapDelayed(); return; } auto &sets = _owner->session().data().stickers().setsRef(); sets.clear(); auto &order = _owner->session().data().stickers().setsOrderRef(); order.clear(); auto &recent = cRefRecentStickers(); recent.clear(); const auto def = sets.emplace( Data::Stickers::DefaultSetId, std::make_unique( &_owner->session().data(), Data::Stickers::DefaultSetId, uint64(0), tr::lng_stickers_default_set(tr::now), QString(), 0, // count 0, // hash (MTPDstickerSet::Flag::f_official | MTPDstickerSet::Flag::f_installed_date | MTPDstickerSet_ClientFlag::f_special), kDefaultStickerInstallDate)).first->second.get(); const auto custom = sets.emplace( Data::Stickers::CustomSetId, std::make_unique( &_owner->session().data(), Data::Stickers::CustomSetId, uint64(0), qsl("Custom stickers"), QString(), 0, // count 0, // hash (MTPDstickerSet::Flag::f_installed_date | MTPDstickerSet_ClientFlag::f_special), kDefaultStickerInstallDate)).first->second.get(); QMap read; while (!stickers.stream.atEnd()) { quint64 id, access; QString name, mime, alt; qint32 date, dc, size, width, height, type; qint16 value; stickers.stream >> id >> value >> access >> date >> name >> mime >> dc >> size >> width >> height >> type; if (stickers.version >= 7021) { stickers.stream >> alt; } if (!value || read.contains(id)) continue; read.insert(id, true); QVector attributes; if (!name.isEmpty()) attributes.push_back(MTP_documentAttributeFilename(MTP_string(name))); if (type == AnimatedDocument) { attributes.push_back(MTP_documentAttributeAnimated()); } else if (type == StickerDocument) { attributes.push_back(MTP_documentAttributeSticker(MTP_flags(0), MTP_string(alt), MTP_inputStickerSetEmpty(), MTPMaskCoords())); } if (width > 0 && height > 0) { attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height))); } const auto doc = _owner->session().data().document( id, access, QByteArray(), date, attributes, mime, QByteArray(), ImageWithLocation(), ImageWithLocation(), dc, size); if (!doc->sticker()) { continue; } if (value > 0) { def->stickers.push_back(doc); ++def->count; } else { custom->stickers.push_back(doc); ++custom->count; } if (qAbs(value) > 1 && (recent.size() < _owner->session().serverConfig().stickersRecentLimit)) { recent.push_back(qMakePair(doc, qAbs(value))); } } if (def->stickers.isEmpty()) { sets.remove(Data::Stickers::DefaultSetId); } else { order.push_front(Data::Stickers::DefaultSetId); } if (custom->stickers.isEmpty()) { sets.remove(Data::Stickers::CustomSetId); } writeInstalledStickers(); writeSessionSettings(); ClearKey(_recentStickersKeyOld, _basePath); _recentStickersKeyOld = 0; writeMapDelayed(); } void Account::readInstalledStickers() { if (!_installedStickersKey) { return importOldRecentStickers(); } _owner->session().data().stickers().setsRef().clear(); readStickerSets( _installedStickersKey, &_owner->session().data().stickers().setsOrderRef(), MTPDstickerSet::Flag::f_installed_date); } void Account::readFeaturedStickers() { readStickerSets( _featuredStickersKey, &_owner->session().data().stickers().featuredSetsOrderRef(), MTPDstickerSet::Flags() | MTPDstickerSet_ClientFlag::f_featured); const auto &sets = _owner->session().data().stickers().sets(); const auto &order = _owner->session().data().stickers().featuredSetsOrder(); int unreadCount = 0; for (const auto setId : order) { auto it = sets.find(setId); if (it != sets.cend() && (it->second->flags & MTPDstickerSet_ClientFlag::f_unread)) { ++unreadCount; } } _owner->session().data().stickers().setFeaturedSetsUnreadCount(unreadCount); } void Account::readRecentStickers() { readStickerSets(_recentStickersKey); } void Account::readFavedStickers() { readStickerSets(_favedStickersKey); } void Account::readArchivedStickers() { static bool archivedStickersRead = false; if (!archivedStickersRead) { readStickerSets(_archivedStickersKey, &_owner->session().data().stickers().archivedSetsOrderRef()); archivedStickersRead = true; } } void Account::writeSavedGifs() { auto &saved = _owner->session().data().stickers().savedGifs(); if (saved.isEmpty()) { if (_savedGifsKey) { ClearKey(_savedGifsKey, _basePath); _savedGifsKey = 0; writeMapDelayed(); } } else { quint32 size = sizeof(quint32); // count for_const (auto gif, saved) { size += Serialize::Document::sizeInStream(gif); } if (!_savedGifsKey) { _savedGifsKey = GenerateKey(_basePath); writeMapQueued(); } EncryptedDescriptor data(size); data.stream << quint32(saved.size()); for_const (auto gif, saved) { Serialize::Document::writeToStream(data.stream, gif); } FileWriteDescriptor file(_savedGifsKey, _basePath); file.writeEncrypted(data, _localKey); } } void Account::readSavedGifs() { if (!_savedGifsKey) return; FileReadDescriptor gifs; if (!ReadEncryptedFile(gifs, _savedGifsKey, _basePath, _localKey)) { ClearKey(_savedGifsKey, _basePath); _savedGifsKey = 0; writeMapDelayed(); return; } auto &saved = _owner->session().data().stickers().savedGifsRef(); const auto failed = [&] { ClearKey(_savedGifsKey, _basePath); _savedGifsKey = 0; saved.clear(); }; saved.clear(); quint32 cnt; gifs.stream >> cnt; saved.reserve(cnt); OrderedSet read; for (uint32 i = 0; i < cnt; ++i) { auto document = Serialize::Document::readFromStream( &_owner->session(), gifs.version, gifs.stream); if (!CheckStreamStatus(gifs.stream)) { return failed(); } else if (!document || !document->isGifv()) { continue; } if (read.contains(document->id)) continue; read.insert(document->id); saved.push_back(document); } } void Account::writeRecentHashtagsAndBots() { const auto &write = cRecentWriteHashtags(); const auto &search = cRecentSearchHashtags(); const auto &bots = cRecentInlineBots(); if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) { readRecentHashtagsAndBots(); } if (write.isEmpty() && search.isEmpty() && bots.isEmpty()) { if (_recentHashtagsAndBotsKey) { ClearKey(_recentHashtagsAndBotsKey, _basePath); _recentHashtagsAndBotsKey = 0; writeMapDelayed(); } return; } if (!_recentHashtagsAndBotsKey) { _recentHashtagsAndBotsKey = GenerateKey(_basePath); writeMapQueued(); } quint32 size = sizeof(quint32) * 3, writeCnt = 0, searchCnt = 0, botsCnt = cRecentInlineBots().size(); for (auto i = write.cbegin(), e = write.cend(); i != e; ++i) { if (!i->first.isEmpty()) { size += Serialize::stringSize(i->first) + sizeof(quint16); ++writeCnt; } } for (auto i = search.cbegin(), e = search.cend(); i != e; ++i) { if (!i->first.isEmpty()) { size += Serialize::stringSize(i->first) + sizeof(quint16); ++searchCnt; } } for (auto i = bots.cbegin(), e = bots.cend(); i != e; ++i) { size += Serialize::peerSize(*i); } EncryptedDescriptor data(size); data.stream << quint32(writeCnt) << quint32(searchCnt); for (auto i = write.cbegin(), e = write.cend(); i != e; ++i) { if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); } for (auto i = search.cbegin(), e = search.cend(); i != e; ++i) { if (!i->first.isEmpty()) data.stream << i->first << quint16(i->second); } data.stream << quint32(botsCnt); for (auto i = bots.cbegin(), e = bots.cend(); i != e; ++i) { Serialize::writePeer(data.stream, *i); } FileWriteDescriptor file(_recentHashtagsAndBotsKey, _basePath); file.writeEncrypted(data, _localKey); } void Account::readRecentHashtagsAndBots() { if (_recentHashtagsAndBotsWereRead) return; _recentHashtagsAndBotsWereRead = true; if (!_recentHashtagsAndBotsKey) return; FileReadDescriptor hashtags; if (!ReadEncryptedFile(hashtags, _recentHashtagsAndBotsKey, _basePath, _localKey)) { ClearKey(_recentHashtagsAndBotsKey, _basePath); _recentHashtagsAndBotsKey = 0; writeMapDelayed(); return; } quint32 writeCount = 0, searchCount = 0, botsCount = 0; hashtags.stream >> writeCount >> searchCount; QString tag; quint16 count; RecentHashtagPack write, search; RecentInlineBots bots; if (writeCount) { write.reserve(writeCount); for (uint32 i = 0; i < writeCount; ++i) { hashtags.stream >> tag >> count; write.push_back(qMakePair(tag.trimmed(), count)); } } if (searchCount) { search.reserve(searchCount); for (uint32 i = 0; i < searchCount; ++i) { hashtags.stream >> tag >> count; search.push_back(qMakePair(tag.trimmed(), count)); } } cSetRecentWriteHashtags(write); cSetRecentSearchHashtags(search); if (!hashtags.stream.atEnd()) { hashtags.stream >> botsCount; if (botsCount) { bots.reserve(botsCount); for (auto i = 0; i < botsCount; ++i) { const auto peer = Serialize::readPeer( &_owner->session(), hashtags.version, hashtags.stream); if (!peer) { return; // Broken data. } else if (peer->isUser() && peer->asUser()->isBot() && !peer->asUser()->botInfo->inlinePlaceholder.isEmpty() && !peer->asUser()->username.isEmpty()) { bots.push_back(peer->asUser()); } } } cSetRecentInlineBots(bots); } } std::optional Account::saveRecentHashtags( Fn getPack, const QString &text) { auto found = false; auto m = QRegularExpressionMatch(); auto recent = getPack(); for (auto i = 0, next = 0; (m = TextUtilities::RegExpHashtag().match(text, i)).hasMatch(); i = next) { i = m.capturedStart(); next = m.capturedEnd(); if (m.hasMatch()) { if (!m.capturedRef(1).isEmpty()) { ++i; } if (!m.capturedRef(2).isEmpty()) { --next; } } const auto tag = text.mid(i + 1, next - i - 1); if (TextUtilities::RegExpHashtagExclude().match(tag).hasMatch()) { continue; } if (!found && cRecentWriteHashtags().isEmpty() && cRecentSearchHashtags().isEmpty()) { readRecentHashtagsAndBots(); recent = getPack(); } found = true; Local::incrementRecentHashtag(recent, tag); } return found ? base::make_optional(recent) : std::nullopt; } void Account::saveRecentSentHashtags(const QString &text) { const auto result = saveRecentHashtags( [] { return cRecentWriteHashtags(); }, text); if (result) { cSetRecentWriteHashtags(*result); writeRecentHashtagsAndBots(); } } void Account::saveRecentSearchHashtags(const QString &text) { const auto result = saveRecentHashtags( [] { return cRecentSearchHashtags(); }, text); if (result) { cSetRecentSearchHashtags(*result); writeRecentHashtagsAndBots(); } } void Account::writeExportSettings(const Export::Settings &settings) { const auto check = Export::Settings(); if (settings.types == check.types && settings.fullChats == check.fullChats && settings.media.types == check.media.types && settings.media.sizeLimit == check.media.sizeLimit && settings.path == check.path && settings.format == check.format && settings.availableAt == check.availableAt && !settings.onlySinglePeer()) { if (_exportSettingsKey) { ClearKey(_exportSettingsKey, _basePath); _exportSettingsKey = 0; writeMapDelayed(); } return; } if (!_exportSettingsKey) { _exportSettingsKey = GenerateKey(_basePath); writeMapQueued(); } quint32 size = sizeof(quint32) * 6 + Serialize::stringSize(settings.path) + sizeof(qint32) * 2 + sizeof(quint64); EncryptedDescriptor data(size); data.stream << quint32(settings.types) << quint32(settings.fullChats) << quint32(settings.media.types) << quint32(settings.media.sizeLimit) << quint32(settings.format) << settings.path << quint32(settings.availableAt); settings.singlePeer.match([&](const MTPDinputPeerUser & user) { data.stream << kSinglePeerTypeUser << qint32(user.vuser_id().v) << quint64(user.vaccess_hash().v); }, [&](const MTPDinputPeerChat & chat) { data.stream << kSinglePeerTypeChat << qint32(chat.vchat_id().v); }, [&](const MTPDinputPeerChannel & channel) { data.stream << kSinglePeerTypeChannel << qint32(channel.vchannel_id().v) << quint64(channel.vaccess_hash().v); }, [&](const MTPDinputPeerSelf &) { data.stream << kSinglePeerTypeSelf; }, [&](const MTPDinputPeerEmpty &) { data.stream << kSinglePeerTypeEmpty; }, [&](const MTPDinputPeerUserFromMessage &) { Unexpected("From message peer in single peer export settings."); }, [&](const MTPDinputPeerChannelFromMessage &) { Unexpected("From message peer in single peer export settings."); }); data.stream << qint32(settings.singlePeerFrom); data.stream << qint32(settings.singlePeerTill); FileWriteDescriptor file(_exportSettingsKey, _basePath); file.writeEncrypted(data, _localKey); } Export::Settings Account::readExportSettings() { FileReadDescriptor file; if (!ReadEncryptedFile(file, _exportSettingsKey, _basePath, _localKey)) { ClearKey(_exportSettingsKey, _basePath); _exportSettingsKey = 0; writeMapDelayed(); return Export::Settings(); } quint32 types = 0, fullChats = 0; quint32 mediaTypes = 0, mediaSizeLimit = 0; quint32 format = 0, availableAt = 0; QString path; qint32 singlePeerType = 0, singlePeerBareId = 0; quint64 singlePeerAccessHash = 0; qint32 singlePeerFrom = 0, singlePeerTill = 0; file.stream >> types >> fullChats >> mediaTypes >> mediaSizeLimit >> format >> path >> availableAt; if (!file.stream.atEnd()) { file.stream >> singlePeerType; switch (singlePeerType) { case kSinglePeerTypeUser: case kSinglePeerTypeChannel: { file.stream >> singlePeerBareId >> singlePeerAccessHash; } break; case kSinglePeerTypeChat: file.stream >> singlePeerBareId; break; case kSinglePeerTypeSelf: case kSinglePeerTypeEmpty: break; default: return Export::Settings(); } } if (!file.stream.atEnd()) { file.stream >> singlePeerFrom >> singlePeerTill; } auto result = Export::Settings(); result.types = Export::Settings::Types::from_raw(types); result.fullChats = Export::Settings::Types::from_raw(fullChats); result.media.types = Export::MediaSettings::Types::from_raw(mediaTypes); result.media.sizeLimit = mediaSizeLimit; result.format = Export::Output::Format(format); result.path = path; result.availableAt = availableAt; result.singlePeer = [&] { switch (singlePeerType) { case kSinglePeerTypeUser: return MTP_inputPeerUser( MTP_int(singlePeerBareId), MTP_long(singlePeerAccessHash)); case kSinglePeerTypeChat: return MTP_inputPeerChat(MTP_int(singlePeerBareId)); case kSinglePeerTypeChannel: return MTP_inputPeerChannel( MTP_int(singlePeerBareId), MTP_long(singlePeerAccessHash)); case kSinglePeerTypeSelf: return MTP_inputPeerSelf(); case kSinglePeerTypeEmpty: return MTP_inputPeerEmpty(); } Unexpected("Type in export data single peer."); }(); result.singlePeerFrom = singlePeerFrom; result.singlePeerTill = singlePeerTill; return (file.stream.status() == QDataStream::Ok && result.validate()) ? result : Export::Settings(); } void Account::writeSelf() { writeMapDelayed(); } void Account::readSelf( not_null session, const QByteArray &serialized, int32 streamVersion) { QDataStream stream(serialized); const auto user = session->user(); const auto wasLoadedStatus = user->loadedStatus(); user->setLoadedStatus(PeerData::LoadedStatus::Not); const auto self = Serialize::readPeer( session, streamVersion, stream); if (!self || !self->isSelf() || self != user) { user->setLoadedStatus(wasLoadedStatus); return; } QString about; stream >> about; if (CheckStreamStatus(stream)) { self->asUser()->setAbout(about); } } void Account::writeTrustedBots() { if (_trustedBots.empty()) { if (_trustedBotsKey) { ClearKey(_trustedBotsKey, _basePath); _trustedBotsKey = 0; writeMapDelayed(); } return; } if (!_trustedBotsKey) { _trustedBotsKey = GenerateKey(_basePath); writeMapQueued(); } quint32 size = sizeof(qint32) + _trustedBots.size() * sizeof(quint64); EncryptedDescriptor data(size); data.stream << qint32(_trustedBots.size()); for_const (auto botId, _trustedBots) { data.stream << quint64(botId); } FileWriteDescriptor file(_trustedBotsKey, _basePath); file.writeEncrypted(data, _localKey); } void Account::readTrustedBots() { if (!_trustedBotsKey) return; FileReadDescriptor trusted; if (!ReadEncryptedFile(trusted, _trustedBotsKey, _basePath, _localKey)) { ClearKey(_trustedBotsKey, _basePath); _trustedBotsKey = 0; writeMapDelayed(); return; } qint32 size = 0; trusted.stream >> size; for (int i = 0; i < size; ++i) { quint64 botId = 0; trusted.stream >> botId; _trustedBots.insert(botId); } } void Account::markBotTrusted(not_null bot) { if (!isBotTrusted(bot)) { _trustedBots.insert(bot->id); writeTrustedBots(); } } bool Account::isBotTrusted(not_null bot) { if (!_trustedBotsRead) { readTrustedBots(); _trustedBotsRead = true; } return _trustedBots.contains(bot->id); } bool Account::encrypt( const void *src, void *dst, uint32 len, const void *key128) const { if (!_localKey) { return false; } MTP::aesEncryptLocal(src, dst, len, _localKey, key128); return true; } bool Account::decrypt( const void *src, void *dst, uint32 len, const void *key128) const { if (!_localKey) { return false; } MTP::aesDecryptLocal(src, dst, len, _localKey, key128); return true; } } // namespace Storage