Fix hybrid-extract multi-part archive + extractor CRC handling

- findReadyArchiveSets: for .part1.rar, require ALL package items
  to be terminal before allowing extraction (prevents premature
  extraction when later parts have no targetPath/fileName yet)
- JVM extractor: remove CRCERROR from isPasswordFailure() — only
  DATAERROR indicates wrong password. CRCERROR on archives where
  7z-JBinding falsely reports encrypted no longer triggers password
  cycling.
- looksLikeWrongPassword: remove CRC text matching, keep only
  explicit "data error" for encrypted archives.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sucukdeluxe 2026-03-03 14:52:00 +01:00
parent 0cf5ebe5e9
commit 1fde0a9951
10 changed files with 22 additions and 2 deletions

View File

@ -356,7 +356,10 @@ public final class JBindExtractorMain {
if (!encrypted || result == null) {
return false;
}
return result == ExtractOperationResult.CRCERROR || result == ExtractOperationResult.DATAERROR;
// Only DATAERROR reliably indicates wrong password. CRCERROR can also mean
// a genuinely corrupt or incomplete archive, and 7z-JBinding sometimes
// falsely reports encrypted=true for non-encrypted RAR files.
return result == ExtractOperationResult.DATAERROR;
}
private static boolean looksLikeWrongPassword(Throwable error, boolean encrypted) {
@ -367,7 +370,9 @@ public final class JBindExtractorMain {
if (text.contains("wrong password") || text.contains("falsches passwort")) {
return true;
}
return encrypted && (text.contains("crc") || text.contains("data error") || text.contains("checksum"));
// Only "data error" suggests wrong password. CRC errors can also mean
// corrupt/incomplete archives, so we don't treat them as password failures.
return encrypted && text.contains("data error");
}
private static boolean shouldUseZip4j(File archiveFile) {

View File

@ -4988,11 +4988,26 @@ export class DownloadManager extends EventEmitter {
}
}
// Pre-compute: does the package still have any non-terminal items?
const packageHasPendingItems = pkg.itemIds.some((itemId) => {
const item = this.session.items[itemId];
return item != null && item.status !== "completed" && item.status !== "failed" && item.status !== "cancelled";
});
for (const candidate of candidates) {
const partsOnDisk = collectArchiveCleanupTargets(candidate, dirFiles);
const allPartsCompleted = partsOnDisk.every((part) => completedPaths.has(pathKey(part)));
if (allPartsCompleted) {
const candidateBase = path.basename(candidate).toLowerCase();
// For multi-part archives (.part1.rar), require ALL package items to be terminal.
// partsOnDisk only contains parts found ON DISK — pending parts that haven't been
// downloaded yet (no targetPath, no fileName) would slip through the regular checks.
if (/\.part0*1\.rar$/i.test(candidateBase) && packageHasPendingItems) {
logger.info(`Hybrid-Extract: ${path.basename(candidate)} übersprungen Paket hat noch ausstehende Items`);
continue;
}
const hasUnstartedParts = [...pendingPaths].some((pendingPath) => {
const pendingName = path.basename(pendingPath).toLowerCase();
return this.looksLikeArchivePart(pendingName, candidateBase);