diff --git a/.vscode/settings.json b/.vscode/settings.json index 131045dc..1aef52e0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -160,7 +160,6 @@ "Hostinger", "hostnames", "htpasswd", - "httpasswd", "httplib", "hugedomains", "idens", @@ -265,7 +264,6 @@ "oxfordnetworks", "Paltalk", "parsedmarc", - "passsword", "pawyo", "pbar", "penyedia", @@ -373,7 +371,6 @@ "Uncategorized", "unfindable", "Uninett", - "unparasable", "unparseable", "unwritable", "uper", diff --git a/parsedmarc/__init__.py b/parsedmarc/__init__.py index ac1c8e04..b4af9f85 100644 --- a/parsedmarc/__init__.py +++ b/parsedmarc/__init__.py @@ -1303,6 +1303,9 @@ def extract_report(content: bytes | str | BinaryIO) -> str: try: file_object.close() except Exception: + # Best-effort close; a failure here shouldn't shadow + # whatever exception (or successful result) is already + # propagating out of this function. pass return report @@ -2171,6 +2174,10 @@ def parse_report_email( return result except (TypeError, ValueError, binascii.Error): + # b64decode() rejected this MIME part's payload as not + # base64-decodable, so it isn't a report attachment; fall + # through and keep walking the message for a part (or the + # feedback-report/sample pair below) that is. pass except InvalidDMARCReport as e: diff --git a/parsedmarc/cli.py b/parsedmarc/cli.py index 8a19f028..b40972b0 100644 --- a/parsedmarc/cli.py +++ b/parsedmarc/cli.py @@ -1524,10 +1524,18 @@ class _ElasticsearchHandle: if not isinstance(conn, str): conn.close() except Exception: + # Best-effort, and deliberately silent: this is the first of + # two independent teardown steps, and swallowing here is what + # lets the second one still run. Nothing reports this error -- + # _close_output_clients logs a warning only if close() itself + # raises, which it cannot while this handler swallows. pass try: elastic.connections.remove_connection("default") except Exception: + # Best-effort and silent for the same reason as above: a + # failure to give up the alias is not actionable during + # teardown, and it is not reported anywhere either. pass @@ -1540,10 +1548,18 @@ class _OpenSearchHandle: if not isinstance(conn, str): conn.close() except Exception: + # Best-effort, and deliberately silent: this is the first of + # two independent teardown steps, and swallowing here is what + # lets the second one still run. Nothing reports this error -- + # _close_output_clients logs a warning only if close() itself + # raises, which it cannot while this handler swallows. pass try: opensearch.connections.remove_connection("default") except Exception: + # Best-effort and silent for the same reason as above: a + # failure to give up the alias is not actionable during + # teardown, and it is not reported anywhere either. pass diff --git a/parsedmarc/resources/maps/collect_domain_info.py b/parsedmarc/resources/maps/collect_domain_info.py index c156ffa5..16f52e6e 100644 --- a/parsedmarc/resources/maps/collect_domain_info.py +++ b/parsedmarc/resources/maps/collect_domain_info.py @@ -611,6 +611,9 @@ def _extract_metadata(domain: str, body: bytes, encoding: str) -> dict: try: parser.feed(text) except Exception: + # Defensive guard against malformed markup partway through a page; + # keep whatever title/description/body text it already extracted + # before the failure rather than losing the whole page's data. pass out["title"] = parser.title out["description"] = parser.description diff --git a/parsedmarc/resources/maps/find_bad_utf8.py b/parsedmarc/resources/maps/find_bad_utf8.py index a9347e95..33de0005 100755 --- a/parsedmarc/resources/maps/find_bad_utf8.py +++ b/parsedmarc/resources/maps/find_bad_utf8.py @@ -271,6 +271,8 @@ def targeted_fix_to_utf8( repaired_lines.append(line_bytes.decode("utf-8", errors="strict")) continue except UnicodeDecodeError: + # Not valid UTF-8 as-is; fall through to the slower + # repair_mixed_utf8_line() path below instead of the fast path. pass fixed_text, fixes = repair_mixed_utf8_line( diff --git a/parsedmarc/s3.py b/parsedmarc/s3.py index 7715e741..f1cedf43 100644 --- a/parsedmarc/s3.py +++ b/parsedmarc/s3.py @@ -111,6 +111,9 @@ class S3Client(object): if self.s3.meta is not None: self.s3.meta.client.close() except Exception: + # Best-effort: this runs during shutdown/cleanup, and a + # failure to close the underlying boto3 client isn't + # actionable here. pass # Backward-compatible alias diff --git a/parsedmarc/utils.py b/parsedmarc/utils.py index 40e494d8..6303045a 100644 --- a/parsedmarc/utils.py +++ b/parsedmarc/utils.py @@ -1407,35 +1407,29 @@ def parse_email(data: bytes | str, *, strip_attachment_payloads: bool = False) - # single representation, matching how "to"/"cc"/"bcc" are handled. if "reply-to" in parsed_email: parsed_email["reply_to"] = list( - map(lambda x: parse_email_address(x), parsed_email.pop("reply-to")) + map(parse_email_address, parsed_email.pop("reply-to")) ) else: parsed_email["reply_to"] = [] if "to" in parsed_email: - parsed_email["to"] = list( - map(lambda x: parse_email_address(x), parsed_email["to"]) - ) + parsed_email["to"] = list(map(parse_email_address, parsed_email["to"])) else: parsed_email["to"] = [] if "cc" in parsed_email: - parsed_email["cc"] = list( - map(lambda x: parse_email_address(x), parsed_email["cc"]) - ) + parsed_email["cc"] = list(map(parse_email_address, parsed_email["cc"])) else: parsed_email["cc"] = [] if "bcc" in parsed_email: - parsed_email["bcc"] = list( - map(lambda x: parse_email_address(x), parsed_email["bcc"]) - ) + parsed_email["bcc"] = list(map(parse_email_address, parsed_email["bcc"])) else: parsed_email["bcc"] = [] if "delivered-to" in parsed_email: parsed_email["delivered_to"] = list( - map(lambda x: parse_email_address(x), parsed_email.pop("delivered-to")) + map(parse_email_address, parsed_email.pop("delivered-to")) ) if "attachments" not in parsed_email: diff --git a/tests/test_cli.py b/tests/test_cli.py index 787aaebd..80bb8ef6 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -4167,10 +4167,7 @@ watch = true # No watch, no mailbox, no files → _main runs through with # empty parsing_results and returns normally. with patch.object(sys, "argv", ["parsedmarc", "nothing-here.xml"]): - try: - parsedmarc.cli._main() - except SystemExit: - pass + parsedmarc.cli._main() kafka_client.close.assert_called_once() es_client.close.assert_called_once() diff --git a/tests/test_init.py b/tests/test_init.py index 233e7789..7a9077ed 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1791,7 +1791,7 @@ class Test(unittest.TestCase): ) report = cast(AggregateReport, result["report"]) rows = parsedmarc.parsed_aggregate_reports_to_csv_rows(report) - self.assertTrue(len(rows) > 0) + self.assertGreater(len(rows), 0) row = rows[0] self.assertIn("np", row) self.assertIn("testing", row) @@ -1986,7 +1986,7 @@ class Test(unittest.TestCase): ) parsed = parsedmarc.parse_smtp_tls_report_json(report_json) rows = parsedmarc.parsed_smtp_tls_reports_to_csv_rows(parsed) - self.assertTrue(len(rows) >= 2) + self.assertGreaterEqual(len(rows), 2) self.assertEqual(rows[0]["organization_name"], "Org") self.assertEqual(rows[0]["policy_domain"], "example.com") @@ -2000,7 +2000,7 @@ class Test(unittest.TestCase): report = cast(AggregateReport, result["report"]) # Pass as a list rows = parsedmarc.parsed_aggregate_reports_to_csv_rows([report]) - self.assertTrue(len(rows) > 0) + self.assertGreater(len(rows), 0) # Verify non-str/int/bool values are cleaned for row in rows: for v in row.values(): @@ -2051,7 +2051,7 @@ class Test(unittest.TestCase): report = parsedmarc.parse_aggregate_report_xml(xml, offline=True) self.assertTrue(report["report_metadata"]["timespan_requires_normalization"]) # Records should be split across days - self.assertTrue(len(report["records"]) > 1) + self.assertGreater(len(report["records"]), 1) total = sum(r["count"] for r in report["records"]) self.assertEqual(total, 90) for r in report["records"]: @@ -2127,7 +2127,7 @@ class Test(unittest.TestCase): self.assertIsNotNone(csv_output) self.assertIn(",", csv_output) rows = parsedmarc.parsed_failure_reports_to_csv_rows(parsed_report) - self.assertTrue(len(rows) > 0) + self.assertGreater(len(rows), 0) print("Passed!") def testFailureReportCsvStripsNulFromFields(self): @@ -2581,7 +2581,7 @@ class TestPolicyPublishedEdgeCases(unittest.TestCase): """ report = parsedmarc.parse_aggregate_report_xml(xml, offline=True) # At least the valid record should be parsed - self.assertTrue(len(report["records"]) >= 1) + self.assertGreaterEqual(len(report["records"]), 1) class TestParseReportFile(unittest.TestCase): @@ -3240,7 +3240,7 @@ class TestGetDmarcReportsFromMbox(unittest.TestCase): path = f.name try: results = parsedmarc.get_dmarc_reports_from_mbox(path, offline=True) - self.assertTrue(len(results["aggregate_reports"]) >= 1) + self.assertGreaterEqual(len(results["aggregate_reports"]), 1) finally: os.remove(path) diff --git a/tests/test_utils.py b/tests/test_utils.py index 13be833e..b8377818 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1143,7 +1143,7 @@ class TestUtilsReverseDnsMap(unittest.TestCase): """load_reverse_dns_map in offline mode loads bundled map""" rdns_map = {} parsedmarc.utils.load_reverse_dns_map(rdns_map, offline=True) - self.assertTrue(len(rdns_map) > 0) + self.assertGreater(len(rdns_map), 0) def testLoadReverseDnsMapLocalOverride(self): """load_reverse_dns_map uses local_file_path when provided""" @@ -1169,7 +1169,7 @@ class TestUtilsReverseDnsMap(unittest.TestCase): side_effect=httpx.ConnectError("no network"), ): parsedmarc.utils.load_reverse_dns_map(rdns_map) - self.assertTrue(len(rdns_map) > 0) + self.assertGreater(len(rdns_map), 0) def testLoadReverseDnsMapInvalidCsvFallback(self): """A fetch that returns a non-map CSV body logs a warning and