diff --git a/zonefile_parser/helper.py b/zonefile_parser/helper.py index 27a9bbf..67b6737 100644 --- a/zonefile_parser/helper.py +++ b/zonefile_parser/helper.py @@ -3,6 +3,8 @@ def remove_comments(line:str): for index,character in enumerate(line): if character == ";" and not is_in_quote(line,index): + if index > 0 and line[index - 1] == '\\': + continue line = line[:index] break return line diff --git a/zonefile_parser/helper_test.py b/zonefile_parser/helper_test.py index db553c3..8b9493f 100644 --- a/zonefile_parser/helper_test.py +++ b/zonefile_parser/helper_test.py @@ -16,6 +16,16 @@ def test_doesnt_change_string_without_comment(self): result = helper.remove_comments(input) assert result == input + def test_doesnt_remove_escaped_semicolon(self): + input = r"v=DMARC1\;" + result = helper.remove_comments(input) + assert result == input + + def test_doesnt_treat_text_after_escaped_semicolon_as_comment(self): + input = r"v=DMARC1\; comment" + result = helper.remove_comments(input) + assert result == input + class TestIsInQuote: def test_returns_whether_index_in_quote(self): input = '"A"' diff --git a/zonefile_parser/main_test.py b/zonefile_parser/main_test.py index ccdc3a2..ba3386c 100644 --- a/zonefile_parser/main_test.py +++ b/zonefile_parser/main_test.py @@ -328,6 +328,37 @@ def test_origin_not_appended_to_txt_rdata_when_name_matches(self): assert record.rtype == "TXT" assert record.rdata == {"value": "v=spf1 include:mail.otherdomain.com ~all"} + def test_issue_49_escaped_semicolon_in_txt(self): + # TXT record with a trailing escaped semicolon (\;) should not raise ValueError + text = """ +$TTL 3600 +$ORIGIN example.com. +_dmarc 3600 IN TXT v=DMARC1\\; +""" + result = zonefile_parser.main.parse(text) + + record = result[0] + + assert record.name == "_dmarc.example.com." + assert record.rtype == "TXT" + assert record.rdata == {"value": "v=DMARC1;"} + + def test_issue_49_dmarc_record_with_comment(self): + # DMARC TXT record with an escaped semicolon followed by a real comment + # the real comment (unescaped ;) should be stripped, \; should survive as ; + text = """ +$TTL 3600 +$ORIGIN example.com. +_dmarc 3600 IN TXT v=DMARC1\\; ; this is a comment +""" + result = zonefile_parser.main.parse(text) + + record = result[0] + + assert record.name == "_dmarc.example.com." + assert record.rtype == "TXT" + assert record.rdata == {"value": "v=DMARC1;"} + def test_multiple_cnames_with_name_in_target(self): # multiple records in the same zone, each with name appearing in their CNAME target text = """