@@ -52,19 +52,21 @@ public class NSURLResponse : NSObject, NSSecureCoding, NSCopying {
52
52
@result The initialized NSURLResponse.
53
53
@discussion This is the designated initializer for NSURLResponse.
54
54
*/
55
- public init ( URL : NSURL , MIMEType : String ? , expectedContentLength length: Int , textEncodingName name: String ? ) {
56
- self . URL = URL
57
- self . MIMEType = MIMEType
55
+ public init ( url : NSURL , mimeType : String ? , expectedContentLength length: Int , textEncodingName name: String ? ) {
56
+ self . url = url
57
+ self . mimeType = mimeType
58
58
self . expectedContentLength = Int64 ( length)
59
59
self . textEncodingName = name
60
+ let c = url. lastPathComponent
61
+ self . suggestedFilename = ( c? . isEmpty ?? true ) ? " Unknown " : c
60
62
}
61
63
62
64
/*!
63
65
@method URL
64
66
@abstract Returns the URL of the receiver.
65
67
@result The URL of the receiver.
66
68
*/
67
- /*@NSCopying*/ public private( set) var URL : NSURL ?
69
+ /*@NSCopying*/ public private( set) var url : NSURL ?
68
70
69
71
70
72
/*!
@@ -78,7 +80,7 @@ public class NSURLResponse : NSObject, NSSecureCoding, NSCopying {
78
80
be made if the origin source did not report any such information.
79
81
@result The MIME type of the receiver.
80
82
*/
81
- public private( set) var MIMEType : String ?
83
+ public private( set) var mimeType : String ?
82
84
83
85
/*!
84
86
@method expectedContentLength
@@ -120,7 +122,7 @@ public class NSURLResponse : NSObject, NSSecureCoding, NSCopying {
120
122
This method always returns a valid filename.
121
123
@result A suggested filename to use if saving the resource to disk.
122
124
*/
123
- public var suggestedFilename : String ? { NSUnimplemented ( ) }
125
+ public private ( set ) var suggestedFilename : String ?
124
126
}
125
127
126
128
/*!
@@ -143,7 +145,17 @@ public class NSHTTPURLResponse : NSURLResponse {
143
145
@result the instance of the object, or NULL if an error occurred during initialization.
144
146
@discussion This API was introduced in Mac OS X 10.7.2 and iOS 5.0 and is not available prior to those releases.
145
147
*/
146
- public init ? ( URL url: NSURL , statusCode: Int , HTTPVersion: String ? , headerFields: [ String : String ] ? ) { NSUnimplemented ( ) }
148
+ public init ? ( url: NSURL , statusCode: Int , httpVersion: String ? , headerFields: [ String : String ] ? ) {
149
+ self . statusCode = statusCode
150
+ self . allHeaderFields = headerFields ?? [ : ]
151
+ super. init ( url: url, mimeType: nil , expectedContentLength: 0 , textEncodingName: nil )
152
+ expectedContentLength = getExpectedContentLength ( fromHeaderFields: headerFields) ?? - 1
153
+ suggestedFilename = getSuggestedFilename ( fromHeaderFields: headerFields) ?? " Unknown "
154
+ if let type = ContentTypeComponents ( headerFields: headerFields) {
155
+ mimeType = type. mimeType. lowercased ( )
156
+ textEncodingName = type. textEncoding? . lowercased ( )
157
+ }
158
+ }
147
159
148
160
public required init ? ( coder aDecoder: NSCoder ) {
149
161
NSUnimplemented ( )
@@ -154,28 +166,192 @@ public class NSHTTPURLResponse : NSURLResponse {
154
166
@abstract Returns the HTTP status code of the receiver.
155
167
@result The HTTP status code of the receiver.
156
168
*/
157
- public var statusCode : Int { NSUnimplemented ( ) }
169
+ public let statusCode : Int
158
170
159
- /*!
160
- @method allHeaderFields
161
- @abstract Returns a dictionary containing all the HTTP header fields
162
- of the receiver.
163
- @discussion By examining this header dictionary, clients can see
164
- the "raw" header information which was reported to the protocol
165
- implementation by the HTTP server. This may be of use to
166
- sophisticated or special-purpose HTTP clients.
167
- @result A dictionary containing all the HTTP header fields of the
168
- receiver.
169
- */
170
- public var allHeaderFields : [ NSObject : AnyObject ] { NSUnimplemented ( ) }
171
+ /// Returns a dictionary containing all the HTTP header fields
172
+ /// of the receiver.
173
+ ///
174
+ /// By examining this header dictionary, clients can see
175
+ /// the "raw" header information which was reported to the protocol
176
+ /// implementation by the HTTP server. This may be of use to
177
+ /// sophisticated or special-purpose HTTP clients.
178
+ ///
179
+ /// - Returns: A dictionary containing all the HTTP header fields of the
180
+ /// receiver.
181
+ ///
182
+ /// - Important: This is an *experimental* change from the
183
+ /// `[NSObject: AnyObject]` type that Darwin Foundation uses.
184
+ public let allHeaderFields : [ String : String ]
171
185
172
- /*!
186
+ /*!
173
187
@method localizedStringForStatusCode:
174
188
@abstract Convenience method which returns a localized string
175
189
corresponding to the status code for this response.
176
190
@param the status code to use to produce a localized string.
177
191
@result A localized string corresponding to the given status code.
178
192
*/
179
- public class func localizedStringForStatusCode( _ statusCode: Int ) -> String { NSUnimplemented ( ) }
193
+ public class func localizedString( forStatusCode statusCode: Int ) -> String { NSUnimplemented ( ) }
194
+ }
195
+ /// Parses the expected content length from the headers.
196
+ ///
197
+ /// Note that the message content length is different from the message
198
+ /// transfer length.
199
+ /// The transfer length can only be derived when the Transfer-Encoding is identity (default).
200
+ /// For compressed content (Content-Encoding other than identity), there is not way to derive the
201
+ /// content length from the transfer length.
202
+ private func getExpectedContentLength( fromHeaderFields headerFields: [ String : String ] ? ) -> Int64 ? {
203
+ guard
204
+ let f = headerFields,
205
+ let contentLengthS = valueForCaseInsensitiveKey ( " content-length " , fields: f) ,
206
+ let contentLength = Int64 ( contentLengthS)
207
+ else { return nil }
208
+ return contentLength
209
+ }
210
+ /// Parses the suggested filename from the `Content-Disposition` header.
211
+ ///
212
+ /// - SeeAlso: [RFC 2183](https://tools.ietf.org/html/rfc2183)
213
+ private func getSuggestedFilename( fromHeaderFields headerFields: [ String : String ] ? ) -> String ? {
214
+ // Typical use looks like this:
215
+ // Content-Disposition: attachment; filename="fname.ext"
216
+ guard
217
+ let f = headerFields,
218
+ let contentDisposition = valueForCaseInsensitiveKey ( " content-disposition " , fields: f) ,
219
+ let field = contentDisposition. httpHeaderParts
220
+ else { return nil }
221
+ for part in field. parameters where part. attribute == " filename " {
222
+ return part. value? . pathComponents. map { $0 == " / " ? " " : $0} . joined ( separator: " _ " )
223
+ }
224
+ return nil
225
+ }
226
+ /// Parts corresponding to the `Content-Type` header field in a HTTP message.
227
+ private struct ContentTypeComponents {
228
+ /// For `text/html; charset=ISO-8859-4` this would be `text/html`
229
+ let mimeType : String
230
+ /// For `text/html; charset=ISO-8859-4` this would be `ISO-8859-4`. Will be
231
+ /// `nil` when no `charset` is specified.
232
+ let textEncoding : String ?
233
+ }
234
+ extension ContentTypeComponents {
235
+ /// Parses the `Content-Type` header field
236
+ ///
237
+ /// `Content-Type: text/html; charset=ISO-8859-4` would result in `("text/html", "ISO-8859-4")`, while
238
+ /// `Content-Type: text/html` would result in `("text/html", nil)`.
239
+ init ? ( headerFields: [ String : String ] ? ) {
240
+ guard
241
+ let f = headerFields,
242
+ let contentType = valueForCaseInsensitiveKey ( " content-type " , fields: f) ,
243
+ let field = contentType. httpHeaderParts
244
+ else { return nil }
245
+ for parameter in field. parameters where parameter. attribute == " charset " {
246
+ self . mimeType = field. value
247
+ self . textEncoding = parameter. value
248
+ return
249
+ }
250
+ self . mimeType = field. value
251
+ self . textEncoding = nil
252
+ }
253
+ }
254
+
255
+ /// A type with paramteres
256
+ ///
257
+ /// RFC 2616 specifies a few types that can have parameters, e.g. `Content-Type`.
258
+ /// These are specified like so
259
+ /// ```
260
+ /// field = value *( ";" parameter )
261
+ /// value = token
262
+ /// ```
263
+ /// where parameters are attribute/value as specified by
264
+ /// ```
265
+ /// parameter = attribute "=" value
266
+ /// attribute = token
267
+ /// value = token | quoted-string
268
+ /// ```
269
+ private struct ValueWithParameters {
270
+ let value : String
271
+ let parameters : [ Parameter ]
272
+ struct Parameter {
273
+ let attribute : String
274
+ let value : String ?
275
+ }
180
276
}
181
277
278
+ private extension String {
279
+ /// Split the string at each ";", remove any quoting.
280
+ ///
281
+ /// The trouble is if there's a
282
+ /// ";" inside something that's quoted. And we can escape the separator and
283
+ /// the quotes with a "\".
284
+ var httpHeaderParts : ValueWithParameters ? {
285
+ var type : String ?
286
+ var parameters : [ ValueWithParameters . Parameter ] = [ ]
287
+ let ws = NSCharacterSet . whitespaceCharacterSet ( )
288
+ func append( _ string: String ) {
289
+ if type == nil {
290
+ type = string
291
+ } else {
292
+ if let r = string. rangeOfString ( " = " ) {
293
+ let name = string [ string. startIndex..< r. startIndex] . stringByTrimmingCharactersInSet ( ws)
294
+ let value = string [ r. endIndex..< string. endIndex] . stringByTrimmingCharactersInSet ( ws)
295
+ parameters. append ( ValueWithParameters . Parameter ( attribute: name, value: value) )
296
+ } else {
297
+ let name = string. stringByTrimmingCharactersInSet ( ws)
298
+ parameters. append ( ValueWithParameters . Parameter ( attribute: name, value: nil ) )
299
+ }
300
+ }
301
+ }
302
+
303
+ let escape = UnicodeScalar ( 0x5c ) // \
304
+ let quote = UnicodeScalar ( 0x22 ) // "
305
+ let separator = UnicodeScalar ( 0x3b ) // ;
306
+ enum State {
307
+ case nonQuoted( String )
308
+ case nonQuotedEscaped( String )
309
+ case quoted( String )
310
+ case quotedEscaped( String )
311
+ }
312
+ var state = State . nonQuoted ( " " )
313
+ for next in unicodeScalars {
314
+ switch ( state, next) {
315
+ case ( . nonQuoted( let s) , separator) :
316
+ append ( s)
317
+ state = . nonQuoted( " " )
318
+ case ( . nonQuoted( let s) , escape) :
319
+ state = . nonQuotedEscaped( s + String( next) )
320
+ case ( . nonQuoted( let s) , quote) :
321
+ state = . quoted( s)
322
+ case ( . nonQuoted( let s) , _) :
323
+ state = . nonQuoted( s + String( next) )
324
+
325
+ case ( . nonQuotedEscaped( let s) , _) :
326
+ state = . nonQuoted( s + String( next) )
327
+
328
+ case ( . quoted( let s) , quote) :
329
+ state = . nonQuoted( s)
330
+ case ( . quoted( let s) , escape) :
331
+ state = . quotedEscaped( s + String( next) )
332
+ case ( . quoted( let s) , _) :
333
+ state = . quoted( s + String( next) )
334
+
335
+ case ( . quotedEscaped( let s) , _) :
336
+ state = . quoted( s + String( next) )
337
+ }
338
+ }
339
+ switch state {
340
+ case . nonQuoted( let s) : append ( s)
341
+ case . nonQuotedEscaped( let s) : append ( s)
342
+ case . quoted( let s) : append ( s)
343
+ case . quotedEscaped( let s) : append ( s)
344
+ }
345
+ guard let t = type else { return nil }
346
+ return ValueWithParameters ( value: t, parameters: parameters)
347
+ }
348
+ }
349
+ private func valueForCaseInsensitiveKey( _ key: String , fields: [ String : String ] ) -> String ? {
350
+ let kk = key. lowercased ( )
351
+ for (k, v) in fields {
352
+ if k. lowercased ( ) == kk {
353
+ return v
354
+ }
355
+ }
356
+ return nil
357
+ }
0 commit comments