Repository: dropwizard/dropwizard
Issue: ProxyServlet does not set Content-Length correctly for gzipped content

I have a Dropwizard application that uses a Jetty ProxyServlet to forward HTTP traffic. This works great when the HTTP traffic is not gzipped. When the HTTP traffic is gzipped, the proxy forwards a bad HTTP request.

The problem appears to be that when Dropwizard receives a gzipped HTTP request, the BiDiGzipHandler processes the request before the ProxyServlet. When the BiDiGzipHandler runs, it decompresses the payload and strips the `Content-Encoding: gzip` header. It does not modify the `Content-Length` header to reflect the length of the decompressed payload. When the ProxyServlet processes the request, it blindly copies the now incorrect `Content-Length` header (that reflects the length of the gzipped payload). It forwards the decompressed payload as the body though. When the request is received, you can see that the `Content-Length` does not reflect the length of the actual HTTP body.

I set up a simple test to demonstrate the problem.

I am running Dropwizard 1.0.0. I configured a ProxyServlet to forward all HTTP traffic matching the _/echo_ path.

```
// Register echo proxy
ServletRegistration echoServletRegistration =
    environment.servlets().addServlet("EchoServlet", new ProxyServlet.Transparent());
echoServletRegistration.addMapping("/echo/*");
echoServletRegistration.setInitParameter("prefix", "/echo");
echoServletRegistration.setInitParameter(
    "proxyTo", "http://localhost:47899");
```

I CURLed a normal HTTP POST to Dropwizard and used netcat to print what the proxy forwarded:

_test.sh_
```
#!/bin/bash

curl --data '@data.txt' \\
     -H "Content-Type: application/json" \\
     -X POST \\
     http://localhost:8080/echo
```

_data.txt_ (length 19 bytes)
```
Hello, Dropwizard!
```

_netcat output_
```
$ nc -l 47899
POST / HTTP/1.1
User-Agent: curl/7.57.0
Accept: */*
Content-Type: application/json
Via: http/1.1 Jordans-MacBook-Pro.local
X-Forwarded-For: 127.0.0.1
X-Forwarded-Proto: http
X-Forwarded-Host: localhost:8080
X-Forwarded-Server: 127.0.0.1
Host: localhost:47899
Content-Length: 18

Hello, Dropwizard!
```

You can see the problem when I did the same thing gzipped.

_gzip-test.sh_
```
#!/bin/bash

curl --data-binary '@data.txt.gz' \\
     -H "Content-Type: application/json" \\
     -H "Content-Encoding: gzip" \\
     -X POST \\
     http://localhost:8080/echo
```

_data.txt.gz_ (length 48 bytes)
```
ã∫Ù}Z

Changed files:
dropwizard-jetty/src/test/java/io/dropwizard/jetty/BiDiGzipHandlerTest.java, dropwizard-jetty/src/main/java/io/dropwizard/jetty/BiDiGzipHandler.java

diff --git a/dropwizard-jetty/src/main/java/io/dropwizard/jetty/BiDiGzipHandler.java b/dropwizard-jetty/src/main/java/io/dropwizard/jetty/BiDiGzipHandler.java
index 375f42539..cdcca89c1 100644
--- a/dropwizard-jetty/src/main/java/io/dropwizard/jetty/BiDiGzipHandler.java
+++ b/dropwizard-jetty/src/main/java/io/dropwizard/jetty/BiDiGzipHandler.java
@@ -1,5 +1,6 @@
 package io.dropwizard.jetty;
 
+import com.google.common.collect.ImmutableSortedSet;
 import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.handler.gzip.GzipHandler;
@@ -59,12 +60,12 @@ public class BiDiGzipHandler extends GzipHandler {
 
     @Override
     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
-            throws IOException, ServletException {
+        throws IOException, ServletException {
         final String encoding = request.getHeader(HttpHeader.CONTENT_ENCODING.asString());
         if (GZIP.equalsIgnoreCase(encoding)) {
-            super.handle(target, baseRequest, wrapGzippedRequest(removeContentEncodingHeader(request)), response);
+            super.handle(target, baseRequest, wrapGzippedRequest(removeContentHeaders(request)), response);
         } else if (DEFLATE.equalsIgnoreCase(encoding)) {
-            super.handle(target, baseRequest, wrapDeflatedRequest(removeContentEncodingHeader(request)), response);
+            super.handle(target, baseRequest, wrapDeflatedRequest(removeContentHeaders(request)), response);
         } else {
             super.handle(target, baseRequest, request, response);
         }
@@ -110,8 +111,13 @@ public class BiDiGzipHandler extends GzipHandler {
         }
     }
 
-    private HttpServletRequest removeContentEncodingHeader(final HttpServletRequest request) {
-        return new RemoveHttpHeaderWrapper(request, HttpHeader.CONTENT_ENCODING.asString());
+    private HttpServletRequest removeContentHeaders(final HttpServletRequest request) {
+        // The decoded content is plain and generated dynamically, therefore the "Content-Encoding" and "Content-Length"
+        // headers should be removed after after the processing.
+        return new RemoveHttpHeadersWrapper(request, ImmutableSortedSet.orderedBy(String::compareToIgnoreCase)
+            .add(HttpHeader.CONTENT_ENCODING.asString())
+            .add(HttpHeader.CONTENT_LENGTH.asString())
+            .build());
     }
 
     private static class WrappedServletRequest extends HttpServletRequestWrapper {
@@ -142,6 +148,17 @@ public class BiDiGzipHandler extends GzipHandler {
         public BufferedReader getReader() throws IOException {
             return reader;
         }
+
+        @Override
+        public int getContentLength() {
+            // Because the we replace the original stream, the new content length is not known.
+            return -1;
+        }
+
+        @Override
+        public long getContentLengthLong() {
+            return -1L;
+        }
     }
 
     private static class WrappedServletInputStream extends ServletInputStream {
@@ -220,12 +237,12 @@ public class BiDiGzipHandler extends GzipHandler {
         }
     }
 
-    private static class RemoveHttpHeaderWrapper extends HttpServletRequestWrapper {
-        private final String headerName;
+    private static class RemoveHttpHeadersWrapper extends HttpServletRequestWrapper {
+        private final ImmutableSortedSet<String> headerNames;
 
-        RemoveHttpHeaderWrapper(final HttpServletRequest request, final String headerName) {
+        RemoveHttpHeadersWrapper(final HttpServletRequest request, final ImmutableSortedSet<String> headerNames) {
             super(request);
-            this.headerName = headerName;
+            this.headerNames = headerNames;
         }
 
         /**
@@ -236,7 +253,7 @@ public class BiDiGzipHandler extends GzipHandler {
          */
         @Override
         public int getIntHeader(final String name) {
-            if (headerName.equalsIgnoreCase(name)) {
+            if (headerNames.contains(name)) {
                 return -1;
             } else {
                 return super.getIntHeader(name);
@@ -251,7 +268,7 @@ public class BiDiGzipHandler extends GzipHandler {
          */
         @Override
         public Enumeration<String> getHeaders(final String name) {
-            if (headerName.equalsIgnoreCase(name)) {
+            if (headerNames.contains(name)) {
                 return Collections.emptyEnumeration();
             } else {
                 return super.getHeaders(name);
@@ -267,7 +284,7 @@ public class BiDiGzipHandler extends GzipHandler {
         @Override
         @Nullable
         public String getHeader(final String name) {
-            if (headerName.equalsIgnoreCase(name)) {
+            if (headerNames.contains(name)) {
                 return null;
             } else {
                 return super.getHeader(name);
@@ -282,7 +299,7 @@ public class BiDiGzipHandler extends GzipHandler {
          */
         @Override
         public long getDateHeader(final String name) {
-            if (headerName.equalsIgnoreCase(name)) {
+            if (headerNames.contains(name)) {
                 return -1L;
             } else {
                 return super.getDateHeader(name);
diff --git a/dropwizard-jetty/src/test/java/io/dropwizard/jetty/BiDiGzipHandlerTest.java b/dropwizard-jetty/src/test/java/io/dropwizard/jetty/BiDiGzipHandlerTest.java
index 711aebde4..fbf8c781b 100644
--- a/dropwizard-jetty/src/test/java/io/dropwizard/jetty/BiDiGzipHandlerTest.java
+++ b/dropwizard-jetty/src/test/java/io/dropwizard/jetty/BiDiGzipHandlerTest.java
@@ -162,6 +162,9 @@ public class BiDiGzipHandlerTest {
         protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
             assertThat(req.getHeader(HttpHeaders.CONTENT_TYPE)).isEqualToIgnoringCase(PLAIN_TEXT_UTF_8);
             assertThat(req.getHeader(HttpHeaders.CONTENT_ENCODING)).isNull();
+            assertThat(req.getHeader(HttpHeaders.CONTENT_LENGTH)).isNull();
+            assertThat(req.getContentLength()).isEqualTo(-1);
+            assertThat(req.getContentLengthLong()).isEqualTo(-1L);
             assertThat(CharStreams.toString(req.getReader())).isEqualTo(
                 Resources.toString(Resources.getResource("assets/new-banner.txt"), StandardCharsets.UTF_8));