diff --git a/daphne/http_protocol.py b/daphne/http_protocol.py index 1319f462..fdd470f1 100755 --- a/daphne/http_protocol.py +++ b/daphne/http_protocol.py @@ -77,9 +77,7 @@ def process(self): return for value in values: if INVALID_HEADER_VALUE_BYTES.intersection(value): - self.basic_error( - 400, b"Bad Request", "Invalid header value" - ) + self.basic_error(400, b"Bad Request", "Invalid header value") return # Get upgrade header diff --git a/daphne/management/commands/runserver.py b/daphne/management/commands/runserver.py index d505f335..1cc02213 100644 --- a/daphne/management/commands/runserver.py +++ b/daphne/management/commands/runserver.py @@ -73,6 +73,22 @@ def add_arguments(self, parser): "seconds (default: 5)" ), ) + parser.add_argument( + "--websocket-max-message-size", + type=int, + help="Maximum size, in bytes, of an incoming WebSocket message. " + "0 disables the limit (not recommended; allows unauthenticated " + "memory exhaustion).", + default=None, + ) + parser.add_argument( + "--websocket-max-frame-size", + type=int, + help="Maximum size, in bytes, of a single incoming WebSocket frame. " + "0 disables the limit (not recommended; allows unauthenticated " + "memory exhaustion).", + default=None, + ) if apps.is_installed("django.contrib.staticfiles"): parser.add_argument( "--nostatic", @@ -95,6 +111,17 @@ def handle(self, *args, **options): raise CommandError( "You have not set ASGI_APPLICATION, which is needed to run the server." ) + self.websocket_max_message_size = options.get("websocket_max_message_size") + if self.websocket_max_message_size is None: + self.websocket_max_message_size = getattr( + settings, "DAPHNE_WEBSOCKET_MAX_MESSAGE_SIZE", 1024 * 1024 + ) + self.websocket_max_frame_size = options.get("websocket_max_frame_size") + if self.websocket_max_frame_size is None: + self.websocket_max_frame_size = getattr( + settings, "DAPHNE_WEBSOCKET_MAX_FRAME_SIZE", 1024 * 1024 + ) + # Dispatch upward super().handle(*args, **options) @@ -145,6 +172,8 @@ def inner_run(self, *args, **options): http_timeout=self.http_timeout, root_path=getattr(settings, "FORCE_SCRIPT_NAME", "") or "", websocket_handshake_timeout=self.websocket_handshake_timeout, + websocket_max_message_size=self.websocket_max_message_size, + websocket_max_frame_size=self.websocket_max_frame_size, ).run() logger.debug("Daphne exited") except KeyboardInterrupt: diff --git a/tests/test_websocket.py b/tests/test_websocket.py index 844b14ee..bf385143 100644 --- a/tests/test_websocket.py +++ b/tests/test_websocket.py @@ -330,9 +330,7 @@ def test_websocket_max_message_size_allows_under_limit(self): sock, _ = self.websocket_handshake(test_app) _, messages = test_app.get_received() self.assert_valid_websocket_connect_message(messages[0]) - test_app.add_send_messages( - [{"type": "websocket.send", "text": "ack"}] - ) + test_app.add_send_messages([{"type": "websocket.send", "text": "ack"}]) self.websocket_send_frame(sock, "x" * 16) assert self.websocket_receive_frame(sock) == "ack" @@ -418,9 +416,7 @@ def test_websocket_upgrade_rejects_smuggled_headers(self): for byte in self.INVALID_BYTES: with self.subTest(byte=byte): value = b"innocent" + byte + b"X-Secret-Auth: admin-token" - response = self.run_daphne_raw( - self._websocket_upgrade_request(value) - ) + response = self.run_daphne_raw(self._websocket_upgrade_request(value)) self.assertTrue( response.startswith(b"HTTP/1.1 400"), f"expected 400 for byte {byte!r}, got {response[:80]!r}",