Repository: pallets/werkzeug

Issue:
invalid Transfer-Encoding header for 1xx and 204 responses
With werkzeug 2.1.0, the connection / transfer-encoding handling changed a bit (https://github.com/pallets/werkzeug/pull/2091).
Werkzeug now sends a `Transfer-Encoding: chunked` instead of a `Connection: close` (when using HTTP1.1).
A later commit removed a check where this was only done for certain responses (https://github.com/pallets/werkzeug/commit/d062807b64a681e49bb4b0771b6539974f743ab7).
Unfortunately I'm experiencing issues on clients (and tests) with the upgrade, as it seems that this behavior now is directly violating [RFC 7230 / HTTP 1.1](https://httpwg.org/specs/rfc7230.html#rfc.section.3.3.1):
> A server MUST NOT send a Transfer-Encoding header field in any response with a status code of [1xx (Informational)](https://httpwg.org/specs/rfc7231.html#status.1xx) or [204 (No Content)](https://httpwg.org/specs/rfc7231.html#status.204). A server MUST NOT send a Transfer-Encoding header field in any [2xx (Successful)](https://httpwg.org/specs/rfc7231.html#status.2xx) response to a CONNECT request ([Section 4.3.6](https://httpwg.org/specs/rfc7231.html#CONNECT) of [[RFC7231]](https://httpwg.org/specs/rfc7230.html#RFC7231)).


This can easily be reproduced with a simple Flask server:
```
from flask import Flask, Response
from flask_cors import CORS

app = Flask(__name__)

@app.route("/", methods=["DELETE"])
def empty_response():
    return Response("", status=204)

if __name__ == "__main__":
    app.run()
```

With Werkzeug/Flask 2.0.3, a response would look as follows (mind HTTP 1.0 and the absence of the Transfer-Encoding header):
```
Hypertext Transfer Protocol
    HTTP/1.0 204 NO CONTENT\r\n
    Content-Type: text/html; charset=utf-8\r\n
    Access-Control-Allow-Origin: *\r\n
    Server: Werkzeug/2.0.3 Python/3.8.10\r\n
    Date: Mon, 04 Apr 2022 08:30:26 GMT\r\n
    \r\n
```

With Werkzeug/Flask 2.1.1, a response now looks like this:
```
Hypertext Transfer Protocol
    HTTP/1.1 204 NO CONTENT\r\n
    Server: Werkzeug/2.1.1 Python/3.8.10\r\n
    Date: Mon, 04 Apr 2022 08:42:24 GMT\r\n
    Content-Type: text/html; charset=utf-8\r\n
    Transfer-Encoding: chunked\r\n
    \r\n
```

I would expect Werkzeug to _not_ set the `Transfer-Encoding` header for responses with 1xx and 204 status codes.
This could easily be done by re-adding the check which was removed with https://github.com/pallets/werkzeug/commit/d062807b64a681e49bb4b0771b6539974f743ab7 when checking if the Transfer-Encoding header should be set here:
https://github.com/pallets/werkzeug/blob/c7ae2fea4fb229ffd71187c2b665874c91b96277/src/werkzeug/serving.py#L269

Environment:
- Python version: 3.8.10
- Werkzeug version: 2.1.1


Discussion:

Resolving patch:
diff --git a/src/werkzeug/debug/repr.py b/src/werkzeug/debug/repr.py
--- a/src/werkzeug/debug/repr.py
+++ b/src/werkzeug/debug/repr.py
@@ -83,8 +83,8 @@ def _add_subclass_info(
     inner: str, obj: object, base: t.Union[t.Type, t.Tuple[t.Type, ...]]
 ) -> str:
     if isinstance(base, tuple):
-        for base in base:
-            if type(obj) is base:
+        for cls in base:
+            if type(obj) is cls:
                 return inner
     elif type(obj) is base:
         return inner
diff --git a/src/werkzeug/serving.py b/src/werkzeug/serving.py
--- a/src/werkzeug/serving.py
+++ b/src/werkzeug/serving.py
@@ -265,7 +265,18 @@ def write(data: bytes) -> None:
                     self.send_header(key, value)
                     header_keys.add(key.lower())
 
-                if "content-length" not in header_keys:
+                # Use chunked transfer encoding if there is no content
+                # length. Do not use for 1xx and 204 responses. 304
+                # responses and HEAD requests are also excluded, which
+                # is the more conservative behavior and matches other
+                # parts of the code.
+                # https://httpwg.org/specs/rfc7230.html#rfc.section.3.3.1
+                if not (
+                    "content-length" in header_keys
+                    or environ["REQUEST_METHOD"] == "HEAD"
+                    or (100 <= code < 200)
+                    or code in {204, 304}
+                ):
                     if self.protocol_version >= "HTTP/1.1":
                         chunk_response = True
                         self.send_header("Transfer-Encoding", "chunked")


Test patch: