From 5d206fb84272d9a0a00ddd8bd4bc5c26a05d73db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Wei=C3=9F?= Date: Wed, 5 Jul 2017 17:43:46 +0100 Subject: [PATCH] fix URLSession crashing on HTTP/0.9 simple-responses also add tests for misbehaving HTTP servers. --- .../NSURLSession/http/HTTPURLProtocol.swift | 10 +- TestFoundation/HTTPServer.swift | 77 ++++++++++++- TestFoundation/TestNSURLSession.swift | 106 ++++++++++++++++++ 3 files changed, 190 insertions(+), 3 deletions(-) diff --git a/Foundation/NSURLSession/http/HTTPURLProtocol.swift b/Foundation/NSURLSession/http/HTTPURLProtocol.swift index f6e8364944..0186bf2203 100644 --- a/Foundation/NSURLSession/http/HTTPURLProtocol.swift +++ b/Foundation/NSURLSession/http/HTTPURLProtocol.swift @@ -439,8 +439,14 @@ extension _HTTPURLProtocol { 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.") } + guard case .transferInProgress(var ts) = internalState else { fatalError("Received body data, but no transfer in progress.") } + if !ts.isHeaderComplete { + ts.response = HTTPURLResponse(url: ts.url, statusCode: 200, httpVersion: "HTTP/0.9", headerFields: [:]) + /* we received body data before CURL tells us that the headers are complete, that happens for HTTP/0.9 simple responses, see + - https://www.w3.org/Protocols/HTTP/1.0/spec.html#Message-Types + - https://github.com/curl/curl/issues/467 + */ + } notifyDelegate(aboutReceivedData: data) internalState = .transferInProgress(ts.byAppending(bodyData: data)) return .proceed diff --git a/TestFoundation/HTTPServer.swift b/TestFoundation/HTTPServer.swift index bec7079e9d..cf6c934f30 100644 --- a/TestFoundation/HTTPServer.swift +++ b/TestFoundation/HTTPServer.swift @@ -104,6 +104,12 @@ class _TCPSocket { return String(str[startIndex.. _HTTPResponse { diff --git a/TestFoundation/TestNSURLSession.swift b/TestFoundation/TestNSURLSession.swift index 285ddd4238..69ecec9088 100644 --- a/TestFoundation/TestNSURLSession.swift +++ b/TestFoundation/TestNSURLSession.swift @@ -41,6 +41,10 @@ class TestURLSession : XCTestCase { ("test_customProtocolResponseWithDelegate", test_customProtocolResponseWithDelegate), ("test_httpRedirection", test_httpRedirection), ("test_httpRedirectionTimeout", test_httpRedirectionTimeout), + ("test_http0_9SimpleResponses", test_http0_9SimpleResponses), + ("test_outOfRangeButCorrectlyFormattedHTTPCode", test_outOfRangeButCorrectlyFormattedHTTPCode), + ("test_missingContentLengthButStillABody", test_missingContentLengthButStillABody), + ("test_illegalHTTPServerResponses", test_illegalHTTPServerResponses), ] } @@ -378,6 +382,108 @@ class TestURLSession : XCTestCase { task.resume() waitForExpectations(timeout: 12) } + + func test_http0_9SimpleResponses() { + for brokenCity in ["Pompeii", "Sodom"] { + let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/LandOfTheLostCities/\(brokenCity)" + let url = URL(string: urlString)! + + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 8 + let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil) + let expect = expectation(description: "URL test with completion handler for \(brokenCity)") + var expectedResult = "unknown" + let task = session.dataTask(with: url) { data, response, error in + XCTAssertNotNil(data) + XCTAssertNotNil(response) + XCTAssertNil(error) + + defer { expect.fulfill() } + + guard let httpResponse = response as? HTTPURLResponse else { + XCTFail("response (\(response.debugDescription)) invalid") + return + } + XCTAssertEqual(200, httpResponse.statusCode, "HTTP response code is not 200") + } + task.resume() + waitForExpectations(timeout: 12) + } + } + + func test_outOfRangeButCorrectlyFormattedHTTPCode() { + let brokenCity = "Kameiros" + let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/LandOfTheLostCities/\(brokenCity)" + let url = URL(string: urlString)! + + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 8 + let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil) + let expect = expectation(description: "URL test with completion handler for \(brokenCity)") + let task = session.dataTask(with: url) { data, response, error in + XCTAssertNotNil(data) + XCTAssertNotNil(response) + XCTAssertNil(error) + + defer { expect.fulfill() } + + guard let httpResponse = response as? HTTPURLResponse else { + XCTFail("response (\(response.debugDescription)) invalid") + return + } + XCTAssertEqual(999, httpResponse.statusCode, "HTTP response code is not 999") + } + task.resume() + waitForExpectations(timeout: 12) + } + + func test_missingContentLengthButStillABody() { + let brokenCity = "Myndus" + let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/LandOfTheLostCities/\(brokenCity)" + let url = URL(string: urlString)! + + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 8 + let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil) + let expect = expectation(description: "URL test with completion handler for \(brokenCity)") + let task = session.dataTask(with: url) { data, response, error in + XCTAssertNotNil(data) + XCTAssertNotNil(response) + XCTAssertNil(error) + + defer { expect.fulfill() } + + guard let httpResponse = response as? HTTPURLResponse else { + XCTFail("response (\(response.debugDescription)) invalid") + return + } + XCTAssertEqual(200, httpResponse.statusCode, "HTTP response code is not 200") + } + task.resume() + waitForExpectations(timeout: 12) + } + + + func test_illegalHTTPServerResponses() { + for brokenCity in ["Gomorrah", "Dinavar", "Kuhikugu"] { + let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/LandOfTheLostCities/\(brokenCity)" + let url = URL(string: urlString)! + + let config = URLSessionConfiguration.default + config.timeoutIntervalForRequest = 8 + let session = URLSession(configuration: config, delegate: nil, delegateQueue: nil) + let expect = expectation(description: "URL test with completion handler for \(brokenCity)") + let task = session.dataTask(with: url) { data, response, error in + XCTAssertNil(data) + XCTAssertNil(response) + XCTAssertNotNil(error) + + defer { expect.fulfill() } + } + task.resume() + waitForExpectations(timeout: 12) + } + } } class SessionDelegate: NSObject, URLSessionDelegate {