@@ -627,6 +627,36 @@ extension KeyValueStringSchema {
627
627
}
628
628
}
629
629
630
+ internal enum KeyValueStringSchemaVerificationFailure : Error , CustomStringConvertible {
631
+
632
+ case preferredKeyValueDelimiterIsInvalid
633
+ case preferredKeyValueDelimiterNotRecognized
634
+ case preferredPairDelimiterIsInvalid
635
+ case preferredPairDelimiterNotRecognized
636
+ case invalidKeyValueDelimiterIsRecognized
637
+ case invalidPairDelimiterIsRecognized
638
+ case inconsistentSpaceEncoding
639
+
640
+ public var description : String {
641
+ switch self {
642
+ case . preferredKeyValueDelimiterIsInvalid:
643
+ return " Schema's preferred key-value delimiter is invalid "
644
+ case . preferredKeyValueDelimiterNotRecognized:
645
+ return " Schema does not recognize its preferred key-value delimiter as a key-value delimiter "
646
+ case . preferredPairDelimiterIsInvalid:
647
+ return " Schema's preferred pair delimiter is invalid "
648
+ case . preferredPairDelimiterNotRecognized:
649
+ return " Schema does not recognize its preferred pair delimiter as a pair delimiter "
650
+ case . invalidKeyValueDelimiterIsRecognized:
651
+ return " isKeyValueDelimiter recognizes an invalid delimiter "
652
+ case . invalidPairDelimiterIsRecognized:
653
+ return " isPairDelimiter recognizes an invalid delimiter "
654
+ case . inconsistentSpaceEncoding:
655
+ return " encodeSpaceAsPlus is true, so decodePlusAsSpace must also be true "
656
+ }
657
+ }
658
+ }
659
+
630
660
extension KeyValueStringSchema {
631
661
632
662
/// Checks this schema for consistency.
@@ -658,19 +688,47 @@ extension KeyValueStringSchema {
658
688
/// > For those that do, it is recommended to run this verification
659
689
/// > as part of your regular unit tests.
660
690
///
661
- public func verify( for component: KeyValuePairsSupportedComponent ) {
691
+ public func verify( for component: KeyValuePairsSupportedComponent ) throws {
692
+
693
+ // Preferred delimiters must not require escaping.
662
694
663
- let delimiters = verifyDelimitersDoNotNeedEscaping ( in: component)
695
+ let preferredDelimiters = verifyDelimitersDoNotNeedEscaping ( in: component)
664
696
665
- if !isKeyValueDelimiter ( delimiters . keyValue) {
666
- fatalError ( " Inconsistent schema: preferred key-value delimiter is not recognized as a key-value delimiter " )
697
+ if preferredDelimiters . keyValue == . max {
698
+ throw KeyValueStringSchemaVerificationFailure . preferredKeyValueDelimiterIsInvalid
667
699
}
668
- if !isPairDelimiter ( delimiters . pair) {
669
- fatalError ( " Inconsistent schema: preferred pair delimiter is not recognized as a pair delimiter " )
700
+ if preferredDelimiters . pair == . max {
701
+ throw KeyValueStringSchemaVerificationFailure . preferredPairDelimiterIsInvalid
670
702
}
703
+
704
+ // isKeyValueDelimiter/isPairDelimiter must recognize preferred delimiters,
705
+ // and must not recognize other reserved characters (e.g. %, ASCII hex digits, +).
706
+
707
+ if !isKeyValueDelimiter( preferredDelimiters. keyValue) {
708
+ throw KeyValueStringSchemaVerificationFailure . preferredKeyValueDelimiterNotRecognized
709
+ }
710
+ if !isPairDelimiter( preferredDelimiters. pair) {
711
+ throw KeyValueStringSchemaVerificationFailure . preferredPairDelimiterNotRecognized
712
+ }
713
+
714
+ func delimiterPredicateIsInvalid( _ isDelimiter: ( UInt8 ) -> Bool ) -> Bool {
715
+ " 0123456789abcdefABCDEF%+ " . utf8. contains ( where: isDelimiter)
716
+ }
717
+
718
+ if delimiterPredicateIsInvalid ( isKeyValueDelimiter) {
719
+ throw KeyValueStringSchemaVerificationFailure . invalidKeyValueDelimiterIsRecognized
720
+ }
721
+ if delimiterPredicateIsInvalid ( isPairDelimiter) {
722
+ throw KeyValueStringSchemaVerificationFailure . invalidPairDelimiterIsRecognized
723
+ }
724
+
725
+ // Space encoding must be consistent.
726
+
671
727
if encodeSpaceAsPlus, !decodePlusAsSpace {
672
- fatalError ( " Inconsistent schema: encodeSpaceAsPlus is true, so decodePlusAsSpace must also be true " )
728
+ throw KeyValueStringSchemaVerificationFailure . inconsistentSpaceEncoding
673
729
}
730
+
731
+ // All checks passed.
674
732
}
675
733
}
676
734
@@ -1064,7 +1122,7 @@ extension WebURL.KeyValuePairs: CustomStringConvertible {
1064
1122
1065
1123
1066
1124
// --------------------------------------------
1067
- // MARK: - Reading: Collection
1125
+ // MARK: - Reading: By Location.
1068
1126
// --------------------------------------------
1069
1127
1070
1128
@@ -1218,7 +1276,7 @@ extension WebURL.KeyValuePairs: Collection {
1218
1276
}
1219
1277
1220
1278
1221
- // MARK: - TODO: BidirectionalCollection.
1279
+ // MARK: TODO: BidirectionalCollection.
1222
1280
1223
1281
1224
1282
extension WebURL . KeyValuePairs {
@@ -2522,41 +2580,40 @@ extension KeyValueStringSchema {
2522
2580
// - Must not be the percent sign (`%`), plus sign (`+`), space, or a hex digit, and
2523
2581
// - Must not require escaping in the URL component(s) used with this schema.
2524
2582
2525
- precondition (
2526
- ASCII ( keyValueDelimiter) ? . isHexDigit == false
2527
- && keyValueDelimiter != ASCII . percentSign. codePoint
2528
- && keyValueDelimiter != ASCII . plus. codePoint,
2529
- " Schema's preferred key-value delimiter is invalid "
2530
- )
2531
- precondition (
2532
- ASCII ( pairDelimiter) ? . isHexDigit == false
2533
- && pairDelimiter != ASCII . percentSign. codePoint
2534
- && pairDelimiter != ASCII . plus. codePoint,
2535
- " Schema's preferred pair delimiter is invalid "
2536
- )
2583
+ guard
2584
+ ASCII ( keyValueDelimiter) ? . isHexDigit == false ,
2585
+ keyValueDelimiter != ASCII . percentSign. codePoint,
2586
+ keyValueDelimiter != ASCII . plus. codePoint
2587
+ else {
2588
+ return ( . max, 0 )
2589
+ }
2590
+ guard
2591
+ ASCII ( pairDelimiter) ? . isHexDigit == false ,
2592
+ pairDelimiter != ASCII . percentSign. codePoint,
2593
+ pairDelimiter != ASCII . plus. codePoint
2594
+ else {
2595
+ return ( 0 , . max)
2596
+ }
2537
2597
2538
2598
switch component. value {
2539
2599
case . query:
2540
2600
let encodeSet = URLEncodeSet . SpecialQuery ( )
2541
- precondition (
2542
- !encodeSet. shouldPercentEncode ( ascii: keyValueDelimiter) ,
2543
- " Schema's preferred key-value delimiter may not be used in the query "
2544
- )
2545
- precondition (
2546
- !encodeSet. shouldPercentEncode ( ascii: pairDelimiter) ,
2547
- " Schema's preferred pair delimiter may not be used in the query "
2548
- )
2601
+ guard !encodeSet. shouldPercentEncode ( ascii: keyValueDelimiter) else {
2602
+ return ( . max, 0 )
2603
+ }
2604
+ guard !encodeSet. shouldPercentEncode ( ascii: pairDelimiter) else {
2605
+ return ( 0 , . max)
2606
+ }
2549
2607
case . fragment:
2550
2608
let encodeSet = URLEncodeSet . Fragment ( )
2551
- precondition (
2552
- !encodeSet. shouldPercentEncode ( ascii: keyValueDelimiter) ,
2553
- " Schema's preferred key-value delimiter may not be used in the fragment "
2554
- )
2555
- precondition (
2556
- !encodeSet. shouldPercentEncode ( ascii: pairDelimiter) ,
2557
- " Schema's preferred pair delimiter may not be used in the fragment "
2558
- )
2609
+ guard !encodeSet. shouldPercentEncode ( ascii: keyValueDelimiter) else {
2610
+ return ( . max, 0 )
2611
+ }
2612
+ guard !encodeSet. shouldPercentEncode ( ascii: pairDelimiter) else {
2613
+ return ( 0 , . max)
2614
+ }
2559
2615
}
2616
+
2560
2617
return ( keyValueDelimiter, pairDelimiter)
2561
2618
}
2562
2619
@@ -2587,6 +2644,17 @@ extension KeyValuePairsSupportedComponent {
2587
2644
}
2588
2645
}
2589
2646
2647
+ @inlinable
2648
+ internal func _trapOnInvalidDelimiters(
2649
+ _ delimiters: ( keyValue: UInt8 , pair: UInt8 )
2650
+ ) -> ( keyValue: UInt8 , pair: UInt8 ) {
2651
+ precondition (
2652
+ delimiters. keyValue != . max && delimiters. pair != . max,
2653
+ " Schema has invalid delimiters "
2654
+ )
2655
+ return delimiters
2656
+ }
2657
+
2590
2658
extension URLStorage {
2591
2659
2592
2660
/// Replaces the given range of key-value pairs with a new collection of key-value pairs.
@@ -2642,7 +2710,9 @@ extension URLStorage {
2642
2710
return . success( newUpper..< newUpper)
2643
2711
}
2644
2712
2645
- let ( keyValueDelimiter, pairDelimiter) = schema. verifyDelimitersDoNotNeedEscaping ( in: component)
2713
+ let ( keyValueDelimiter, pairDelimiter) = _trapOnInvalidDelimiters (
2714
+ schema. verifyDelimitersDoNotNeedEscaping ( in: component)
2715
+ )
2646
2716
2647
2717
// Measure the replacement string.
2648
2718
@@ -2971,7 +3041,7 @@ extension URLStorage {
2971
3041
2972
3042
if insertDelimiter {
2973
3043
precondition ( !buffer. isEmpty)
2974
- buffer [ 0 ] = schema. verifyDelimitersDoNotNeedEscaping ( in: component) . keyValue
3044
+ buffer [ 0 ] = _trapOnInvalidDelimiters ( schema. verifyDelimitersDoNotNeedEscaping ( in: component) ) . keyValue
2975
3045
buffer = UnsafeMutableBufferPointer (
2976
3046
start: buffer. baseAddress! + 1 ,
2977
3047
count: buffer. count &- 1
0 commit comments