fix: validate raw archive targets before conflicts
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.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -34,9 +34,10 @@ import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -246,12 +247,26 @@ public final class JBindExtractorMain {
|
||||
}
|
||||
|
||||
List<FileHeader> fileHeaders = zipFile.getFileHeaders();
|
||||
if (fileHeaders == null) {
|
||||
fileHeaders = new ArrayList<FileHeader>();
|
||||
}
|
||||
|
||||
long totalUnits = 0;
|
||||
boolean encrypted = false;
|
||||
if (fileHeaders == null) {
|
||||
fileHeaders = new ArrayList<FileHeader>();
|
||||
}
|
||||
|
||||
RawArchivePlanInvariant rawPlan = new RawArchivePlanInvariant();
|
||||
for (FileHeader header : fileHeaders) {
|
||||
if (header == null) {
|
||||
continue;
|
||||
}
|
||||
String entryName = normalizeEntryName(header.getFileName(), "file");
|
||||
if (header.isDirectory()) {
|
||||
resolveDirectory(request.targetDir, entryName);
|
||||
} else {
|
||||
secureResolve(request.targetDir, entryName);
|
||||
}
|
||||
rawPlan.add(entryName, header.isDirectory());
|
||||
}
|
||||
|
||||
long totalUnits = 0;
|
||||
boolean encrypted = false;
|
||||
for (FileHeader header : fileHeaders) {
|
||||
if (header == null || header.isDirectory()) {
|
||||
continue;
|
||||
@@ -259,43 +274,47 @@ public final class JBindExtractorMain {
|
||||
encrypted = encrypted || header.isEncrypted();
|
||||
totalUnits += safeSize(header.getUncompressedSize());
|
||||
}
|
||||
ProgressTracker progress = new ProgressTracker(totalUnits);
|
||||
progress.emitStart();
|
||||
|
||||
Set<String> preflightReserved = new HashSet<String>();
|
||||
Set<String> reserved = new HashSet<String>();
|
||||
TargetPlanInvariant targetPlan = new TargetPlanInvariant();
|
||||
Map<FileHeader, String> plannedEntryNames = new IdentityHashMap<FileHeader, String>();
|
||||
Map<FileHeader, File> plannedDirectories = new IdentityHashMap<FileHeader, File>();
|
||||
Map<FileHeader, OutputTarget> plannedOutputs = new IdentityHashMap<FileHeader, OutputTarget>();
|
||||
for (FileHeader header : fileHeaders) {
|
||||
if (header == null) {
|
||||
continue;
|
||||
}
|
||||
String entryName = normalizeEntryName(header.getFileName(), "file");
|
||||
plannedEntryNames.put(header, entryName);
|
||||
if (header.isDirectory()) {
|
||||
File dir = resolveDirectory(request.targetDir, entryName);
|
||||
targetPlan.add(dir, true);
|
||||
preflightReserved.add(pathKey(dir));
|
||||
reserved.add(pathKey(dir));
|
||||
plannedDirectories.put(header, dir);
|
||||
} else {
|
||||
OutputTarget outputTarget = resolveOutputFile(request.targetDir, entryName, request.conflictMode, preflightReserved);
|
||||
OutputTarget outputTarget = resolveOutputFile(request.targetDir, entryName, request.conflictMode, reserved);
|
||||
targetPlan.add(outputTarget.reportedFile, false);
|
||||
plannedOutputs.put(header, outputTarget);
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> reserved = new HashSet<String>();
|
||||
for (FileHeader header : fileHeaders) {
|
||||
if (header == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String entryName = normalizeEntryName(header.getFileName(), "file");
|
||||
ProgressTracker progress = new ProgressTracker(totalUnits);
|
||||
progress.emitStart();
|
||||
|
||||
for (FileHeader header : fileHeaders) {
|
||||
if (header == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String entryName = plannedEntryNames.get(header);
|
||||
if (header.isDirectory()) {
|
||||
File dir = resolveDirectory(request.targetDir, entryName);
|
||||
File dir = plannedDirectories.get(header);
|
||||
ensureDirectory(dir);
|
||||
rejectLinkedPath(request.targetDir, dir);
|
||||
reserved.add(pathKey(dir));
|
||||
continue;
|
||||
}
|
||||
|
||||
long itemUnits = safeSize(header.getUncompressedSize());
|
||||
OutputTarget outputTarget = resolveOutputFile(request.targetDir, entryName, request.conflictMode, reserved);
|
||||
continue;
|
||||
}
|
||||
|
||||
long itemUnits = safeSize(header.getUncompressedSize());
|
||||
OutputTarget outputTarget = plannedOutputs.get(header);
|
||||
File output = outputTarget.file;
|
||||
if (output == null) {
|
||||
emitOutput(request.archiveFile, entryName, outputTarget.reportedFile, "complete", outputTarget.disposition);
|
||||
@@ -387,26 +406,43 @@ public final class JBindExtractorMain {
|
||||
try {
|
||||
context = openSevenZipArchive(request.archiveFile, password);
|
||||
IInArchive archive = context.archive;
|
||||
int itemCount = archive.getNumberOfItems();
|
||||
int itemCount = archive.getNumberOfItems();
|
||||
if (itemCount <= 0) {
|
||||
throw new IOException("Archiv enthalt keine Eintrage oder konnte nicht gelesen werden: " + request.archiveFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
long totalUnits = 0;
|
||||
boolean encrypted = false;
|
||||
List<String> rawEntryNames = new ArrayList<String>();
|
||||
List<Boolean> rawEntryDirectories = new ArrayList<Boolean>();
|
||||
RawArchivePlanInvariant rawPlan = new RawArchivePlanInvariant();
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
Boolean isFolder = (Boolean) archive.getProperty(i, PropID.IS_FOLDER);
|
||||
String entryPath = (String) archive.getProperty(i, PropID.PATH);
|
||||
String entryName = normalizeEntryName(entryPath, "item-" + i);
|
||||
if (Boolean.TRUE.equals(isFolder)) {
|
||||
resolveDirectory(request.targetDir, entryName);
|
||||
} else {
|
||||
secureResolve(request.targetDir, entryName);
|
||||
}
|
||||
rawPlan.add(entryName, Boolean.TRUE.equals(isFolder));
|
||||
rawEntryNames.add(entryName);
|
||||
rawEntryDirectories.add(Boolean.valueOf(Boolean.TRUE.equals(isFolder)));
|
||||
}
|
||||
|
||||
long totalUnits = 0;
|
||||
boolean encrypted = false;
|
||||
List<Integer> fileIndices = new ArrayList<Integer>();
|
||||
List<File> outputFiles = new ArrayList<File>();
|
||||
List<File> reportedFiles = new ArrayList<File>();
|
||||
List<Long> fileSizes = new ArrayList<Long>();
|
||||
List<String> entryNames = new ArrayList<String>();
|
||||
List<String> dispositions = new ArrayList<String>();
|
||||
List<File> outputDirectories = new ArrayList<File>();
|
||||
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);
|
||||
String entryPath = (String) archive.getProperty(i, PropID.PATH);
|
||||
String entryName = normalizeEntryName(entryPath, "item-" + i);
|
||||
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
Boolean isFolder = rawEntryDirectories.get(i);
|
||||
String entryName = rawEntryNames.get(i);
|
||||
|
||||
if (Boolean.TRUE.equals(isFolder)) {
|
||||
File dir = resolveDirectory(request.targetDir, entryName);
|
||||
@@ -430,16 +466,20 @@ public final class JBindExtractorMain {
|
||||
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);
|
||||
}
|
||||
fileIndices.add(i);
|
||||
outputFiles.add(output);
|
||||
reportedFiles.add(outputTarget.reportedFile);
|
||||
fileSizes.add(itemSize);
|
||||
entryNames.add(entryName);
|
||||
dispositions.add(outputTarget.disposition);
|
||||
}
|
||||
|
||||
for (int i = 0; i < outputFiles.size(); i++) {
|
||||
if (outputFiles.get(i) == null) {
|
||||
emitOutput(request.archiveFile, entryNames.get(i), reportedFiles.get(i), "complete", dispositions.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
for (File directory : outputDirectories) {
|
||||
ensureDirectory(directory);
|
||||
rejectLinkedPath(request.targetDir, directory);
|
||||
@@ -811,6 +851,73 @@ public final class JBindExtractorMain {
|
||||
return value;
|
||||
}
|
||||
|
||||
private static final class RawArchivePlanInvariant {
|
||||
private final RawArchivePlanNode root = new RawArchivePlanNode("");
|
||||
|
||||
void add(String relativeTarget, boolean directory) throws IOException {
|
||||
String normalizedSpelling = relativeTarget == null ? "" : relativeTarget.replace('\\', '/');
|
||||
while (normalizedSpelling.endsWith("/")) {
|
||||
normalizedSpelling = normalizedSpelling.substring(0, normalizedSpelling.length() - 1);
|
||||
}
|
||||
if (normalizedSpelling.length() == 0) {
|
||||
throw new IOException("Raw-Archivplan-Kollision: leeres Ziel");
|
||||
}
|
||||
String[] segments = normalizedSpelling.split("/", -1);
|
||||
StringBuilder canonicalKey = new StringBuilder();
|
||||
RawArchivePlanNode node = root;
|
||||
for (String segment : segments) {
|
||||
if (node.entry != null && !node.entry.directory) {
|
||||
throw new IOException("Raw-Archivplan-Kollision: Datei ist Vorfahr von " + normalizedSpelling);
|
||||
}
|
||||
String canonicalSegment = segment.toLowerCase(Locale.ROOT);
|
||||
if (canonicalKey.length() > 0) {
|
||||
canonicalKey.append('/');
|
||||
}
|
||||
canonicalKey.append(canonicalSegment);
|
||||
RawArchivePlanNode child = node.children.get(canonicalSegment);
|
||||
if (child == null) {
|
||||
child = new RawArchivePlanNode(segment);
|
||||
node.children.put(canonicalSegment, child);
|
||||
} else if (!child.segmentSpelling.equals(segment)) {
|
||||
throw new IOException("Raw-Archivplan-Kollision: Windows-Case-Alias " + normalizedSpelling);
|
||||
}
|
||||
node = child;
|
||||
}
|
||||
if (node.entry != null) {
|
||||
if (node.entry.directory && directory && node.entry.normalizedSpelling.equals(normalizedSpelling)) {
|
||||
return;
|
||||
}
|
||||
throw new IOException("Raw-Archivplan-Kollision: mehrfaches oder typwidriges Ziel " + node.entry.canonicalWindowsKey);
|
||||
}
|
||||
if (!directory && !node.children.isEmpty()) {
|
||||
throw new IOException("Raw-Archivplan-Kollision: Datei ist Vorfahr eines anderen Ziels " + normalizedSpelling);
|
||||
}
|
||||
node.entry = new RawArchivePlanEntry(canonicalKey.toString(), normalizedSpelling, directory);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RawArchivePlanNode {
|
||||
private final String segmentSpelling;
|
||||
private final Map<String, RawArchivePlanNode> children = new HashMap<String, RawArchivePlanNode>();
|
||||
private RawArchivePlanEntry entry;
|
||||
|
||||
private RawArchivePlanNode(String segmentSpelling) {
|
||||
this.segmentSpelling = segmentSpelling;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RawArchivePlanEntry {
|
||||
private final String canonicalWindowsKey;
|
||||
private final String normalizedSpelling;
|
||||
private final boolean directory;
|
||||
|
||||
private RawArchivePlanEntry(String canonicalWindowsKey, String normalizedSpelling, boolean directory) {
|
||||
this.canonicalWindowsKey = canonicalWindowsKey;
|
||||
this.normalizedSpelling = normalizedSpelling;
|
||||
this.directory = directory;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TargetPlanInvariant {
|
||||
private final TargetPlanNode root = new TargetPlanNode();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user