Fix extraction failures on encrypted RAR5 archives with correct file content

- Retry extraction with 2.5s delay on CRC/password errors (Windows file handle race)
- Improve auto-recovery: force re-download when known password fails (content corruption)
- Expand auto-recovery to wrong_password category for encrypted RAR5
- Add fsync after download for pre-allocated files
- Fix permanent extraction failure loop for archives with valid headers but corrupt content

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe
2026-03-08 00:50:03 +01:00
co-authored by Claude Opus 4.6
parent 94126943d5
commit 9e255d8110
2 changed files with 95 additions and 8 deletions
+58 -5
View File
@@ -1970,11 +1970,61 @@ async function runExternalExtract(
}
} catch (legacyError) {
const legacyText = String((legacyError as Error)?.message || legacyError || "");
const suggestRedownload = jvmCodecError && classifyExtractionError(legacyText) === "crc_error";
throw withExtractionErrorHints(legacyError, {
suggestRedownload,
jvmFailureReason: jvmFailureReason || undefined
});
const legacyCategory = classifyExtractionError(legacyText);
const isCrcOrWrongPw = legacyCategory === "crc_error" || legacyCategory === "wrong_password";
// ── Retry once after 2s delay ──
// On Windows, freshly completed downloads may still have file handles not
// fully released by the OS. Encrypted RAR5 headers are especially sensitive:
// even a single unreadable byte causes "Checksum error in the encrypted file"
// at bestPercent=0, indistinguishable from a wrong password.
// A short delay allows the OS to finalise all handles and flush caches.
if (isCrcOrWrongPw && !signal?.aborted) {
const retryDelayMs = 2500;
logger.warn(
`Legacy-Extraktion fehlgeschlagen (${legacyCategory}), Retry nach ${retryDelayMs}ms Delay: ${archiveName}`
);
await extractRetryDelay(retryDelayMs);
if (!signal?.aborted) {
try {
const retryCmd = usedCommand;
const retryPassword = await runExternalExtractInner(
retryCmd,
archivePath,
effectiveTargetDir,
conflictMode,
passwordCandidates,
onArchiveProgress,
signal,
timeoutMs,
hybridMode,
onPasswordAttempt,
forceFlatMode,
flatModeResult
);
logger.info(`Legacy-Retry erfolgreich: ${archiveName}`);
password = retryPassword;
usedCommand = retryCmd;
} catch (retryError) {
const retryText = String((retryError as Error)?.message || retryError || "");
const retryCategory = classifyExtractionError(retryText);
logger.warn(`Legacy-Retry ebenfalls fehlgeschlagen (${retryCategory}): ${archiveName}`);
const suggestRedownload = jvmCodecError && (retryCategory === "crc_error" || retryCategory === "wrong_password");
throw withExtractionErrorHints(retryError, {
suggestRedownload,
jvmFailureReason: jvmFailureReason || undefined
});
}
} else {
throw legacyError;
}
} else {
const suggestRedownload = jvmCodecError && isCrcOrWrongPw;
throw withExtractionErrorHints(legacyError, {
suggestRedownload,
jvmFailureReason: jvmFailureReason || undefined
});
}
}
const legacyMs = Date.now() - legacyStartedAt;
const extractorName = path.basename(usedCommand).replace(/\.exe$/i, "");
@@ -2145,6 +2195,9 @@ async function runExternalExtractInner(
throw new Error(lastError || "Entpacken fehlgeschlagen");
}
// Delay helper for extraction retries (allows file handles to be released on Windows)
const extractRetryDelay = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
function isZipSafetyGuardError(error: unknown): boolean {
const text = String(error || "").toLowerCase();
return text.includes("path traversal")