| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See http://swift.org/LICENSE.txt for license information |
| // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| |
| |
| // ----------------------------------------------------------------------------- |
| /// |
| /// This header file describes the constructs used to represent URL |
| /// load requests in a manner independent of protocol and URL scheme. |
| /// Immutable and mutable variants of this URL load request concept |
| /// are described, named `NSURLRequest` and `NSMutableURLRequest`, |
| /// respectively. A collection of constants is also declared to |
| /// exercise control over URL content caching policy. |
| /// |
| /// `NSURLRequest` and `NSMutableURLRequest` are designed to be |
| /// customized to support protocol-specific requests. Protocol |
| /// implementors who need to extend the capabilities of `NSURLRequest` |
| /// and `NSMutableURLRequest` are encouraged to provide categories on |
| /// these classes as appropriate to support protocol-specific data. To |
| /// store and retrieve data, category methods can use the |
| /// `propertyForKey(_:,inRequest:)` and |
| /// `setProperty(_:,forKey:,inRequest:)` class methods on |
| /// `URLProtocol`. See the `NSHTTPURLRequest` on `NSURLRequest` and |
| /// `NSMutableHTTPURLRequest` on `NSMutableURLRequest` for examples of |
| /// such extensions. |
| /// |
| /// The main advantage of this design is that a client of the URL |
| /// loading library can implement request policies in a standard way |
| /// without type checking of requests or protocol checks on URLs. Any |
| /// protocol-specific details that have been set on a URL request will |
| /// be used if they apply to the particular URL being loaded, and will |
| /// be ignored if they do not apply. |
| /// |
| // ----------------------------------------------------------------------------- |
| |
| /// A cache policy |
| /// |
| /// The `NSURLRequestCachePolicy` `enum` defines constants that |
| /// can be used to specify the type of interactions that take place with |
| /// the caching system when the URL loading system processes a request. |
| /// Specifically, these constants cover interactions that have to do |
| /// with whether already-existing cache data is returned to satisfy a |
| /// URL load request. |
| extension NSURLRequest { |
| public enum CachePolicy : UInt { |
| /// Specifies that the caching logic defined in the protocol |
| /// implementation, if any, is used for a particular URL load request. This |
| /// is the default policy for URL load requests. |
| case useProtocolCachePolicy |
| /// Specifies that the data for the URL load should be loaded from the |
| /// origin source. No existing local cache data, regardless of its freshness |
| /// or validity, should be used to satisfy a URL load request. |
| case reloadIgnoringLocalCacheData |
| /// Specifies that not only should the local cache data be ignored, but that |
| /// proxies and other intermediates should be instructed to disregard their |
| /// caches so far as the protocol allows. Unimplemented. |
| case reloadIgnoringLocalAndRemoteCacheData // Unimplemented |
| /// Older name for `NSURLRequestReloadIgnoringLocalCacheData`. |
| public static var reloadIgnoringCacheData: CachePolicy { return .reloadIgnoringLocalCacheData } |
| /// Specifies that the existing cache data should be used to satisfy a URL |
| /// load request, regardless of its age or expiration date. However, if |
| /// there is no existing data in the cache corresponding to a URL load |
| /// request, the URL is loaded from the origin source. |
| case returnCacheDataElseLoad |
| /// Specifies that the existing cache data should be used to satisfy a URL |
| /// load request, regardless of its age or expiration date. However, if |
| /// there is no existing data in the cache corresponding to a URL load |
| /// request, no attempt is made to load the URL from the origin source, and |
| /// the load is considered to have failed. This constant specifies a |
| /// behavior that is similar to an "offline" mode. |
| case returnCacheDataDontLoad |
| /// Specifies that the existing cache data may be used provided the origin |
| /// source confirms its validity, otherwise the URL is loaded from the |
| /// origin source. |
| /// - Note: Unimplemented. |
| case reloadRevalidatingCacheData // Unimplemented |
| } |
| |
| public enum NetworkServiceType : UInt { |
| case `default` // Standard internet traffic |
| case voip // Voice over IP control traffic |
| case video // Video traffic |
| case background // Background traffic |
| case voice // Voice data |
| case networkServiceTypeCallSignaling // Call Signaling |
| } |
| } |
| |
| /// An `NSURLRequest` object represents a URL load request in a |
| /// manner independent of protocol and URL scheme. |
| /// |
| /// `NSURLRequest` encapsulates basic data elements about a URL load request. |
| /// |
| /// In addition, `NSURLRequest` is designed to be extended to support |
| /// protocol-specific data by adding categories to access a property |
| /// object provided in an interface targeted at protocol implementors. |
| /// |
| /// Protocol implementors should direct their attention to the |
| /// `NSURLRequestExtensibility` category on `NSURLRequest` for more |
| /// information on how to provide extensions on `NSURLRequest` to |
| /// support protocol-specific request information. |
| /// |
| /// Clients of this API who wish to create `NSURLRequest` objects to |
| /// load URL content should consult the protocol-specific `NSURLRequest` |
| /// categories that are available. The `NSHTTPURLRequest` category on |
| /// `NSURLRequest` is an example. |
| /// |
| /// Objects of this class are used with the `URLSession` API to perform the |
| /// load of a URL. |
| open class NSURLRequest : NSObject, NSSecureCoding, NSCopying, NSMutableCopying { |
| |
| open override func copy() -> Any { |
| return copy(with: nil) |
| } |
| |
| open func copy(with zone: NSZone? = nil) -> Any { |
| if type(of: self) === NSURLRequest.self { |
| // Already immutable |
| return self |
| } |
| let c = NSURLRequest(url: url!) |
| c.setValues(from: self) |
| return c |
| } |
| |
| public convenience init(url: URL) { |
| self.init(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 60.0) |
| } |
| |
| public init(url: URL, cachePolicy: NSURLRequest.CachePolicy, timeoutInterval: TimeInterval) { |
| self.url = url |
| self.cachePolicy = cachePolicy |
| self.timeoutInterval = timeoutInterval |
| } |
| |
| private func setValues(from source: NSURLRequest) { |
| self.allHTTPHeaderFields = source.allHTTPHeaderFields |
| self.url = source.url |
| self.mainDocumentURL = source.mainDocumentURL |
| self.httpMethod = source.httpMethod |
| } |
| |
| open override func mutableCopy() -> Any { |
| return mutableCopy(with: nil) |
| } |
| |
| open func mutableCopy(with zone: NSZone? = nil) -> Any { |
| let c = NSMutableURLRequest(url: url!) |
| c.setValues(from: self) |
| return c |
| } |
| |
| public required init?(coder aDecoder: NSCoder) { |
| guard aDecoder.allowsKeyedCoding else { |
| preconditionFailure("Unkeyed coding is unsupported.") |
| } |
| |
| if let encodedURL = aDecoder.decodeObject(forKey: "NS.url") as? NSURL { |
| self.url = encodedURL._swiftObject |
| } |
| |
| if let encodedHeaders = aDecoder.decodeObject(forKey: "NS._allHTTPHeaderFields") as? NSDictionary { |
| self.allHTTPHeaderFields = encodedHeaders.reduce([String : String]()) { result, item in |
| var result = result |
| if let key = item.key as? NSString, |
| let value = item.value as? NSString { |
| result[key._swiftObject] = value._swiftObject |
| } |
| return result |
| } |
| } |
| |
| if let encodedDocumentURL = aDecoder.decodeObject(forKey: "NS.mainDocumentURL") as? NSURL { |
| self.mainDocumentURL = encodedDocumentURL._swiftObject |
| } |
| |
| if let encodedMethod = aDecoder.decodeObject(forKey: "NS.httpMethod") as? NSString { |
| self.httpMethod = encodedMethod._swiftObject |
| } |
| |
| let encodedCachePolicy = aDecoder.decodeObject(forKey: "NS._cachePolicy") as! NSNumber |
| self.cachePolicy = CachePolicy(rawValue: encodedCachePolicy.uintValue)! |
| |
| let encodedTimeout = aDecoder.decodeObject(forKey: "NS._timeoutInterval") as! NSNumber |
| self.timeoutInterval = encodedTimeout.doubleValue |
| |
| let encodedHttpBody: Data? = aDecoder.withDecodedUnsafeBufferPointer(forKey: "NS.httpBody") { |
| guard let buffer = $0 else { return nil } |
| return Data(buffer: buffer) |
| } |
| |
| if let encodedHttpBody = encodedHttpBody { |
| self._body = .data(encodedHttpBody) |
| } |
| |
| let encodedNetworkServiceType = aDecoder.decodeObject(forKey: "NS._networkServiceType") as! NSNumber |
| self.networkServiceType = NetworkServiceType(rawValue: encodedNetworkServiceType.uintValue)! |
| |
| let encodedCellularAccess = aDecoder.decodeObject(forKey: "NS._allowsCellularAccess") as! NSNumber |
| self.allowsCellularAccess = encodedCellularAccess.boolValue |
| |
| let encodedHandleCookies = aDecoder.decodeObject(forKey: "NS._httpShouldHandleCookies") as! NSNumber |
| self.httpShouldHandleCookies = encodedHandleCookies.boolValue |
| |
| let encodedUsePipelining = aDecoder.decodeObject(forKey: "NS._httpShouldUsePipelining") as! NSNumber |
| self.httpShouldUsePipelining = encodedUsePipelining.boolValue |
| } |
| |
| open func encode(with aCoder: NSCoder) { |
| guard aCoder.allowsKeyedCoding else { |
| preconditionFailure("Unkeyed coding is unsupported.") |
| } |
| |
| aCoder.encode(self.url?._bridgeToObjectiveC(), forKey: "NS.url") |
| aCoder.encode(self.allHTTPHeaderFields?._bridgeToObjectiveC(), forKey: "NS._allHTTPHeaderFields") |
| aCoder.encode(self.mainDocumentURL?._bridgeToObjectiveC(), forKey: "NS.mainDocumentURL") |
| aCoder.encode(self.httpMethod?._bridgeToObjectiveC(), forKey: "NS.httpMethod") |
| aCoder.encode(self.cachePolicy.rawValue._bridgeToObjectiveC(), forKey: "NS._cachePolicy") |
| aCoder.encode(self.timeoutInterval._bridgeToObjectiveC(), forKey: "NS._timeoutInterval") |
| if let httpBody = self.httpBody?._bridgeToObjectiveC() { |
| let bytePtr = httpBody.bytes.bindMemory(to: UInt8.self, capacity: httpBody.length) |
| aCoder.encodeBytes(bytePtr, length: httpBody.length, forKey: "NS.httpBody") |
| } |
| //On macOS input stream is not encoded. |
| aCoder.encode(self.networkServiceType.rawValue._bridgeToObjectiveC(), forKey: "NS._networkServiceType") |
| aCoder.encode(self.allowsCellularAccess._bridgeToObjectiveC(), forKey: "NS._allowsCellularAccess") |
| aCoder.encode(self.httpShouldHandleCookies._bridgeToObjectiveC(), forKey: "NS._httpShouldHandleCookies") |
| aCoder.encode(self.httpShouldUsePipelining._bridgeToObjectiveC(), forKey: "NS._httpShouldUsePipelining") |
| } |
| |
| open override func isEqual(_ object: Any?) -> Bool { |
| //On macOS this fields do not determine the result: |
| //allHTTPHeaderFields |
| //timeoutInterval |
| //httBody |
| //networkServiceType |
| //httpShouldUsePipelining |
| guard let other = object as? NSURLRequest else { return false } |
| return other === self |
| || (other.url == self.url |
| && other.mainDocumentURL == self.mainDocumentURL |
| && other.httpMethod == self.httpMethod |
| && other.cachePolicy == self.cachePolicy |
| && other.httpBodyStream == self.httpBodyStream |
| && other.allowsCellularAccess == self.allowsCellularAccess |
| && other.httpShouldHandleCookies == self.httpShouldHandleCookies) |
| } |
| |
| /// Indicates that NSURLRequest implements the NSSecureCoding protocol. |
| open class var supportsSecureCoding: Bool { return true } |
| |
| /// The URL of the receiver. |
| /*@NSCopying */open fileprivate(set) var url: URL? |
| |
| /// The main document URL associated with this load. |
| /// |
| /// This URL is used for the cookie "same domain as main |
| /// document" policy. There may also be other future uses. |
| /*@NSCopying*/ open fileprivate(set) var mainDocumentURL: URL? |
| |
| open internal(set) var cachePolicy: CachePolicy = .useProtocolCachePolicy |
| |
| open internal(set) var timeoutInterval: TimeInterval = 60.0 |
| |
| /// Returns the HTTP request method of the receiver. |
| open fileprivate(set) var httpMethod: String? = "GET" |
| |
| /// A dictionary containing all the HTTP header fields |
| /// of the receiver. |
| open internal(set) var allHTTPHeaderFields: [String : String]? = nil |
| |
| /// Returns the value which corresponds to the given header field. |
| /// |
| /// Note that, in keeping with the HTTP RFC, HTTP header field |
| /// names are case-insensitive. |
| /// - Parameter field: the header field name to use for the lookup |
| /// (case-insensitive). |
| /// - Returns: the value associated with the given header field, or `nil` if |
| /// there is no value associated with the given header field. |
| open func value(forHTTPHeaderField field: String) -> String? { |
| guard let f = allHTTPHeaderFields else { return nil } |
| return existingHeaderField(field, inHeaderFields: f)?.1 |
| } |
| |
| internal enum Body { |
| case data(Data) |
| case stream(InputStream) |
| } |
| internal var _body: Body? |
| |
| open var httpBody: Data? { |
| if let body = _body { |
| switch body { |
| case .data(let data): |
| return data |
| case .stream(_): |
| return nil |
| } |
| } |
| return nil |
| } |
| |
| open var httpBodyStream: InputStream? { |
| if let body = _body { |
| switch body { |
| case .data(_): |
| return nil |
| case .stream(let stream): |
| return stream |
| } |
| } |
| return nil |
| } |
| |
| open internal(set) var networkServiceType: NetworkServiceType = .default |
| |
| open internal(set) var allowsCellularAccess: Bool = true |
| |
| open internal(set) var httpShouldHandleCookies: Bool = true |
| |
| open internal(set) var httpShouldUsePipelining: Bool = true |
| } |
| |
| /// An `NSMutableURLRequest` object represents a mutable URL load |
| /// request in a manner independent of protocol and URL scheme. |
| /// |
| /// This specialization of `NSURLRequest` is provided to aid |
| /// developers who may find it more convenient to mutate a single request |
| /// object for a series of URL loads instead of creating an immutable |
| /// `NSURLRequest` for each load. This programming model is supported by |
| /// the following contract stipulation between `NSMutableURLRequest` and the |
| /// `URLSession` API: `URLSession` makes a deep copy of each |
| /// `NSMutableURLRequest` object passed to it. |
| /// |
| /// `NSMutableURLRequest` is designed to be extended to support |
| /// protocol-specific data by adding categories to access a property |
| /// object provided in an interface targeted at protocol implementors. |
| /// |
| /// Protocol implementors should direct their attention to the |
| /// `NSMutableURLRequestExtensibility` category on |
| /// `NSMutableURLRequest` for more information on how to provide |
| /// extensions on `NSMutableURLRequest` to support protocol-specific |
| /// request information. |
| /// |
| /// Clients of this API who wish to create `NSMutableURLRequest` |
| /// objects to load URL content should consult the protocol-specific |
| /// `NSMutableURLRequest` categories that are available. The |
| /// `NSMutableHTTPURLRequest` category on `NSMutableURLRequest` is an |
| /// example. |
| open class NSMutableURLRequest : NSURLRequest { |
| public required init?(coder aDecoder: NSCoder) { |
| super.init(coder: aDecoder) |
| } |
| |
| public convenience init(url: URL) { |
| self.init(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 60.0) |
| } |
| |
| public override init(url: URL, cachePolicy: NSURLRequest.CachePolicy, timeoutInterval: TimeInterval) { |
| super.init(url: url, cachePolicy: cachePolicy, timeoutInterval: timeoutInterval) |
| } |
| |
| open override func copy(with zone: NSZone? = nil) -> Any { |
| return mutableCopy(with: zone) |
| } |
| |
| /*@NSCopying */ open override var url: URL? { |
| get { return super.url } |
| //TODO: set { super.URL = newValue.map{ $0.copy() as! NSURL } } |
| set { super.url = newValue } |
| } |
| |
| /// The main document URL. |
| /// |
| /// The caller should pass the URL for an appropriate main |
| /// document, if known. For example, when loading a web page, the URL |
| /// of the main html document for the top-level frame should be |
| /// passed. This main document will be used to implement the cookie |
| /// *only from same domain as main document* policy, and possibly |
| /// other things in the future. |
| /*@NSCopying*/ open override var mainDocumentURL: URL? { |
| get { return super.mainDocumentURL } |
| //TODO: set { super.mainDocumentURL = newValue.map{ $0.copy() as! NSURL } } |
| set { super.mainDocumentURL = newValue } |
| } |
| |
| |
| /// The HTTP request method of the receiver. |
| open override var httpMethod: String? { |
| get { return super.httpMethod } |
| set { super.httpMethod = newValue } |
| } |
| |
| open override var cachePolicy: CachePolicy { |
| get { return super.cachePolicy } |
| set { super.cachePolicy = newValue } |
| } |
| |
| open override var timeoutInterval: TimeInterval { |
| get { return super.timeoutInterval } |
| set { super.timeoutInterval = newValue } |
| } |
| |
| open override var allHTTPHeaderFields: [String : String]? { |
| get { return super.allHTTPHeaderFields } |
| set { super.allHTTPHeaderFields = newValue } |
| } |
| |
| /// Sets the value of the given HTTP header field. |
| /// |
| /// If a value was previously set for the given header |
| /// field, that value is replaced with the given value. Note that, in |
| /// keeping with the HTTP RFC, HTTP header field names are |
| /// case-insensitive. |
| /// - Parameter value: the header field value. |
| /// - Parameter field: the header field name (case-insensitive). |
| open func setValue(_ value: String?, forHTTPHeaderField field: String) { |
| var f: [String : String] = allHTTPHeaderFields ?? [:] |
| if let old = existingHeaderField(field, inHeaderFields: f) { |
| f.removeValue(forKey: old.0) |
| } |
| f[field] = value |
| allHTTPHeaderFields = f |
| } |
| |
| /// Adds an HTTP header field in the current header dictionary. |
| /// |
| /// This method provides a way to add values to header |
| /// fields incrementally. If a value was previously set for the given |
| /// header field, the given value is appended to the previously-existing |
| /// value. The appropriate field delimiter, a comma in the case of HTTP, |
| /// is added by the implementation, and should not be added to the given |
| /// value by the caller. Note that, in keeping with the HTTP RFC, HTTP |
| /// header field names are case-insensitive. |
| /// - Parameter value: the header field value. |
| /// - Parameter field: the header field name (case-insensitive). |
| open func addValue(_ value: String, forHTTPHeaderField field: String) { |
| var f: [String : String] = allHTTPHeaderFields ?? [:] |
| if let old = existingHeaderField(field, inHeaderFields: f) { |
| f[old.0] = old.1 + "," + value |
| } else { |
| f[field] = value |
| } |
| allHTTPHeaderFields = f |
| } |
| |
| open override var httpBody: Data? { |
| get { |
| if let body = _body { |
| switch body { |
| case .data(let data): |
| return data |
| case .stream(_): |
| return nil |
| } |
| } |
| return nil |
| } |
| set { |
| if let value = newValue { |
| _body = Body.data(value) |
| } else { |
| _body = nil |
| } |
| } |
| } |
| |
| open override var httpBodyStream: InputStream? { |
| get { |
| if let body = _body { |
| switch body { |
| case .data(_): |
| return nil |
| case .stream(let stream): |
| return stream |
| } |
| } |
| return nil |
| } |
| set { |
| if let value = newValue { |
| _body = Body.stream(value) |
| } else { |
| _body = nil |
| } |
| } |
| } |
| |
| open override var networkServiceType: NetworkServiceType { |
| get { return super.networkServiceType } |
| set { super.networkServiceType = newValue } |
| } |
| |
| open override var allowsCellularAccess: Bool { |
| get { return super.allowsCellularAccess } |
| set { super.allowsCellularAccess = newValue } |
| } |
| |
| open override var httpShouldHandleCookies: Bool { |
| get { return super.httpShouldHandleCookies } |
| set { super.httpShouldHandleCookies = newValue } |
| } |
| |
| open override var httpShouldUsePipelining: Bool { |
| get { return super.httpShouldUsePipelining } |
| set { super.httpShouldUsePipelining = newValue } |
| } |
| } |
| |
| /// Returns an existing key-value pair inside the header fields if it exists. |
| private func existingHeaderField(_ key: String, inHeaderFields fields: [String : String]) -> (String, String)? { |
| for (k, v) in fields { |
| if k.lowercased() == key.lowercased() { |
| return (k, v) |
| } |
| } |
| return nil |
| } |
| |
| extension NSURLRequest : _StructTypeBridgeable { |
| public typealias _StructType = URLRequest |
| |
| public func _bridgeToSwift() -> URLRequest { |
| return URLRequest._unconditionallyBridgeFromObjectiveC(self) |
| } |
| } |