From 27df0a407e17eba9fe558da3b1aa7c98cda06bad Mon Sep 17 00:00:00 2001 From: missytake Date: Thu, 16 Apr 2026 14:31:52 +0200 Subject: [PATCH] config: validate domains when formatting them --- chatmaild/pyproject.toml | 1 + chatmaild/src/chatmaild/config.py | 3 ++ chatmaild/src/chatmaild/tests/test_config.py | 57 +++++++++++++++++++- cmdeploy/src/cmdeploy/tests/plugin.py | 2 +- 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/chatmaild/pyproject.toml b/chatmaild/pyproject.toml index 01ef93d2..c043f39b 100644 --- a/chatmaild/pyproject.toml +++ b/chatmaild/pyproject.toml @@ -10,6 +10,7 @@ dependencies = [ "filelock", "requests", "crypt-r >= 3.13.1 ; python_version >= '3.11'", + "domain-validator", ] [tool.setuptools] diff --git a/chatmaild/src/chatmaild/config.py b/chatmaild/src/chatmaild/config.py index 35733b57..fb040683 100644 --- a/chatmaild/src/chatmaild/config.py +++ b/chatmaild/src/chatmaild/config.py @@ -2,6 +2,7 @@ import ipaddress from pathlib import Path import iniconfig +from domain_validator import DomainValidator from chatmaild.user import User @@ -194,10 +195,12 @@ def is_valid_ipv4(address: str) -> bool: def format_arpa_address(address: str) -> str: if is_valid_ipv4(address): return ipaddress.IPv4Address(address).reverse_pointer + DomainValidator().validate_domain_re(address) return address def format_deliverable_domain(mail_domain: str) -> str: if is_valid_ipv4(mail_domain): return f"[{mail_domain}]" + DomainValidator().validate_domain_re(mail_domain) return mail_domain diff --git a/chatmaild/src/chatmaild/tests/test_config.py b/chatmaild/src/chatmaild/tests/test_config.py index f23928e3..ca609000 100644 --- a/chatmaild/src/chatmaild/tests/test_config.py +++ b/chatmaild/src/chatmaild/tests/test_config.py @@ -1,6 +1,14 @@ +from contextlib import nullcontext as does_not_raise + import pytest -from chatmaild.config import parse_size_mb, read_config +from chatmaild.config import ( + format_arpa_address, + format_deliverable_domain, + is_valid_ipv4, + parse_size_mb, + read_config, +) def test_read_config_basic(example_config): @@ -16,6 +24,11 @@ def test_read_config_basic(example_config): assert example_config.mail_domain_deliverable == "chat.example.org" +def test_read_config_deliverable(ipv4_config): + assert ipv4_config.mail_domain == "1.3.3.7" + assert ipv4_config.mail_domain_deliverable == "[1.3.3.7]" + + def test_read_config_basic_using_defaults(tmp_path, maildomain): inipath = tmp_path.joinpath("chatmail.ini") inipath.write_text(f"[params]\nmail_domain = {maildomain}") @@ -136,3 +149,45 @@ def test_max_mailbox_size_mb(make_config): config = make_config("chat.example.org") assert config.max_mailbox_size == "500M" assert config.max_mailbox_size_mb == 500 + + +@pytest.mark.parametrize( + ["input", "result"], + [ + ("example.org", False), + ("1.3.3.7", True), + ("fe::1", False), + ("ad.1e.dag.adf", False), + ("12394142", False), + ], +) +def test_is_valid_ipv4(input, result): + assert result == is_valid_ipv4(input) + + +@pytest.mark.parametrize( + ["input", "result", "exception"], + [ + ("example.org", "example.org", does_not_raise()), + ("1.3.3.7", "7.3.3.1.in-addr.arpa", does_not_raise()), + ("fe::1", None, pytest.raises(ValueError)), + ("12394142", None, pytest.raises(ValueError)), + ], +) +def test_format_arpa_address(input, result, exception): + with exception: + assert result == format_arpa_address(input) + + +@pytest.mark.parametrize( + ["input", "result", "exception"], + [ + ("example.org", "example.org", does_not_raise()), + ("1.3.3.7", "[1.3.3.7]", does_not_raise()), + ("fe::1", None, pytest.raises(ValueError)), + ("12394142", None, pytest.raises(ValueError)), + ], +) +def test_format_deliverable_domain(input, result, exception): + with exception: + assert result == format_deliverable_domain(input) diff --git a/cmdeploy/src/cmdeploy/tests/plugin.py b/cmdeploy/src/cmdeploy/tests/plugin.py index f0d86d78..7deef20a 100644 --- a/cmdeploy/src/cmdeploy/tests/plugin.py +++ b/cmdeploy/src/cmdeploy/tests/plugin.py @@ -297,7 +297,7 @@ def gencreds(chatmail_config): password = "".join( random.choices(alphanumeric, k=chatmail_config.password_min_length) ) - yield f"{user}@{addr_domain}", f"{password}" + yield f"{user}@{domain}", f"{password}" return lambda domain=None: next(gen(domain))