diff --git a/Sources/App/ZIP/ZipInspector.swift b/Sources/App/ZIP/ZipInspector.swift index da71b65..b5919ef 100644 --- a/Sources/App/ZIP/ZipInspector.swift +++ b/Sources/App/ZIP/ZipInspector.swift @@ -134,7 +134,11 @@ public enum ZipInspector { } private static func validatePath(_ name: String) throws { - guard !name.hasPrefix("/"), !name.hasPrefix("\\"), + guard !name.hasPrefix("/"), !name.hasPrefix("\\"), !name.hasPrefix("-"), + // unzip treats selected entries as glob patterns, not literal + // strings. Reject patterns rather than expanding selections. + !name.contains("*"), !name.contains("?"), + !name.contains("["), !name.contains("]"), !name.contains("\\"), !name.contains("\0"), !name.contains(":"), !name.contains("//"), name.utf8.count <= 1024, !name.split(separator: "/").contains(where: { $0 == ".." || $0 == "." }), @@ -150,7 +154,7 @@ public enum ZipInspector { selected: Set?, shouldCancel: @escaping @Sendable () -> Bool = { false }) throws -> URL { let listing = try inspect(archive) - let picked = listing.filter { selected == nil || selected!.contains($0.name) } + let picked = listing.filter { selected?.contains($0.name) ?? true } guard !picked.isEmpty else { throw Invalid.malformed("No ZIP entries selected.") } guard !shouldCancel() else { throw CancellationError() } let fm = FileManager.default diff --git a/Tests/ZipInspectorTests.swift b/Tests/ZipInspectorTests.swift index 801a4f4..367f3d2 100644 --- a/Tests/ZipInspectorTests.swift +++ b/Tests/ZipInspectorTests.swift @@ -49,6 +49,19 @@ struct ZipInspectorTests { catch { rejected = true } assert(rejected, "Traversal must be refused before extraction.") } + // unzip interprets entry selections as argv options or filename globs. + // Refuse these at inspection instead of broadening a selective extract. + let original = [UInt8](try Data(contentsOf: archive)) + for byte in [UInt8(45), UInt8(42), UInt8(63), UInt8(91)] { + var edited = original + edited[start] = byte + let candidate = tmp.appendingPathComponent("selector-\(Int(byte)).zip") + try Data(edited).write(to: candidate) + var rejected = false + do { _ = try ZipInspector.inspect(candidate) } + catch { rejected = true } + assert(rejected, "Option-like or wildcard ZIP entry names must be rejected.") + } print("ZipInspectorTests passed") } }