diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/H2Processors.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/H2Processors.java
index b6cf55270..6d3227f08 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/H2Processors.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/H2Processors.java
@@ -38,6 +38,7 @@
import org.apache.hc.core5.http2.protocol.H2RequestConformance;
import org.apache.hc.core5.http2.protocol.H2RequestConnControl;
import org.apache.hc.core5.http2.protocol.H2RequestContent;
+import org.apache.hc.core5.http2.protocol.H2RequestTE;
import org.apache.hc.core5.http2.protocol.H2RequestTargetHost;
import org.apache.hc.core5.http2.protocol.H2RequestValidateHost;
import org.apache.hc.core5.http2.protocol.H2ResponseConformance;
@@ -86,6 +87,7 @@ public static HttpProcessorBuilder customClient(final String agentInfo) {
H2RequestTargetHost.INSTANCE,
H2RequestContent.INSTANCE,
H2RequestConnControl.INSTANCE,
+ H2RequestTE.INSTANCE,
new RequestUserAgent(!TextUtils.isBlank(agentInfo) ? agentInfo :
VersionInfo.getSoftwareInfo(SOFTWARE, "org.apache.hc.core5", HttpProcessors.class)),
RequestExpectContinue.INSTANCE);
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestTE.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestTE.java
new file mode 100644
index 000000000..11c6248f6
--- /dev/null
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestTE.java
@@ -0,0 +1,130 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+
+package org.apache.hc.core5.http2.protocol;
+
+import java.io.IOException;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.ProtocolVersion;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.http.protocol.RequestTE;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.Tokenizer;
+
+/**
+ * HTTP request interceptor responsible for validating the {@code TE} header in HTTP/2 requests.
+ *
+ * The {@code TE} header in HTTP/2 is restricted to containing only the {@code trailers} directive.
+ * This interceptor ensures compliance by validating that the {@code TE} header does not include
+ * any other directives or transfer codings. If any value other than {@code trailers} is present,
+ * a {@link ProtocolException} is thrown.
+ *
+ * For HTTP/1.x requests, this interceptor falls back to the behavior of {@link RequestTE},
+ * where other transfer codings may be allowed.
+ *
+ * @since 5.5
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public class H2RequestTE extends RequestTE {
+
+ /**
+ * Singleton instance of the {@code H2RequestTE} interceptor.
+ */
+ public static final HttpRequestInterceptor INSTANCE = new H2RequestTE();
+
+ /**
+ * Processes the {@code TE} header for HTTP/2 compliance.
+ *
+ * If the protocol version is HTTP/2, this method checks if the {@code TE} header contains
+ * only the {@code trailers} directive. If any other value is found, it throws a {@link ProtocolException}.
+ * For HTTP/1.x requests, it delegates processing to the parent {@link RequestTE} class.
+ *
+ * @param request the HTTP request to validate
+ * @param entity the entity associated with the request (may be {@code null})
+ * @param context the execution context for the request
+ * @throws HttpException if the {@code TE} header contains invalid values for HTTP/2
+ * @throws IOException in case of an I/O error
+ */
+ @Override
+ public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context)
+ throws HttpException, IOException {
+
+ Args.notNull(context, "HTTP context");
+ final ProtocolVersion ver = context.getProtocolVersion();
+
+ // If the protocol version is HTTP/2
+ if (ver.getMajor() >= 2) {
+ // Check if TE header is present
+ final Header teHeader = request.getFirstHeader(HttpHeaders.TE);
+ if (teHeader != null) {
+ final String teValue = teHeader.getValue();
+ validateTEHeaderForHttp2(teValue);
+ }
+ } else {
+ // For HTTP/1.x, fall back to the parent TE logic
+ super.process(request, entity, context);
+ }
+ }
+
+ /**
+ * Validates that the {@code TE} header for HTTP/2 contains only the {@code trailers} directive.
+ *
+ * This method parses the {@code TE} header and ensures that only the {@code trailers} directive is present.
+ * If any other value is found, a {@link ProtocolException} is thrown.
+ *
+ * @param teValue the value of the {@code TE} header to validate
+ * @throws HttpException if the {@code TE} header contains invalid values for HTTP/2
+ */
+ private void validateTEHeaderForHttp2(final String teValue) throws HttpException {
+ final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, teValue.length());
+
+ while (!cursor.atEnd()) {
+ final String member = Tokenizer.INSTANCE.parseToken(teValue, cursor, DELIMITER).trim();
+
+ // Only 'trailers' is allowed in HTTP/2
+ if (!"trailers".equalsIgnoreCase(member)) {
+ throw new ProtocolException("In HTTP/2, the TE header must only contain 'trailers'. Found: " + member);
+ }
+
+ // Skip any whitespace and delimiter before moving to the next value
+ if (!cursor.atEnd()) {
+ Tokenizer.INSTANCE.skipWhiteSpace(teValue, cursor);
+ cursor.updatePos(cursor.getPos() + 1);
+ }
+ }
+ }
+}
+
diff --git a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/protocol/TestH2RequestTE.java b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/protocol/TestH2RequestTE.java
new file mode 100644
index 000000000..15c557e84
--- /dev/null
+++ b/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/protocol/TestH2RequestTE.java
@@ -0,0 +1,101 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+
+package org.apache.hc.core5.http2.protocol;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.Method;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TestH2RequestTE {
+
+ private HttpCoreContext context;
+
+ @BeforeEach
+ void setUp() {
+ context = HttpCoreContext.create();
+ context.setProtocolVersion(HttpVersion.HTTP_2); // Set the protocol to HTTP/2 for tests
+ }
+
+ @Test
+ void testValidTEHeaderForHttp2() throws Exception {
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ request.setHeader(HttpHeaders.TE, "trailers");
+
+ final HttpRequestInterceptor interceptor = new H2RequestTE();
+ interceptor.process(request, request.getEntity(), context);
+
+ // Assertions
+ assertNotNull(request.getHeader(HttpHeaders.TE));
+ assertEquals("trailers", request.getHeader(HttpHeaders.TE).getValue());
+ }
+
+ @Test
+ void testInvalidTEHeaderForHttp2() {
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ request.setHeader(HttpHeaders.TE, "trailers, deflate;q=0.5");
+
+ final H2RequestTE interceptor = new H2RequestTE();
+ // Expect a ProtocolException due to invalid value in the TE header for HTTP/2
+ assertThrows(ProtocolException.class, () ->
+ interceptor.process(request, request.getEntity(), context));
+ }
+
+ @Test
+ void testTEHeaderWithoutTrailersForHttp2() {
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ request.setHeader(HttpHeaders.TE, "gzip;q=0.5");
+
+ final H2RequestTE interceptor = new H2RequestTE();
+ // Expect a ProtocolException because 'trailers' is not present
+ assertThrows(ProtocolException.class, () ->
+ interceptor.process(request, request.getEntity(), context));
+ }
+
+ @Test
+ void testNoTEHeaderForHttp2() throws Exception {
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+
+ final H2RequestTE interceptor = new H2RequestTE();
+ interceptor.process(request, request.getEntity(), context);
+
+ // Ensure that no TE header is present, which is valid
+ assertNull(request.getHeader(HttpHeaders.TE));
+ }
+
+}
\ No newline at end of file
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTest.java
index e92582efd..22f27425c 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/classic/ClassicIntegrationTest.java
@@ -65,6 +65,7 @@
import org.apache.hc.core5.http.protocol.RequestConnControl;
import org.apache.hc.core5.http.protocol.RequestContent;
import org.apache.hc.core5.http.protocol.RequestExpectContinue;
+import org.apache.hc.core5.http.protocol.RequestTE;
import org.apache.hc.core5.http.protocol.RequestTargetHost;
import org.apache.hc.core5.http.protocol.RequestUserAgent;
import org.apache.hc.core5.testing.extension.classic.ClassicTestResources;
@@ -638,7 +639,8 @@ void testHttpPostNoContentLength() throws Exception {
RequestTargetHost.INSTANCE,
RequestConnControl.INSTANCE,
RequestUserAgent.INSTANCE,
- RequestExpectContinue.INSTANCE));
+ RequestExpectContinue.INSTANCE,
+ RequestTE.INSTANCE));
client.start();
final HttpCoreContext context = HttpCoreContext.create();
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/HttpProcessors.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/HttpProcessors.java
index 3da9d11dd..6753cd47d 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/HttpProcessors.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/HttpProcessors.java
@@ -32,6 +32,7 @@
import org.apache.hc.core5.http.protocol.RequestConnControl;
import org.apache.hc.core5.http.protocol.RequestContent;
import org.apache.hc.core5.http.protocol.RequestExpectContinue;
+import org.apache.hc.core5.http.protocol.RequestTE;
import org.apache.hc.core5.http.protocol.RequestTargetHost;
import org.apache.hc.core5.http.protocol.RequestUserAgent;
import org.apache.hc.core5.http.protocol.RequestValidateHost;
@@ -69,6 +70,7 @@ public static HttpProcessorBuilder customServer(final String serverInfo) {
ResponseContent.INSTANCE,
ResponseConnControl.INSTANCE)
.addAll(
+ RequestTE.INSTANCE,
RequestValidateHost.INSTANCE,
RequestConformance.INSTANCE);
}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestTE.java b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestTE.java
new file mode 100644
index 000000000..bf2d542a6
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/protocol/RequestTE.java
@@ -0,0 +1,190 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+
+package org.apache.hc.core5.http.protocol;
+
+import java.io.IOException;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HeaderElements;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.Tokenizer;
+
+/**
+ * HTTP request interceptor responsible for validating and processing the {@code TE} header field in HTTP/1.1 requests.
+ *
+ * The {@code TE} header is used to indicate transfer codings the client is willing to accept and, in some cases, whether
+ * the client is willing to accept trailer fields. This interceptor ensures that the {@code TE} header does not include
+ * the {@code chunked} transfer coding and validates the presence of the {@code Connection: TE} header.
+ *
+ * For HTTP/1.1 requests, the {@code TE} header can contain multiple values separated by commas and may include quality
+ * values (denoted by {@code q=}) separated by semicolons.
+ *
+ * In case of HTTP/2, this validation is skipped, and another layer of logic handles the specifics of HTTP/2 compliance.
+ *
+ * @since 5.5
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+public class RequestTE implements HttpRequestInterceptor {
+
+ /**
+ * Singleton instance of the {@code RequestTE} interceptor.
+ */
+ public static final HttpRequestInterceptor INSTANCE = new RequestTE();
+
+ /**
+ * Delimiter used to parse the {@code TE} header, recognizing both commas (',') and semicolons (';') as delimiters.
+ */
+ public static final Tokenizer.Delimiter DELIMITER = Tokenizer.delimiters(',', ';');
+
+ /**
+ * Default constructor.
+ */
+ public RequestTE() {
+ super();
+ }
+
+ /**
+ * Processes the {@code TE} header of the given HTTP request and ensures compliance with HTTP/1.1 requirements.
+ *
+ * If the {@code TE} header is present, this method validates that:
+ *
+ * - The {@code TE} header does not include the {@code chunked} transfer coding, which is implicitly supported for HTTP/1.1.
+ * - The {@code Connection} header includes the {@code TE} directive, as required by the protocol.
+ *
+ *
+ * @param request the HTTP request containing the headers to validate
+ * @param entity the entity associated with the request (may be {@code null})
+ * @param context the execution context for the request
+ * @throws HttpException if the {@code TE} header contains invalid values or the {@code Connection} header is missing
+ * @throws IOException in case of an I/O error
+ */
+ @Override
+ public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context)
+ throws HttpException, IOException {
+ Args.notNull(request, "HTTP request");
+
+ // Fetch the TE header
+ final Header teHeader = request.getFirstHeader(HttpHeaders.TE);
+
+ if (teHeader == null) {
+ return; // No further validation needed
+ }
+
+ final String teValue = teHeader.getValue();
+ validateTEField(teValue);
+
+ validateConnectionHeader(request);
+ }
+
+ /**
+ * Validates the {@code TE} header values for compliance with HTTP/1.1.
+ *
+ * Specifically, this method ensures that:
+ *
+ * - The {@code TE} header does not contain the {@code chunked} transfer coding.
+ * - The {@code trailers} directive is allowed and treated as valid.
+ *
+ *
+ * @param teValue the value of the {@code TE} header
+ * @throws HttpException if the {@code TE} header contains invalid values
+ */
+ private void validateTEField(final String teValue) throws HttpException {
+ final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, teValue.length());
+
+ while (!cursor.atEnd()) {
+ Tokenizer.INSTANCE.skipWhiteSpace(teValue, cursor);
+
+ final String member = Tokenizer.INSTANCE.parseToken(teValue, cursor, DELIMITER);
+
+ if (member.isEmpty()) {
+ if (!cursor.atEnd()) {
+ Tokenizer.INSTANCE.skipWhiteSpace(teValue, cursor);
+ cursor.updatePos(cursor.getPos() + 1);
+ }
+ continue;
+ }
+
+ if ("trailers".equalsIgnoreCase(member)) {
+ continue;
+ }
+
+ if (HeaderElements.CHUNKED_ENCODING.equalsIgnoreCase(member)) {
+ throw new ProtocolException("'chunked' transfer coding must not be listed in the TE header for HTTP/1.1.");
+ }
+
+ if (!cursor.atEnd()) {
+ Tokenizer.INSTANCE.skipWhiteSpace(teValue, cursor);
+ cursor.updatePos(cursor.getPos() + 1);
+ }
+ }
+ }
+
+ /**
+ * Validates the presence of the {@code Connection: TE} header when the {@code TE} header is present.
+ *
+ * If the {@code TE} header is used, the HTTP/1.1 protocol requires that the {@code Connection} header includes the {@code TE} directive to prevent forwarding by intermediaries.
+ *
+ * @param request the HTTP request to validate
+ * @throws HttpException if the {@code Connection: TE} header is missing
+ */
+ private void validateConnectionHeader(final HttpRequest request) throws HttpException {
+ final Header connectionHeader = request.getFirstHeader(HttpHeaders.CONNECTION);
+ if (connectionHeader == null) {
+ throw new ProtocolException("The 'TE' header is present, but the 'Connection' header is missing.");
+ }
+ final String connectionValue = connectionHeader.getValue();
+ final Tokenizer.Cursor cursor = new Tokenizer.Cursor(0, connectionValue.length());
+
+ boolean hasTE = false;
+
+ while (!cursor.atEnd()) {
+ final String directive = Tokenizer.INSTANCE.parseToken(connectionValue, cursor, DELIMITER).trim();
+
+ if ("TE".equalsIgnoreCase(directive)) {
+ hasTE = true;
+ break;
+ }
+
+ if (!cursor.atEnd()) {
+ cursor.updatePos(cursor.getPos() + 1);
+ }
+ }
+
+ if (!hasTE) {
+ throw new ProtocolException("The 'Connection' header must include the 'TE' directive when the 'TE' header is present.");
+ }
+ }
+}
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestRequestTE.java b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestRequestTE.java
new file mode 100644
index 000000000..990c4909b
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestRequestTE.java
@@ -0,0 +1,199 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+
+package org.apache.hc.core5.http.protocol;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.Method;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class TestRequestTE {
+
+ @Test
+ void testValidTEHeader() throws Exception {
+ final HttpCoreContext context = HttpCoreContext.create();
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ context.setProtocolVersion(HttpVersion.HTTP_1_1);
+
+ // Set the TE header and Connection header
+ request.setHeader(HttpHeaders.TE, "trailers");
+ request.setHeader(HttpHeaders.CONNECTION, "TE");
+
+ final HttpRequestInterceptor interceptor = new RequestTE();
+ interceptor.process(request, request.getEntity(), context);
+
+ assertNotNull(request.getHeader(HttpHeaders.TE));
+ assertEquals("trailers", request.getHeader(HttpHeaders.TE).getValue());
+ }
+
+
+ @Test
+ void testMultipleValidTEHeaders() throws Exception {
+ final HttpCoreContext context = HttpCoreContext.create();
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ context.setProtocolVersion(HttpVersion.HTTP_1_1);
+
+ // Set both the TE header and the Connection header
+ request.setHeader(HttpHeaders.TE, "trailers, deflate;q=0.5");
+ request.setHeader(HttpHeaders.CONNECTION, "TE");
+
+ final HttpRequestInterceptor interceptor = new RequestTE();
+ interceptor.process(request, request.getEntity(), context);
+
+ assertNotNull(request.getHeader(HttpHeaders.TE));
+ assertEquals("trailers, deflate;q=0.5", request.getHeader(HttpHeaders.TE).getValue());
+ }
+
+
+ @Test
+ void testTEHeaderNotPresent() throws Exception {
+ final HttpCoreContext context = HttpCoreContext.create();
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ context.setProtocolVersion(HttpVersion.HTTP_1_1);
+
+ final HttpRequestInterceptor interceptor = new RequestTE();
+ interceptor.process(request, request.getEntity(), context);
+
+ // No TE header, no validation should occur
+ assertNull(request.getHeader(HttpHeaders.TE));
+ }
+
+ @Test
+ void testTEHeaderContainsChunked() {
+ final HttpCoreContext context = HttpCoreContext.create();
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ context.setProtocolVersion(HttpVersion.HTTP_1_1);
+ request.setHeader(HttpHeaders.TE, "chunked");
+
+ final HttpRequestInterceptor interceptor = new RequestTE();
+ Assertions.assertThrows(ProtocolException.class, () ->
+ interceptor.process(request, request.getEntity(), context));
+ }
+
+ @Test
+ void testTEHeaderInvalidTransferCoding() {
+ final HttpCoreContext context = HttpCoreContext.create();
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ context.setProtocolVersion(HttpVersion.HTTP_1_1);
+ request.setHeader(HttpHeaders.TE, "invalid;q=abc");
+
+ final HttpRequestInterceptor interceptor = new RequestTE();
+ Assertions.assertThrows(ProtocolException.class, () ->
+ interceptor.process(request, request.getEntity(), context));
+ }
+
+ @Test
+ void testTEHeaderAlreadySet() throws Exception {
+ final HttpCoreContext context = HttpCoreContext.create();
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ context.setProtocolVersion(HttpVersion.HTTP_1_1);
+
+ final String teValue = "trailers";
+ request.setHeader(HttpHeaders.TE, teValue);
+ request.setHeader(HttpHeaders.CONNECTION, "TE"); // Add the Connection header as required
+
+ final HttpRequestInterceptor interceptor = new RequestTE();
+ interceptor.process(request, request.getEntity(), context);
+
+ assertEquals(HttpHeaders.TE, request.getHeader(HttpHeaders.TE).getName());
+ assertNotNull(request.getHeader(HttpHeaders.TE));
+ assertEquals(teValue, request.getHeader(HttpHeaders.TE).getValue());
+ }
+
+
+ @Test
+ void testTEHeaderWithConnectionHeaderValidation() throws Exception {
+ final HttpCoreContext context = HttpCoreContext.create();
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ context.setProtocolVersion(HttpVersion.HTTP_1_1);
+ request.setHeader(HttpHeaders.TE, "trailers");
+ request.setHeader(HttpHeaders.CONNECTION, "TE");
+
+ final HttpRequestInterceptor interceptor = new RequestTE();
+ interceptor.process(request, request.getEntity(), context);
+
+ assertEquals(HttpHeaders.TE, request.getHeader(HttpHeaders.TE).getName());
+ assertNotNull(request.getHeader(HttpHeaders.TE));
+ assertEquals("trailers", request.getHeader(HttpHeaders.TE).getValue());
+ }
+
+ @Test
+ void testTEHeaderWithoutConnectionHeaderThrowsException() {
+ final HttpCoreContext context = HttpCoreContext.create();
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ context.setProtocolVersion(HttpVersion.HTTP_1_1);
+ request.setHeader(HttpHeaders.TE, "trailers");
+
+ final HttpRequestInterceptor interceptor = new RequestTE();
+ Assertions.assertThrows(ProtocolException.class, () ->
+ interceptor.process(request, request.getEntity(), context));
+ }
+
+ @Test
+ void testTEHeaderWithoutTEInConnectionHeaderThrowsException() {
+ final HttpCoreContext context = HttpCoreContext.create();
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ context.setProtocolVersion(HttpVersion.HTTP_1_1);
+ // Set TE header but Connection header does not include "TE"
+ request.setHeader(HttpHeaders.TE, "trailers");
+ request.setHeader(HttpHeaders.CONNECTION, "keep-alive"); // Missing "TE"
+
+ final HttpRequestInterceptor interceptor = new RequestTE();
+ Assertions.assertThrows(ProtocolException.class, () ->
+ interceptor.process(request, request.getEntity(), context));
+ }
+
+ @Test
+ void testTEHeaderWithMultipleDirectivesInConnectionHeader() throws Exception {
+ final HttpCoreContext context = HttpCoreContext.create();
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ context.setProtocolVersion(HttpVersion.HTTP_1_1);
+
+ // Set TE header and a Connection header with multiple directives
+ request.setHeader(HttpHeaders.TE, "trailers");
+ request.setHeader(HttpHeaders.CONNECTION, "keep-alive, close, TE");
+
+ final HttpRequestInterceptor interceptor = new RequestTE();
+ interceptor.process(request, request.getEntity(), context);
+
+ assertNotNull(request.getHeader(HttpHeaders.CONNECTION));
+ assertTrue(request.getHeader(HttpHeaders.CONNECTION).getValue().contains("TE"));
+ }
+
+
+}
+
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestStandardInterceptors.java b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestStandardInterceptors.java
index bdf336a0d..e9f66b744 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestStandardInterceptors.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/protocol/TestStandardInterceptors.java
@@ -442,6 +442,25 @@ void testRequestTargetHostConnectHttp10() throws Exception {
Assertions.assertNull(header);
}
+ @Test
+ void testTEHeaderWithConnectionTE() throws Exception {
+ final HttpCoreContext context = HttpCoreContext.create();
+ final BasicClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, "/");
+ context.setProtocolVersion(HttpVersion.HTTP_1_1);
+
+ // Set both TE and Connection headers as per the requirement
+ request.setHeader(HttpHeaders.TE, "trailers");
+ request.setHeader(HttpHeaders.CONNECTION, "TE");
+
+ final RequestTE interceptor = new RequestTE();
+ interceptor.process(request, request.getEntity(), context);
+
+ final Header connectionHeader = request.getFirstHeader(HttpHeaders.CONNECTION);
+ Assertions.assertNotNull(connectionHeader);
+ Assertions.assertEquals("TE", connectionHeader.getValue());
+ }
+
+
@Test
void testRequestUserAgentGenerated() throws Exception {
final HttpCoreContext context = HttpCoreContext.create();