make backup listing resilient to corrupt archives

A corrupt or unreadable zip no longer aborts the whole loadBackups
loop or leaks the zip handle - the entry check now sits inside the
per-file try so one bad archive is skipped like any other invalid
backup.
This commit is contained in:
Kevin Gatera 2026-08-02 18:16:03 -04:00
parent adcd1bae96
commit 4d7d7832ef
No known key found for this signature in database
GPG key ID: F0D9F5932458CFB9

View file

@ -390,7 +390,7 @@ class BackupManager {
const fullFilePath = Path.join(this.backupPath, filename)
let zip = null
let data = null
let backup = null
try {
zip = new StreamZip.async({ file: fullFilePath })
const entries = await zip.entries()
@ -407,25 +407,24 @@ class BackupManager {
continue
}
data = await zip.entryData('details')
const data = await zip.entryData('details')
const details = data.toString('utf8').split('\n')
backup = new Backup({ details, fullPath: fullFilePath })
const backupDialect = this.getBackupDialect(backup)
const databaseEntryName = this.getBackupEntryName(backupDialect)
if (!backupDialect || !entries[databaseEntryName]) {
Logger.error(`[BackupManager] Unsupported database backup format found "${backup.filename}"`)
await zip.close().catch(() => {})
continue
}
} catch (error) {
Logger.error(`[BackupManager] Failed to unzip backup "${fullFilePath}"`, error)
if (zip) await zip.close().catch(() => {})
continue
}
const details = data.toString('utf8').split('\n')
const backup = new Backup({ details, fullPath: fullFilePath })
const backupDialect = this.getBackupDialect(backup)
const databaseEntryName = this.getBackupEntryName(backupDialect)
if (!backupDialect || !Object.keys(await zip.entries()).includes(databaseEntryName)) {
Logger.error(`[BackupManager] Unsupported database backup format found "${backup.filename}"`)
await zip.close()
continue
}
if (!backup.serverVersion) {
// Backups before v2
Logger.error(`[BackupManager] Old unsupported backup was found "${backup.filename}"`)