From ae57c2a0d8d97df69f56ddac0ad407d08790a104 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 12 Apr 2019 16:01:43 +0100 Subject: [PATCH] Validate that connection-specific headers aren't sent. (#95) Motivation: RFC 7540 forbids the use of connection-specific header fields, except for TE sent with the value "trailers". We should police that. Modifications: - Added extra policing around regular header fields. Result: Better validation --- .../NIOHTTP2/HPACKHeaders+Validation.swift | 56 +++- Sources/NIOHTTP2/HTTP2Error.swift | 12 + .../ConnectionStateMachineTests+XCTest.swift | 9 + .../ConnectionStateMachineTests.swift | 252 ++++++++++++++++++ 4 files changed, 315 insertions(+), 14 deletions(-) diff --git a/Sources/NIOHTTP2/HPACKHeaders+Validation.swift b/Sources/NIOHTTP2/HPACKHeaders+Validation.swift index b0f38750..fae384a7 100644 --- a/Sources/NIOHTTP2/HPACKHeaders+Validation.swift +++ b/Sources/NIOHTTP2/HPACKHeaders+Validation.swift @@ -86,6 +86,7 @@ extension HeaderBlockValidator { for (name, value, _) in block { let fieldName = try HeaderFieldName(name) try blockSection.validField(fieldName) + try fieldName.legalHeaderField(value: value) let thisPseudoHeaderFieldType = try seenPseudoHeaders.seenNewHeaderField(fieldName) @@ -142,22 +143,28 @@ extension RequestBlockValidator: HeaderBlockValidator { // This is a bit awkward. // // For now we don't support extended-CONNECT, but when we do we'll need to update the logic here. - guard let pseudoHeaderType = pseudoHeaderType else { - // Nothing to do here. - return - } + if let pseudoHeaderType = pseudoHeaderType { + assert(name.fieldType == .pseudoHeaderField) + + switch pseudoHeaderType { + case .method: + // This is a method pseudo-header. Check if the value is CONNECT. + self.isConnectRequest = value == "CONNECT" + case .path: + // This is a path pseudo-header. It must not be empty. + if value.utf8.count == 0 { + throw NIOHTTP2Errors.EmptyPathHeader() + } + default: + break + } + } else { + assert(name.fieldType == .regularHeaderField) - switch pseudoHeaderType { - case .method: - // This is a method pseudo-header. Check if the value is CONNECT. - self.isConnectRequest = value == "CONNECT" - case .path: - // This is a path pseudo-header. It must not be empty. - if value.utf8.count == 0 { - throw NIOHTTP2Errors.EmptyPathHeader() + // We want to check that if the TE header field is present, it only contains "trailers". + if name.baseName == "te" && value != "trailers" { + throw NIOHTTP2Errors.ForbiddenHeaderField(name: String(name.baseName), value: value) } - default: - break } } @@ -249,6 +256,27 @@ extension HeaderFieldName { throw NIOHTTP2Errors.InvalidHTTP2HeaderFieldName(fieldName) } } + + func legalHeaderField(value: String) throws { + // RFC 7540 ยง 8.1.2.2 forbids all connection-specific header fields. A connection-specific header field technically + // is one that is listed in the Connection header, but could also be proxy-connection & transfer-encoding, even though + // those are not usually listed in the Connection header. For defensiveness sake, we forbid those too. + // + // There is one more wrinkle, which is that the client is allowed to send TE: trailers, and forbidden from sending TE + // with anything else. We police that separately, as TE is only defined on requests, so we can avoid checking for it + // on responses and trailers. + guard self.fieldType == .regularHeaderField else { + // Pseudo-headers are never connection-specific. + return + } + + switch self.baseName { + case "connection", "transfer-encoding", "proxy-connection": + throw NIOHTTP2Errors.ForbiddenHeaderField(name: String(self.baseName), value: value) + default: + return + } + } } diff --git a/Sources/NIOHTTP2/HTTP2Error.swift b/Sources/NIOHTTP2/HTTP2Error.swift index 6616bfe4..8f2ab795 100644 --- a/Sources/NIOHTTP2/HTTP2Error.swift +++ b/Sources/NIOHTTP2/HTTP2Error.swift @@ -252,6 +252,18 @@ public enum NIOHTTP2Errors { self.fieldName = fieldName } } + + /// Connection-specific header fields are forbidden in HTTP/2: this error is raised when one is + /// sent or received. + public struct ForbiddenHeaderField: NIOHTTP2Error { + public var name: String + public var value: String + + public init(name: String, value: String) { + self.name = name + self.value = value + } + } } diff --git a/Tests/NIOHTTP2Tests/ConnectionStateMachineTests+XCTest.swift b/Tests/NIOHTTP2Tests/ConnectionStateMachineTests+XCTest.swift index 03e6f82f..154b88e0 100644 --- a/Tests/NIOHTTP2Tests/ConnectionStateMachineTests+XCTest.swift +++ b/Tests/NIOHTTP2Tests/ConnectionStateMachineTests+XCTest.swift @@ -113,6 +113,15 @@ extension ConnectionStateMachineTests { ("testAllowSimpleConnectRequest", testAllowSimpleConnectRequest), ("testRejectRequestHeadersWithoutAuthorityFieldOnCONNECT", testRejectRequestHeadersWithoutAuthorityFieldOnCONNECT), ("testAllowRequestHeadersWithoutAuthorityFieldOnCONNECTValidationDisabled", testAllowRequestHeadersWithoutAuthorityFieldOnCONNECTValidationDisabled), + ("testRejectHeadersWithConnectionHeader", testRejectHeadersWithConnectionHeader), + ("testAllowHeadersWithConnectionHeaderWhenValidationDisabled", testAllowHeadersWithConnectionHeaderWhenValidationDisabled), + ("testRejectHeadersWithTransferEncodingHeader", testRejectHeadersWithTransferEncodingHeader), + ("testAllowHeadersWithTransferEncodingHeaderWhenValidationDisabled", testAllowHeadersWithTransferEncodingHeaderWhenValidationDisabled), + ("testRejectHeadersWithProxyConnectionHeader", testRejectHeadersWithProxyConnectionHeader), + ("testAllowHeadersWithProxyConnectionHeaderWhenValidationDisabled", testAllowHeadersWithProxyConnectionHeaderWhenValidationDisabled), + ("testRejectHeadersWithTEHeaderNotTrailers", testRejectHeadersWithTEHeaderNotTrailers), + ("testAllowHeadersWithTEHeaderNotTrailersWhenValidationDisabled", testAllowHeadersWithTEHeaderNotTrailersWhenValidationDisabled), + ("testAllowHeadersWithTEHeaderSetToTrailers", testAllowHeadersWithTEHeaderSetToTrailers), ] } } diff --git a/Tests/NIOHTTP2Tests/ConnectionStateMachineTests.swift b/Tests/NIOHTTP2Tests/ConnectionStateMachineTests.swift index e086e542..d81e60d4 100644 --- a/Tests/NIOHTTP2Tests/ConnectionStateMachineTests.swift +++ b/Tests/NIOHTTP2Tests/ConnectionStateMachineTests.swift @@ -2246,6 +2246,258 @@ class ConnectionStateMachineTests: XCTestCase { assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: headers, isEndStreamSet: true)) assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: headers, isEndStreamSet: true)) } + + func testRejectHeadersWithConnectionHeader() { + let streamOne = HTTP2StreamID(1) + let streamThree = HTTP2StreamID(3) + let streamFive = HTTP2StreamID(5) + + self.exchangePreamble() + + let invalidExtraHeaders = [("connection", "close")] + + // First, test that client initial headers may not contain the connection header. + assertStreamError(type: .protocolError, self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, set up a valid stream for the client and confirm that the server response cannot have the connection header. + assertSucceeds(self.client.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.server.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.client.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, test this with trailers. + assertSucceeds(self.client.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertStreamError(type: .protocolError, self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + } + + func testAllowHeadersWithConnectionHeaderWhenValidationDisabled() { + let streamOne = HTTP2StreamID(1) + let streamThree = HTTP2StreamID(3) + let streamFive = HTTP2StreamID(5) + + // Override the setup with validation disabled. + self.server = .init(role: .server, headerBlockValidation: .disabled) + self.client = .init(role: .client, headerBlockValidation: .disabled) + + self.exchangePreamble() + + let invalidExtraHeaders = [("connection", "close")] + + // First, test that client initial headers may not contain the connection header. + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, set up a valid stream for the client and confirm that the server response cannot have the connection header. + assertSucceeds(self.client.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.client.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, test this with trailers. + assertSucceeds(self.client.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + } + + func testRejectHeadersWithTransferEncodingHeader() { + let streamOne = HTTP2StreamID(1) + let streamThree = HTTP2StreamID(3) + let streamFive = HTTP2StreamID(5) + + self.exchangePreamble() + + let invalidExtraHeaders = [("transfer-encoding", "chunked")] + + // First, test that client initial headers may not contain the transfer-encoding header. + assertStreamError(type: .protocolError, self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, set up a valid stream for the client and confirm that the server response cannot have the transfer-encoding header. + assertSucceeds(self.client.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.server.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.client.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, test this with trailers. + assertSucceeds(self.client.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertStreamError(type: .protocolError, self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + } + + func testAllowHeadersWithTransferEncodingHeaderWhenValidationDisabled() { + let streamOne = HTTP2StreamID(1) + let streamThree = HTTP2StreamID(3) + let streamFive = HTTP2StreamID(5) + + // Override the setup with validation disabled. + self.server = .init(role: .server, headerBlockValidation: .disabled) + self.client = .init(role: .client, headerBlockValidation: .disabled) + + self.exchangePreamble() + + let invalidExtraHeaders = [("transfer-encoding", "chunked")] + + // First, test that client initial headers may not contain the transfer-encoding header. + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, set up a valid stream for the client and confirm that the server response cannot have the transfer-encoding header. + assertSucceeds(self.client.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.client.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, test this with trailers. + assertSucceeds(self.client.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + } + + func testRejectHeadersWithProxyConnectionHeader() { + let streamOne = HTTP2StreamID(1) + let streamThree = HTTP2StreamID(3) + let streamFive = HTTP2StreamID(5) + + self.exchangePreamble() + + let invalidExtraHeaders = [("proxy-connection", "close")] + + // First, test that client initial headers may not contain the proxy=connection header. + assertStreamError(type: .protocolError, self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, set up a valid stream for the client and confirm that the server response cannot have the proxy-connection header. + assertSucceeds(self.client.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.server.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.client.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, test this with trailers. + assertSucceeds(self.client.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertStreamError(type: .protocolError, self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + } + + func testAllowHeadersWithProxyConnectionHeaderWhenValidationDisabled() { + let streamOne = HTTP2StreamID(1) + let streamThree = HTTP2StreamID(3) + let streamFive = HTTP2StreamID(5) + + // Override the setup with validation disabled. + self.server = .init(role: .server, headerBlockValidation: .disabled) + self.client = .init(role: .client, headerBlockValidation: .disabled) + + self.exchangePreamble() + + let invalidExtraHeaders = [("proxy-connection", "close")] + + // First, test that client initial headers may not contain the proxy-connection header. + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, set up a valid stream for the client and confirm that the server response cannot have the proxy-connection header. + assertSucceeds(self.client.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.client.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, test this with trailers. + assertSucceeds(self.client.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + } + + func testRejectHeadersWithTEHeaderNotTrailers() { + let streamOne = HTTP2StreamID(1) + let streamThree = HTTP2StreamID(3) + let streamFive = HTTP2StreamID(5) + + self.exchangePreamble() + + let invalidExtraHeaders = [("te", "deflate")] + + // First, test that client initial headers may not contain the TE header. + assertStreamError(type: .protocolError, self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertStreamError(type: .protocolError, self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, set up a valid stream for the client and confirm that the server response *may* have the TE header. This is allowed as TE is only defined on requests. + assertSucceeds(self.client.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.client.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, test this with trailers. Again, this is allowed. + assertSucceeds(self.client.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + } + + func testAllowHeadersWithTEHeaderNotTrailersWhenValidationDisabled() { + let streamOne = HTTP2StreamID(1) + let streamThree = HTTP2StreamID(3) + let streamFive = HTTP2StreamID(5) + + // Override the setup with validation disabled. + self.server = .init(role: .server, headerBlockValidation: .disabled) + self.client = .init(role: .client, headerBlockValidation: .disabled) + + self.exchangePreamble() + + let invalidExtraHeaders = [("te", "deflate")] + + // First, test that client initial headers may not contain the TE header. + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, set up a valid stream for the client and confirm that the server response *may* have the TE header. This is allowed as TE is only defined on requests. + assertSucceeds(self.client.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.sendHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.client.receiveHeaders(streamID: streamThree, headers: ConnectionStateMachineTests.responseHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + + // Next, test this with trailers. Again, this is allowed. + assertSucceeds(self.client.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true)) + assertSucceeds(self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.responseHeaders, isEndStreamSet: false)) + assertSucceeds(self.server.sendHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.client.receiveHeaders(streamID: streamFive, headers: ConnectionStateMachineTests.trailers.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + } + + func testAllowHeadersWithTEHeaderSetToTrailers() { + let streamOne = HTTP2StreamID(1) + + self.exchangePreamble() + + let invalidExtraHeaders = [("te", "trailers")] + + // Client headers may contain TE: trailers. + assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders.withExtraHeaders(invalidExtraHeaders), isEndStreamSet: true)) + } }