CVE-2024-29025: Insufficient Information (NVD-CWE-noinfo)
value: Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. The `HttpPostRequestDecoder` can be tricked to accumulate data. While the decoder can store items on the disk if configured so, there are no limits to the number of fields the form can have, an attacher can send a chunked post consisting of many small fields that will be accumulated in the `bodyListHttpData` list. The decoder cumulates bytes in the `undecodedChunk` buffer until it can decode a field, this field can cumulate data without limits. This vulnerability is fixed in 4.1.108.Final.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-03-25T20:15Z
Repository: https://github.com/netty/netty
Language: Java
Files changed: codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java, codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java, codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostStandardRequestDecoder.java, codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java
Commit date: 2024-03-21 10:34:49 +0100
Commit message: Merge pull request from GHSA-5jpm-x58v-624v

* The InterfaceHttpPostRequestDecoder form implementations does not provide hard limits for the number of fields a form can have and the number of accumulated bytes. The former can be used by sending a large amount of fields that will fill the bodyListHttpData list, the later can be used by sending a very large field filling the undecodedChunk buffer since the decoder implementation buffers the field before handling it.

This provides hard limits for both: maxFields defines the maximum number of fields the form can have, maxBufferedBytes defines the maximum number of bytes a field can cumulate. When a limit is reached, a decoder exception is thrown, letting the decoder controller take care of it.

* Set default limits for maxFields/maxUnbufferedBytes (breaking change)

* Update codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java

* Update codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java

---------

Co-authored-by: Julien Viet <julien@julienviet.com>
Diff: diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java
index 09bd880d84b..bb25b74895c 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java
@@ -62,10 +62,20 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
     /**
      * Request to decode
      */
     private final HttpRequest request;
 
+    /**
+     * The maximum number of fields allows by the form
+     */
+    private final int maxFields;
+
+    /**
+     * The maximum number of accumulated bytes when decoding a field
+     */
+    private final int maxBufferedBytes;
+
     /**
      * Default charset to use
      */
     private Charset charset;
 
@@ -173,13 +183,38 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
      * @throws ErrorDataDecoderException
      *             if the default charset was wrong when decoding or other
      *             errors
      */
     public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
+        this(factory, request, charset, HttpPostRequestDecoder.DEFAULT_MAX_FIELDS, HttpPostRequestDecoder.DEFAULT_MAX_BUFFERED_BYTES);
+    }
+
+    /**
+     *
+     * @param factory
+     *            the factory used to create InterfaceHttpData
+     * @param request
+     *            the request to decode
+     * @param charset
+     *            the charset to use as default
+     * @param maxFields
+     *            the maximum number of fields the form can have, {@code -1} to disable
+     * @param maxBufferedBytes
+     *            the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable
+     * @throws NullPointerException
+     *             for request or charset or factory
+     * @throws ErrorDataDecoderException
+     *             if the default charset was wrong when decoding or other
+     *             errors
+     */
+    public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset,
+                                           int maxFields, int maxBufferedBytes) {
         this.request = checkNotNull(request, "request");
         this.charset = checkNotNull(charset, "charset");
         this.factory = checkNotNull(factory, "factory");
+        this.maxFields = maxFields;
+        this.maxBufferedBytes = maxBufferedBytes;
         // Fill default values
 
         String contentTypeValue = this.request.headers().get(HttpHeaderNames.CONTENT_TYPE);
         if (contentTypeValue == null) {
             throw new ErrorDataDecoderException("No '" + HttpHeaderNames.CONTENT_TYPE + "' header present.");
@@ -344,10 +379,13 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
                     buf.alloc().buffer(buf.readableBytes()).writeBytes(buf);
         } else {
             undecodedChunk.writeBytes(buf);
         }
         parseBody();
+        if (maxBufferedBytes > 0 && undecodedChunk != null && undecodedChunk.readableBytes() > maxBufferedBytes) {
+            throw new HttpPostRequestDecoder.TooLongFormFieldException();
+        }
         if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) {
             if (undecodedChunk.refCnt() == 1) {
                 // It's safe to call discardBytes() as we are the only owner of the buffer.
                 undecodedChunk.discardReadBytes();
             } else {
@@ -438,10 +476,13 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
      */
     protected void addHttpData(InterfaceHttpData data) {
         if (data == null) {
             return;
         }
+        if (maxFields > 0 && bodyListHttpData.size() >= maxFields) {
+            throw new HttpPostRequestDecoder.TooManyFormFieldsException();
+        }
         List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
         if (datas == null) {
             datas = new ArrayList<InterfaceHttpData>(1);
             bodyMapHttpData.put(data.getName(), datas);
         }
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java
index eb6bd792de4..b776d6dc25f 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoder.java
@@ -35,10 +35,14 @@ import java.util.List;
  */
 public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder {
 
     static final int DEFAULT_DISCARD_THRESHOLD = 10 * 1024 * 1024;
 
+    static final int DEFAULT_MAX_FIELDS = 128;
+
+    static final int DEFAULT_MAX_BUFFERED_BYTES = 1024;
+
     private final InterfaceHttpPostRequestDecoder decoder;
 
     /**
      *
      * @param request
@@ -51,10 +55,29 @@ public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder {
      */
     public HttpPostRequestDecoder(HttpRequest request) {
         this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET);
     }
 
+    /**
+     *
+     * @param request
+     *            the request to decode
+     * @param maxFields
+     *            the maximum number of fields the form can have, {@code -1} to disable
+     * @param maxBufferedBytes
+     *            the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable
+     * @throws NullPointerException
+     *             for request
+     * @throws ErrorDataDecoderException
+     *             if the default charset was wrong when decoding or other
+     *             errors
+     */
+    public HttpPostRequestDecoder(HttpRequest request, int maxFields, int maxBufferedBytes) {
+        this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET,
+             maxFields, maxBufferedBytes);
+    }
+
     /**
      *
      * @param factory
      *            the factory used to create InterfaceHttpData
      * @param request
@@ -94,10 +117,42 @@ public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder {
         } else {
             decoder = new HttpPostStandardRequestDecoder(factory, request, charset);
         }
     }
 
+    /**
+     *
+     * @param factory
+     *            the factory used to create InterfaceHttpData
+     * @param request
+     *            the request to decode
+     * @param charset
+     *            the charset to use as default
+     * @param maxFields
+     *            the maximum number of fields the form can have, {@code -1} to disable
+     * @param maxBufferedBytes
+     *            the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable
+     * @throws NullPointerException
+     *             for request or charset or factory
+     * @throws ErrorDataDecoderException
+     *             if the default charset was wrong when decoding or other
+     *             errors
+     */
+    public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset,
+                                  int maxFields, int maxBufferedBytes) {
+        ObjectUtil.checkNotNull(factory, "factory");
+        ObjectUtil.checkNotNull(request, "request");
+        ObjectUtil.checkNotNull(charset, "charset");
+
+        // Fill default values
+        if (isMultipart(request)) {
+            decoder = new HttpPostMultipartRequestDecoder(factory, request, charset, maxFields, maxBufferedBytes);
+        } else {
+            decoder = new HttpPostStandardRequestDecoder(factory, request, charset, maxFields, maxBufferedBytes);
+        }
+    }
+
     /**
      * states follow NOTSTARTED PREAMBLE ( (HEADERDELIMITER DISPOSITION (FIELD |
      * FILEUPLOAD))* (HEADERDELIMITER DISPOSITION MIXEDPREAMBLE (MIXEDDELIMITER
      * MIXEDDISPOSITION MIXEDFILEUPLOAD)+ MIXEDCLOSEDELIMITER)* CLOSEDELIMITER)+
      * EPILOGUE
@@ -336,6 +391,20 @@ public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder {
 
         public ErrorDataDecoderException(String msg, Throwable cause) {
             super(msg, cause);
         }
     }
+
+    /**
+     * Exception when the maximum number of fields for a given form is reached
+     */
+    public static final class TooManyFormFieldsException extends DecoderException {
+        private static final long serialVersionUID = 1336267941020800769L;
+    }
+
+    /**
+     * Exception when a field content is too long
+     */
+    public static final class TooLongFormFieldException extends DecoderException {
+        private static final long serialVersionUID = 1336267941020800769L;
+    }
 }
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostStandardRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostStandardRequestDecoder.java
index 817f828145f..ecbd36253b7 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostStandardRequestDecoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostStandardRequestDecoder.java
@@ -15,20 +15,23 @@
  */
 package io.netty.handler.codec.http.multipart;
 
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
+import io.netty.handler.codec.DecoderException;
 import io.netty.handler.codec.http.HttpConstants;
 import io.netty.handler.codec.http.HttpContent;
 import io.netty.handler.codec.http.HttpRequest;
 import io.netty.handler.codec.http.LastHttpContent;
 import io.netty.handler.codec.http.QueryStringDecoder;
 import io.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadOptimize;
 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.MultiPartStatus;
 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
+import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.TooManyFormFieldsException;
+import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.TooLongFormFieldException;
 import io.netty.util.ByteProcessor;
 import io.netty.util.internal.PlatformDependent;
 import io.netty.util.internal.StringUtil;
 
 import java.io.IOException;
@@ -61,10 +64,20 @@ public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestD
     /**
      * Default charset to use
      */
     private final Charset charset;
 
+    /**
+     * The maximum number of fields allows by the form
+     */
+    private final int maxFields;
+
+    /**
+     * The maximum number of accumulated bytes when decoding a field
+     */
+    private final int maxBufferedBytes;
+
     /**
      * Does the last chunk already received
      */
     private boolean isLastChunk;
 
@@ -146,13 +159,38 @@ public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestD
      * @throws ErrorDataDecoderException
      *             if the default charset was wrong when decoding or other
      *             errors
      */
     public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
+        this(factory, request, charset, HttpPostRequestDecoder.DEFAULT_MAX_FIELDS, HttpPostRequestDecoder.DEFAULT_MAX_BUFFERED_BYTES);
+    }
+
+    /**
+     *
+     * @param factory
+     *            the factory used to create InterfaceHttpData
+     * @param request
+     *            the request to decode
+     * @param charset
+     *            the charset to use as default
+     * @param maxFields
+     *            the maximum number of fields the form can have, {@code -1} to disable
+     * @param maxBufferedBytes
+     *            the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable
+     * @throws NullPointerException
+     *             for request or charset or factory
+     * @throws ErrorDataDecoderException
+     *             if the default charset was wrong when decoding or other
+     *             errors
+     */
+    public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset,
+                                          int maxFields, int maxBufferedBytes) {
         this.request = checkNotNull(request, "request");
         this.charset = checkNotNull(charset, "charset");
         this.factory = checkNotNull(factory, "factory");
+        this.maxFields = maxFields;
+        this.maxBufferedBytes = maxBufferedBytes;
         try {
             if (request instanceof HttpContent) {
                 // Offer automatically if the given request is as type of HttpContent
                 // See #1089
                 offer((HttpContent) request);
@@ -295,10 +333,13 @@ public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestD
                     buf.alloc().buffer(buf.readableBytes()).writeBytes(buf);
         } else {
             undecodedChunk.writeBytes(buf);
         }
         parseBody();
+        if (maxBufferedBytes > 0 && undecodedChunk != null && undecodedChunk.readableBytes() > maxBufferedBytes) {
+            throw new TooLongFormFieldException();
+        }
         if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) {
             if (undecodedChunk.refCnt() == 1) {
                 // It's safe to call discardBytes() as we are the only owner of the buffer.
                 undecodedChunk.discardReadBytes();
             } else {
@@ -385,10 +426,13 @@ public class HttpPostStandardRequestDecoder implements InterfaceHttpPostRequestD
      */
     protected void addHttpData(InterfaceHttpData data) {
         if (data == null) {
             return;
         }
+        if (maxFields > 0 && bodyListHttpData.size() >= maxFields) {
+            throw new TooManyFormFieldsException();
+        }
         List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
         if (datas == null) {
             datas = new ArrayList<InterfaceHttpData>(1);
             bodyMapHttpData.put(data.getName(), datas);
         }
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java
index 6099062e5c1..0ec8500ad7e 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestDecoderTest.java
@@ -17,10 +17,11 @@ package io.netty.handler.codec.http.multipart;
 
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufAllocator;
 import io.netty.buffer.Unpooled;
 import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.handler.codec.DecoderException;
 import io.netty.handler.codec.DecoderResult;
 import io.netty.handler.codec.http.DefaultFullHttpRequest;
 import io.netty.handler.codec.http.DefaultHttpContent;
 import io.netty.handler.codec.http.DefaultHttpRequest;
 import io.netty.handler.codec.http.DefaultLastHttpContent;
@@ -1038,6 +1039,107 @@ public class HttpPostRequestDecoderTest {
         } finally {
             assertTrue(req.release());
         }
     }
 
+    @Test
+    public void testTooManyFormFieldsPostStandardDecoder() {
+        HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
+
+        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, 1024, -1);
+
+        int num = 0;
+        while (true) {
+            try {
+                decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer("foo=bar&".getBytes())));
+            } catch (DecoderException e) {
+                assertEquals(HttpPostRequestDecoder.TooManyFormFieldsException.class, e.getClass());
+                break;
+            }
+            assertTrue(num++ < 1024);
+        }
+        assertEquals(1024, num);
+    }
+
+    @Test
+    public void testTooManyFormFieldsPostMultipartDecoder() {
+        HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
+        req.headers().add("Content-Type", "multipart/form-data;boundary=be38b42a9ad2713f");
+
+        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, 1024, -1);
+        decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer("--be38b42a9ad2713f\n".getBytes())));
+
+        int num = 0;
+        while (true) {
+            try {
+                byte[] bodyBytes = ("content-disposition: form-data; name=\"title\"\n" +
+                        "content-length: 10\n" +
+                        "content-type: text/plain; charset=UTF-8\n" +
+                        "\n" +
+                        "bar-stream\n" +
+                        "--be38b42a9ad2713f\n").getBytes();
+                ByteBuf content = Unpooled.wrappedBuffer(bodyBytes);
+                decoder.offer(new DefaultHttpContent(content));
+            } catch (DecoderException e) {
+                assertEquals(HttpPostRequestDecoder.TooManyFormFieldsException.class, e.getClass());
+                break;
+            }
+            assertTrue(num++ < 1024);
+        }
+        assertEquals(1024, num);
+    }
+
+    @Test
+    public void testTooLongFormFieldStandardDecoder() {
+        HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
+
+        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, 16 * 1024);
+
+        try {
+            decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer(new byte[16 * 1024 + 1])));
+            fail();
+        } catch (DecoderException e) {
+            assertEquals(HttpPostRequestDecoder.TooLongFormFieldException.class, e.getClass());
+        }
+    }
+
+    @Test
+    public void testFieldGreaterThanMaxBufferedBytesStandardDecoder() {
+        HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
+
+        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, 6);
+
+        decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer("foo=bar".getBytes())));
+    }
+
+    @Test
+    public void testTooLongFormFieldMultipartDecoder() {
+        HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
+        req.headers().add("Content-Type", "multipart/form-data;boundary=be38b42a9ad2713f");
+
+        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, 16 * 1024);
+
+        try {
+            decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer(new byte[16 * 1024 + 1])));
+            fail();
+        } catch (DecoderException e) {
+            assertEquals(HttpPostRequestDecoder.TooLongFormFieldException.class, e.getClass());
+        }
+    }
+
+    @Test
+    public void testFieldGreaterThanMaxBufferedBytesMultipartDecoder() {
+        HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
+        req.headers().add("Content-Type", "multipart/form-data;boundary=be38b42a9ad2713f");
+
+        byte[] bodyBytes = ("content-disposition: form-data; name=\"title\"\n" +
+                "content-length: 10\n" +
+                "content-type: text/plain; charset=UTF-8\n" +
+                "\n" +
+                "bar-stream\n" +
+                "--be38b42a9ad2713f\n").getBytes();
+
+        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, bodyBytes.length - 1);
+
+        decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer(bodyBytes)));
+    }
 }

Fixed code:     /**
     * The maximum number of fields allows by the form
     */
    private final int maxFields;

    /**
     * The maximum number of accumulated bytes when decoding a field
     */
    private final int maxBufferedBytes;

        this(factory, request, charset, HttpPostRequestDecoder.DEFAULT_MAX_FIELDS, HttpPostRequestDecoder.DEFAULT_MAX_BUFFERED_BYTES);
    }

    /**
     *
     * @param factory
     *            the factory used to create InterfaceHttpData
     * @param request
     *            the request to decode
     * @param charset
     *            the charset to use as default
     * @param maxFields
     *            the maximum number of fields the form can have, {@code -1} to disable
     * @param maxBufferedBytes
     *            the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable
     * @throws NullPointerException
     *             for request or charset or factory
     * @throws ErrorDataDecoderException
     *             if the default charset was wrong when decoding or other
     *             errors
     */
    public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset,
                                           int maxFields, int maxBufferedBytes) {
        this.maxFields = maxFields;
        this.maxBufferedBytes = maxBufferedBytes;
        if (maxBufferedBytes > 0 && undecodedChunk != null && undecodedChunk.readableBytes() > maxBufferedBytes) {
            throw new HttpPostRequestDecoder.TooLongFormFieldException();
        }
        if (maxFields > 0 && bodyListHttpData.size() >= maxFields) {
            throw new HttpPostRequestDecoder.TooManyFormFieldsException();
        }
    static final int DEFAULT_MAX_FIELDS = 128;

    static final int DEFAULT_MAX_BUFFERED_BYTES = 1024;

    /**
     *
     * @param request
     *            the request to decode
     * @param maxFields
     *            the maximum number of fields the form can have, {@code -1} to disable
     * @param maxBufferedBytes
     *            the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable
     * @throws NullPointerException
     *             for request
     * @throws ErrorDataDecoderException
     *             if the default charset was wrong when decoding or other
     *             errors
     */
    public HttpPostRequestDecoder(HttpRequest request, int maxFields, int maxBufferedBytes) {
        this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET,
             maxFields, maxBufferedBytes);
    }

    /**
     *
     * @param factory
     *            the factory used to create InterfaceHttpData
     * @param request
     *            the request to decode
     * @param charset
     *            the charset to use as default
     * @param maxFields
     *            the maximum number of fields the form can have, {@code -1} to disable
     * @param maxBufferedBytes
     *            the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable
     * @throws NullPointerException
     *             for request or charset or factory
     * @throws ErrorDataDecoderException
     *             if the default charset was wrong when decoding or other
     *             errors
     */
    public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset,
                                  int maxFields, int maxBufferedBytes) {
        ObjectUtil.checkNotNull(factory, "factory");
        ObjectUtil.checkNotNull(request, "request");
        ObjectUtil.checkNotNull(charset, "charset");

        // Fill default values
        if (isMultipart(request)) {
            decoder = new HttpPostMultipartRequestDecoder(factory, request, charset, maxFields, maxBufferedBytes);
        } else {
            decoder = new HttpPostStandardRequestDecoder(factory, request, charset, maxFields, maxBufferedBytes);
        }
    }


    /**
     * Exception when the maximum number of fields for a given form is reached
     */
    public static final class TooManyFormFieldsException extends DecoderException {
        private static final long serialVersionUID = 1336267941020800769L;
    }

    /**
     * Exception when a field content is too long
     */
    public static final class TooLongFormFieldException extends DecoderException {
        private static final long serialVersionUID = 1336267941020800769L;
    }
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.TooManyFormFieldsException;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.TooLongFormFieldException;
    /**
     * The maximum number of fields allows by the form
     */
    private final int maxFields;

    /**
     * The maximum number of accumulated bytes when decoding a field
     */
    private final int maxBufferedBytes;

        this(factory, request, charset, HttpPostRequestDecoder.DEFAULT_MAX_FIELDS, HttpPostRequestDecoder.DEFAULT_MAX_BUFFERED_BYTES);
    }

    /**
     *
     * @param factory
     *            the factory used to create InterfaceHttpData
     * @param request
     *            the request to decode
     * @param charset
     *            the charset to use as default
     * @param maxFields
     *            the maximum number of fields the form can have, {@code -1} to disable
     * @param maxBufferedBytes
     *            the maximum number of bytes the decoder can buffer when decoding a field, {@code -1} to disable
     * @throws NullPointerException
     *             for request or charset or factory
     * @throws ErrorDataDecoderException
     *             if the default charset was wrong when decoding or other
     *             errors
     */
    public HttpPostStandardRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset,
                                          int maxFields, int maxBufferedBytes) {
        this.maxFields = maxFields;
        this.maxBufferedBytes = maxBufferedBytes;
        if (maxBufferedBytes > 0 && undecodedChunk != null && undecodedChunk.readableBytes() > maxBufferedBytes) {
            throw new TooLongFormFieldException();
        }
        if (maxFields > 0 && bodyListHttpData.size() >= maxFields) {
            throw new TooManyFormFieldsException();
        }
import io.netty.handler.codec.DecoderException;
    @Test
    public void testTooManyFormFieldsPostStandardDecoder() {
        HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");

        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, 1024, -1);

        int num = 0;
        while (true) {
            try {
                decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer("foo=bar&".getBytes())));
            } catch (DecoderException e) {
                assertEquals(HttpPostRequestDecoder.TooManyFormFieldsException.class, e.getClass());
                break;
            }
            assertTrue(num++ < 1024);
        }
        assertEquals(1024, num);
    }

    @Test
    public void testTooManyFormFieldsPostMultipartDecoder() {
        HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
        req.headers().add("Content-Type", "multipart/form-data;boundary=be38b42a9ad2713f");

        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, 1024, -1);
        decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer("--be38b42a9ad2713f\n".getBytes())));

        int num = 0;
        while (true) {
            try {
                byte[] bodyBytes = ("content-disposition: form-data; name=\"title\"\n" +
                        "content-length: 10\n" +
                        "content-type: text/plain; charset=UTF-8\n" +
                        "\n" +
                        "bar-stream\n" +
                        "--be38b42a9ad2713f\n").getBytes();
                ByteBuf content = Unpooled.wrappedBuffer(bodyBytes);
                decoder.offer(new DefaultHttpContent(content));
            } catch (DecoderException e) {
                assertEquals(HttpPostRequestDecoder.TooManyFormFieldsException.class, e.getClass());
                break;
            }
            assertTrue(num++ < 1024);
        }
        assertEquals(1024, num);
    }

    @Test
    public void testTooLongFormFieldStandardDecoder() {
        HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");

        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, 16 * 1024);

        try {
            decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer(new byte[16 * 1024 + 1])));
            fail();
        } catch (DecoderException e) {
            assertEquals(HttpPostRequestDecoder.TooLongFormFieldException.class, e.getClass());
        }
    }

    @Test
    public void testFieldGreaterThanMaxBufferedBytesStandardDecoder() {
        HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");

        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, 6);

        decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer("foo=bar".getBytes())));
    }

    @Test
    public void testTooLongFormFieldMultipartDecoder() {
        HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
        req.headers().add("Content-Type", "multipart/form-data;boundary=be38b42a9ad2713f");

        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, 16 * 1024);

        try {
            decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer(new byte[16 * 1024 + 1])));
            fail();
        } catch (DecoderException e) {
            assertEquals(HttpPostRequestDecoder.TooLongFormFieldException.class, e.getClass());
        }
    }

    @Test
    public void testFieldGreaterThanMaxBufferedBytesMultipartDecoder() {
        HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/");
        req.headers().add("Content-Type", "multipart/form-data;boundary=be38b42a9ad2713f");

        byte[] bodyBytes = ("content-disposition: form-data; name=\"title\"\n" +
                "content-length: 10\n" +
                "content-type: text/plain; charset=UTF-8\n" +
                "\n" +
                "bar-stream\n" +
                "--be38b42a9ad2713f\n").getBytes();

        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(req, -1, bodyBytes.length - 1);

        decoder.offer(new DefaultHttpContent(Unpooled.wrappedBuffer(bodyBytes)));
    }