From a4813cbdb785176deee0f1d3965167d892742359 Mon Sep 17 00:00:00 2001 From: Karl <5254025+karwa@users.noreply.github.com> Date: Wed, 16 Mar 2022 15:20:04 +0100 Subject: [PATCH 1/2] [Tests] Add new URLSession tests to allTests array --- Tests/Foundation/Tests/TestURLSession.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/Foundation/Tests/TestURLSession.swift b/Tests/Foundation/Tests/TestURLSession.swift index be82612c64..930bb7b549 100644 --- a/Tests/Foundation/Tests/TestURLSession.swift +++ b/Tests/Foundation/Tests/TestURLSession.swift @@ -1819,6 +1819,8 @@ class TestURLSession: LoopbackServerTest { ("test_taskError", test_taskError), ("test_taskCopy", test_taskCopy), ("test_cancelTask", test_cancelTask), + ("test_unhandledURLProtocol", test_unhandledURLProtocol), + ("test_requestToNilURL", test_requestToNilURL), /* ⚠️ */ ("test_suspendResumeTask", testExpectedToFail(test_suspendResumeTask, "Occasionally breaks")), ("test_taskTimeout", test_taskTimeout), ("test_verifyRequestHeaders", test_verifyRequestHeaders), From 17554b974dd681c528a5167be2af0c1cfff5f6f0 Mon Sep 17 00:00:00 2001 From: Karl <5254025+karwa@users.noreply.github.com> Date: Tue, 15 Mar 2022 18:39:15 +0100 Subject: [PATCH 2/2] [URLSession] Do not crash for unsupported URLs --- .../URLSession/URLSessionTask.swift | 14 ++++-- Tests/Foundation/Tests/TestURLSession.swift | 48 +++++++++++++++++++ 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/Sources/FoundationNetworking/URLSession/URLSessionTask.swift b/Sources/FoundationNetworking/URLSession/URLSessionTask.swift index 83146a2d33..699b799887 100644 --- a/Sources/FoundationNetworking/URLSession/URLSessionTask.swift +++ b/Sources/FoundationNetworking/URLSession/URLSessionTask.swift @@ -115,7 +115,7 @@ open class URLSessionTask : NSObject, NSCopying { fileprivate var _protocolStorage: ProtocolState = .toBeCreated internal var _lastCredentialUsedFromStorageDuringAuthentication: (protectionSpace: URLProtectionSpace, credential: URLCredential)? - private var _protocolClass: URLProtocol.Type { + private var _protocolClass: URLProtocol.Type? { guard let request = currentRequest else { fatalError("A protocol class was requested, but we do not have a current request") } let protocolClasses = session.configuration.protocolClasses ?? [] if let urlProtocolClass = URLProtocol.getProtocolClass(protocols: protocolClasses, request: request) { @@ -128,8 +128,7 @@ open class URLSessionTask : NSObject, NSCopying { return urlProtocol } } - - fatalError("Couldn't find a protocol appropriate for request: \(request)") + return nil } func _getProtocol(_ callback: @escaping (URLProtocol?) -> Void) { @@ -137,6 +136,11 @@ open class URLSessionTask : NSObject, NSCopying { switch _protocolStorage { case .toBeCreated: + guard let protocolClass = self._protocolClass else { + _protocolLock.unlock() // Balances above ⬆ + callback(nil) + break + } if let cache = session.configuration.urlCache, let me = self as? URLSessionDataTask { let bag: Bag<(URLProtocol?) -> Void> = Bag() bag.values.append(callback) @@ -145,11 +149,11 @@ open class URLSessionTask : NSObject, NSCopying { _protocolLock.unlock() // Balances above ⬆ cache.getCachedResponse(for: me) { (response) in - let urlProtocol = self._protocolClass.init(task: self, cachedResponse: response, client: nil) + let urlProtocol = protocolClass.init(task: self, cachedResponse: response, client: nil) self._satisfyProtocolRequest(with: urlProtocol) } } else { - let urlProtocol = _protocolClass.init(task: self, cachedResponse: nil, client: nil) + let urlProtocol = protocolClass.init(task: self, cachedResponse: nil, client: nil) _protocolStorage = .existing(urlProtocol) _protocolLock.unlock() // Balances above ⬆ diff --git a/Tests/Foundation/Tests/TestURLSession.swift b/Tests/Foundation/Tests/TestURLSession.swift index 930bb7b549..901ccea721 100644 --- a/Tests/Foundation/Tests/TestURLSession.swift +++ b/Tests/Foundation/Tests/TestURLSession.swift @@ -331,6 +331,54 @@ class TestURLSession: LoopbackServerTest { waitForExpectations(timeout: 12) } + func test_unhandledURLProtocol() { + let urlString = "foobar://127.0.0.1:\(TestURLSession.serverPort)/Nepal" + let url = URL(string: urlString)! + let session = URLSession(configuration: URLSessionConfiguration.default, + delegate: nil, + delegateQueue: nil) + let completionExpectation = expectation(description: "GET \(urlString): Unsupported URL error") + let task = session.dataTask(with: url) { (data, response, _error) in + XCTAssertNil(data) + XCTAssertNil(response) + let error = _error as? URLError + XCTAssertNotNil(error) + XCTAssertEqual(error?.code, .unsupportedURL) + completionExpectation.fulfill() + } + task.resume() + + waitForExpectations(timeout: 5) { error in + XCTAssertNil(error) + XCTAssertEqual((task.error as? URLError)?.code, .unsupportedURL) + } + } + + func test_requestToNilURL() { + let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/Nepal" + let url = URL(string: urlString)! + let session = URLSession(configuration: URLSessionConfiguration.default, + delegate: nil, + delegateQueue: nil) + let completionExpectation = expectation(description: "DataTask with nil URL: Unsupported URL error") + var request = URLRequest(url: url) + request.url = nil + let task = session.dataTask(with: request) { (data, response, _error) in + XCTAssertNil(data) + XCTAssertNil(response) + let error = _error as? URLError + XCTAssertNotNil(error) + XCTAssertEqual(error?.code, .unsupportedURL) + completionExpectation.fulfill() + } + task.resume() + + waitForExpectations(timeout: 5) { error in + XCTAssertNil(error) + XCTAssertEqual((task.error as? URLError)?.code, .unsupportedURL) + } + } + func test_suspendResumeTask() throws { let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/get" let url = try XCTUnwrap(URL(string: urlString))