Validate avatar read recovery and bounded typed blobs
This commit is contained in:
1 parent
6106fc1b5d
commit
bd105c630d
2 files changed
+62
-12
No files matched your search
@@ -48,7 +48,7 @@ QImage GroupAvatarManager::image(const QString& channel, quint64 version, const
|
||||
bool read = false;
|
||||
const QByteArray bytes = m_db->getChannelAvatar(channel, &read);
|
||||
if (!read) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: database snapshot unavailable, cache unchanged channel=%s", qPrintable(channel)); return {};
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: database snapshot unavailable, image not cached channel=%s", qPrintable(channel)); return {};
|
||||
}
|
||||
m_loading.insert(key);
|
||||
m_pool.start(QRunnable::create([this, key, bytes, hash, channel] {
|
||||
@@ -128,11 +128,14 @@ void GroupAvatarManager::upload(const QString& channel, const QString& source) {
|
||||
void GroupAvatarManager::remove(const QString& channel) { publish(channel, {}, ++m_generation[channel]); }
|
||||
void GroupAvatarManager::publish(const QString& channel, const QByteArray& bytes, quint64 generation) {
|
||||
const quint64 group = channel.toULongLong();
|
||||
if (!m_db || !m_db->isOpen() || !gui_bridge_is_uasync_ready() || !group || group > quint64(std::numeric_limits<qint64>::max())
|
||||
|| QString::number(group) != channel || bytes.size() > GROUP_AVATAR_MAX) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: GUI publication unavailable channel=%s", qPrintable(channel));
|
||||
if (!m_db || !m_db->isOpen() || !gui_bridge_is_uasync_ready()) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: publication requires running chat and open DB channel=%s", qPrintable(channel));
|
||||
emit status(channel, text("Chat is stopped")); return;
|
||||
}
|
||||
if (!group || group > quint64(std::numeric_limits<qint64>::max()) || QString::number(group) != channel || bytes.size() > GROUP_AVATAR_MAX) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: invalid publication channel_bytes=%d image_bytes=%d", int(channel.toUtf8().size()), int(bytes.size()));
|
||||
emit status(channel, text("Cannot update group avatar")); return;
|
||||
}
|
||||
auto* q = (group_avatar_gui_request*)u_calloc(1, sizeof(group_avatar_gui_request));
|
||||
if (!q) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: GUI publication allocation failed"); emit status(channel, text("Cannot update group avatar")); return; }
|
||||
q->inst = gui_bridge_get_inst(); q->group = group; q->size = bytes.size();
|
||||
@@ -168,7 +171,7 @@ void GroupAvatarManager::onResult(const uint8_t* bytes, int length) {
|
||||
if (m_generation.value(request.first) != request.second) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "group avatar: ignoring stale publication result request=%llu", (unsigned long long)id); return;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "group avatar: publication completed request=%llu channel=%s result=%u", (unsigned long long)id,
|
||||
qPrintable(request.first), bytes[16]);
|
||||
if (bytes[16]) DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: publication failed request=%llu channel=%s", (unsigned long long)id, qPrintable(request.first));
|
||||
else DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "group avatar: publication completed request=%llu channel=%s", (unsigned long long)id, qPrintable(request.first));
|
||||
emit status(request.first, bytes[16] ? text("Cannot update group avatar") : text("Group avatar updated"));
|
||||
}
|
||||
@@ -288,7 +288,13 @@ private slots:
|
||||
QCOMPARE(GroupAvatarManager::instance(), ¤t);
|
||||
}
|
||||
|
||||
void avatarReadFailureCanBeRetried_data() {
|
||||
QTest::addColumn<bool>("prepare");
|
||||
QTest::newRow("prepare-denied") << true;
|
||||
QTest::newRow("step-interrupted") << false;
|
||||
}
|
||||
void avatarReadFailureCanBeRetried() {
|
||||
QFETCH(bool, prepare);
|
||||
SettingsCore core; QVERIFY(core.start());
|
||||
QImage image(32, 32, QImage::Format_RGB32); image.fill(Qt::green);
|
||||
QByteArray jpeg; QBuffer output(&jpeg); QVERIFY(output.open(QIODevice::WriteOnly)); QVERIFY(image.save(&output, "JPEG"));
|
||||
@@ -296,18 +302,59 @@ private slots:
|
||||
const auto row = core.db.getChannels()[0];
|
||||
GroupAvatarManager avatars(&core.db); QSignalSpy changed(&avatars, &GroupAvatarManager::changed);
|
||||
bool denied = false;
|
||||
QCOMPARE(sqlite3_set_authorizer(core.inst->topo_sqlite_db,
|
||||
[](void* context, int action, const char*, const char* column, const char*, const char*) {
|
||||
if (action != SQLITE_READ || !column || strcmp(column, "avatar_data")) return SQLITE_OK;
|
||||
*static_cast<bool*>(context) = true; return SQLITE_DENY;
|
||||
}, &denied), SQLITE_OK);
|
||||
auto restore = qScopeGuard([&] { sqlite3_set_authorizer(core.inst->topo_sqlite_db, nullptr, nullptr); });
|
||||
if (prepare) QCOMPARE(sqlite3_set_authorizer(core.inst->topo_sqlite_db,
|
||||
[](void* context, int action, const char*, const char* column, const char*, const char*) {
|
||||
if (action != SQLITE_READ || !column || strcmp(column, "avatar_data")) return SQLITE_OK;
|
||||
*static_cast<bool*>(context) = true; return SQLITE_DENY;
|
||||
}, &denied), SQLITE_OK);
|
||||
else sqlite3_progress_handler(core.inst->topo_sqlite_db, 1, [](void* context) { *static_cast<bool*>(context) = true; return 1; }, &denied);
|
||||
auto restore = qScopeGuard([&] {
|
||||
sqlite3_set_authorizer(core.inst->topo_sqlite_db, nullptr, nullptr); sqlite3_progress_handler(core.inst->topo_sqlite_db, 0, nullptr, nullptr);
|
||||
});
|
||||
QVERIFY(avatars.image(core.channel, row.avatarVersion, row.avatarHash).isNull()); QVERIFY(denied);
|
||||
QCOMPARE(sqlite3_set_authorizer(core.inst->topo_sqlite_db, nullptr, nullptr), SQLITE_OK);
|
||||
sqlite3_progress_handler(core.inst->topo_sqlite_db, 0, nullptr, nullptr);
|
||||
avatars.image(core.channel, row.avatarVersion, row.avatarHash);
|
||||
QTRY_VERIFY_WITH_TIMEOUT(!avatars.currentImage(core.channel).isNull(), 1000);
|
||||
QCOMPARE(changed.count(), 1);
|
||||
}
|
||||
|
||||
void avatarBlobTypesAndLimits_data() {
|
||||
QTest::addColumn<QByteArray>("value"); QTest::addColumn<bool>("valid"); QTest::addColumn<int>("bytes");
|
||||
QTest::newRow("empty-blob") << QByteArray("zeroblob(0)") << true << 0;
|
||||
QTest::newRow("maximum-blob") << QByteArray("zeroblob(16384)") << true << 16384;
|
||||
QTest::newRow("oversized-blob") << QByteArray("zeroblob(16385)") << false << 0;
|
||||
QTest::newRow("null") << QByteArray("NULL") << false << 0;
|
||||
QTest::newRow("text") << QByteArray("'jpeg'") << false << 0;
|
||||
QTest::newRow("integer") << QByteArray("1") << false << 0;
|
||||
}
|
||||
void avatarBlobTypesAndLimits() {
|
||||
QFETCH(QByteArray, value); QFETCH(bool, valid); QFETCH(int, bytes);
|
||||
SettingsCore core; QVERIFY(core.start());
|
||||
const QByteArray sql = "UPDATE channels SET avatar_data=" + value;
|
||||
QCOMPARE(sqlite3_exec(core.inst->topo_sqlite_db, sql.constData(), nullptr, nullptr, nullptr), SQLITE_OK);
|
||||
bool ok = !valid; const QByteArray result = core.db.getChannelAvatar(core.channel, &ok);
|
||||
QCOMPARE(ok, valid); QCOMPARE(result.size(), bytes);
|
||||
QVERIFY(core.db.getChannelAvatar("missing", &ok).isEmpty()); QVERIFY(!ok);
|
||||
}
|
||||
|
||||
#ifdef UTUN_TEST_SETTINGS_ALLOC_FAILURES
|
||||
void avatarAllocationFailure_data() {
|
||||
QTest::addColumn<QByteArray>("source");
|
||||
QTest::newRow("request") << QByteArray("groupavatarmanager.cpp");
|
||||
QTest::newRow("bridge") << QByteArray("gui_bridge_impl.cpp");
|
||||
}
|
||||
void avatarAllocationFailure() {
|
||||
QFETCH(QByteArray, source);
|
||||
SettingsCore core; QVERIFY(core.start()); GroupAvatarManager avatars(&core.db);
|
||||
QSignalSpy statuses(&avatars, &GroupAvatarManager::status); const auto baseline = u_get_allocated_count();
|
||||
allocationFile = source.constData(); failAllocation = 1;
|
||||
auto reset = qScopeGuard([] { failAllocation = 0; allocationFile = nullptr; });
|
||||
avatars.remove(core.channel); QCOMPARE(failAllocation, 0);
|
||||
QCOMPARE(statuses.count(), 1); QCOMPARE(statuses[0][1].toString(), QString("Cannot update group avatar"));
|
||||
gui_bridge_set_inst(nullptr); QCOMPARE(u_get_allocated_count(), baseline);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
QTEST_MAIN(ChannelSettingsTest)
|
||||
|
||||
Reference in new issue
Block a user