@@ -516,36 +516,31 @@ extension StructuredFieldValueParser {
516516 throw StructuredHeaderError . invalidDisplayString
517517 }
518518
519- let startIndex = self . underlyingData. startIndex
520- let secondIndex = self . underlyingData. index ( after: startIndex)
521- let octetHex = self . underlyingData [ ... secondIndex]
519+ let octetHex = EncodedHex ( ArraySlice ( self . underlyingData. prefix ( 2 ) ) )
522520
523521 self . underlyingData = self . underlyingData. dropFirst ( 2 )
524522
525- guard
526- octetHex. allSatisfy ( { asciiDigits. contains ( $0) || asciiLowercases. contains ( $0) } ) ,
527- let octet = UInt8 . decodeHex ( octetHex)
528- else {
523+ guard let octet = octetHex. decode ( ) else {
529524 throw StructuredHeaderError . invalidDisplayString
530525 }
531526
532527 byteArray. append ( octet)
533528 case asciiDquote:
534- let unicodeSequence = try byteArray. withUnsafeBytes {
535- try $0. withMemoryRebound ( to: CChar . self) {
536- guard let baseAddress = $0. baseAddress else {
537- throw StructuredHeaderError . invalidDisplayString
538- }
529+ #if compiler(>=6.0)
530+ if #available( macOS 15 . 0 , iOS 18 . 0 , tvOS 18 . 0 , watchOS 11 . 0 , * ) {
531+ let unicodeSequence = String ( validating: byteArray, as: UTF8 . self)
539532
540- return String ( validatingUTF8: baseAddress)
533+ guard let unicodeSequence else {
534+ throw StructuredHeaderError . invalidDisplayString
541535 }
542- }
543536
544- guard let unicodeSequence else {
545- throw StructuredHeaderError . invalidDisplayString
537+ return . displayString( unicodeSequence)
538+ } else {
539+ return try _decodeDisplayString ( byteArray: & byteArray)
546540 }
547-
548- return . displayString( unicodeSequence)
541+ #else
542+ return try _decodeDisplayString ( byteArray: & byteArray)
543+ #endif
549544 default :
550545 byteArray. append ( char)
551546 }
@@ -555,6 +550,30 @@ extension StructuredFieldValueParser {
555550 throw StructuredHeaderError . invalidDisplayString
556551 }
557552
553+ /// This method is called in environments where `String(validating:as:)` is unavailable. It uses
554+ /// `String(validatingUTF8:)` which requires `byteArray` to be null terminated. `String(validating:as:)`
555+ /// does not require that requirement. Therefore, it does not perform null checks, which makes it more optimal.
556+ private func _decodeDisplayString( byteArray: inout [ UInt8] ) throws -> RFC9651 BareItem {
557+ // String(validatingUTF8:) requires byteArray to be null-terminated.
558+ byteArray. append ( 0 )
559+
560+ let unicodeSequence = try byteArray. withUnsafeBytes {
561+ try $0. withMemoryRebound ( to: CChar . self) {
562+ guard let baseAddress = $0. baseAddress else {
563+ throw StructuredHeaderError . invalidDisplayString
564+ }
565+
566+ return String ( validatingUTF8: baseAddress)
567+ }
568+ }
569+
570+ guard let unicodeSequence else {
571+ throw StructuredHeaderError . invalidDisplayString
572+ }
573+
574+ return . displayString( unicodeSequence)
575+ }
576+
558577 private mutating func _parseParameters( ) throws -> OrderedMap< Key, RFC9651 BareItem> {
559578 var parameters = OrderedMap < Key , RFC9651BareItem > ( )
560579
@@ -708,33 +727,36 @@ extension StrippingStringEscapesCollection.Index: Comparable {
708727 }
709728}
710729
711- extension UInt8 {
712- /// Converts a hex value given in UTF8 to base 10.
713- fileprivate static func decodeHex< Bytes: RandomAccessCollection > ( _ bytes: Bytes ) -> Self ?
714- where Bytes. Element == Self {
715- var result = Self ( 0 )
716- var power = Self ( bytes. count)
730+ /// `EncodedHex` represents a (possibly invalid) hex value in UTF8.
731+ struct EncodedHex {
732+ private( set) var firstChar : UInt8
733+ private( set) var secondChar : UInt8
717734
718- for byte in bytes {
719- power -= 1
735+ init ( _ slice: ArraySlice < UInt8 > ) {
736+ precondition ( slice. count == 2 )
737+ self . firstChar = slice [ slice. startIndex]
738+ self . secondChar = slice [ slice. index ( after: slice. startIndex) ]
739+ }
720740
721- guard let integer = Self . htoi ( byte) else { return nil }
722- result += integer << ( power * 4 )
723- }
741+ /// Validates and converts `EncodedHex` to a base 10 UInt8.
742+ ///
743+ /// If `EncodedHex` does not represent a valid hex value, the result of this method is nil.
744+ fileprivate func decode( ) -> UInt8 ? {
745+ guard
746+ let firstCharAsInteger = self . htoi ( self . firstChar) ,
747+ let secondCharAsInteger = self . htoi ( self . secondChar)
748+ else { return nil }
724749
725- return result
750+ return ( firstCharAsInteger << 4 ) + secondCharAsInteger
726751 }
727752
728753 /// Converts a hex character given in UTF8 to its integer value.
729- private static func htoi( _ value: Self ) -> Self ? {
730- let charA = Self ( UnicodeScalar ( " a " ) . value)
731- let char0 = Self ( UnicodeScalar ( " 0 " ) . value)
732-
733- switch value {
734- case char0... char0 + 9 :
735- return value - char0
736- case charA... charA + 5 :
737- return value - charA + 10
754+ private func htoi( _ asciiChar: UInt8 ) -> UInt8 ? {
755+ switch asciiChar {
756+ case asciiZero... asciiNine:
757+ return asciiChar - asciiZero
758+ case asciiLowerA... asciiLowerF:
759+ return asciiChar - asciiLowerA + 10
738760 default :
739761 return nil
740762 }
0 commit comments