From c0276a208e7081d673394a2aab6530307f0a6ee5 Mon Sep 17 00:00:00 2001 From: Tino Hager Date: Wed, 22 Jan 2025 11:22:34 +0100 Subject: [PATCH] optimize dkim validator, optimize object naming --- .../DkimHeaderParserTests/BasicTest.cs | 4 +- .../DkimHeaderParserTests/SelectorTest.cs | 8 +- .../DkimHeaderParserTests/VersionTest.cs | 8 +- .../DmarcRecordParserTests/BasicTest.cs | 18 +- .../DmarcRecordParserTests/ComplexTest.cs | 4 +- .../FailureReportingOptionsTest.cs | 14 +- .../PolicyPercentageTest.cs | 16 +- .../DmarcRecordParserTests/PolicyTest.cs | 20 +- .../ReportFormatTest.cs | 18 +- .../ReportingIntervalTest.cs | 22 +- .../DkimHeaderParser.cs | 109 +++++++--- .../DmarcRecordParser.cs | 190 +++++++++--------- .../KeyValueParserBase.cs | 22 +- .../MappingHandler.cs | 4 +- .../Models/ParseError.cs | 18 -- .../Models/ParsingResult.cs | 18 ++ .../{ErrorSeverity.cs => ParsingStatus.cs} | 4 +- 17 files changed, 272 insertions(+), 225 deletions(-) delete mode 100644 src/Nager.EmailAuthentication/Models/ParseError.cs create mode 100644 src/Nager.EmailAuthentication/Models/ParsingResult.cs rename src/Nager.EmailAuthentication/Models/{ErrorSeverity.cs => ParsingStatus.cs} (88%) diff --git a/src/Nager.EmailAuthentication.UnitTest/DkimHeaderParserTests/BasicTest.cs b/src/Nager.EmailAuthentication.UnitTest/DkimHeaderParserTests/BasicTest.cs index c41dde4..83cab2a 100644 --- a/src/Nager.EmailAuthentication.UnitTest/DkimHeaderParserTests/BasicTest.cs +++ b/src/Nager.EmailAuthentication.UnitTest/DkimHeaderParserTests/BasicTest.cs @@ -8,11 +8,11 @@ public void TryParse_ValidDkimHeaderString1_ReturnsTrueAndPopulatesDataFragment( { var dkimHeader = "v=1; a=rsa-sha256; c=relaxed/simple; q=dns/txt; d=domain.com; i=noreply@domain.com; s=mailjet; x=1737017824; h=message-id:from:from:reply-to:to:to:subject:subject:date:date:list-unsubscribe-post:list-unsubscribe:feedback-id:x-csa-complaints:x-mj-mid:x-report-abuse-to:mime-version:content-type; bh=TyN/x6t3AOfI298rgJAgZHgdWcq/XLISGen5nN3NLAc=; b=HLCLiikV92Ku/k9mGlZM0bmqPjKggGnMI0igqhXmPRzPJUC+5SUWRS6/FLUpxbX6AUGJRDYQnKKMtp6uZkYVuKG8SPZ01cUkvIiiAkczb4bK6IVvPbZOnsWqHkD6EvK3TrpIhgFfGLlcG+zIwgdDZ3O++uhpJkIX1WJlkXZYqxQ="; - var isSuccessful = DkimHeaderParser.TryParse(dkimHeader, out var dkimHeaderDataFragment, out var parseErrors); + var isSuccessful = DkimHeaderParser.TryParse(dkimHeader, out var dkimHeaderDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dkimHeaderDataFragment); - Assert.IsNull(parseErrors, "ParseErrors is not null"); + //Assert.IsNull(parsingResults, "ParsingResults is not null"); } } } diff --git a/src/Nager.EmailAuthentication.UnitTest/DkimHeaderParserTests/SelectorTest.cs b/src/Nager.EmailAuthentication.UnitTest/DkimHeaderParserTests/SelectorTest.cs index 20a1de7..3bad05f 100644 --- a/src/Nager.EmailAuthentication.UnitTest/DkimHeaderParserTests/SelectorTest.cs +++ b/src/Nager.EmailAuthentication.UnitTest/DkimHeaderParserTests/SelectorTest.cs @@ -11,11 +11,11 @@ public void TryParse_ValidSelector_ReturnsTrueAndPopulatesDataFragment(string se { var dkimHeader = $"v=1; a=rsa-sha256; d=domain.com; s={selector}; h=message-id:from; bh=testbodyhash=; b=signaturedata"; - var isSuccessful = DkimHeaderParser.TryParse(dkimHeader, out var dkimHeaderDataFragment, out var parseErrors); + var isSuccessful = DkimHeaderParser.TryParse(dkimHeader, out var dkimHeaderDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dkimHeaderDataFragment); - Assert.IsNull(parseErrors, "ParseErrors is not null"); + Assert.IsNull(parsingResults, "ParsingResults is not null"); } [DataRow("verylongandinvalidselectorverylongandinvalidselectorverylongandinvalidselector")] @@ -24,11 +24,11 @@ public void TryParse_InvalidSelector_ReturnsTrueAndPopulatesDataFragment(string { var dkimHeader = $"v=1; a=rsa-sha256; d=domain.com; s={selector}; h=message-id:from; bh=testbodyhash=; b=signaturedata"; - var isSuccessful = DkimHeaderParser.TryParse(dkimHeader, out var dkimHeaderDataFragment, out var parseErrors); + var isSuccessful = DkimHeaderParser.TryParse(dkimHeader, out var dkimHeaderDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dkimHeaderDataFragment); - Assert.IsNotNull(parseErrors, "ParseErrors is null"); + Assert.IsNotNull(parsingResults, "ParsingResults is null"); } } } diff --git a/src/Nager.EmailAuthentication.UnitTest/DkimHeaderParserTests/VersionTest.cs b/src/Nager.EmailAuthentication.UnitTest/DkimHeaderParserTests/VersionTest.cs index 43a2f3b..30bd176 100644 --- a/src/Nager.EmailAuthentication.UnitTest/DkimHeaderParserTests/VersionTest.cs +++ b/src/Nager.EmailAuthentication.UnitTest/DkimHeaderParserTests/VersionTest.cs @@ -11,11 +11,11 @@ public void TryParse_ValidVersion_ReturnsTrueAndPopulatesDataFragment(string ver { var dkimHeader = $"v={version}; a=rsa-sha256; d=domain.com; s=myselector; h=message-id:from; bh=testbodyhash=; b=signaturedata"; - var isSuccessful = DkimHeaderParser.TryParse(dkimHeader, out var dkimHeaderDataFragment, out var parseErrors); + var isSuccessful = DkimHeaderParser.TryParse(dkimHeader, out var dkimHeaderDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dkimHeaderDataFragment); - Assert.IsNull(parseErrors, "ParseErrors is not null"); + Assert.IsNull(parsingResults, "ParsingResults is not null"); } [DataRow("a")] @@ -25,11 +25,11 @@ public void TryParse_InvalidVersion_ReturnsTrueAndPopulatesDataFragment(string v { var dkimHeader = $"v={version}; a=rsa-sha256; d=domain.com; s=myselector; h=message-id:from; bh=testbodyhash=; b=signaturedata"; - var isSuccessful = DkimHeaderParser.TryParse(dkimHeader, out var dkimHeaderDataFragment, out var parseErrors); + var isSuccessful = DkimHeaderParser.TryParse(dkimHeader, out var dkimHeaderDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dkimHeaderDataFragment); - Assert.IsNotNull(parseErrors, "ParseErrors is null"); + Assert.IsNotNull(parsingResults, "ParsingResults is null"); } } } diff --git a/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/BasicTest.cs b/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/BasicTest.cs index 35ffd2d..2c2d4b1 100644 --- a/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/BasicTest.cs +++ b/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/BasicTest.cs @@ -6,42 +6,42 @@ public sealed class BasicTest [TestMethod] public void TryParse_InvalidDmarcString1_ReturnsTrueAndPopulatesDmarcRecord() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); - Assert.IsNotNull(parseErrors, "ParseErrors is null"); + Assert.IsNotNull(parsingResults, "ParsingResults is null"); } [TestMethod] public void TryParse_InvalidDmarcString2_ReturnsTrueAndPopulatesDmarcRecord() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); - Assert.IsNull(parseErrors, "ParseErrors is not null"); + Assert.IsNull(parsingResults, "ParsingResults is not null"); } [TestMethod] public void TryParse_CorruptDmarcString1_ReturnsFalseAndParseErrors() { - var isSuccessful = DmarcRecordParser.TryParse("verification=123456789", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("verification=123456789", out var dmarcDataFragment, out var parsingResults); Assert.IsFalse(isSuccessful); Assert.IsNotNull(dmarcDataFragment); - Assert.IsNotNull(parseErrors, "ParseErrors is null"); - Assert.IsTrue(parseErrors.Length == 1); + Assert.IsNotNull(parsingResults, "ParsingResults is null"); + Assert.IsTrue(parsingResults.Length == 1); } [TestMethod] public void TryParse_CorruptDmarcString2_ReturnsFalse() { - var isSuccessful = DmarcRecordParser.TryParse(" ", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse(" ", out var dmarcDataFragment, out var parsingResults); Assert.IsFalse(isSuccessful); Assert.IsNull(dmarcDataFragment); - Assert.IsNull(parseErrors, "ParseErrors is not null"); + Assert.IsNull(parsingResults, "ParsingResults is not null"); } } } diff --git a/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/ComplexTest.cs b/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/ComplexTest.cs index 7cb404f..47b127f 100644 --- a/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/ComplexTest.cs +++ b/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/ComplexTest.cs @@ -6,7 +6,7 @@ public sealed class ComplexTest [TestMethod] public void TryParse_ValidDmarcString1_ReturnsTrueAndPopulatesDmarcRecord() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; rua=mailto:postmaster@example.com, mailto:dmarc@example.com; pct=100; adkim=s; aspf=s", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; rua=mailto:postmaster@example.com, mailto:dmarc@example.com; pct=100; adkim=s; aspf=s", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); @@ -15,7 +15,7 @@ public void TryParse_ValidDmarcString1_ReturnsTrueAndPopulatesDmarcRecord() Assert.AreEqual("100", dmarcDataFragment.PolicyPercentage); Assert.AreEqual("s", dmarcDataFragment.DkimAlignmentMode); Assert.AreEqual("s", dmarcDataFragment.SpfAlignmentMode); - Assert.IsNull(parseErrors, "ParseErrors is not null"); + Assert.IsNull(parsingResults, "ParsingResults is not null"); } } } diff --git a/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/FailureReportingOptionsTest.cs b/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/FailureReportingOptionsTest.cs index 1555e56..6c77ec6 100644 --- a/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/FailureReportingOptionsTest.cs +++ b/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/FailureReportingOptionsTest.cs @@ -12,14 +12,14 @@ public sealed class FailureReportingOptionsTest [DataTestMethod] public void TryParse_ValidDmarcString1_ReturnsTrueAndPopulatesDmarcRecord(string failureReportingOptions) { - var isSuccessful = DmarcRecordParser.TryParse($"v=DMARC1; p=reject; fo={failureReportingOptions}", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse($"v=DMARC1; p=reject; fo={failureReportingOptions}", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy); Assert.AreEqual(failureReportingOptions, dmarcDataFragment.FailureReportingOptions); - Assert.IsNotNull(parseErrors, "ParseErrors is null"); - Assert.IsTrue(parseErrors.Length == 1); + Assert.IsNotNull(parsingResults, "ParsingResults is null"); + Assert.IsTrue(parsingResults.Length == 1); } [DataRow("a", 2)] @@ -29,16 +29,16 @@ public void TryParse_ValidDmarcString1_ReturnsTrueAndPopulatesDmarcRecord(string [DataRow("wrong", 2)] [DataRow("s:x:dd", 3)] [DataTestMethod] - public void TryParse_InvalidDmarcString1_ReturnsTrueAndPopulatesDmarcRecordWithParseErrors(string failureReportingOptions, int parseErrorCount) + public void TryParse_InvalidDmarcString1_ReturnsTrueAndPopulatesDmarcRecordWithParseErrors(string failureReportingOptions, int parsingResultsCount) { - var isSuccessful = DmarcRecordParser.TryParse($"v=DMARC1; p=reject; fo={failureReportingOptions}", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse($"v=DMARC1; p=reject; fo={failureReportingOptions}", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy); Assert.AreEqual(failureReportingOptions, dmarcDataFragment.FailureReportingOptions); - Assert.IsNotNull(parseErrors, "ParseErrors is null"); - Assert.IsTrue(parseErrors.Length == parseErrorCount); + Assert.IsNotNull(parsingResults, "ParsingResults is null"); + Assert.IsTrue(parsingResults.Length == parsingResultsCount); } } } diff --git a/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/PolicyPercentageTest.cs b/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/PolicyPercentageTest.cs index 070dbfd..1c79c40 100644 --- a/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/PolicyPercentageTest.cs +++ b/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/PolicyPercentageTest.cs @@ -6,39 +6,39 @@ public sealed class PolicyPercentageTest [TestMethod] public void TryParse_ValidDmarcString1_ReturnsTrueAndPopulatesDmarcRecord() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; pct=60;", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; pct=60;", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy); Assert.AreEqual("60", dmarcDataFragment.PolicyPercentage); - Assert.IsNull(parseErrors, "ParseErrors is not null"); + Assert.IsNull(parsingResults, "ParsingResults is not null"); } [TestMethod] public void TryParse_InvalidDmarcString1_ReturnsTrueWithErrors() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; pct=;", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; pct=;", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy); Assert.AreEqual("", dmarcDataFragment.PolicyPercentage); - Assert.IsNotNull(parseErrors, "ParseErrors is null"); - Assert.IsTrue(parseErrors.Length == 1); + Assert.IsNotNull(parsingResults, "ParsingResults is null"); + Assert.IsTrue(parsingResults.Length == 1); } [TestMethod] public void TryParse_InvalidDmarcString2_ReturnsTrueWithErrors() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; pct=200;", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; pct=200;", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy); Assert.AreEqual("200", dmarcDataFragment.PolicyPercentage); - Assert.IsNotNull(parseErrors, "ParseErrors is null"); - Assert.IsTrue(parseErrors.Length == 1); + Assert.IsNotNull(parsingResults, "ParsingResults is null"); + Assert.IsTrue(parsingResults.Length == 1); } } } diff --git a/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/PolicyTest.cs b/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/PolicyTest.cs index 85b896e..887a2ba 100644 --- a/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/PolicyTest.cs +++ b/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/PolicyTest.cs @@ -6,47 +6,47 @@ public sealed class PolicyTest [TestMethod] public void TryParse_InvalidDmarcString1_ReturnsTrueAndPopulatesDmarcRecord() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=Test", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=Test", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("Test", dmarcDataFragment.DomainPolicy); - Assert.IsNotNull(parseErrors); - Assert.IsTrue(parseErrors.Length == 1); + Assert.IsNotNull(parsingResults); + Assert.IsTrue(parsingResults.Length == 1); } [TestMethod] public void TryParse_InvalidDmarcString2_ReturnsTrueAndPopulatesDmarcRecord() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=Test;", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=Test;", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("Test", dmarcDataFragment.DomainPolicy); - Assert.IsNotNull(parseErrors); - Assert.IsTrue(parseErrors.Length == 1); + Assert.IsNotNull(parsingResults); + Assert.IsTrue(parsingResults.Length == 1); } [TestMethod] public void TryParse_ValidDmarcString1_ReturnsTrueAndPopulatesDmarcRecord() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject;", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject;", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy); - Assert.IsNull(parseErrors, "ParseErrors is not null"); + Assert.IsNull(parsingResults, "ParsingResults is not null"); } [TestMethod] public void TryParse_ValidDmarcString2_ReturnsTrueAndPopulatesDmarcRecord() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; sp=none;", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; sp=none;", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy); Assert.AreEqual("none", dmarcDataFragment.SubdomainPolicy); - Assert.IsNull(parseErrors, "ParseErrors is not null"); + Assert.IsNull(parsingResults, "ParsingResults is not null"); } } } diff --git a/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/ReportFormatTest.cs b/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/ReportFormatTest.cs index e8a1c45..bbb4c81 100644 --- a/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/ReportFormatTest.cs +++ b/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/ReportFormatTest.cs @@ -6,40 +6,40 @@ public sealed class ReportFormatTest [TestMethod] public void TryParse_ValidDmarcString1_ReturnsTrueAndPopulatesDmarcRecord() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; rf=afrf", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; rf=afrf", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy); Assert.AreEqual("afrf", dmarcDataFragment.ReportFormat); - Assert.IsNotNull(parseErrors, "ParseErrors is null"); - Assert.IsTrue(parseErrors.Length == 1); + Assert.IsNotNull(parsingResults, "ParsingResults is null"); + Assert.IsTrue(parsingResults.Length == 1); } [TestMethod] public void TryParse_InvalidDmarcString1_ReturnsTrueAndPopulatesDmarcRecordWithParseErrors() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; rf=afrf1", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; rf=afrf1", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy); Assert.AreEqual("afrf1", dmarcDataFragment.ReportFormat); - Assert.IsNotNull(parseErrors, "ParseErrors is null"); - Assert.IsTrue(parseErrors.Length == 1); + Assert.IsNotNull(parsingResults, "ParsingResults is null"); + Assert.IsTrue(parsingResults.Length == 1); } [TestMethod] public void TryParse_InvalidDmarcString2_ReturnsTrueAndPopulatesDmarcRecordWithParseErrors() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; rf=", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; rf=", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy); Assert.AreEqual("", dmarcDataFragment.ReportFormat); - Assert.IsNotNull(parseErrors, "ParseErrors is null"); - Assert.IsTrue(parseErrors.Length == 1); + Assert.IsNotNull(parsingResults, "ParsingResults is null"); + Assert.IsTrue(parsingResults.Length == 1); } } } diff --git a/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/ReportingIntervalTest.cs b/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/ReportingIntervalTest.cs index e258e31..5799d42 100644 --- a/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/ReportingIntervalTest.cs +++ b/src/Nager.EmailAuthentication.UnitTest/DmarcRecordParserTests/ReportingIntervalTest.cs @@ -6,52 +6,52 @@ public sealed class ReportingIntervalTest [TestMethod] public void TryParse_ValidDmarcString1_ReturnsTrueAndPopulatesDmarcRecord() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; ri=86400;", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; ri=86400;", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy); Assert.AreEqual("86400", dmarcDataFragment.ReportingInterval); - Assert.IsNull(parseErrors, "ParseErrors is not null"); + Assert.IsNull(parsingResults, "ParsingResults is not null"); } [TestMethod] public void TryParse_InvalidDmarcString1_ReturnsTrueWithErrors() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; ri=1000;", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; ri=1000;", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy); Assert.AreEqual("1000", dmarcDataFragment.ReportingInterval); - Assert.IsNotNull(parseErrors, "ParseErrors is null"); - Assert.IsTrue(parseErrors.Length == 1); + Assert.IsNotNull(parsingResults, "ParsingResults is null"); + Assert.IsTrue(parsingResults.Length == 1); } [TestMethod] public void TryParse_InvalidDmarcString2_ReturnsTrueWithErrors() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; ri=1000000;", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; ri=1000000;", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy); Assert.AreEqual("1000000", dmarcDataFragment.ReportingInterval); - Assert.IsNotNull(parseErrors, "ParseErrors is null"); - Assert.IsTrue(parseErrors.Length == 1); + Assert.IsNotNull(parsingResults, "ParsingResults is null"); + Assert.IsTrue(parsingResults.Length == 1); } [TestMethod] public void TryParse_InvalidDmarcString3_ReturnsTrueWithErrors() { - var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; ri=-1000000;", out var dmarcDataFragment, out var parseErrors); + var isSuccessful = DmarcRecordParser.TryParse("v=DMARC1; p=reject; ri=-1000000;", out var dmarcDataFragment, out var parsingResults); Assert.IsTrue(isSuccessful); Assert.IsNotNull(dmarcDataFragment); Assert.AreEqual("reject", dmarcDataFragment.DomainPolicy); Assert.AreEqual("-1000000", dmarcDataFragment.ReportingInterval); - Assert.IsNotNull(parseErrors, "ParseErrors is null"); - Assert.IsTrue(parseErrors.Length == 1); + Assert.IsNotNull(parsingResults, "ParsingResults is null"); + Assert.IsTrue(parsingResults.Length == 1); } } } diff --git a/src/Nager.EmailAuthentication/DkimHeaderParser.cs b/src/Nager.EmailAuthentication/DkimHeaderParser.cs index 0d4eae1..fe4821c 100644 --- a/src/Nager.EmailAuthentication/DkimHeaderParser.cs +++ b/src/Nager.EmailAuthentication/DkimHeaderParser.cs @@ -1,5 +1,4 @@ using Nager.EmailAuthentication.Models; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace Nager.EmailAuthentication { @@ -15,7 +14,7 @@ public static bool TryParse( public static bool TryParse( string dkimHeader, out DkimHeaderDataFragment? dkimHeaderDataFragment, - out ParseError[]? parseErrors) + out ParsingResult[]? parsingResults) { var handlers = new Dictionary> { @@ -80,7 +79,8 @@ public static bool TryParse( { "h", new MappingHandler { - Map = (dataFragment, value) => dataFragment.SignedHeaderFields = value + Map = (dataFragment, value) => dataFragment.SignedHeaderFields = value, + Validate = ValidateSignedHeaderFields } }, { @@ -98,19 +98,19 @@ public static bool TryParse( }; var parserBase = new KeyValueParserBase(handlers); - return parserBase.TryParse(dkimHeader, out dkimHeaderDataFragment, out parseErrors); + return parserBase.TryParse(dkimHeader, out dkimHeaderDataFragment, out parsingResults); } - private static ParseError[] ValidatePositiveNumber(ValidateRequest validateRequest) + private static ParsingResult[] ValidatePositiveNumber(ValidateRequest validateRequest) { - var errors = new List(); + var errors = new List(); if (string.IsNullOrEmpty(validateRequest.Value)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} is empty" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} is empty" }); return [.. errors]; @@ -118,10 +118,10 @@ private static ParseError[] ValidatePositiveNumber(ValidateRequest validateReque if (!int.TryParse(validateRequest.Value, out var reportInterval)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} value is not a number" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} value is not a number" }); return [.. errors]; @@ -129,10 +129,10 @@ private static ParseError[] ValidatePositiveNumber(ValidateRequest validateReque if (int.IsNegative(reportInterval)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} number is negative" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} number is negative" }); return [.. errors]; @@ -141,16 +141,16 @@ private static ParseError[] ValidatePositiveNumber(ValidateRequest validateReque return []; } - private static ParseError[] ValidateSignatureAlgorithm(ValidateRequest validateRequest) + private static ParsingResult[] ValidateSignatureAlgorithm(ValidateRequest validateRequest) { - var errors = new List(); + var errors = new List(); if (string.IsNullOrEmpty(validateRequest.Value)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} is empty" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} is empty" }); return [.. errors]; @@ -158,10 +158,10 @@ private static ParseError[] ValidateSignatureAlgorithm(ValidateRequest validateR if (!validateRequest.Value.StartsWith("rsa-", StringComparison.OrdinalIgnoreCase)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} starts not with rsa-" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} starts not with rsa-" }); return [.. errors]; @@ -170,16 +170,16 @@ private static ParseError[] ValidateSignatureAlgorithm(ValidateRequest validateR return []; } - private static ParseError[] ValidateSelector(ValidateRequest validateRequest) + private static ParsingResult[] ValidateSelector(ValidateRequest validateRequest) { - var errors = new List(); + var errors = new List(); if (string.IsNullOrEmpty(validateRequest.Value)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} is empty" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} is empty" }); return [.. errors]; @@ -188,14 +188,61 @@ private static ParseError[] ValidateSelector(ValidateRequest validateRequest) var maxDnsLabelLength = 63; if (validateRequest.Value.Length > maxDnsLabelLength) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"selector name length limit reached" + Status = ParsingStatus.Error, + Message = $"selector name length limit reached" }); } return [.. errors]; } + + private static ParsingResult[] ValidateSignedHeaderFields(ValidateRequest validateRequest) + { + var errors = new List(); + + if (string.IsNullOrEmpty(validateRequest.Value)) + { + errors.Add(new ParsingResult + { + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} is empty" + }); + + return [.. errors]; + } + + var importantHeaders = new string[] { "from", "to", "subject" }; + + var colonIndex = validateRequest.Value.IndexOf(':'); + if (colonIndex == -1) + { + errors.Add(new ParsingResult + { + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} no colon found" + }); + + return [.. errors]; + } + + var parts = validateRequest.Value.Split(':'); + + //https://security.stackexchange.com/questions/265408/how-many-times-need-e-mail-headers-be-signed-with-dkim-to-mitigate-dkim-header-i#:~:text=If%20the%20e%2Dmail%20uses,field%20of%20the%20DKIM%20signature. + var groupedHeaders = parts.GroupBy(o => o).Select(g => new { Key = g.Key, Count = g.Count() }); + foreach (var groupedHeader in groupedHeaders) + { + if (groupedHeader.Count == 2) + { + errors.Add(new ParsingResult { Status = ParsingStatus.Info, Message = $"{groupedHeader.Key} oversigning detected" }); + } + } + + //TODO: Check important Headers + //TODO: check that headers are signed at most twice (only oversigning) + + return [.. errors]; + } } } diff --git a/src/Nager.EmailAuthentication/DmarcRecordParser.cs b/src/Nager.EmailAuthentication/DmarcRecordParser.cs index ee59304..ae0aa10 100644 --- a/src/Nager.EmailAuthentication/DmarcRecordParser.cs +++ b/src/Nager.EmailAuthentication/DmarcRecordParser.cs @@ -27,14 +27,14 @@ public static bool TryParse( /// /// The raw DMARC string to parse. /// The parsed DMARC record, if successful. - /// A list of errors in the DMARC string, if any. + /// A list of errors in the DMARC string, if any. /// if parsing is successful; otherwise . public static bool TryParse( string dmarcRaw, out DmarcDataFragment? dmarcDataFragment, - out ParseError[]? parseErrors) + out ParsingResult[]? parsingResults) { - parseErrors = null; + parsingResults = null; if (string.IsNullOrWhiteSpace(dmarcRaw)) { @@ -124,19 +124,19 @@ public static bool TryParse( }; var parserBase = new KeyValueParserBase(handlers); - return parserBase.TryParse(dmarcRaw, out dmarcDataFragment, out parseErrors); + return parserBase.TryParse(dmarcRaw, out dmarcDataFragment, out parsingResults); } - private static ParseError[] ValidateVersion(ValidateRequest validateRequest) + private static ParsingResult[] ValidateVersion(ValidateRequest validateRequest) { - var errors = new List(); + var errors = new List(); if (string.IsNullOrEmpty(validateRequest.Value)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Critical, - ErrorMessage = "DMARC record is invalid: it must start with 'v=DMARC1'." + Status = ParsingStatus.Critical, + Message = "DMARC record is invalid: it must start with 'v=DMARC1'." }); return [.. errors]; @@ -144,24 +144,24 @@ private static ParseError[] ValidateVersion(ValidateRequest validateRequest) if (!validateRequest.Value.Equals("DMARC1", StringComparison.OrdinalIgnoreCase)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Critical, - ErrorMessage = "DMARC record is invalid: it must start with 'v=DMARC1'." + Status = ParsingStatus.Critical, + Message = "DMARC record is invalid: it must start with 'v=DMARC1'." }); } return [.. errors]; } - private static ParseError[] ValidateDomainPolicy(ValidateRequest validateRequest) + private static ParsingResult[] ValidateDomainPolicy(ValidateRequest validateRequest) { if (string.IsNullOrEmpty(validateRequest.Value)) { return []; } - var errors = new List(); + var errors = new List(); var domainPolicy = AllowedPolicies .Where(policy => policy.Equals(validateRequest.Value, StringComparison.OrdinalIgnoreCase)) @@ -169,26 +169,26 @@ private static ParseError[] ValidateDomainPolicy(ValidateRequest validateRequest if (domainPolicy == null) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"Unknown policy \"{validateRequest.Value}\"" + Status = ParsingStatus.Error, + Message = $"Unknown policy \"{validateRequest.Value}\"" }); } return [.. errors]; } - private static ParseError[] ValidatePolicyPercentage(ValidateRequest validateRequest) + private static ParsingResult[] ValidatePolicyPercentage(ValidateRequest validateRequest) { - var errors = new List(); + var errors = new List(); if (string.IsNullOrEmpty(validateRequest.Value)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} is empty" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} is empty" }); return [.. errors]; @@ -196,10 +196,10 @@ private static ParseError[] ValidatePolicyPercentage(ValidateRequest validateReq if (!int.TryParse(validateRequest.Value, out var percentage)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} value is not a number" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} value is not a number" }); return [.. errors]; @@ -207,10 +207,10 @@ private static ParseError[] ValidatePolicyPercentage(ValidateRequest validateReq if (percentage < 0 || percentage > 100) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} value is not in allowed range" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} value is not in allowed range" }); return [.. errors]; @@ -219,16 +219,16 @@ private static ParseError[] ValidatePolicyPercentage(ValidateRequest validateReq return []; } - private static ParseError[] ValidateReportFormat(ValidateRequest validateRequest) + private static ParsingResult[] ValidateReportFormat(ValidateRequest validateRequest) { - var errors = new List(); + var errors = new List(); if (string.IsNullOrEmpty(validateRequest.Value)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} is empty" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} is empty" }); return [.. errors]; @@ -236,43 +236,43 @@ private static ParseError[] ValidateReportFormat(ValidateRequest validateRequest if (!validateRequest.Value.Equals("afrf", StringComparison.OrdinalIgnoreCase)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} only allow Authentication Failure Reporting Format (afrf)" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} only allow Authentication Failure Reporting Format (afrf)" }); return [.. errors]; } - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Info, - ErrorMessage = $"{validateRequest.Field} is not required" + Status = ParsingStatus.Info, + Message = $"{validateRequest.Field} is not required" }); return [.. errors]; } - private static ParseError[] ValidateFailureReportingOptions(ValidateRequest validateRequest) + private static ParsingResult[] ValidateFailureReportingOptions(ValidateRequest validateRequest) { - var errors = new List(); + var errors = new List(); if (string.IsNullOrEmpty(validateRequest.Value)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} is empty" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} is empty" }); return [.. errors]; } - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Info, - ErrorMessage = $"{validateRequest.Field} is not required as failure reports are not very common" + Status = ParsingStatus.Info, + Message = $"{validateRequest.Field} is not required, as failure reports are not very common" }); var allowedOptions = new char[] { '0', '1', 'd', 's' }; @@ -281,10 +281,10 @@ private static ParseError[] ValidateFailureReportingOptions(ValidateRequest vali { if (!allowedOptions.Contains(validateRequest.Value[0])) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} wrong config {validateRequest.Value}" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} wrong config {validateRequest.Value}" }); } @@ -294,10 +294,10 @@ private static ParseError[] ValidateFailureReportingOptions(ValidateRequest vali var colonIndex = validateRequest.Value.IndexOf(':'); if (colonIndex == -1) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} no colon found" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} no colon found" }); return [.. errors]; @@ -308,10 +308,10 @@ private static ParseError[] ValidateFailureReportingOptions(ValidateRequest vali { if (part.Length != 1) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} option invalid {part}" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} option invalid {part}" }); continue; @@ -319,10 +319,10 @@ private static ParseError[] ValidateFailureReportingOptions(ValidateRequest vali if (!allowedOptions.Contains(part[0])) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} wrong config {part[0]}" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} wrong config {part[0]}" }); } } @@ -330,16 +330,16 @@ private static ParseError[] ValidateFailureReportingOptions(ValidateRequest vali return [.. errors]; } - private static ParseError[] ValidateReportingInterval(ValidateRequest validateRequest) + private static ParsingResult[] ValidateReportingInterval(ValidateRequest validateRequest) { - var errors = new List(); + var errors = new List(); if (string.IsNullOrEmpty(validateRequest.Value)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} is empty" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} is empty" }); return [.. errors]; @@ -347,10 +347,10 @@ private static ParseError[] ValidateReportingInterval(ValidateRequest validateRe if (!int.TryParse(validateRequest.Value, out var reportInterval)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} value is not a number" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} value is not a number" }); return [.. errors]; @@ -359,10 +359,10 @@ private static ParseError[] ValidateReportingInterval(ValidateRequest validateRe // Time interval is less than one hour if (reportInterval < 3600) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Warning, - ErrorMessage = $"{validateRequest.Field} value is to small" + Status = ParsingStatus.Warning, + Message = $"{validateRequest.Field} value is to small" }); return [.. errors]; @@ -371,10 +371,10 @@ private static ParseError[] ValidateReportingInterval(ValidateRequest validateRe // Time interval is greater than 2 days if (reportInterval > 86400 * 2) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Warning, - ErrorMessage = $"{validateRequest.Field} value is to large" + Status = ParsingStatus.Warning, + Message = $"{validateRequest.Field} value is to large" }); return [.. errors]; @@ -383,16 +383,16 @@ private static ParseError[] ValidateReportingInterval(ValidateRequest validateRe return []; } - private static ParseError[] ValidateAlignmentMode(ValidateRequest validateRequest) + private static ParsingResult[] ValidateAlignmentMode(ValidateRequest validateRequest) { - var errors = new List(); + var errors = new List(); if (string.IsNullOrEmpty(validateRequest.Value)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} is empty" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} is empty" }); return [.. errors]; @@ -404,35 +404,35 @@ private static ParseError[] ValidateAlignmentMode(ValidateRequest validateReques { if (!allowedOptions.Contains(validateRequest.Value[0])) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} wrong config {validateRequest.Value}" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} wrong config {validateRequest.Value}" }); } return [.. errors]; } - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} wrong config {validateRequest.Value}" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} wrong config {validateRequest.Value}" }); return [.. errors]; } - private static ParseError[] ValidateAddresses(ValidateRequest validateRequest) + private static ParsingResult[] ValidateAddresses(ValidateRequest validateRequest) { - var errors = new List(); + var errors = new List(); if (string.IsNullOrEmpty(validateRequest.Value)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} is empty" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} is empty" }); return [.. errors]; @@ -444,10 +444,10 @@ private static ParseError[] ValidateAddresses(ValidateRequest validateRequest) { if (!DmarcEmailDetail.TryParse(dmarcUri.Trim(), out var dmarcEmailDetail)) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} wrong dmarc uri {dmarcUri}" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} wrong dmarc uri {dmarcUri}" }); continue; @@ -460,10 +460,10 @@ private static ParseError[] ValidateAddresses(ValidateRequest validateRequest) if (!dmarcEmailDetail.IsValidEmailAddress) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"{validateRequest.Field} wrong email address {dmarcEmailDetail.EmailAddress}" + Status = ParsingStatus.Error, + Message = $"{validateRequest.Field} wrong email address {dmarcEmailDetail.EmailAddress}" }); } } diff --git a/src/Nager.EmailAuthentication/KeyValueParserBase.cs b/src/Nager.EmailAuthentication/KeyValueParserBase.cs index 45813ed..14c4ea0 100644 --- a/src/Nager.EmailAuthentication/KeyValueParserBase.cs +++ b/src/Nager.EmailAuthentication/KeyValueParserBase.cs @@ -32,14 +32,14 @@ public KeyValueParserBase( /// /// The raw input string containing key-value pairs. /// The parsed object of type if parsing is successful; otherwise, null. - /// An array of parsing errors or warnings, if any; otherwise, null. + /// An array of parsing errors or warnings, if any; otherwise, null. /// True if at least one key-value pair is successfully mapped; otherwise, false. public bool TryParse( string rawData, out T? dataFragment, - out ParseError[]? parseErrors) + out ParsingResult[]? parsingResults) { - parseErrors = null; + parsingResults = null; if (!this._keyValueParser.TryParse(rawData, out var parseResult) || parseResult == null) { @@ -47,7 +47,7 @@ public bool TryParse( return false; } - var errors = new List(); + var errors = new List(); // Detect duplicate keys var duplicateConfigurations = parseResult.KeyValues @@ -56,10 +56,10 @@ public bool TryParse( foreach (var duplicate in duplicateConfigurations) { - errors.Add(new ParseError + errors.Add(new ParsingResult { - Severity = ErrorSeverity.Error, - ErrorMessage = $"Duplicate configuration detected for key: '{duplicate.Key}'." + Status = ParsingStatus.Error, + Message = $"Duplicate configuration detected for key: '{duplicate.Key}'." }); } @@ -88,14 +88,14 @@ public bool TryParse( continue; } - errors.Add(new ParseError + errors.Add(new ParsingResult { - ErrorMessage = $"Unrecognized part: {keyValue.Key}{this._keyValueSeparator}{keyValue.Value}", - Severity = ErrorSeverity.Warning + Message = $"Unrecognized part: {keyValue.Key}{this._keyValueSeparator}{keyValue.Value}", + Status = ParsingStatus.Warning }); } - parseErrors = errors.Count == 0 ? null : [.. errors]; + parsingResults = errors.Count == 0 ? null : [.. errors]; dataFragment = tempDataFragment; return mappingFound; diff --git a/src/Nager.EmailAuthentication/MappingHandler.cs b/src/Nager.EmailAuthentication/MappingHandler.cs index 54f2c2d..48ac4fb 100644 --- a/src/Nager.EmailAuthentication/MappingHandler.cs +++ b/src/Nager.EmailAuthentication/MappingHandler.cs @@ -15,8 +15,8 @@ internal class MappingHandler /// /// Gets or sets the optional validation logic for the input string. - /// Returns an array of objects indicating validation issues, or null if validation is not performed. + /// Returns an array of objects indicating validation issues, or null if validation is not performed. /// - public Func? Validate { get; set; } + public Func? Validate { get; set; } } } diff --git a/src/Nager.EmailAuthentication/Models/ParseError.cs b/src/Nager.EmailAuthentication/Models/ParseError.cs deleted file mode 100644 index 7e6821f..0000000 --- a/src/Nager.EmailAuthentication/Models/ParseError.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Nager.EmailAuthentication.Models -{ - /// - /// Parse Error - /// - public class ParseError - { - /// - /// Description of the error - /// - public required string ErrorMessage { get; set; } - - /// - /// Severity of the error - /// - public ErrorSeverity Severity { get; set; } - } -} diff --git a/src/Nager.EmailAuthentication/Models/ParsingResult.cs b/src/Nager.EmailAuthentication/Models/ParsingResult.cs new file mode 100644 index 0000000..38f55c2 --- /dev/null +++ b/src/Nager.EmailAuthentication/Models/ParsingResult.cs @@ -0,0 +1,18 @@ +namespace Nager.EmailAuthentication.Models +{ + /// + /// Parsing Result + /// + public class ParsingResult + { + /// + /// Description of the parsing result + /// + public required string Message { get; set; } + + /// + /// Status of the parsing result + /// + public ParsingStatus Status { get; set; } + } +} diff --git a/src/Nager.EmailAuthentication/Models/ErrorSeverity.cs b/src/Nager.EmailAuthentication/Models/ParsingStatus.cs similarity index 88% rename from src/Nager.EmailAuthentication/Models/ErrorSeverity.cs rename to src/Nager.EmailAuthentication/Models/ParsingStatus.cs index 3e28c20..2828af2 100644 --- a/src/Nager.EmailAuthentication/Models/ErrorSeverity.cs +++ b/src/Nager.EmailAuthentication/Models/ParsingStatus.cs @@ -1,9 +1,9 @@ namespace Nager.EmailAuthentication.Models { /// - /// Error Severity + /// Parsing Status /// - public enum ErrorSeverity + public enum ParsingStatus { /// /// Info