1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

fix(history): Heal invalid resume_file_id's saved to history

Replace underlength resume_file_id's with arbitrary data. Loaded file IDs are
not used for anything at this time, but they should not be null nor invalid.
Any 32-byte value is valid, so use all 0's for consistency.

Fix #6553
This commit is contained in:
Anthony Bilinski 2022-03-18 17:42:59 -07:00
parent 47ee51c61d
commit f777aa885c
No known key found for this signature in database
GPG Key ID: 2AA8E0DA1B31FB3C
3 changed files with 94 additions and 3 deletions

View File

@ -26,7 +26,7 @@
#include <QString>
namespace {
constexpr int SCHEMA_VERSION = 9;
constexpr int SCHEMA_VERSION = 10;
struct BadEntry
{
@ -262,7 +262,8 @@ bool DbUpgrader::dbSchemaUpgrade(std::shared_ptr<RawDatabase>& db)
using DbSchemaUpgradeFn = bool (*)(RawDatabase&);
std::vector<DbSchemaUpgradeFn> upgradeFns = {dbSchema0to1, dbSchema1to2, dbSchema2to3,
dbSchema3to4, dbSchema4to5, dbSchema5to6,
dbSchema6to7, dbSchema7to8, dbSchema8to9};
dbSchema6to7, dbSchema7to8, dbSchema8to9,
dbSchema9to10};
assert(databaseSchemaVersion < static_cast<int>(upgradeFns.size()));
assert(upgradeFns.size() == SCHEMA_VERSION);
@ -610,3 +611,18 @@ bool DbUpgrader::dbSchema8to9(RawDatabase& db)
upgradeQueries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = 9;"));
return db.execNow(upgradeQueries);
}
bool DbUpgrader::dbSchema9to10(RawDatabase& db)
{
// not technically a schema update, but still a database version update based on healing invalid user data.
// We inserted some empty resume_file_id's due to #6553. Heal the under-length entries.
// The resume file ID isn't actually used for loaded files at this time, so we can heal it to an arbitrary
// value of full length.
constexpr int resumeFileIdLengthNow = 32;
QByteArray dummyResumeId(resumeFileIdLengthNow, 0);
QVector<RawDatabase::Query> upgradeQueries;
upgradeQueries += RawDatabase::Query(QStringLiteral("UPDATE file_transfers SET file_restart_id = ? WHERE LENGTH(file_restart_id) != 32;"),
{dummyResumeId});
upgradeQueries += RawDatabase::Query(QStringLiteral("PRAGMA user_version = 10;"));
return db.execNow(upgradeQueries);
}

View File

@ -37,4 +37,5 @@ namespace DbUpgrader
bool dbSchema6to7(RawDatabase& db);
bool dbSchema7to8(RawDatabase& db);
bool dbSchema8to9(RawDatabase& db);
bool dbSchema9to10(RawDatabase& db);
}

View File

@ -19,6 +19,7 @@
#include "src/persistence/db/rawdatabase.h"
#include "src/persistence/dbupgrader.h"
#include "src/core/toxfile.h"
#include <QtTest/QtTest>
#include <QString>
@ -26,6 +27,47 @@
#include <algorithm>
#include <memory>
namespace {
bool insertFileId(RawDatabase& db, int row, bool valid)
{
QByteArray validResumeId(32, 1);
QByteArray invalidResumeId;
QByteArray resumeId;
if (valid) {
resumeId = validResumeId;
} else {
resumeId = invalidResumeId;
}
QVector<RawDatabase::Query> upgradeQueries;
upgradeQueries += RawDatabase::Query(
QString("INSERT INTO file_transfers "
" (id, message_type, sender_alias, "
" file_restart_id, file_name, file_path, "
" file_hash, file_size, direction, file_state) "
"VALUES ( "
" %1, "
" 'F', "
" 1, "
" ?, "
" %2, "
" %3, "
" %4, "
" 1, "
" 1, "
" %5 "
");")
.arg(row)
.arg("\"fooname\"")
.arg("\"foo/path\"")
.arg("\"foohash\"")
.arg(ToxFile::CANCELED)
, {resumeId});
return db.execNow(upgradeQueries);
}
} // namespace
struct SqliteMasterEntry {
QString name;
QString sql;
@ -54,6 +96,7 @@ private slots:
void test6to7();
// test7to8 omitted, version only upgrade, versions are not verified in this
// test8to9 omitted, data corruption correction upgrade with no schema change
void test9to10();
// test suite
void cleanupTestCase() const;
@ -66,7 +109,7 @@ private:
const QString testFileList[] = {"testCreation.db", "testIsNewDbTrue.db", "testIsNewDbFalse.db",
"test0to1.db", "test1to2.db", "test2to3.db",
"test3to4.db", "test4to5.db", "test5to6.db",
"test6to7.db"};
"test6to7.db", "test9to10.db"};
// db schemas can be select with "SELECT name, sql FROM sqlite_master;" on the database.
@ -162,6 +205,8 @@ const std::vector<SqliteMasterEntry> schema7{
"FOREIGN KEY (id, message_type) REFERENCES history(id, message_type))"},
{"chat_id_idx", "CREATE INDEX chat_id_idx on history (chat_id)"}};
const std::vector<SqliteMasterEntry> schema9 = schema7;
const std::vector<SqliteMasterEntry> schema10 = schema9;
void TestDbSchema::initTestCase()
{
@ -434,5 +479,34 @@ void TestDbSchema::test6to7()
verifyDb(db, schema7);
}
void TestDbSchema::test9to10()
{
auto db = std::shared_ptr<RawDatabase>{new RawDatabase{"test9to10.db", {}, {}}};
createSchemaAtVersion(db, schema9);
QVERIFY(insertFileId(*db, 1, true));
QVERIFY(insertFileId(*db, 2, true));
QVERIFY(insertFileId(*db, 3, false));
QVERIFY(insertFileId(*db, 4, true));
QVERIFY(insertFileId(*db, 5, false));
QVERIFY(DbUpgrader::dbSchema9to10(*db));
int numHealed = 0;
int numUnchanged = 0;
QVERIFY(db->execNow(RawDatabase::Query("SELECT file_restart_id from file_transfers;",
[&](const QVector<QVariant>& row) {
auto resumeId = row[0].toByteArray();
if (resumeId == QByteArray(32, 0)) {
++numHealed;
} else if (resumeId == QByteArray(32, 1)) {
++numUnchanged;
} else {
QFAIL("Invalid file_restart_id");
}
})));
QVERIFY(numHealed == 2);
QVERIFY(numUnchanged == 3);
verifyDb(db, schema10);
}
QTEST_GUILESS_MAIN(TestDbSchema)
#include "dbschema_test.moc"