Skip to content

Commit

Permalink
Merge pull request #356 from yozachar/workshop
Browse files Browse the repository at this point in the history
feat: adds `private` parameter to `ip_address`, `hostname` & `url`
  • Loading branch information
yozachar committed Apr 3, 2024
2 parents a2b4fc0 + 9327008 commit cab0c87
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 11 deletions.
19 changes: 19 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@ Note to self: Breaking changes must increment either
-->

## 0.27.0 (2024-04-03)

_**Breaking**_ ⚠️

- patch: moves `base58` and `base64` into `encoding` by @yozachar in [#354](https://github.com/python-validators/validators/pull/354)

_**Features**_

- feat: lays foundation for URI validation by @yozachar in [#353](https://github.com/python-validators/validators/pull/353)
- feat: adds `private` parameter to `ip_address`, `hostname` & `url` by @yozachar in [#356](https://github.com/python-validators/validators/pull/356)

_**Maintenance**_

- patch: adds `encoding` tests and docs by @yozachar in [#355](https://github.com/python-validators/validators/pull/355)

**Full Changelog**: [`0.26.0...0.27.0`](https://github.com/python-validators/validators/compare/0.26.0...0.27.0)

---

## 0.26.0 (2024-04-02)

_**Breaking**_
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

| Version | Supported |
| ---------- | ------------------ |
| `>=0.26.0` | :white_check_mark: |
| `>=0.27.0` | :white_check_mark: |

## Reporting a Vulnerability

Expand Down
2 changes: 1 addition & 1 deletion src/validators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,4 @@
"validator",
)

__version__ = "0.26.0"
__version__ = "0.27.0"
8 changes: 6 additions & 2 deletions src/validators/hostname.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# standard
from functools import lru_cache
import re
from typing import Optional

from .domain import domain

Expand Down Expand Up @@ -54,6 +55,7 @@ def hostname(
skip_ipv4_addr: bool = False,
may_have_port: bool = True,
maybe_simple: bool = True,
private: Optional[bool] = None, # only for ip-addresses
rfc_1034: bool = False,
rfc_2782: bool = False,
):
Expand Down Expand Up @@ -92,6 +94,8 @@ def hostname(
Hostname string may contain port number.
maybe_simple:
Hostname string maybe only hyphens and alpha-numerals.
private:
Embedded IP address is public if `False`, private/local if `True`.
rfc_1034:
Allow trailing dot in domain/host name.
Ref: [RFC 1034](https://www.rfc-editor.org/rfc/rfc1034).
Expand All @@ -110,13 +114,13 @@ def hostname(
return (
(_simple_hostname_regex().match(host_seg) if maybe_simple else False)
or domain(host_seg, rfc_1034=rfc_1034, rfc_2782=rfc_2782)
or (False if skip_ipv4_addr else ipv4(host_seg, cidr=False))
or (False if skip_ipv4_addr else ipv4(host_seg, cidr=False, private=private))
or (False if skip_ipv6_addr else ipv6(host_seg, cidr=False))
)

return (
(_simple_hostname_regex().match(value) if maybe_simple else False)
or domain(value, rfc_1034=rfc_1034, rfc_2782=rfc_2782)
or (False if skip_ipv4_addr else ipv4(value, cidr=False))
or (False if skip_ipv4_addr else ipv4(value, cidr=False, private=private))
or (False if skip_ipv6_addr else ipv6(value, cidr=False))
)
47 changes: 40 additions & 7 deletions src/validators/ip_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,44 @@
IPv6Network,
NetmaskValueError,
)
import re
from typing import Optional

# local
from .utils import validator


def _check_private_ip(value: str, is_private: Optional[bool]):
if is_private is None:
return True
if is_private and (
any(
value.startswith(l_bit)
for l_bit in {
"10.", # private
"192.168.", # private
"169.254.", # link-local
"127.", # localhost
"0.0.0.0", # loopback #nosec
}
)
or re.match(r"^172\.(?:1[6-9]|2\d|3[0-1])\.", value) # private
or re.match(r"^(?:22[4-9]|23[0-9]|24[0-9]|25[0-5])\.", value) # broadcast
):
return True
return False


@validator
def ipv4(value: str, /, *, cidr: bool = True, strict: bool = False, host_bit: bool = True):
def ipv4(
value: str,
/,
*,
cidr: bool = True,
strict: bool = False,
private: Optional[bool] = None,
host_bit: bool = True,
):
"""Returns whether a given value is a valid IPv4 address.
From Python version 3.9.5 leading zeros are no longer tolerated
Expand All @@ -36,9 +67,11 @@ def ipv4(value: str, /, *, cidr: bool = True, strict: bool = False, host_bit: bo
value:
IP address string to validate.
cidr:
IP address string may contain CIDR notation
IP address string may contain CIDR notation.
strict:
IP address string is strictly in CIDR notation
IP address string is strictly in CIDR notation.
private:
IP address is public if `False`, private/local/loopback/broadcast if `True`.
host_bit:
If `False` and host bits (along with network bits) _are_ set in the supplied
address, this function raises a validation error. ref [IPv4Network][2].
Expand All @@ -54,8 +87,8 @@ def ipv4(value: str, /, *, cidr: bool = True, strict: bool = False, host_bit: bo
if cidr:
if strict and value.count("/") != 1:
raise ValueError("IPv4 address was expected in CIDR notation")
return IPv4Network(value, strict=not host_bit)
return IPv4Address(value)
return IPv4Network(value, strict=not host_bit) and _check_private_ip(value, private)
return IPv4Address(value) and _check_private_ip(value, private)
except (ValueError, AddressValueError, NetmaskValueError):
return False

Expand All @@ -81,9 +114,9 @@ def ipv6(value: str, /, *, cidr: bool = True, strict: bool = False, host_bit: bo
value:
IP address string to validate.
cidr:
IP address string may contain CIDR annotation
IP address string may contain CIDR annotation.
strict:
IP address string is strictly in CIDR notation
IP address string is strictly in CIDR notation.
host_bit:
If `False` and host bits (along with network bits) _are_ set in the supplied
address, this function raises a validation error. ref [IPv6Network][2].
Expand Down
8 changes: 8 additions & 0 deletions src/validators/url.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# standard
from functools import lru_cache
import re
from typing import Optional
from urllib.parse import parse_qs, unquote, urlsplit

# local
Expand Down Expand Up @@ -80,6 +81,7 @@ def _validate_netloc(
skip_ipv4_addr: bool,
may_have_port: bool,
simple_host: bool,
private: Optional[bool],
rfc_1034: bool,
rfc_2782: bool,
):
Expand All @@ -97,6 +99,7 @@ def _validate_netloc(
skip_ipv4_addr=skip_ipv4_addr,
may_have_port=may_have_port,
maybe_simple=simple_host,
private=private,
rfc_1034=rfc_1034,
rfc_2782=rfc_2782,
)
Expand All @@ -111,6 +114,7 @@ def _validate_netloc(
skip_ipv4_addr=skip_ipv4_addr,
may_have_port=may_have_port,
maybe_simple=simple_host,
private=private,
rfc_1034=rfc_1034,
rfc_2782=rfc_2782,
) and _validate_auth_segment(basic_auth)
Expand Down Expand Up @@ -151,6 +155,7 @@ def url(
may_have_port: bool = True,
simple_host: bool = False,
strict_query: bool = True,
private: Optional[bool] = None, # only for ip-addresses
rfc_1034: bool = False,
rfc_2782: bool = False,
):
Expand Down Expand Up @@ -191,6 +196,8 @@ def url(
URL string maybe only hyphens and alpha-numerals.
strict_query:
Fail validation on query string parsing error.
private:
Embedded IP address is public if `False`, private/local if `True`.
rfc_1034:
Allow trailing dot in domain/host name.
Ref: [RFC 1034](https://www.rfc-editor.org/rfc/rfc1034).
Expand Down Expand Up @@ -220,6 +227,7 @@ def url(
skip_ipv4_addr,
may_have_port,
simple_host,
private,
rfc_1034,
rfc_2782,
)
Expand Down
30 changes: 30 additions & 0 deletions tests/test_url.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"""Test URL."""

# standard
from typing import Optional

# external
import pytest

Expand Down Expand Up @@ -106,6 +109,19 @@ def test_returns_true_on_valid_url(value: str):
assert url(value)


@pytest.mark.parametrize(
"value, private",
[
("http://username:password@10.0.10.1/", True),
("http://username:password@192.168.10.10:4010/", True),
("http://127.0.0.1", True),
],
)
def test_returns_true_on_valid_private_url(value: str, private: Optional[bool]):
"""Test returns true on valid private url."""
assert url(value, private=private)


@pytest.mark.parametrize(
"value",
[
Expand Down Expand Up @@ -188,3 +204,17 @@ def test_returns_true_on_valid_url(value: str):
def test_returns_failed_validation_on_invalid_url(value: str):
"""Test returns failed validation on invalid url."""
assert isinstance(url(value), ValidationError)


@pytest.mark.parametrize(
"value, private",
[
("http://username:password@192.168.10.10:4010", False),
("http://username:password@127.0.0.1:8080", False),
("http://10.0.10.1", False),
("http://255.255.255.255", False),
],
)
def test_returns_failed_validation_on_invalid_private_url(value: str, private: Optional[bool]):
"""Test returns failed validation on invalid private url."""
assert isinstance(url(value, private=private), ValidationError)

0 comments on commit cab0c87

Please sign in to comment.