Merge pull request #1027 from nethraravindran/swift-3.1-branch

diff --git a/Foundation.xcodeproj/project.pbxproj b/Foundation.xcodeproj/project.pbxproj
index 4716c68..3ae9951 100644
--- a/Foundation.xcodeproj/project.pbxproj
+++ b/Foundation.xcodeproj/project.pbxproj
@@ -78,6 +78,7 @@
 		5B1FD9DE1D6D16580080E83C /* TaskRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1FD9D21D6D16580080E83C /* TaskRegistry.swift */; };
 		5B1FD9DF1D6D16580080E83C /* TransferState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1FD9D31D6D16580080E83C /* TransferState.swift */; };
 		5B1FD9E11D6D178E0080E83C /* libcurl.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B1FD9E01D6D178E0080E83C /* libcurl.3.dylib */; };
+                E429ED451E9638DA0031BC20 /* HTTPURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E429ED441E9638DA0031BC20 /* HTTPURLProtocol.swift */; };
 		5B1FD9E31D6D17B80080E83C /* TestNSURLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1FD9E21D6D17B80080E83C /* TestNSURLSession.swift */; };
 		5B23AB871CE62D17000DB898 /* Boxing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B23AB861CE62D17000DB898 /* Boxing.swift */; };
 		5B23AB891CE62D4D000DB898 /* ReferenceConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B23AB881CE62D4D000DB898 /* ReferenceConvertible.swift */; };
@@ -495,6 +496,7 @@
 		5B1FD9D21D6D16580080E83C /* TaskRegistry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TaskRegistry.swift; sourceTree = "<group>"; };
 		5B1FD9D31D6D16580080E83C /* TransferState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransferState.swift; sourceTree = "<group>"; };
 		5B1FD9E01D6D178E0080E83C /* libcurl.3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcurl.3.dylib; path = usr/lib/libcurl.3.dylib; sourceTree = SDKROOT; };
+                E429ED441E9638DA0031BC20 /* HTTPURLProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HTTPURLProtocol.swift;  path = HTTPURLProtocol.swift; sourceTree = "<group>"; };
 		5B1FD9E21D6D17B80080E83C /* TestNSURLSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSURLSession.swift; sourceTree = "<group>"; };
 		5B23AB861CE62D17000DB898 /* Boxing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Boxing.swift; sourceTree = "<group>"; };
 		5B23AB881CE62D4D000DB898 /* ReferenceConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReferenceConvertible.swift; sourceTree = "<group>"; };
@@ -928,23 +930,32 @@
 		5B1FD9C71D6D162D0080E83C /* Session */ = {
 			isa = PBXGroup;
 			children = (
+				E4F889331E9CF04D008A70EB /* http */,
 				5B1FD9C81D6D16580080E83C /* Configuration.swift */,
-				5B1FD9C91D6D16580080E83C /* EasyHandle.swift */,
-				5B1FD9CA1D6D16580080E83C /* HTTPBodySource.swift */,
-				5B1FD9CB1D6D16580080E83C /* HTTPMessage.swift */,
-				5B1FD9CC1D6D16580080E83C /* libcurlHelpers.swift */,
-				5B1FD9CD1D6D16580080E83C /* MultiHandle.swift */,
 				5B1FD9CE1D6D16580080E83C /* NSURLSession.swift */,
 				5B1FD9CF1D6D16580080E83C /* NSURLSessionConfiguration.swift */,
 				5B1FD9D01D6D16580080E83C /* NSURLSessionDelegate.swift */,
 				5B1FD9D11D6D16580080E83C /* NSURLSessionTask.swift */,
 				5B1FD9D21D6D16580080E83C /* TaskRegistry.swift */,
-				5B1FD9D31D6D16580080E83C /* TransferState.swift */,
 			);
 			name = Session;
 			path = NSURLSession;
 			sourceTree = "<group>";
 		};
+		E4F889331E9CF04D008A70EB /* http */ = {
+			isa = PBXGroup;
+			children = (
+				E429ED441E9638DA0031BC20 /* HTTPURLProtocol.swift */,
+				5B1FD9C91D6D16580080E83C /* EasyHandle.swift */,
+                                5B1FD9CA1D6D16580080E83C /* HTTPBodySource.swift */,
+                                5B1FD9CB1D6D16580080E83C /* HTTPMessage.swift */,
+                                5B1FD9CC1D6D16580080E83C /* libcurlHelpers.swift */,
+                                5B1FD9CD1D6D16580080E83C /* MultiHandle.swift */,
+                                5B1FD9D31D6D16580080E83C /* TransferState.swift */,
+                        );
+                        name = http;
+                        sourceTree = "<group>";
+                };
 		5B5D88531BBC938800234F36 = {
 			isa = PBXGroup;
 			children = (
@@ -2040,6 +2051,7 @@
 				5B23AB871CE62D17000DB898 /* Boxing.swift in Sources */,
 				5BF7AEA41BCD51F9008F214A /* Bundle.swift in Sources */,
 				5B23AB891CE62D4D000DB898 /* ReferenceConvertible.swift in Sources */,
+				E429ED451E9638DA0031BC20 /* HTTPURLProtocol.swift in Sources */,
 				D3E8D6D11C367AB600295652 /* NSSpecialValue.swift in Sources */,
 				5B1FD9D51D6D16580080E83C /* EasyHandle.swift in Sources */,
 				EAB57B721BD1C7A5004AC5C5 /* NSPortMessage.swift in Sources */,
diff --git a/Foundation/NSURLProtocol.swift b/Foundation/NSURLProtocol.swift
index 5004d1d..30299fd 100644
--- a/Foundation/NSURLProtocol.swift
+++ b/Foundation/NSURLProtocol.swift
@@ -7,6 +7,9 @@
 // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
 //
 
+import CoreFoundation
+import Dispatch
+
 /*!
     @header NSURLProtocol.h
 
@@ -142,6 +145,96 @@
     func urlProtocol(_ protocol: URLProtocol, didCancel challenge: URLAuthenticationChallenge)
 }
 
+internal class _ProtocolClient : NSObject, URLProtocolClient {
+
+    func urlProtocol(_ protocol: URLProtocol, didReceive response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy) {
+        `protocol`.task?.response = response
+    }
+
+    func urlProtocolDidFinishLoading(_ protocol: URLProtocol) {
+        guard let task = `protocol`.task else { fatalError() }
+        guard let session = task.session as? URLSession else { fatalError() }
+        switch session.behaviour(for: task) {
+        case .taskDelegate(let delegate):
+            guard let s = session as? URLSession else { fatalError() }
+            s.delegateQueue.addOperation {
+                delegate.urlSession(s, task: task, didCompleteWithError: nil)
+                task.state = .completed
+            }
+        case .noDelegate:
+            task.state = .completed
+        case .dataCompletionHandler(let completion):
+            let data = Data()
+            guard let client = `protocol`.client else { fatalError() }
+            client.urlProtocol(`protocol`, didLoad: data)
+            return
+        case .downloadCompletionHandler(let completion):
+            guard let s = session as? URLSession else { fatalError() }
+            s.delegateQueue.addOperation {
+                completion(task.currentRequest?.url, task.response, nil)
+                task.state = .completed
+            }
+        }
+    }
+
+    func urlProtocol(_ protocol: URLProtocol, didCancel challenge: URLAuthenticationChallenge) {
+        NSUnimplemented()
+    }
+
+    func urlProtocol(_ protocol: URLProtocol, didReceive challenge: URLAuthenticationChallenge) {
+        NSUnimplemented()
+    }
+
+    func urlProtocol(_ protocol: URLProtocol, didLoad data: Data) {
+        guard let task = `protocol`.task else { fatalError() }
+        guard let session = task.session as? URLSession else { fatalError() }
+        switch session.behaviour(for: task) {
+        case .dataCompletionHandler(let completion):
+            guard let s = task.session as? URLSession else { fatalError() }
+            s.delegateQueue.addOperation {
+                completion(data, task.response, nil)
+                task.state = .completed
+            }
+        default: return
+        }
+    }
+
+    func urlProtocol(_ protocol: URLProtocol, didFailWithError error: Error) {
+        guard let task = `protocol`.task else { fatalError() }
+        guard let session = task.session as? URLSession else { fatalError() }
+        switch session.behaviour(for: task) {
+        case .taskDelegate(let delegate):
+            guard let s = session as? URLSession else { fatalError() }
+            s.delegateQueue.addOperation {
+                delegate.urlSession(s, task: task, didCompleteWithError: error as Error)
+                task.state = .completed
+            }
+        case .noDelegate:
+            task.state = .completed
+        case .dataCompletionHandler(let completion):
+            guard let s = session as? URLSession else { fatalError() }
+            s.delegateQueue.addOperation {
+                completion(nil, nil, error)
+                task.state = .completed
+            }
+        case .downloadCompletionHandler(let completion):
+            guard let s = session as? URLSession else { fatalError() }
+            s.delegateQueue.addOperation {
+                completion(nil, nil, error)
+                task.state = .completed
+            }
+        }
+    }
+
+    func urlProtocol(_ protocol: URLProtocol, cachedResponseIsValid cachedResponse: CachedURLResponse) {
+        NSUnimplemented()
+    }
+
+    func urlProtocol(_ protocol: URLProtocol, wasRedirectedTo request: URLRequest, redirectResponse: URLResponse) {
+        NSUnimplemented()
+    }
+}
+
 /*!
     @class NSURLProtocol
  
@@ -151,7 +244,9 @@
     or more protocols or URL schemes.
 */
 open class URLProtocol : NSObject {
-    
+
+    private static var _registeredProtocolClasses = [AnyClass]()
+    private static var _classesLock = NSLock()
     /*! 
         @method initWithRequest:cachedResponse:client:
         @abstract Initializes an NSURLProtocol given request, 
@@ -165,28 +260,43 @@
         interface the protocol implementation can use to report results back
         to the URL loading system.
     */
-    public init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { NSUnimplemented() }
-    
+    public required init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
+        self._request = request
+        self._cachedResponse = cachedResponse
+        self._client = client ?? _ProtocolClient()
+    }
+
+    private var _request : URLRequest
+    private var _cachedResponse : CachedURLResponse?
+    private var _client : URLProtocolClient?
+
     /*! 
         @method client
         @abstract Returns the NSURLProtocolClient of the receiver. 
         @result The NSURLProtocolClient of the receiver.  
     */
-    open var client: URLProtocolClient? { NSUnimplemented() }
+    open var client: URLProtocolClient? {
+        set { self._client = newValue }
+        get { return self._client }
+    }
     
     /*! 
         @method request
         @abstract Returns the NSURLRequest of the receiver. 
         @result The NSURLRequest of the receiver. 
     */
-    /*@NSCopying*/ open var request: URLRequest { NSUnimplemented() }
+    /*@NSCopying*/ open var request: URLRequest {
+        return _request
+     }
     
     /*! 
         @method cachedResponse
         @abstract Returns the NSCachedURLResponse of the receiver.  
         @result The NSCachedURLResponse of the receiver. 
     */
-    /*@NSCopying*/ open var cachedResponse: CachedURLResponse? { NSUnimplemented() }
+    /*@NSCopying*/ open var cachedResponse: CachedURLResponse? {
+        return _cachedResponse
+     }
     
     /*======================================================================
       Begin responsibilities for protocol implementors
@@ -207,7 +317,9 @@
         @param request A request to inspect.
         @result YES if the protocol can handle the given request, NO if not.
     */
-    open class func canInit(with request: URLRequest) -> Bool { NSUnimplemented() }
+    open class func canInit(with request: URLRequest) -> Bool {
+        NSRequiresConcreteImplementation()
+    }
     
     /*! 
         @method canonicalRequestForRequest:
@@ -246,7 +358,9 @@
         @discussion When this method is called, the protocol implementation
         should start loading a request.
     */
-    open func startLoading() { NSUnimplemented() }
+    open func startLoading() {
+        NSRequiresConcreteImplementation()
+    }
     
     /*! 
         @method stopLoading
@@ -256,7 +370,9 @@
         to a cancel operation, so protocol implementations must be able to
         handle this call while a load is in progress.
     */
-    open func stopLoading() { NSUnimplemented() }
+    open func stopLoading() {
+        NSRequiresConcreteImplementation()
+    }
     
     /*======================================================================
       End responsibilities for protocol implementors
@@ -323,8 +439,40 @@
         The only way that failure can occur is if the given class is not a
         subclass of NSURLProtocol.
     */
-    open class func registerClass(_ protocolClass: AnyClass) -> Bool { NSUnimplemented() }
-    
+    open class func registerClass(_ protocolClass: AnyClass) -> Bool {
+        if protocolClass is URLProtocol.Type {
+            _classesLock.lock()
+            guard !_registeredProtocolClasses.contains(where: { $0 === protocolClass }) else {
+                _classesLock.unlock()
+                return true
+            }
+            _registeredProtocolClasses.append(protocolClass)
+            _classesLock.unlock()
+            return true
+        }
+        return false
+    }
+
+    internal class func getProtocolClass(protocols: [AnyClass], request: URLRequest) -> AnyClass? {
+        // Registered protocols are consulted in reverse order.
+        // This behaviour makes the latest registered protocol to be consulted first
+        _classesLock.lock()
+        let protocolClasses = protocols
+        for protocolClass in protocolClasses {
+            let urlProtocolClass: AnyClass = protocolClass
+            guard let urlProtocol = urlProtocolClass as? URLProtocol.Type else { fatalError() }
+            if urlProtocol.canInit(with: request) {
+                _classesLock.unlock()
+                return urlProtocol
+            }
+        }
+        _classesLock.unlock()
+        return nil
+    }
+
+    internal class func getProtocols() -> [AnyClass]? {
+        return _registeredProtocolClasses
+    }
     /*! 
         @method unregisterClass:
         @abstract This method unregisters a protocol. 
@@ -332,10 +480,24 @@
         consulted in calls to NSURLProtocol class methods.
         @param protocolClass The class to unregister.
     */
-    open class func unregisterClass(_ protocolClass: AnyClass) { NSUnimplemented() }
+    open class func unregisterClass(_ protocolClass: AnyClass) {
+        _classesLock.lock()
+        if let idx = _registeredProtocolClasses.index(where: { $0 === protocolClass }) {
+            _registeredProtocolClasses.remove(at: idx)
+        }
+        _classesLock.unlock()
+    }
 
     open class func canInit(with task: URLSessionTask) -> Bool { NSUnimplemented() }
-    public convenience init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) { NSUnimplemented() }
-    /*@NSCopying*/ open var task: URLSessionTask? { NSUnimplemented() }
-}
+    public required convenience init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
+        let urlRequest = task.originalRequest
+        self.init(request: urlRequest!, cachedResponse: cachedResponse, client: client)
+        self.task = task
+    }
+    /*@NSCopying*/ open var task: URLSessionTask? {
+        set { self._task = newValue }
+        get { return self._task }
+    }
 
+    private var _task : URLSessionTask? = nil
+}
diff --git a/Foundation/NSURLSession/NSURLSession.swift b/Foundation/NSURLSession/NSURLSession.swift
index ab966c6..805e086 100644
--- a/Foundation/NSURLSession/NSURLSession.swift
+++ b/Foundation/NSURLSession/NSURLSession.swift
@@ -221,6 +221,8 @@
         let c = URLSession._Configuration(URLSessionConfiguration: configuration)
         self._configuration = c
         self.multiHandle = _MultiHandle(configuration: c, workQueue: workQueue)
+        // registering all the protocol classes with URLProtocol
+        let _ = URLProtocol.registerClass(_HTTPURLProtocol.self)
     }
 
     /*
@@ -246,6 +248,8 @@
         let c = URLSession._Configuration(URLSessionConfiguration: configuration)
         self._configuration = c
         self.multiHandle = _MultiHandle(configuration: c, workQueue: workQueue)
+        // registering all the protocol classes with URLProtocol
+        let _ = URLProtocol.registerClass(_HTTPURLProtocol.self)
     }
     
     open let delegateQueue: OperationQueue
diff --git a/Foundation/NSURLSession/NSURLSessionTask.swift b/Foundation/NSURLSession/NSURLSessionTask.swift
index 0b09fcf..1e010b8 100644
--- a/Foundation/NSURLSession/NSURLSessionTask.swift
+++ b/Foundation/NSURLSession/NSURLSessionTask.swift
@@ -26,55 +26,19 @@
 /// of processing a given request.
 open class URLSessionTask : NSObject, NSCopying {
     /// How many times the task has been suspended, 0 indicating a running task.
-    fileprivate var suspendCount = 1
-    fileprivate var easyHandle: _EasyHandle!
-    fileprivate var totalDownloaded = 0
-    fileprivate var session: URLSessionProtocol! //change to nil when task completes
-    fileprivate let body: _Body
-    fileprivate let tempFileURL: URL
+    internal var suspendCount = 1
+    internal var totalDownloaded = 0
+    internal var session: URLSessionProtocol! //change to nil when task completes
+    internal let body: _Body
+    internal let tempFileURL: URL
+    fileprivate var _protocol: URLProtocol! = nil
     
-    /// The internal state that the task is in.
-    ///
-    /// Setting this value will also add / remove the easy handle.
-    /// It is independt of the `state: URLSessionTask.State`. The
-    /// `internalState` tracks the state of transfers / waiting for callbacks.
-    /// The `state` tracks the overall state of the task (running vs.
-    /// completed).
-    /// - SeeAlso: URLSessionTask._InternalState
-    fileprivate var internalState = _InternalState.initial {
-        // We manage adding / removing the easy handle and pausing / unpausing
-        // here at a centralized place to make sure the internal state always
-        // matches up with the state of the easy handle being added and paused.
-        willSet {
-            if !internalState.isEasyHandlePaused && newValue.isEasyHandlePaused {
-                fatalError("Need to solve pausing receive.")
-            }
-            if internalState.isEasyHandleAddedToMultiHandle && !newValue.isEasyHandleAddedToMultiHandle {
-                session.remove(handle: easyHandle)
-            }
-        }
-        didSet {    
-            if !oldValue.isEasyHandleAddedToMultiHandle && internalState.isEasyHandleAddedToMultiHandle {
-                session.add(handle: easyHandle)
-            }
-            if oldValue.isEasyHandlePaused && !internalState.isEasyHandlePaused {
-                fatalError("Need to solve pausing receive.")
-            }
-            if case .taskCompleted = internalState {
-                updateTaskState()
-                guard let s = session as? URLSession else { fatalError() }
-                s.workQueue.async {
-                    s.taskRegistry.remove(self)
-                }
-            }
-        }
-    }
     /// All operations must run on this queue.
-    fileprivate let workQueue: DispatchQueue 
+    internal let workQueue: DispatchQueue 
     /// Using dispatch semaphore to make public attributes thread safe.
     /// A semaphore is a simpler option against the usage of concurrent queue
     /// as the critical sections are very short.
-    fileprivate let semaphore = DispatchSemaphore(value: 1)
+    fileprivate let semaphore = DispatchSemaphore(value: 1)    
     
     public override init() {
         // Darwin Foundation oddly allows calling this initializer, even though
@@ -111,14 +75,28 @@
         _ = FileManager.default.createFile(atPath: fileName, contents: nil)
         self.tempFileURL = URL(fileURLWithPath: fileName)
         super.init()
-        self.easyHandle = _EasyHandle(delegate: self)
+        if session.configuration.protocolClasses != nil {
+            guard let protocolClasses = session.configuration.protocolClasses else { fatalError() }
+            if let urlProtocolClass = URLProtocol.getProtocolClass(protocols: protocolClasses, request: request) {
+                guard let urlProtocol = urlProtocolClass as? URLProtocol.Type else { fatalError() }
+                self._protocol = urlProtocol.init(task: self, cachedResponse: nil, client: nil)
+            } else {
+                guard let protocolClasses = URLProtocol.getProtocols() else { fatalError() }
+                if let urlProtocolClass = URLProtocol.getProtocolClass(protocols: protocolClasses, request: request) {
+                    guard let urlProtocol = urlProtocolClass as? URLProtocol.Type else { fatalError() }
+                    self._protocol = urlProtocol.init(task: self, cachedResponse: nil, client: nil)
+                }
+            }
+        } else {
+            guard let protocolClasses = URLProtocol.getProtocols() else { fatalError() }
+            if let urlProtocolClass = URLProtocol.getProtocolClass(protocols: protocolClasses, request: request) {
+                guard let urlProtocol = urlProtocolClass as? URLProtocol.Type else { fatalError() }
+                self._protocol = urlProtocol.init(task: self, cachedResponse: nil, client: nil)
+            }
+        }
     }
     deinit {
-        //TODO: Can we ensure this somewhere else? This might run on the wrong
-        // thread / queue.
-        //if internalState.isEasyHandleAddedToMultiHandle {
-        //    session.removeHandle(easyHandle)
-        //}
+        //TODO: Do we remove the EasyHandle from the session here? This might run on the wrong thread / queue.
     }
     
     open override func copy() -> Any {
@@ -136,7 +114,7 @@
     /*@NSCopying*/ open let originalRequest: URLRequest?
     
     /// May differ from originalRequest due to http server redirection
-    /*@NSCopying*/ open fileprivate(set) var currentRequest: URLRequest? {
+    /*@NSCopying*/ open internal(set) var currentRequest: URLRequest? {
         get {
             semaphore.wait()
             defer {
@@ -151,7 +129,7 @@
         }
     }
     fileprivate var _currentRequest: URLRequest? = nil
-    /*@NSCopying*/ open fileprivate(set) var response: URLResponse? {
+    /*@NSCopying*/ open internal(set) var response: URLResponse? {
         get {
             semaphore.wait()
             defer {
@@ -173,7 +151,7 @@
      */
     
     /// Number of body bytes already received
-   open fileprivate(set) var countOfBytesReceived: Int64 {
+    open fileprivate(set) var countOfBytesReceived: Int64 {
         get {
             semaphore.wait()
             defer {
@@ -204,6 +182,7 @@
             semaphore.signal()
         }
     }
+    
     fileprivate var _countOfBytesSent: Int64 = 0
     
     /// Number of body bytes we expect to send, derived from the Content-Length of the HTTP request */
@@ -222,7 +201,18 @@
      * cases, the task may signal other work before it acknowledges the
      * cancelation.  -cancel may be sent to a task that has been suspended.
      */
-    open func cancel() { NSUnimplemented() }
+    open func cancel() {
+        workQueue.sync {
+            guard self.state == .running || self.state == .suspended else { return }
+            self.state = .canceling
+            self.workQueue.async {
+                let urlError = URLError(_nsError: NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled, userInfo: nil))
+                self.error = urlError
+                self._protocol.stopLoading()
+                self._protocol.client?.urlProtocol(self._protocol, didFailWithError: urlError)
+            }
+        }
+    }
     
     /*
      * The current state of the task within the session.
@@ -247,7 +237,7 @@
      * The error, if any, delivered via -URLSession:task:didCompleteWithError:
      * This property will be nil in the event that no error occured.
      */
-    /*@NSCopying*/ open fileprivate(set) var error: Error?
+    /*@NSCopying*/ open internal(set) var error: Error?
     
     /// Suspend the task.
     ///
@@ -281,7 +271,7 @@
             
             if self.suspendCount == 1 {
                 self.workQueue.async {
-                    self.performSuspend()
+                    self._protocol.stopLoading()
                 }
             }
         }
@@ -296,7 +286,7 @@
             self.updateTaskState()
             if self.suspendCount == 0 {
                 self.workQueue.async {
-                    self.performResume()
+                    self._protocol.startLoading()
                 }
             }
         }
@@ -346,103 +336,12 @@
     }
 }
 
-fileprivate extension URLSessionTask {
-    /// The calls to `suspend` can be nested. This one is only called when the
-    /// task is not suspended and needs to go into suspended state.
-    func performSuspend() {
-        if case .transferInProgress(let transferState) = internalState {
-            internalState = .transferReady(transferState)
-        }
-    }
-    /// The calls to `resume` can be nested. This one is only called when the
-    /// task is suspended and needs to go out of suspended state.
-    func performResume() {
-        if case .initial = internalState {
-            guard let r = originalRequest else { fatalError("Task has no original request.") }
-            startNewTransfer(with: r)
-        }
-        if case .transferReady(let transferState) = internalState {
-            internalState = .transferInProgress(transferState)
-        }
-    }
-}
-
-internal extension URLSessionTask {
-    /// The is independent of the public `state: URLSessionTask.State`.
-    enum _InternalState {
-        /// Task has been created, but nothing has been done, yet
-        case initial
-        /// The easy handle has been fully configured. But it is not added to
-        /// the multi handle.
-        case transferReady(_TransferState)
-        /// The easy handle is currently added to the multi handle
-        case transferInProgress(_TransferState)
-        /// The transfer completed.
-        ///
-        /// The easy handle has been removed from the multi handle. This does
-        /// not (necessarily mean the task completed. A task that gets
-        /// redirected will do multiple transfers.
-        case transferCompleted(response: HTTPURLResponse, bodyDataDrain: _TransferState._DataDrain)
-        /// The transfer failed.
-        ///
-        /// Same as `.transferCompleted`, but without response / body data
-        case transferFailed
-        /// Waiting for the completion handler of the HTTP redirect callback.
-        ///
-        /// When we tell the delegate that we're about to perform an HTTP
-        /// redirect, we need to wait for the delegate to let us know what
-        /// action to take.
-        case waitingForRedirectCompletionHandler(response: HTTPURLResponse, bodyDataDrain: _TransferState._DataDrain)
-        /// Waiting for the completion handler of the 'did receive response' callback.
-        ///
-        /// When we tell the delegate that we received a response (i.e. when
-        /// we received a complete header), we need to wait for the delegate to
-        /// let us know what action to take. In this state the easy handle is
-        /// paused in order to suspend delegate callbacks.
-        case waitingForResponseCompletionHandler(_TransferState)
-        /// The task is completed
-        ///
-        /// Contrast this with `.transferCompleted`.
-        case taskCompleted
-    }
-}
-
-fileprivate extension URLSessionTask._InternalState {
-    var isEasyHandleAddedToMultiHandle: Bool {
-        switch self {
-        case .initial:                             return false
-        case .transferReady:                       return false
-        case .transferInProgress:                  return true
-        case .transferCompleted:                   return false
-        case .transferFailed:                      return false
-        case .waitingForRedirectCompletionHandler: return false
-        case .waitingForResponseCompletionHandler: return true
-        case .taskCompleted:                       return false
-        }
-    }
-    var isEasyHandlePaused: Bool {
-        switch self {
-        case .initial:                             return false
-        case .transferReady:                       return false
-        case .transferInProgress:                  return false
-        case .transferCompleted:                   return false
-        case .transferFailed:                      return false
-        case .waitingForRedirectCompletionHandler: return false
-        case .waitingForResponseCompletionHandler: return true
-        case .taskCompleted:                       return false
-        }
-    }
-}
-
 internal extension URLSessionTask {
     /// Updates the (public) state based on private / internal state.
     ///
     /// - Note: This must be called on the `workQueue`.
-    fileprivate func updateTaskState() {
+    internal func updateTaskState() {
         func calculateState() -> URLSessionTask.State {
-            if case .taskCompleted = internalState {
-                return .completed
-            }
             if suspendCount == 0 {
                 return .running
             } else {
@@ -462,7 +361,7 @@
         case stream(InputStream)
     }
 }
-fileprivate extension URLSessionTask._Body {
+internal extension URLSessionTask._Body {
     enum _Error : Error {
         case fileForBodyDataNotFound
     }
@@ -485,252 +384,6 @@
     }
 }
 
-/// Easy handle related
-fileprivate extension URLSessionTask {
-    /// Start a new transfer
-    func startNewTransfer(with request: URLRequest) {
-        currentRequest = request
-        guard let url = request.url else { fatalError("No URL in request.") }
-        internalState = .transferReady(createTransferState(url: url))
-        configureEasyHandle(for: request)
-        if suspendCount < 1 {
-            performResume()
-        }
-    }
-    /// Creates a new transfer state with the given behaviour:
-    func createTransferState(url: URL) -> URLSessionTask._TransferState {
-        let drain = createTransferBodyDataDrain()
-        switch body {
-        case .none:
-            return URLSessionTask._TransferState(url: url, bodyDataDrain: drain)
-        case .data(let data):
-            let source = _HTTPBodyDataSource(data: data)
-            return URLSessionTask._TransferState(url: url, bodyDataDrain: drain, bodySource: source)
-        case .file(let fileURL):
-            let source = _HTTPBodyFileSource(fileURL: fileURL, workQueue: workQueue, dataAvailableHandler: { [weak self] in
-                // Unpause the easy handle
-                self?.easyHandle.unpauseSend()
-                })
-            return URLSessionTask._TransferState(url: url, bodyDataDrain: drain, bodySource: source)
-        case .stream:
-            NSUnimplemented()
-        }
-        
-    }
-    /// The data drain.
-    ///
-    /// This depends on what the delegate / completion handler need.
-    fileprivate func createTransferBodyDataDrain() -> URLSessionTask._TransferState._DataDrain {
-        switch session.behaviour(for: self) {
-        case .noDelegate:
-            return .ignore
-        case .taskDelegate:
-            // Data will be forwarded to the delegate as we receive it, we don't
-            // need to do anything about it.
-            return .ignore
-        case .dataCompletionHandler:
-            // Data needs to be concatenated in-memory such that we can pass it
-            // to the completion handler upon completion.
-            return .inMemory(nil)
-        case .downloadCompletionHandler:
-            // Data needs to be written to a file (i.e. a download task).
-            let fileHandle = try! FileHandle(forWritingTo: tempFileURL)
-            return .toFile(tempFileURL, fileHandle) 
-        }
-    }
-    /// Set options on the easy handle to match the given request.
-    ///
-    /// This performs a series of `curl_easy_setopt()` calls.
-    fileprivate func configureEasyHandle(for request: URLRequest) {
-        // At this point we will call the equivalent of curl_easy_setopt()
-        // to configure everything on the handle. Since we might be re-using
-        // a handle, we must be sure to set everything and not rely on defaul
-        // values.
-        
-        //TODO: We could add a strong reference from the easy handle back to
-        // its URLSessionTask by means of CURLOPT_PRIVATE -- that would ensure
-        // that the task is always around while the handle is running.
-        // We would have to break that retain cycle once the handle completes
-        // its transfer.
-        
-        // Behavior Options
-        easyHandle.set(verboseModeOn: enableLibcurlDebugOutput)
-        easyHandle.set(debugOutputOn: enableLibcurlDebugOutput, task: self)
-        easyHandle.set(passHeadersToDataStream: false)
-        easyHandle.set(progressMeterOff: true)
-        easyHandle.set(skipAllSignalHandling: true)
-        
-        // Error Options:
-        easyHandle.set(errorBuffer: nil)
-        easyHandle.set(failOnHTTPErrorCode: false)
-        
-        // Network Options:
-        guard let url = request.url else { fatalError("No URL in request.") }
-        easyHandle.set(url: url)
-        easyHandle.setAllowedProtocolsToHTTPAndHTTPS()
-        easyHandle.set(preferredReceiveBufferSize: Int.max)
-        do {
-            switch (body, try body.getBodyLength()) {
-            case (.none, _):
-                set(requestBodyLength: .noBody)
-            case (_, .some(let length)):
-                set(requestBodyLength: .length(length))
-            case (_, .none):
-                set(requestBodyLength: .unknown)
-            }
-        } catch let e {
-            // Fail the request here.
-            // TODO: We have multiple options:
-            //     NSURLErrorNoPermissionsToReadFile
-            //     NSURLErrorFileDoesNotExist
-            internalState = .transferFailed
-            failWith(errorCode: errorCode(fileSystemError: e), request: request)
-            return
-        }
-        
-        // HTTP Options:
-        easyHandle.set(followLocation: false)
-        
-        // The httpAdditionalHeaders from session configuration has to be added to the request.
-        // The request.allHTTPHeaders can override the httpAdditionalHeaders elements. Add the
-        // httpAdditionalHeaders from session configuration first and then append/update the
-        // request.allHTTPHeaders so that request.allHTTPHeaders can override httpAdditionalHeaders.
-        
-        let httpSession = session as! URLSession
-        var httpHeaders: [AnyHashable : Any]?
-        
-        if let hh = httpSession.configuration.httpAdditionalHeaders {
-            httpHeaders = hh
-        }
-        
-        if let hh = currentRequest?.allHTTPHeaderFields {
-            if httpHeaders == nil {
-                httpHeaders = hh
-            } else {
-                hh.forEach {
-                    httpHeaders![$0] = $1
-                }
-            }
-        }
-
-        let customHeaders: [String]
-        let headersForRequest = curlHeaders(for: httpHeaders)
-        if ((request.httpMethod == "POST") && (request.value(forHTTPHeaderField: "Content-Type") == nil)) {
-            customHeaders = headersForRequest + ["Content-Type:application/x-www-form-urlencoded"]
-        } else {
-            customHeaders = headersForRequest
-        }
-
-        easyHandle.set(customHeaders: customHeaders)
-
-        //Options unavailable on Ubuntu 14.04 (libcurl 7.36)
-        //TODO: Introduce something like an #if
-        //easyHandle.set(waitForPipeliningAndMultiplexing: true)
-        //easyHandle.set(streamWeight: priority)
-
-        //set the request timeout
-        //TODO: the timeout value needs to be reset on every data transfer
-        var timeoutInterval = Int(httpSession.configuration.timeoutIntervalForRequest) * 1000
-        if request.isTimeoutIntervalSet {
-           timeoutInterval = Int(request.timeoutInterval) * 1000
-        }
-        let timeoutHandler = DispatchWorkItem { [weak self] in
-            guard let currentTask = self else { fatalError("Timeout on a task that doesn't exist") } //this guard must always pass
-            currentTask.internalState = .transferFailed
-            let urlError = URLError(_nsError: NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut, userInfo: nil))
-            currentTask.completeTask(withError: urlError)
-        }
-        easyHandle.timeoutTimer = _TimeoutSource(queue: workQueue, milliseconds: timeoutInterval, handler: timeoutHandler)
-
-        easyHandle.set(automaticBodyDecompression: true)
-        easyHandle.set(requestMethod: request.httpMethod ?? "GET")
-        if request.httpMethod == "HEAD" {
-            easyHandle.set(noBody: true)
-        }
-    }
-}
-
-fileprivate extension URLSessionTask {
-    /// These are a list of headers that should be passed to libcurl.
-    ///
-    /// Headers will be returned as `Accept: text/html` strings for
-    /// setting fields, `Accept:` for disabling the libcurl default header, or
-    /// `Accept;` for a header with no content. This is the format that libcurl
-    /// expects.
-    ///
-    /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
-    func curlHeaders(for httpHeaders: [AnyHashable : Any]?) -> [String] {
-        var result: [String] = []
-        var names = Set<String>()
-        if httpHeaders != nil {
-            let hh = httpHeaders as! [String:String]
-            hh.forEach {
-                let name = $0.0.lowercased()
-                guard !names.contains(name) else { return }
-                names.insert(name)
-                
-                if $0.1.isEmpty {
-                    result.append($0.0 + ";")
-                } else {
-                    result.append($0.0 + ": " + $0.1)
-                }
-            }
-        }
-        curlHeadersToSet.forEach {
-            let name = $0.0.lowercased()
-            guard !names.contains(name) else { return }
-            names.insert(name)
-            
-            if $0.1.isEmpty {
-                result.append($0.0 + ";")
-            } else {
-                result.append($0.0 + ": " + $0.1)
-            }
-        }
-        curlHeadersToRemove.forEach {
-            let name = $0.lowercased()
-            guard !names.contains(name) else { return }
-            names.insert(name)
-            result.append($0 + ":")
-        }
-        return result
-    }
-    /// Any header values that should be passed to libcurl
-    ///
-    /// These will only be set if not already part of the request.
-    /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
-    var curlHeadersToSet: [(String,String)] {
-        var result = [("Connection", "keep-alive"),
-                      ("User-Agent", userAgentString),
-                      ]
-        if let language = NSLocale.current.languageCode {
-            result.append(("Accept-Language", language))
-        }
-        return result
-    }
-    /// Any header values that should be removed from the ones set by libcurl
-    /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
-    var curlHeadersToRemove: [String] {
-        if case .none = body {
-            return []
-        } else {
-            return ["Expect"]
-        }
-    }
-}
-
-fileprivate var userAgentString: String = {
-    // Darwin uses something like this: "xctest (unknown version) CFNetwork/760.4.2 Darwin/15.4.0 (x86_64)"
-    let info = ProcessInfo.processInfo
-    let name = info.processName
-    let curlVersion = CFURLSessionCurlVersionInfo()
-    //TODO: Should probably use sysctl(3) to get these:
-    // kern.ostype: Darwin
-    // kern.osrelease: 15.4.0
-    //TODO: Use NSBundle to get the version number?
-    return "\(name) (unknown version) curl/\(curlVersion.major).\(curlVersion.minor).\(curlVersion.patch)"
-}()
-
 fileprivate func errorCode(fileSystemError error: Error) -> Int {
     func fromCocoaErrorCode(_ code: Int) -> Int {
         switch code {
@@ -750,454 +403,15 @@
     }
 }
 
-fileprivate extension URLSessionTask {
-    /// Set request body length.
-    ///
-    /// An unknown length
-    func set(requestBodyLength length: URLSessionTask._RequestBodyLength) {
-        switch length {
-        case .noBody:
-            easyHandle.set(upload: false)
-            easyHandle.set(requestBodyLength: 0)
-        case .length(let length):
-            easyHandle.set(upload: true)
-            easyHandle.set(requestBodyLength: Int64(length))
-        case .unknown:
-            easyHandle.set(upload: true)
-            easyHandle.set(requestBodyLength: -1)
-        }
-    }
-    enum _RequestBodyLength {
-        case noBody
-        ///
-        case length(UInt64)
-        /// Will result in a chunked upload
-        case unknown
-    }
-}
-
-extension URLSessionTask: _EasyHandleDelegate {
-    func didReceive(data: Data) -> _EasyHandle._Action {
-        guard case .transferInProgress(let ts) = internalState else { fatalError("Received body data, but no transfer in progress.") }
-        guard ts.isHeaderComplete else { fatalError("Received body data, but the header is not complete, yet.") }
-        notifyDelegate(aboutReceivedData: data)
-        internalState = .transferInProgress(ts.byAppending(bodyData: data))
-        return .proceed
-    }
-
-    fileprivate func notifyDelegate(aboutReceivedData data: Data) {
-        if case .taskDelegate(let delegate) = session.behaviour(for: self),
-            let dataDelegate = delegate as? URLSessionDataDelegate,
-            let task = self as? URLSessionDataTask {
-            // Forward to the delegate:
-            guard let s = session as? URLSession else { fatalError() }
-            s.delegateQueue.addOperation {
-                dataDelegate.urlSession(s, dataTask: task, didReceive: data)
-            }
-        } else if case .taskDelegate(let delegate) = session.behaviour(for: self),
-            let downloadDelegate = delegate as? URLSessionDownloadDelegate,
-            let task = self as? URLSessionDownloadTask {
-                guard let s = session as? URLSession else { fatalError() }
-                let fileHandle = try! FileHandle(forWritingTo: tempFileURL)
-                _ = fileHandle.seekToEndOfFile()
-                fileHandle.write(data)
-                self.totalDownloaded += data.count
-            
-                s.delegateQueue.addOperation {
-                    downloadDelegate.urlSession(s, downloadTask: task, didWriteData: Int64(data.count), totalBytesWritten: Int64(self.totalDownloaded),
-                        totalBytesExpectedToWrite: Int64(self.easyHandle.fileLength))
-                }
-                if Int(self.easyHandle.fileLength) == totalDownloaded {
-                    fileHandle.closeFile()
-                    s.delegateQueue.addOperation {
-                        downloadDelegate.urlSession(s, downloadTask: task, didFinishDownloadingTo: self.tempFileURL)
-                    }
-                }
-            
-        }
-    }
-
-    func didReceive(headerData data: Data) -> _EasyHandle._Action {
-        guard case .transferInProgress(let ts) = internalState else { fatalError("Received body data, but no transfer in progress.") }
-        do {
-            let newTS = try ts.byAppending(headerLine: data)
-            internalState = .transferInProgress(newTS)
-            let didCompleteHeader = !ts.isHeaderComplete && newTS.isHeaderComplete
-            if didCompleteHeader {
-                // The header is now complete, but wasn't before.
-                didReceiveResponse()
-            }
-            return .proceed
-        } catch {
-            return .abort
-        }
-    }
-
-    func fill(writeBuffer buffer: UnsafeMutableBufferPointer<Int8>) -> _EasyHandle._WriteBufferResult {
-        guard case .transferInProgress(let ts) = internalState else { fatalError("Requested to fill write buffer, but transfer isn't in progress.") }
-        guard let source = ts.requestBodySource else { fatalError("Requested to fill write buffer, but transfer state has no body source.") }
-        switch source.getNextChunk(withLength: buffer.count) {
-        case .data(let data):
-            copyDispatchData(data, infoBuffer: buffer)
-            let count = data.count
-            assert(count > 0)
-            return .bytes(count)
-        case .done:
-            return .bytes(0)
-        case .retryLater:
-            // At this point we'll try to pause the easy handle. The body source
-            // is responsible for un-pausing the handle once data becomes
-            // available.
-            return .pause
-        case .error:
-            return .abort
-        }
-    }
-
-    func transferCompleted(withErrorCode errorCode: Int?) {
-        // At this point the transfer is complete and we can decide what to do.
-        // If everything went well, we will simply forward the resulting data
-        // to the delegate. But in case of redirects etc. we might send another
-        // request.
-        guard case .transferInProgress(let ts) = internalState else { fatalError("Transfer completed, but it wasn't in progress.") }
-        guard let request = currentRequest else { fatalError("Transfer completed, but there's no current request.") }
-        guard errorCode == nil else {
-            internalState = .transferFailed
-            failWith(errorCode: errorCode!, request: request)
-            return
-        }
-        
-        guard let response = ts.response else { fatalError("Transfer completed, but there's no response.") }
-        internalState = .transferCompleted(response: response, bodyDataDrain: ts.bodyDataDrain)
-        
-        let action = completionAction(forCompletedRequest: request, response: response)
-        switch action {
-        case .completeTask:
-            completeTask()
-        case .failWithError(let errorCode):
-            internalState = .transferFailed
-            failWith(errorCode: errorCode, request: request)
-        case .redirectWithRequest(let newRequest):
-            redirectFor(request: newRequest)
-        }
-    }
-    func seekInputStream(to position: UInt64) throws {
-        // We will reset the body sourse and seek forward.
-        NSUnimplemented()
-    }
-    func updateProgressMeter(with propgress: _EasyHandle._Progress) {
-        //TODO: Update progress. Note that a single URLSessionTask might
-        // perform multiple transfers. The values in `progress` are only for
-        // the current transfer.
-    }
-}
-
-/// State Transfers
-extension URLSessionTask {
-    func completeTask() {
-        guard case .transferCompleted(response: let response, bodyDataDrain: let bodyDataDrain) = internalState else {
-            fatalError("Trying to complete the task, but its transfer isn't complete.")
-        }
-        self.response = response
-
-        //We don't want a timeout to be triggered after this. The timeout timer needs to be cancelled.
-        easyHandle.timeoutTimer = nil
-
-        //because we deregister the task with the session on internalState being set to taskCompleted
-        //we need to do the latter after the delegate/handler was notified/invoked
-        switch session.behaviour(for: self) {
-        case .taskDelegate(let delegate):
-            guard let s = session as? URLSession else { fatalError() }
-            s.delegateQueue.addOperation {
-                delegate.urlSession(s, task: self, didCompleteWithError: nil)
-                self.internalState = .taskCompleted
-            }
-        case .noDelegate:
-            internalState = .taskCompleted
-        case .dataCompletionHandler(let completion):
-            guard case .inMemory(let bodyData) = bodyDataDrain else {
-                fatalError("Task has data completion handler, but data drain is not in-memory.")
-            }
-
-            guard let s = session as? URLSession else { fatalError() }
-
-            var data = Data()
-            if let body = bodyData {
-                data = Data(bytes: body.bytes, count: body.length)
-            }
-
-            s.delegateQueue.addOperation {
-                completion(data, response, nil)
-                self.internalState = .taskCompleted
-                self.session = nil
-            }
-        case .downloadCompletionHandler(let completion):
-            guard case .toFile(let url, let fileHandle?) = bodyDataDrain else {
-                fatalError("Task has data completion handler, but data drain is not a file handle.")
-            }
-
-            guard let s = session as? URLSession else { fatalError() }
-            //The contents are already written, just close the file handle and call the handler
-            fileHandle.closeFile()
-            
-            s.delegateQueue.addOperation {
-                completion(url, response, nil) 
-                self.internalState = .taskCompleted
-                self.session = nil
-            }
-            
-        }
-    }
-    func completeTask(withError error: Error) {
-        self.error = error
-        
-        guard case .transferFailed = internalState else {
-            fatalError("Trying to complete the task, but its transfer isn't complete / failed.")
-        }
-
-        //We don't want a timeout to be triggered after this. The timeout timer needs to be cancelled.
-        easyHandle.timeoutTimer = nil
-
-        switch session.behaviour(for: self) {
-        case .taskDelegate(let delegate):
-            guard let s = session as? URLSession else { fatalError() }
-            s.delegateQueue.addOperation {
-                delegate.urlSession(s, task: self, didCompleteWithError: error as Error)
-                self.internalState = .taskCompleted
-            }
-        case .noDelegate:
-            internalState = .taskCompleted
-        case .dataCompletionHandler(let completion):
-            guard let s = session as? URLSession else { fatalError() }
-            s.delegateQueue.addOperation {
-                completion(nil, nil, error)
-                self.internalState = .taskCompleted
-            }
-        case .downloadCompletionHandler(let completion): 
-            guard let s = session as? URLSession else { fatalError() }
-            s.delegateQueue.addOperation {
-                completion(nil, nil, error)
-                self.internalState = .taskCompleted
-            }
-        }
-    }
-    func failWith(errorCode: Int, request: URLRequest) {
-        //TODO: Error handling
-        let userInfo: [String : Any]? = request.url.map {
-            [
-                NSURLErrorFailingURLErrorKey: $0,
-                NSURLErrorFailingURLStringErrorKey: $0.absoluteString,
-                ]
-        }
-        let error = URLError(_nsError: NSError(domain: NSURLErrorDomain, code: errorCode, userInfo: userInfo))
-        completeTask(withError: error)
-    }
-    func redirectFor(request: URLRequest) {
-        //TODO: Should keep track of the number of redirects that this
-        // request has gone through and err out once it's too large, i.e.
-        // call into `failWith(errorCode: )` with NSURLErrorHTTPTooManyRedirects
-        guard case .transferCompleted(response: let response, bodyDataDrain: let bodyDataDrain) = internalState else {
-            fatalError("Trying to redirect, but the transfer is not complete.")
-        }
-        
-        switch session.behaviour(for: self) {
-        case .taskDelegate(let delegate):
-            // At this point we need to change the internal state to note
-            // that we're waiting for the delegate to call the completion
-            // handler. Then we'll call the delegate callback
-            // (willPerformHTTPRedirection). The task will then switch out of
-            // its internal state once the delegate calls the completion
-            // handler.
-            
-            //TODO: Should the `public response: URLResponse` property be updated
-            // before we call delegate API
-            // `func urlSession(session: session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: NSHTTPURLResponse, newRequest request: NSURLRequest, completionHandler: (NSURLRequest?) -> Void)`
-            // ?
-            
-            internalState = .waitingForRedirectCompletionHandler(response: response, bodyDataDrain: bodyDataDrain)
-            // We need this ugly cast in order to be able to support `URLSessionTask.init()`
-            guard let s = session as? URLSession else { fatalError() }
-            s.delegateQueue.addOperation {
-                delegate.urlSession(s, task: self, willPerformHTTPRedirection: response, newRequest: request) { [weak self] (request: URLRequest?) in
-                    guard let task = self else { return }
-                    task.workQueue.async {
-                        task.didCompleteRedirectCallback(request)
-                    }
-                }
-            }
-        case .noDelegate, .dataCompletionHandler, .downloadCompletionHandler:
-            // Follow the redirect.
-            startNewTransfer(with: request)
-        }
-    }
-    fileprivate func didCompleteRedirectCallback(_ request: URLRequest?) {
-        guard case .waitingForRedirectCompletionHandler(response: let response, bodyDataDrain: let bodyDataDrain) = internalState else {
-            fatalError("Received callback for HTTP redirection, but we're not waiting for it. Was it called multiple times?")
-        }
-        // If the request is `nil`, we're supposed to treat the current response
-        // as the final response, i.e. not do any redirection.
-        // Otherwise, we'll start a new transfer with the passed in request.
-        if let r = request {
-            startNewTransfer(with: r)
-        } else {
-            internalState = .transferCompleted(response: response, bodyDataDrain: bodyDataDrain)
-            completeTask()
-        }
-    }
-}
-
-
-/// Response processing
-fileprivate extension URLSessionTask {
-    /// Whenever we receive a response (i.e. a complete header) from libcurl,
-    /// this method gets called.
-    func didReceiveResponse() {
-        guard let dt = self as? URLSessionDataTask else { return }
-        guard case .transferInProgress(let ts) = internalState else { fatalError("Transfer not in progress.") }
-        guard let response = ts.response else { fatalError("Header complete, but not URL response.") }
-        switch session.behaviour(for: self) {
-        case .noDelegate:
-            break
-        case .taskDelegate(let delegate as URLSessionDataDelegate):
-            //TODO: There's a problem with libcurl / with how we're using it.
-            // We're currently unable to pause the transfer / the easy handle:
-            // https://curl.haxx.se/mail/lib-2016-03/0222.html
-            //
-            // For now, we'll notify the delegate, but won't pause the transfer,
-            // and we'll disregard the completion handler:
-            guard let s = session as? URLSession else { fatalError() }
-            s.delegateQueue.addOperation {
-                delegate.urlSession(s, dataTask: dt, didReceive: response, completionHandler: { _ in
-                    URLSession.printDebug("warning: Ignoring disposition from completion handler.")
-                })
-            }
-        case .taskDelegate:
-            break
-        case .dataCompletionHandler:
-            break
-        case .downloadCompletionHandler:
-            break
-        }
-    }
-    /// Give the delegate a chance to tell us how to proceed once we have a
-    /// response / complete header.
-    ///
-    /// This will pause the transfer.
-    func askDelegateHowToProceedAfterCompleteResponse(_ response: HTTPURLResponse, delegate: URLSessionDataDelegate) {
-        // Ask the delegate how to proceed.
-        
-        // This will pause the easy handle. We need to wait for the
-        // delegate before processing any more data.
-        guard case .transferInProgress(let ts) = internalState else { fatalError("Transfer not in progress.") }
-        internalState = .waitingForResponseCompletionHandler(ts)
-        
-        let dt = self as! URLSessionDataTask
-        
-        // We need this ugly cast in order to be able to support `URLSessionTask.init()`
-        guard let s = session as? URLSession else { fatalError() }
-        s.delegateQueue.addOperation {
-            delegate.urlSession(s, dataTask: dt, didReceive: response, completionHandler: { [weak self] disposition in
-                guard let task = self else { return }
-                task.workQueue.async {
-                    task.didCompleteResponseCallback(disposition: disposition)
-                }
-                })
-        }
-    }
-    /// This gets called (indirectly) when the data task delegates lets us know
-    /// how we should proceed after receiving a response (i.e. complete header).
-    func didCompleteResponseCallback(disposition: URLSession.ResponseDisposition) {
-        guard case .waitingForResponseCompletionHandler(let ts) = internalState else { fatalError("Received response disposition, but we're not waiting for it.") }
-        switch disposition {
-        case .cancel:
-            let error = URLError(_nsError: NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled))
-            self.completeTask(withError: error)
-        case .allow:
-            // Continue the transfer. This will unpause the easy handle.
-            internalState = .transferInProgress(ts)
-        case .becomeDownload:
-            /* Turn this request into a download */
-            NSUnimplemented()
-        case .becomeStream:
-            /* Turn this task into a stream task */
-            NSUnimplemented()
-        }
-    }
-    
-    /// Action to be taken after a transfer completes
-    enum _CompletionAction {
-        case completeTask
-        case failWithError(Int)
-        case redirectWithRequest(URLRequest)
-    }
-    
-    /// What action to take
-    func completionAction(forCompletedRequest request: URLRequest, response: HTTPURLResponse) -> _CompletionAction {
-        // Redirect:
-        if let request = redirectRequest(for: response, fromRequest: request) {
-            return .redirectWithRequest(request)
-        }
-        return .completeTask
-    }
-    /// If the response is a redirect, return the new request
-    ///
-    /// RFC 7231 section 6.4 defines redirection behavior for HTTP/1.1
-    ///
-    /// - SeeAlso: <https://tools.ietf.org/html/rfc7231#section-6.4>
-    func redirectRequest(for response: HTTPURLResponse, fromRequest: URLRequest) -> URLRequest? {
-        //TODO: Do we ever want to redirect for HEAD requests?
-        func methodAndURL() -> (String, URL)? {
-            guard
-                let location = response.value(forHeaderField: .location),
-                let targetURL = URL(string: location)
-                else {
-                    // Can't redirect when there's no location to redirect to.
-                    return nil
-            }
-            
-            // Check for a redirect:
-            switch response.statusCode {
-            //TODO: Should we do this for 300 "Multiple Choices", too?
-            case 301, 302, 303:
-                // Change into "GET":
-                return ("GET", targetURL)
-            case 307:
-                // Re-use existing method:
-                return (fromRequest.httpMethod ?? "GET", targetURL)
-            default:
-                return nil
-            }
-        }
-        guard let (method, targetURL) = methodAndURL() else { return nil }
-        var request = fromRequest
-        request.httpMethod = method
-        request.url = targetURL
-        return request
-    }
-}
-
-
-fileprivate extension HTTPURLResponse {
-    /// Type safe HTTP header field name(s)
-    enum _Field: String {
-        /// `Location`
-        /// - SeeAlso: RFC 2616 section 14.30 <https://tools.ietf.org/html/rfc2616#section-14.30>
-        case location = "Location"
-    }
-    func value(forHeaderField field: _Field) -> String? {
-        return field.rawValue
-    }
-}
-
 public extension URLSessionTask {
-    /// The default URL session task priority, used implicitly for any task you 
+    /// The default URL session task priority, used implicitly for any task you
     /// have not prioritized. The floating point value of this constant is 0.5.
     public static let defaultPriority: Float = 0.5
-
-    /// A low URL session task priority, with a floating point value above the 
+    
+    /// A low URL session task priority, with a floating point value above the
     /// minimum of 0 and below the default value.
     public static let lowPriority: Float = 0.25
-
+    
     /// A high URL session task priority, with a floating point value above the
     /// default value and below the maximum of 1.0.
     public static let highPriority: Float = 0.75
@@ -1314,19 +528,4 @@
 }
 
 /* Key in the userInfo dictionary of an NSError received during a failed download. */
-public let URLSessionDownloadTaskResumeData: String = "" // NSUnimplemented
-
-
-extension URLSession {
-    static func printDebug(_ text: @autoclosure () -> String) {
-        guard enableDebugOutput else { return }
-        debugPrint(text())
-    }
-}
-
-fileprivate let enableLibcurlDebugOutput: Bool = {
-    return (ProcessInfo.processInfo.environment["URLSessionDebugLibcurl"] != nil)
-}()
-fileprivate let enableDebugOutput: Bool = {
-    return (ProcessInfo.processInfo.environment["URLSessionDebug"] != nil)
-}()
+public let URLSessionDownloadTaskResumeData: String = "NSURLSessionDownloadTaskResumeData"
diff --git a/Foundation/NSURLSession/EasyHandle.swift b/Foundation/NSURLSession/http/EasyHandle.swift
similarity index 100%
rename from Foundation/NSURLSession/EasyHandle.swift
rename to Foundation/NSURLSession/http/EasyHandle.swift
diff --git a/Foundation/NSURLSession/HTTPBodySource.swift b/Foundation/NSURLSession/http/HTTPBodySource.swift
similarity index 100%
rename from Foundation/NSURLSession/HTTPBodySource.swift
rename to Foundation/NSURLSession/http/HTTPBodySource.swift
diff --git a/Foundation/NSURLSession/HTTPMessage.swift b/Foundation/NSURLSession/http/HTTPMessage.swift
similarity index 83%
rename from Foundation/NSURLSession/HTTPMessage.swift
rename to Foundation/NSURLSession/http/HTTPMessage.swift
index 6598d06..91be843 100644
--- a/Foundation/NSURLSession/HTTPMessage.swift
+++ b/Foundation/NSURLSession/http/HTTPMessage.swift
@@ -20,7 +20,7 @@
 import CoreFoundation
 
 
-extension URLSessionTask {
+extension _HTTPURLProtocol {
     /// An HTTP header being parsed.
     ///
     /// It can either be complete (i.e. the final CR LF CR LF has been
@@ -46,14 +46,14 @@
     }
 }
 
-extension URLSessionTask._ParsedResponseHeader {
+extension _HTTPURLProtocol._ParsedResponseHeader {
     /// Parse a header line passed by libcurl.
     ///
     /// These contain the <CRLF> ending and the final line contains nothing but
     /// that ending.
     /// - Returns: Returning nil indicates failure. Otherwise returns a new
     ///     `ParsedResponseHeader` with the given line added.
-    func byAppending(headerLine data: Data) -> URLSessionTask._ParsedResponseHeader? {
+    func byAppending(headerLine data: Data) -> _HTTPURLProtocol._ParsedResponseHeader? {
         // The buffer must end in CRLF
         guard
             2 <= data.count &&
@@ -70,33 +70,33 @@
     /// is a complete header. Otherwise it's a partial header.
     /// - Note: Appending a line to a complete header results in a partial
     ///     header with just that line.
-    private func byAppending(headerLine line: String) -> URLSessionTask._ParsedResponseHeader {
+    private func byAppending(headerLine line: String) -> _HTTPURLProtocol._ParsedResponseHeader {
         if line.isEmpty {
             switch self {
             case .partial(let header): return .complete(header)
-            case .complete: return .partial(URLSessionTask._ResponseHeaderLines())
+            case .complete: return .partial(_HTTPURLProtocol._ResponseHeaderLines())
             }
         } else {
             let header = partialResponseHeader
             return .partial(header.byAppending(headerLine: line))
         }
     }
-    private var partialResponseHeader: URLSessionTask._ResponseHeaderLines {
+    private var partialResponseHeader: _HTTPURLProtocol._ResponseHeaderLines {
         switch self {
         case .partial(let header): return header
-        case .complete: return URLSessionTask._ResponseHeaderLines()
+        case .complete: return _HTTPURLProtocol._ResponseHeaderLines()
         }
     }
 }
-private extension URLSessionTask._ResponseHeaderLines {
+private extension _HTTPURLProtocol._ResponseHeaderLines {
     /// Returns a copy of the lines with the new line appended to it.
-    func byAppending(headerLine line: String) -> URLSessionTask._ResponseHeaderLines {
+    func byAppending(headerLine line: String) -> _HTTPURLProtocol._ResponseHeaderLines {
         var l = self.lines
         l.append(line)
-        return URLSessionTask._ResponseHeaderLines(headerLines: l)
+        return _HTTPURLProtocol._ResponseHeaderLines(headerLines: l)
     }
 }
-internal extension URLSessionTask._ResponseHeaderLines {
+internal extension _HTTPURLProtocol._ResponseHeaderLines {
     /// Create an `NSHTTPRULResponse` from the lines.
     ///
     /// This will parse the header lines.
@@ -105,17 +105,17 @@
         guard let message = createHTTPMessage() else { return nil }
         return HTTPURLResponse(message: message, URL: URL)
     }
-    /// Parse the lines into a `URLSessionTask.HTTPMessage`.
-    func createHTTPMessage() -> URLSessionTask._HTTPMessage? {
+    /// Parse the lines into a `_HTTPURLProtocol.HTTPMessage`.
+    func createHTTPMessage() -> _HTTPURLProtocol._HTTPMessage? {
         guard let (head, tail) = lines.decompose else { return nil }
-        guard let startline = URLSessionTask._HTTPMessage._StartLine(line: head) else { return nil }
+        guard let startline = _HTTPURLProtocol._HTTPMessage._StartLine(line: head) else { return nil }
         guard let headers = createHeaders(from: tail) else { return nil }
-        return URLSessionTask._HTTPMessage(startLine: startline, headers: headers)
+        return _HTTPURLProtocol._HTTPMessage(startLine: startline, headers: headers)
     }
 }
 
 extension HTTPURLResponse {
-    fileprivate convenience init?(message: URLSessionTask._HTTPMessage, URL: URL) {
+    fileprivate convenience init?(message: _HTTPURLProtocol._HTTPMessage, URL: URL) {
         /// This needs to be a request, i.e. it needs to have a status line.
         guard case .statusLine(let statusLine) = message.startLine else { return nil }
         let fields = message.headersAsDictionary
@@ -124,7 +124,7 @@
 }
 
 
-extension URLSessionTask {
+extension _HTTPURLProtocol {
     /// HTTP Message
     ///
     /// A message consist of a *start-line* optionally followed by one or multiple
@@ -134,12 +134,12 @@
     ///
     /// - SeeAlso: https://tools.ietf.org/html/rfc2616#section-4
     struct _HTTPMessage {
-        let startLine: URLSessionTask._HTTPMessage._StartLine
-        let headers: [URLSessionTask._HTTPMessage._Header]
+        let startLine: _HTTPURLProtocol._HTTPMessage._StartLine
+        let headers: [_HTTPURLProtocol._HTTPMessage._Header]
     }
 }
 
-extension URLSessionTask._HTTPMessage {
+extension _HTTPURLProtocol._HTTPMessage {
     var headersAsDictionary: [String: String] {
         var result: [String: String] = [:]
         headers.forEach {
@@ -153,7 +153,7 @@
         return result
     }
 }
-extension URLSessionTask._HTTPMessage {
+extension _HTTPURLProtocol._HTTPMessage {
     /// A single HTTP message header field
     ///
     /// Most HTTP messages have multiple header fields.
@@ -168,17 +168,17 @@
     enum _StartLine {
         /// RFC 2616 Section 5.1 *Request Line*
         /// - SeeAlso: https://tools.ietf.org/html/rfc2616#section-5.1
-        case requestLine(method: String, uri: URL, version: URLSessionTask._HTTPMessage._Version)
+        case requestLine(method: String, uri: URL, version: _HTTPURLProtocol._HTTPMessage._Version)
         /// RFC 2616 Section 6.1 *Status Line*
         /// - SeeAlso: https://tools.ietf.org/html/rfc2616#section-6.1
-        case statusLine(version: URLSessionTask._HTTPMessage._Version, status: Int, reason: String)
+        case statusLine(version: _HTTPURLProtocol._HTTPMessage._Version, status: Int, reason: String)
     }
     /// A HTTP version, e.g. "HTTP/1.1"
     struct _Version: RawRepresentable {
         let rawValue: String
     }
 }
-extension URLSessionTask._HTTPMessage._Version {
+extension _HTTPURLProtocol._HTTPMessage._Version {
     init?(versionString: String) {
         rawValue = versionString
     }
@@ -200,14 +200,14 @@
     static let Separators = NSCharacterSet(charactersIn: "()<>@,;:\\\"/[]?={} \t")
 }
 
-private extension URLSessionTask._HTTPMessage._StartLine {
+private extension _HTTPURLProtocol._HTTPMessage._StartLine {
     init?(line: String) {
         guard let r = line.splitRequestLine() else { return nil }
-        if let version = URLSessionTask._HTTPMessage._Version(versionString: r.0) {
+        if let version = _HTTPURLProtocol._HTTPMessage._Version(versionString: r.0) {
             // Status line:
             guard let status = Int(r.1), 100 <= status && status <= 999 else { return nil }
             self = .statusLine(version: version, status: status, reason: r.2)
-        } else if let version = URLSessionTask._HTTPMessage._Version(versionString: r.2),
+        } else if let version = _HTTPURLProtocol._HTTPMessage._Version(versionString: r.2),
             let URI = URL(string: r.1) {
             // The request method must be a token (i.e. without seperators):
             let seperatorIdx = r.0.unicodeScalars.index(where: { !$0.isValidMessageToken } )
@@ -247,19 +247,19 @@
 /// This respects the header folding as described by
 /// https://tools.ietf.org/html/rfc2616#section-2.2 :
 ///
-/// - SeeAlso: `URLSessionTask.HTTPMessage.Header.createOne(from:)`
-private func createHeaders(from lines: ArraySlice<String>) -> [URLSessionTask._HTTPMessage._Header]? {
+/// - SeeAlso: `_HTTPURLProtocol.HTTPMessage.Header.createOne(from:)`
+private func createHeaders(from lines: ArraySlice<String>) -> [_HTTPURLProtocol._HTTPMessage._Header]? {
 
     var headerLines = Array(lines)
-    var headers: [URLSessionTask._HTTPMessage._Header] = []
+    var headers: [_HTTPURLProtocol._HTTPMessage._Header] = []
     while !headerLines.isEmpty {
-        guard let (header, remaining) = URLSessionTask._HTTPMessage._Header.createOne(from: headerLines) else { return nil }
+        guard let (header, remaining) = _HTTPURLProtocol._HTTPMessage._Header.createOne(from: headerLines) else { return nil }
         headers.append(header)
         headerLines = remaining
     }
     return headers
 }
-private extension URLSessionTask._HTTPMessage._Header {
+private extension _HTTPURLProtocol._HTTPMessage._Header {
     /// Parse a single HTTP message header field
     ///
     /// Each header field consists
@@ -278,7 +278,7 @@
     /// If an error occurs, it returns `nil`.
     ///
     /// - SeeAlso: https://tools.ietf.org/html/rfc2616#section-4.2
-    static func createOne(from lines: [String]) -> (URLSessionTask._HTTPMessage._Header, [String])? {
+    static func createOne(from lines: [String]) -> (_HTTPURLProtocol._HTTPMessage._Header, [String])? {
         // HTTP/1.1 header field values can be folded onto multiple lines if the
         // continuation line begins with a space or horizontal tab. All linear
         // white space, including folding, has the same semantics as SP. A
@@ -309,7 +309,7 @@
                 let valuePart = String(v)
                 value = value.map { $0 + " " + valuePart } ?? valuePart
             }
-            return (URLSessionTask._HTTPMessage._Header(name: name, value: value ?? ""), Array(t))
+            return (_HTTPURLProtocol._HTTPMessage._Header(name: name, value: value ?? ""), Array(t))
         }
     }
 }
diff --git a/Foundation/NSURLSession/http/HTTPURLProtocol.swift b/Foundation/NSURLSession/http/HTTPURLProtocol.swift
new file mode 100644
index 0000000..8f31fa1
--- /dev/null
+++ b/Foundation/NSURLSession/http/HTTPURLProtocol.swift
@@ -0,0 +1,892 @@
+// 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
+//
+
+import CoreFoundation
+import Dispatch
+
+internal class _HTTPURLProtocol: URLProtocol {
+
+    fileprivate var easyHandle: _EasyHandle!
+
+    public override required init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
+        self.internalState = _InternalState.initial
+        super.init(request: task.originalRequest!, cachedResponse: cachedResponse, client: client)
+        self.task = task
+        self.easyHandle = _EasyHandle(delegate: self)
+    }
+
+    public override required init(request: URLRequest, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) {
+        self.internalState = _InternalState.initial
+        super.init(request: request, cachedResponse: cachedResponse, client: client)
+        self.easyHandle = _EasyHandle(delegate: self)
+    }
+
+    override class func canInit(with request: URLRequest) -> Bool {
+        guard request.url?.scheme == "http" || request.url?.scheme == "https" else { return false }
+        return true
+    }
+
+    override class func canonicalRequest(for request: URLRequest) -> URLRequest {
+        return request
+    }
+
+    override func startLoading() {
+        resume()
+    }
+
+    override func stopLoading() {
+        if task?.state == .suspended {
+            suspend()
+        } else {
+            self.internalState = .transferFailed
+            guard let error = self.task?.error else { fatalError() }
+            completeTask(withError: error)
+            return
+        }
+    }
+
+    /// The internal state that the task is in.
+    ///
+    /// Setting this value will also add / remove the easy handle.
+    /// It is independt of the `state: URLSessionTask.State`. The
+    /// `internalState` tracks the state of transfers / waiting for callbacks.
+    /// The `state` tracks the overall state of the task (running vs.
+    /// completed).
+    fileprivate var internalState: _InternalState {
+        // We manage adding / removing the easy handle and pausing / unpausing
+        // here at a centralized place to make sure the internal state always
+        // matches up with the state of the easy handle being added and paused.
+        willSet {
+            if !internalState.isEasyHandlePaused && newValue.isEasyHandlePaused {
+                fatalError("Need to solve pausing receive.")
+            }
+            if internalState.isEasyHandleAddedToMultiHandle && !newValue.isEasyHandleAddedToMultiHandle {
+                task?.session.remove(handle: easyHandle)
+            }
+        }
+        didSet {
+            if !oldValue.isEasyHandleAddedToMultiHandle && internalState.isEasyHandleAddedToMultiHandle {
+                task?.session.add(handle: easyHandle)
+            }
+            if oldValue.isEasyHandlePaused && !internalState.isEasyHandlePaused {
+                fatalError("Need to solve pausing receive.")
+            }
+        }
+    }
+}
+
+fileprivate extension _HTTPURLProtocol {
+
+    /// Set options on the easy handle to match the given request.
+    ///
+    /// This performs a series of `curl_easy_setopt()` calls.
+    fileprivate func configureEasyHandle(for request: URLRequest) {
+        // At this point we will call the equivalent of curl_easy_setopt()
+        // to configure everything on the handle. Since we might be re-using
+        // a handle, we must be sure to set everything and not rely on defaul
+        // values.
+
+        //TODO: We could add a strong reference from the easy handle back to
+        // its URLSessionTask by means of CURLOPT_PRIVATE -- that would ensure
+        // that the task is always around while the handle is running.
+        // We would have to break that retain cycle once the handle completes
+        // its transfer.
+
+        // Behavior Options
+        easyHandle.set(verboseModeOn: enableLibcurlDebugOutput)
+        easyHandle.set(debugOutputOn: enableLibcurlDebugOutput, task: task!)
+        easyHandle.set(passHeadersToDataStream: false)
+        easyHandle.set(progressMeterOff: true)
+        easyHandle.set(skipAllSignalHandling: true)
+
+        // Error Options:
+        easyHandle.set(errorBuffer: nil)
+        easyHandle.set(failOnHTTPErrorCode: false)
+
+        // Network Options:
+        guard let url = request.url else { fatalError("No URL in request.") }
+        easyHandle.set(url: url)
+        easyHandle.setAllowedProtocolsToHTTPAndHTTPS()
+        easyHandle.set(preferredReceiveBufferSize: Int.max)
+        do {
+            switch (task?.body, try task?.body.getBodyLength()) {
+            case (.none, _):
+                set(requestBodyLength: .noBody)
+            case (_, .some(let length)):
+                set(requestBodyLength: .length(length))
+            case (_, .none):
+                set(requestBodyLength: .unknown)
+            }
+        } catch let e {
+            // Fail the request here.
+            // TODO: We have multiple options:
+            //     NSURLErrorNoPermissionsToReadFile
+            //     NSURLErrorFileDoesNotExist
+            self.internalState = .transferFailed
+            failWith(errorCode: errorCode(fileSystemError: e), request: request)
+            return
+        }
+
+        // HTTP Options:
+        easyHandle.set(followLocation: false)
+
+        // The httpAdditionalHeaders from session configuration has to be added to the request.
+        // The request.allHTTPHeaders can override the httpAdditionalHeaders elements. Add the
+        // httpAdditionalHeaders from session configuration first and then append/update the
+        // request.allHTTPHeaders so that request.allHTTPHeaders can override httpAdditionalHeaders.
+
+        let httpSession = self.task?.session as! URLSession
+        var httpHeaders: [AnyHashable : Any]?
+
+        if let hh = httpSession.configuration.httpAdditionalHeaders {
+            httpHeaders = hh
+        }
+
+        if let hh = self.task?.originalRequest?.allHTTPHeaderFields {
+            if httpHeaders == nil {
+                httpHeaders = hh
+            } else {
+                hh.forEach {
+                    httpHeaders![$0] = $1
+                }
+            }
+        }
+        let customHeaders: [String]
+        let headersForRequest = curlHeaders(for: httpHeaders)
+        if ((request.httpMethod == "POST") && (request.value(forHTTPHeaderField: "Content-Type") == nil)) {
+            customHeaders = headersForRequest + ["Content-Type:application/x-www-form-urlencoded"]
+        } else {
+            customHeaders = headersForRequest
+        }
+
+        easyHandle.set(customHeaders: customHeaders)
+
+        //TODO: The CURLOPT_PIPEDWAIT option is unavailable on Ubuntu 14.04 (libcurl 7.36)
+        //TODO: Introduce something like an #if, if we want to set them here
+
+        //set the request timeout
+        //TODO: the timeout value needs to be reset on every data transfer
+
+        var timeoutInterval = Int(httpSession.configuration.timeoutIntervalForRequest) * 1000
+        if request.isTimeoutIntervalSet {
+           timeoutInterval = Int(request.timeoutInterval) * 1000
+        }
+        let timeoutHandler = DispatchWorkItem { [weak self] in
+            guard let _ = self?.task else { fatalError("Timeout on a task that doesn't exist") } //this guard must always pass
+            self?.internalState = .transferFailed
+            let urlError = URLError(_nsError: NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut, userInfo: nil))
+            self?.completeTask(withError: urlError)
+            self?.client?.urlProtocol(self!, didFailWithError: urlError)
+        }
+	guard let task = self.task else { fatalError() }
+        easyHandle.timeoutTimer = _TimeoutSource(queue: task.workQueue, milliseconds: timeoutInterval, handler: timeoutHandler)
+
+        easyHandle.set(automaticBodyDecompression: true)
+        easyHandle.set(requestMethod: request.httpMethod ?? "GET")
+        if request.httpMethod == "HEAD" {
+            easyHandle.set(noBody: true)
+        }
+    }
+
+    /// These are a list of headers that should be passed to libcurl.
+    ///
+    /// Headers will be returned as `Accept: text/html` strings for
+    /// setting fields, `Accept:` for disabling the libcurl default header, or
+    /// `Accept;` for a header with no content. This is the format that libcurl
+    /// expects.
+    ///
+    /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
+    func curlHeaders(for httpHeaders: [AnyHashable : Any]?) -> [String] {
+        var result: [String] = []
+        var names = Set<String>()
+        if httpHeaders != nil {
+            let hh = httpHeaders as! [String:String]
+            hh.forEach {
+                let name = $0.0.lowercased()
+                guard !names.contains(name) else { return }
+                names.insert(name)
+
+                if $0.1.isEmpty {
+                    result.append($0.0 + ";")
+                } else {
+                    result.append($0.0 + ": " + $0.1)
+                }
+            }
+        }
+        curlHeadersToSet.forEach {
+            let name = $0.0.lowercased()
+            guard !names.contains(name) else { return }
+            names.insert(name)
+            
+            if $0.1.isEmpty {
+                result.append($0.0 + ";")
+            } else {
+                result.append($0.0 + ": " + $0.1)
+            }
+        }
+        curlHeadersToRemove.forEach {
+            let name = $0.lowercased()
+            guard !names.contains(name) else { return }
+            names.insert(name)
+            result.append($0 + ":")
+        }
+        return result
+    }
+    /// Any header values that should be passed to libcurl
+    ///
+    /// These will only be set if not already part of the request.
+    /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
+    var curlHeadersToSet: [(String,String)] {
+        var result = [("Connection", "keep-alive"),
+                      ("User-Agent", userAgentString),
+                      ]
+        if let language = NSLocale.current.languageCode {
+            result.append(("Accept-Language", language))
+        }
+        return result
+    }
+    /// Any header values that should be removed from the ones set by libcurl
+    /// - SeeAlso: https://curl.haxx.se/libcurl/c/CURLOPT_HTTPHEADER.html
+    var curlHeadersToRemove: [String] {
+        if case .none = task?.body {
+            return []
+        } else {
+            return ["Expect"]
+        }
+    }
+}
+
+fileprivate extension _HTTPURLProtocol {
+    /// Set request body length.
+    ///
+    /// An unknown length
+    func set(requestBodyLength length: _HTTPURLProtocol._RequestBodyLength) {
+        switch length {
+        case .noBody:
+            easyHandle.set(upload: false)
+            easyHandle.set(requestBodyLength: 0)
+        case .length(let length):
+            easyHandle.set(upload: true)
+            easyHandle.set(requestBodyLength: Int64(length))
+        case .unknown:
+            easyHandle.set(upload: true)
+            easyHandle.set(requestBodyLength: -1)
+        }
+    }
+    enum _RequestBodyLength {
+        case noBody
+        ///
+        case length(UInt64)
+        /// Will result in a chunked upload
+        case unknown
+    }
+}
+
+fileprivate var userAgentString: String = {
+    // Darwin uses something like this: "xctest (unknown version) CFNetwork/760.4.2 Darwin/15.4.0 (x86_64)"
+    let info = ProcessInfo.processInfo
+    let name = info.processName
+    let curlVersion = CFURLSessionCurlVersionInfo()
+    //TODO: Should probably use sysctl(3) to get these:
+    // kern.ostype: Darwin
+    // kern.osrelease: 15.4.0
+    //TODO: Use NSBundle to get the version number?
+    return "\(name) (unknown version) curl/\(curlVersion.major).\(curlVersion.minor).\(curlVersion.patch)"
+}()
+
+fileprivate let enableLibcurlDebugOutput: Bool = {
+    return (ProcessInfo.processInfo.environment["URLSessionDebugLibcurl"] != nil)
+}()
+fileprivate let enableDebugOutput: Bool = {
+    return (ProcessInfo.processInfo.environment["URLSessionDebug"] != nil)
+}()
+
+extension URLSession {
+    static func printDebug(_ text: @autoclosure () -> String) {
+        guard enableDebugOutput else { return }
+        debugPrint(text())
+    }
+}
+
+internal extension _HTTPURLProtocol {
+    enum _Body {
+        case none
+        case data(DispatchData)
+        /// Body data is read from the given file URL
+        case file(URL)
+        case stream(InputStream)
+    }
+
+    func failWith(errorCode: Int, request: URLRequest) {
+        //TODO: Error handling
+        let userInfo: [String : Any]? = request.url.map {
+            [
+                NSURLErrorFailingURLErrorKey: $0,
+                NSURLErrorFailingURLStringErrorKey: $0.absoluteString,
+                ]
+        }
+        let error = URLError(_nsError: NSError(domain: NSURLErrorDomain, code: errorCode, userInfo: userInfo))
+        completeTask(withError: error)
+        self.client?.urlProtocol(self, didFailWithError: error)
+    }
+}
+
+fileprivate extension _HTTPURLProtocol._Body {
+    enum _Error : Error {
+        case fileForBodyDataNotFound
+    }
+    /// - Returns: The body length, or `nil` for no body (e.g. `GET` request).
+    func getBodyLength() throws -> UInt64? {
+        switch self {
+        case .none:
+            return 0
+        case .data(let d):
+            return UInt64(d.count)
+        /// Body data is read from the given file URL
+        case .file(let fileURL):
+            guard let s = try FileManager.default.attributesOfItem(atPath: fileURL.path)[.size] as? NSNumber else {
+                throw _Error.fileForBodyDataNotFound
+            }
+            return s.uint64Value
+        case .stream:
+            return nil
+        }
+    }
+}
+
+fileprivate func errorCode(fileSystemError error: Error) -> Int {
+    func fromCocoaErrorCode(_ code: Int) -> Int {
+        switch code {
+        case CocoaError.fileReadNoSuchFile.rawValue:
+            return NSURLErrorFileDoesNotExist
+        case CocoaError.fileReadNoPermission.rawValue:
+            return NSURLErrorNoPermissionsToReadFile
+        default:
+            return NSURLErrorUnknown
+        }
+    }
+    switch error {
+    case let e as NSError where e.domain == NSCocoaErrorDomain:
+        return fromCocoaErrorCode(e.code)
+    default:
+        return NSURLErrorUnknown
+    }
+}
+
+internal extension _HTTPURLProtocol {
+    /// The data drain.
+    ///
+    /// This depends on what the delegate / completion handler need.
+    fileprivate func createTransferBodyDataDrain() -> _DataDrain {
+        guard let task = task else { fatalError() }
+        let s = task.session as! URLSession
+        switch s.behaviour(for: task) {
+        case .noDelegate:
+            return .ignore
+        case .taskDelegate:
+            // Data will be forwarded to the delegate as we receive it, we don't
+            // need to do anything about it.
+            return .ignore
+        case .dataCompletionHandler:
+            // Data needs to be concatenated in-memory such that we can pass it
+            // to the completion handler upon completion.
+            return .inMemory(nil)
+        case .downloadCompletionHandler:
+            // Data needs to be written to a file (i.e. a download task).
+            let fileHandle = try! FileHandle(forWritingTo: task.tempFileURL)
+            return .toFile(task.tempFileURL, fileHandle)
+        }
+    }
+}
+
+extension _HTTPURLProtocol {
+
+    /// Creates a new transfer state with the given behaviour:
+    func createTransferState(url: URL, workQueue: DispatchQueue) -> _HTTPTransferState {
+        let drain = createTransferBodyDataDrain()
+        guard let t = task else { fatalError("Cannot create transfer state") }
+        switch t.body {
+        case .none:
+            return _HTTPTransferState(url: url, bodyDataDrain: drain)
+        case .data(let data):
+            let source = _HTTPBodyDataSource(data: data)
+            return _HTTPTransferState(url: url, bodyDataDrain: drain, bodySource: source)
+        case .file(let fileURL):
+            let source = _HTTPBodyFileSource(fileURL: fileURL, workQueue: workQueue, dataAvailableHandler: { [weak self] in
+                // Unpause the easy handle
+                self?.easyHandle.unpauseSend()
+            })
+            return _HTTPTransferState(url: url, bodyDataDrain: drain, bodySource: source)
+        case .stream:
+            NSUnimplemented()
+        }
+    }
+}
+
+extension _HTTPURLProtocol: _EasyHandleDelegate {
+    
+    func didReceive(data: Data) -> _EasyHandle._Action {
+        guard case .transferInProgress(let ts) = internalState else { fatalError("Received body data, but no transfer in progress.") }
+        guard ts.isHeaderComplete else { fatalError("Received body data, but the header is not complete, yet.") }
+        notifyDelegate(aboutReceivedData: data)
+        internalState = .transferInProgress(ts.byAppending(bodyData: data))
+        return .proceed
+    }
+
+    fileprivate func notifyDelegate(aboutReceivedData data: Data) {
+        guard let t = self.task else { fatalError("Cannot notify") }
+        if case .taskDelegate(let delegate) = t.session.behaviour(for: self.task!),
+            let dataDelegate = delegate as? URLSessionDataDelegate,
+            let task = self.task as? URLSessionDataTask {
+            // Forward to the delegate:
+            guard let s = self.task?.session as? URLSession else { fatalError() }
+            s.delegateQueue.addOperation {
+                dataDelegate.urlSession(s, dataTask: task, didReceive: data)
+            }
+        } else if case .taskDelegate(let delegate) = t.session.behaviour(for: self.task!),
+            let downloadDelegate = delegate as? URLSessionDownloadDelegate,
+            let task = self.task as? URLSessionDownloadTask {
+            guard let s = self.task?.session as? URLSession else { fatalError() }
+            let fileHandle = try! FileHandle(forWritingTo: task.tempFileURL)
+            _ = fileHandle.seekToEndOfFile()
+            fileHandle.write(data)
+            self.task?.totalDownloaded += data.count
+            
+            s.delegateQueue.addOperation {
+                downloadDelegate.urlSession(s, downloadTask: task, didWriteData: Int64(data.count), totalBytesWritten: Int64(t.totalDownloaded),
+                                            totalBytesExpectedToWrite: Int64(self.easyHandle.fileLength))
+            }
+            if Int(self.easyHandle.fileLength) == self.task?.totalDownloaded {
+                fileHandle.closeFile()
+                s.delegateQueue.addOperation {
+                    downloadDelegate.urlSession(s, downloadTask: task, didFinishDownloadingTo: t.tempFileURL)
+                }
+            }
+        }
+    }
+
+    func didReceive(headerData data: Data) -> _EasyHandle._Action {
+        guard case .transferInProgress(let ts) = internalState else { fatalError("Received body data, but no transfer in progress.") }
+        do {
+            let newTS = try ts.byAppending(headerLine: data)
+            internalState = .transferInProgress(newTS)
+            let didCompleteHeader = !ts.isHeaderComplete && newTS.isHeaderComplete
+            if didCompleteHeader {
+                // The header is now complete, but wasn't before.
+                didReceiveResponse()
+            }
+            return .proceed
+        } catch {
+            return .abort
+        }
+    }
+
+    func fill(writeBuffer buffer: UnsafeMutableBufferPointer<Int8>) -> _EasyHandle._WriteBufferResult {
+        guard case .transferInProgress(let ts) = internalState else { fatalError("Requested to fill write buffer, but transfer isn't in progress.") }
+        guard let source = ts.requestBodySource else { fatalError("Requested to fill write buffer, but transfer state has no body source.") }
+        switch source.getNextChunk(withLength: buffer.count) {
+        case .data(let data):
+            copyDispatchData(data, infoBuffer: buffer)
+            let count = data.count
+            assert(count > 0)
+            return .bytes(count)
+        case .done:
+            return .bytes(0)
+        case .retryLater:
+            // At this point we'll try to pause the easy handle. The body source
+            // is responsible for un-pausing the handle once data becomes
+            // available.
+            return .pause
+        case .error:
+            return .abort
+        }
+    }
+
+    func transferCompleted(withErrorCode errorCode: Int?) {
+        // At this point the transfer is complete and we can decide what to do.
+        // If everything went well, we will simply forward the resulting data
+        // to the delegate. But in case of redirects etc. we might send another
+        // request.
+        guard case .transferInProgress(let ts) = internalState else { fatalError("Transfer completed, but it wasn't in progress.") }
+        guard let request = task?.currentRequest else { fatalError("Transfer completed, but there's no current request.") }
+        guard errorCode == nil else {
+            internalState = .transferFailed
+            failWith(errorCode: errorCode!, request: request)
+            return
+        }
+
+        if let response = task?.response as? HTTPURLResponse {
+            var transferState = ts
+            transferState.response = response
+        }
+
+        guard let response = ts.response else { fatalError("Transfer completed, but there's no response.") }
+        internalState = .transferCompleted(response: response, bodyDataDrain: ts.bodyDataDrain)
+        let action = completionAction(forCompletedRequest: request, response: response)
+        
+        switch action {
+        case .completeTask:
+            completeTask()
+        case .failWithError(let errorCode):
+            internalState = .transferFailed
+            failWith(errorCode: errorCode, request: request)
+        case .redirectWithRequest(let newRequest):
+            redirectFor(request: newRequest)
+        }
+    }
+
+    func seekInputStream(to position: UInt64) throws {
+        // We will reset the body sourse and seek forward.
+        NSUnimplemented()
+    }
+ 
+    func updateProgressMeter(with propgress: _EasyHandle._Progress) {
+        //TODO: Update progress. Note that a single URLSessionTask might
+        // perform multiple transfers. The values in `progress` are only for
+        // the current transfer.
+    }
+}
+
+extension _HTTPURLProtocol {
+    /// The is independent of the public `state: URLSessionTask.State`.
+    enum _InternalState {
+        /// Task has been created, but nothing has been done, yet
+        case initial
+        /// The easy handle has been fully configured. But it is not added to
+        /// the multi handle.
+        case transferReady(_HTTPTransferState)
+        /// The easy handle is currently added to the multi handle
+        case transferInProgress(_HTTPTransferState)
+        /// The transfer completed.
+        ///
+        /// The easy handle has been removed from the multi handle. This does
+        /// not (necessarily mean the task completed. A task that gets
+        /// redirected will do multiple transfers.
+        case transferCompleted(response: URLResponse, bodyDataDrain: _DataDrain)
+        /// The transfer failed.
+        ///
+        /// Same as `.transferCompleted`, but without response / body data
+        case transferFailed
+        /// Waiting for the completion handler of the HTTP redirect callback.
+        ///
+        /// When we tell the delegate that we're about to perform an HTTP
+        /// redirect, we need to wait for the delegate to let us know what
+        /// action to take.
+        case waitingForRedirectCompletionHandler(response: URLResponse, bodyDataDrain: _DataDrain)
+        /// Waiting for the completion handler of the 'did receive response' callback.
+        ///
+        /// When we tell the delegate that we received a response (i.e. when
+        /// we received a complete header), we need to wait for the delegate to
+        /// let us know what action to take. In this state the easy handle is
+        /// paused in order to suspend delegate callbacks.
+        case waitingForResponseCompletionHandler(_HTTPTransferState)
+        /// The task is completed
+        ///
+        /// Contrast this with `.transferCompleted`.
+        case taskCompleted
+    }
+}
+
+extension _HTTPURLProtocol._InternalState {
+    var isEasyHandleAddedToMultiHandle: Bool {
+        switch self {
+        case .initial:                             return false
+        case .transferReady:                       return false
+        case .transferInProgress:                  return true
+        case .transferCompleted:                   return false
+        case .transferFailed:                      return false
+        case .waitingForRedirectCompletionHandler: return false
+        case .waitingForResponseCompletionHandler: return true
+        case .taskCompleted:                       return false
+        }
+    }
+    var isEasyHandlePaused: Bool {
+        switch self {
+        case .initial:                             return false
+        case .transferReady:                       return false
+        case .transferInProgress:                  return false
+        case .transferCompleted:                   return false
+        case .transferFailed:                      return false
+        case .waitingForRedirectCompletionHandler: return false
+        case .waitingForResponseCompletionHandler: return true
+        case .taskCompleted:                       return false
+        }
+    }
+}
+
+internal extension _HTTPURLProtocol {
+    /// Start a new transfer
+    func startNewTransfer(with request: URLRequest) {
+        guard let t = task else { fatalError() }
+        t.currentRequest = request
+        guard let url = request.url else { fatalError("No URL in request.") }
+
+        self.internalState = .transferReady(createTransferState(url: url, workQueue: t.workQueue))
+        configureEasyHandle(for: request)
+        if (t.suspendCount) < 1 {
+            resume()
+        }
+    }
+
+    func resume() {
+        if case .initial = self.internalState {
+            guard let r = task?.originalRequest else { fatalError("Task has no original request.") }
+            startNewTransfer(with: r)
+        }
+        
+        if case .transferReady(let transferState) = self.internalState {
+            self.internalState = .transferInProgress(transferState)
+        }
+    }
+
+    func suspend() {
+        if case .transferInProgress(let transferState) =  self.internalState {
+            self.internalState = .transferReady(transferState)
+        }
+    }
+}
+
+/// State Transfers
+extension _HTTPURLProtocol {
+    func completeTask() {
+        guard case .transferCompleted(response: let response, bodyDataDrain: let bodyDataDrain) = self.internalState else {
+            fatalError("Trying to complete the task, but its transfer isn't complete.")
+        }
+        task?.response = response
+
+        //We don't want a timeout to be triggered after this. The timeout timer needs to be cancelled.
+        easyHandle.timeoutTimer = nil
+
+        //because we deregister the task with the session on internalState being set to taskCompleted
+        //we need to do the latter after the delegate/handler was notified/invoked
+        if case .inMemory(let bodyData) = bodyDataDrain {
+            var data = Data()
+            if let body = bodyData {
+                data = Data(bytes: body.bytes, count: body.length)
+            }
+            self.client?.urlProtocol(self, didLoad: data)
+            self.internalState = .taskCompleted
+            return
+        }
+
+        if case .toFile(let url, let fileHandle?) = bodyDataDrain {
+            fileHandle.closeFile()
+        }
+        self.client?.urlProtocolDidFinishLoading(self)
+        self.internalState = .taskCompleted
+    }
+
+    func completeTask(withError error: Error) {
+        task?.error = error
+        
+        guard case .transferFailed = self.internalState else {
+            fatalError("Trying to complete the task, but its transfer isn't complete / failed.")
+        }
+
+        //We don't want a timeout to be triggered after this. The timeout timer needs to be cancelled.
+        easyHandle.timeoutTimer = nil
+        self.internalState = .taskCompleted
+    }
+
+    func redirectFor(request: URLRequest) {
+        //TODO: Should keep track of the number of redirects that this
+        // request has gone through and err out once it's too large, i.e.
+        // call into `failWith(errorCode: )` with NSURLErrorHTTPTooManyRedirects
+        guard case .transferCompleted(response: let response, bodyDataDrain: let bodyDataDrain) = self.internalState else {
+            fatalError("Trying to redirect, but the transfer is not complete.")
+        }
+
+        let session = task?.session as! URLSession
+        switch session.behaviour(for: task!) {
+        case .taskDelegate(let delegate):
+            // At this point we need to change the internal state to note
+            // that we're waiting for the delegate to call the completion
+            // handler. Then we'll call the delegate callback
+            // (willPerformHTTPRedirection). The task will then switch out of
+            // its internal state once the delegate calls the completion
+            // handler.
+
+            //TODO: Should the `public response: URLResponse` property be updated
+            // before we call delegate API
+
+            self.internalState = .waitingForRedirectCompletionHandler(response: response, bodyDataDrain: bodyDataDrain)
+            // We need this ugly cast in order to be able to support `URLSessionTask.init()`
+            guard let s = session as? URLSession else { fatalError() }
+            s.delegateQueue.addOperation {
+                delegate.urlSession(s, task: self.task!, willPerformHTTPRedirection: response as! HTTPURLResponse, newRequest: request) { [weak self] (request: URLRequest?) in
+                    guard let task = self else { return }
+                    self?.task?.workQueue.async {
+                        task.didCompleteRedirectCallback(request)
+                    }
+                }
+            }
+        case .noDelegate, .dataCompletionHandler, .downloadCompletionHandler:
+            // Follow the redirect.
+            startNewTransfer(with: request)
+        }
+    }
+
+    fileprivate func didCompleteRedirectCallback(_ request: URLRequest?) {
+        guard case .waitingForRedirectCompletionHandler(response: let response, bodyDataDrain: let bodyDataDrain) = self.internalState else {
+            fatalError("Received callback for HTTP redirection, but we're not waiting for it. Was it called multiple times?")
+        }
+        // If the request is `nil`, we're supposed to treat the current response
+        // as the final response, i.e. not do any redirection.
+        // Otherwise, we'll start a new transfer with the passed in request.
+        if let r = request {
+            startNewTransfer(with: r)
+        } else {
+            self.internalState = .transferCompleted(response: response, bodyDataDrain: bodyDataDrain)
+            completeTask()
+        }
+    }
+}
+
+/// Response processing
+internal extension _HTTPURLProtocol {
+    /// Whenever we receive a response (i.e. a complete header) from libcurl,
+    /// this method gets called.
+    func didReceiveResponse() {
+        guard let dt = task as? URLSessionDataTask else { return }
+        guard case .transferInProgress(let ts) = self.internalState else { fatalError("Transfer not in progress.") }
+        guard let response = ts.response else { fatalError("Header complete, but not URL response.") }
+        let session = task?.session as! URLSession
+        switch session.behaviour(for: self.task!) {
+        case .noDelegate:
+            break
+        case .taskDelegate(let delegate as URLSessionDataDelegate):
+            //TODO: There's a problem with libcurl / with how we're using it.
+            // We're currently unable to pause the transfer / the easy handle:
+            // https://curl.haxx.se/mail/lib-2016-03/0222.html
+            //
+            // For now, we'll notify the delegate, but won't pause the transfer,
+            // and we'll disregard the completion handler:
+            guard let s = session as? URLSession else { fatalError() }
+            s.delegateQueue.addOperation {
+                delegate.urlSession(s, dataTask: dt, didReceive: response, completionHandler: { _ in
+                    URLSession.printDebug("warning: Ignoring disposition from completion handler.")
+                })
+            }
+        case .taskDelegate:
+            break
+        case .dataCompletionHandler:
+            break
+        case .downloadCompletionHandler:
+            break
+        }
+    }
+    /// Give the delegate a chance to tell us how to proceed once we have a
+    /// response / complete header.
+    ///
+    /// This will pause the transfer.
+    func askDelegateHowToProceedAfterCompleteResponse(_ response: HTTPURLResponse, delegate: URLSessionDataDelegate) {
+        // Ask the delegate how to proceed.
+        
+        // This will pause the easy handle. We need to wait for the
+        // delegate before processing any more data.
+        guard case .transferInProgress(let ts) = self.internalState else { fatalError("Transfer not in progress.") }
+        self.internalState = .waitingForResponseCompletionHandler(ts)
+        
+        let dt = task as! URLSessionDataTask
+        
+        // We need this ugly cast in order to be able to support `URLSessionTask.init()`
+        guard let s = task?.session as? URLSession else { fatalError() }
+        s.delegateQueue.addOperation {
+            delegate.urlSession(s, dataTask: dt, didReceive: response, completionHandler: { [weak self] disposition in
+                guard let task = self else { return }
+                self?.task?.workQueue.async {
+                    task.didCompleteResponseCallback(disposition: disposition)
+                }
+            })
+        }
+    }
+    /// This gets called (indirectly) when the data task delegates lets us know
+    /// how we should proceed after receiving a response (i.e. complete header).
+    func didCompleteResponseCallback(disposition: URLSession.ResponseDisposition) {
+        guard case .waitingForResponseCompletionHandler(let ts) = self.internalState else { fatalError("Received response disposition, but we're not waiting for it.") }
+        switch disposition {
+        case .cancel:
+            let error = URLError(_nsError: NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled))
+            self.completeTask(withError: error)
+            self.client?.urlProtocol(self, didFailWithError: error)
+        case .allow:
+            // Continue the transfer. This will unpause the easy handle.
+            self.internalState = .transferInProgress(ts)
+        case .becomeDownload:
+            /* Turn this request into a download */
+            NSUnimplemented()
+        case .becomeStream:
+            /* Turn this task into a stream task */
+            NSUnimplemented()
+        }
+    }
+
+    /// Action to be taken after a transfer completes
+    enum _CompletionAction {
+        case completeTask
+        case failWithError(Int)
+        case redirectWithRequest(URLRequest)
+    }
+
+    /// What action to take
+    func completionAction(forCompletedRequest request: URLRequest, response: HTTPURLResponse) -> _CompletionAction {
+        // Redirect:
+        if let request = redirectRequest(for: response, fromRequest: request) {
+            return .redirectWithRequest(request)
+        }
+        return .completeTask
+    }
+    /// If the response is a redirect, return the new request
+    ///
+    /// RFC 7231 section 6.4 defines redirection behavior for HTTP/1.1
+    ///
+    /// - SeeAlso: <https://tools.ietf.org/html/rfc7231#section-6.4>
+    func redirectRequest(for response: HTTPURLResponse, fromRequest: URLRequest) -> URLRequest? {
+        //TODO: Do we ever want to redirect for HEAD requests?
+        func methodAndURL() -> (String, URL)? {
+            guard
+                let location = response.value(forHeaderField: .location),
+                let targetURL = URL(string: location)
+                else {
+                    // Can't redirect when there's no location to redirect to.
+                    return nil
+            }
+
+            // Check for a redirect:
+            switch response.statusCode {
+            //TODO: Should we do this for 300 "Multiple Choices", too?
+            case 301, 302, 303:
+                // Change into "GET":
+                return ("GET", targetURL)
+            case 307:
+                // Re-use existing method:
+                return (fromRequest.httpMethod ?? "GET", targetURL)
+            default:
+                return nil
+            }
+        }
+        guard let (method, targetURL) = methodAndURL() else { return nil }
+        var request = fromRequest
+        request.httpMethod = method
+        request.url = targetURL
+        return request
+    }
+}
+
+fileprivate extension HTTPURLResponse {
+    /// Type safe HTTP header field name(s)
+    enum _Field: String {
+        /// `Location`
+        /// - SeeAlso: RFC 2616 section 14.30 <https://tools.ietf.org/html/rfc2616#section-14.30>
+        case location = "Location"
+    }
+    func value(forHeaderField field: _Field) -> String? {
+        return field.rawValue
+    }
+}
diff --git a/Foundation/NSURLSession/MultiHandle.swift b/Foundation/NSURLSession/http/MultiHandle.swift
similarity index 100%
rename from Foundation/NSURLSession/MultiHandle.swift
rename to Foundation/NSURLSession/http/MultiHandle.swift
diff --git a/Foundation/NSURLSession/TransferState.swift b/Foundation/NSURLSession/http/TransferState.swift
similarity index 66%
rename from Foundation/NSURLSession/TransferState.swift
rename to Foundation/NSURLSession/http/TransferState.swift
index 1532067..8e86c35 100644
--- a/Foundation/NSURLSession/TransferState.swift
+++ b/Foundation/NSURLSession/http/TransferState.swift
@@ -21,7 +21,7 @@
 
 
 
-extension URLSessionTask {
+extension _HTTPURLProtocol {
     /// State related to an ongoing transfer.
     ///
     /// This contains headers received so far, body data received so far, etc.
@@ -31,51 +31,52 @@
     ///
     /// - TODO: Might move the `EasyHandle` into this `struct` ?
     /// - SeeAlso: `URLSessionTask.EasyHandle`
-    internal struct _TransferState {
+    internal struct _HTTPTransferState {
         /// The URL that's being requested
         let url: URL
         /// Raw headers received.
         let parsedResponseHeader: _ParsedResponseHeader
         /// Once the headers is complete, this will contain the response
-        let response: HTTPURLResponse?
+        var response: HTTPURLResponse?
         /// The body data to be sent in the request
         let requestBodySource: _HTTPBodySource?
         /// Body data received
         let bodyDataDrain: _DataDrain
         /// Describes what to do with received body data for this transfer:
-        enum _DataDrain {
-            /// Concatenate in-memory
-            case inMemory(NSMutableData?)
-            /// Write to file
-            case toFile(URL, FileHandle?)
-            /// Do nothing. Might be forwarded to delegate
-            case ignore
-        }
     }
 }
 
+extension _HTTPURLProtocol {
+    enum _DataDrain {
+        /// Concatenate in-memory
+        case inMemory(NSMutableData?)
+        /// Write to file
+        case toFile(URL, FileHandle?)
+        /// Do nothing. Might be forwarded to delegate
+        case ignore
+    }
+}
 
-
-extension URLSessionTask._TransferState {
+extension _HTTPURLProtocol._HTTPTransferState {
     /// Transfer state that can receive body data, but will not send body data.
-    init(url: URL, bodyDataDrain: _DataDrain) {
+    init(url: URL, bodyDataDrain: _HTTPURLProtocol._DataDrain) {
         self.url = url
-        self.parsedResponseHeader = URLSessionTask._ParsedResponseHeader()
+        self.parsedResponseHeader = _HTTPURLProtocol._ParsedResponseHeader()
         self.response = nil
         self.requestBodySource = nil
         self.bodyDataDrain = bodyDataDrain
     }
     /// Transfer state that sends body data and can receive body data.
-    init(url: URL, bodyDataDrain: _DataDrain, bodySource: _HTTPBodySource) {
+    init(url: URL, bodyDataDrain: _HTTPURLProtocol._DataDrain, bodySource: _HTTPBodySource) {
         self.url = url
-        self.parsedResponseHeader = URLSessionTask._ParsedResponseHeader()
+        self.parsedResponseHeader = _HTTPURLProtocol._ParsedResponseHeader()
         self.response = nil
         self.requestBodySource = bodySource
         self.bodyDataDrain = bodyDataDrain
     }
 }
 
-extension URLSessionTask._TransferState {
+extension _HTTPURLProtocol._HTTPTransferState {
     enum _Error: Error {
         case parseSingleLineError
         case parseCompleteHeaderError
@@ -86,7 +87,7 @@
     /// return value's `isHeaderComplete` will then by `true`.
     ///
     /// - Throws: When a parsing error occurs
-    func byAppending(headerLine data: Data) throws -> URLSessionTask._TransferState {
+    func byAppending(headerLine data: Data) throws -> _HTTPURLProtocol._HTTPTransferState {
         guard let h = parsedResponseHeader.byAppending(headerLine: data) else {
             throw _Error.parseSingleLineError
         }
@@ -96,9 +97,9 @@
             guard response != nil else {
                 throw _Error.parseCompleteHeaderError
             }
-            return URLSessionTask._TransferState(url: url, parsedResponseHeader: URLSessionTask._ParsedResponseHeader(), response: response, requestBodySource: requestBodySource, bodyDataDrain: bodyDataDrain)
+            return _HTTPURLProtocol._HTTPTransferState(url: url, parsedResponseHeader: _HTTPURLProtocol._ParsedResponseHeader(), response: response, requestBodySource: requestBodySource, bodyDataDrain: bodyDataDrain)
         } else {
-            return URLSessionTask._TransferState(url: url, parsedResponseHeader: h, response: nil, requestBodySource: requestBodySource, bodyDataDrain: bodyDataDrain)
+            return _HTTPURLProtocol._HTTPTransferState(url: url, parsedResponseHeader: h, response: nil, requestBodySource: requestBodySource, bodyDataDrain: bodyDataDrain)
         }
     }
     var isHeaderComplete: Bool {
@@ -109,13 +110,13 @@
     /// - Important: This will mutate the existing `NSMutableData` that the
     ///     struct may already have in place -- copying the data is too
     ///     expensive. This behaviour
-    func byAppending(bodyData buffer: Data) -> URLSessionTask._TransferState {
+    func byAppending(bodyData buffer: Data) -> _HTTPURLProtocol._HTTPTransferState {
         switch bodyDataDrain {
         case .inMemory(let bodyData):
             let data: NSMutableData = bodyData ?? NSMutableData()
             data.append(buffer)
-            let drain = _DataDrain.inMemory(data)
-            return URLSessionTask._TransferState(url: url, parsedResponseHeader: parsedResponseHeader, response: response, requestBodySource: requestBodySource, bodyDataDrain: drain)
+            let drain = _HTTPURLProtocol._DataDrain.inMemory(data)
+            return _HTTPURLProtocol._HTTPTransferState(url: url, parsedResponseHeader: parsedResponseHeader, response: response, requestBodySource: requestBodySource, bodyDataDrain: drain)
         case .toFile(_, let fileHandle):
              //TODO: Create / open the file for writing
              // Append to the file
@@ -130,8 +131,7 @@
     ///
     /// This can be used to either set the initial body source, or to reset it
     /// e.g. when restarting a transfer.
-    func bySetting(bodySource newSource: _HTTPBodySource) -> URLSessionTask._TransferState {
-        return URLSessionTask._TransferState(url: url, parsedResponseHeader: parsedResponseHeader, response: response, requestBodySource: newSource, bodyDataDrain: bodyDataDrain)
+    func bySetting(bodySource newSource: _HTTPBodySource) -> _HTTPURLProtocol._HTTPTransferState {
+        return _HTTPURLProtocol._HTTPTransferState(url: url, parsedResponseHeader: parsedResponseHeader, response: response, requestBodySource: newSource, bodyDataDrain: bodyDataDrain)
     }
 }
-
diff --git a/Foundation/NSURLSession/libcurlHelpers.swift b/Foundation/NSURLSession/http/libcurlHelpers.swift
similarity index 100%
rename from Foundation/NSURLSession/libcurlHelpers.swift
rename to Foundation/NSURLSession/http/libcurlHelpers.swift
diff --git a/TestFoundation/TestNSURLSession.swift b/TestFoundation/TestNSURLSession.swift
index 275016c..ea3e3da 100644
--- a/TestFoundation/TestNSURLSession.swift
+++ b/TestFoundation/TestNSURLSession.swift
@@ -24,20 +24,21 @@
 //Disabling to avoid https://bugs.swift.org/browse/SR-4677 and a timeout failure
 //            ("test_dataTaskWithURL", test_dataTaskWithURL),
 //            ("test_dataTaskWithURLRequest", test_dataTaskWithURLRequest),
-//            ("test_dataTaskWithURLCompletionHandler", test_dataTaskWithURLCompletionHandler),
-//            ("test_dataTaskWithURLRequestCompletionHandler", test_dataTaskWithURLRequestCompletionHandler),
+            ("test_dataTaskWithURLCompletionHandler", test_dataTaskWithURLCompletionHandler),
+            ("test_dataTaskWithURLRequestCompletionHandler", test_dataTaskWithURLRequestCompletionHandler),
 //            ("test_downloadTaskWithURL", test_downloadTaskWithURL),
 //            ("test_downloadTaskWithURLRequest", test_downloadTaskWithURLRequest),
-//            ("test_downloadTaskWithRequestAndHandler", test_downloadTaskWithRequestAndHandler),
-//            ("test_downloadTaskWithURLAndHandler", test_downloadTaskWithURLAndHandler),
+            ("test_downloadTaskWithRequestAndHandler", test_downloadTaskWithRequestAndHandler),
+            ("test_downloadTaskWithURLAndHandler", test_downloadTaskWithURLAndHandler),
 //            ("test_finishTaskAndInvalidate", test_finishTasksAndInvalidate),
 //            ("test_taskError", test_taskError),
-//            ("test_taskCopy", test_taskCopy),
+            ("test_taskCopy", test_taskCopy),
 //            ("test_cancelTask", test_cancelTask),
 //            ("test_taskTimeout", test_taskTimeout),
-//            ("test_verifyRequestHeaders", test_verifyRequestHeaders),
-//            ("test_verifyHttpAdditionalHeaders", test_verifyHttpAdditionalHeaders),
+            ("test_verifyRequestHeaders", test_verifyRequestHeaders),
+            ("test_verifyHttpAdditionalHeaders", test_verifyHttpAdditionalHeaders),
             ("test_timeoutInterval", test_timeoutInterval),
+	    ("test_customProtocol", test_customProtocol),
         ]
     }
 
@@ -418,6 +419,37 @@
 
         waitForExpectations(timeout: 30)
     }
+
+    func test_customProtocol () {
+        let serverReady = ServerSemaphore()
+        globalDispatchQueue.async {
+            do {
+                try self.runServer(with: serverReady)
+            } catch {
+                XCTAssertTrue(true)
+                return
+            }
+        }
+        serverReady.wait()
+        let urlString = "http://127.0.0.1:\(serverPort)/USA"
+        let url = URL(string: urlString)!
+        let config = URLSessionConfiguration.default
+        config.protocolClasses = [CustomProtocol.self]
+        config.timeoutIntervalForRequest = 8
+        let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil)
+        let expect = expectation(description: "URL test with custom protocol")
+        let task = session.dataTask(with: url) { data, response, error in
+            defer { expect.fulfill() }
+            if let e = error as? URLError {
+                XCTAssertEqual(e.code, .timedOut, "Unexpected error code")
+                return
+            }
+            let httpResponse = response as! HTTPURLResponse?
+            XCTAssertEqual(429, httpResponse!.statusCode, "HTTP response code is not 429")
+        }
+        task.resume()
+        waitForExpectations(timeout: 12)
+    }
 }
 
 class SessionDelegate: NSObject, URLSessionDelegate {
@@ -526,3 +558,28 @@
        dwdExpectation.fulfill()
    }
 }
+
+class CustomProtocol : URLProtocol {
+
+    override class func canInit(with request: URLRequest) -> Bool {
+        return true
+    }
+
+    func sendResponse(statusCode: Int, headers: [String: String] = [:], data: Data) {
+        let response = HTTPURLResponse(url: self.request.url!, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: headers)
+        self.client?.urlProtocol(self, didReceive: response!, cacheStoragePolicy: .notAllowed)
+        self.client?.urlProtocolDidFinishLoading(self)
+    }
+
+    override class func canonicalRequest(for request: URLRequest) -> URLRequest {
+        return request
+    }
+ 
+    override func startLoading() {
+        sendResponse(statusCode: 429, data: Data())
+    }
+
+    override func stopLoading() {
+        return
+    }
+}
diff --git a/build.py b/build.py
index e569e2b..5592735 100644
--- a/build.py
+++ b/build.py
@@ -398,17 +398,18 @@
 	'Foundation/NSURLRequest.swift',
 	'Foundation/NSURLResponse.swift',
 	'Foundation/NSURLSession/Configuration.swift',
-	'Foundation/NSURLSession/EasyHandle.swift',
-	'Foundation/NSURLSession/HTTPBodySource.swift',
-	'Foundation/NSURLSession/HTTPMessage.swift',
-	'Foundation/NSURLSession/MultiHandle.swift',
+	'Foundation/NSURLSession/http/EasyHandle.swift',
+	'Foundation/NSURLSession/http/HTTPBodySource.swift',
+	'Foundation/NSURLSession/http/HTTPMessage.swift',
+	'Foundation/NSURLSession/http/MultiHandle.swift',
 	'Foundation/NSURLSession/NSURLSession.swift',
 	'Foundation/NSURLSession/NSURLSessionConfiguration.swift',
 	'Foundation/NSURLSession/NSURLSessionDelegate.swift',
 	'Foundation/NSURLSession/NSURLSessionTask.swift',
 	'Foundation/NSURLSession/TaskRegistry.swift',
-	'Foundation/NSURLSession/TransferState.swift',
-	'Foundation/NSURLSession/libcurlHelpers.swift',
+	'Foundation/NSURLSession/http/TransferState.swift',
+	'Foundation/NSURLSession/http/libcurlHelpers.swift',
+        'Foundation/NSURLSession/http/HTTPURLProtocol.swift',
 	'Foundation/NSUserDefaults.swift',
 	'Foundation/NSUUID.swift',
 	'Foundation/NSValue.swift',