From b8bf9c491c4972f8f41287cf4b5f01ffb0f50258 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Sat, 7 Mar 2026 20:34:17 +0100 Subject: [PATCH] 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 --- src/main/extractor.ts | 95 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 15 deletions(-) diff --git a/src/main/extractor.ts b/src/main/extractor.ts index 937b012..0cbee07 100644 --- a/src/main/extractor.ts +++ b/src/main/extractor.ts @@ -1718,6 +1718,35 @@ async function resolveExtractorCommand(): Promise { } } +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 { + 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( archivePath: string, targetDir: string, @@ -1815,22 +1844,58 @@ async function runExternalExtract( const command = await resolveExtractorCommand(); const legacyStartedAt = Date.now(); - const password = await runExternalExtractInner( - command, - archivePath, - effectiveTargetDir, - conflictMode, - passwordCandidates, - onArchiveProgress, - signal, - timeoutMs, - hybridMode, - onPasswordAttempt, - forceFlatMode, - flatModeResult - ); + let password: string; + let usedCommand = command; + try { + password = await runExternalExtractInner( + command, + archivePath, + effectiveTargetDir, + conflictMode, + passwordCandidates, + onArchiveProgress, + signal, + timeoutMs, + 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 extractorName = path.basename(command).replace(/\.exe$/i, ""); + const extractorName = path.basename(usedCommand).replace(/\.exe$/i, ""); if (jvmFailureReason) { logger.info(`Entpackt via legacy/${extractorName} (nach JVM-Fehler): ${archiveName}`); } else {