Merge pull request #1079 from ianpartridge/nsdecimalnumberhandler

NSDecimalNumberHandler: default is now a property
diff --git a/Foundation/JSONEncoder.swift b/Foundation/JSONEncoder.swift
index 43fc52c..c6f09e0 100644
--- a/Foundation/JSONEncoder.swift
+++ b/Foundation/JSONEncoder.swift
@@ -119,7 +119,7 @@
     ///
     /// - parameter value: The value to encode.
     /// - returns: A new `Data` value containing the encoded JSON data.
-    /// - throws: `EncodingError.invalidValue` if a non-comforming floating-point value is encountered during encoding, and the encoding strategy is `.throw`.
+    /// - throws: `EncodingError.invalidValue` if a non-conforming floating-point value is encountered during encoding, and the encoding strategy is `.throw`.
     /// - throws: An error if any value throws an error during encoding.
     open func encode<T : Encodable>(_ value: T) throws -> Data {
         let encoder = _JSONEncoder(options: self.options)
diff --git a/Foundation/LengthFormatter.swift b/Foundation/LengthFormatter.swift
index a3c6bb9..ffb72b6 100644
--- a/Foundation/LengthFormatter.swift
+++ b/Foundation/LengthFormatter.swift
@@ -69,7 +69,7 @@
         //Extract the number from the measurement
         let numberInUnit = unitMeasurement.value
         
-        if isForPersonHeightUse && !numberFormatter.locale.sr3202_fix_isMetricSystemLocale() {
+        if isForPersonHeightUse && !numberFormatter.locale.usesMetricSystem {
             let feet = numberInUnit.rounded(.towardZero)
             let feetString = string(fromValue: feet, unit: .foot)
             
@@ -123,7 +123,7 @@
     /// - Parameter numberInMeters: the magnitude in terms of meters
     /// - Returns: Returns the appropriate unit
     private func unit(fromMeters numberInMeters: Double) -> Unit {
-        if numberFormatter.locale.sr3202_fix_isMetricSystemLocale() {
+        if numberFormatter.locale.usesMetricSystem {
             //Person height is always returned in cm for metric system
             if isForPersonHeightUse { return .centimeter }
             
diff --git a/Foundation/Locale.swift b/Foundation/Locale.swift
index 29b58ba..7b81c63 100644
--- a/Foundation/Locale.swift
+++ b/Foundation/Locale.swift
@@ -221,7 +221,7 @@
     /// -seealso: MeasurementFormatter
     public var usesMetricSystem: Bool {
         // NSLocale should not return nil here, but just in case
-        if let result = (_wrapped.object(forKey: .usesMetricSystem) as? NSNumber)?.boolValue {
+        if let result = _wrapped.object(forKey: .usesMetricSystem) as? Bool {
             return result
         } else {
             return false
diff --git a/Foundation/MassFormatter.swift b/Foundation/MassFormatter.swift
index 101856a..3d4b82f 100644
--- a/Foundation/MassFormatter.swift
+++ b/Foundation/MassFormatter.swift
@@ -134,7 +134,7 @@
     /// - Parameter numberInKilograms: the magnitude in terms of kilograms
     /// - Returns: Returns the appropriate unit
     private func convertedUnit(fromKilograms numberInKilograms: Double) -> Unit {
-        if numberFormatter.locale.sr3202_fix_isMetricSystemLocale() {
+        if numberFormatter.locale.usesMetricSystem {
             if numberInKilograms > 1.0 || numberInKilograms <= 0.0 {
                 return .kilogram
             } else {
@@ -236,22 +236,3 @@
                                                             .pound: "pounds",
                                                             .stone: "stones"]
 }
-
-internal extension Locale {
-    /// TODO: Replace calls to the below function to use Locale.usesMetricSystem
-    /// Temporary workaround due to unpopulated Locale attributes
-    /// See https://bugs.swift.org/browse/SR-3202
-    internal func sr3202_fix_isMetricSystemLocale() -> Bool {
-        switch self.identifier {
-        case "en_US": return false
-        case "en_US_POSIX": return false
-        case "haw_US": return false
-        case "es_US": return false
-        case "chr_US": return false
-        case "my_MM": return false
-        case "en_LR": return false
-        case "vai_LR": return false
-        default: return true
-        }
-    }
-}
diff --git a/Foundation/NSURLProtocol.swift b/Foundation/NSURLProtocol.swift
index 74bf665..67f6bb2 100644
--- a/Foundation/NSURLProtocol.swift
+++ b/Foundation/NSURLProtocol.swift
@@ -159,6 +159,9 @@
 
     private static var _registeredProtocolClasses = [AnyClass]()
     private static var _classesLock = NSLock()
+
+    //TODO: The right way to do this is using URLProtocol.property(forKey:in) and URLProtocol.setProperty(_:forKey:in)
+    var properties: [URLProtocol._PropertyKey: Any] = [:]
     /*! 
         @method initWithRequest:cachedResponse:client:
         @abstract Initializes an NSURLProtocol given request, 
diff --git a/Foundation/NSURLSession/NSURLSessionTask.swift b/Foundation/NSURLSession/NSURLSessionTask.swift
index f843780..bcf06da 100644
--- a/Foundation/NSURLSession/NSURLSessionTask.swift
+++ b/Foundation/NSURLSession/NSURLSessionTask.swift
@@ -526,7 +526,20 @@
 extension _ProtocolClient : URLProtocolClient {
 
     func urlProtocol(_ protocol: URLProtocol, didReceive response: URLResponse, cacheStoragePolicy policy: URLCache.StoragePolicy) {
-        `protocol`.task?.response = response
+        guard let task = `protocol`.task else { fatalError("Received response, but there's no task.") }
+        task.response = response
+        let session = task.session as! URLSession
+        guard let dataTask = task as? URLSessionDataTask else { return }
+        switch session.behaviour(for: task) {
+        case .taskDelegate(let delegate as URLSessionDataDelegate):
+            session.delegateQueue.addOperation {
+                delegate.urlSession(session, dataTask: dataTask, didReceive: response, completionHandler: { _ in
+                    URLSession.printDebug("warning: Ignoring disposition from completion handler.")
+                })
+            }
+        case .noDelegate, .taskDelegate, .dataCompletionHandler, .downloadCompletionHandler:
+            break
+        }
     }
 
     func urlProtocolDidFinishLoading(_ protocol: URLProtocol) {
@@ -543,13 +556,14 @@
             task.state = .completed
             session.taskRegistry.remove(task)
         case .dataCompletionHandler(let completion):
-            let data = Data()
-            guard let client = `protocol`.client else { fatalError() }
-            client.urlProtocol(`protocol`, didLoad: data)
-            return
+            session.delegateQueue.addOperation {
+                completion(`protocol`.properties[URLProtocol._PropertyKey.responseData] as? Data ?? Data(), task.response, nil)
+                task.state = .completed
+                session.taskRegistry.remove(task)
+            }
         case .downloadCompletionHandler(let completion):
             session.delegateQueue.addOperation {
-                completion(task.currentRequest?.url, task.response, nil)
+                completion(`protocol`.properties[URLProtocol._PropertyKey.temporaryFileURL] as? URL, task.response, nil)
                 task.state = .completed
                 session.taskRegistry.remove(task)
             }
@@ -565,15 +579,15 @@
     }
 
     func urlProtocol(_ protocol: URLProtocol, didLoad data: Data) {
+        `protocol`.properties[.responseData] = 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
-                s.taskRegistry.remove(task)
+        case .taskDelegate(let delegate):
+            let dataDelegate = delegate as? URLSessionDataDelegate
+            let dataTask = task as? URLSessionDataTask
+            session.delegateQueue.addOperation {
+                dataDelegate?.urlSession(session, dataTask: dataTask!, didReceive: data)
             }
         default: return
         }
@@ -615,3 +629,10 @@
         NSUnimplemented()
     }
 }
+
+extension URLProtocol {
+    enum _PropertyKey: String {
+        case responseData
+        case temporaryFileURL
+    }
+}
diff --git a/Foundation/NSURLSession/http/EasyHandle.swift b/Foundation/NSURLSession/http/EasyHandle.swift
index 9cb2cad..2fe0ca1 100644
--- a/Foundation/NSURLSession/http/EasyHandle.swift
+++ b/Foundation/NSURLSession/http/EasyHandle.swift
@@ -240,9 +240,16 @@
         try! CFURLSession_easy_setopt_int64(rawHandle, CFURLSessionOptionINFILESIZE_LARGE, length).asError()
     }
 
-   func set(timeout value: Int) {
+    func set(timeout value: Int) {
        try! CFURLSession_easy_setopt_long(rawHandle, CFURLSessionOptionTIMEOUT, value).asError()
-   }
+    }
+
+    func getTimeoutIntervalSpent() -> Double {
+        var timeSpent = Double()
+        CFURLSession_easy_getinfo_double(rawHandle, CFURLSessionInfoTOTAL_TIME, &timeSpent)
+        return timeSpent / 1000
+    }
+
 }
 
 fileprivate func printLibcurlDebug(handle: CFURLSessionEasyHandle, type: CInt, data: UnsafeMutablePointer<Int8>, size: Int, userInfo: UnsafeMutableRawPointer?) -> CInt {
diff --git a/Foundation/NSURLSession/http/HTTPURLProtocol.swift b/Foundation/NSURLSession/http/HTTPURLProtocol.swift
index 39d030b..efb647b 100644
--- a/Foundation/NSURLSession/http/HTTPURLProtocol.swift
+++ b/Foundation/NSURLSession/http/HTTPURLProtocol.swift
@@ -679,10 +679,10 @@
             }
             self.client?.urlProtocol(self, didLoad: data)
             self.internalState = .taskCompleted
-            return
         }
 
-        if case .toFile(_, let fileHandle?) = bodyDataDrain {
+        if case .toFile(let url, let fileHandle?) = bodyDataDrain {
+            self.properties[.temporaryFileURL] = url
             fileHandle.closeFile()
         }
         self.client?.urlProtocolDidFinishLoading(self)
@@ -901,6 +901,8 @@
         components.path = targetURL.relativeString
         guard let urlString = components.string else { fatalError("Invalid URL") }
         request.url = URL(string: urlString)
+        let timeSpent = easyHandle.getTimeoutIntervalSpent()
+        request.timeoutInterval = fromRequest.timeoutInterval - timeSpent
 	return request
     }
 }
diff --git a/TestFoundation/TestLengthFormatter.swift b/TestFoundation/TestLengthFormatter.swift
index 1255a4f..05e7bcb 100644
--- a/TestFoundation/TestLengthFormatter.swift
+++ b/TestFoundation/TestLengthFormatter.swift
@@ -67,7 +67,7 @@
     }
     
     func test_stringFromMetersMetric() {
-        formatter.numberFormatter.locale = Locale(identifier: "en_UK")
+        formatter.numberFormatter.locale = Locale(identifier: "en_GB")
         XCTAssertEqual(formatter.string(fromMeters: -10000), "-10 km")
         XCTAssertEqual(formatter.string(fromMeters: -1), "-0.001 km")
         XCTAssertEqual(formatter.string(fromMeters: 0.00001), "0.01 mm")
@@ -85,7 +85,7 @@
     }
     
     func test_stringFromMetersMetricPersonHeight() {
-        formatter.numberFormatter.locale = Locale(identifier: "en_UK")
+        formatter.numberFormatter.locale = Locale(identifier: "en_GB")
         formatter.isForPersonHeightUse = true
         XCTAssertEqual(formatter.string(fromMeters: -1), "-100 cm")
         XCTAssertEqual(formatter.string(fromMeters: 0.001), "0.1 cm")
diff --git a/TestFoundation/TestNSTimer.swift b/TestFoundation/TestNSTimer.swift
index 7fcb31d..b80aede 100644
--- a/TestFoundation/TestNSTimer.swift
+++ b/TestFoundation/TestNSTimer.swift
@@ -59,7 +59,7 @@
             XCTAssertEqual(timer.timeInterval, interval)
 
             let currentInterval = Date().timeIntervalSince1970
-            XCTAssertEqual(currentInterval, previousInterval + interval, accuracy: 0.01)
+            XCTAssertEqual(currentInterval, previousInterval + interval, accuracy: 0.2)
             previousInterval = currentInterval
             
             flag += 1
diff --git a/TestFoundation/TestNSURLSession.swift b/TestFoundation/TestNSURLSession.swift
index 630b174..285ddd4 100644
--- a/TestFoundation/TestNSURLSession.swift
+++ b/TestFoundation/TestNSURLSession.swift
@@ -38,7 +38,9 @@
             ("test_verifyHttpAdditionalHeaders", test_verifyHttpAdditionalHeaders),
             ("test_timeoutInterval", test_timeoutInterval),
             ("test_customProtocol", test_customProtocol),
+	    ("test_customProtocolResponseWithDelegate", test_customProtocolResponseWithDelegate),
             ("test_httpRedirection", test_httpRedirection),
+            ("test_httpRedirectionTimeout", test_httpRedirectionTimeout),
         ]
     }
     
@@ -343,6 +345,14 @@
         task.resume()
         waitForExpectations(timeout: 12)
     }
+
+    func test_customProtocolResponseWithDelegate() {
+        let url = URL(string: "http://127.0.0.1:\(TestURLSession.serverPort)/Peru")!
+        let d = DataTask(with: expectation(description: "Custom protocol with delegate"), protocolClasses: [CustomProtocol.self])
+        d.responseReceivedExpectation = expectation(description: "A response wasn't received")
+        d.run(with: url)
+        waitForExpectations(timeout: 12)
+    }
     
     func test_httpRedirection() {
         let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/UnitedStates"
@@ -351,6 +361,23 @@
         d.run(with: url)
         waitForExpectations(timeout: 12)
     }
+
+    func test_httpRedirectionTimeout() {
+        var req = URLRequest(url: URL(string: "http://127.0.0.1:\(TestURLSession.serverPort)/UnitedStates")!)
+        req.timeoutInterval = 3
+        let config = URLSessionConfiguration.default
+        var expect = expectation(description: "download task with handler")
+        let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil)
+        let task = session.dataTask(with: req) { data, response, error in
+            defer { expect.fulfill() }
+            if let e = error as? URLError {
+                XCTAssertEqual(e.code, .timedOut, "Unexpected error code")
+                return
+            }
+        }
+        task.resume()
+        waitForExpectations(timeout: 12)
+    }
 }
 
 class SessionDelegate: NSObject, URLSessionDelegate {
@@ -369,16 +396,22 @@
     var session: URLSession! = nil
     var task: URLSessionDataTask! = nil
     var cancelExpectation: XCTestExpectation?
+    var responseReceivedExpectation: XCTestExpectation?
+    var protocols: [AnyClass]?
     
     public var error = false
     
-    init(with expectation: XCTestExpectation) {
+    init(with expectation: XCTestExpectation, protocolClasses: [AnyClass]? = nil) {
         dataTaskExpectation = expectation
+	protocols = protocolClasses
     }
     
     func run(with request: URLRequest) {
         let config = URLSessionConfiguration.default
         config.timeoutIntervalForRequest = 8
+        if let customProtocols = protocols {
+            config.protocolClasses = customProtocols
+        }
         session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
         task = session.dataTask(with: request)
         task.resume()
@@ -387,6 +420,9 @@
     func run(with url: URL) {
         let config = URLSessionConfiguration.default
         config.timeoutIntervalForRequest = 8
+        if let customProtocols = protocols {
+            config.protocolClasses = customProtocols
+        }
         session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
         task = session.dataTask(with: url)
         task.resume()
@@ -401,6 +437,14 @@
     public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
         capital = String(data: data, encoding: String.Encoding.utf8)!
     }
+
+    public func urlSession(_ session: URLSession,
+                    dataTask: URLSessionDataTask,
+                    didReceive response: URLResponse,
+                    completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
+        guard responseReceivedExpectation != nil else { return }
+        responseReceivedExpectation!.fulfill()
+    }
 }
 
 extension DataTask : URLSessionTaskDelegate {