From cfa7abaee5f9d6262a807f8552478b41716a9e55 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Fri, 19 May 2023 09:57:56 -0700 Subject: [PATCH] Validations to ensure Record.name and Zone.name have no whitespace --- CHANGELOG.md | 4 ++++ octodns/record/base.py | 4 ++++ octodns/record/exception.py | 2 +- octodns/zone.py | 3 +++ tests/test_octodns_record.py | 22 ++++++++++++++++++++++ tests/test_octodns_zone.py | 5 +++++ 6 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc478da..5c78c4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v1.0.0.rc2 - 2023-??-?? - + +* Record and Zone validation now ensures there's no whitespace in names + ## v1.0.0.rc0 - 2023-05-16 - First of the ones #### Noteworthy changes diff --git a/octodns/record/base.py b/octodns/record/base.py index e9f9599..b6e3ed3 100644 --- a/octodns/record/base.py +++ b/octodns/record/base.py @@ -41,6 +41,10 @@ class Record(EqualityTupleMixin): # convert the error into a reason reasons.append(str(e)) name = str(name) + + if ' ' in name or '\t' in name: + reasons.append('invalid record, whitespace is not allowed') + fqdn = f'{name}.{zone.name}' if name else zone.name try: _type = data['type'] diff --git a/octodns/record/exception.py b/octodns/record/exception.py index 2d27c8d..34aea0b 100644 --- a/octodns/record/exception.py +++ b/octodns/record/exception.py @@ -13,7 +13,7 @@ class ValidationError(RecordException): @classmethod def build_message(cls, fqdn, reasons): reasons = '\n - '.join(reasons) - return f'Invalid record {idna_decode(fqdn)}\n - {reasons}' + return f'Invalid record "{idna_decode(fqdn)}"\n - {reasons}' def __init__(self, fqdn, reasons): super().__init__(self.build_message(fqdn, reasons)) diff --git a/octodns/zone.py b/octodns/zone.py index 82596d5..c35e94c 100644 --- a/octodns/zone.py +++ b/octodns/zone.py @@ -28,6 +28,9 @@ class Zone(object): def __init__(self, name, sub_zones): if not name[-1] == '.': raise Exception(f'Invalid zone name {name}, missing ending dot') + elif ' ' in name or '\t' in name: + raise Exception(f'Invalid zone name {name}, whitespace not allowed') + # internally everything is idna self.name = idna_encode(str(name)) if name else name # we'll keep a decoded version around for logs and errors diff --git a/tests/test_octodns_record.py b/tests/test_octodns_record.py index 98b5f9f..88a7dfc 100644 --- a/tests/test_octodns_record.py +++ b/tests/test_octodns_record.py @@ -398,6 +398,28 @@ class TestRecordValidation(TestCase): zone = Zone('unit.tests.', []) def test_base(self): + # no spaces + for name in ( + ' ', + ' leading', + 'trailing ', + 'in the middle', + '\t', + '\tleading', + 'trailing\t', + 'in\tthe\tmiddle', + ): + with self.assertRaises(ValidationError) as ctx: + Record.new( + self.zone, + name, + {'ttl': 300, 'type': 'A', 'value': '1.2.3.4'}, + ) + reason = ctx.exception.reasons[0] + self.assertEqual( + 'invalid record, whitespace is not allowed', reason + ) + # name = '@' with self.assertRaises(ValidationError) as ctx: name = '@' diff --git a/tests/test_octodns_zone.py b/tests/test_octodns_zone.py index cacfe27..07e94ea 100644 --- a/tests/test_octodns_zone.py +++ b/tests/test_octodns_zone.py @@ -186,6 +186,11 @@ class TestZone(TestCase): Zone('not.allowed', []) self.assertTrue('missing ending dot' in str(ctx.exception)) + def test_whitespace(self): + with self.assertRaises(Exception) as ctx: + Zone('space not allowed.', []) + self.assertTrue('whitespace not allowed' in str(ctx.exception)) + def test_sub_zones(self): # NS for exactly the sub is allowed