Fallback to UnRAR when 7-Zip fails on encrypted RAR archives

If the primary extractor (7-Zip) fails with wrong_password/checksum
error on a .rar file, automatically try the alternative extractor
(UnRAR/WinRAR) which handles RAR format natively and more reliably.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe 2026-03-07 20:34:17 +01:00
parent ed0070c711
commit b8bf9c491c

View File

@ -1718,6 +1718,35 @@ async function resolveExtractorCommand(): Promise<string> {
} }
} }
function is7zCommand(command: string): boolean {
const lower = command.toLowerCase();
return lower.includes("7z") && !lower.includes("unrar") && !lower.includes("winrar");
}
function isUnrarCommand(command: string): boolean {
const lower = command.toLowerCase();
return lower.includes("unrar") || lower.includes("winrar");
}
async function findAlternativeExtractor(currentCommand: string): Promise<string | null> {
const candidates = nativeExtractorCandidates();
const currentIs7z = is7zCommand(currentCommand);
for (const candidate of candidates) {
if (candidate === currentCommand) continue;
// If current is 7z, look for UnRAR/WinRAR. If current is UnRAR, look for 7z.
if (currentIs7z && !isUnrarCommand(candidate)) continue;
if (!currentIs7z && !is7zCommand(candidate)) continue;
if (isAbsoluteCommand(candidate) && !fs.existsSync(candidate)) continue;
const lower = candidate.toLowerCase();
const probeArgs = (lower.includes("winrar") || lower.includes("unrar")) ? ["-?"] : ["?"];
const probe = await runExtractCommand(candidate, probeArgs, undefined, undefined, EXTRACTOR_PROBE_TIMEOUT_MS);
if (probe.ok || (!probe.missingCommand && !probe.timedOut)) {
return candidate;
}
}
return null;
}
async function runExternalExtract( async function runExternalExtract(
archivePath: string, archivePath: string,
targetDir: string, targetDir: string,
@ -1815,22 +1844,58 @@ async function runExternalExtract(
const command = await resolveExtractorCommand(); const command = await resolveExtractorCommand();
const legacyStartedAt = Date.now(); const legacyStartedAt = Date.now();
const password = await runExternalExtractInner( let password: string;
command, let usedCommand = command;
archivePath, try {
effectiveTargetDir, password = await runExternalExtractInner(
conflictMode, command,
passwordCandidates, archivePath,
onArchiveProgress, effectiveTargetDir,
signal, conflictMode,
timeoutMs, passwordCandidates,
hybridMode, onArchiveProgress,
onPasswordAttempt, signal,
forceFlatMode, timeoutMs,
flatModeResult hybridMode,
); onPasswordAttempt,
forceFlatMode,
flatModeResult
);
} catch (primaryError) {
// If the primary extractor (typically 7-Zip) fails on a RAR archive,
// try the alternative extractor (UnRAR/WinRAR) which handles RAR natively.
const isRar = /\.rar$/i.test(archiveName) || /\.r\d{2,3}$/i.test(archiveName);
const errText = String((primaryError as Error)?.message || primaryError || "");
const isPasswordOrCorrupt = /wrong.password|checksum error|corrupt/i.test(errText);
if (isRar && isPasswordOrCorrupt && !signal?.aborted) {
const alt = await findAlternativeExtractor(command);
if (alt) {
const altName = path.basename(alt).replace(/\.exe$/i, "");
logger.info(`Legacy-Fallback: ${path.basename(command)} fehlgeschlagen bei RAR, versuche ${altName}: ${archiveName}`);
usedCommand = alt;
password = await runExternalExtractInner(
alt,
archivePath,
effectiveTargetDir,
conflictMode,
passwordCandidates,
onArchiveProgress,
signal,
timeoutMs,
hybridMode,
onPasswordAttempt,
forceFlatMode,
flatModeResult
);
} else {
throw primaryError;
}
} else {
throw primaryError;
}
}
const legacyMs = Date.now() - legacyStartedAt; const legacyMs = Date.now() - legacyStartedAt;
const extractorName = path.basename(command).replace(/\.exe$/i, ""); const extractorName = path.basename(usedCommand).replace(/\.exe$/i, "");
if (jvmFailureReason) { if (jvmFailureReason) {
logger.info(`Entpackt via legacy/${extractorName} (nach JVM-Fehler): ${archiveName}`); logger.info(`Entpackt via legacy/${extractorName} (nach JVM-Fehler): ${archiveName}`);
} else { } else {