diff --git a/Sources/PklSwift/EvaluatorOptions.swift b/Sources/PklSwift/EvaluatorOptions.swift index 065bcb6..849591c 100644 --- a/Sources/PklSwift/EvaluatorOptions.swift +++ b/Sources/PklSwift/EvaluatorOptions.swift @@ -244,7 +244,7 @@ extension EvaluatorOptions { public func withModuleReader(_ reader: ModuleReader) -> EvaluatorOptions { var options = self var allowedModules = options.allowedModules ?? [] - allowedModules.append(reader.scheme) + allowedModules.append(quoteRegex(reader.scheme+":")) var moduleReaders = options.moduleReaders ?? [] moduleReaders.append(reader) options.allowedModules = allowedModules @@ -256,7 +256,7 @@ extension EvaluatorOptions { public func withResourceReader(_ reader: ResourceReader) -> EvaluatorOptions { var options = self var allowedResources = options.allowedResources ?? [] - allowedResources.append(reader.scheme) + allowedResources.append(quoteRegex(reader.scheme+":")) var resourceReaders = options.resourceReaders ?? [] resourceReaders.append(reader) options.allowedResources = allowedResources diff --git a/Sources/PklSwift/Utils.swift b/Sources/PklSwift/Utils.swift index 092a981..a949ee6 100644 --- a/Sources/PklSwift/Utils.swift +++ b/Sources/PklSwift/Utils.swift @@ -118,3 +118,14 @@ public struct OptionalDictionaryKey: Hashable, Decodable, CodingKeyRepr fatalError("cannot initialize OptionalDictionaryKey from CodingKey") } } + +private let regexSpecialCharacters = CharacterSet(charactersIn: #"\.+*?()|[]{}^$"#) + +func quoteRegex(_ pattern: String) -> String { + if pattern.rangeOfCharacter(from: regexSpecialCharacters) == nil { + return pattern + } + + // return quoted pattern per https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html + return #"\Q\#(pattern)\E"# +} diff --git a/Tests/PklSwiftTests/EvaluatorTest.swift b/Tests/PklSwiftTests/EvaluatorTest.swift index 846cd4c..042ae6c 100644 --- a/Tests/PklSwiftTests/EvaluatorTest.swift +++ b/Tests/PklSwiftTests/EvaluatorTest.swift @@ -347,6 +347,23 @@ final class PklSwiftTests: XCTestCase { XCTAssertEqual(output, #"result = "yes pizza"\#n"#) } + func testCustomResourceReaderWithSchemeContainingRegexControlCharacters() async throws { + let reader = VirtualResourceReader( + scheme: "foo+bar.baz", + isGlobbable: false, + hasHierarchicalUris: false, + read: { _ in [UInt8]("Hello, World!".utf8) }, + listElements: { _ in [] } + ) + let options = EvaluatorOptions.preconfigured.withResourceReader(reader) + XCTAssert(options.allowedResources!.contains(#"\Qfoo+bar.baz:\E"#)) + let evaluator = try await manager.newEvaluator(options: options) + let output = try await evaluator.evaluateOutputText(source: .text(""" + result = read("foo+bar.baz:quz").text + """)) + XCTAssertEqual(output, #"result = "Hello, World!"\#n"#) + } + func testFailedEvaluation() async throws { let evaluator = try await manager.newEvaluator(options: EvaluatorOptions.preconfigured) do {