PadXcode-iPad/Validation/FileOperationValidator.swift
2026-04-12 00:46:30 -07:00

50 lines
2 KiB
Swift

import Foundation
enum FileValidationError: LocalizedError {
case pathTraversal(String)
case emptyPath
case exceedsMaxSize(Int, Int)
case unsupportedBinaryExtension(String)
case reservedSystemPath(String)
var errorDescription: String? {
switch self {
case .pathTraversal(let p): return "Path traversal in '\(p)'"
case .emptyPath: return "File path is empty"
case .exceedsMaxSize(let a, let l): return "File \(a/1_048_576)MB exceeds \(l/1_048_576)MB limit"
case .unsupportedBinaryExtension(let e): return ".\(e) is binary and cannot be text-edited"
case .reservedSystemPath(let p): return "'\(p)' is a system path"
}
}
}
enum FileOperationValidator {
private static let maxBytes: Int = 10 * 1_048_576
private static let binaryExtensions: Set<String> = [
"png","jpg","jpeg","gif","webp","pdf","zip","tar","gz",
"ipa","a","dylib","framework","app","xcassets","car","nib",
"storyboard","mp3","mp4","mov"
]
private static let reservedPrefixes = ["/System/","/usr/","/bin/","/sbin/","/private/var/db/"]
static func validateSave(path: String, content: String) throws {
try validatePath(path)
guard content.utf8.count <= maxBytes else {
throw FileValidationError.exceedsMaxSize(content.utf8.count, maxBytes)
}
}
static func validatePath(_ path: String) throws {
guard !path.isEmpty else { throw FileValidationError.emptyPath }
if path.contains("../") || path.contains("/..") {
throw FileValidationError.pathTraversal(path)
}
for prefix in reservedPrefixes where path.hasPrefix(prefix) {
throw FileValidationError.reservedSystemPath(path)
}
let ext = (path as NSString).pathExtension.lowercased()
if binaryExtensions.contains(ext) {
throw FileValidationError.unsupportedBinaryExtension(ext)
}
}
}