Move SSD tree path validation to typed Kotlin
ober
1003f2058176cf5b91c8031c1256307a62348fef
--- a/templates/ssd-review.ss +++ b/templates/ssd-review.ss @@ -5286,7 +5286,9 @@ (typed-kotlin-file "com/sfb/ssdreview/PathSafety.kt" (typed-library (com sfb ssdreview) (export safePathComponent safeLeaf safeZipEntryName validSourceKey - validatedSourceKey treeFileMime) + validatedSourceKey treeFileMime + safeTreePathParts treePathLeaf treePathDirectoryParts) + (type Int32) (def (safePathComponent (name : String)) : Bool (let ((n (string-length name))) (and (and (>= n 1) (<= n 128)) @@ -5303,6 +5305,22 @@ (if (string-ends-with? name ".jsonl") "application/x-ndjson" "application/octet-stream"))) + (def (safeTreePathParts (parts : (List String)) + (maxDepth : Int32)) : Bool + (let ((n (list-size parts))) + (and (and (>= n (int32 1)) (<= n maxDepth)) + (for/fold ((safe #t)) + ((i (in-range (int32 0) n))) + (and safe (safePathComponent (list-ref parts i))))))) + (def (treePathLeaf (parts : (List String))) : String + (list-ref parts (- (list-size parts) (int32 1)))) + (def (treePathDirectoryParts (parts : (List String))) : (MutableList String) + (for/fold ((out (mutable-list-empty String))) + ((i (in-range (int32 0) + (- (list-size parts) (int32 1))))) + (begin + (mutable-list-add! out (list-ref parts i)) + out))) (def (safeZipEntryName (name : String)) : Bool (let ((n (string-length name))) (and (and (>= n 1) (<= n 240)) @@ -8503,10 +8521,10 @@ " require(source.isFile && !Files.isSymbolicLink(source.toPath()))" " require(source.length() <= MAX_ZIP_ENTRY_BYTES) { \"Tree export file exceeds byte limit\" }" " val parts = trimmedNonBlankPathParts(relativeName, File.separatorChar, '/')" - " require(parts.size in 1..MAX_ZIP_DEPTH && parts.all { safePathComponent(it) })" - " val fileName = parts.lastOrNull() ?: return" + " require(safeTreePathParts(parts, MAX_ZIP_DEPTH))" + " val fileName = treePathLeaf(parts)" " var dir = parent" - " parts.dropLast(1).forEach { dir = ensureTreeDir(dir, it) }" + " treePathDirectoryParts(parts).forEach { dir = ensureTreeDir(dir, it) }" " dir.findFile(fileName)?.delete()" " val mime = treeFileMime(fileName)" " val doc = dir.createFile(mime, fileName)"