File netty-CVE-2021-21295.patch of Package netty.18722

--- netty-netty-4.1.13.Final/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java	2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java	2021-03-12 11:48:15.242277753 +0100
@@ -15,6 +15,8 @@
  */
 package io.netty.handler.codec.http;
 
+import static io.netty.util.internal.ObjectUtil.checkPositive;
+
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelHandlerContext;
@@ -105,6 +107,7 @@
     private final int maxChunkSize;
     private final boolean chunkedSupported;
     protected final boolean validateHeaders;
+	private final boolean allowDuplicateContentLengths;
     private final HeaderParser headerParser;
     private final LineParser lineParser;
 
@@ -165,9 +168,20 @@
         this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, validateHeaders, 128);
     }
 
+    /**
+     * Creates a new instance with the specified parameters.
+     */
     protected HttpObjectDecoder(
             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
             boolean chunkedSupported, boolean validateHeaders, int initialBufferSize) {
+        this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, validateHeaders, initialBufferSize,
+             false);
+    }
+
+    protected HttpObjectDecoder(
+            int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
+            boolean chunkedSupported, boolean validateHeaders, int initialBufferSize,
+            boolean allowDuplicateContentLengths) {
         if (maxInitialLineLength <= 0) {
             throw new IllegalArgumentException(
                     "maxInitialLineLength must be a positive integer: " +
@@ -189,6 +203,7 @@
         this.maxChunkSize = maxChunkSize;
         this.chunkedSupported = chunkedSupported;
         this.validateHeaders = validateHeaders;
+        this.allowDuplicateContentLengths = allowDuplicateContentLengths;
     }
 
     @Override
@@ -602,6 +617,19 @@
         value = null;
 
         State nextState;
+        List<String> contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
+        if (!contentLengthFields.isEmpty()) {
+            HttpVersion version = message.protocolVersion();
+            boolean isHttp10OrEarlier = version.majorVersion() < 1 || (version.majorVersion() == 1
+                    && version.minorVersion() == 0);
+            // Guard against multiple Content-Length headers as stated in
+            // https://tools.ietf.org/html/rfc7230#section-3.3.2:
+            contentLength = HttpUtil.normalizeAndGetContentLength(contentLengthFields,
+                    isHttp10OrEarlier, allowDuplicateContentLengths);
+            if (contentLength != -1) {
+                headers.set(HttpHeaderNames.CONTENT_LENGTH, contentLength);
+            }
+        }
 
         if (isContentAlwaysEmpty(message)) {
             HttpUtil.setTransferEncodingChunked(message, false);
--- netty-netty-4.1.13.Final/codec-http/src/main/java/io/netty/handler/codec/http/HttpStatusClass.java	2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec-http/src/main/java/io/netty/handler/codec/http/HttpStatusClass.java	2021-03-12 11:48:15.242277753 +0100
@@ -74,6 +74,27 @@
         return UNKNOWN;
     }
 
+    /**
+     * Returns the class of the specified HTTP status code.
+     * @param code Just the numeric portion of the http status code.
+     */
+    public static HttpStatusClass valueOf(CharSequence code) {
+        if (code != null && code.length() == 3) {
+            char c0 = code.charAt(0);
+            return isDigit(c0) && isDigit(code.charAt(1)) && isDigit(code.charAt(2)) ? valueOf(digit(c0) * 100)
+                                                                                     : UNKNOWN;
+        }
+        return UNKNOWN;
+    }
+
+    private static int digit(char c) {
+        return c - '0';
+    }
+
+    private static boolean isDigit(char c) {
+        return c >= '0' && c <= '9';
+    }
+
     private final int min;
     private final int max;
     private final AsciiString defaultReasonPhrase;
--- netty-netty-4.1.13.Final/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java	2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java	2021-03-12 11:48:15.242277753 +0100
@@ -18,6 +18,7 @@
 import io.netty.util.AsciiString;
 import io.netty.util.CharsetUtil;
 
+import java.net.InetSocketAddress;
 import java.net.URI;
 import java.util.ArrayList;
 import java.nio.charset.Charset;
@@ -25,6 +26,15 @@
 import java.util.Iterator;
 import java.util.List;
 
+import io.netty.handler.codec.Headers;
+import io.netty.util.AsciiString;
+import io.netty.util.CharsetUtil;
+import io.netty.util.NetUtil;
+import io.netty.util.internal.ObjectUtil;
+import io.netty.util.internal.UnstableApi;
+
+import static io.netty.util.internal.StringUtil.COMMA;
+
 /**
  * Utility methods useful in the HTTP context.
  */
@@ -40,6 +50,7 @@
     static final EmptyHttpHeaders EMPTY_HEADERS = new EmptyHttpHeaders();
     private static final AsciiString CHARSET_EQUALS = AsciiString.of(HttpHeaderValues.CHARSET + "=");
     private static final AsciiString SEMICOLON = AsciiString.of(";");
+    private static final String COMMA_STRING = String.valueOf(COMMA);
 
     private HttpUtil() { }
 
@@ -519,4 +530,103 @@
             return contentTypeValue.length() > 0 ? contentTypeValue : null;
         }
     }
+
+    /**
+     * Formats the host string of an address so it can be used for computing an HTTP component
+     * such as a URL or a Host header
+     *
+     * @param addr the address
+     * @return the formatted String
+     */
+    public static String formatHostnameForHttp(InetSocketAddress addr) {
+        String hostString = NetUtil.getHostname(addr);
+        if (NetUtil.isValidIpV6Address(hostString)) {
+            if (!addr.isUnresolved()) {
+                hostString = NetUtil.toAddressString(addr.getAddress());
+            }
+            return '[' + hostString + ']';
+        }
+        return hostString;
+    }
+
+    /**
+     * Validates, and optionally extracts the content length from headers. This method is not intended for
+     * general use, but is here to be shared between HTTP/1 and HTTP/2 parsing.
+     *
+     * @param contentLengthFields the content-length header fields.
+     * @param isHttp10OrEarlier {@code true} if we are handling HTTP/1.0 or earlier
+     * @param allowDuplicateContentLengths {@code true}  if multiple, identical-value content lengths should be allowed.
+     * @return the normalized content length from the headers or {@code -1} if the fields were empty.
+     * @throws IllegalArgumentException if the content-length fields are not valid
+     */
+    @UnstableApi
+    public static long normalizeAndGetContentLength(
+            List<? extends CharSequence> contentLengthFields, boolean isHttp10OrEarlier,
+            boolean allowDuplicateContentLengths) {
+        if (contentLengthFields.isEmpty()) {
+            return -1;
+        }
+
+        // Guard against multiple Content-Length headers as stated in
+        // https://tools.ietf.org/html/rfc7230#section-3.3.2:
+        //
+        // If a message is received that has multiple Content-Length header
+        //   fields with field-values consisting of the same decimal value, or a
+        //   single Content-Length header field with a field value containing a
+        //   list of identical decimal values (e.g., "Content-Length: 42, 42"),
+        //   indicating that duplicate Content-Length header fields have been
+        //   generated or combined by an upstream message processor, then the
+        //   recipient MUST either reject the message as invalid or replace the
+        //   duplicated field-values with a single valid Content-Length field
+        //   containing that decimal value prior to determining the message body
+        //   length or forwarding the message.
+        String firstField = contentLengthFields.get(0).toString();
+        boolean multipleContentLengths =
+                contentLengthFields.size() > 1 || firstField.indexOf(COMMA) >= 0;
+
+        if (multipleContentLengths && !isHttp10OrEarlier) {
+            if (allowDuplicateContentLengths) {
+                // Find and enforce that all Content-Length values are the same
+                String firstValue = null;
+                for (CharSequence field : contentLengthFields) {
+                    String[] tokens = field.toString().split(COMMA_STRING, -1);
+                    for (String token : tokens) {
+                        String trimmed = token.trim();
+                        if (firstValue == null) {
+                            firstValue = trimmed;
+                        } else if (!trimmed.equals(firstValue)) {
+                            throw new IllegalArgumentException(
+                                    "Multiple Content-Length values found: " + contentLengthFields);
+                        }
+                    }
+                }
+                // Replace the duplicated field-values with a single valid Content-Length field
+                firstField = firstValue;
+            } else {
+                // Reject the message as invalid
+                throw new IllegalArgumentException(
+                        "Multiple Content-Length values found: " + contentLengthFields);
+            }
+        }
+        // Ensure we not allow sign as part of the content-length:
+        // See https://github.com/squid-cache/squid/security/advisories/GHSA-qf3v-rc95-96j5
+        if (!Character.isDigit(firstField.charAt(0))) {
+            // Reject the message as invalid
+            throw new IllegalArgumentException(
+                    "Content-Length value is not a number: " + firstField);
+        }
+        try {
+            final long value = Long.parseLong(firstField);
+            if (value < 0) {
+                // Reject the message as invalid
+                throw new IllegalArgumentException(
+                        "Content-Length value must be >=0: " + value);
+            }
+            return value;
+        } catch (NumberFormatException e) {
+            // Reject the message as invalid
+            throw new IllegalArgumentException(
+                    "Content-Length value is not a number: " + firstField, e);
+        }
+    }
 }
--- netty-netty-4.1.13.Final/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java	2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoder.java	2021-03-12 11:48:15.242277753 +0100
@@ -16,13 +16,18 @@
 
 import io.netty.buffer.ByteBuf;
 import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpStatusClass;
+import io.netty.handler.codec.http.HttpUtil;
 import io.netty.handler.codec.http2.Http2Connection.Endpoint;
+import io.netty.util.internal.SystemPropertyUtil;
 import io.netty.util.internal.UnstableApi;
 import io.netty.util.internal.logging.InternalLogger;
 import io.netty.util.internal.logging.InternalLoggerFactory;
 
 import java.util.List;
 
+import static io.netty.handler.codec.http.HttpStatusClass.INFORMATIONAL;
 import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
 import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
 import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
@@ -47,6 +52,8 @@
  */
 @UnstableApi
 public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
+    private static final boolean VALIDATE_CONTENT_LENGTH =
+            SystemPropertyUtil.getBoolean("io.netty.http2.validateContentLength", true);
     private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultHttp2ConnectionDecoder.class);
     private Http2FrameListener internalFrameListener = new PrefaceFrameListener();
     private final Http2Connection connection;
@@ -55,6 +62,7 @@
     private final Http2FrameReader frameReader;
     private Http2FrameListener listener;
     private final Http2PromisedRequestVerifier requestVerifier;
+    private final Http2Connection.PropertyKey contentLengthKey;
 
     public DefaultHttp2ConnectionDecoder(Http2Connection connection,
                                          Http2ConnectionEncoder encoder,
@@ -67,6 +75,7 @@
                                          Http2FrameReader frameReader,
                                          Http2PromisedRequestVerifier requestVerifier) {
         this.connection = checkNotNull(connection, "connection");
+        contentLengthKey = this.connection.newKey();
         this.frameReader = checkNotNull(frameReader, "frameReader");
         this.encoder = checkNotNull(encoder, "encoder");
         this.requestVerifier = checkNotNull(requestVerifier, "requestVerifier");
@@ -169,6 +178,23 @@
         listener.onUnknownFrame(ctx, frameType, streamId, flags, payload);
     }
 
+    // See https://tools.ietf.org/html/rfc7540#section-8.1.2.6
+    private void verifyContentLength(Http2Stream stream, int data, boolean isEnd) throws Http2Exception {
+        if (!VALIDATE_CONTENT_LENGTH) {
+            return;
+        }
+        ContentLength contentLength = stream.getProperty(contentLengthKey);
+        if (contentLength != null) {
+            try {
+                contentLength.increaseReceivedBytes(connection.isServer(), stream.id(), data, isEnd);
+            } finally {
+                if (isEnd) {
+                    stream.removeProperty(contentLengthKey);
+                }
+            }
+        }
+    }
+
     /**
      * Handles all inbound frames from the network.
      */
@@ -178,7 +204,8 @@
                               boolean endOfStream) throws Http2Exception {
             Http2Stream stream = connection.stream(streamId);
             Http2LocalFlowController flowController = flowController();
-            int bytesToReturn = data.readableBytes() + padding;
+            int readable = data.readableBytes();
+            int bytesToReturn = readable + padding;
 
             final boolean shouldIgnore;
             try {
@@ -233,6 +259,8 @@
                     throw error;
                 }
 
+                verifyContentLength(stream, readable, endOfStream);
+
                 // Call back the application and retrieve the number of bytes that have been
                 // immediately processed.
                 bytesToReturn = listener.onDataRead(ctx, streamId, data, padding, endOfStream);
@@ -282,6 +310,14 @@
                 return;
             }
 
+            boolean isInformational = !connection.isServer() &&
+                    HttpStatusClass.valueOf(headers.status()) == INFORMATIONAL;
+            if ((isInformational || !endOfStream) && stream.isHeadersReceived() || stream.isTrailersReceived()) {
+                throw streamError(streamId, PROTOCOL_ERROR,
+                                  "Stream %d received too many headers EOS: %s state: %s",
+                                  streamId, endOfStream, stream.state());
+            }
+
             switch (stream.state()) {
                 case RESERVED_REMOTE:
                     stream.open(endOfStream);
@@ -305,15 +341,36 @@
                             stream.state());
             }
 
-            encoder.flowController().updateDependencyTree(streamId, streamDependency, weight, exclusive);
-
-            listener.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endOfStream);
+            if (!stream.isHeadersReceived()) {
+                // extract the content-length header
+                List<? extends CharSequence> contentLength = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
+                if (contentLength != null && !contentLength.isEmpty()) {
+                    try {
+                        long cLength = HttpUtil.normalizeAndGetContentLength(contentLength, false, true);
+                        if (cLength != -1) {
+                            headers.setLong(HttpHeaderNames.CONTENT_LENGTH, cLength);
+                            stream.setProperty(contentLengthKey, new ContentLength(cLength));
+                        }
+                    } catch (IllegalArgumentException e) {
+                        throw streamError(stream.id(), PROTOCOL_ERROR,
+                                "Multiple content-length headers received", e);
+                    }
+                }
+            }
 
+            stream.headersReceived(isInformational);
+            try {
+                verifyContentLength(stream, 0, endOfStream);
+                encoder.flowController().updateDependencyTree(streamId, streamDependency, weight, exclusive);
+                listener.onHeadersRead(ctx, streamId, headers, streamDependency,
+                        weight, exclusive, padding, endOfStream);
+            } finally {
             // If the headers completes this stream, close it.
             if (endOfStream) {
                 lifecycleManager.closeStreamRemote(stream, ctx.newSucceededFuture());
             }
         }
+        }
 
         @Override
         public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
@@ -662,4 +719,40 @@
             onUnknownFrame0(ctx, frameType, streamId, flags, payload);
         }
     }
+
+    private static final class ContentLength {
+        private final long expected;
+        private long seen;
+
+        ContentLength(long expected) {
+            this.expected = expected;
+        }
+
+        void increaseReceivedBytes(boolean server, int streamId, int bytes, boolean isEnd) throws Http2Exception {
+            seen += bytes;
+            // Check for overflow
+            if (seen < 0) {
+                throw streamError(streamId, PROTOCOL_ERROR,
+                        "Received amount of data did overflow and so not match content-length header %d", expected);
+            }
+            // Check if we received more data then what was advertised via the content-length header.
+            if (seen > expected) {
+                throw streamError(streamId, PROTOCOL_ERROR,
+                        "Received amount of data %d does not match content-length header %d", seen, expected);
+            }
+
+            if (isEnd) {
+                if (seen == 0 && !server) {
+                    // This may be a response to a HEAD request, let's just allow it.
+                    return;
+                }
+
+                // Check that we really saw what was told via the content-length header.
+                if (expected > seen) {
+                    throw streamError(streamId, PROTOCOL_ERROR,
+                            "Received amount of data %d does not match content-length header %d", seen, expected);
+                }
+            }
+        }
+    }
 }
--- netty-netty-4.1.13.Final/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java	2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java	2021-03-12 11:48:15.242277753 +0100
@@ -375,7 +375,10 @@
     private class DefaultStream implements Http2Stream {
         private static final byte SENT_STATE_RST = 0x1;
         private static final byte SENT_STATE_HEADERS = 0x2;
-        private static final byte SENT_STATE_PUSHPROMISE = 0x4;
+        private static final byte SENT_STATE_TRAILERS = 0x4;
+        private static final byte SENT_STATE_PUSHPROMISE = 0x8;
+        private static final byte RECV_STATE_HEADERS = 0x10;
+        private static final byte RECV_STATE_TRAILERS = 0x20;
         private final int id;
         private final PropertyMap properties = new PropertyMap();
         private State state;
@@ -419,6 +422,29 @@
         }
 
         @Override
+        public boolean isTrailersSent() {
+            return (sentState & SENT_STATE_TRAILERS) != 0;
+        }
+
+        @Override
+        public Http2Stream headersReceived(boolean isInformational) {
+            if (!isInformational) {
+                sentState |= isHeadersReceived() ? RECV_STATE_TRAILERS : RECV_STATE_HEADERS;
+            }
+            return this;
+        }
+
+        @Override
+        public boolean isHeadersReceived() {
+            return (sentState & RECV_STATE_HEADERS) != 0;
+        }
+
+        @Override
+        public boolean isTrailersReceived() {
+            return (sentState & RECV_STATE_TRAILERS) != 0;
+        }
+
+        @Override
         public Http2Stream pushPromiseSent() {
             sentState |= SENT_STATE_PUSHPROMISE;
             return this;
Only in netty-netty-4.1.13.Final/codec-http2/src/main/java/io/netty/handler/codec/http2: Http2SettingsReceivedConsumer.java
--- netty-netty-4.1.13.Final/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Stream.java	2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Stream.java	2021-03-12 11:48:15.242277753 +0100
@@ -140,6 +140,29 @@
     boolean isHeadersSent();
 
     /**
+     * Indicates whether or not trailers were sent to the remote endpoint.
+     */
+    boolean isTrailersSent();
+
+    /**
+     * Indicates that headers have been received. The first call to this method would be for the initial headers
+     * (see {@link #isHeadersReceived()}} and the second call would indicate the trailers
+     * (see {@link #isTrailersReceived()}).
+     * @param isInformational {@code true} if the headers contain an informational status code (for responses only).
+     */
+    Http2Stream headersReceived(boolean isInformational);
+
+    /**
+     * Indicates whether or not the initial headers have been received.
+     */
+    boolean isHeadersReceived();
+
+    /**
+     * Indicates whether or not the trailers have been received.
+     */
+    boolean isTrailersReceived();
+
+    /**
      * Indicates that a push promise was sent to the remote endpoint.
      */
     Http2Stream pushPromiseSent();
--- netty-netty-4.1.13.Final/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java	2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionDecoderTest.java	2021-03-12 11:48:15.242277753 +0100
@@ -21,16 +21,21 @@
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelPromise;
 import io.netty.channel.DefaultChannelPromise;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpResponseStatus;
 import junit.framework.AssertionFailedError;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
 import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
@@ -126,6 +131,21 @@
         when(stream.id()).thenReturn(STREAM_ID);
         when(stream.state()).thenReturn(OPEN);
         when(stream.open(anyBoolean())).thenReturn(stream);
+
+        final Map<Object, Object> properties = new IdentityHashMap<Object, Object>();
+        when(stream.getProperty(ArgumentMatchers.<Http2Connection.PropertyKey>any())).thenAnswer(new Answer<Object>() {
+            @Override
+            public Object answer(InvocationOnMock invocationOnMock) {
+                return properties.get(invocationOnMock.getArgument(0));
+            }
+        });
+        when(stream.setProperty(ArgumentMatchers.<Http2Connection.PropertyKey>any(), any())).then(new Answer<Object>() {
+            @Override
+            public Object answer(InvocationOnMock invocationOnMock) {
+                return properties.put(invocationOnMock.getArgument(0), invocationOnMock.getArgument(1));
+            }
+        });
+
         when(pushStream.id()).thenReturn(PUSH_STREAM_ID);
         doAnswer(new Answer<Http2Stream>() {
             @Override
@@ -652,6 +672,115 @@
         verify(listener).onGoAwayRead(eq(ctx), eq(1), eq(2L), eq(EMPTY_BUFFER));
     }
 
+    @Test(expected = Http2Exception.StreamException.class)
+    public void dataContentLengthMissmatch() throws Exception {
+        dataContentLengthInvalid(false);
+    }
+
+    @Test(expected = Http2Exception.StreamException.class)
+    public void dataContentLengthInvalid() throws Exception {
+        dataContentLengthInvalid(true);
+    }
+
+    private void dataContentLengthInvalid(boolean negative) throws Exception {
+        final ByteBuf data = dummyData();
+        int padding = 10;
+        int processedBytes = data.readableBytes() + padding;
+        mockFlowControl(processedBytes);
+        try {
+            decode().onHeadersRead(ctx, STREAM_ID, new DefaultHttp2Headers()
+                    .setLong(HttpHeaderNames.CONTENT_LENGTH, negative ? -1L : 1L), padding, false);
+            decode().onDataRead(ctx, STREAM_ID, data, padding, true);
+            verify(localFlow).receiveFlowControlledFrame(eq(stream), eq(data), eq(padding), eq(true));
+            verify(localFlow).consumeBytes(eq(stream), eq(processedBytes));
+
+            verify(listener, times(1)).onHeadersRead(eq(ctx), anyInt(),
+                    any(Http2Headers.class), eq(0), eq(DEFAULT_PRIORITY_WEIGHT), eq(false),
+                    eq(padding), eq(false));
+            // Verify that the event was absorbed and not propagated to the observer.
+            verify(listener, never()).onDataRead(eq(ctx), anyInt(), any(ByteBuf.class), anyInt(), anyBoolean());
+        } finally {
+            data.release();
+        }
+    }
+
+    @Test(expected = Http2Exception.StreamException.class)
+    public void headersContentLengthPositiveSign() throws Exception {
+        headersContentLengthSign("+1");
+    }
+
+    @Test(expected = Http2Exception.StreamException.class)
+    public void headersContentLengthNegativeSign() throws Exception {
+        headersContentLengthSign("-1");
+    }
+
+    private void headersContentLengthSign(String length) throws Exception {
+        int padding = 10;
+        when(connection.isServer()).thenReturn(true);
+        decode().onHeadersRead(ctx, STREAM_ID, new DefaultHttp2Headers()
+                .set(HttpHeaderNames.CONTENT_LENGTH, length), padding, false);
+
+        // Verify that the event was absorbed and not propagated to the observer.
+        verify(listener, never()).onHeadersRead(eq(ctx), anyInt(),
+                any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean());
+    }
+
+    @Test(expected = Http2Exception.StreamException.class)
+    public void headersContentLengthMissmatch() throws Exception {
+        headersContentLength(false);
+    }
+
+    @Test(expected = Http2Exception.StreamException.class)
+    public void headersContentLengthInvalid() throws Exception {
+        headersContentLength(true);
+    }
+
+    private void headersContentLength(boolean negative) throws Exception {
+        int padding = 10;
+        when(connection.isServer()).thenReturn(true);
+        decode().onHeadersRead(ctx, STREAM_ID, new DefaultHttp2Headers()
+                .setLong(HttpHeaderNames.CONTENT_LENGTH, negative ? -1L : 1L), padding, true);
+
+        // Verify that the event was absorbed and not propagated to the observer.
+        verify(listener, never()).onHeadersRead(eq(ctx), anyInt(),
+                any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void multipleHeadersContentLengthSame() throws Exception {
+        multipleHeadersContentLength(true);
+    }
+
+    @Test(expected = Http2Exception.StreamException.class)
+    public void multipleHeadersContentLengthDifferent() throws Exception {
+        multipleHeadersContentLength(false);
+    }
+
+    private void multipleHeadersContentLength(boolean same) throws Exception {
+        int padding = 10;
+        when(connection.isServer()).thenReturn(true);
+        Http2Headers headers = new DefaultHttp2Headers();
+        if (same) {
+            headers.addLong(HttpHeaderNames.CONTENT_LENGTH, 0);
+            headers.addLong(HttpHeaderNames.CONTENT_LENGTH, 0);
+        } else {
+            headers.addLong(HttpHeaderNames.CONTENT_LENGTH, 0);
+            headers.addLong(HttpHeaderNames.CONTENT_LENGTH, 1);
+        }
+
+        decode().onHeadersRead(ctx, STREAM_ID, headers, padding, true);
+
+        if (same) {
+            verify(listener, times(1)).onHeadersRead(eq(ctx), anyInt(),
+                    any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean());
+            assertEquals(1, headers.getAll(HttpHeaderNames.CONTENT_LENGTH).size());
+        } else {
+            // Verify that the event was absorbed and not propagated to the observer.
+            verify(listener, never()).onHeadersRead(eq(ctx), anyInt(),
+                    any(Http2Headers.class), anyInt(), anyShort(), anyBoolean(), anyInt(), anyBoolean());
+        }
+    }
+
     private static ByteBuf dummyData() {
         // The buffer is purposely 8 bytes so it will even work for a ping frame.
         return wrappedBuffer("abcdefgh".getBytes(UTF_8));
--- netty-netty-4.1.13.Final/common/src/main/java/io/netty/util/NetUtil.java	2017-07-06 13:23:51.000000000 +0200
+++ netty-netty-4.1.13.Final/common/src/main/java/io/netty/util/NetUtil.java	2021-03-12 11:48:15.242277753 +0100
@@ -1069,6 +1069,16 @@
     }
 
     /**
+     * Returns {@link InetSocketAddress#getHostString()} if Java >= 7,
+     * or {@link InetSocketAddress#getHostName()} otherwise.
+     * @param addr The address
+     * @return the host string
+     */
+    public static String getHostname(InetSocketAddress addr) {
+        return PlatformDependent.javaVersion() >= 7 ? addr.getHostString() : addr.getHostName();
+    }
+
+    /**
      * Does a range check on {@code value} if is within {@code start} (inclusive) and {@code end} (exclusive).
      * @param value The value to checked if is within {@code start} (inclusive) and {@code end} (exclusive)
      * @param start The start of the range (inclusive)
openSUSE Build Service is sponsored by