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:
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -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