2018-04-12 22:12:39 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
//
|
|
|
|
// This source file is part of the SwiftNIO open source project
|
|
|
|
//
|
2021-08-12 20:49:46 +08:00
|
|
|
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
|
2018-04-12 22:12:39 +08:00
|
|
|
// Licensed under Apache License v2.0
|
|
|
|
//
|
|
|
|
// See LICENSE.txt for license information
|
|
|
|
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
import XCTest
|
2021-08-12 20:49:46 +08:00
|
|
|
import NIOCore
|
|
|
|
import NIOEmbedded
|
2018-04-12 22:12:39 +08:00
|
|
|
import NIOHTTP1
|
|
|
|
|
|
|
|
private class MessageEndHandler<Head: Equatable, Body: Equatable>: ChannelInboundHandler {
|
|
|
|
typealias InboundIn = HTTPPart<Head, Body>
|
|
|
|
|
|
|
|
var seenEnd = false
|
|
|
|
var seenBody = false
|
|
|
|
var seenHead = false
|
|
|
|
|
2019-02-26 02:20:22 +08:00
|
|
|
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
2018-04-12 22:12:39 +08:00
|
|
|
switch self.unwrapInboundIn(data) {
|
|
|
|
case .head:
|
|
|
|
XCTAssertFalse(self.seenHead)
|
|
|
|
self.seenHead = true
|
|
|
|
case .body:
|
|
|
|
XCTAssertFalse(self.seenBody)
|
|
|
|
self.seenBody = true
|
|
|
|
case .end:
|
|
|
|
XCTAssertFalse(self.seenEnd)
|
|
|
|
self.seenEnd = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Tests for the HTTP decoder's handling of message body framing.
|
|
|
|
///
|
|
|
|
/// Mostly tests assertions in [RFC 7230 § 3.3.3](https://tools.ietf.org/html/rfc7230#section-3.3.3).
|
|
|
|
class HTTPDecoderLengthTest: XCTestCase {
|
|
|
|
private var channel: EmbeddedChannel!
|
2019-11-28 03:25:06 +08:00
|
|
|
private var loop: EmbeddedEventLoop {
|
|
|
|
return self.channel.embeddedEventLoop
|
|
|
|
}
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
override func setUp() {
|
|
|
|
self.channel = EmbeddedChannel()
|
|
|
|
}
|
|
|
|
|
|
|
|
override func tearDown() {
|
2019-11-28 03:25:06 +08:00
|
|
|
XCTAssertNoThrow(try self.channel?.finish(acceptAlreadyClosed: true))
|
2018-04-12 22:12:39 +08:00
|
|
|
self.channel = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The mechanism by which EOF is being sent.
|
|
|
|
enum EOFMechanism {
|
|
|
|
case channelInactive
|
|
|
|
case halfClosure
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The various header fields that can be used to frame a response.
|
|
|
|
enum FramingField {
|
|
|
|
case contentLength
|
|
|
|
case transferEncoding
|
|
|
|
case neither
|
|
|
|
}
|
|
|
|
|
|
|
|
private func assertSemanticEOFOnChannelInactiveResponse(version: HTTPVersion, how eofMechanism: EOFMechanism) throws {
|
|
|
|
class ChannelInactiveHandler: ChannelInboundHandler {
|
|
|
|
typealias InboundIn = HTTPClientResponsePart
|
|
|
|
var response: HTTPResponseHead?
|
|
|
|
var receivedEnd = false
|
|
|
|
var eof = false
|
|
|
|
var body: [UInt8]?
|
|
|
|
private let eofMechanism: EOFMechanism
|
|
|
|
|
|
|
|
init(_ eofMechanism: EOFMechanism) {
|
|
|
|
self.eofMechanism = eofMechanism
|
|
|
|
}
|
|
|
|
|
2019-02-26 02:20:22 +08:00
|
|
|
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
2018-04-12 22:12:39 +08:00
|
|
|
switch self.unwrapInboundIn(data) {
|
|
|
|
case .head(let h):
|
|
|
|
self.response = h
|
|
|
|
case .end:
|
|
|
|
self.receivedEnd = true
|
|
|
|
case .body(var b):
|
|
|
|
XCTAssertNil(self.body)
|
|
|
|
self.body = b.readBytes(length: b.readableBytes)!
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-26 02:20:22 +08:00
|
|
|
func channelInactive(context: ChannelHandlerContext) {
|
2018-04-12 22:12:39 +08:00
|
|
|
if case .channelInactive = self.eofMechanism {
|
|
|
|
XCTAssert(self.receivedEnd, "Received channelInactive before response end!")
|
|
|
|
self.eof = true
|
|
|
|
} else {
|
|
|
|
XCTAssert(self.eof, "Did not receive .inputClosed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-26 02:20:22 +08:00
|
|
|
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
|
2018-04-12 22:12:39 +08:00
|
|
|
guard case .halfClosure = self.eofMechanism else {
|
|
|
|
XCTFail("Got half closure when not expecting it")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let evt = event as? ChannelEvent, case .inputClosed = evt else {
|
2019-02-26 02:20:22 +08:00
|
|
|
context.fireUserInboundEventTriggered(event)
|
2018-04-12 22:12:39 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
XCTAssert(self.receivedEnd, "Received inputClosed before response end!")
|
|
|
|
self.eof = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-21 19:46:54 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(HTTPRequestEncoder()).wait())
|
2019-03-06 19:51:42 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(HTTPResponseDecoder())).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
let handler = ChannelInactiveHandler(eofMechanism)
|
2019-02-21 19:46:54 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(handler).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// Prime the decoder with a GET and consume it.
|
2019-03-22 20:34:16 +08:00
|
|
|
XCTAssertTrue(try channel.writeOutbound(HTTPClientRequestPart.head(HTTPRequestHead(version: version, method: .GET, uri: "/"))).isFull)
|
2019-03-04 17:40:59 +08:00
|
|
|
XCTAssertNoThrow(XCTAssertNotNil(try channel.readOutbound(as: ByteBuffer.self)))
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// We now want to send a HTTP/1.1 response. This response has no content-length, no transfer-encoding,
|
|
|
|
// is not a response to a HEAD request, is not a 2XX response to CONNECT, and is not 1XX, 204, or 304.
|
2018-05-08 14:42:59 +08:00
|
|
|
// That means, per RFC 7230 § 3.3.3, the body is framed by EOF. Because this is a response, that EOF
|
2018-04-12 22:12:39 +08:00
|
|
|
// may be transmitted by channelInactive.
|
|
|
|
let response = "HTTP/\(version.major).\(version.minor) 200 OK\r\nServer: example\r\n\r\n"
|
2020-06-05 04:02:11 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeInbound(IOData.byteBuffer(channel.allocator.buffer(string: response))))
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// We should have a response but no body.
|
|
|
|
XCTAssertNotNil(handler.response)
|
|
|
|
XCTAssertNil(handler.body)
|
|
|
|
XCTAssertFalse(handler.receivedEnd)
|
|
|
|
XCTAssertFalse(handler.eof)
|
|
|
|
|
|
|
|
// Send a body chunk. This should be immediately passed on. Still no end or EOF.
|
2020-06-05 04:02:11 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeInbound(IOData.byteBuffer(channel.allocator.buffer(string: "some body data"))))
|
2018-04-12 22:12:39 +08:00
|
|
|
XCTAssertNotNil(handler.response)
|
|
|
|
XCTAssertEqual(handler.body!, Array("some body data".utf8))
|
|
|
|
XCTAssertFalse(handler.receivedEnd)
|
|
|
|
XCTAssertFalse(handler.eof)
|
|
|
|
|
|
|
|
// Now we send EOF. This should cause a response end. The handler will enforce ordering.
|
|
|
|
if case .halfClosure = eofMechanism {
|
|
|
|
channel.pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
|
|
|
|
} else {
|
|
|
|
channel.pipeline.fireChannelInactive()
|
|
|
|
}
|
|
|
|
XCTAssertNotNil(handler.response)
|
|
|
|
XCTAssertEqual(handler.body!, Array("some body data".utf8))
|
|
|
|
XCTAssertTrue(handler.receivedEnd)
|
|
|
|
XCTAssertTrue(handler.eof)
|
|
|
|
|
2019-03-22 20:34:16 +08:00
|
|
|
XCTAssertTrue(try channel.finish().isClean)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testHTTP11SemanticEOFOnChannelInactive() throws {
|
2021-01-20 01:27:02 +08:00
|
|
|
try assertSemanticEOFOnChannelInactiveResponse(version: .http1_1, how: .channelInactive)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testHTTP10SemanticEOFOnChannelInactive() throws {
|
2021-01-20 01:27:02 +08:00
|
|
|
try assertSemanticEOFOnChannelInactiveResponse(version: .http1_0, how: .channelInactive)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testHTTP11SemanticEOFOnHalfClosure() throws {
|
2021-01-20 01:27:02 +08:00
|
|
|
try assertSemanticEOFOnChannelInactiveResponse(version: .http1_1, how: .halfClosure)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testHTTP10SemanticEOFOnHalfClosure() throws {
|
2021-01-20 01:27:02 +08:00
|
|
|
try assertSemanticEOFOnChannelInactiveResponse(version: .http1_0, how: .halfClosure)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
private func assertIgnoresLengthFields(requestMethod: HTTPMethod,
|
|
|
|
responseStatus: HTTPResponseStatus,
|
|
|
|
responseFramingField: FramingField) throws {
|
2019-02-21 19:46:54 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(HTTPRequestEncoder()).wait())
|
2021-11-09 02:41:08 +08:00
|
|
|
let decoder = HTTPResponseDecoder(leftOverBytesStrategy: .dropBytes, informationalResponseStrategy: .forward)
|
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(decoder)).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
let handler = MessageEndHandler<HTTPResponseHead, ByteBuffer>()
|
2019-02-21 19:46:54 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(handler).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// Prime the decoder with a request and consume it.
|
2021-01-20 01:27:02 +08:00
|
|
|
XCTAssertTrue(try channel.writeOutbound(HTTPClientRequestPart.head(HTTPRequestHead(version: .http1_1,
|
2018-04-12 22:12:39 +08:00
|
|
|
method: requestMethod,
|
2019-03-22 20:34:16 +08:00
|
|
|
uri: "/"))).isFull)
|
2019-03-04 17:40:59 +08:00
|
|
|
XCTAssertNoThrow(XCTAssertNotNil(try channel.readOutbound(as: ByteBuffer.self)))
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// We now want to send a HTTP/1.1 response. This response may contain some length framing fields that RFC 7230 says MUST
|
|
|
|
// be ignored.
|
|
|
|
var response = channel.allocator.buffer(capacity: 256)
|
2019-02-12 19:11:45 +08:00
|
|
|
response.writeString("HTTP/1.1 \(responseStatus.code) \(responseStatus.reasonPhrase)\r\nServer: example\r\n")
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
switch responseFramingField {
|
|
|
|
case .contentLength:
|
2019-02-12 19:11:45 +08:00
|
|
|
response.writeStaticString("Content-Length: 16\r\n")
|
2018-04-12 22:12:39 +08:00
|
|
|
case .transferEncoding:
|
2019-02-12 19:11:45 +08:00
|
|
|
response.writeStaticString("Transfer-Encoding: chunked\r\n")
|
2018-04-12 22:12:39 +08:00
|
|
|
case .neither:
|
|
|
|
break
|
|
|
|
}
|
2019-02-12 19:11:45 +08:00
|
|
|
response.writeStaticString("\r\n")
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
XCTAssertNoThrow(try channel.writeInbound(IOData.byteBuffer(response)))
|
|
|
|
|
|
|
|
// We should have a response, no body, and immediately see EOF.
|
|
|
|
XCTAssert(handler.seenHead)
|
2021-11-09 02:41:08 +08:00
|
|
|
switch responseStatus.code {
|
|
|
|
case 100, 102..<200:
|
|
|
|
// If an informational response header is tested, we expect another "real" header to
|
|
|
|
// follow. For this reason, we don't expect an `.end` here.
|
|
|
|
XCTAssertFalse(handler.seenBody)
|
|
|
|
XCTAssertFalse(handler.seenEnd)
|
|
|
|
|
|
|
|
default:
|
|
|
|
XCTAssertFalse(handler.seenBody)
|
|
|
|
XCTAssert(handler.seenEnd)
|
|
|
|
}
|
|
|
|
|
2019-03-22 20:34:16 +08:00
|
|
|
XCTAssertTrue(try channel.finish().isClean)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testIgnoresTransferEncodingFieldOnCONNECTResponses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .CONNECT, responseStatus: .ok, responseFramingField: .transferEncoding)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testIgnoresContentLengthFieldOnCONNECTResponses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .CONNECT, responseStatus: .ok, responseFramingField: .contentLength)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testEarlyFinishWithoutLengthAtAllOnCONNECTResponses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .CONNECT, responseStatus: .ok, responseFramingField: .neither)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testIgnoresTransferEncodingFieldOnHEADResponses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .HEAD, responseStatus: .ok, responseFramingField: .transferEncoding)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testIgnoresContentLengthFieldOnHEADResponses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .HEAD, responseStatus: .ok, responseFramingField: .contentLength)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testEarlyFinishWithoutLengthAtAllOnHEADResponses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .HEAD, responseStatus: .ok, responseFramingField: .neither)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testIgnoresTransferEncodingFieldOn1XXResponses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .GET,
|
|
|
|
responseStatus: .custom(code: 103, reasonPhrase: "Early Hints"),
|
|
|
|
responseFramingField: .transferEncoding)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testIgnoresContentLengthFieldOn1XXResponses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .GET,
|
|
|
|
responseStatus: .custom(code: 103, reasonPhrase: "Early Hints"),
|
|
|
|
responseFramingField: .contentLength)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testEarlyFinishWithoutLengthAtAllOn1XXResponses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .GET,
|
|
|
|
responseStatus: .custom(code: 103, reasonPhrase: "Early Hints"),
|
|
|
|
responseFramingField: .neither)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testIgnoresTransferEncodingFieldOn204Responses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .GET, responseStatus: .noContent, responseFramingField: .transferEncoding)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testIgnoresContentLengthFieldOn204Responses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .GET, responseStatus: .noContent, responseFramingField: .contentLength)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testEarlyFinishWithoutLengthAtAllOn204Responses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .GET, responseStatus: .noContent, responseFramingField: .neither)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testIgnoresTransferEncodingFieldOn304Responses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .GET, responseStatus: .notModified, responseFramingField: .transferEncoding)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testIgnoresContentLengthFieldOn304Responses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .GET, responseStatus: .notModified, responseFramingField: .contentLength)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testEarlyFinishWithoutLengthAtAllOn304Responses() throws {
|
|
|
|
try assertIgnoresLengthFields(requestMethod: .GET, responseStatus: .notModified, responseFramingField: .neither)
|
|
|
|
}
|
|
|
|
|
2020-02-11 00:44:44 +08:00
|
|
|
private func assertRequestTransferEncodingInError(transferEncodingHeader: String) throws {
|
2019-03-06 19:51:42 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder())).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
let handler = MessageEndHandler<HTTPRequestHead, ByteBuffer>()
|
2019-02-21 19:46:54 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(handler).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// Send a GET with the appropriate Transfer Encoding header.
|
2020-06-05 04:02:11 +08:00
|
|
|
XCTAssertThrowsError(try channel.writeInbound(channel.allocator.buffer(string: "POST / HTTP/1.1\r\nTransfer-Encoding: \(transferEncodingHeader)\r\n\r\n"))) { error in
|
2020-02-11 00:44:44 +08:00
|
|
|
XCTAssertEqual(error as? HTTPParserError, .unknown)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func testMultipleTEWithChunkedLastWorksFine() throws {
|
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder())).wait())
|
|
|
|
|
|
|
|
let handler = MessageEndHandler<HTTPRequestHead, ByteBuffer>()
|
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(handler).wait())
|
|
|
|
|
|
|
|
// Send a GET with the appropriate Transfer Encoding header.
|
2020-06-05 04:02:11 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeInbound(channel.allocator.buffer(string: "POST / HTTP/1.1\r\nTransfer-Encoding: gzip, chunked\r\n\r\n0\r\n\r\n")))
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// We should have a request, no body, and immediately see end of request.
|
|
|
|
XCTAssert(handler.seenHead)
|
|
|
|
XCTAssertFalse(handler.seenBody)
|
|
|
|
XCTAssert(handler.seenEnd)
|
|
|
|
|
2019-03-22 20:34:16 +08:00
|
|
|
XCTAssertTrue(try channel.finish().isClean)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testMultipleTEWithChunkedFirstHasNoBodyOnRequest() throws {
|
2020-02-11 00:44:44 +08:00
|
|
|
try assertRequestTransferEncodingInError(transferEncodingHeader: "chunked, gzip")
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testMultipleTEWithChunkedInTheMiddleHasNoBodyOnRequest() throws {
|
2020-02-11 00:44:44 +08:00
|
|
|
try assertRequestTransferEncodingInError(transferEncodingHeader: "gzip, chunked, deflate")
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
private func assertResponseTransferEncodingHasBodyTerminatedByEOF(transferEncodingHeader: String, eofMechanism: EOFMechanism) throws {
|
2019-02-21 19:46:54 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(HTTPRequestEncoder()).wait())
|
2019-03-06 19:51:42 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(HTTPResponseDecoder())).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
let handler = MessageEndHandler<HTTPResponseHead, ByteBuffer>()
|
2019-02-21 19:46:54 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(handler).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// Prime the decoder with a request and consume it.
|
2021-01-20 01:27:02 +08:00
|
|
|
XCTAssertTrue(try channel.writeOutbound(HTTPClientRequestPart.head(HTTPRequestHead(version: .http1_1,
|
2018-04-12 22:12:39 +08:00
|
|
|
method: .GET,
|
2019-03-22 20:34:16 +08:00
|
|
|
uri: "/"))).isFull)
|
2019-03-04 17:40:59 +08:00
|
|
|
XCTAssertNoThrow(XCTAssertNotNil(try channel.readOutbound(as: ByteBuffer.self)))
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// Send a 200 with the appropriate Transfer Encoding header. We should see the request,
|
|
|
|
// but no body or end.
|
2020-06-05 04:02:11 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeInbound(channel.allocator.buffer(string: "HTTP/1.1 200 OK\r\nTransfer-Encoding: \(transferEncodingHeader)\r\n\r\n")))
|
2018-04-12 22:12:39 +08:00
|
|
|
XCTAssert(handler.seenHead)
|
|
|
|
XCTAssertFalse(handler.seenBody)
|
|
|
|
XCTAssertFalse(handler.seenEnd)
|
|
|
|
|
|
|
|
// Now send body. Note that this is *not* chunk encoded. We should also see a body.
|
2020-06-05 04:02:11 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeInbound(channel.allocator.buffer(string: "caribbean")))
|
2018-04-12 22:12:39 +08:00
|
|
|
XCTAssert(handler.seenHead)
|
|
|
|
XCTAssert(handler.seenBody)
|
|
|
|
XCTAssertFalse(handler.seenEnd)
|
|
|
|
|
|
|
|
// Now send EOF. This should send the end as well.
|
|
|
|
if case .halfClosure = eofMechanism {
|
|
|
|
channel.pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
|
|
|
|
} else {
|
|
|
|
channel.pipeline.fireChannelInactive()
|
|
|
|
}
|
|
|
|
XCTAssert(handler.seenHead)
|
|
|
|
XCTAssert(handler.seenBody)
|
|
|
|
XCTAssert(handler.seenEnd)
|
|
|
|
|
2019-03-22 20:34:16 +08:00
|
|
|
XCTAssertTrue(try channel.finish().isClean)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
2020-02-11 00:44:44 +08:00
|
|
|
private func assertResponseTransferEncodingHasBodyTerminatedByEndOfChunk(transferEncodingHeader: String, eofMechanism: EOFMechanism) throws {
|
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(HTTPRequestEncoder()).wait())
|
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(HTTPResponseDecoder())).wait())
|
|
|
|
|
|
|
|
let handler = MessageEndHandler<HTTPResponseHead, ByteBuffer>()
|
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(handler).wait())
|
|
|
|
|
|
|
|
// Prime the decoder with a request and consume it.
|
2021-01-20 01:27:02 +08:00
|
|
|
XCTAssertTrue(try channel.writeOutbound(HTTPClientRequestPart.head(HTTPRequestHead(version: .http1_1,
|
2020-02-11 00:44:44 +08:00
|
|
|
method: .GET,
|
|
|
|
uri: "/"))).isFull)
|
|
|
|
XCTAssertNoThrow(XCTAssertNotNil(try channel.readOutbound(as: ByteBuffer.self)))
|
|
|
|
|
|
|
|
// Send a 200 with the appropriate Transfer Encoding header. We should see the request.
|
2020-06-05 04:02:11 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeInbound(channel.allocator.buffer(string: "HTTP/1.1 200 OK\r\nTransfer-Encoding: \(transferEncodingHeader)\r\n\r\n")))
|
2020-02-11 00:44:44 +08:00
|
|
|
XCTAssert(handler.seenHead)
|
|
|
|
XCTAssertFalse(handler.seenBody)
|
|
|
|
XCTAssertFalse(handler.seenEnd)
|
|
|
|
|
|
|
|
// Now send body. Note that this *is* chunk encoded. We should also see a body.
|
2020-06-05 04:02:11 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeInbound(channel.allocator.buffer(string: "9\r\ncaribbean\r\n")))
|
2020-02-11 00:44:44 +08:00
|
|
|
XCTAssert(handler.seenHead)
|
|
|
|
XCTAssert(handler.seenBody)
|
|
|
|
XCTAssertFalse(handler.seenEnd)
|
|
|
|
|
|
|
|
// Now send EOF. This should error, as we're expecting the end chunk.
|
|
|
|
if case .halfClosure = eofMechanism {
|
|
|
|
channel.pipeline.fireUserInboundEventTriggered(ChannelEvent.inputClosed)
|
|
|
|
} else {
|
|
|
|
channel.pipeline.fireChannelInactive()
|
|
|
|
}
|
|
|
|
|
|
|
|
XCTAssert(handler.seenHead)
|
|
|
|
XCTAssert(handler.seenBody)
|
|
|
|
XCTAssertFalse(handler.seenEnd)
|
|
|
|
|
|
|
|
XCTAssertThrowsError(try channel.throwIfErrorCaught()) { error in
|
|
|
|
XCTAssertEqual(error as? HTTPParserError, .invalidEOFState)
|
|
|
|
}
|
|
|
|
|
|
|
|
XCTAssertTrue(try channel.finish().isClean)
|
|
|
|
}
|
|
|
|
|
2018-04-12 22:12:39 +08:00
|
|
|
func testMultipleTEWithChunkedLastHasEOFBodyOnResponseWithChannelInactive() throws {
|
2020-02-11 00:44:44 +08:00
|
|
|
try assertResponseTransferEncodingHasBodyTerminatedByEndOfChunk(transferEncodingHeader: "gzip, chunked", eofMechanism: .channelInactive)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testMultipleTEWithChunkedFirstHasEOFBodyOnResponseWithChannelInactive() throws {
|
|
|
|
// Here http_parser is right, and this is EOF terminated.
|
|
|
|
try assertResponseTransferEncodingHasBodyTerminatedByEOF(transferEncodingHeader: "chunked, gzip", eofMechanism: .channelInactive)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testMultipleTEWithChunkedInTheMiddleHasEOFBodyOnResponseWithChannelInactive() throws {
|
|
|
|
// Here http_parser is right, and this is EOF terminated.
|
|
|
|
try assertResponseTransferEncodingHasBodyTerminatedByEOF(transferEncodingHeader: "gzip, chunked, deflate", eofMechanism: .channelInactive)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testMultipleTEWithChunkedLastHasEOFBodyOnResponseWithHalfClosure() throws {
|
2020-02-11 00:44:44 +08:00
|
|
|
try assertResponseTransferEncodingHasBodyTerminatedByEndOfChunk(transferEncodingHeader: "gzip, chunked", eofMechanism: .halfClosure)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testMultipleTEWithChunkedFirstHasEOFBodyOnResponseWithHalfClosure() throws {
|
|
|
|
// Here http_parser is right, and this is EOF terminated.
|
|
|
|
try assertResponseTransferEncodingHasBodyTerminatedByEOF(transferEncodingHeader: "chunked, gzip", eofMechanism: .halfClosure)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testMultipleTEWithChunkedInTheMiddleHasEOFBodyOnResponseWithHalfClosure() throws {
|
|
|
|
// Here http_parser is right, and this is EOF terminated.
|
|
|
|
try assertResponseTransferEncodingHasBodyTerminatedByEOF(transferEncodingHeader: "gzip, chunked, deflate", eofMechanism: .halfClosure)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testRequestWithTEAndContentLengthErrors() throws {
|
2019-03-06 19:51:42 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder())).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// Send a GET with the invalid headers.
|
2020-06-05 04:02:11 +08:00
|
|
|
let request = channel.allocator.buffer(string: "POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\nContent-Length: 4\r\n\r\n")
|
2020-03-04 02:14:49 +08:00
|
|
|
XCTAssertThrowsError(try channel.writeInbound(request)) { error in
|
|
|
|
XCTAssertEqual(HTTPParserError.unexpectedContentLength, error as? HTTPParserError)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Must spin the loop.
|
|
|
|
XCTAssertFalse(channel.isActive)
|
2019-03-01 00:30:36 +08:00
|
|
|
channel.embeddedEventLoop.run()
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testResponseWithTEAndContentLengthErrors() throws {
|
2019-02-21 19:46:54 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(HTTPRequestEncoder()).wait())
|
2019-03-06 19:51:42 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(HTTPResponseDecoder())).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// Prime the decoder with a request.
|
2021-01-20 01:27:02 +08:00
|
|
|
XCTAssertTrue(try channel.writeOutbound(HTTPClientRequestPart.head(HTTPRequestHead(version: .http1_1,
|
2018-04-12 22:12:39 +08:00
|
|
|
method: .GET,
|
2019-03-22 20:34:16 +08:00
|
|
|
uri: "/"))).isFull)
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// Send a 200 OK with the invalid headers.
|
2020-03-04 02:14:49 +08:00
|
|
|
let response = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Length: 4\r\n\r\n"
|
2020-06-05 04:02:11 +08:00
|
|
|
XCTAssertThrowsError(try channel.writeInbound(channel.allocator.buffer(string: response))) { error in
|
2020-03-04 02:14:49 +08:00
|
|
|
XCTAssertEqual(HTTPParserError.unexpectedContentLength, error as? HTTPParserError)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Must spin the loop.
|
|
|
|
XCTAssertFalse(channel.isActive)
|
2019-03-01 00:30:36 +08:00
|
|
|
channel.embeddedEventLoop.run()
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
private func assertRequestWithInvalidCLErrors(contentLengthField: String) throws {
|
2019-03-06 19:51:42 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder())).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// Send a GET with the invalid headers.
|
2020-03-04 02:14:49 +08:00
|
|
|
let request = "POST / HTTP/1.1\r\nContent-Length: \(contentLengthField)\r\n\r\n"
|
2020-06-05 04:02:11 +08:00
|
|
|
XCTAssertThrowsError(try channel.writeInbound(channel.allocator.buffer(string: request))) { error in
|
2020-03-04 02:14:49 +08:00
|
|
|
XCTAssert(HTTPParserError.unexpectedContentLength == error as? HTTPParserError ||
|
|
|
|
HTTPParserError.invalidContentLength == error as? HTTPParserError)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Must spin the loop.
|
|
|
|
XCTAssertFalse(channel.isActive)
|
2019-03-01 00:30:36 +08:00
|
|
|
channel.embeddedEventLoop.run()
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testRequestWithMultipleDifferentContentLengthsFails() throws {
|
|
|
|
try assertRequestWithInvalidCLErrors(contentLengthField: "4, 5")
|
|
|
|
}
|
|
|
|
|
|
|
|
func testRequestWithMultipleDifferentContentLengthsOnDifferentLinesFails() throws {
|
|
|
|
try assertRequestWithInvalidCLErrors(contentLengthField: "4\r\nContent-Length: 5")
|
|
|
|
}
|
|
|
|
|
|
|
|
func testRequestWithInvalidContentLengthFails() throws {
|
|
|
|
try assertRequestWithInvalidCLErrors(contentLengthField: "pie")
|
|
|
|
}
|
|
|
|
|
|
|
|
func testRequestWithIdenticalContentLengthRepeatedErrors() throws {
|
|
|
|
// This is another case where http_parser is, if not wrong, then aggressively interpreting
|
|
|
|
// the spec. Regardless, we match it.
|
2019-03-06 19:51:42 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder())).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// Send two POSTs with repeated content length, one with one field and one with two.
|
|
|
|
// Both should error.
|
2020-03-04 02:14:49 +08:00
|
|
|
let request = "POST / HTTP/1.1\r\nContent-Length: 4, 4\r\n\r\n"
|
2020-06-05 04:02:11 +08:00
|
|
|
XCTAssertThrowsError(try channel.writeInbound(channel.allocator.buffer(string: request))) { error in
|
2020-03-04 02:14:49 +08:00
|
|
|
XCTAssertEqual(HTTPParserError.invalidContentLength, error as? HTTPParserError)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Must spin the loop.
|
|
|
|
XCTAssertFalse(channel.isActive)
|
2019-03-01 00:30:36 +08:00
|
|
|
channel.embeddedEventLoop.run()
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testRequestWithMultipleIdenticalContentLengthFieldsErrors() throws {
|
|
|
|
// This is another case where http_parser is, if not wrong, then aggressively interpreting
|
|
|
|
// the spec. Regardless, we match it.
|
2019-03-06 19:51:42 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder())).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
2020-03-04 02:14:49 +08:00
|
|
|
let request = "POST / HTTP/1.1\r\nContent-Length: 4\r\nContent-Length: 4\r\n\r\n"
|
2020-06-05 04:02:11 +08:00
|
|
|
XCTAssertThrowsError(try channel.writeInbound(channel.allocator.buffer(string: request))) { error in
|
2020-03-04 02:14:49 +08:00
|
|
|
XCTAssertEqual(HTTPParserError.unexpectedContentLength, error as? HTTPParserError)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Must spin the loop.
|
|
|
|
XCTAssertFalse(channel.isActive)
|
2019-03-01 00:30:36 +08:00
|
|
|
channel.embeddedEventLoop.run()
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func testRequestWithoutExplicitLengthIsZeroLength() throws {
|
2019-03-06 19:51:42 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder())).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
let handler = MessageEndHandler<HTTPRequestHead, ByteBuffer>()
|
2019-02-21 19:46:54 +08:00
|
|
|
XCTAssertNoThrow(try channel.pipeline.addHandler(handler).wait())
|
2018-04-12 22:12:39 +08:00
|
|
|
|
|
|
|
// Send a POST without a length field of any kind. This should be a zero-length request,
|
|
|
|
// so .end should come immediately.
|
2020-06-05 04:02:11 +08:00
|
|
|
XCTAssertNoThrow(try channel.writeInbound(channel.allocator.buffer(string: "POST / HTTP/1.1\r\nHost: example.org\r\n\r\n")))
|
2018-04-12 22:12:39 +08:00
|
|
|
XCTAssert(handler.seenHead)
|
|
|
|
XCTAssertFalse(handler.seenBody)
|
|
|
|
XCTAssert(handler.seenEnd)
|
|
|
|
|
2019-03-22 20:34:16 +08:00
|
|
|
XCTAssertTrue(try channel.finish().isClean)
|
2018-04-12 22:12:39 +08:00
|
|
|
}
|
|
|
|
}
|