From 912b46d3651121635cc4e513ba5b0165dce047ae Mon Sep 17 00:00:00 2001 From: Arturo Bernal Date: Fri, 18 Oct 2024 21:29:49 +0200 Subject: [PATCH] Remove H2 intercept. --- .../hc/core5/http2/impl/H2Processors.java | 2 - .../hc/core5/http2/protocol/H2RequestTE.java | 130 ------------------ .../core5/http2/protocol/TestH2RequestTE.java | 101 -------------- .../hc/core5/http/impl/HttpProcessors.java | 82 ++++++++++- .../hc/core5/http/protocol/RequestTE.java | 2 +- 5 files changed, 81 insertions(+), 236 deletions(-) delete mode 100644 httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestTE.java delete mode 100644 httpcore5-h2/src/test/java/org/apache/hc/core5/http2/protocol/TestH2RequestTE.java 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 6d3227f08..b6cf55270 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,7 +38,6 @@ 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; @@ -87,7 +86,6 @@ 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 deleted file mode 100644 index 11c6248f6..000000000 --- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/protocol/H2RequestTE.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * ==================================================================== - * 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 deleted file mode 100644 index 15c557e84..000000000 --- a/httpcore5-h2/src/test/java/org/apache/hc/core5/http2/protocol/TestH2RequestTE.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * ==================================================================== - * 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/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..5a47dd0ff 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; @@ -112,6 +113,43 @@ public static HttpProcessorBuilder customClient(final String agentInfo) { RequestExpectContinue.INSTANCE); } + /** + * Creates an {@link HttpProcessorBuilder} initialized with strict protocol interceptors + * for client-side HTTP/1.1 processing. + *

+ * This configuration enforces stricter validation and processing of client requests, + * ensuring compliance with the HTTP protocol. It includes interceptors for handling + * target hosts, content, connection controls, and TE header validation, among others. + * The user agent can be customized using the provided {@code agentInfo} parameter. + * + * @param agentInfo the user agent info to be included in the {@code User-Agent} header. + * If {@code null} or blank, a default value will be used. + * @return the {@link HttpProcessorBuilder} configured with strict client-side interceptors. + * @since 5.4 + */ + public static HttpProcessorBuilder strictClient(final String agentInfo) { + return HttpProcessorBuilder.create() + .addAll( + RequestTargetHost.INSTANCE, + RequestContent.INSTANCE, + RequestConnControl.INSTANCE, + RequestTE.INSTANCE, + new RequestUserAgent(!TextUtils.isBlank(agentInfo) ? agentInfo : + VersionInfo.getSoftwareInfo(SOFTWARE, "org.apache.hc.core5", HttpProcessors.class)), + RequestExpectContinue.INSTANCE); + } + + /** + * Creates {@link HttpProcessorBuilder} initialized with default protocol interceptors + * for client side HTTP/1.1 processing. + * + * @param agentInfo the agent info text or {@code null} for default. + * @return the processor builder. + */ + public static HttpProcessorBuilder customClient(final String agentInfo, final boolean strict) { + return strict ? strictClient(agentInfo) : customClient(agentInfo); + } + /** * Creates {@link HttpProcessor} initialized with default protocol interceptors * for client side HTTP/1.1 processing. @@ -120,7 +158,7 @@ public static HttpProcessorBuilder customClient(final String agentInfo) { * @return the processor. */ public static HttpProcessor client(final String agentInfo) { - return customClient(agentInfo).build(); + return client(agentInfo, false); } /** @@ -130,7 +168,47 @@ public static HttpProcessor client(final String agentInfo) { * @return the processor. */ public static HttpProcessor client() { - return customClient(null).build(); + return client(null); + } + + /** + * Creates an {@link HttpProcessor} for client-side HTTP/2 processing. + * This method allows the option to include strict protocol interceptors. + * + * @param agentInfo the agent info text or {@code null} for default. + * @param strict if {@code true}, strict protocol interceptors will be added, including the {@code TE} header validation. + * @return the configured HTTP processor. + * @since 5.4 + */ + public static HttpProcessor client(final String agentInfo, final boolean strict) { + return customClient(agentInfo, strict).build(); + } + + /** + * Creates an {@link HttpProcessor} for client-side HTTP/2 processing + * with strict protocol validation interceptors by default. + *

+ * Strict validation includes additional checks such as validating the {@code TE} header. + * + * @return the configured strict HTTP processor. + * @since 5.4 + */ + public static HttpProcessor clientStrict() { + return customClient(null, true).build(); + } + + /** + * Creates an {@link HttpProcessor} for client-side HTTP/2 processing + * with strict protocol validation interceptors, using the specified agent information. + *

+ * Strict validation includes additional checks such as validating the {@code TE} header. + * + * @param agentInfo the agent info text or {@code null} for default. + * @return the configured strict HTTP processor. + * @since 5.4 + */ + public static HttpProcessor clientStrict(final String agentInfo) { + return customClient(agentInfo, true).build(); } } 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 index bf2d542a6..bf8a453ae 100644 --- 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 @@ -54,7 +54,7 @@ *

* In case of HTTP/2, this validation is skipped, and another layer of logic handles the specifics of HTTP/2 compliance. * - * @since 5.5 + * @since 5.4 */ @Contract(threading = ThreadingBehavior.IMMUTABLE) public class RequestTE implements HttpRequestInterceptor {