/ Sources / FoundationNetworking / NSURLRequest.swift
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  }