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 19:33:34 UTC

[httpcomponents-core] 01/01: 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 dcb1ecda284d35cac9c7aeca59c2d13db7a26b2d
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Mon Dec 23 15:34:42 2019 +0100

    Support for status code 431 (Request Header Fields Too Large)
---
 .../core5/testing/classic/ClassicTestServer.java   |  9 +++--
 .../testing/classic/ClassicIntegrationTest.java    | 39 ++++++++++++++++++++--
 .../hc/core5/testing/nio/Http1IntegrationTest.java | 33 ++++++++++++++++++
 .../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 +--
 8 files changed, 110 insertions(+), 12 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..3c787db 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;
@@ -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());
@@ -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) {
@@ -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/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" +