diff --git a/CHANGELOG.md b/CHANGELOG.md index b8633bf..f1ee744 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ a plan with a single change type with a single value, e.g. CNAME. * Support added for config env variable expansion on nested levels, not just top-level provider/processor keys +* _ChunkedValue ASCII validation added, SPF & TXT ## v1.4.0 - 2023-12-04 - Minor Meta diff --git a/octodns/record/chunked.py b/octodns/record/chunked.py index 976baea..3f088e3 100644 --- a/octodns/record/chunked.py +++ b/octodns/record/chunked.py @@ -52,6 +52,10 @@ class _ChunkedValue(str): for value in data: if cls._unescaped_semicolon_re.search(value): reasons.append(f'unescaped ; in "{value}"') + try: + value.encode('ascii') + except UnicodeEncodeError: + reasons.append(f'non ASCII character in "{value}"') return reasons @classmethod diff --git a/tests/test_octodns_record_chunked.py b/tests/test_octodns_record_chunked.py index ded2e9b..30592cc 100644 --- a/tests/test_octodns_record_chunked.py +++ b/tests/test_octodns_record_chunked.py @@ -37,3 +37,33 @@ class TestRecordChunked(TestCase): zone = Zone('unit.tests.', []) a = SpfRecord(zone, 'a', {'ttl': 42, 'value': 'some.target.'}) self.assertEqual('some.target.', a.values[0].rdata_text) + + +class TestChunkedValue(TestCase): + def test_validate(self): + # valid stuff + for data in ('a', 'ab', 'abcdefg', 'abc def', 'abc\\; def'): + self.assertFalse(_ChunkedValue.validate(data, 'TXT')) + self.assertFalse(_ChunkedValue.validate([data], 'TXT')) + + # missing + for data in (None, []): + self.assertEqual( + ['missing value(s)'], _ChunkedValue.validate(data, 'TXT') + ) + + # unescaped ; + self.assertEqual( + ['unescaped ; in "hello; world"'], + _ChunkedValue.validate('hello; world', 'TXT'), + ) + + # non-asci + self.assertEqual( + ['non ASCII character in "v=spf1 –all"'], + _ChunkedValue.validate('v=spf1 –all', 'TXT'), + ) + self.assertEqual( + ['non ASCII character in "Déjà vu"'], + _ChunkedValue.validate('Déjà vu', 'TXT'), + )