55
66import Foundation
77
8+ fileprivate extension String {
9+ func readNextQuoted( startIdx: String . Index , delim: Character = " , " ) throws -> ( String . Index , String ) {
10+ // startIdx is start of the quoted value, there must be at least an ending quotation mark
11+ if !( self . index ( after: startIdx) < self . endIndex) {
12+ throw HeaderDeserializationError . invalidStringHeaderList ( value: self )
13+ }
14+
15+ // find first non-escaped quote or end of string
16+ var endIdx = self . index ( after: startIdx)
17+ while endIdx < self . endIndex {
18+ let char = self [ endIdx]
19+ if char == " \\ " {
20+ // skip escaped chars
21+ endIdx = self . index ( after: endIdx)
22+ } else if char == " \" " {
23+ break
24+ }
25+ endIdx = self . index ( after: endIdx)
26+ }
27+
28+ let next = self [ self . index ( after: startIdx) ..< endIdx]
29+
30+ // consume trailing quote
31+ if endIdx >= self . endIndex || self [ endIdx] != " \" " {
32+ throw HeaderDeserializationError . invalidStringHeaderList ( value: self )
33+ }
34+ assert ( endIdx < self . endIndex)
35+ assert ( self [ endIdx] == " \" " )
36+
37+ endIdx = self . index ( after: endIdx)
38+
39+ // consume delim
40+ while endIdx < self . endIndex {
41+ let char = self [ endIdx]
42+ if char == " " || char == " \t " {
43+ endIdx = self . index ( after: endIdx)
44+ } else if char == delim {
45+ endIdx = self . index ( after: endIdx)
46+ break
47+ } else {
48+ throw HeaderDeserializationError . invalidStringHeaderList ( value: self )
49+ }
50+ }
51+
52+ let unescaped = next. replacingOccurrences ( of: " \\ \" " , with: " \" " )
53+ . replacingOccurrences ( of: " \\ \\ " , with: " \\ " )
54+
55+ return ( endIdx, unescaped)
56+ }
57+
58+ func readNextUnquoted( startIdx: String . Index , delim: Character = " , " ) -> ( String . Index , String ) {
59+ assert ( startIdx < self . endIndex)
60+
61+ var endIdx = startIdx
62+
63+ while endIdx < self . endIndex && self [ endIdx] != delim {
64+ endIdx = self . index ( after: endIdx)
65+ }
66+
67+ let next = self [ startIdx..< endIdx]
68+ if endIdx < self . endIndex && self [ endIdx] == delim {
69+ endIdx = self . index ( after: endIdx)
70+ }
71+
72+ return ( endIdx, next. trim ( ) )
73+ }
74+ }
75+
76+ // chars in an HTTP header value that require quotations
77+ private let QUOTABLE_HEADER_VALUE_CHARS = " \" ,() "
78+
79+ public func quoteHeaderValue( _ value: String ) -> String {
80+ if value. trim ( ) . count != value. count || value. contains ( where: { char1 in
81+ QUOTABLE_HEADER_VALUE_CHARS . contains { char2 in
82+ char1 == char2
83+ }
84+ } ) {
85+ let formatted = value. replacingOccurrences ( of: " \\ " , with: " \\ \\ " )
86+ . replacingOccurrences ( of: " \" " , with: " \\ \" " )
87+ return " \" \( formatted) \" "
88+ } else {
89+ return value
90+ }
91+ }
92+
893/// Expands the compact Header Representation of List of any type except Dates
9- public func splitHeaderListValues( _ value: String ? ) -> [ String ] ? {
10- guard let value = value else { return nil }
11- return value. components ( separatedBy: " , " ) . map { $0. trim ( ) }
94+ public func splitHeaderListValues( _ value: String ? ) throws -> [ String ] ? {
95+ guard let value = value else {
96+ return nil
97+ }
98+ var results : [ String ] = [ ]
99+ var currIdx = value. startIndex
100+
101+ while currIdx < value. endIndex {
102+ let next : ( idx: String . Index , str: String )
103+
104+ switch value [ currIdx] {
105+ case " " , " \t " :
106+ currIdx = value. index ( after: currIdx)
107+ continue
108+ case " \" " :
109+ next = try value. readNextQuoted ( startIdx: currIdx)
110+ default :
111+ next = value. readNextUnquoted ( startIdx: currIdx)
112+ }
113+
114+ currIdx = next. idx
115+ results. append ( next. str)
116+ }
117+
118+ return results
119+
12120}
13121
14122/// Expands the compact HTTP Header Representation of List of Dates
15123public func splitHttpDateHeaderListValues( _ value: String ? ) throws -> [ String ] ? {
16124 guard let value = value else { return nil }
17125
18- let separator = " , "
19- let totalSeparators = value . components ( separatedBy : separator ) . count - 1
20- if totalSeparators <= 1 {
126+ let n = value . filter ( { $0 == " , " } ) . count
127+
128+ if n <= 1 {
21129 return [ value]
22- } else if totalSeparators % 2 == 0 {
23- return splitHeaderListValues ( value)
130+ } else if n % 2 == 0 {
131+ throw HeaderDeserializationError . invalidTimestampHeaderList ( value : value)
24132 }
25133
26134 var cnt = 0
27135 var splits : [ String ] = [ ]
28- var start = 0
29- var startIdx = value. index ( value. startIndex, offsetBy: start)
30-
31- for i in 1 ... value. count {
32- let currIdx = value. index ( value. startIndex, offsetBy: i- 1 )
33- if value [ currIdx] == " , " {
136+ var startIdx = value. startIndex
137+
138+ for i in value. indices [ value. startIndex..< value. endIndex] {
139+ if value [ i] == " , " {
34140 cnt += 1
35141 }
36-
142+
37143 // split on every other ','
38144 if cnt > 1 {
39- startIdx = value. index ( value. startIndex, offsetBy: start)
40- splits. append ( String ( value [ startIdx..< currIdx] ) . trim ( ) )
41- start = i + 1
145+ splits. append ( value [ startIdx..< i] . trim ( ) )
146+ startIdx = value. index ( after: i)
42147 cnt = 0
43148 }
44149 }
45-
46- if start < value. count {
47- startIdx = value. index ( value. startIndex, offsetBy: start)
48- splits. append ( String ( value [ startIdx..< value. endIndex] ) . trim ( ) )
150+
151+ if startIdx < value. endIndex {
152+ splits. append ( value [ startIdx... ] . trim ( ) )
49153 }
50-
154+
51155 return splits
52156}
53157
@@ -64,19 +168,19 @@ extension HeaderDeserializationError: LocalizedError {
64168 switch self {
65169 case . invalidTimestampHeaderList( let value) :
66170 return NSLocalizedString ( " Invalid HTTP Header List with Timestamps: \( value) " ,
67- comment: " Client Deserialization Error " )
171+ comment: " Client Deserialization Error " )
68172 case . invalidTimestampHeader( let value) :
69173 return NSLocalizedString ( " Invalid HTTP Header with Timestamp: \( value) " ,
70- comment: " Client Deserialization Error " )
174+ comment: " Client Deserialization Error " )
71175 case . invalidBooleanHeaderList( let value) :
72176 return NSLocalizedString ( " Invalid HTTP Header List with Booleans: \( value) " ,
73- comment: " Client Deserialization Error " )
177+ comment: " Client Deserialization Error " )
74178 case . invalidNumbersHeaderList( let value) :
75179 return NSLocalizedString ( " Invalid HTTP Header List with Booleans: \( value) " ,
76- comment: " Client Deserialization Error " )
180+ comment: " Client Deserialization Error " )
77181 case . invalidStringHeaderList( let value) :
78182 return NSLocalizedString ( " Invalid HTTP Header List with Strings: \( value) " ,
79- comment: " Client Deserialization Error " )
183+ comment: " Client Deserialization Error " )
80184 }
81185 }
82186}
0 commit comments