diff --git a/sanic/exceptions.py b/sanic/exceptions.py index d3bd2613..7d9da083 100644 --- a/sanic/exceptions.py +++ b/sanic/exceptions.py @@ -51,6 +51,10 @@ class InvalidUsage(SanicException): quiet = True +class BadURL(InvalidUsage): + ... + + class MethodNotSupported(SanicException): """ **Status**: 405 Method Not Allowed diff --git a/sanic/request.py b/sanic/request.py index 4e3510e5..5f0de362 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -30,10 +30,11 @@ from types import SimpleNamespace from urllib.parse import parse_qs, parse_qsl, unquote, urlunparse from httptools import parse_url # type: ignore +from httptools.parser.errors import HttpParserInvalidURLError # type: ignore from sanic.compat import CancelledErrors, Header from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE -from sanic.exceptions import InvalidUsage, ServerError +from sanic.exceptions import BadURL, InvalidUsage, ServerError from sanic.headers import ( AcceptContainer, Options, @@ -129,8 +130,10 @@ class Request: ): self.raw_url = url_bytes - # TODO: Content-Encoding detection - self._parsed_url = parse_url(url_bytes) + try: + self._parsed_url = parse_url(url_bytes) + except HttpParserInvalidURLError: + raise BadURL(f"Bad URL: {url_bytes.decode()}") self._id: Optional[Union[uuid.UUID, str, int]] = None self._name: Optional[str] = None self.app = app diff --git a/tests/test_request.py b/tests/test_request.py index ca2c1e4a..8de22df1 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -4,6 +4,7 @@ from uuid import UUID, uuid4 import pytest from sanic import Sanic, response +from sanic.exceptions import BadURL from sanic.request import Request, uuid from sanic.server import HttpProtocol @@ -176,3 +177,17 @@ def test_request_accept(): "text/x-dvi; q=0.8", "text/plain; q=0.5", ] + + +def test_bad_url_parse(): + message = "Bad URL: my.redacted-domain.com:443" + with pytest.raises(BadURL, match=message): + Request( + b"my.redacted-domain.com:443", + Mock(), + Mock(), + Mock(), + Mock(), + Mock(), + Mock(), + )