//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #if FOUNDATION_FRAMEWORK internal import RegexBuilder #endif #if canImport(CollectionsInternal) internal import CollectionsInternal #elseif canImport(OrderedCollections) internal import OrderedCollections #elseif canImport(_FoundationCollections) internal import _FoundationCollections #endif extension URL.Template { struct Expression: Sendable, Hashable { var `operator`: Operator? var elements: [Element] struct Element: Sendable, Hashable { var name: URL.Template.VariableName var maximumLength: Int? var explode: Bool } enum Operator: String, Sendable, Hashable { /// `+` Reserved character strings; case reserved = "+" /// `#` Fragment identifiers prefixed by "#"; case fragment = "#" /// `.` Name labels or extensions prefixed by "."; case nameLabel = "." /// `/` Path segments prefixed by "/"; case pathSegment = "/" /// `;` Path parameter name or name=value pairs prefixed by ";"; case pathParameter = ";" /// `?` Query component beginning with "?" and consisting of /// name=value pairs separated by "&"; and, case queryComponent = "?" /// `&` Continuation of query-style &name=value pairs within /// a literal query component. case continuation = "&" } } } extension URL.Template { fileprivate struct InvalidExpression: Swift.Error { var text: String } } extension Substring { fileprivate mutating func popPrefixMatch(_ regex: Regex) throws -> Regex.Match? { guard let match = try regex.prefixMatch(in: self) else { return nil } self = self[match.range.upperBound.. let separatorRegex: Regex<(Substring)> let elementRegex: Regex<(Substring, Substring, Substring?, Substring??)> let uriTemplateRegex: Regex.RegexOutput>.RegexOutput)>.RegexOutput> private init() { self.operatorRegex = Regex { Optionally { Capture { One(.anyOf("+#./;?&")) } } } .asciiOnlyWordCharacters() .asciiOnlyDigits() .asciiOnlyCharacterClasses() self.separatorRegex = Regex { "," } .asciiOnlyWordCharacters() .asciiOnlyDigits() .asciiOnlyCharacterClasses() self.elementRegex = Regex { Capture { One(("a"..."z").union("A"..."Z")) ZeroOrMore(("a"..."z").union("A"..."Z").union("0"..."9").union(.anyOf("_"))) } Optionally { Capture { ChoiceOf { Regex { ":" Capture { ZeroOrMore(.digit) } } "*" } } } } .asciiOnlyWordCharacters() .asciiOnlyDigits() .asciiOnlyCharacterClasses() self.uriTemplateRegex = Regex { "{" Capture { OneOrMore { CharacterClass.any.subtracting(.anyOf("}")) } } "}" } } } } #endif // .------------------------------------------------------------------. // | NUL + . / ; ? & # | // |------------------------------------------------------------------| // | first | "" "" "." "/" ";" "?" "&" "#" | // | sep | "," "," "." "/" ";" "&" "&" "," | // | named | false false false false true true true false | // | ifemp | "" "" "" "" "" "=" "=" "" | // | allow | U U+R U U U U U U+R | // `------------------------------------------------------------------' extension URL.Template.Expression.Operator? { var firstPrefix: Character? { switch self { case nil: return nil case .reserved?: return nil case .nameLabel?: return "." case .pathSegment?: return "/" case .pathParameter?: return ";" case .queryComponent?: return "?" case .continuation?: return "&" case .fragment?: return "#" } } var separator: Character { switch self { case nil: return "," case .reserved?: return "," case .nameLabel?: return "." case .pathSegment?: return "/" case .pathParameter?: return ";" case .queryComponent?: return "&" case .continuation?: return "&" case .fragment?: return "," } } var isNamed: Bool { switch self { case nil: return false case .reserved?: return false case .nameLabel?: return false case .pathSegment?: return false case .pathParameter?: return true case .queryComponent?: return true case .continuation?: return true case .fragment?: return false } } var replacementForEmpty: Character? { switch self { case nil: return nil case .reserved?: return nil case .nameLabel?: return nil case .pathSegment?: return nil case .pathParameter?: return nil case .queryComponent?: return "=" case .continuation?: return "=" case .fragment?: return nil } } var allowedCharacters: URL.Template.Expression.Operator.AllowedCharacters { switch self { case nil: return .unreserved case .reserved?: return .unreservedReserved case .nameLabel?: return .unreserved case .pathSegment?: return .unreserved case .pathParameter?: return .unreserved case .queryComponent?: return .unreserved case .continuation?: return .unreserved case .fragment?: return .unreservedReserved } } } extension URL.Template.Expression.Operator { enum AllowedCharacters { case unreserved // The union of (unreserved / reserved / pct-encoded) case unreservedReserved } }