diff --git a/reactor-netty5-http/src/main/java/reactor/netty5/http/server/Http2StreamBridgeServerHandler.java b/reactor-netty5-http/src/main/java/reactor/netty5/http/server/Http2StreamBridgeServerHandler.java index 22d2bd7126..247ccb402a 100644 --- a/reactor-netty5-http/src/main/java/reactor/netty5/http/server/Http2StreamBridgeServerHandler.java +++ b/reactor-netty5-http/src/main/java/reactor/netty5/http/server/Http2StreamBridgeServerHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2023 VMware, Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2018-2024 VMware, Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -134,12 +134,13 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { readTimeout, requestTimeout, secured, - timestamp); + timestamp, + true); } catch (RuntimeException e) { pendingResponse = false; request.setDecoderResult(DecoderResult.failure(e.getCause() != null ? e.getCause() : e)); - HttpServerOperations.sendDecodingFailures(ctx, listener, secured, e, msg, httpMessageLogFactory, true, timestamp, connectionInfo, remoteAddress); + HttpServerOperations.sendDecodingFailures(ctx, listener, secured, e, msg, httpMessageLogFactory, true, timestamp, connectionInfo, remoteAddress, true); return; } ops.bind(); diff --git a/reactor-netty5-http/src/main/java/reactor/netty5/http/server/HttpServerConfig.java b/reactor-netty5-http/src/main/java/reactor/netty5/http/server/HttpServerConfig.java index cf23841daa..4a3d76cfee 100644 --- a/reactor-netty5-http/src/main/java/reactor/netty5/http/server/HttpServerConfig.java +++ b/reactor-netty5-http/src/main/java/reactor/netty5/http/server/HttpServerConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2023 VMware, Inc. or its affiliates, All Rights Reserved. + * Copyright (c) 2020-2024 VMware, Inc. or its affiliates, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -636,7 +636,7 @@ static void configureHttp11OrH2CleartextPipeline(ChannelPipeline p, NettyPipeline.HttpTrafficHandler, new HttpTrafficHandler(compressPredicate, formDecoderProvider, forwardedHeaderHandler, httpMessageLogFactory, idleTimeout, listener, mapHandle, maxKeepAliveRequests, - readTimeout, requestTimeout)); + readTimeout, requestTimeout, decoder.validateHeaders())); if (accessLogEnabled) { p.addAfter(NettyPipeline.HttpTrafficHandler, NettyPipeline.AccessLogHandler, AccessLogHandlerFactory.H1.create(accessLog)); @@ -702,7 +702,7 @@ static void configureHttp11Pipeline(ChannelPipeline p, NettyPipeline.HttpTrafficHandler, new HttpTrafficHandler(compressPredicate, formDecoderProvider, forwardedHeaderHandler, httpMessageLogFactory, idleTimeout, listener, mapHandle, maxKeepAliveRequests, - readTimeout, requestTimeout)); + readTimeout, requestTimeout, decoder.validateHeaders())); if (accessLogEnabled) { p.addAfter(NettyPipeline.HttpTrafficHandler, NettyPipeline.AccessLogHandler, AccessLogHandlerFactory.H1.create(accessLog)); diff --git a/reactor-netty5-http/src/main/java/reactor/netty5/http/server/HttpServerOperations.java b/reactor-netty5-http/src/main/java/reactor/netty5/http/server/HttpServerOperations.java index 28438b15ae..b4c323bd6a 100644 --- a/reactor-netty5-http/src/main/java/reactor/netty5/http/server/HttpServerOperations.java +++ b/reactor-netty5-http/src/main/java/reactor/netty5/http/server/HttpServerOperations.java @@ -99,6 +99,7 @@ import static io.netty5.buffer.DefaultBufferAllocators.preferredAllocator; import static io.netty5.handler.codec.http.HttpUtil.isTransferEncodingChunked; +import static io.netty5.handler.codec.http.headers.DefaultHttpHeadersFactory.headersFactory; import static io.netty5.handler.codec.http.headers.DefaultHttpHeadersFactory.trailersFactory; import static reactor.netty5.ReactorNetty.format; import static reactor.netty5.http.server.HttpServerFormDecoderProvider.DEFAULT_FORM_DECODER_SPEC; @@ -126,6 +127,7 @@ class HttpServerOperations extends HttpOperations compressionPredicate; boolean isWebsocket; @@ -160,6 +162,7 @@ class HttpServerOperations extends HttpOperations withConn @Override protected HttpMessage newFullBodyMessage(Buffer body) { HttpResponse res = - new DefaultFullHttpResponse(version(), status(), body); + new DefaultFullHttpResponse(version(), status(), body, + headersFactory().withNameValidation(validateHeaders).withValueValidation(validateHeaders), + trailersFactory().withNameValidation(validateHeaders).withValueValidation(validateHeaders)); if (!HttpMethod.HEAD.equals(method())) { responseHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING); @@ -406,7 +414,9 @@ public Flux receiveObject() { if (!hasSentHeaders()) { return channel().writeAndFlush( new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE, - preferredAllocator().allocate(0))); + preferredAllocator().allocate(0), + headersFactory().withNameValidation(validateHeaders).withValueValidation(validateHeaders), + trailersFactory().withNameValidation(validateHeaders).withValueValidation(validateHeaders))); } return channel().newSucceededFuture(); }) @@ -960,7 +970,8 @@ else if (HttpUtil.getContentLength(nettyResponse, -1) != -1) { responseHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING); } - return new DefaultFullHttpResponse(version(), status(), body, responseHeaders, trailersFactory().newHeaders()); + return new DefaultFullHttpResponse(version(), status(), body, responseHeaders, + trailersFactory().withNameValidation(validateHeaders).withValueValidation(validateHeaders).newHeaders()); } static long requestsCounter(Channel channel) { @@ -982,8 +993,9 @@ static void sendDecodingFailures( HttpMessageLogFactory httpMessageLogFactory, @Nullable ZonedDateTime timestamp, @Nullable ConnectionInfo connectionInfo, - SocketAddress remoteAddress) { - sendDecodingFailures(ctx, listener, secure, t, msg, httpMessageLogFactory, false, timestamp, connectionInfo, remoteAddress); + SocketAddress remoteAddress, + boolean validateHeaders) { + sendDecodingFailures(ctx, listener, secure, t, msg, httpMessageLogFactory, false, timestamp, connectionInfo, remoteAddress, validateHeaders); } @SuppressWarnings("FutureReturnValueIgnored") @@ -997,7 +1009,8 @@ static void sendDecodingFailures( boolean isHttp2, @Nullable ZonedDateTime timestamp, @Nullable ConnectionInfo connectionInfo, - SocketAddress remoteAddress) { + SocketAddress remoteAddress, + boolean validateHeaders) { Throwable cause = t.getCause() != null ? t.getCause() : t; @@ -1020,7 +1033,9 @@ else if (cause instanceof TooLongHttpHeaderException) { status = HttpResponseStatus.BAD_REQUEST; } FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, - ctx.bufferAllocator().allocate(0)); + ctx.bufferAllocator().allocate(0), + headersFactory().withNameValidation(validateHeaders).withValueValidation(validateHeaders), + trailersFactory().withNameValidation(validateHeaders).withValueValidation(validateHeaders)); response.headers() .set(HttpHeaderNames.CONTENT_LENGTH, HttpHeaderValues.ZERO) .set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE); @@ -1031,7 +1046,7 @@ else if (cause instanceof TooLongHttpHeaderException) { if (msg instanceof HttpRequest request) { ops = new FailedHttpServerRequest(conn, listener, request, response, httpMessageLogFactory, isHttp2, secure, timestamp == null ? ZonedDateTime.now(ReactorNetty.ZONE_ID_SYSTEM) : timestamp, - connectionInfo == null ? new ConnectionInfo(ctx.channel().localAddress(), remoteAddress, secure) : connectionInfo); + connectionInfo == null ? new ConnectionInfo(ctx.channel().localAddress(), remoteAddress, secure) : connectionInfo, validateHeaders); ops.bind(); } else { @@ -1207,9 +1222,10 @@ static final class FailedHttpServerRequest extends HttpServerOperations { boolean isHttp2, boolean secure, ZonedDateTime timestamp, - ConnectionInfo connectionInfo) { + ConnectionInfo connectionInfo, + boolean validateHeaders) { super(c, listener, nettyRequest, null, connectionInfo, - DEFAULT_FORM_DECODER_SPEC, httpMessageLogFactory, isHttp2, null, null, null, secure, timestamp); + DEFAULT_FORM_DECODER_SPEC, httpMessageLogFactory, isHttp2, null, null, null, secure, timestamp, validateHeaders); this.customResponse = nettyResponse; } diff --git a/reactor-netty5-http/src/main/java/reactor/netty5/http/server/HttpTrafficHandler.java b/reactor-netty5-http/src/main/java/reactor/netty5/http/server/HttpTrafficHandler.java index 82b62a15b2..1fa83cb341 100644 --- a/reactor-netty5-http/src/main/java/reactor/netty5/http/server/HttpTrafficHandler.java +++ b/reactor-netty5-http/src/main/java/reactor/netty5/http/server/HttpTrafficHandler.java @@ -81,6 +81,7 @@ final class HttpTrafficHandler extends ChannelHandlerAdapter implements Runnable final int maxKeepAliveRequests; final Duration readTimeout; final Duration requestTimeout; + final boolean validateHeaders; ChannelHandlerContext ctx; @@ -111,7 +112,8 @@ final class HttpTrafficHandler extends ChannelHandlerAdapter implements Runnable @Nullable BiFunction, ? super Connection, ? extends Mono> mapHandle, int maxKeepAliveRequests, @Nullable Duration readTimeout, - @Nullable Duration requestTimeout) { + @Nullable Duration requestTimeout, + boolean validateHeaders) { this.listener = listener; this.formDecoderProvider = formDecoderProvider; this.forwardedHeaderHandler = forwardedHeaderHandler; @@ -122,6 +124,7 @@ final class HttpTrafficHandler extends ChannelHandlerAdapter implements Runnable this.maxKeepAliveRequests = maxKeepAliveRequests; this.readTimeout = readTimeout; this.requestTimeout = requestTimeout; + this.validateHeaders = validateHeaders; } @Override @@ -177,7 +180,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { IllegalStateException e = new IllegalStateException( "Unexpected request [" + request.method() + " " + request.uri() + " HTTP/2.0]"); request.setDecoderResult(DecoderResult.failure(e.getCause() != null ? e.getCause() : e)); - sendDecodingFailures(e, msg); + sendDecodingFailures(e, msg, validateHeaders); return; } @@ -207,7 +210,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { DecoderResult decoderResult = request.decoderResult(); if (decoderResult.isFailure()) { - sendDecodingFailures(decoderResult.cause(), msg); + sendDecodingFailures(decoderResult.cause(), msg, validateHeaders); return; } @@ -232,11 +235,12 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) { readTimeout, requestTimeout, secure, - timestamp); + timestamp, + validateHeaders); } catch (RuntimeException e) { request.setDecoderResult(DecoderResult.failure(e.getCause() != null ? e.getCause() : e)); - sendDecodingFailures(e, msg, timestamp, connectionInfo); + sendDecodingFailures(e, msg, timestamp, connectionInfo, validateHeaders); return; } ops.bind(); @@ -251,7 +255,7 @@ else if (persistentConnection && pendingResponses == 0) { if (msg instanceof LastHttpContent lastHttpContent) { DecoderResult decoderResult = lastHttpContent.decoderResult(); if (decoderResult.isFailure()) { - sendDecodingFailures(decoderResult.cause(), msg); + sendDecodingFailures(decoderResult.cause(), msg, validateHeaders); return; } @@ -283,7 +287,7 @@ else if (overflow) { if (msg instanceof DecoderResultProvider decoderResultProvider) { DecoderResult decoderResult = decoderResultProvider.decoderResult(); if (decoderResult.isFailure()) { - sendDecodingFailures(decoderResult.cause(), msg); + sendDecodingFailures(decoderResult.cause(), msg, validateHeaders); return; } } @@ -333,14 +337,14 @@ public void flush(ChannelHandlerContext ctx) { } } - void sendDecodingFailures(Throwable t, Object msg) { - sendDecodingFailures(t, msg, null, null); + void sendDecodingFailures(Throwable t, Object msg, boolean validateHeaders) { + sendDecodingFailures(t, msg, null, null, validateHeaders); } - void sendDecodingFailures(Throwable t, Object msg, @Nullable ZonedDateTime timestamp, @Nullable ConnectionInfo connectionInfo) { + void sendDecodingFailures(Throwable t, Object msg, @Nullable ZonedDateTime timestamp, @Nullable ConnectionInfo connectionInfo, boolean validateHeaders) { persistentConnection = false; HttpServerOperations.sendDecodingFailures(ctx, listener, secure, t, msg, httpMessageLogFactory, timestamp, connectionInfo, - remoteAddress); + remoteAddress, validateHeaders); } void doPipeline(ChannelHandlerContext ctx, Object msg) { @@ -477,7 +481,7 @@ public void run() { DecoderResult decoderResult = nextRequest.decoderResult(); if (decoderResult.isFailure()) { - sendDecodingFailures(decoderResult.cause(), nextRequest, holder.timestamp, null); + sendDecodingFailures(decoderResult.cause(), nextRequest, holder.timestamp, null, validateHeaders); discard(); return; } @@ -502,11 +506,12 @@ public void run() { readTimeout, requestTimeout, secure, - holder.timestamp); + holder.timestamp, + validateHeaders); } catch (RuntimeException e) { holder.request.setDecoderResult(DecoderResult.failure(e.getCause() != null ? e.getCause() : e)); - sendDecodingFailures(e, holder.request, holder.timestamp, connectionInfo); + sendDecodingFailures(e, holder.request, holder.timestamp, connectionInfo, validateHeaders); return; } ops.bind(); diff --git a/reactor-netty5-http/src/test/java/reactor/netty5/http/server/HttpServerTests.java b/reactor-netty5-http/src/test/java/reactor/netty5/http/server/HttpServerTests.java index 3126b741a3..08e707646c 100644 --- a/reactor-netty5-http/src/test/java/reactor/netty5/http/server/HttpServerTests.java +++ b/reactor-netty5-http/src/test/java/reactor/netty5/http/server/HttpServerTests.java @@ -1995,7 +1995,8 @@ private void doTestStatus(HttpResponseStatus status) { null, null, false, - ZonedDateTime.now(ReactorNetty.ZONE_ID_SYSTEM)); + ZonedDateTime.now(ReactorNetty.ZONE_ID_SYSTEM), + true); ops.status(status); try (Buffer buffer = channel.bufferAllocator().allocate(0)) { HttpMessage response = ops.newFullBodyMessage(buffer); @@ -2993,7 +2994,8 @@ private void doTestIsFormUrlencoded(String headerValue, boolean expectation) { null, null, false, - ZonedDateTime.now(ReactorNetty.ZONE_ID_SYSTEM)); + ZonedDateTime.now(ReactorNetty.ZONE_ID_SYSTEM), + true); assertThat(ops.isFormUrlencoded()).isEqualTo(expectation); channel.close(); }