* @link https://www.oxidmodule.com */ declare(strict_types=1); namespace D3\MailAuthenticationCheck\Model; use D3\MailAuthenticationCheck\Enum\DMARCMechanism as Mechanism; use D3\MailAuthenticationCheck\Mechanism\AbstractMechanism; use D3\MailAuthenticationCheck\Mechanism\DMARC\DkimAlignment; use D3\MailAuthenticationCheck\Mechanism\DMARC\Percentage; use D3\MailAuthenticationCheck\Mechanism\DMARC\ReportingFormat; use D3\MailAuthenticationCheck\Mechanism\DMARC\ReportingInterval; use D3\MailAuthenticationCheck\Mechanism\DMARC\SpfAlignment; use D3\MailAuthenticationCheck\Mechanism\DMARC\ForensicReportOptions; use D3\MailAuthenticationCheck\Mechanism\DMARC\RejectPolicy; use D3\MailAuthenticationCheck\Mechanism\DMARC\ReportUriAggregateData; use D3\MailAuthenticationCheck\Mechanism\DMARC\ReportUriForensicData; use D3\MailAuthenticationCheck\Mechanism\DMARC\SubRejectPolicy; use LogicException; class DMARCRecord { private string $rawRecord; public function __construct(string $rawRecord = '') { $this->rawRecord = $rawRecord; } public function getRawRecord(): string { return $this->rawRecord; } /** * @return AbstractMechanism[] */ public function getTerms(): iterable { $terms = explode(' ', $this->rawRecord); array_shift($terms); // Remove first part (v=DMARC1) foreach ($terms as $term) { if(empty($term)) { continue; } preg_match('`^(?(p|rua|ruf|sp|aspf|adkim|fo|ri|rf|pct))=(?[^;]*);*$`U', $term, $matches); switch(strtolower($matches['qualifier'])) { case Mechanism::REJECT_POLICY: yield new RejectPolicy($term, $matches['qualifier'], $matches['term']); break; case Mechanism::SUB_REJECT_POLICY: yield new SubRejectPolicy($term, $matches['qualifier'], $matches['term']); break; case Mechanism::REPORT_URI_AGGREGATE: yield new ReportUriAggregateData($term, $matches['qualifier'], $matches['term']); break; case Mechanism::REPORT_URI_FORENSIC: yield new ReportUriForensicData($term, $matches['qualifier'], $matches['term']); break; case Mechanism::SPF_ALIGNMENT: yield new SpfAlignment($term, $matches['qualifier'], $matches['term']); break; case Mechanism::DKIM_ALIGNMENT: yield new DkimAlignment($term, $matches['qualifier'], $matches['term']); break; case Mechanism::REPORTING_INTERVAL: yield new ReportingInterval($term, $matches['qualifier'], $matches['term']); break; case Mechanism::REPORTING_FORMAT: yield new ReportingFormat($term, $matches['qualifier'], $matches['term']); break; case Mechanism::FORENSIC_REPORT_OPTIONS: yield new ForensicReportOptions($term, $matches['qualifier'], $matches['term']); break; case Mechanism::PERCENTAGE: yield new Percentage($term, $matches['qualifier'], $matches['term']); break; default: throw new LogicException('Unknown mechanism '.$matches['qualifier']); } } } public function isValid(): bool { return (bool) preg_match( '/(^v=DMARC1;)?( +(p=(none|quarantine|reject);|rua=mailto:([^;]*);|ruf=mailto:([^;]*);|sp=(none|quarantine|reject);|aspf=s;|adkim=s;|fo=((?!:)(:?\w+)+);))/i', $this->rawRecord ); } /** * @return RejectPolicy * @throws LogicException */ public function getRejectPolicy(): RejectPolicy { /** @var RejectPolicy $mechanism */ $mechanism = $this->getTerm(RejectPolicy::class); return $mechanism; } /** * @return ReportUriAggregateData * @throws LogicException */ public function getReportUriAggregate(): ReportUriAggregateData { /** @var ReportUriAggregateData $mechanism */ $mechanism = $this->getTerm(ReportUriAggregateData::class); return $mechanism; } /** * @return ReportUriForensicData * @throws LogicException */ public function getReportUriForensic(): ReportUriForensicData { /** @var ReportUriForensicData $mechanism */ $mechanism = $this->getTerm(ReportUriForensicData::class); return $mechanism; } /** * @param string $className * * @return AbstractMechanism * @throws LogicException */ public function getTerm(string $className): AbstractMechanism { foreach ($this->getTerms() as $term) { if ( $term instanceof $className ) { return $term; } } throw new LogicException('undefined term '.$className); } }