#
|
|
#
|
|
#
|
|
|
|
from unittest import TestCase
|
|
|
|
from octodns.record.chunked import _ChunkedValue, _parse
|
|
from octodns.record.rr import RrParseError
|
|
from octodns.record.spf import SpfRecord
|
|
from octodns.zone import Zone
|
|
|
|
|
|
class TestRecordChunked(TestCase):
|
|
def test_chunked_value_rdata_text(self):
|
|
for s in (
|
|
None,
|
|
'',
|
|
'word',
|
|
42,
|
|
42.43,
|
|
'1.2.3',
|
|
'some.words.that.here',
|
|
'1.2.word.4',
|
|
'1.2.3.4',
|
|
):
|
|
self.assertEqual(s, _ChunkedValue.parse_rdata_text(s))
|
|
|
|
# quotes are removed
|
|
s = '"Hello World!"'
|
|
self.assertEqual(s.replace('"', ''), _ChunkedValue.parse_rdata_text(s))
|
|
|
|
# semi-colons are escaped
|
|
self.assertEqual(
|
|
'Hello\\; World!', _ChunkedValue.parse_rdata_text('"Hello; World!"')
|
|
)
|
|
|
|
# unquoted whitespace seperated pieces are concatenated
|
|
self.assertEqual(
|
|
'thisrunstogether',
|
|
_ChunkedValue.parse_rdata_text('this runs\ttogether'),
|
|
)
|
|
|
|
# mix of quoted and unquoted
|
|
self.assertEqual(
|
|
'This is quoted andthisisnot, this is back to being quoted',
|
|
_ChunkedValue.parse_rdata_text(
|
|
'"This is quoted " and this is not ", this is back to being quoted"'
|
|
),
|
|
)
|
|
|
|
for s in (
|
|
'"no closing quote',
|
|
'"no closing quote ',
|
|
'"no closing \\" quote',
|
|
):
|
|
with self.assertRaises(RrParseError):
|
|
_ChunkedValue.parse_rdata_text(s)
|
|
|
|
# since we're always a string validate and __init__ don't
|
|
# parse_rdata_text
|
|
|
|
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'),
|
|
)
|
|
|
|
def test_quoted(self):
|
|
# test escaped double quotes
|
|
for value, expected in (
|
|
(
|
|
'"This is a quoted string with escaped \\"quotes\\""',
|
|
'This is a quoted string with escaped "quotes"',
|
|
),
|
|
):
|
|
chunked = _ChunkedValue.process([value])
|
|
self.assertEqual(1, len(chunked))
|
|
chunked = chunked[0]
|
|
self.assertEqual(expected, chunked)
|
|
|
|
# all whitespace
|
|
chunked = _ChunkedValue.process(['" \t\t"'])
|
|
self.assertEqual(1, len(chunked))
|
|
self.assertEqual(' \t\t', chunked[0])
|
|
|
|
# missing closing quote
|
|
value = '"This is quoted, but has no end'
|
|
chunked = _ChunkedValue.process([value])
|
|
self.assertEqual(1, len(chunked))
|
|
self.assertEqual(value[1:], chunked[0])
|
|
|
|
value = 'This is not quoted, but has end quote"'
|
|
chunked = _ChunkedValue.process([value])
|
|
self.assertEqual(1, len(chunked))
|
|
self.assertEqual(value, chunked[0])
|
|
|
|
def test_unquoted(self):
|
|
for value in (
|
|
'This is not quoted',
|
|
' This has leading space',
|
|
' This has leading spaces',
|
|
'\tThis has a leading tab',
|
|
'\t\tThis has leading tabs',
|
|
' \tThis has leading tabs',
|
|
'This has trailing space ',
|
|
'This has trailing spaces ',
|
|
'This has a trailing tab\t',
|
|
'This has trailing tabs\t\t',
|
|
' \tThis has leading tabs\t ',
|
|
' This has leading and trailing space ',
|
|
' This has leading and trailing space ',
|
|
'\tThis has a leading and trailing tab\t',
|
|
'\t\tThis has leading and trailing tabs\t\t',
|
|
'This has a quote " in the middle',
|
|
):
|
|
chunked = _ChunkedValue.process([value])
|
|
self.assertEqual(1, len(chunked))
|
|
self.assertEqual(value.strip(), chunked[0])
|
|
|
|
# all whitespace
|
|
chunked = _ChunkedValue.process([' '])
|
|
self.assertEqual(1, len(chunked))
|
|
self.assertEqual('', chunked[0])
|
|
|
|
def test_spec_unquoted(self):
|
|
for value in (
|
|
'This is not quoted',
|
|
' This has leading space',
|
|
' This has leading spaces',
|
|
'\tThis has a leading tab',
|
|
'\t\tThis has leading tabs',
|
|
' \tThis has leading tabs',
|
|
'This has trailing space ',
|
|
'This has trailing spaces ',
|
|
'This has a trailing tab\t',
|
|
'This has trailing tabs\t\t',
|
|
' \tThis has leading tabs\t ',
|
|
' This has leading and trailing space ',
|
|
' This has leading and trailing space ',
|
|
'\tThis has a leading and trailing tab\t',
|
|
'\t\tThis has leading and trailing tabs\t\t',
|
|
):
|
|
parsed = list(_parse(value, spec_unquoted=True))
|
|
self.assertEqual(value.strip().split(), parsed)
|
|
|
|
def test_large_values(self):
|
|
# There is additional testing in TXT
|
|
|
|
# "standard" format quoted and split value
|
|
value = (
|
|
'"Lorem ipsum dolor sit amet, consectetur '
|
|
'adipiscing elit, sed do eiusmod tempor incididunt ut '
|
|
'labore et dolore magna aliqua. Ut enim ad minim veniam, '
|
|
'quis nostrud exercitation ullamco laboris nisi ut aliquip '
|
|
'ex" " ea commodo consequat. Duis aute irure dolor in '
|
|
'reprehenderit in voluptate velit esse cillum dolore eu '
|
|
'fugiat nulla pariatur. Excepteur sint occaecat cupidatat '
|
|
'non proident, sunt in culpa qui officia deserunt mollit '
|
|
'anim id est laborum."'
|
|
)
|
|
chunked = _ChunkedValue.process([value])
|
|
self.assertEqual(1, len(chunked))
|
|
chunked = chunked[0]
|
|
self.assertIsInstance(chunked, _ChunkedValue)
|
|
dechunked_value = (
|
|
'Lorem ipsum dolor sit amet, consectetur '
|
|
'adipiscing elit, sed do eiusmod tempor incididunt ut '
|
|
'labore et dolore magna aliqua. Ut enim ad minim veniam, '
|
|
'quis nostrud exercitation ullamco laboris nisi ut aliquip '
|
|
'ex ea commodo consequat. Duis aute irure dolor in '
|
|
'reprehenderit in voluptate velit esse cillum dolore eu '
|
|
'fugiat nulla pariatur. Excepteur sint occaecat cupidatat '
|
|
'non proident, sunt in culpa qui officia deserunt mollit '
|
|
'anim id est laborum.'
|
|
)
|
|
self.assertEqual(dechunked_value, chunked)
|
|
|
|
# non-quoted is a no-op
|
|
chunked = _ChunkedValue.process([dechunked_value])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
|
|
# leading whitespace
|
|
chunked = _ChunkedValue.process([f' {value}'])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
chunked = _ChunkedValue.process([f' {value}'])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
chunked = _ChunkedValue.process([f'\t{value}'])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
chunked = _ChunkedValue.process([f'\t\t{value}'])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
chunked = _ChunkedValue.process([f' \t{value}'])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
|
|
# trailing whitespace
|
|
chunked = _ChunkedValue.process([f'{value} '])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
chunked = _ChunkedValue.process([f'{value} '])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
chunked = _ChunkedValue.process([f'{value}\t'])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
chunked = _ChunkedValue.process([f'{value}\t\t'])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
chunked = _ChunkedValue.process([f'{value} \t'])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
|
|
# both
|
|
chunked = _ChunkedValue.process([f' {value} '])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
chunked = _ChunkedValue.process([f'\t{value} '])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
chunked = _ChunkedValue.process([f' {value}\t'])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
|
|
# variations of whitepsace in the chunk seperator
|
|
multi = value.replace('" "', '" "')
|
|
chunked = _ChunkedValue.process([multi])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
multi = value.replace('" "', '"\t"')
|
|
chunked = _ChunkedValue.process([multi])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
multi = value.replace('" "', '"\t\t"')
|
|
chunked = _ChunkedValue.process([multi])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
multi = value.replace('" "', '" \t"')
|
|
chunked = _ChunkedValue.process([multi])[0]
|
|
self.assertEqual(dechunked_value, chunked)
|
|
|
|
# ~real world test case
|
|
values = [
|
|
'before',
|
|
'"v=DKIM1\\; h=sha256\\; k=rsa\\; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx78E7PtJvr8vpoNgHdIAe+llFKoy8WuTXDd6Z5mm3D4AUva9MBt5fFetxg/kcRy3KMDnMw6kDybwbpS/oPw1ylk6DL1xit7Cr5xeYYSWKukxXURAlHwT2K72oUsFKRUvN1X9lVysAeo+H8H/22Z9fJ0P30sOuRIRqCaiz+OiUYicxy4x" "rpfH2s9a+o3yRwX3zhlp8GjRmmmyK5mf7CkQTCfjnKVsYtB7mabXXmClH9tlcymnBMoN9PeXxaS5JRRysVV8RBCC9/wmfp9y//cck8nvE/MavFpSUHvv+TfTTdVKDlsXPjKX8iZQv0nO3xhspgkqFquKjydiR8nf4meHhwIDAQAB"',
|
|
'z after',
|
|
]
|
|
chunked = _ChunkedValue.process(values)
|
|
expected = [
|
|
'before',
|
|
'v=DKIM1\\; h=sha256\\; k=rsa\\; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx78E7PtJvr8vpoNgHdIAe+llFKoy8WuTXDd6Z5mm3D4AUva9MBt5fFetxg/kcRy3KMDnMw6kDybwbpS/oPw1ylk6DL1xit7Cr5xeYYSWKukxXURAlHwT2K72oUsFKRUvN1X9lVysAeo+H8H/22Z9fJ0P30sOuRIRqCaiz+OiUYicxy4xrpfH2s9a+o3yRwX3zhlp8GjRmmmyK5mf7CkQTCfjnKVsYtB7mabXXmClH9tlcymnBMoN9PeXxaS5JRRysVV8RBCC9/wmfp9y//cck8nvE/MavFpSUHvv+TfTTdVKDlsXPjKX8iZQv0nO3xhspgkqFquKjydiR8nf4meHhwIDAQAB',
|
|
'z after',
|
|
]
|
|
self.assertEqual(expected, chunked)
|
|
|
|
def test_rdata_text(self):
|
|
value = _ChunkedValue('hello world')
|
|
self.assertEqual('"hello world"', value.rdata_text)
|
|
|
|
value = _ChunkedValue('hello " world')
|
|
self.assertEqual('"hello \\" world"', value.rdata_text)
|
|
|
|
# 254 chars
|
|
val = 'x' * 254
|
|
value = _ChunkedValue(val)
|
|
self.assertEqual(f'"{val}"', value.rdata_text)
|
|
|
|
# 255 chars
|
|
val = 'x' * 255
|
|
value = _ChunkedValue(val)
|
|
self.assertEqual(f'"{val}"', value.rdata_text)
|
|
|
|
# 256 chars, single split
|
|
val = 'x' * 256
|
|
value = _ChunkedValue(val)
|
|
got = value.rdata_text
|
|
expected = '"' + ('x' * 255) + '"'
|
|
self.assertEqual(expected, got[:257])
|
|
self.assertEqual(' "x"', got[257:])
|
|
|
|
val = (
|
|
'Lorem ipsum dolor sit amet, consectetur '
|
|
'adipiscing elit, sed do eiusmod tempor incididunt ut '
|
|
'labore et dolore magna aliqua. Ut enim ad minim veniam, '
|
|
'quis nostrud exercitation ullamco laboris nisi ut aliquip '
|
|
'ex ea commodo consequat. Duis aute irure dolor in '
|
|
'reprehenderit in voluptate velit esse cillum dolore eu '
|
|
'fugiat nulla pariatur. Excepteur sint occaecat cupidatat '
|
|
'non proident, sunt in culpa qui officia deserunt mollit '
|
|
'anim id est laborum.'
|
|
)
|
|
value = _ChunkedValue(val)
|
|
expected = (
|
|
'"Lorem ipsum dolor sit amet, consectetur '
|
|
'adipiscing elit, sed do eiusmod tempor incididunt ut '
|
|
'labore et dolore magna aliqua. Ut enim ad minim veniam, '
|
|
'quis nostrud exercitation ullamco laboris nisi ut aliquip '
|
|
'ex ea commodo consequat. Duis aute irure dolor i" "n '
|
|
'reprehenderit in voluptate velit esse cillum dolore eu '
|
|
'fugiat nulla pariatur. Excepteur sint occaecat cupidatat '
|
|
'non proident, sunt in culpa qui officia deserunt mollit '
|
|
'anim id est laborum."'
|
|
)
|
|
got = value.rdata_text
|
|
self.assertEqual(expected, got)
|