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