fix: close Windows extraction namespace gaps
Reject alternate data streams, reserved Win32 device names, and trailing-dot or trailing-space aliases before internal ZIP, JVM, or native extraction writes. Preflight native archive entry lists, reconcile opened JVM outputs to partial or removed state on abnormal results, and replace hardlink-based owner markers with portable exclusive reservation plus atomic replacement while allowing direct scoped extraction to continue when marker persistence is unavailable.
This commit is contained in:
@@ -668,20 +668,25 @@ public final class JBindExtractorMain {
|
||||
return output;
|
||||
}
|
||||
|
||||
private static String normalizeEntryName(String value, String fallback) {
|
||||
String entry = value == null ? "" : value.trim();
|
||||
if (entry.length() == 0) {
|
||||
return fallback;
|
||||
}
|
||||
entry = entry.replace('\\', '/');
|
||||
private static String normalizeEntryName(String value, String fallback) {
|
||||
String entry = value == null ? "" : value;
|
||||
if (entry.trim().length() == 0) {
|
||||
return fallback;
|
||||
}
|
||||
entry = entry.replace('\\', '/');
|
||||
while (entry.startsWith("./")) {
|
||||
entry = entry.substring(2);
|
||||
}
|
||||
if (entry.length() == 0) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
String[] segments = entry.split("/", -1);
|
||||
if (entry.length() == 0) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
while (entry.endsWith("/")) {
|
||||
entry = entry.substring(0, entry.length() - 1);
|
||||
}
|
||||
validateWindowsEntryName(entry);
|
||||
|
||||
String[] segments = entry.split("/", -1);
|
||||
StringBuilder sanitized = new StringBuilder();
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
if (i > 0) {
|
||||
@@ -693,8 +698,33 @@ public final class JBindExtractorMain {
|
||||
if (entry.length() == 0) {
|
||||
return fallback;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
private static void validateWindowsEntryName(String entry) {
|
||||
if (entry.startsWith("/") || entry.matches("^[a-zA-Z]:.*")) {
|
||||
throw new IllegalArgumentException("Ungueltiger Windows-Archivpfad: " + entry);
|
||||
}
|
||||
String[] segments = entry.split("/", -1);
|
||||
for (String segment : segments) {
|
||||
if (segment.length() == 0 || ".".equals(segment) || "..".equals(segment)
|
||||
|| segment.endsWith(".") || segment.endsWith(" ")
|
||||
|| WINDOWS_SPECIAL_CHARS_RE.matcher(segment).find()) {
|
||||
throw new IllegalArgumentException("Ungueltiger Windows-Archivpfad: " + entry);
|
||||
}
|
||||
for (int i = 0; i < segment.length(); i++) {
|
||||
if (segment.charAt(i) < 32) {
|
||||
throw new IllegalArgumentException("Ungueltiger Windows-Archivpfad: " + entry);
|
||||
}
|
||||
}
|
||||
int dot = segment.indexOf('.');
|
||||
String base = (dot >= 0 ? segment.substring(0, dot) : segment).toUpperCase(Locale.ROOT);
|
||||
if ("CON".equals(base) || "PRN".equals(base) || "AUX".equals(base) || "NUL".equals(base)
|
||||
|| base.matches("COM[1-9¹²³]") || base.matches("LPT[1-9¹²³]")) {
|
||||
throw new IllegalArgumentException("Reservierter Windows-Archivpfad: " + entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static long safeSize(Long value) {
|
||||
if (value == null) {
|
||||
|
||||
Reference in New Issue
Block a user