Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Sources/App/ZIP/ZipInspector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 == "." }),
Expand All @@ -150,7 +154,7 @@ public enum ZipInspector {
selected: Set<String>?,
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
Expand Down
13 changes: 13 additions & 0 deletions Tests/ZipInspectorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
Loading