init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
677
Telegram/SourceFiles/storage/details/storage_file_utilities.cpp
Normal file
677
Telegram/SourceFiles/storage/details/storage_file_utilities.cpp
Normal file
@@ -0,0 +1,677 @@
|
||||
/*
|
||||
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/details/storage_file_utilities.h"
|
||||
|
||||
#include "mtproto/mtproto_auth_key.h"
|
||||
#include "base/platform/base_platform_file_utilities.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
|
||||
#include <crl/crl_object_on_thread.h>
|
||||
#include <QtCore/QtEndian>
|
||||
#include <QtCore/QSaveFile>
|
||||
|
||||
namespace Storage {
|
||||
namespace details {
|
||||
namespace {
|
||||
|
||||
constexpr char TdfMagic[] = { 'T', 'D', 'F', '$' };
|
||||
constexpr auto TdfMagicLen = int(sizeof(TdfMagic));
|
||||
|
||||
constexpr auto kStrongIterationsCount = 100'000;
|
||||
|
||||
struct WriteEntry {
|
||||
QString basePath;
|
||||
QString base;
|
||||
QByteArray data;
|
||||
QByteArray md5;
|
||||
};
|
||||
|
||||
class WriteManager final {
|
||||
public:
|
||||
explicit WriteManager(crl::weak_on_thread<WriteManager> weak);
|
||||
|
||||
void write(WriteEntry &&entry);
|
||||
void writeSync(WriteEntry &&entry);
|
||||
void writeSyncAll();
|
||||
|
||||
private:
|
||||
void scheduleWrite();
|
||||
void writeScheduled();
|
||||
bool writeOneScheduledNow();
|
||||
void writeNow(WriteEntry &&entry);
|
||||
|
||||
template <typename File>
|
||||
[[nodiscard]] bool open(File &file, const WriteEntry &entry, char postfix);
|
||||
|
||||
[[nodiscard]] QString path(const WriteEntry &entry, char postfix) const;
|
||||
[[nodiscard]] bool writeHeader(
|
||||
const QString &basePath,
|
||||
QFileDevice &file);
|
||||
|
||||
crl::weak_on_thread<WriteManager> _weak;
|
||||
std::deque<WriteEntry> _scheduled;
|
||||
|
||||
};
|
||||
|
||||
class AsyncWriteManager final {
|
||||
public:
|
||||
void write(WriteEntry &&entry);
|
||||
void writeSync(WriteEntry &&entry);
|
||||
void sync();
|
||||
void stop();
|
||||
|
||||
private:
|
||||
std::optional<crl::object_on_thread<WriteManager>> _manager;
|
||||
bool _finished = false;
|
||||
|
||||
};
|
||||
|
||||
WriteManager::WriteManager(crl::weak_on_thread<WriteManager> weak)
|
||||
: _weak(std::move(weak)) {
|
||||
}
|
||||
|
||||
void WriteManager::write(WriteEntry &&entry) {
|
||||
const auto i = ranges::find(_scheduled, entry.base, &WriteEntry::base);
|
||||
if (i == end(_scheduled)) {
|
||||
_scheduled.push_back(std::move(entry));
|
||||
} else {
|
||||
*i = std::move(entry);
|
||||
}
|
||||
scheduleWrite();
|
||||
}
|
||||
|
||||
void WriteManager::writeSync(WriteEntry &&entry) {
|
||||
const auto i = ranges::find(_scheduled, entry.base, &WriteEntry::base);
|
||||
if (i != end(_scheduled)) {
|
||||
_scheduled.erase(i);
|
||||
}
|
||||
writeNow(std::move(entry));
|
||||
}
|
||||
|
||||
void WriteManager::writeNow(WriteEntry &&entry) {
|
||||
const auto path = [&](char postfix) {
|
||||
return this->path(entry, postfix);
|
||||
};
|
||||
const auto open = [&](auto &file, char postfix) {
|
||||
return this->open(file, entry, postfix);
|
||||
};
|
||||
const auto write = [&](auto &file) {
|
||||
file.write(entry.data);
|
||||
file.write(entry.md5);
|
||||
};
|
||||
const auto safe = path('s');
|
||||
const auto simple = path('0');
|
||||
const auto backup = path('1');
|
||||
QSaveFile save;
|
||||
if (open(save, 's')) {
|
||||
write(save);
|
||||
if (save.commit()) {
|
||||
QFile::remove(simple);
|
||||
QFile::remove(backup);
|
||||
return;
|
||||
}
|
||||
LOG(("Storage Error: Could not commit '%1'.").arg(safe));
|
||||
}
|
||||
QFile plain;
|
||||
if (open(plain, '0')) {
|
||||
write(plain);
|
||||
base::Platform::FlushFileData(plain);
|
||||
plain.close();
|
||||
|
||||
QFile::remove(backup);
|
||||
if (base::Platform::RenameWithOverwrite(simple, safe)) {
|
||||
return;
|
||||
}
|
||||
QFile::remove(safe);
|
||||
LOG(("Storage Error: Could not rename '%1' to '%2', removing.").arg(
|
||||
simple,
|
||||
safe));
|
||||
}
|
||||
}
|
||||
|
||||
void WriteManager::writeSyncAll() {
|
||||
while (writeOneScheduledNow()) {
|
||||
}
|
||||
}
|
||||
|
||||
bool WriteManager::writeOneScheduledNow() {
|
||||
if (_scheduled.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto entry = std::move(_scheduled.front());
|
||||
_scheduled.pop_front();
|
||||
|
||||
writeNow(std::move(entry));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteManager::writeHeader(const QString &basePath, QFileDevice &file) {
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
const auto dir = QDir(basePath);
|
||||
if (dir.exists()) {
|
||||
return false;
|
||||
} else if (!QDir().mkpath(dir.absolutePath())) {
|
||||
return false;
|
||||
} else if (!file.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
file.write(TdfMagic, TdfMagicLen);
|
||||
const auto version = qint32(AppVersion);
|
||||
file.write((const char*)&version, sizeof(version));
|
||||
return true;
|
||||
}
|
||||
|
||||
QString WriteManager::path(const WriteEntry &entry, char postfix) const {
|
||||
return entry.base + postfix;
|
||||
}
|
||||
|
||||
template <typename File>
|
||||
bool WriteManager::open(File &file, const WriteEntry &entry, char postfix) {
|
||||
const auto name = path(entry, postfix);
|
||||
file.setFileName(name);
|
||||
if (!writeHeader(entry.basePath, file)) {
|
||||
LOG(("Storage Error: Could not open '%1' for writing.").arg(name));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WriteManager::scheduleWrite() {
|
||||
_weak.with([](WriteManager &that) {
|
||||
that.writeScheduled();
|
||||
});
|
||||
}
|
||||
|
||||
void WriteManager::writeScheduled() {
|
||||
if (writeOneScheduledNow() && !_scheduled.empty()) {
|
||||
scheduleWrite();
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncWriteManager::write(WriteEntry &&entry) {
|
||||
Expects(!_finished);
|
||||
|
||||
if (!_manager) {
|
||||
_manager.emplace();
|
||||
}
|
||||
_manager->with([entry = std::move(entry)](WriteManager &manager) mutable {
|
||||
manager.write(std::move(entry));
|
||||
});
|
||||
}
|
||||
|
||||
void AsyncWriteManager::writeSync(WriteEntry &&entry) {
|
||||
Expects(!_finished);
|
||||
|
||||
if (!_manager) {
|
||||
_manager.emplace();
|
||||
}
|
||||
_manager->with_sync([&](WriteManager &manager) {
|
||||
manager.writeSync(std::move(entry));
|
||||
});
|
||||
}
|
||||
|
||||
void AsyncWriteManager::sync() {
|
||||
if (_manager) {
|
||||
_manager->with_sync([](WriteManager &manager) {
|
||||
manager.writeSyncAll();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncWriteManager::stop() {
|
||||
if (_manager) {
|
||||
sync();
|
||||
_manager.reset();
|
||||
}
|
||||
_finished = true;
|
||||
}
|
||||
|
||||
AsyncWriteManager Manager;
|
||||
|
||||
} // namespace
|
||||
|
||||
QString ToFilePart(FileKey val) {
|
||||
QString result;
|
||||
result.reserve(0x10);
|
||||
for (int32 i = 0; i < 0x10; ++i) {
|
||||
uchar v = (val & 0x0F);
|
||||
result.push_back((v < 0x0A) ? QChar('0' + v) : QChar('A' + (v - 0x0A)));
|
||||
val >>= 4;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool KeyAlreadyUsed(QString &name) {
|
||||
name += '0';
|
||||
if (QFileInfo::exists(name)) {
|
||||
return true;
|
||||
}
|
||||
name[name.size() - 1] = '1';
|
||||
if (QFileInfo::exists(name)) {
|
||||
return true;
|
||||
}
|
||||
name[name.size() - 1] = 's';
|
||||
if (QFileInfo::exists(name)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FileKey GenerateKey(const QString &basePath) {
|
||||
FileKey result;
|
||||
QString path;
|
||||
path.reserve(basePath.size() + 0x11);
|
||||
path += basePath;
|
||||
do {
|
||||
result = base::RandomValue<FileKey>();
|
||||
path.resize(basePath.size());
|
||||
path += ToFilePart(result);
|
||||
} while (!result || KeyAlreadyUsed(path));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ClearKey(const FileKey &key, const QString &basePath) {
|
||||
QString name;
|
||||
name.reserve(basePath.size() + 0x11);
|
||||
name.append(basePath).append(ToFilePart(key)).append('0');
|
||||
QFile::remove(name);
|
||||
name[name.size() - 1] = '1';
|
||||
QFile::remove(name);
|
||||
name[name.size() - 1] = 's';
|
||||
QFile::remove(name);
|
||||
}
|
||||
|
||||
bool CheckStreamStatus(QDataStream &stream) {
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("Bad data stream status: %1").arg(stream.status()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MTP::AuthKeyPtr CreateLocalKey(
|
||||
const QByteArray &passcode,
|
||||
const QByteArray &salt) {
|
||||
const auto s = bytes::make_span(salt);
|
||||
const auto hash = openssl::Sha512(s, bytes::make_span(passcode), s);
|
||||
const auto iterationsCount = passcode.isEmpty()
|
||||
? 1 // Don't slow down for no password.
|
||||
: kStrongIterationsCount;
|
||||
|
||||
auto key = MTP::AuthKey::Data{ { gsl::byte{} } };
|
||||
PKCS5_PBKDF2_HMAC(
|
||||
reinterpret_cast<const char*>(hash.data()),
|
||||
hash.size(),
|
||||
reinterpret_cast<const unsigned char*>(s.data()),
|
||||
s.size(),
|
||||
iterationsCount,
|
||||
EVP_sha512(),
|
||||
key.size(),
|
||||
reinterpret_cast<unsigned char*>(key.data()));
|
||||
return std::make_shared<MTP::AuthKey>(key);
|
||||
}
|
||||
|
||||
MTP::AuthKeyPtr CreateLegacyLocalKey(
|
||||
const QByteArray &passcode,
|
||||
const QByteArray &salt) {
|
||||
auto key = MTP::AuthKey::Data{ { gsl::byte{} } };
|
||||
const auto iterationsCount = passcode.isEmpty()
|
||||
? LocalEncryptNoPwdIterCount // Don't slow down for no password.
|
||||
: LocalEncryptIterCount;
|
||||
|
||||
PKCS5_PBKDF2_HMAC_SHA1(
|
||||
passcode.constData(),
|
||||
passcode.size(),
|
||||
(uchar*)salt.data(),
|
||||
salt.size(),
|
||||
iterationsCount,
|
||||
key.size(),
|
||||
(uchar*)key.data());
|
||||
|
||||
return std::make_shared<MTP::AuthKey>(key);
|
||||
}
|
||||
|
||||
FileReadDescriptor::~FileReadDescriptor() {
|
||||
if (version) {
|
||||
stream.setDevice(nullptr);
|
||||
if (buffer.isOpen()) {
|
||||
buffer.close();
|
||||
}
|
||||
buffer.setBuffer(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
EncryptedDescriptor::EncryptedDescriptor() {
|
||||
}
|
||||
|
||||
EncryptedDescriptor::EncryptedDescriptor(uint32 size) {
|
||||
uint32 fullSize = sizeof(uint32) + size;
|
||||
if (fullSize & 0x0F) fullSize += 0x10 - (fullSize & 0x0F);
|
||||
data.reserve(fullSize);
|
||||
|
||||
data.resize(sizeof(uint32));
|
||||
buffer.setBuffer(&data);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
buffer.seek(sizeof(uint32));
|
||||
stream.setDevice(&buffer);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
}
|
||||
|
||||
EncryptedDescriptor::~EncryptedDescriptor() {
|
||||
finish();
|
||||
}
|
||||
|
||||
void EncryptedDescriptor::finish() {
|
||||
if (stream.device()) stream.setDevice(nullptr);
|
||||
if (buffer.isOpen()) buffer.close();
|
||||
buffer.setBuffer(nullptr);
|
||||
}
|
||||
|
||||
FileWriteDescriptor::FileWriteDescriptor(
|
||||
const FileKey &key,
|
||||
const QString &basePath,
|
||||
bool sync)
|
||||
: FileWriteDescriptor(ToFilePart(key), basePath, sync) {
|
||||
}
|
||||
|
||||
FileWriteDescriptor::FileWriteDescriptor(
|
||||
const QString &name,
|
||||
const QString &basePath,
|
||||
bool sync)
|
||||
: _basePath(basePath)
|
||||
, _sync(sync) {
|
||||
init(name);
|
||||
}
|
||||
|
||||
FileWriteDescriptor::~FileWriteDescriptor() {
|
||||
finish();
|
||||
}
|
||||
|
||||
void FileWriteDescriptor::init(const QString &name) {
|
||||
_base = _basePath + name;
|
||||
_buffer.setBuffer(&_safeData);
|
||||
const auto opened = _buffer.open(QIODevice::WriteOnly);
|
||||
Assert(opened);
|
||||
_stream.setDevice(&_buffer);
|
||||
}
|
||||
|
||||
void FileWriteDescriptor::writeData(const QByteArray &data) {
|
||||
if (!_stream.device()) {
|
||||
return;
|
||||
}
|
||||
_stream << data;
|
||||
quint32 len = data.isNull() ? 0xffffffff : data.size();
|
||||
if (QSysInfo::ByteOrder != QSysInfo::BigEndian) {
|
||||
len = qbswap(len);
|
||||
}
|
||||
_md5.feed(&len, sizeof(len));
|
||||
_md5.feed(data.constData(), data.size());
|
||||
_fullSize += sizeof(len) + data.size();
|
||||
}
|
||||
|
||||
void FileWriteDescriptor::writeEncrypted(
|
||||
EncryptedDescriptor &data,
|
||||
const MTP::AuthKeyPtr &key) {
|
||||
writeData(PrepareEncrypted(data, key));
|
||||
}
|
||||
|
||||
void FileWriteDescriptor::finish() {
|
||||
if (!_stream.device()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_stream.setDevice(nullptr);
|
||||
_md5.feed(&_fullSize, sizeof(_fullSize));
|
||||
qint32 version = AppVersion;
|
||||
_md5.feed(&version, sizeof(version));
|
||||
_md5.feed(TdfMagic, TdfMagicLen);
|
||||
|
||||
_buffer.close();
|
||||
|
||||
auto entry = WriteEntry{
|
||||
.basePath = _basePath,
|
||||
.base = _base,
|
||||
.data = _safeData,
|
||||
.md5 = QByteArray((const char*)_md5.result(), 0x10)
|
||||
};
|
||||
if (_sync) {
|
||||
Manager.writeSync(std::move(entry));
|
||||
} else {
|
||||
Manager.write(std::move(entry));
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] QByteArray PrepareEncrypted(
|
||||
EncryptedDescriptor &data,
|
||||
const MTP::AuthKeyPtr &key) {
|
||||
data.finish();
|
||||
QByteArray &toEncrypt(data.data);
|
||||
|
||||
// prepare for encryption
|
||||
uint32 size = toEncrypt.size(), fullSize = size;
|
||||
if (fullSize & 0x0F) {
|
||||
fullSize += 0x10 - (fullSize & 0x0F);
|
||||
toEncrypt.resize(fullSize);
|
||||
base::RandomFill(toEncrypt.data() + size, fullSize - size);
|
||||
}
|
||||
*(uint32*)toEncrypt.data() = size;
|
||||
QByteArray encrypted(0x10 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data
|
||||
hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data());
|
||||
MTP::aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 0x10, fullSize, key, encrypted.constData());
|
||||
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
bool ReadFile(
|
||||
FileReadDescriptor &result,
|
||||
const QString &name,
|
||||
const QString &basePath) {
|
||||
const auto base = basePath + name;
|
||||
|
||||
// detect order of read attempts
|
||||
QString toTry[2];
|
||||
const auto modern = base + 's';
|
||||
if (QFileInfo::exists(modern)) {
|
||||
toTry[0] = modern;
|
||||
} else {
|
||||
// Legacy way.
|
||||
toTry[0] = base + '0';
|
||||
QFileInfo toTry0(toTry[0]);
|
||||
if (toTry0.exists()) {
|
||||
toTry[1] = basePath + name + '1';
|
||||
QFileInfo toTry1(toTry[1]);
|
||||
if (toTry1.exists()) {
|
||||
QDateTime mod0 = toTry0.lastModified();
|
||||
QDateTime mod1 = toTry1.lastModified();
|
||||
if (mod0 < mod1) {
|
||||
qSwap(toTry[0], toTry[1]);
|
||||
}
|
||||
} else {
|
||||
toTry[1] = QString();
|
||||
}
|
||||
} else {
|
||||
toTry[0][toTry[0].size() - 1] = '1';
|
||||
}
|
||||
}
|
||||
for (int32 i = 0; i < 2; ++i) {
|
||||
QString fname(toTry[i]);
|
||||
if (fname.isEmpty()) break;
|
||||
|
||||
QFile f(fname);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
DEBUG_LOG(("App Info: failed to open '%1' for reading"
|
||||
).arg(name));
|
||||
continue;
|
||||
}
|
||||
|
||||
// check magic
|
||||
char magic[TdfMagicLen];
|
||||
if (f.read(magic, TdfMagicLen) != TdfMagicLen) {
|
||||
DEBUG_LOG(("App Info: failed to read magic from '%1'"
|
||||
).arg(name));
|
||||
continue;
|
||||
}
|
||||
if (memcmp(magic, TdfMagic, TdfMagicLen)) {
|
||||
DEBUG_LOG(("App Info: bad magic %1 in '%2'").arg(
|
||||
Logs::mb(magic, TdfMagicLen).str(),
|
||||
name));
|
||||
continue;
|
||||
}
|
||||
|
||||
// read app version
|
||||
qint32 version;
|
||||
if (f.read((char*)&version, sizeof(version)) != sizeof(version)) {
|
||||
DEBUG_LOG(("App Info: failed to read version from '%1'"
|
||||
).arg(name));
|
||||
continue;
|
||||
}
|
||||
if (version > AppVersion) {
|
||||
DEBUG_LOG(("App Info: version too big %1 for '%2', my version %3"
|
||||
).arg(version
|
||||
).arg(name
|
||||
).arg(AppVersion));
|
||||
continue;
|
||||
}
|
||||
|
||||
// read data
|
||||
QByteArray bytes = f.read(f.size());
|
||||
int32 dataSize = bytes.size() - 16;
|
||||
if (dataSize < 0) {
|
||||
DEBUG_LOG(("App Info: bad file '%1', could not read sign part"
|
||||
).arg(name));
|
||||
continue;
|
||||
}
|
||||
|
||||
// check signature
|
||||
HashMd5 md5;
|
||||
md5.feed(bytes.constData(), dataSize);
|
||||
md5.feed(&dataSize, sizeof(dataSize));
|
||||
md5.feed(&version, sizeof(version));
|
||||
md5.feed(magic, TdfMagicLen);
|
||||
if (memcmp(md5.result(), bytes.constData() + dataSize, 16)) {
|
||||
DEBUG_LOG(("App Info: bad file '%1', signature did not match"
|
||||
).arg(name));
|
||||
continue;
|
||||
}
|
||||
|
||||
bytes.resize(dataSize);
|
||||
result.data = bytes;
|
||||
bytes = QByteArray();
|
||||
|
||||
result.version = version;
|
||||
result.buffer.setBuffer(&result.data);
|
||||
result.buffer.open(QIODevice::ReadOnly);
|
||||
result.stream.setDevice(&result.buffer);
|
||||
result.stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
if ((i == 0 && !toTry[1].isEmpty()) || i == 1) {
|
||||
QFile::remove(toTry[1 - i]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DecryptLocal(
|
||||
EncryptedDescriptor &result,
|
||||
const QByteArray &encrypted,
|
||||
const MTP::AuthKeyPtr &key) {
|
||||
if (encrypted.size() <= 16 || (encrypted.size() & 0x0F)) {
|
||||
LOG(("App Error: bad encrypted part size: %1").arg(encrypted.size()));
|
||||
return false;
|
||||
}
|
||||
uint32 fullLen = encrypted.size() - 16;
|
||||
|
||||
QByteArray decrypted;
|
||||
decrypted.resize(fullLen);
|
||||
const char *encryptedKey = encrypted.constData(), *encryptedData = encrypted.constData() + 16;
|
||||
aesDecryptLocal(encryptedData, decrypted.data(), fullLen, key, encryptedKey);
|
||||
uchar sha1Buffer[20];
|
||||
if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), encryptedKey, 16)) {
|
||||
LOG(("App Info: bad decrypt key, data not decrypted - incorrect password?"));
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 dataLen = *(const uint32*)decrypted.constData();
|
||||
if (dataLen > uint32(decrypted.size()) || dataLen <= fullLen - 16 || dataLen < sizeof(uint32)) {
|
||||
LOG(("App Error: bad decrypted part size: %1, fullLen: %2, decrypted size: %3").arg(dataLen).arg(fullLen).arg(decrypted.size()));
|
||||
return false;
|
||||
}
|
||||
|
||||
decrypted.resize(dataLen);
|
||||
result.data = decrypted;
|
||||
decrypted = QByteArray();
|
||||
|
||||
result.buffer.setBuffer(&result.data);
|
||||
result.buffer.open(QIODevice::ReadOnly);
|
||||
result.buffer.seek(sizeof(uint32)); // skip len
|
||||
result.stream.setDevice(&result.buffer);
|
||||
result.stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadEncryptedFile(
|
||||
FileReadDescriptor &result,
|
||||
const QString &name,
|
||||
const QString &basePath,
|
||||
const MTP::AuthKeyPtr &key) {
|
||||
if (!ReadFile(result, name, basePath)) {
|
||||
return false;
|
||||
}
|
||||
QByteArray encrypted;
|
||||
result.stream >> encrypted;
|
||||
|
||||
EncryptedDescriptor data;
|
||||
if (!DecryptLocal(data, encrypted, key)) {
|
||||
result.stream.setDevice(nullptr);
|
||||
if (result.buffer.isOpen()) result.buffer.close();
|
||||
result.buffer.setBuffer(nullptr);
|
||||
result.data = QByteArray();
|
||||
result.version = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
result.stream.setDevice(0);
|
||||
if (result.buffer.isOpen()) {
|
||||
result.buffer.close();
|
||||
}
|
||||
result.buffer.setBuffer(0);
|
||||
result.data = data.data;
|
||||
result.buffer.setBuffer(&result.data);
|
||||
result.buffer.open(QIODevice::ReadOnly);
|
||||
result.buffer.seek(data.buffer.pos());
|
||||
result.stream.setDevice(&result.buffer);
|
||||
result.stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadEncryptedFile(
|
||||
FileReadDescriptor &result,
|
||||
const FileKey &fkey,
|
||||
const QString &basePath,
|
||||
const MTP::AuthKeyPtr &key) {
|
||||
return ReadEncryptedFile(result, ToFilePart(fkey), basePath, key);
|
||||
}
|
||||
|
||||
void Sync() {
|
||||
Manager.sync();
|
||||
}
|
||||
|
||||
void Finish() {
|
||||
Manager.stop();
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
} // namespace Storage
|
||||
113
Telegram/SourceFiles/storage/details/storage_file_utilities.h
Normal file
113
Telegram/SourceFiles/storage/details/storage_file_utilities.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "storage/storage_account.h"
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
|
||||
namespace Storage {
|
||||
namespace details {
|
||||
|
||||
[[nodiscard]] QString ToFilePart(FileKey val);
|
||||
[[nodiscard]] bool KeyAlreadyUsed(QString &name);
|
||||
[[nodiscard]] FileKey GenerateKey(const QString &basePath);
|
||||
void ClearKey(const FileKey &key, const QString &basePath);
|
||||
|
||||
[[nodiscard]] bool CheckStreamStatus(QDataStream &stream);
|
||||
[[nodiscard]] MTP::AuthKeyPtr CreateLocalKey(
|
||||
const QByteArray &passcode,
|
||||
const QByteArray &salt);
|
||||
[[nodiscard]] MTP::AuthKeyPtr CreateLegacyLocalKey(
|
||||
const QByteArray &passcode,
|
||||
const QByteArray &salt);
|
||||
|
||||
struct FileReadDescriptor final {
|
||||
~FileReadDescriptor();
|
||||
|
||||
int32 version = 0;
|
||||
QByteArray data;
|
||||
QBuffer buffer;
|
||||
QDataStream stream;
|
||||
};
|
||||
|
||||
struct EncryptedDescriptor final {
|
||||
EncryptedDescriptor();
|
||||
explicit EncryptedDescriptor(uint32 size);
|
||||
~EncryptedDescriptor();
|
||||
|
||||
void finish();
|
||||
|
||||
QByteArray data;
|
||||
QBuffer buffer;
|
||||
QDataStream stream;
|
||||
};
|
||||
|
||||
[[nodiscard]] QByteArray PrepareEncrypted(
|
||||
EncryptedDescriptor &data,
|
||||
const MTP::AuthKeyPtr &key);
|
||||
|
||||
class FileWriteDescriptor final {
|
||||
public:
|
||||
FileWriteDescriptor(
|
||||
const FileKey &key,
|
||||
const QString &basePath,
|
||||
bool sync = false);
|
||||
FileWriteDescriptor(
|
||||
const QString &name,
|
||||
const QString &basePath,
|
||||
bool sync = false);
|
||||
~FileWriteDescriptor();
|
||||
|
||||
void writeData(const QByteArray &data);
|
||||
void writeEncrypted(
|
||||
EncryptedDescriptor &data,
|
||||
const MTP::AuthKeyPtr &key);
|
||||
|
||||
private:
|
||||
void init(const QString &name);
|
||||
void finish();
|
||||
|
||||
const QString _basePath;
|
||||
QBuffer _buffer;
|
||||
QDataStream _stream;
|
||||
QByteArray _safeData;
|
||||
QString _base;
|
||||
HashMd5 _md5;
|
||||
int _fullSize = 0;
|
||||
bool _sync = false;
|
||||
|
||||
};
|
||||
|
||||
bool ReadFile(
|
||||
FileReadDescriptor &result,
|
||||
const QString &name,
|
||||
const QString &basePath);
|
||||
|
||||
bool DecryptLocal(
|
||||
EncryptedDescriptor &result,
|
||||
const QByteArray &encrypted,
|
||||
const MTP::AuthKeyPtr &key);
|
||||
|
||||
bool ReadEncryptedFile(
|
||||
FileReadDescriptor &result,
|
||||
const QString &name,
|
||||
const QString &basePath,
|
||||
const MTP::AuthKeyPtr &key);
|
||||
|
||||
bool ReadEncryptedFile(
|
||||
FileReadDescriptor &result,
|
||||
const FileKey &fkey,
|
||||
const QString &basePath,
|
||||
const MTP::AuthKeyPtr &key);
|
||||
|
||||
void Sync();
|
||||
void Finish();
|
||||
|
||||
} // namespace details
|
||||
} // namespace Storage
|
||||
1201
Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp
Normal file
1201
Telegram/SourceFiles/storage/details/storage_settings_scheme.cpp
Normal file
File diff suppressed because it is too large
Load Diff
178
Telegram/SourceFiles/storage/details/storage_settings_scheme.h
Normal file
178
Telegram/SourceFiles/storage/details/storage_settings_scheme.h
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/mtproto_dc_options.h"
|
||||
#include "main/main_session_settings.h"
|
||||
#include "storage/storage_account.h"
|
||||
|
||||
namespace MTP {
|
||||
class AuthKey;
|
||||
} // namespace MTP
|
||||
|
||||
namespace Storage {
|
||||
namespace details {
|
||||
|
||||
struct ReadSettingsContext {
|
||||
[[nodiscard]] Main::SessionSettings &sessionSettings() {
|
||||
if (!sessionSettingsStorage) {
|
||||
sessionSettingsStorage
|
||||
= std::make_unique<Main::SessionSettings>();
|
||||
}
|
||||
return *sessionSettingsStorage;
|
||||
}
|
||||
|
||||
// This field is read in ReadSetting.
|
||||
bool legacyHasCustomDayBackground = false;
|
||||
|
||||
// Those fields are written in ReadSetting.
|
||||
MTP::DcOptions fallbackConfigLegacyDcOptions
|
||||
= MTP::DcOptions(MTP::Environment::Production);
|
||||
qint32 fallbackConfigLegacyChatSizeMax = 0;
|
||||
qint32 fallbackConfigLegacySavedGifsLimit = 0;
|
||||
qint32 fallbackConfigLegacyStickersRecentLimit = 0;
|
||||
qint32 fallbackConfigLegacyStickersFavedLimit = 0;
|
||||
qint32 fallbackConfigLegacyMegagroupSizeMax = 0;
|
||||
QString fallbackConfigLegacyTxtDomainString;
|
||||
QByteArray fallbackConfig;
|
||||
|
||||
qint64 cacheTotalSizeLimit = 0;
|
||||
qint32 cacheTotalTimeLimit = 0;
|
||||
qint64 cacheBigFileTotalSizeLimit = 0;
|
||||
qint32 cacheBigFileTotalTimeLimit = 0;
|
||||
|
||||
std::unique_ptr<Main::SessionSettings> sessionSettingsStorage;
|
||||
|
||||
FileKey themeKeyLegacy = 0;
|
||||
FileKey themeKeyDay = 0;
|
||||
FileKey themeKeyNight = 0;
|
||||
FileKey backgroundKeyDay = 0;
|
||||
FileKey backgroundKeyNight = 0;
|
||||
bool backgroundKeysRead = false;
|
||||
bool tileDay = false;
|
||||
bool tileNight = true;
|
||||
bool tileRead = false;
|
||||
FileKey langPackKey = 0;
|
||||
FileKey languagesKey = 0;
|
||||
|
||||
QByteArray mtpAuthorization;
|
||||
std::vector<std::shared_ptr<MTP::AuthKey>> mtpLegacyKeys;
|
||||
qint32 mtpLegacyMainDcId = 0;
|
||||
qint32 mtpLegacyUserId = 0;
|
||||
|
||||
bool legacyRead = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool ReadSetting(
|
||||
quint32 blockId,
|
||||
QDataStream &stream,
|
||||
int version,
|
||||
ReadSettingsContext &context);
|
||||
|
||||
void ApplyReadFallbackConfig(ReadSettingsContext &context);
|
||||
|
||||
enum {
|
||||
dbiKey = 0x00,
|
||||
dbiUser = 0x01,
|
||||
dbiDcOptionOldOld = 0x02,
|
||||
dbiChatSizeMaxOld = 0x03,
|
||||
dbiMutePeerOld = 0x04,
|
||||
dbiSendKeyOld = 0x05,
|
||||
dbiAutoStart = 0x06,
|
||||
dbiStartMinimized = 0x07,
|
||||
dbiSoundFlashBounceNotifyOld = 0x08,
|
||||
dbiWorkModeOld = 0x09,
|
||||
dbiSeenTrayTooltip = 0x0a,
|
||||
dbiDesktopNotifyOld = 0x0b,
|
||||
dbiAutoUpdate = 0x0c,
|
||||
dbiLastUpdateCheck = 0x0d,
|
||||
dbiWindowPositionOld = 0x0e,
|
||||
dbiConnectionTypeOldOld = 0x0f,
|
||||
// 0x10 reserved
|
||||
dbiDefaultAttach = 0x11,
|
||||
dbiCatsAndDogsOld = 0x12,
|
||||
dbiReplaceEmojiOld = 0x13,
|
||||
dbiAskDownloadPathOld = 0x14,
|
||||
dbiDownloadPathOldOld = 0x15,
|
||||
dbiScaleOld = 0x16,
|
||||
dbiEmojiTabOld = 0x17,
|
||||
dbiRecentEmojiOldOldOld = 0x18,
|
||||
dbiLoggedPhoneNumberOld = 0x19,
|
||||
dbiMutedPeersOld = 0x1a,
|
||||
// 0x1b reserved
|
||||
dbiNotifyViewOld = 0x1c,
|
||||
dbiSendToMenu = 0x1d,
|
||||
dbiCompressPastedImageOld = 0x1e,
|
||||
dbiLangOld = 0x1f,
|
||||
dbiLangFileOld = 0x20,
|
||||
dbiTileBackgroundOld = 0x21,
|
||||
dbiAutoLockOld = 0x22,
|
||||
dbiDialogLastPath = 0x23,
|
||||
dbiRecentEmojiOldOld = 0x24,
|
||||
dbiEmojiVariantsOldOld = 0x25,
|
||||
dbiRecentStickers = 0x26,
|
||||
dbiDcOptionOld = 0x27,
|
||||
dbiTryIPv6Old = 0x28,
|
||||
dbiSongVolumeOld = 0x29,
|
||||
dbiWindowsNotificationsOld = 0x30,
|
||||
dbiIncludeMutedOld = 0x31,
|
||||
dbiMegagroupSizeMaxOld = 0x32,
|
||||
dbiDownloadPathOld = 0x33,
|
||||
dbiAutoDownloadOld = 0x34,
|
||||
dbiSavedGifsLimitOld = 0x35,
|
||||
dbiShowingSavedGifsOld = 0x36,
|
||||
dbiAutoPlayOld = 0x37,
|
||||
dbiAdaptiveForWideOld = 0x38,
|
||||
dbiHiddenPinnedMessagesOld = 0x39,
|
||||
dbiRecentEmojiOld = 0x3a,
|
||||
dbiEmojiVariantsOld = 0x3b,
|
||||
dbiDialogsModeOld = 0x40,
|
||||
dbiModerateModeOld = 0x41,
|
||||
dbiVideoVolumeOld = 0x42,
|
||||
dbiStickersRecentLimitOld = 0x43,
|
||||
dbiNativeNotificationsOld = 0x44,
|
||||
dbiNotificationsCountOld = 0x45,
|
||||
dbiNotificationsCornerOld = 0x46,
|
||||
dbiThemeKeyOld = 0x47,
|
||||
dbiDialogsWidthRatioOld = 0x48,
|
||||
dbiUseExternalVideoPlayerOld = 0x49,
|
||||
dbiDcOptionsOld = 0x4a,
|
||||
dbiMtpAuthorization = 0x4b,
|
||||
dbiLastSeenWarningSeenOld = 0x4c,
|
||||
dbiSessionSettings = 0x4d,
|
||||
dbiLangPackKey = 0x4e,
|
||||
dbiConnectionTypeOld = 0x4f,
|
||||
dbiStickersFavedLimitOld = 0x50,
|
||||
dbiSuggestStickersByEmojiOld = 0x51,
|
||||
dbiSuggestEmojiOld = 0x52,
|
||||
dbiTxtDomainStringOldOld = 0x53,
|
||||
dbiThemeKey = 0x54,
|
||||
dbiTileBackground = 0x55,
|
||||
dbiCacheSettingsOld = 0x56,
|
||||
dbiPowerSaving = 0x57,
|
||||
dbiScalePercent = 0x58,
|
||||
dbiPlaybackSpeedOld = 0x59,
|
||||
dbiLanguagesKey = 0x5a,
|
||||
dbiCallSettingsOld = 0x5b,
|
||||
dbiCacheSettings = 0x5c,
|
||||
dbiTxtDomainStringOld = 0x5d,
|
||||
dbiApplicationSettings = 0x5e,
|
||||
dbiDialogsFiltersOld = 0x5f,
|
||||
dbiFallbackProductionConfig = 0x60,
|
||||
dbiBackgroundKey = 0x61,
|
||||
|
||||
dbiEncryptedWithSalt = 333,
|
||||
dbiEncrypted = 444,
|
||||
|
||||
// 500-600 reserved
|
||||
|
||||
dbiVersion = 666,
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
} // namespace Storage
|
||||
Reference in New Issue
Block a user