NSURLRequest.swift
1 // This source file is part of the Swift.org open source project 2 // 3 // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors 4 // Licensed under Apache License v2.0 with Runtime Library Exception 5 // 6 // See http://swift.org/LICENSE.txt for license information 7 // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 8 // 9 10 #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 11 import SwiftFoundation 12 #else 13 import Foundation 14 #endif 15 16 // ----------------------------------------------------------------------------- 17 /// 18 /// This header file describes the constructs used to represent URL 19 /// load requests in a manner independent of protocol and URL scheme. 20 /// Immutable and mutable variants of this URL load request concept 21 /// are described, named `NSURLRequest` and `NSMutableURLRequest`, 22 /// respectively. A collection of constants is also declared to 23 /// exercise control over URL content caching policy. 24 /// 25 /// `NSURLRequest` and `NSMutableURLRequest` are designed to be 26 /// customized to support protocol-specific requests. Protocol 27 /// implementors who need to extend the capabilities of `NSURLRequest` 28 /// and `NSMutableURLRequest` are encouraged to provide categories on 29 /// these classes as appropriate to support protocol-specific data. To 30 /// store and retrieve data, category methods can use the 31 /// `propertyForKey(_:,inRequest:)` and 32 /// `setProperty(_:,forKey:,inRequest:)` class methods on 33 /// `URLProtocol`. See the `NSHTTPURLRequest` on `NSURLRequest` and 34 /// `NSMutableHTTPURLRequest` on `NSMutableURLRequest` for examples of 35 /// such extensions. 36 /// 37 /// The main advantage of this design is that a client of the URL 38 /// loading library can implement request policies in a standard way 39 /// without type checking of requests or protocol checks on URLs. Any 40 /// protocol-specific details that have been set on a URL request will 41 /// be used if they apply to the particular URL being loaded, and will 42 /// be ignored if they do not apply. 43 /// 44 // ----------------------------------------------------------------------------- 45 46 /// A cache policy 47 /// 48 /// The `NSURLRequestCachePolicy` `enum` defines constants that 49 /// can be used to specify the type of interactions that take place with 50 /// the caching system when the URL loading system processes a request. 51 /// Specifically, these constants cover interactions that have to do 52 /// with whether already-existing cache data is returned to satisfy a 53 /// URL load request. 54 extension NSURLRequest { 55 public enum CachePolicy : UInt { 56 /// Specifies that the caching logic defined in the protocol 57 /// implementation, if any, is used for a particular URL load request. This 58 /// is the default policy for URL load requests. 59 case useProtocolCachePolicy 60 /// Specifies that the data for the URL load should be loaded from the 61 /// origin source. No existing local cache data, regardless of its freshness 62 /// or validity, should be used to satisfy a URL load request. 63 case reloadIgnoringLocalCacheData 64 /// Specifies that not only should the local cache data be ignored, but that 65 /// proxies and other intermediates should be instructed to disregard their 66 /// caches so far as the protocol allows. Unimplemented. 67 case reloadIgnoringLocalAndRemoteCacheData // Unimplemented 68 /// Older name for `NSURLRequestReloadIgnoringLocalCacheData`. 69 public static var reloadIgnoringCacheData: CachePolicy { return .reloadIgnoringLocalCacheData } 70 /// Specifies that the existing cache data should be used to satisfy a URL 71 /// load request, regardless of its age or expiration date. However, if 72 /// there is no existing data in the cache corresponding to a URL load 73 /// request, the URL is loaded from the origin source. 74 case returnCacheDataElseLoad 75 /// Specifies that the existing cache data should be used to satisfy a URL 76 /// load request, regardless of its age or expiration date. However, if 77 /// there is no existing data in the cache corresponding to a URL load 78 /// request, no attempt is made to load the URL from the origin source, and 79 /// the load is considered to have failed. This constant specifies a 80 /// behavior that is similar to an "offline" mode. 81 case returnCacheDataDontLoad 82 /// Specifies that the existing cache data may be used provided the origin 83 /// source confirms its validity, otherwise the URL is loaded from the 84 /// origin source. 85 /// - Note: Unimplemented. 86 case reloadRevalidatingCacheData // Unimplemented 87 } 88 89 public enum NetworkServiceType : UInt { 90 case `default` // Standard internet traffic 91 case voip // Voice over IP control traffic 92 case video // Video traffic 93 case background // Background traffic 94 case voice // Voice data 95 case networkServiceTypeCallSignaling // Call Signaling 96 } 97 } 98 99 /// An `NSURLRequest` object represents a URL load request in a 100 /// manner independent of protocol and URL scheme. 101 /// 102 /// `NSURLRequest` encapsulates basic data elements about a URL load request. 103 /// 104 /// In addition, `NSURLRequest` is designed to be extended to support 105 /// protocol-specific data by adding categories to access a property 106 /// object provided in an interface targeted at protocol implementors. 107 /// 108 /// Protocol implementors should direct their attention to the 109 /// `NSURLRequestExtensibility` category on `NSURLRequest` for more 110 /// information on how to provide extensions on `NSURLRequest` to 111 /// support protocol-specific request information. 112 /// 113 /// Clients of this API who wish to create `NSURLRequest` objects to 114 /// load URL content should consult the protocol-specific `NSURLRequest` 115 /// categories that are available. The `NSHTTPURLRequest` category on 116 /// `NSURLRequest` is an example. 117 /// 118 /// Objects of this class are used with the `URLSession` API to perform the 119 /// load of a URL. 120 open class NSURLRequest : NSObject, NSSecureCoding, NSCopying, NSMutableCopying { 121 122 open override func copy() -> Any { 123 return copy(with: nil) 124 } 125 126 open func copy(with zone: NSZone? = nil) -> Any { 127 if type(of: self) === NSURLRequest.self { 128 // Already immutable 129 return self 130 } 131 let c = NSURLRequest(url: url!) 132 c.setValues(from: self) 133 return c 134 } 135 136 public convenience init(url: URL) { 137 self.init(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 60.0) 138 } 139 140 public init(url: URL, cachePolicy: NSURLRequest.CachePolicy, timeoutInterval: TimeInterval) { 141 self.url = url.absoluteURL 142 self.cachePolicy = cachePolicy 143 self.timeoutInterval = timeoutInterval 144 } 145 146 private func setValues(from source: NSURLRequest) { 147 self.url = source.url 148 self.mainDocumentURL = source.mainDocumentURL 149 self.cachePolicy = source.cachePolicy 150 self.timeoutInterval = source.timeoutInterval 151 self.httpMethod = source.httpMethod 152 self.allHTTPHeaderFields = source.allHTTPHeaderFields 153 self._body = source._body 154 self.networkServiceType = source.networkServiceType 155 self.allowsCellularAccess = source.allowsCellularAccess 156 self.httpShouldHandleCookies = source.httpShouldHandleCookies 157 self.httpShouldUsePipelining = source.httpShouldUsePipelining 158 } 159 160 open override func mutableCopy() -> Any { 161 return mutableCopy(with: nil) 162 } 163 164 open func mutableCopy(with zone: NSZone? = nil) -> Any { 165 let c = NSMutableURLRequest(url: url!) 166 c.setValues(from: self) 167 return c 168 } 169 170 public required init?(coder aDecoder: NSCoder) { 171 guard aDecoder.allowsKeyedCoding else { 172 preconditionFailure("Unkeyed coding is unsupported.") 173 } 174 175 super.init() 176 177 if let encodedURL = aDecoder.decodeObject(forKey: "NS.url") as? NSURL { 178 self.url = encodedURL as URL 179 } 180 181 if let encodedHeaders = aDecoder.decodeObject(forKey: "NS._allHTTPHeaderFields") as? NSDictionary { 182 self.allHTTPHeaderFields = encodedHeaders.reduce([String : String]()) { result, item in 183 var result = result 184 if let key = item.key as? NSString, 185 let value = item.value as? NSString { 186 result[key as String] = value as String 187 } 188 return result 189 } 190 } 191 192 if let encodedDocumentURL = aDecoder.decodeObject(forKey: "NS.mainDocumentURL") as? NSURL { 193 self.mainDocumentURL = encodedDocumentURL as URL 194 } 195 196 if let encodedMethod = aDecoder.decodeObject(forKey: "NS.httpMethod") as? NSString { 197 self.httpMethod = encodedMethod as String 198 } 199 200 let encodedCachePolicy = aDecoder.decodeObject(forKey: "NS._cachePolicy") as! NSNumber 201 self.cachePolicy = CachePolicy(rawValue: encodedCachePolicy.uintValue)! 202 203 let encodedTimeout = aDecoder.decodeObject(forKey: "NS._timeoutInterval") as! NSNumber 204 self.timeoutInterval = encodedTimeout.doubleValue 205 206 let encodedHttpBody: Data? = aDecoder.withDecodedUnsafeBufferPointer(forKey: "NS.httpBody") { 207 guard let buffer = $0 else { return nil } 208 return Data(buffer: buffer) 209 } 210 211 if let encodedHttpBody = encodedHttpBody { 212 self._body = .data(encodedHttpBody) 213 } 214 215 let encodedNetworkServiceType = aDecoder.decodeObject(forKey: "NS._networkServiceType") as! NSNumber 216 self.networkServiceType = NetworkServiceType(rawValue: encodedNetworkServiceType.uintValue)! 217 218 let encodedCellularAccess = aDecoder.decodeObject(forKey: "NS._allowsCellularAccess") as! NSNumber 219 self.allowsCellularAccess = encodedCellularAccess.boolValue 220 221 let encodedHandleCookies = aDecoder.decodeObject(forKey: "NS._httpShouldHandleCookies") as! NSNumber 222 self.httpShouldHandleCookies = encodedHandleCookies.boolValue 223 224 let encodedUsePipelining = aDecoder.decodeObject(forKey: "NS._httpShouldUsePipelining") as! NSNumber 225 self.httpShouldUsePipelining = encodedUsePipelining.boolValue 226 } 227 228 open func encode(with aCoder: NSCoder) { 229 guard aCoder.allowsKeyedCoding else { 230 preconditionFailure("Unkeyed coding is unsupported.") 231 } 232 233 aCoder.encode(self.url?._bridgeToObjectiveC(), forKey: "NS.url") 234 aCoder.encode(self.allHTTPHeaderFields?._bridgeToObjectiveC(), forKey: "NS._allHTTPHeaderFields") 235 aCoder.encode(self.mainDocumentURL?._bridgeToObjectiveC(), forKey: "NS.mainDocumentURL") 236 aCoder.encode(self.httpMethod?._bridgeToObjectiveC(), forKey: "NS.httpMethod") 237 aCoder.encode(self.cachePolicy.rawValue._bridgeToObjectiveC(), forKey: "NS._cachePolicy") 238 aCoder.encode(self.timeoutInterval._bridgeToObjectiveC(), forKey: "NS._timeoutInterval") 239 if let httpBody = self.httpBody?._bridgeToObjectiveC() { 240 withExtendedLifetime(httpBody) { 241 let bytePtr = httpBody.bytes.bindMemory(to: UInt8.self, capacity: httpBody.length) 242 aCoder.encodeBytes(bytePtr, length: httpBody.length, forKey: "NS.httpBody") 243 } 244 } 245 //On macOS input stream is not encoded. 246 aCoder.encode(self.networkServiceType.rawValue._bridgeToObjectiveC(), forKey: "NS._networkServiceType") 247 aCoder.encode(self.allowsCellularAccess._bridgeToObjectiveC(), forKey: "NS._allowsCellularAccess") 248 aCoder.encode(self.httpShouldHandleCookies._bridgeToObjectiveC(), forKey: "NS._httpShouldHandleCookies") 249 aCoder.encode(self.httpShouldUsePipelining._bridgeToObjectiveC(), forKey: "NS._httpShouldUsePipelining") 250 } 251 252 open override func isEqual(_ object: Any?) -> Bool { 253 //On macOS this fields do not determine the result: 254 //allHTTPHeaderFields 255 //timeoutInterval 256 //httBody 257 //networkServiceType 258 //httpShouldUsePipelining 259 guard let other = object as? NSURLRequest else { return false } 260 return other === self 261 || (other.url == self.url 262 && other.mainDocumentURL == self.mainDocumentURL 263 && other.httpMethod == self.httpMethod 264 && other.cachePolicy == self.cachePolicy 265 && other.httpBodyStream == self.httpBodyStream 266 && other.allowsCellularAccess == self.allowsCellularAccess 267 && other.httpShouldHandleCookies == self.httpShouldHandleCookies) 268 } 269 270 open override var hash: Int { 271 var hasher = Hasher() 272 hasher.combine(url) 273 hasher.combine(mainDocumentURL) 274 hasher.combine(httpMethod) 275 hasher.combine(httpBodyStream) 276 hasher.combine(allowsCellularAccess) 277 hasher.combine(httpShouldHandleCookies) 278 return hasher.finalize() 279 } 280 281 /// Indicates that NSURLRequest implements the NSSecureCoding protocol. 282 open class var supportsSecureCoding: Bool { return true } 283 284 /// The URL of the receiver. 285 /*@NSCopying */open fileprivate(set) var url: URL? 286 287 /// The main document URL associated with this load. 288 /// 289 /// This URL is used for the cookie "same domain as main 290 /// document" policy. There may also be other future uses. 291 /*@NSCopying*/ open fileprivate(set) var mainDocumentURL: URL? 292 293 open internal(set) var cachePolicy: CachePolicy = .useProtocolCachePolicy 294 295 open internal(set) var timeoutInterval: TimeInterval = 60.0 296 297 internal var _httpMethod: String? = "GET" 298 299 /// Returns the HTTP request method of the receiver. 300 open fileprivate(set) var httpMethod: String? { 301 get { return _httpMethod } 302 set { _httpMethod = NSURLRequest._normalized(newValue) } 303 } 304 305 private class func _normalized(_ raw: String?) -> String { 306 guard let raw = raw else { 307 return "GET" 308 } 309 310 let nsMethod = NSString(string: raw) 311 312 for method in ["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT"] { 313 if nsMethod.caseInsensitiveCompare(method) == .orderedSame { 314 return method 315 } 316 } 317 return raw 318 } 319 320 /// A dictionary containing all the HTTP header fields 321 /// of the receiver. 322 open internal(set) var allHTTPHeaderFields: [String : String]? = nil 323 324 /// Returns the value which corresponds to the given header field. 325 /// 326 /// Note that, in keeping with the HTTP RFC, HTTP header field 327 /// names are case-insensitive. 328 /// - Parameter field: the header field name to use for the lookup 329 /// (case-insensitive). 330 /// - Returns: the value associated with the given header field, or `nil` if 331 /// there is no value associated with the given header field. 332 open func value(forHTTPHeaderField field: String) -> String? { 333 guard let f = allHTTPHeaderFields else { return nil } 334 return existingHeaderField(field, inHeaderFields: f)?.1 335 } 336 337 internal enum Body { 338 case data(Data) 339 case stream(InputStream) 340 } 341 internal var _body: Body? 342 343 open var httpBody: Data? { 344 if let body = _body { 345 switch body { 346 case .data(let data): 347 return data 348 case .stream: 349 return nil 350 } 351 } 352 return nil 353 } 354 355 open var httpBodyStream: InputStream? { 356 if let body = _body { 357 switch body { 358 case .data: 359 return nil 360 case .stream(let stream): 361 return stream 362 } 363 } 364 return nil 365 } 366 367 open internal(set) var networkServiceType: NetworkServiceType = .default 368 369 open internal(set) var allowsCellularAccess: Bool = true 370 371 open internal(set) var httpShouldHandleCookies: Bool = true 372 373 open internal(set) var httpShouldUsePipelining: Bool = true 374 375 open override var description: String { 376 let url = self.url?.description ?? "(null)" 377 return super.description + " { URL: \(url) }" 378 } 379 } 380 381 /// An `NSMutableURLRequest` object represents a mutable URL load 382 /// request in a manner independent of protocol and URL scheme. 383 /// 384 /// This specialization of `NSURLRequest` is provided to aid 385 /// developers who may find it more convenient to mutate a single request 386 /// object for a series of URL loads instead of creating an immutable 387 /// `NSURLRequest` for each load. This programming model is supported by 388 /// the following contract stipulation between `NSMutableURLRequest` and the 389 /// `URLSession` API: `URLSession` makes a deep copy of each 390 /// `NSMutableURLRequest` object passed to it. 391 /// 392 /// `NSMutableURLRequest` is designed to be extended to support 393 /// protocol-specific data by adding categories to access a property 394 /// object provided in an interface targeted at protocol implementors. 395 /// 396 /// Protocol implementors should direct their attention to the 397 /// `NSMutableURLRequestExtensibility` category on 398 /// `NSMutableURLRequest` for more information on how to provide 399 /// extensions on `NSMutableURLRequest` to support protocol-specific 400 /// request information. 401 /// 402 /// Clients of this API who wish to create `NSMutableURLRequest` 403 /// objects to load URL content should consult the protocol-specific 404 /// `NSMutableURLRequest` categories that are available. The 405 /// `NSMutableHTTPURLRequest` category on `NSMutableURLRequest` is an 406 /// example. 407 open class NSMutableURLRequest : NSURLRequest { 408 public required init?(coder aDecoder: NSCoder) { 409 super.init(coder: aDecoder) 410 } 411 412 public convenience init(url: URL) { 413 self.init(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 60.0) 414 } 415 416 public override init(url: URL, cachePolicy: NSURLRequest.CachePolicy, timeoutInterval: TimeInterval) { 417 super.init(url: url, cachePolicy: cachePolicy, timeoutInterval: timeoutInterval) 418 } 419 420 open override func copy(with zone: NSZone? = nil) -> Any { 421 return mutableCopy(with: zone) 422 } 423 424 /*@NSCopying */ open override var url: URL? { 425 get { return super.url } 426 //TODO: set { super.URL = newValue.map{ $0.copy() as! NSURL } } 427 set { super.url = newValue } 428 } 429 430 /// The main document URL. 431 /// 432 /// The caller should pass the URL for an appropriate main 433 /// document, if known. For example, when loading a web page, the URL 434 /// of the main html document for the top-level frame should be 435 /// passed. This main document will be used to implement the cookie 436 /// *only from same domain as main document* policy, and possibly 437 /// other things in the future. 438 /*@NSCopying*/ open override var mainDocumentURL: URL? { 439 get { return super.mainDocumentURL } 440 //TODO: set { super.mainDocumentURL = newValue.map{ $0.copy() as! NSURL } } 441 set { super.mainDocumentURL = newValue } 442 } 443 444 445 /// The HTTP request method of the receiver. 446 open override var httpMethod: String? { 447 get { return super.httpMethod } 448 set { super.httpMethod = newValue } 449 } 450 451 open override var cachePolicy: CachePolicy { 452 get { return super.cachePolicy } 453 set { super.cachePolicy = newValue } 454 } 455 456 open override var timeoutInterval: TimeInterval { 457 get { return super.timeoutInterval } 458 set { super.timeoutInterval = newValue } 459 } 460 461 open override var allHTTPHeaderFields: [String : String]? { 462 get { return super.allHTTPHeaderFields } 463 set { super.allHTTPHeaderFields = newValue } 464 } 465 466 /// Sets the value of the given HTTP header field. 467 /// 468 /// If a value was previously set for the given header 469 /// field, that value is replaced with the given value. Note that, in 470 /// keeping with the HTTP RFC, HTTP header field names are 471 /// case-insensitive. 472 /// - Parameter value: the header field value. 473 /// - Parameter field: the header field name (case-insensitive). 474 open func setValue(_ value: String?, forHTTPHeaderField field: String) { 475 // Store the field name capitalized to match native Foundation 476 let capitalizedFieldName = field.capitalized 477 var f: [String : String] = allHTTPHeaderFields ?? [:] 478 if let old = existingHeaderField(capitalizedFieldName, inHeaderFields: f) { 479 f.removeValue(forKey: old.0) 480 } 481 f[capitalizedFieldName] = value 482 allHTTPHeaderFields = f 483 } 484 485 /// Adds an HTTP header field in the current header dictionary. 486 /// 487 /// This method provides a way to add values to header 488 /// fields incrementally. If a value was previously set for the given 489 /// header field, the given value is appended to the previously-existing 490 /// value. The appropriate field delimiter, a comma in the case of HTTP, 491 /// is added by the implementation, and should not be added to the given 492 /// value by the caller. Note that, in keeping with the HTTP RFC, HTTP 493 /// header field names are case-insensitive. 494 /// - Parameter value: the header field value. 495 /// - Parameter field: the header field name (case-insensitive). 496 open func addValue(_ value: String, forHTTPHeaderField field: String) { 497 // Store the field name capitalized to match native Foundation 498 let capitalizedFieldName = field.capitalized 499 var f: [String : String] = allHTTPHeaderFields ?? [:] 500 if let old = existingHeaderField(capitalizedFieldName, inHeaderFields: f) { 501 f[old.0] = old.1 + "," + value 502 } else { 503 f[capitalizedFieldName] = value 504 } 505 allHTTPHeaderFields = f 506 } 507 508 open override var httpBody: Data? { 509 get { 510 if let body = _body { 511 switch body { 512 case .data(let data): 513 return data 514 case .stream: 515 return nil 516 } 517 } 518 return nil 519 } 520 set { 521 if let value = newValue { 522 _body = Body.data(value) 523 } else { 524 _body = nil 525 } 526 } 527 } 528 529 open override var httpBodyStream: InputStream? { 530 get { 531 if let body = _body { 532 switch body { 533 case .data: 534 return nil 535 case .stream(let stream): 536 return stream 537 } 538 } 539 return nil 540 } 541 set { 542 if let value = newValue { 543 _body = Body.stream(value) 544 } else { 545 _body = nil 546 } 547 } 548 } 549 550 open override var networkServiceType: NetworkServiceType { 551 get { return super.networkServiceType } 552 set { super.networkServiceType = newValue } 553 } 554 555 open override var allowsCellularAccess: Bool { 556 get { return super.allowsCellularAccess } 557 set { super.allowsCellularAccess = newValue } 558 } 559 560 open override var httpShouldHandleCookies: Bool { 561 get { return super.httpShouldHandleCookies } 562 set { super.httpShouldHandleCookies = newValue } 563 } 564 565 open override var httpShouldUsePipelining: Bool { 566 get { return super.httpShouldUsePipelining } 567 set { super.httpShouldUsePipelining = newValue } 568 } 569 570 // These properties are settable using URLProtocol's class methods. 571 var protocolProperties: [String: Any] = [:] 572 } 573 574 /// Returns an existing key-value pair inside the header fields if it exists. 575 private func existingHeaderField(_ key: String, inHeaderFields fields: [String : String]) -> (String, String)? { 576 for (k, v) in fields { 577 if k.lowercased() == key.lowercased() { 578 return (k, v) 579 } 580 } 581 return nil 582 } 583 584 extension NSURLRequest : _StructTypeBridgeable { 585 public typealias _StructType = URLRequest 586 587 public func _bridgeToSwift() -> URLRequest { 588 return URLRequest._unconditionallyBridgeFromObjectiveC(self) 589 } 590 }