diff --git a/backend/app/services/dmarc_parser.py b/backend/app/services/dmarc_parser.py index d892582..df8327a 100644 --- a/backend/app/services/dmarc_parser.py +++ b/backend/app/services/dmarc_parser.py @@ -128,6 +128,14 @@ class DMARCParser: return None + @staticmethod + def _strip_namespace(el) -> None: + """Recursively remove XML namespace prefixes from element tags in-place.""" + if "}" in el.tag: + el.tag = el.tag.split("}", 1)[1] + for child in el: + DMARCParser._strip_namespace(child) + @staticmethod def _parse_metadata(root) -> dict: """Parse the report_metadata section of a DMARC XML report.""" @@ -216,6 +224,7 @@ class DMARCParser: """ try: root = ET.fromstring(xml_content) + DMARCParser._strip_namespace(root) report = DMARCParser._parse_metadata(root) diff --git a/backend/app/tests/test_data.py b/backend/app/tests/test_data.py index 24642ec..b418553 100644 --- a/backend/app/tests/test_data.py +++ b/backend/app/tests/test_data.py @@ -47,3 +47,51 @@ SAMPLE_XML = """\ """ + +SAMPLE_XML_WITH_NAMESPACE = """\ + + + + web.de + dmarc@web.de + 987654321 + + 1597449600 + 1597535999 + + + + example.com + r + r +

reject

+ reject + 100 +
+ + + 198.51.100.5 + 3 + + reject + pass + pass + + + + example.com + + + + example.com + pass + s1 + + + example.com + pass + + + +
+""" diff --git a/backend/app/tests/test_dmarc_parser.py b/backend/app/tests/test_dmarc_parser.py index 8361d52..16c3b10 100644 --- a/backend/app/tests/test_dmarc_parser.py +++ b/backend/app/tests/test_dmarc_parser.py @@ -4,7 +4,7 @@ import zipfile import pytest from app.services.dmarc_parser import DMARCParser -from app.tests.test_data import SAMPLE_XML +from app.tests.test_data import SAMPLE_XML, SAMPLE_XML_WITH_NAMESPACE class TestDMARCParser: @@ -67,6 +67,37 @@ class TestDMARCParser: with pytest.raises(ValueError): DMARCParser.parse_file(b"not xml at all", "report.xml") + def test_parse_xml_report_with_namespace(self): + """Test parsing a DMARC XML report that uses an XML namespace (e.g. web.de/gmx.net).""" + xml_bytes = SAMPLE_XML_WITH_NAMESPACE.encode("utf-8") + result = DMARCParser.parse_file(xml_bytes, "report.xml") + + # Metadata + assert result["report_id"] == "987654321" + assert result["org_name"] == "web.de" + assert result["email"] == "dmarc@web.de" + assert result["begin_timestamp"] == 1597449600 + assert result["end_timestamp"] == 1597535999 + + # Policy published + assert result["domain"] == "example.com" + assert result["policy"]["p"] == "reject" + + # Records + assert len(result["records"]) == 1 + record = result["records"][0] + assert record["source_ip"] == "198.51.100.5" + assert record["count"] == 3 + assert record["disposition"] == "reject" + assert record["dkim_result"] == "pass" + assert record["spf_result"] == "pass" + assert record["header_from"] == "example.com" + + # Summary + assert result["summary"]["total_count"] == 3 + assert result["summary"]["passed_count"] == 3 + assert result["summary"]["failed_count"] == 0 + def test_unsupported_extension_returns_none(self): """Test that an unsupported file extension raises ValueError.""" with pytest.raises(ValueError, match="Could not extract XML"):