@@ -150,6 +150,9 @@ internal enum URLSessionTransportError: Error {
150150 /// Returned `URLResponse` could not be converted to `HTTPURLResponse`.
151151 case notHTTPResponse( URLResponse )
152152
153+ /// Returned `HTTPURLResponse` has an invalid status code
154+ case invalidResponseStatusCode( HTTPURLResponse )
155+
153156 /// Returned `URLResponse` was nil
154157 case noResponse( url: URL ? )
155158
@@ -162,14 +165,18 @@ extension HTTPResponse {
162165 guard let httpResponse = urlResponse as? HTTPURLResponse else {
163166 throw URLSessionTransportError . notHTTPResponse ( urlResponse)
164167 }
165- var headerFields = HTTPFields ( )
166- for (headerName, headerValue) in httpResponse. allHeaderFields {
167- guard let rawName = headerName as? String , let name = HTTPField . Name ( rawName) ,
168- let value = headerValue as? String
169- else { continue }
170- headerFields [ name] = value
168+ guard ( 0 ... 999 ) . contains ( httpResponse. statusCode) else {
169+ throw URLSessionTransportError . invalidResponseStatusCode ( httpResponse)
170+ }
171+ self . init ( status: . init( code: httpResponse. statusCode) )
172+ if let fields = httpResponse. allHeaderFields as? [ String : String ] {
173+ self . headerFields. reserveCapacity ( fields. count)
174+ for (name, value) in fields {
175+ if let name = HTTPField . Name ( name) {
176+ self . headerFields. append ( HTTPField ( name: name, isoLatin1Value: value) )
177+ }
178+ }
171179 }
172- self . init ( status: . init( code: httpResponse. statusCode) , headerFields: headerFields)
173180 }
174181}
175182
@@ -193,7 +200,50 @@ extension URLRequest {
193200 }
194201 self . init ( url: url)
195202 self . httpMethod = request. method. rawValue
196- for header in request. headerFields { setValue ( header. value, forHTTPHeaderField: header. name. canonicalName) }
203+ var combinedFields = [ HTTPField . Name: String] ( minimumCapacity: request. headerFields. count)
204+ for field in request. headerFields {
205+ if let existingValue = combinedFields [ field. name] {
206+ let separator = field. name == . cookie ? " ; " : " , "
207+ combinedFields [ field. name] = " \( existingValue) \( separator) \( field. isoLatin1Value) "
208+ } else {
209+ combinedFields [ field. name] = field. isoLatin1Value
210+ }
211+ }
212+ var headerFields = [ String: String] ( minimumCapacity: combinedFields. count)
213+ for (name, value) in combinedFields { headerFields [ name. rawName] = value }
214+ self . allHTTPHeaderFields = headerFields
215+ }
216+ }
217+
218+ extension String { fileprivate var isASCII : Bool { self . utf8. allSatisfy { $0 & 0x80 == 0 } } }
219+
220+ extension HTTPField {
221+ fileprivate init ( name: Name , isoLatin1Value: String ) {
222+ if isoLatin1Value. isASCII {
223+ self . init ( name: name, value: isoLatin1Value)
224+ } else {
225+ self = withUnsafeTemporaryAllocation ( of: UInt8 . self, capacity: isoLatin1Value. unicodeScalars. count) {
226+ buffer in
227+ for (index, scalar) in isoLatin1Value. unicodeScalars. enumerated ( ) {
228+ if scalar. value > UInt8 . max {
229+ buffer [ index] = 0x20
230+ } else {
231+ buffer [ index] = UInt8 ( truncatingIfNeeded: scalar. value)
232+ }
233+ }
234+ return HTTPField ( name: name, value: buffer)
235+ }
236+ }
237+ }
238+
239+ fileprivate var isoLatin1Value : String {
240+ if self . value. isASCII { return self . value }
241+ return self . withUnsafeBytesOfValue { buffer in
242+ let scalars = buffer. lazy. map { UnicodeScalar ( UInt32 ( $0) ) ! }
243+ var string = " "
244+ string. unicodeScalars. append ( contentsOf: scalars)
245+ return string
246+ }
197247 }
198248}
199249
@@ -211,6 +261,8 @@ extension URLSessionTransportError: CustomStringConvertible {
211261 " Invalid request URL from request path: \( path) , method: \( method) , relative to base URL: \( baseURL. absoluteString) "
212262 case . notHTTPResponse( let response) :
213263 return " Received a non-HTTP response, of type: \( String ( describing: type ( of: response) ) ) "
264+ case . invalidResponseStatusCode( let response) :
265+ return " Received an HTTP response with invalid status code: \( response. statusCode) ) "
214266 case . noResponse( let url) : return " Received a nil response for \( url? . absoluteString ?? " <nil URL> " ) "
215267 case . streamingNotSupported: return " Streaming is not supported on this platform "
216268 }
0 commit comments