diff --git a/CHANGES.md b/CHANGES.md index 144a53f9..281f765a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -147,19 +147,21 @@ _**Breaking**_ ⚠️ - `extremes.py` renamed to `_extremes.py` and is no longer exposed. - `truthy` was discarded in favour of simple `bool()` function. - `ipv4_cidr()` and `ipv6_cidr()` has been dropped in favour of `cidr: bool = True` and `cidr: bool = True` keyword-only parameters. -- `email` API now accepts the following keyword-only arguments: +- `email()` API now accepts the following keyword-only arguments: - `simple_host: bool = False`, - `ipv6_address: bool = False`, - `ipv4_address: bool = False`, - `rfc_1034: bool = False` and - `rfc_2782: bool = False`. -- `url` has been refactored, it accepts the following keyword-only arguments: +- `whitelist=None` has been removed from `email()`. +- `url()` has been refactored, it accepts the following keyword-only arguments: - `skip_ipv6_addr: bool = False`, - `skip_ipv4_addr: bool = False`, - `may_have_port: bool = True`, - `simple_host: bool = False`, - `rfc_1034: bool = False` and - `rfc_2782: bool = False`. +- `public=False` keyword argument has been removed from `url()`. - Exposes `i18n` functions directly via `__init__.py`. - `@validator` decorator catches `Exception`. diff --git a/src/validators/domain.py b/src/validators/domain.py index 1c25df5f..a8f991c4 100644 --- a/src/validators/domain.py +++ b/src/validators/domain.py @@ -42,7 +42,7 @@ def domain(value: str, /, *, rfc_1034: bool = False, rfc_2782: bool = False): # First character of the domain rf"^(?:[a-zA-Z0-9{'_'if rfc_2782 else ''}]" # Sub domain + hostname - + r"(?:[a-zA-Z0-9-_]{0,61}[A-Za-z0-9])?\.)" + + rf"(?:[a-zA-Z0-9-_]{{0,61}}[A-Za-z0-9{'_'if rfc_2782 else ''}])?\.)" # First 61 characters of the gTLD + r"+[A-Za-z0-9][A-Za-z0-9-_]{0,61}" # Last character of the gTLD diff --git a/src/validators/url.py b/src/validators/url.py index ac7f9364..177e94d1 100644 --- a/src/validators/url.py +++ b/src/validators/url.py @@ -38,7 +38,14 @@ def _validate_scheme(value: str): """Validate scheme.""" # More schemes will be considered later. return ( - value in {"ftp", "ftps", "git", "http", "https", "rtsp", "sftp", "ssh", "telnet"} + value + # fmt: off + in { + "ftp", "ftps", "git", "http", "https", + "rtmp", "rtmps", "rtsp", "sftp", + "ssh", "telnet", + } + # fmt: on if value else False ) @@ -112,8 +119,18 @@ def _validate_optionals(path: str, query: str, fragment: str, strict_query: bool optional_segments = True if path: optional_segments &= bool(_path_regex().match(path)) - if query and parse_qs(query, strict_parsing=strict_query): - optional_segments &= True + try: + if ( + query + # ref: https://github.com/python/cpython/issues/117109 + and parse_qs(query, strict_parsing=strict_query, separator="&") + and parse_qs(query, strict_parsing=strict_query, separator=";") + ): + optional_segments &= True + except TypeError: + # for Python < v3.9.2 (official v3.10) + if query and parse_qs(query, strict_parsing=strict_query): + optional_segments &= True if fragment: # See RFC3986 Section 3.5 Fragment for allowed characters optional_segments &= bool(re.fullmatch(r"[0-9a-zA-Z?/:@\-._~%!$&'()*+,;=]*", fragment)) diff --git a/tests/test_domain.py b/tests/test_domain.py index 9f82b55d..4cd9c658 100644 --- a/tests/test_domain.py +++ b/tests/test_domain.py @@ -18,6 +18,7 @@ ("11.com", False, False), ("3.cn.", True, False), ("_example.com", False, True), + ("example_.com", False, True), ("a.cn", False, False), ("sub1.sub2.sample.co.uk", False, False), ("somerandomexample.xn--fiqs8s", False, False), diff --git a/tests/test_url.py b/tests/test_url.py index 23baf8fa..079e62e2 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -152,6 +152,7 @@ def test_returns_true_on_valid_url(value: str): "http://[2010:836B:4179::836B:4179", "http://2010:836B:4179::836B:4179", "http://2010:836B:4179::836B:4179:80/index.html", + "https://example.org?q=search');alert(document.domain);", "https://www.example.com/foo/?bar=baz&inga=42&quux", "https://foo.com/img/bar/baz.jpg?-62169987208", "https://foo.bar.net/baz.php?-/inga/test-lenient-query/",