diff --git a/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests+XCTest.swift index b979690d3..e272eb8c1 100644 --- a/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests+XCTest.swift @@ -34,6 +34,8 @@ extension HTTP1ConnectionTests { ("testConnectionClosesAfterTheRequestWithoutHavingSentAnCloseHeader", testConnectionClosesAfterTheRequestWithoutHavingSentAnCloseHeader), ("testConnectionIsClosedAfterSwitchingProtocols", testConnectionIsClosedAfterSwitchingProtocols), ("testConnectionDoesntCrashAfterConnectionCloseAndEarlyHints", testConnectionDoesntCrashAfterConnectionCloseAndEarlyHints), + ("testConnectionIsClosedIfResponseIsReceivedBeforeRequest", testConnectionIsClosedIfResponseIsReceivedBeforeRequest), + ("testDoubleHTTPResponseLine", testDoubleHTTPResponseLine), ("testDownloadStreamingBackpressure", testDownloadStreamingBackpressure), ] } diff --git a/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests.swift b/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests.swift index 825a65d89..4638b8b5a 100644 --- a/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP1ConnectionTests.swift @@ -490,6 +490,93 @@ class HTTP1ConnectionTests: XCTestCase { } } + func testConnectionIsClosedIfResponseIsReceivedBeforeRequest() { + let embedded = EmbeddedChannel() + let logger = Logger(label: "test.http1.connection") + + XCTAssertNoThrow(try embedded.connect(to: SocketAddress(ipAddress: "127.0.0.1", port: 0)).wait()) + + let connectionDelegate = MockConnectionDelegate() + XCTAssertNoThrow(try HTTP1Connection.start( + channel: embedded, + connectionID: 0, + delegate: connectionDelegate, + configuration: .init(decompression: .enabled(limit: .ratio(4))), + logger: logger + )) + + let responseString = """ + HTTP/1.1 200 OK\r\n\ + date: Mon, 27 Sep 2021 17:53:14 GMT\r\n\ + \r\n\ + \r\n + """ + + XCTAssertEqual(connectionDelegate.hitConnectionClosed, 0) + XCTAssertEqual(connectionDelegate.hitConnectionReleased, 0) + + XCTAssertThrowsError(try embedded.writeInbound(ByteBuffer(string: responseString))) { + XCTAssertEqual($0 as? NIOHTTPDecoderError, .unsolicitedResponse) + } + XCTAssertFalse(embedded.isActive) + (embedded.eventLoop as! EmbeddedEventLoop).run() // tick once to run futures. + XCTAssertEqual(connectionDelegate.hitConnectionClosed, 1) + XCTAssertEqual(connectionDelegate.hitConnectionReleased, 0) + } + + func testDoubleHTTPResponseLine() { + let embedded = EmbeddedChannel() + let logger = Logger(label: "test.http1.connection") + + XCTAssertNoThrow(try embedded.connect(to: SocketAddress(ipAddress: "127.0.0.1", port: 0)).wait()) + + var maybeConnection: HTTP1Connection? + let connectionDelegate = MockConnectionDelegate() + XCTAssertNoThrow(maybeConnection = try HTTP1Connection.start( + channel: embedded, + connectionID: 0, + delegate: connectionDelegate, + configuration: .init(decompression: .enabled(limit: .ratio(4))), + logger: logger + )) + guard let connection = maybeConnection else { return XCTFail("Expected to have a connection at this point.") } + + var maybeRequest: HTTPClient.Request? + XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(url: "http://swift.org/")) + guard let request = maybeRequest else { return XCTFail("Expected to be able to create a request") } + + let delegate = ResponseAccumulator(request: request) + var maybeRequestBag: RequestBag? + XCTAssertNoThrow(maybeRequestBag = try RequestBag( + request: request, + eventLoopPreference: .delegate(on: embedded.eventLoop), + task: .init(eventLoop: embedded.eventLoop, logger: logger), + redirectHandler: nil, + connectionDeadline: .now() + .seconds(30), + requestOptions: .forTests(), + delegate: delegate + )) + guard let requestBag = maybeRequestBag else { return XCTFail("Expected to be able to create a request bag") } + + connection.executeRequest(requestBag) + + let responseString = """ + HTTP/1.0 200 OK\r\n\ + HTTP/1.0 200 OK\r\n\r\n + """ + + XCTAssertNoThrow(try embedded.readOutbound(as: ByteBuffer.self)) // head + XCTAssertNoThrow(try embedded.readOutbound(as: ByteBuffer.self)) // end + + XCTAssertEqual(connectionDelegate.hitConnectionClosed, 0) + XCTAssertEqual(connectionDelegate.hitConnectionReleased, 0) + XCTAssertNoThrow(try embedded.writeInbound(ByteBuffer(string: responseString))) + XCTAssertFalse(embedded.isActive) + (embedded.eventLoop as! EmbeddedEventLoop).run() // tick once to run futures. + XCTAssertEqual(connectionDelegate.hitConnectionClosed, 1) + XCTAssertEqual(connectionDelegate.hitConnectionReleased, 0) + } + // In order to test backpressure we need to make sure that reads will not happen // until the backpressure promise is succeeded. Since we cannot guarantee when // messages will be delivered to a client pipeline and we need this test to be diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift index dc9c1f701..8664c6520 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift @@ -26,11 +26,7 @@ extension HTTPClientInternalTests { static var allTests: [(String, (HTTPClientInternalTests) -> () throws -> Void)] { return [ ("testHTTPPartsHandler", testHTTPPartsHandler), - ("testBadHTTPRequest", testBadHTTPRequest), - ("testHostPort", testHostPort), ("testHTTPPartsHandlerMultiBody", testHTTPPartsHandlerMultiBody), - ("testHTTPResponseHeadBeforeRequestHead", testHTTPResponseHeadBeforeRequestHead), - ("testHTTPResponseDoubleHead", testHTTPResponseDoubleHead), ("testRequestFinishesAfterRedirectIfServerRespondsBeforeClientFinishes", testRequestFinishesAfterRedirectIfServerRespondsBeforeClientFinishes), ("testProxyStreaming", testProxyStreaming), ("testProxyStreamingFailure", testProxyStreamingFailure), diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift index 2c7230e58..94a9dd129 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift @@ -75,65 +75,6 @@ class HTTPClientInternalTests: XCTestCase { XCTAssertNoThrow(try channel.writeInbound(HTTPClientResponsePart.end(nil))) } - func testBadHTTPRequest() throws { - let channel = EmbeddedChannel() - let recorder = RecordingHandler() - let task = Task(eventLoop: channel.eventLoop, logger: HTTPClient.loggingDisabled) - - XCTAssertNoThrow(try channel.pipeline.addHandler(recorder).wait()) - XCTAssertNoThrow(try channel.pipeline.addHandler(TaskHandler(task: task, - kind: .host, - delegate: TestHTTPDelegate(), - redirectHandler: nil, - ignoreUncleanSSLShutdown: false, - logger: HTTPClient.loggingDisabled)).wait()) - - var request = try Request(url: "http://localhost/get") - request.headers.add(name: "X-Test-Header", value: "X-Test-Value") - request.headers.add(name: "Transfer-Encoding", value: "identity") - request.body = .string("1234") - - XCTAssertThrowsError(try channel.writeOutbound(request)) { error in - XCTAssertEqual(HTTPClientError.identityCodingIncorrectlyPresent, error as? HTTPClientError) - } - } - - func testHostPort() throws { - let channel = EmbeddedChannel() - let recorder = RecordingHandler() - let task = Task(eventLoop: channel.eventLoop, logger: HTTPClient.loggingDisabled) - - try channel.pipeline.addHandler(recorder).wait() - try channel.pipeline.addHandler(TaskHandler(task: task, - kind: .host, - delegate: TestHTTPDelegate(), - redirectHandler: nil, - ignoreUncleanSSLShutdown: false, - logger: HTTPClient.loggingDisabled)).wait() - - let request1 = try Request(url: "http://localhost:80/get") - XCTAssertNoThrow(try channel.writeOutbound(request1)) - let request2 = try Request(url: "https://localhost/get") - XCTAssertNoThrow(try channel.writeOutbound(request2)) - let request3 = try Request(url: "http://localhost:8080/get") - XCTAssertNoThrow(try channel.writeOutbound(request3)) - let request4 = try Request(url: "http://localhost:443/get") - XCTAssertNoThrow(try channel.writeOutbound(request4)) - let request5 = try Request(url: "https://localhost:80/get") - XCTAssertNoThrow(try channel.writeOutbound(request5)) - - let head1 = HTTPRequestHead(version: HTTPVersion(major: 1, minor: 1), method: .GET, uri: "/get", headers: ["host": "localhost"]) - XCTAssertEqual(HTTPClientRequestPart.head(head1), recorder.writes[0]) - let head2 = HTTPRequestHead(version: HTTPVersion(major: 1, minor: 1), method: .GET, uri: "/get", headers: ["host": "localhost"]) - XCTAssertEqual(HTTPClientRequestPart.head(head2), recorder.writes[2]) - let head3 = HTTPRequestHead(version: HTTPVersion(major: 1, minor: 1), method: .GET, uri: "/get", headers: ["host": "localhost:8080"]) - XCTAssertEqual(HTTPClientRequestPart.head(head3), recorder.writes[4]) - let head4 = HTTPRequestHead(version: HTTPVersion(major: 1, minor: 1), method: .GET, uri: "/get", headers: ["host": "localhost:443"]) - XCTAssertEqual(HTTPClientRequestPart.head(head4), recorder.writes[6]) - let head5 = HTTPRequestHead(version: HTTPVersion(major: 1, minor: 1), method: .GET, uri: "/get", headers: ["host": "localhost:80"]) - XCTAssertEqual(HTTPClientRequestPart.head(head5), recorder.writes[8]) - } - func testHTTPPartsHandlerMultiBody() throws { let channel = EmbeddedChannel() let delegate = TestHTTPDelegate() @@ -163,55 +104,6 @@ class HTTPClientInternalTests: XCTestCase { } } - func testHTTPResponseHeadBeforeRequestHead() throws { - let channel = EmbeddedChannel() - XCTAssertNoThrow(try channel.connect(to: try SocketAddress(unixDomainSocketPath: "/fake")).wait()) - - let delegate = TestHTTPDelegate() - let task = Task(eventLoop: channel.eventLoop, logger: HTTPClient.loggingDisabled) - let handler = TaskHandler(task: task, - kind: .host, - delegate: delegate, - redirectHandler: nil, - ignoreUncleanSSLShutdown: false, - logger: HTTPClient.loggingDisabled) - - XCTAssertNoThrow(try channel.pipeline.addHTTPClientHandlers().wait()) - XCTAssertNoThrow(try channel.pipeline.addHandler(handler).wait()) - - XCTAssertNoThrow(try channel.writeInbound(ByteBuffer(string: "HTTP/1.0 200 OK\r\n\r\n"))) - - XCTAssertThrowsError(try task.futureResult.wait()) { error in - XCTAssertEqual(error as? NIOHTTPDecoderError, NIOHTTPDecoderError.unsolicitedResponse) - } - } - - func testHTTPResponseDoubleHead() throws { - let channel = EmbeddedChannel() - XCTAssertNoThrow(try channel.connect(to: try SocketAddress(unixDomainSocketPath: "/fake")).wait()) - - let delegate = TestHTTPDelegate() - let task = Task(eventLoop: channel.eventLoop, logger: HTTPClient.loggingDisabled) - let handler = TaskHandler(task: task, - kind: .host, - delegate: delegate, - redirectHandler: nil, - ignoreUncleanSSLShutdown: false, - logger: HTTPClient.loggingDisabled) - - XCTAssertNoThrow(try channel.pipeline.addHTTPClientHandlers().wait()) - XCTAssertNoThrow(try channel.pipeline.addHandler(handler).wait()) - - let request = try HTTPClient.Request(url: "http://localhost/get") - XCTAssertNoThrow(try channel.writeOutbound(request)) - - XCTAssertNoThrow(try channel.writeInbound(ByteBuffer(string: "HTTP/1.0 200 OK\r\nHTTP/1.0 200 OK\r\n\r\n"))) - - XCTAssertThrowsError(try task.futureResult.wait()) { error in - XCTAssertEqual((error as? HTTPParserError)?.debugDescription, "invalid character in header") - } - } - func testRequestFinishesAfterRedirectIfServerRespondsBeforeClientFinishes() throws { let channel = EmbeddedChannel() diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift index 2ab8856bf..daf06ad88 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift @@ -136,6 +136,7 @@ extension HTTPClientTests { ("testErrorAfterCloseWhileBackpressureExerted", testErrorAfterCloseWhileBackpressureExerted), ("testRequestSpecificTLS", testRequestSpecificTLS), ("testConnectionPoolSizeConfigValueIsRespected", testConnectionPoolSizeConfigValueIsRespected), + ("testRequestWithHeaderTransferEncodingIdentityFails", testRequestWithHeaderTransferEncodingIdentityFails), ] } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 3311ffba7..2a9a21dd8 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -3103,4 +3103,23 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(httpBin.createdConnections, poolSize) } + + func testRequestWithHeaderTransferEncodingIdentityFails() { + let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } + + let client = HTTPClient(eventLoopGroupProvider: .shared(group)) + defer { XCTAssertNoThrow(try client.syncShutdown()) } + + guard var request = try? Request(url: "http://localhost/get") else { + return XCTFail("Expected to have a request here.") + } + request.headers.add(name: "X-Test-Header", value: "X-Test-Value") + request.headers.add(name: "Transfer-Encoding", value: "identity") + request.body = .string("1234") + + XCTAssertThrowsError(try client.execute(request: request).wait()) { + XCTAssertEqual($0 as? HTTPClientError, .identityCodingIncorrectlyPresent) + } + } } diff --git a/Tests/AsyncHTTPClientTests/RequestValidationTests+XCTest.swift b/Tests/AsyncHTTPClientTests/RequestValidationTests+XCTest.swift index 7a21dffdd..839a8dcc2 100644 --- a/Tests/AsyncHTTPClientTests/RequestValidationTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/RequestValidationTests+XCTest.swift @@ -42,6 +42,7 @@ extension RequestValidationTests { ("testTransferEncodingHeaderHasBody", testTransferEncodingHeaderHasBody), ("testBothHeadersNoBody", testBothHeadersNoBody), ("testBothHeadersHasBody", testBothHeadersHasBody), + ("testHostHeaderIsSetCorrectlyInCreateRequestHead", testHostHeaderIsSetCorrectlyInCreateRequestHead), ] } } diff --git a/Tests/AsyncHTTPClientTests/RequestValidationTests.swift b/Tests/AsyncHTTPClientTests/RequestValidationTests.swift index 61ccd9b4e..c5610cf9e 100644 --- a/Tests/AsyncHTTPClientTests/RequestValidationTests.swift +++ b/Tests/AsyncHTTPClientTests/RequestValidationTests.swift @@ -316,4 +316,24 @@ class RequestValidationTests: XCTestCase { XCTAssertThrowsError(try headers.validate(method: method, body: .byteBuffer(ByteBuffer(bytes: [0])))) } } + + func testHostHeaderIsSetCorrectlyInCreateRequestHead() { + let req1 = try! HTTPClient.Request(url: "http://localhost:80/get") + XCTAssertEqual(try req1.createRequestHead().0.headers["host"].first, "localhost") + + let req2 = try! HTTPClient.Request(url: "https://localhost/get") + XCTAssertEqual(try req2.createRequestHead().0.headers["host"].first, "localhost") + + let req3 = try! HTTPClient.Request(url: "http://localhost:8080/get") + XCTAssertEqual(try req3.createRequestHead().0.headers["host"].first, "localhost:8080") + + let req4 = try! HTTPClient.Request(url: "http://localhost:443/get") + XCTAssertEqual(try req4.createRequestHead().0.headers["host"].first, "localhost:443") + + let req5 = try! HTTPClient.Request(url: "https://localhost:80/get") + XCTAssertEqual(try req5.createRequestHead().0.headers["host"].first, "localhost:80") + + let req6 = try! HTTPClient.Request(url: "https://localhost/get", headers: ["host": "foo"]) + XCTAssertEqual(try req6.createRequestHead().0.headers["host"].first, "foo") + } }