diff --git a/.changelog/85710a9264524662becdc7e52e71e241.md b/.changelog/85710a9264524662becdc7e52e71e241.md new file mode 100644 index 0000000..85d1f8c --- /dev/null +++ b/.changelog/85710a9264524662becdc7e52e71e241.md @@ -0,0 +1,4 @@ +--- +type: minor +--- +Add validation to TXT records to check for double escaped semi-colons \ No newline at end of file diff --git a/octodns/record/chunked.py b/octodns/record/chunked.py index a07acfd..433163a 100644 --- a/octodns/record/chunked.py +++ b/octodns/record/chunked.py @@ -44,6 +44,7 @@ class _ChunkedValuesMixin(ValuesMixin): class _ChunkedValue(str): _unescaped_semicolon_re = re.compile(r'\w;') + _double_escaped_semicolon_re = re.compile(r'\\\\;') @classmethod def parse_rdata_text(cls, value): @@ -62,6 +63,8 @@ class _ChunkedValue(str): for value in data: if cls._unescaped_semicolon_re.search(value): reasons.append(f'unescaped ; in "{value}"') + if cls._double_escaped_semicolon_re.search(value): + reasons.append(f'double escaped ; in "{value}"') try: value.encode('ascii') except UnicodeEncodeError: diff --git a/tests/config-semis/escaped.semis.yaml b/tests/config-semis/escaped.semis.yaml index 634ed15..241c3f8 100644 --- a/tests/config-semis/escaped.semis.yaml +++ b/tests/config-semis/escaped.semis.yaml @@ -6,3 +6,4 @@ two: type: TXT values: - This has a semi-colon too\; that is escaped. + - \; diff --git a/tests/test_octodns_provider_yaml.py b/tests/test_octodns_provider_yaml.py index 035439d..f60f741 100644 --- a/tests/test_octodns_provider_yaml.py +++ b/tests/test_octodns_provider_yaml.py @@ -15,6 +15,7 @@ from octodns.idna import idna_encode from octodns.provider import ProviderException from octodns.provider.yaml import SplitYamlProvider, YamlProvider from octodns.record import Create, NsValue, Record, ValuesMixin +from octodns.record.exception import ValidationError from octodns.zone import SubzoneRecordException, Zone @@ -494,21 +495,14 @@ xn--dj-kia8a: ) zone = Zone('escaped.semis.', []) - source.populate(zone) - self.assertEqual(2, len(zone.records)) - one = next(r for r in zone.records if r.name == 'one') - self.assertTrue(one) - # self.assertEqual( - # ["This has a semi-colon\\; that isn't escaped."], one.values - # ) - two = next(r for r in zone.records if r.name == 'two') - self.assertTrue(two) - - -# self.assertEqual( -# ["This has a semi-colon too\\; that isn't escaped.", '\\;'], -# two.values, -# ) + with self.assertRaises(ValidationError) as ctx: + source.populate(zone) + self.assertEqual( + [ + 'double escaped ; in "This has a semi-colon\\\\; that is escaped."' + ], + ctx.exception.reasons, + ) class TestSplitYamlProvider(TestCase): diff --git a/tests/test_octodns_record_chunked.py b/tests/test_octodns_record_chunked.py index 4d7bbc0..6b40752 100644 --- a/tests/test_octodns_record_chunked.py +++ b/tests/test_octodns_record_chunked.py @@ -59,6 +59,12 @@ class TestChunkedValue(TestCase): _ChunkedValue.validate('hello; world', 'TXT'), ) + # double escaped ; + self.assertEqual( + ['double escaped ; in "hello\\\\; world"'], + _ChunkedValue.validate('hello\\\\; world', 'TXT'), + ) + # non-asci self.assertEqual( ['non ASCII character in "v=spf1 –all"'],