@@ -150,6 +150,9 @@ internal enum URLSessionTransportError: Error {
150
150
/// Returned `URLResponse` could not be converted to `HTTPURLResponse`.
151
151
case notHTTPResponse( URLResponse )
152
152
153
+ /// Returned `HTTPURLResponse` has an invalid status code
154
+ case invalidResponseStatusCode( HTTPURLResponse )
155
+
153
156
/// Returned `URLResponse` was nil
154
157
case noResponse( url: URL ? )
155
158
@@ -162,14 +165,18 @@ extension HTTPResponse {
162
165
guard let httpResponse = urlResponse as? HTTPURLResponse else {
163
166
throw URLSessionTransportError . notHTTPResponse ( urlResponse)
164
167
}
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
+ }
171
179
}
172
- self . init ( status: . init( code: httpResponse. statusCode) , headerFields: headerFields)
173
180
}
174
181
}
175
182
@@ -193,7 +200,50 @@ extension URLRequest {
193
200
}
194
201
self . init ( url: url)
195
202
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
+ }
197
247
}
198
248
}
199
249
@@ -211,6 +261,8 @@ extension URLSessionTransportError: CustomStringConvertible {
211
261
" Invalid request URL from request path: \( path) , method: \( method) , relative to base URL: \( baseURL. absoluteString) "
212
262
case . notHTTPResponse( let response) :
213
263
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) ) "
214
266
case . noResponse( let url) : return " Received a nil response for \( url? . absoluteString ?? " <nil URL> " ) "
215
267
case . streamingNotSupported: return " Streaming is not supported on this platform "
216
268
}
0 commit comments