You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2019/12/23 13:57:24 UTC

[httpcomponents-core] 04/04: Support for status code 431 (Request Header Fields Too Large)

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch development
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 04531ef0c0b3e58db25f00776ee58e1e97ce6790
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sat Dec 14 19:24:06 2019 +0100

    Support for status code 431 (Request Header Fields Too Large)
---
 .../core5/testing/classic/ClassicTestServer.java   |  9 ++-
 .../testing/classic/ClassicIntegrationTest.java    | 77 ++++++++++++++++------
 .../hc/core5/testing/nio/Http1IntegrationTest.java | 33 ++++++++++
 .../http/RequestHeaderFieldsTooLargeException.java | 58 ++++++++++++++++
 .../apache/hc/core5/http/impl/ServerSupport.java   |  3 +
 .../http/impl/io/DefaultHttpRequestParser.java     | 14 ++++
 .../apache/hc/core5/http/impl/io/HttpService.java  |  6 +-
 .../http/impl/nio/DefaultHttpRequestParser.java    | 14 ++++
 .../hc/core5/http/impl/io/TestRequestParser.java   |  4 +-
 9 files changed, 187 insertions(+), 31 deletions(-)

diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/ClassicTestServer.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/ClassicTestServer.java
index c7d4ff4..4abe8b2 100644
--- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/ClassicTestServer.java
+++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/classic/ClassicTestServer.java
@@ -99,7 +99,10 @@ public class ClassicTestServer {
         throw new IllegalStateException("Server not running");
     }
 
-    public void start(final HttpProcessor httpProcessor, final Decorator<HttpServerRequestHandler> handlerDecorator) throws IOException {
+    public void start(
+            final Http1Config http1Config,
+            final HttpProcessor httpProcessor,
+            final Decorator<HttpServerRequestHandler> handlerDecorator) throws IOException {
         if (serverRef.get() == null) {
             final HttpServerRequestHandler handler = new BasicHttpServerRequestHandler(registry);
             final HttpService httpService = new HttpService(
@@ -115,7 +118,7 @@ public class ClassicTestServer {
                     sslContext != null ? sslContext.getServerSocketFactory() : ServerSocketFactory.getDefault(),
                     new LoggingBHttpServerConnectionFactory(
                             sslContext != null ? URIScheme.HTTPS.id : URIScheme.HTTP.id,
-                            Http1Config.DEFAULT,
+                            http1Config != null ? http1Config : Http1Config.DEFAULT,
                             CharCodingConfig.DEFAULT),
                     null,
                     LoggingExceptionListener.INSTANCE);
@@ -128,7 +131,7 @@ public class ClassicTestServer {
     }
 
     public void start() throws IOException {
-        start(null, null);
+        start(null, null, null);
     }
 
     public void shutdown(final CloseMode closeMode) {
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 6971c4e..4ce02d6 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
@@ -57,6 +57,7 @@ import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.HttpVersion;
 import org.apache.hc.core5.http.Method;
 import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.config.Http1Config;
 import org.apache.hc.core5.http.io.HttpRequestHandler;
 import org.apache.hc.core5.http.io.HttpServerRequestHandler;
 import org.apache.hc.core5.http.io.SocketConfig;
@@ -90,8 +91,8 @@ public class ClassicIntegrationTest {
     @Parameterized.Parameters(name = "{0}")
     public static Collection<Object[]> protocols() {
         return Arrays.asList(new Object[][]{
-                { URIScheme.HTTP },
-                { URIScheme.HTTPS }
+                {URIScheme.HTTP},
+                {URIScheme.HTTPS}
         });
     }
 
@@ -108,7 +109,7 @@ public class ClassicIntegrationTest {
         @Override
         protected void before() throws Throwable {
             server = new ClassicTestServer(
-                    scheme == URIScheme.HTTPS  ? SSLTestContexts.createServerSSLContext() : null,
+                    scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null,
                     SocketConfig.custom()
                             .setSoTimeout(5, TimeUnit.SECONDS)
                             .build());
@@ -135,7 +136,7 @@ public class ClassicIntegrationTest {
         @Override
         protected void before() throws Throwable {
             client = new ClassicTestClient(
-                    scheme == URIScheme.HTTPS  ? SSLTestContexts.createClientSSLContext() : null,
+                    scheme == URIScheme.HTTPS ? SSLTestContexts.createClientSSLContext() : null,
                     SocketConfig.custom()
                             .setSoTimeout(5, TimeUnit.SECONDS)
                             .build());
@@ -489,7 +490,7 @@ public class ClassicIntegrationTest {
 
         });
 
-        this.server.start(null, new Decorator<HttpServerRequestHandler>() {
+        this.server.start(null, null, new Decorator<HttpServerRequestHandler>() {
 
             @Override
             public HttpServerRequestHandler decorate(final HttpServerRequestHandler handler) {
@@ -602,22 +603,22 @@ public class ClassicIntegrationTest {
 
         final String[] patterns = {
 
-            "0123456789ABCDEF",
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
-            "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that"
+                "0123456789ABCDEF",
+                "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
+                        "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
+                        "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
+                        "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
+                        "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
+                        "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
+                        "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
+                        "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
+                        "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
+                        "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
+                        "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
+                        "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
+                        "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
+                        "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that-" +
+                        "yadayada-blahblah-this-and-that-yadayada-blahblah-this-and-that"
         };
 
         // Initialize the server-side request handler
@@ -885,4 +886,38 @@ public class ClassicIntegrationTest {
         }
     }
 
+    @Test
+    public void testHeaderTooLarge() throws Exception {
+        this.server.registerHandler("*", new HttpRequestHandler() {
+
+            @Override
+            public void handle(
+                    final ClassicHttpRequest request,
+                    final ClassicHttpResponse response,
+                    final HttpContext context) throws HttpException, IOException {
+                response.setEntity(new StringEntity("All is well", StandardCharsets.US_ASCII));
+            }
+
+        });
+
+        this.server.start(
+                Http1Config.custom()
+                        .setMaxLineLength(100)
+                        .build(),
+                null,
+                null);
+        this.client.start();
+
+        final HttpCoreContext context = HttpCoreContext.create();
+        final HttpHost host = new HttpHost(scheme.id, "localhost", this.server.getPort());
+
+        final BasicClassicHttpRequest get1 = new BasicClassicHttpRequest(Method.GET, "/");
+        get1.setHeader("big-f-header", "1234567890123456789012345678901234567890123456789012345678901234567890" +
+                "1234567890123456789012345678901234567890");
+        try (final ClassicHttpResponse response1 = this.client.execute(host, get1, context)) {
+            Assert.assertEquals(431, response1.getCode());
+            EntityUtils.consume(response1.getEntity());
+        }
+    }
+
 }
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java
index 6132f15..79c1dc0 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java
@@ -1789,4 +1789,37 @@ public class Http1IntegrationTest extends InternalHttp1ServerTestBase {
         Assert.assertEquals("Boom!!!", entity1);
     }
 
+    @Test
+    public void testHeaderTooLarge() throws Exception {
+        server.register("/hello", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new SingleLineResponseHandler("Hi there");
+            }
+
+        });
+        final InetSocketAddress serverEndpoint = server.start(null, Http1Config.custom()
+                .setMaxLineLength(100)
+                .build());
+        client.start();
+
+        final Future<ClientSessionEndpoint> connectFuture = client.connect(
+                "localhost", serverEndpoint.getPort(), TIMEOUT);
+        final ClientSessionEndpoint streamEndpoint = connectFuture.get();
+
+        final HttpRequest request1 = new BasicHttpRequest(Method.GET, createRequestURI(serverEndpoint, "/hello"));
+        request1.setHeader("big-f-header", "1234567890123456789012345678901234567890123456789012345678901234567890" +
+                "1234567890123456789012345678901234567890");
+        final Future<Message<HttpResponse, String>> future1 = streamEndpoint.execute(
+                new BasicRequestProducer(request1, null),
+                new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
+        final Message<HttpResponse, String> result1 = future1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
+        Assert.assertNotNull(result1);
+        final HttpResponse response1 = result1.getHead();
+        Assert.assertNotNull(response1);
+        Assert.assertEquals(431, response1.getCode());
+        Assert.assertEquals("Maximum line length limit exceeded", result1.getBody());
+    }
+
 }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/RequestHeaderFieldsTooLargeException.java b/httpcore5/src/main/java/org/apache/hc/core5/http/RequestHeaderFieldsTooLargeException.java
new file mode 100644
index 0000000..ac6cbe3
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/RequestHeaderFieldsTooLargeException.java
@@ -0,0 +1,58 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.core5.http;
+
+/**
+ * Signals request header field length or total field size violation.
+ *
+ * @since 5.0
+ */
+public class RequestHeaderFieldsTooLargeException extends ProtocolException {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Creates a new RequestHeaderFieldsTooLargeException with the specified detail message.
+     *
+     * @param message The exception detail message
+     */
+    public RequestHeaderFieldsTooLargeException(final String message) {
+        super(message);
+    }
+
+    /**
+     * Creates a new RequestHeaderFieldsTooLargeException with the specified detail message and cause.
+     *
+     * @param message the exception detail message
+     * @param cause the {@code Throwable} that caused this exception, or {@code null}
+     * if the cause is unavailable, unknown, or not a {@code Throwable}
+     */
+    public RequestHeaderFieldsTooLargeException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/ServerSupport.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/ServerSupport.java
index 29cca6b..df6bc12 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/ServerSupport.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/ServerSupport.java
@@ -34,6 +34,7 @@ import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.MethodNotSupportedException;
 import org.apache.hc.core5.http.NotImplementedException;
 import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.RequestHeaderFieldsTooLargeException;
 import org.apache.hc.core5.http.UnsupportedHttpVersionException;
 
 /**
@@ -70,6 +71,8 @@ public class ServerSupport {
             code = HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED;
         } else if (ex instanceof NotImplementedException) {
             code = HttpStatus.SC_NOT_IMPLEMENTED;
+        } else if (ex instanceof RequestHeaderFieldsTooLargeException) {
+            code = HttpStatus.SC_REQUEST_HEADER_FIELDS_TOO_LARGE;
         } else if (ex instanceof ProtocolException) {
             code = HttpStatus.SC_BAD_REQUEST;
         } else {
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestParser.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestParser.java
index a1e8e59..55e6caf 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestParser.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/DefaultHttpRequestParser.java
@@ -28,12 +28,16 @@
 package org.apache.hc.core5.http.impl.io;
 
 import java.io.IOException;
+import java.io.InputStream;
 
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ConnectionClosedException;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpRequestFactory;
+import org.apache.hc.core5.http.MessageConstraintException;
+import org.apache.hc.core5.http.RequestHeaderFieldsTooLargeException;
 import org.apache.hc.core5.http.config.Http1Config;
+import org.apache.hc.core5.http.io.SessionInputBuffer;
 import org.apache.hc.core5.http.message.LineParser;
 import org.apache.hc.core5.http.message.RequestLine;
 import org.apache.hc.core5.util.CharArrayBuffer;
@@ -88,6 +92,16 @@ public class DefaultHttpRequestParser extends AbstractMessageParser<ClassicHttpR
     }
 
     @Override
+    public ClassicHttpRequest parse(
+            final SessionInputBuffer buffer, final InputStream inputStream) throws IOException, HttpException {
+        try {
+            return super.parse(buffer, inputStream);
+        } catch (final MessageConstraintException ex) {
+            throw new RequestHeaderFieldsTooLargeException(ex.getMessage(), ex);
+        }
+    }
+
+    @Override
     protected ClassicHttpRequest createMessage(final CharArrayBuffer buffer) throws IOException, HttpException {
         final RequestLine requestLine = getLineParser().parseRequestLine(buffer);
         final ClassicHttpRequest request = this.requestFactory.newHttpRequest(requestLine.getMethod(), requestLine.getUri());
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpService.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpService.java
index 31dd3a1..72ef10d 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpService.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/io/HttpService.java
@@ -266,11 +266,7 @@ public class HttpService {
      */
     protected void handleException(final HttpException ex, final ClassicHttpResponse response) {
         response.setCode(toStatusCode(ex));
-        String message = ex.getMessage();
-        if (message == null) {
-            message = ex.toString();
-        }
-        response.setEntity(new StringEntity(message, ContentType.TEXT_PLAIN));
+        response.setEntity(new StringEntity(ServerSupport.toErrorMessage(ex), ContentType.TEXT_PLAIN));
     }
 
     protected int toStatusCode(final Exception ex) {
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestParser.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestParser.java
index 0f61845..67724df 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestParser.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/nio/DefaultHttpRequestParser.java
@@ -27,12 +27,17 @@
 
 package org.apache.hc.core5.http.impl.nio;
 
+import java.io.IOException;
+
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpRequestFactory;
+import org.apache.hc.core5.http.MessageConstraintException;
+import org.apache.hc.core5.http.RequestHeaderFieldsTooLargeException;
 import org.apache.hc.core5.http.config.Http1Config;
 import org.apache.hc.core5.http.message.LineParser;
 import org.apache.hc.core5.http.message.RequestLine;
+import org.apache.hc.core5.http.nio.SessionInputBuffer;
 import org.apache.hc.core5.util.Args;
 import org.apache.hc.core5.util.CharArrayBuffer;
 
@@ -79,6 +84,15 @@ public class DefaultHttpRequestParser<T extends HttpRequest> extends AbstractMes
     }
 
     @Override
+    public T parse(final SessionInputBuffer sessionBuffer, final boolean endOfStream) throws IOException, HttpException {
+        try {
+            return super.parse(sessionBuffer, endOfStream);
+        } catch (final MessageConstraintException ex) {
+            throw new RequestHeaderFieldsTooLargeException(ex.getMessage(), ex);
+        }
+    }
+
+    @Override
     protected T createMessage(final CharArrayBuffer buffer) throws HttpException {
         final RequestLine requestLine = getLineParser().parseRequestLine(buffer);
         final T request = this.requestFactory.newHttpRequest(requestLine.getMethod(), requestLine.getUri());
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestRequestParser.java b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestRequestParser.java
index 918f7dd..0fe8a20 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestRequestParser.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/http/impl/io/TestRequestParser.java
@@ -33,8 +33,8 @@ import java.nio.charset.StandardCharsets;
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ConnectionClosedException;
 import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.MessageConstraintException;
 import org.apache.hc.core5.http.Method;
+import org.apache.hc.core5.http.RequestHeaderFieldsTooLargeException;
 import org.apache.hc.core5.http.config.Http1Config;
 import org.apache.hc.core5.http.io.SessionInputBuffer;
 import org.junit.Assert;
@@ -95,7 +95,7 @@ public class TestRequestParser {
         Assert.assertEquals(1, headers.length);
     }
 
-    @Test(expected = MessageConstraintException.class)
+    @Test(expected = RequestHeaderFieldsTooLargeException.class)
     public void testBasicMessageParsingTooManyLeadingEmptyLines() throws Exception {
         final String s =
                 "\r\n" +