fix: reject colliding archive targets before extraction
Build a typed normalized target plan for internal ZIP, native list preflight, Zip4j, and SevenZipJBinding before any output directory or file is mutated. Reject file-directory aliases, file ancestors, case-insensitive aliases, and duplicate file targets while allowing identical directory declarations. Preserve linear archive planning and ship the rebuilt Java runtime classes.
This commit is contained in:
@@ -263,6 +263,7 @@ public final class JBindExtractorMain {
|
||||
progress.emitStart();
|
||||
|
||||
Set<String> preflightReserved = new HashSet<String>();
|
||||
TargetPlanInvariant targetPlan = new TargetPlanInvariant();
|
||||
for (FileHeader header : fileHeaders) {
|
||||
if (header == null) {
|
||||
continue;
|
||||
@@ -270,9 +271,11 @@ public final class JBindExtractorMain {
|
||||
String entryName = normalizeEntryName(header.getFileName(), "file");
|
||||
if (header.isDirectory()) {
|
||||
File dir = resolveDirectory(request.targetDir, entryName);
|
||||
targetPlan.add(dir, true);
|
||||
preflightReserved.add(pathKey(dir));
|
||||
} else {
|
||||
resolveOutputFile(request.targetDir, entryName, request.conflictMode, preflightReserved);
|
||||
OutputTarget outputTarget = resolveOutputFile(request.targetDir, entryName, request.conflictMode, preflightReserved);
|
||||
targetPlan.add(outputTarget.reportedFile, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,7 +400,8 @@ public final class JBindExtractorMain {
|
||||
List<String> entryNames = new ArrayList<String>();
|
||||
List<String> dispositions = new ArrayList<String>();
|
||||
List<File> outputDirectories = new ArrayList<File>();
|
||||
Set<String> reserved = new HashSet<String>();
|
||||
Set<String> reserved = new HashSet<String>();
|
||||
TargetPlanInvariant targetPlan = new TargetPlanInvariant();
|
||||
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
Boolean isFolder = (Boolean) archive.getProperty(i, PropID.IS_FOLDER);
|
||||
@@ -406,6 +410,7 @@ public final class JBindExtractorMain {
|
||||
|
||||
if (Boolean.TRUE.equals(isFolder)) {
|
||||
File dir = resolveDirectory(request.targetDir, entryName);
|
||||
targetPlan.add(dir, true);
|
||||
outputDirectories.add(dir);
|
||||
reserved.add(pathKey(dir));
|
||||
continue;
|
||||
@@ -423,6 +428,7 @@ public final class JBindExtractorMain {
|
||||
totalUnits += itemSize;
|
||||
|
||||
OutputTarget outputTarget = resolveOutputFile(request.targetDir, entryName, request.conflictMode, reserved);
|
||||
targetPlan.add(outputTarget.reportedFile, false);
|
||||
File output = outputTarget.file;
|
||||
if (output == null) {
|
||||
emitOutput(request.archiveFile, entryName, outputTarget.reportedFile, "complete", outputTarget.disposition);
|
||||
@@ -797,13 +803,54 @@ public final class JBindExtractorMain {
|
||||
}
|
||||
}
|
||||
|
||||
private static String pathKey(File file) {
|
||||
String value = file.getAbsolutePath();
|
||||
if (isWindows()) {
|
||||
value = value.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
private static String pathKey(File file) {
|
||||
String value = file.toPath().toAbsolutePath().normalize().toString();
|
||||
if (isWindows()) {
|
||||
value = value.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static final class TargetPlanInvariant {
|
||||
private final TargetPlanNode root = new TargetPlanNode();
|
||||
|
||||
void add(File file, boolean directory) throws IOException {
|
||||
String key = pathKey(file).replace('\\', '/');
|
||||
String[] segments = key.split("/");
|
||||
TargetPlanNode node = root;
|
||||
for (String segment : segments) {
|
||||
if (segment.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (node.file) {
|
||||
throw new IOException("Target-Plan-Kollision: Datei ist Vorfahr von " + file.getAbsolutePath());
|
||||
}
|
||||
TargetPlanNode child = node.children.get(segment);
|
||||
if (child == null) {
|
||||
child = new TargetPlanNode();
|
||||
node.children.put(segment, child);
|
||||
}
|
||||
node = child;
|
||||
}
|
||||
if (node.file || node.directory) {
|
||||
if (directory && node.directory && !node.file) {
|
||||
return;
|
||||
}
|
||||
throw new IOException("Target-Plan-Kollision: mehrfaches oder typwidriges Ziel " + file.getAbsolutePath());
|
||||
}
|
||||
if (!directory && !node.children.isEmpty()) {
|
||||
throw new IOException("Target-Plan-Kollision: Datei ist Vorfahr eines anderen Ziels " + file.getAbsolutePath());
|
||||
}
|
||||
node.directory = directory;
|
||||
node.file = !directory;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TargetPlanNode {
|
||||
private final Map<String, TargetPlanNode> children = new HashMap<String, TargetPlanNode>();
|
||||
private boolean file;
|
||||
private boolean directory;
|
||||
}
|
||||
|
||||
private static boolean isWindows() {
|
||||
String osName = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
|
||||
|
||||
Reference in New Issue
Block a user