Repository: python-websockets/websockets

Issue:
Shelly devices send: 'Content-Length' = "0" which is being rejected by server
When using a Shelly Plus Plug it will send a header of 'Content-Length' = "0". The current code will raise a `ValueError` when that happens as it thinks that indicates a request body. But since the length is zero there is no request body.

Hints from issue comments:

Solution patch:
diff --git a/src/websockets/http11.py b/src/websockets/http11.py
index 530ac3d..eddec97 100644
--- a/src/websockets/http11.py
+++ b/src/websockets/http11.py
@@ -159,7 +159,12 @@ class Request:
             raise NotImplementedError("transfer codings aren't supported")
 
         if "Content-Length" in headers:
-            raise ValueError("unsupported request body")
+            content_length = headers["Content-Length"]
+            if content_length != "0":
+                raise ValueError(
+                    f"unsupported request body as 'Content-Length' is "
+                    f"non-zero: {content_length}"
+                )
 
         return cls(path, headers)
 


Test patch:
diff --git a/tests/test_http11.py b/tests/test_http11.py
index 3afb6d0..86f0b8d 100644
--- a/tests/test_http11.py
+++ b/tests/test_http11.py
@@ -83,9 +83,27 @@ class RequestTests(GeneratorTestCase):
             next(self.parse())
         self.assertEqual(
             str(raised.exception),
-            "unsupported request body",
+            "unsupported request body as 'Content-Length' is non-zero: 3",
         )
 
+    def test_parse_body_content_length_zero(self):
+        # Example from the protocol overview in RFC 6455
+        self.reader.feed_data(
+            b"GET /chat HTTP/1.1\r\n"
+            b"Host: server.example.com\r\n"
+            b"Upgrade: websocket\r\n"
+            b"Connection: Upgrade\r\n"
+            b"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
+            b"Origin: http://example.com\r\n"
+            b"Sec-WebSocket-Protocol: chat, superchat\r\n"
+            b"Sec-WebSocket-Version: 13\r\n"
+            b"Content-Length: 0\r\n"
+            b"\r\n"
+        )
+        request = self.assertGeneratorReturns(self.parse())
+        self.assertEqual(request.path, "/chat")
+        self.assertEqual(request.headers["Content-Length"], "0")
+
     def test_parse_body_with_transfer_encoding(self):
         self.reader.feed_data(b"GET / HTTP/1.1\r\nTransfer-Encoding: compress\r\n\r\n")
         with self.assertRaises(NotImplementedError) as raised: