URLProtocol.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 @header URLProtocol.h 18 19 This header file describes the constructs used to represent URL 20 protocols, and describes the extensible system by which specific 21 classes can be made to handle the loading of particular URL types or 22 schemes. 23 24 <p>URLProtocol is an abstract class which provides the 25 basic structure for performing protocol-specific loading of URL 26 data. 27 28 <p>The URLProtocolClient describes the integration points a 29 protocol implementation can use to hook into the URL loading system. 30 URLProtocolClient describes the methods a protocol implementation 31 needs to drive the URL loading system from a URLProtocol subclass. 32 33 <p>To support customization of protocol-specific requests, 34 protocol implementors are encouraged to provide categories on 35 NSURLRequest and NSMutableURLRequest. Protocol implementors who 36 need to extend the capabilities of NSURLRequest and 37 NSMutableURLRequest in this way can store and retrieve 38 protocol-specific request data by using the 39 <tt>+propertyForKey:inRequest:</tt> and 40 <tt>+setProperty:forKey:inRequest:</tt> class methods on 41 URLProtocol. See the NSHTTPURLRequest on NSURLRequest and 42 NSMutableHTTPURLRequest on NSMutableURLRequest for examples of 43 such extensions. 44 45 <p>An essential responsibility for a protocol implementor is 46 creating a URLResponse for each request it processes successfully. 47 A protocol implementor may wish to create a custom, mutable 48 URLResponse class to aid in this work. 49 */ 50 51 /*! 52 @protocol URLProtocolClient 53 @discussion URLProtocolClient provides the interface to the URL 54 loading system that is intended for use by URLProtocol 55 implementors. 56 */ 57 public protocol URLProtocolClient : NSObjectProtocol { 58 59 60 /*! 61 @method URLProtocol:wasRedirectedToRequest: 62 @abstract Indicates to an URLProtocolClient that a redirect has 63 occurred. 64 @param URLProtocol the URLProtocol object sending the message. 65 @param request the NSURLRequest to which the protocol implementation 66 has redirected. 67 */ 68 func urlProtocol(_ protocol: URLProtocol, wasRedirectedTo request: URLRequest, redirectResponse: URLResponse) 69 70 71 /*! 72 @method URLProtocol:cachedResponseIsValid: 73 @abstract Indicates to an URLProtocolClient that the protocol 74 implementation has examined a cached response and has 75 determined that it is valid. 76 @param URLProtocol the URLProtocol object sending the message. 77 @param cachedResponse the NSCachedURLResponse object that has 78 examined and is valid. 79 */ 80 func urlProtocol(_ protocol: URLProtocol, cachedResponseIsValid cachedResponse: CachedURLResponse) 81 82 83 /*! 84 @method URLProtocol:didReceiveResponse: 85 @abstract Indicates to an URLProtocolClient that the protocol 86 implementation has created an URLResponse for the current load. 87 @param URLProtocol the URLProtocol object sending the message. 88 @param response the URLResponse object the protocol implementation 89 has created. 90 @param cacheStoragePolicy The URLCache.StoragePolicy the protocol 91 has determined should be used for the given response if the 92 response is to be stored in a cache. 93 */ 94 func urlProtocol(_ protocol: URLProtocol, didReceive response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy) 95 96 97 /*! 98 @method URLProtocol:didLoadData: 99 @abstract Indicates to an NSURLProtocolClient that the protocol 100 implementation has loaded URL data. 101 @discussion The data object must contain only new data loaded since 102 the previous call to this method (if any), not cumulative data for 103 the entire load. 104 @param URLProtocol the NSURLProtocol object sending the message. 105 @param data URL load data being made available. 106 */ 107 func urlProtocol(_ protocol: URLProtocol, didLoad data: Data) 108 109 110 /*! 111 @method URLProtocolDidFinishLoading: 112 @abstract Indicates to an NSURLProtocolClient that the protocol 113 implementation has finished loading successfully. 114 @param URLProtocol the NSURLProtocol object sending the message. 115 */ 116 func urlProtocolDidFinishLoading(_ protocol: URLProtocol) 117 118 119 /*! 120 @method URLProtocol:didFailWithError: 121 @abstract Indicates to an NSURLProtocolClient that the protocol 122 implementation has failed to load successfully. 123 @param URLProtocol the NSURLProtocol object sending the message. 124 @param error The error that caused the load to fail. 125 */ 126 func urlProtocol(_ protocol: URLProtocol, didFailWithError error: Error) 127 128 129 /*! 130 @method URLProtocol:didReceiveAuthenticationChallenge: 131 @abstract Start authentication for the specified request 132 @param protocol The protocol object requesting authentication. 133 @param challenge The authentication challenge. 134 @discussion The protocol client guarantees that it will answer the 135 request on the same thread that called this method. It may add a 136 default credential to the challenge it issues to the connection delegate, 137 if the protocol did not provide one. 138 */ 139 func urlProtocol(_ protocol: URLProtocol, didReceive challenge: URLAuthenticationChallenge) 140 141 142 /*! 143 @method URLProtocol:didCancelAuthenticationChallenge: 144 @abstract Cancel authentication for the specified request 145 @param protocol The protocol object cancelling authentication. 146 @param challenge The authentication challenge. 147 */ 148 func urlProtocol(_ protocol: URLProtocol, didCancel challenge: URLAuthenticationChallenge) 149 } 150 151 internal class _ProtocolClient : NSObject { 152 var cachePolicy: URLCache.StoragePolicy = .notAllowed 153 var cacheableData: [Data]? 154 var cacheableResponse: URLResponse? 155 } 156 157 /*! 158 @class NSURLProtocol 159 160 @abstract NSURLProtocol is an abstract class which provides the 161 basic structure for performing protocol-specific loading of URL 162 data. Concrete subclasses handle the specifics associated with one 163 or more protocols or URL schemes. 164 */ 165 open class URLProtocol : NSObject { 166 167 private static var _registeredProtocolClasses = [AnyClass]() 168 private static var _classesLock = NSLock() 169 170 //TODO: The right way to do this is using URLProtocol.property(forKey:in) and URLProtocol.setProperty(_:forKey:in) 171 var properties: [URLProtocol._PropertyKey: Any] = [:] 172 /*! 173 @method initWithRequest:cachedResponse:client: 174 @abstract Initializes an NSURLProtocol given request, 175 cached response, and client. 176 @param request The request to load. 177 @param cachedResponse A response that has been retrieved from the 178 cache for the given request. The protocol implementation should 179 apply protocol-specific validity checks if such tests are 180 necessary. 181 @param client The NSURLProtocolClient object that serves as the 182 interface the protocol implementation can use to report results back 183 to the URL loading system. 184 */ 185 public required init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { 186 self._request = request 187 self._cachedResponse = cachedResponse 188 self._client = client ?? _ProtocolClient() 189 } 190 191 private var _request : URLRequest 192 private var _cachedResponse : CachedURLResponse? 193 private var _client : URLProtocolClient? 194 195 /*! 196 @method client 197 @abstract Returns the NSURLProtocolClient of the receiver. 198 @result The NSURLProtocolClient of the receiver. 199 */ 200 open var client: URLProtocolClient? { 201 set { self._client = newValue } 202 get { return self._client } 203 } 204 205 /*! 206 @method request 207 @abstract Returns the NSURLRequest of the receiver. 208 @result The NSURLRequest of the receiver. 209 */ 210 /*@NSCopying*/ open var request: URLRequest { 211 return _request 212 } 213 214 /*! 215 @method cachedResponse 216 @abstract Returns the NSCachedURLResponse of the receiver. 217 @result The NSCachedURLResponse of the receiver. 218 */ 219 /*@NSCopying*/ open var cachedResponse: CachedURLResponse? { 220 return _cachedResponse 221 } 222 223 /*====================================================================== 224 Begin responsibilities for protocol implementors 225 226 The methods between this set of begin-end markers must be 227 implemented in order to create a working protocol. 228 ======================================================================*/ 229 230 /*! 231 @method canInitWithRequest: 232 @abstract This method determines whether this protocol can handle 233 the given request. 234 @discussion A concrete subclass should inspect the given request and 235 determine whether or not the implementation can perform a load with 236 that request. This is an abstract method. Sublasses must provide an 237 implementation. The implementation in this class calls 238 NSRequestConcreteImplementation. 239 @param request A request to inspect. 240 @result YES if the protocol can handle the given request, NO if not. 241 */ 242 open class func canInit(with request: URLRequest) -> Bool { 243 NSRequiresConcreteImplementation() 244 } 245 246 /*! 247 @method canonicalRequestForRequest: 248 @abstract This method returns a canonical version of the given 249 request. 250 @discussion It is up to each concrete protocol implementation to 251 define what "canonical" means. However, a protocol should 252 guarantee that the same input request always yields the same 253 canonical form. Special consideration should be given when 254 implementing this method since the canonical form of a request is 255 used to look up objects in the URL cache, a process which performs 256 equality checks between NSURLRequest objects. 257 <p> 258 This is an abstract method; sublasses must provide an 259 implementation. The implementation in this class calls 260 NSRequestConcreteImplementation. 261 @param request A request to make canonical. 262 @result The canonical form of the given request. 263 */ 264 open class func canonicalRequest(for request: URLRequest) -> URLRequest { 265 NSRequiresConcreteImplementation() 266 } 267 268 /*! 269 @method requestIsCacheEquivalent:toRequest: 270 @abstract Compares two requests for equivalence with regard to caching. 271 @discussion Requests are considered equivalent for cache purposes 272 if and only if they would be handled by the same protocol AND that 273 protocol declares them equivalent after performing 274 implementation-specific checks. 275 @result YES if the two requests are cache-equivalent, NO otherwise. 276 */ 277 open class func requestIsCacheEquivalent(_ a: URLRequest, to b: URLRequest) -> Bool { 278 NSRequiresConcreteImplementation() 279 } 280 281 /*! 282 @method startLoading 283 @abstract Starts protocol-specific loading of a request. 284 @discussion When this method is called, the protocol implementation 285 should start loading a request. 286 */ 287 open func startLoading() { 288 NSRequiresConcreteImplementation() 289 } 290 291 /*! 292 @method stopLoading 293 @abstract Stops protocol-specific loading of a request. 294 @discussion When this method is called, the protocol implementation 295 should end the work of loading a request. This could be in response 296 to a cancel operation, so protocol implementations must be able to 297 handle this call while a load is in progress. 298 */ 299 open func stopLoading() { 300 NSRequiresConcreteImplementation() 301 } 302 303 /*====================================================================== 304 End responsibilities for protocol implementors 305 ======================================================================*/ 306 307 /*! 308 @method propertyForKey:inRequest: 309 @abstract Returns the property in the given request previously 310 stored with the given key. 311 @discussion The purpose of this method is to provide an interface 312 for protocol implementors to access protocol-specific information 313 associated with NSURLRequest objects. 314 @param key The string to use for the property lookup. 315 @param request The request to use for the property lookup. 316 @result The property stored with the given key, or nil if no property 317 had previously been stored with the given key in the given request. 318 */ 319 open class func property(forKey key: String, in request: URLRequest) -> Any? { 320 return request.protocolProperties[key] 321 } 322 323 /*! 324 @method setProperty:forKey:inRequest: 325 @abstract Stores the given property in the given request using the 326 given key. 327 @discussion The purpose of this method is to provide an interface 328 for protocol implementors to customize protocol-specific 329 information associated with NSMutableURLRequest objects. 330 @param value The property to store. 331 @param key The string to use for the property storage. 332 @param request The request in which to store the property. 333 */ 334 open class func setProperty(_ value: Any, forKey key: String, in request: NSMutableURLRequest) { 335 request.protocolProperties[key] = value 336 } 337 338 /*! 339 @method removePropertyForKey:inRequest: 340 @abstract Remove any property stored under the given key 341 @discussion Like setProperty:forKey:inRequest: above, the purpose of this 342 method is to give protocol implementors the ability to store 343 protocol-specific information in an NSURLRequest 344 @param key The key whose value should be removed 345 @param request The request to be modified 346 */ 347 open class func removeProperty(forKey key: String, in request: NSMutableURLRequest) { 348 request.protocolProperties.removeValue(forKey: key) 349 } 350 351 /*! 352 @method registerClass: 353 @abstract This method registers a protocol class, making it visible 354 to several other NSURLProtocol class methods. 355 @discussion When the URL loading system begins to load a request, 356 each protocol class that has been registered is consulted in turn to 357 see if it can be initialized with a given request. The first 358 protocol handler class to provide a YES answer to 359 <tt>+canInitWithRequest:</tt> "wins" and that protocol 360 implementation is used to perform the URL load. There is no 361 guarantee that all registered protocol classes will be consulted. 362 Hence, it should be noted that registering a class places it first 363 on the list of classes that will be consulted in calls to 364 <tt>+canInitWithRequest:</tt>, moving it in front of all classes 365 that had been registered previously. 366 <p>A similar design governs the process to create the canonical form 367 of a request with the <tt>+canonicalRequestForRequest:</tt> class 368 method. 369 @param protocolClass the class to register. 370 @result YES if the protocol was registered successfully, NO if not. 371 The only way that failure can occur is if the given class is not a 372 subclass of NSURLProtocol. 373 */ 374 open class func registerClass(_ protocolClass: AnyClass) -> Bool { 375 if protocolClass is URLProtocol.Type { 376 _classesLock.lock() 377 guard !_registeredProtocolClasses.contains(where: { $0 === protocolClass }) else { 378 _classesLock.unlock() 379 return true 380 } 381 _registeredProtocolClasses.append(protocolClass) 382 _classesLock.unlock() 383 return true 384 } 385 return false 386 } 387 388 internal class func getProtocolClass(protocols: [AnyClass], request: URLRequest) -> AnyClass? { 389 // Registered protocols are consulted in reverse order. 390 // This behaviour makes the latest registered protocol to be consulted first 391 _classesLock.lock() 392 let protocolClasses = protocols 393 for protocolClass in protocolClasses { 394 let urlProtocolClass: AnyClass = protocolClass 395 guard let urlProtocol = urlProtocolClass as? URLProtocol.Type else { fatalError() } 396 if urlProtocol.canInit(with: request) { 397 _classesLock.unlock() 398 return urlProtocol 399 } 400 } 401 _classesLock.unlock() 402 return nil 403 } 404 405 internal class func getProtocols() -> [AnyClass]? { 406 _classesLock.lock() 407 defer { _classesLock.unlock() } 408 return _registeredProtocolClasses 409 } 410 /*! 411 @method unregisterClass: 412 @abstract This method unregisters a protocol. 413 @discussion After unregistration, a protocol class is no longer 414 consulted in calls to NSURLProtocol class methods. 415 @param protocolClass The class to unregister. 416 */ 417 open class func unregisterClass(_ protocolClass: AnyClass) { 418 _classesLock.lock() 419 if let idx = _registeredProtocolClasses.firstIndex(where: { $0 === protocolClass }) { 420 _registeredProtocolClasses.remove(at: idx) 421 } 422 _classesLock.unlock() 423 } 424 425 open class func canInit(with task: URLSessionTask) -> Bool { 426 guard let request = task.currentRequest else { return false } 427 return canInit(with: request) 428 } 429 public required convenience init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { 430 let urlRequest = task.originalRequest 431 self.init(request: urlRequest!, cachedResponse: cachedResponse, client: client) 432 self.task = task 433 } 434 /*@NSCopying*/ open var task: URLSessionTask? { 435 set { self._task = newValue } 436 get { return self._task } 437 } 438 439 private var _task : URLSessionTask? = nil 440 }