release: harden archive recovery and lifecycle for v2.0.63
Corroborate CRC failures across extraction backends, retry only implicated multipart volumes, and distinguish corruption, missing volumes, I/O failures, and wrong passwords across native, Zip4j, and JBinding paths. Make disk retries generation-safe, preserve selective run scopes and cooldowns, protect shared output files during cleanup, validate manual extraction batches atomically, and restore interrupted integrity work safely. Prioritize active package operations in the UI, strengthen extraction IPC validation, compile the JVM sidecar before release builds, and verify shipped JVM resources byte-for-byte in every Windows artifact.
This commit is contained in:
@@ -2,7 +2,8 @@ package com.sucukdeluxe.extractor;
|
||||
|
||||
import net.lingala.zip4j.ZipFile;
|
||||
import net.lingala.zip4j.exception.ZipException;
|
||||
import net.lingala.zip4j.model.FileHeader;
|
||||
import net.lingala.zip4j.model.FileHeader;
|
||||
import net.lingala.zip4j.model.enums.EncryptionMethod;
|
||||
import net.sf.sevenzipjbinding.ExtractAskMode;
|
||||
import net.sf.sevenzipjbinding.ExtractOperationResult;
|
||||
import net.sf.sevenzipjbinding.IArchiveExtractCallback;
|
||||
@@ -106,115 +107,234 @@ public final class JBindExtractorMain {
|
||||
}
|
||||
}
|
||||
|
||||
private static ExtractionRequest parseDaemonRequest(String jsonLine) {
|
||||
|
||||
ExtractionRequest request = new ExtractionRequest();
|
||||
request.archiveFile = new File(extractJsonString(jsonLine, "archive"));
|
||||
request.targetDir = new File(extractJsonString(jsonLine, "target"));
|
||||
String conflict = extractJsonString(jsonLine, "conflict");
|
||||
if (conflict.length() > 0) {
|
||||
request.conflictMode = ConflictMode.fromValue(conflict);
|
||||
}
|
||||
String backend = extractJsonString(jsonLine, "backend");
|
||||
if (backend.length() > 0) {
|
||||
request.backend = Backend.fromValue(backend);
|
||||
}
|
||||
|
||||
int pwStart = jsonLine.indexOf("\"passwords\"");
|
||||
if (pwStart >= 0) {
|
||||
int arrStart = jsonLine.indexOf('[', pwStart);
|
||||
int arrEnd = jsonLine.indexOf(']', arrStart);
|
||||
if (arrStart >= 0 && arrEnd > arrStart) {
|
||||
String arrContent = jsonLine.substring(arrStart + 1, arrEnd);
|
||||
int idx = 0;
|
||||
while (idx < arrContent.length()) {
|
||||
int qStart = arrContent.indexOf('"', idx);
|
||||
if (qStart < 0) break;
|
||||
int qEnd = findClosingQuote(arrContent, qStart + 1);
|
||||
if (qEnd < 0) break;
|
||||
request.passwords.add(unescapeJsonString(arrContent.substring(qStart + 1, qEnd)));
|
||||
idx = qEnd + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
private static ExtractionRequest parseDaemonRequest(String jsonLine) {
|
||||
Map<String, Object> values = new DaemonJsonParser(jsonLine).parseObject();
|
||||
ExtractionRequest request = new ExtractionRequest();
|
||||
request.archiveFile = new File(requireJsonString(values, "archive"));
|
||||
request.targetDir = new File(requireJsonString(values, "target"));
|
||||
String conflict = optionalJsonString(values, "conflict");
|
||||
if (conflict.length() > 0) {
|
||||
request.conflictMode = ConflictMode.fromValue(conflict);
|
||||
}
|
||||
String backend = optionalJsonString(values, "backend");
|
||||
if (backend.length() > 0) {
|
||||
request.backend = Backend.fromValue(backend);
|
||||
}
|
||||
Object rawPasswords = values.get("passwords");
|
||||
if (rawPasswords != null) {
|
||||
if (!(rawPasswords instanceof List<?>)) {
|
||||
throw new IllegalArgumentException("Daemon-Feld passwords muss ein String-Array sein");
|
||||
}
|
||||
for (Object value : (List<?>) rawPasswords) {
|
||||
if (!(value instanceof String)) {
|
||||
throw new IllegalArgumentException("Daemon-Feld passwords muss nur Strings enthalten");
|
||||
}
|
||||
request.passwords.add((String) value);
|
||||
}
|
||||
}
|
||||
if (request.archiveFile == null || !request.archiveFile.exists() || !request.archiveFile.isFile()) {
|
||||
throw new IllegalArgumentException("Archiv nicht gefunden: " +
|
||||
(request.archiveFile == null ? "null" : request.archiveFile.getAbsolutePath()));
|
||||
}
|
||||
if (request.targetDir == null) {
|
||||
throw new IllegalArgumentException("--target fehlt");
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
private static String extractJsonString(String json, String key) {
|
||||
String search = "\"" + key + "\"";
|
||||
int keyIdx = json.indexOf(search);
|
||||
if (keyIdx < 0) return "";
|
||||
int colonIdx = json.indexOf(':', keyIdx + search.length());
|
||||
if (colonIdx < 0) return "";
|
||||
int qStart = json.indexOf('"', colonIdx + 1);
|
||||
if (qStart < 0) return "";
|
||||
int qEnd = findClosingQuote(json, qStart + 1);
|
||||
if (qEnd < 0) return "";
|
||||
return unescapeJsonString(json.substring(qStart + 1, qEnd));
|
||||
}
|
||||
|
||||
private static int findClosingQuote(String s, int from) {
|
||||
for (int i = from; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c == '\\') {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (c == '"') return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static String unescapeJsonString(String s) {
|
||||
if (s.indexOf('\\') < 0) return s;
|
||||
StringBuilder sb = new StringBuilder(s.length());
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c == '\\' && i + 1 < s.length()) {
|
||||
char next = s.charAt(i + 1);
|
||||
switch (next) {
|
||||
case '"': sb.append('"'); i++; break;
|
||||
case '\\': sb.append('\\'); i++; break;
|
||||
case '/': sb.append('/'); i++; break;
|
||||
case 'n': sb.append('\n'); i++; break;
|
||||
case 'r': sb.append('\r'); i++; break;
|
||||
case 't': sb.append('\t'); i++; break;
|
||||
default: sb.append(c); break;
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
private static String requireJsonString(Map<String, Object> values, String key) {
|
||||
String value = optionalJsonString(values, key);
|
||||
if (value.length() == 0) {
|
||||
throw new IllegalArgumentException("Daemon-Feld fehlt: " + key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String optionalJsonString(Map<String, Object> values, String key) {
|
||||
Object value = values.get(key);
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
if (!(value instanceof String)) {
|
||||
throw new IllegalArgumentException("Daemon-Feld muss ein String sein: " + key);
|
||||
}
|
||||
return (String) value;
|
||||
}
|
||||
|
||||
private static final class DaemonJsonParser {
|
||||
private final String input;
|
||||
private int index;
|
||||
|
||||
DaemonJsonParser(String input) {
|
||||
this.input = input == null ? "" : input;
|
||||
}
|
||||
|
||||
Map<String, Object> parseObject() {
|
||||
skipWhitespace();
|
||||
expect('{');
|
||||
Map<String, Object> values = new HashMap<String, Object>();
|
||||
skipWhitespace();
|
||||
if (consume('}')) {
|
||||
requireEnd();
|
||||
return values;
|
||||
}
|
||||
while (true) {
|
||||
String key = parseString();
|
||||
skipWhitespace();
|
||||
expect(':');
|
||||
skipWhitespace();
|
||||
Object value = peek('"') ? parseString() : parseStringArray();
|
||||
if (values.containsKey(key)) {
|
||||
throw malformed();
|
||||
}
|
||||
values.put(key, value);
|
||||
skipWhitespace();
|
||||
if (consume('}')) {
|
||||
requireEnd();
|
||||
return values;
|
||||
}
|
||||
expect(',');
|
||||
skipWhitespace();
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> parseStringArray() {
|
||||
expect('[');
|
||||
List<String> values = new ArrayList<String>();
|
||||
skipWhitespace();
|
||||
if (consume(']')) {
|
||||
return values;
|
||||
}
|
||||
while (true) {
|
||||
values.add(parseString());
|
||||
skipWhitespace();
|
||||
if (consume(']')) {
|
||||
return values;
|
||||
}
|
||||
expect(',');
|
||||
skipWhitespace();
|
||||
}
|
||||
}
|
||||
|
||||
private String parseString() {
|
||||
expect('"');
|
||||
StringBuilder value = new StringBuilder();
|
||||
while (index < input.length()) {
|
||||
char current = input.charAt(index++);
|
||||
if (current == '"') {
|
||||
return value.toString();
|
||||
}
|
||||
if (current < 0x20) {
|
||||
throw malformed();
|
||||
}
|
||||
if (current != '\\') {
|
||||
value.append(current);
|
||||
continue;
|
||||
}
|
||||
if (index >= input.length()) {
|
||||
throw malformed();
|
||||
}
|
||||
char escaped = input.charAt(index++);
|
||||
switch (escaped) {
|
||||
case '"': value.append('"'); break;
|
||||
case '\\': value.append('\\'); break;
|
||||
case '/': value.append('/'); break;
|
||||
case 'b': value.append('\b'); break;
|
||||
case 'f': value.append('\f'); break;
|
||||
case 'n': value.append('\n'); break;
|
||||
case 'r': value.append('\r'); break;
|
||||
case 't': value.append('\t'); break;
|
||||
case 'u': value.append(parseUnicodeEscape()); break;
|
||||
default: throw malformed();
|
||||
}
|
||||
}
|
||||
throw malformed();
|
||||
}
|
||||
|
||||
private char parseUnicodeEscape() {
|
||||
if (index + 4 > input.length()) {
|
||||
throw malformed();
|
||||
}
|
||||
int value = 0;
|
||||
for (int offset = 0; offset < 4; offset += 1) {
|
||||
int digit = Character.digit(input.charAt(index++), 16);
|
||||
if (digit < 0) {
|
||||
throw malformed();
|
||||
}
|
||||
value = (value << 4) | digit;
|
||||
}
|
||||
return (char) value;
|
||||
}
|
||||
|
||||
private void requireEnd() {
|
||||
skipWhitespace();
|
||||
if (index != input.length()) {
|
||||
throw malformed();
|
||||
}
|
||||
}
|
||||
|
||||
private void skipWhitespace() {
|
||||
while (index < input.length()) {
|
||||
char current = input.charAt(index);
|
||||
if (current != ' ' && current != '\t' && current != '\r' && current != '\n') {
|
||||
return;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean peek(char expected) {
|
||||
return index < input.length() && input.charAt(index) == expected;
|
||||
}
|
||||
|
||||
private boolean consume(char expected) {
|
||||
if (!peek(expected)) {
|
||||
return false;
|
||||
}
|
||||
index += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void expect(char expected) {
|
||||
if (!consume(expected)) {
|
||||
throw malformed();
|
||||
}
|
||||
}
|
||||
|
||||
private IllegalArgumentException malformed() {
|
||||
return new IllegalArgumentException("Ungueltige Daemon-JSON-Anfrage");
|
||||
}
|
||||
}
|
||||
|
||||
private static int runExtraction(ExtractionRequest request) throws Exception {
|
||||
List<String> passwords = normalizePasswords(request.passwords);
|
||||
Exception lastError = null;
|
||||
boolean hadWrongPassword = false;
|
||||
for (String password : passwords) {
|
||||
try {
|
||||
extractSingle(request, password);
|
||||
emitPassword(password);
|
||||
emitDone();
|
||||
Exception lastError = null;
|
||||
Exception integrityError = null;
|
||||
boolean hadWrongPassword = false;
|
||||
for (int passwordIndex = 0; passwordIndex < passwords.size(); passwordIndex++) {
|
||||
String password = passwords.get(passwordIndex);
|
||||
emitPasswordAttempt(passwordIndex + 1, passwords.size());
|
||||
try {
|
||||
extractSingle(request, password);
|
||||
emitDone();
|
||||
return 0;
|
||||
} catch (WrongPasswordException wrongPassword) {
|
||||
hadWrongPassword = true;
|
||||
lastError = wrongPassword;
|
||||
} catch (Exception error) {
|
||||
lastError = error;
|
||||
} catch (AmbiguousPasswordOrIntegrityException ambiguous) {
|
||||
integrityError = ambiguous;
|
||||
lastError = ambiguous;
|
||||
} catch (WrongPasswordException wrongPassword) {
|
||||
hadWrongPassword = true;
|
||||
lastError = wrongPassword;
|
||||
} catch (Exception error) {
|
||||
integrityError = null;
|
||||
lastError = error;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hadWrongPassword && (lastError instanceof WrongPasswordException)) {
|
||||
if (integrityError != null) {
|
||||
throw integrityError;
|
||||
}
|
||||
if (hadWrongPassword && (lastError instanceof WrongPasswordException)) {
|
||||
emitError("Falsches Archiv-Passwort");
|
||||
return 1;
|
||||
}
|
||||
@@ -272,8 +392,31 @@ public final class JBindExtractorMain {
|
||||
continue;
|
||||
}
|
||||
encrypted = encrypted || header.isEncrypted();
|
||||
totalUnits += safeSize(header.getUncompressedSize());
|
||||
}
|
||||
totalUnits += safeSize(header.getUncompressedSize());
|
||||
}
|
||||
if (encrypted) {
|
||||
for (FileHeader header : fileHeaders) {
|
||||
if (header == null || header.isDirectory() || !header.isEncrypted()) {
|
||||
continue;
|
||||
}
|
||||
InputStream passwordProbe = null;
|
||||
try {
|
||||
passwordProbe = zipFile.getInputStream(header);
|
||||
} catch (ZipException error) {
|
||||
if (isWrongPassword(error, true, false)) {
|
||||
throw new WrongPasswordException(error);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
if (passwordProbe != null) {
|
||||
try {
|
||||
passwordProbe.close();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Set<String> reserved = new HashSet<String>();
|
||||
TargetPlanInvariant targetPlan = new TargetPlanInvariant();
|
||||
Map<FileHeader, String> plannedEntryNames = new IdentityHashMap<FileHeader, String>();
|
||||
@@ -326,8 +469,9 @@ public final class JBindExtractorMain {
|
||||
emitOutput(request.archiveFile, entryName, output, "opened", outputTarget.disposition);
|
||||
ensureDirectory(output.getParentFile());
|
||||
rejectLinkedPath(request.targetDir, output);
|
||||
long[] remaining = new long[] { itemUnits };
|
||||
boolean extractionSuccess = false;
|
||||
long[] remaining = new long[] { itemUnits };
|
||||
boolean extractionSuccess = false;
|
||||
boolean outputProduced = false;
|
||||
try {
|
||||
InputStream in = zipFile.getInputStream(header);
|
||||
try {
|
||||
@@ -341,8 +485,9 @@ public final class JBindExtractorMain {
|
||||
}
|
||||
if (read == 0) {
|
||||
continue;
|
||||
}
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
out.write(buffer, 0, read);
|
||||
outputProduced = true;
|
||||
long accounted = Math.min(remaining[0], (long) read);
|
||||
remaining[0] -= accounted;
|
||||
progress.advance(accounted);
|
||||
@@ -369,10 +514,18 @@ public final class JBindExtractorMain {
|
||||
extractionSuccess = true;
|
||||
emitOutput(request.archiveFile, entryName, output, "complete", outputTarget.disposition);
|
||||
} catch (ZipException error) {
|
||||
if (isWrongPassword(error, encrypted)) {
|
||||
throw new WrongPasswordException(error);
|
||||
}
|
||||
throw error;
|
||||
if (isWrongPassword(error, encrypted, outputProduced)) {
|
||||
throw new WrongPasswordException(error);
|
||||
}
|
||||
if (isZipIntegrityFailure(error, encrypted, outputProduced)) {
|
||||
throw zipIntegrityFailure(header, error);
|
||||
}
|
||||
throw error;
|
||||
} catch (IOException error) {
|
||||
if (isZipIntegrityFailure(error, encrypted, outputProduced)) {
|
||||
throw zipIntegrityFailure(header, error);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
if (!extractionSuccess && output.exists()) {
|
||||
if (output.delete()) {
|
||||
@@ -405,8 +558,13 @@ public final class JBindExtractorMain {
|
||||
ensureSevenZipInitialized();
|
||||
SevenZipArchiveContext context = null;
|
||||
try {
|
||||
context = openSevenZipArchive(request.archiveFile, password);
|
||||
IInArchive archive = context.archive;
|
||||
context = openSevenZipArchive(request.archiveFile, password);
|
||||
IInArchive archive = context.archive;
|
||||
Object rawArchiveError = archive.getArchiveProperty(PropID.ERROR);
|
||||
String archiveError = rawArchiveError == null ? "" : String.valueOf(rawArchiveError).trim();
|
||||
if (archiveError.length() > 0) {
|
||||
throw new IOException(archiveError);
|
||||
}
|
||||
int itemCount = archive.getNumberOfItems();
|
||||
if (itemCount <= 0) {
|
||||
throw new IOException("Archiv enthalt keine Eintrage oder konnte nicht gelesen werden: " + request.archiveFile.getAbsolutePath());
|
||||
@@ -512,19 +670,21 @@ public final class JBindExtractorMain {
|
||||
final File[] currentOutput = new File[1];
|
||||
final FileOutputStream[] currentStream = new FileOutputStream[1];
|
||||
final boolean[] currentSuccess = new boolean[1];
|
||||
final long[] currentRemaining = new long[1];
|
||||
final Throwable[] firstError = new Throwable[1];
|
||||
final int[] currentPos = new int[] { -1 };
|
||||
|
||||
final long[] currentRemaining = new long[1];
|
||||
final Throwable[] firstError = new Throwable[1];
|
||||
final int[] currentPos = new int[] { -1 };
|
||||
final boolean[] passwordRequested = new boolean[1];
|
||||
final boolean[] outputProduced = new boolean[1];
|
||||
|
||||
BulkExtractCallback extractCallback = new BulkExtractCallback(
|
||||
archive, request.archiveFile, request.targetDir, indexToPos, fileIndices, outputFiles, fileSizes, entryNames, dispositions,
|
||||
progress, encryptedFinal, effectivePassword, currentOutput,
|
||||
currentStream, currentSuccess, currentRemaining, currentPos, firstError
|
||||
currentStream, currentSuccess, currentRemaining, currentPos, firstError, passwordRequested, outputProduced
|
||||
);
|
||||
try {
|
||||
archive.extract(indices, false, extractCallback);
|
||||
} catch (SevenZipException error) {
|
||||
if (looksLikeWrongPassword(error, encryptedFinal)) {
|
||||
if (!outputProduced[0] && looksLikeWrongPassword(error, encryptedFinal || passwordRequested[0])) {
|
||||
throw new WrongPasswordException(error);
|
||||
}
|
||||
throw error;
|
||||
@@ -552,15 +712,22 @@ public final class JBindExtractorMain {
|
||||
String effectivePassword = password == null ? "" : password;
|
||||
SevenZipVolumeCallback callback = new SevenZipVolumeCallback(archiveFile, effectivePassword);
|
||||
|
||||
if (SEVEN_ZIP_SPLIT_RE.matcher(nameLower).matches()) {
|
||||
VolumedArchiveInStream volumed = new VolumedArchiveInStream(archiveFile.getName(), callback);
|
||||
try {
|
||||
IInArchive archive = SevenZip.openInArchive(null, volumed, callback);
|
||||
return new SevenZipArchiveContext(archive, null, volumed, callback);
|
||||
} catch (Exception error) {
|
||||
callback.close();
|
||||
throw error;
|
||||
}
|
||||
if (SEVEN_ZIP_SPLIT_RE.matcher(nameLower).matches()) {
|
||||
VolumedArchiveInStream volumed = new VolumedArchiveInStream(archiveFile.getName(), callback);
|
||||
try {
|
||||
IInArchive archive = SevenZip.openInArchive(null, volumed, callback);
|
||||
return new SevenZipArchiveContext(archive, null, volumed, callback);
|
||||
} catch (Exception error) {
|
||||
SevenZipException volumeAccessError = callback.getVolumeAccessError();
|
||||
callback.close();
|
||||
if (volumeAccessError != null) {
|
||||
throw volumeAccessError;
|
||||
}
|
||||
if (callback.wasPasswordRequested()) {
|
||||
throw new WrongPasswordException(error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(archiveFile, "r");
|
||||
@@ -568,23 +735,34 @@ public final class JBindExtractorMain {
|
||||
try {
|
||||
IInArchive archive = SevenZip.openInArchive(null, stream, callback);
|
||||
return new SevenZipArchiveContext(archive, stream, null, callback);
|
||||
} catch (Exception error) {
|
||||
} catch (Exception error) {
|
||||
SevenZipException volumeAccessError = callback.getVolumeAccessError();
|
||||
try {
|
||||
stream.close();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
try {
|
||||
raf.close();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
throw error;
|
||||
try {
|
||||
raf.close();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
callback.close();
|
||||
if (volumeAccessError != null) {
|
||||
throw volumeAccessError;
|
||||
}
|
||||
if (callback.wasPasswordRequested()) {
|
||||
throw new WrongPasswordException(error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isWrongPassword(ZipException error, boolean encrypted) {
|
||||
if (error == null) {
|
||||
return false;
|
||||
}
|
||||
private static boolean isWrongPassword(ZipException error, boolean encrypted, boolean outputProduced) {
|
||||
if (error == null) {
|
||||
return false;
|
||||
}
|
||||
if (outputProduced) {
|
||||
return false;
|
||||
}
|
||||
if (error.getType() == ZipException.Type.WRONG_PASSWORD) {
|
||||
return true;
|
||||
}
|
||||
@@ -592,13 +770,31 @@ public final class JBindExtractorMain {
|
||||
if (text.contains("wrong password") || text.contains("falsches passwort")) {
|
||||
return true;
|
||||
}
|
||||
return encrypted && (text.contains("checksum") || text.contains("crc") || text.contains("password"));
|
||||
}
|
||||
return encrypted && text.contains("password");
|
||||
}
|
||||
|
||||
private static boolean isZipIntegrityFailure(Throwable error, boolean encrypted, boolean outputProduced) {
|
||||
if (!encrypted || error == null) {
|
||||
return false;
|
||||
}
|
||||
String text = safeMessage(error).toLowerCase(Locale.ROOT);
|
||||
return outputProduced || text.contains("aes verification failed") || text.contains("checksum") || text.contains("crc");
|
||||
}
|
||||
|
||||
private static Exception zipIntegrityFailure(FileHeader header, Throwable error) {
|
||||
if (header != null && header.getEncryptionMethod() == EncryptionMethod.ZIP_STANDARD) {
|
||||
return new AmbiguousPasswordOrIntegrityException(error);
|
||||
}
|
||||
return new IOException("zip4j-Fehler: CRCERROR", error);
|
||||
}
|
||||
|
||||
private static boolean isPasswordFailure(ExtractOperationResult result, boolean encrypted) {
|
||||
if (!encrypted || result == null) {
|
||||
return false;
|
||||
}
|
||||
private static boolean isPasswordFailure(ExtractOperationResult result, boolean encrypted, boolean outputProduced) {
|
||||
if (result == ExtractOperationResult.WRONG_PASSWORD) {
|
||||
return true;
|
||||
}
|
||||
if (!encrypted || outputProduced || result == null) {
|
||||
return false;
|
||||
}
|
||||
return result == ExtractOperationResult.CRCERROR || result == ExtractOperationResult.DATAERROR;
|
||||
}
|
||||
|
||||
@@ -1031,10 +1227,9 @@ public final class JBindExtractorMain {
|
||||
System.out.println("RD_BACKEND " + backend.value);
|
||||
}
|
||||
|
||||
private static void emitPassword(String password) {
|
||||
String encoded = Base64.getEncoder().encodeToString((password == null ? "" : password).getBytes(StandardCharsets.UTF_8));
|
||||
System.out.println("RD_PASSWORD " + encoded);
|
||||
}
|
||||
private static void emitPasswordAttempt(int attempt, int total) {
|
||||
System.out.println("RD_PASSWORD_ATTEMPT " + attempt + " " + total);
|
||||
}
|
||||
|
||||
private static void emitDone() {
|
||||
System.out.println("RD_DONE");
|
||||
@@ -1141,16 +1336,18 @@ public final class JBindExtractorMain {
|
||||
private final FileOutputStream[] currentStream;
|
||||
private final boolean[] currentSuccess;
|
||||
private final long[] currentRemaining;
|
||||
private final int[] currentPos;
|
||||
private final Throwable[] firstError;
|
||||
private final int[] currentPos;
|
||||
private final Throwable[] firstError;
|
||||
private final boolean[] passwordRequested;
|
||||
private final boolean[] outputProduced;
|
||||
|
||||
BulkExtractCallback(IInArchive archive, File archiveFile, File targetDir, Map<Integer, Integer> indexToPos,
|
||||
List<Integer> fileIndices, List<File> outputFiles, List<Long> fileSizes,
|
||||
List<String> entryNames, List<String> dispositions,
|
||||
ProgressTracker progress, boolean encrypted, String password,
|
||||
File[] currentOutput, FileOutputStream[] currentStream,
|
||||
boolean[] currentSuccess, long[] currentRemaining, int[] currentPos,
|
||||
Throwable[] firstError) {
|
||||
ProgressTracker progress, boolean encrypted, String password,
|
||||
File[] currentOutput, FileOutputStream[] currentStream,
|
||||
boolean[] currentSuccess, long[] currentRemaining, int[] currentPos,
|
||||
Throwable[] firstError, boolean[] passwordRequested, boolean[] outputProduced) {
|
||||
this.archive = archive;
|
||||
this.archiveFile = archiveFile;
|
||||
this.targetDir = targetDir;
|
||||
@@ -1168,12 +1365,15 @@ public final class JBindExtractorMain {
|
||||
this.currentSuccess = currentSuccess;
|
||||
this.currentRemaining = currentRemaining;
|
||||
this.currentPos = currentPos;
|
||||
this.firstError = firstError;
|
||||
this.firstError = firstError;
|
||||
this.passwordRequested = passwordRequested;
|
||||
this.outputProduced = outputProduced;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cryptoGetTextPassword() {
|
||||
return password;
|
||||
public String cryptoGetTextPassword() {
|
||||
passwordRequested[0] = true;
|
||||
return password;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1225,9 +1425,10 @@ public final class JBindExtractorMain {
|
||||
if (data == null || data.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
currentStream[0].write(data);
|
||||
} catch (IOException error) {
|
||||
try {
|
||||
currentStream[0].write(data);
|
||||
outputProduced[0] = true;
|
||||
} catch (IOException error) {
|
||||
throw new SevenZipException("Fehler beim Schreiben: " + error.getMessage(), error);
|
||||
}
|
||||
long accounted = Math.min(currentRemaining[0], (long) data.length);
|
||||
@@ -1267,8 +1468,8 @@ public final class JBindExtractorMain {
|
||||
}
|
||||
} else {
|
||||
discardCurrentOutput();
|
||||
if (firstError[0] == null) {
|
||||
if (isPasswordFailure(result, encrypted)) {
|
||||
if (firstError[0] == null) {
|
||||
if (isPasswordFailure(result, encrypted || passwordRequested[0], outputProduced[0])) {
|
||||
firstError[0] = new WrongPasswordException(new IOException("Falsches Passwort"));
|
||||
} else {
|
||||
firstError[0] = new IOException("7z-Fehler: " + result.name());
|
||||
@@ -1310,13 +1511,21 @@ public final class JBindExtractorMain {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class WrongPasswordException extends Exception {
|
||||
private static final class WrongPasswordException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
WrongPasswordException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class AmbiguousPasswordOrIntegrityException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
AmbiguousPasswordOrIntegrityException(Throwable cause) {
|
||||
super("zip4j-Fehler: CRCERROR", cause);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ProgressTracker {
|
||||
private final long total;
|
||||
@@ -1401,7 +1610,9 @@ public final class JBindExtractorMain {
|
||||
private static final class SevenZipVolumeCallback implements IArchiveOpenCallback, IArchiveOpenVolumeCallback, ICryptoGetTextPassword, Closeable {
|
||||
private final File archiveDir;
|
||||
private final String firstFileName;
|
||||
private final String password;
|
||||
private final String password;
|
||||
private boolean passwordRequested;
|
||||
private SevenZipException volumeAccessError;
|
||||
private final Map<String, RandomAccessFile> openRafs = new HashMap<String, RandomAccessFile>();
|
||||
|
||||
SevenZipVolumeCallback(File archiveFile, String password) {
|
||||
@@ -1433,8 +1644,9 @@ public final class JBindExtractorMain {
|
||||
}
|
||||
raf.seek(0L);
|
||||
return new RandomAccessFileInStream(raf);
|
||||
} catch (IOException error) {
|
||||
throw new SevenZipException("Volume konnte nicht geoffnet werden: " + filename, error);
|
||||
} catch (IOException error) {
|
||||
volumeAccessError = new SevenZipException("Volume konnte nicht geoffnet werden: " + filename, error);
|
||||
throw volumeAccessError;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1449,9 +1661,18 @@ public final class JBindExtractorMain {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cryptoGetTextPassword() {
|
||||
return password;
|
||||
}
|
||||
public String cryptoGetTextPassword() {
|
||||
passwordRequested = true;
|
||||
return password;
|
||||
}
|
||||
|
||||
boolean wasPasswordRequested() {
|
||||
return passwordRequested;
|
||||
}
|
||||
|
||||
SevenZipException getVolumeAccessError() {
|
||||
return volumeAccessError;
|
||||
}
|
||||
|
||||
private File resolveVolumeFile(String filename) {
|
||||
if (filename == null || filename.trim().length() == 0) {
|
||||
|
||||
Reference in New Issue
Block a user