Fix opendkim header parser (#1232)

This commit is contained in:
RainLoop Team 2016-10-25 22:13:16 +03:00
parent 149cb62642
commit d79fe8757e

View file

@ -1,480 +1,480 @@
<?php <?php
/* /*
* This file is part of MailSo. * This file is part of MailSo.
* *
* (c) 2014 Usenko Timur * (c) 2014 Usenko Timur
* *
* For the full copyright and license information, please view the LICENSE * For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
namespace MailSo\Mime; namespace MailSo\Mime;
/** /**
* @category MailSo * @category MailSo
* @package Mime * @package Mime
*/ */
class HeaderCollection extends \MailSo\Base\Collection class HeaderCollection extends \MailSo\Base\Collection
{ {
/** /**
* @return string * @return string
*/ */
protected $sRawHeaders; protected $sRawHeaders;
/** /**
* @var strign * @var strign
*/ */
protected $sParentCharset; protected $sParentCharset;
/** /**
* @access protected * @access protected
* *
* @param string $sRawHeaders = '' * @param string $sRawHeaders = ''
* @param bool $bStoreRawHeaders = true * @param bool $bStoreRawHeaders = true
*/ */
protected function __construct($sRawHeaders = '', $bStoreRawHeaders = true) protected function __construct($sRawHeaders = '', $bStoreRawHeaders = true)
{ {
parent::__construct(); parent::__construct();
$this->sRawHeaders = ''; $this->sRawHeaders = '';
$this->sParentCharset = ''; $this->sParentCharset = '';
if (0 < \strlen($sRawHeaders)) if (0 < \strlen($sRawHeaders))
{ {
$this->Parse($sRawHeaders, $bStoreRawHeaders); $this->Parse($sRawHeaders, $bStoreRawHeaders);
} }
} }
/** /**
* @param string $sRawHeaders = '' * @param string $sRawHeaders = ''
* @param bool $bStoreRawHeaders = true * @param bool $bStoreRawHeaders = true
* *
* @return \MailSo\Mime\HeaderCollection * @return \MailSo\Mime\HeaderCollection
*/ */
public static function NewInstance($sRawHeaders = '', $bStoreRawHeaders = true) public static function NewInstance($sRawHeaders = '', $bStoreRawHeaders = true)
{ {
return new self($sRawHeaders, $bStoreRawHeaders); return new self($sRawHeaders, $bStoreRawHeaders);
} }
/** /**
* @param string $sName * @param string $sName
* @param string $sValue * @param string $sValue
* @param bool $bToTop = false * @param bool $bToTop = false
* *
* @return \MailSo\Mime\HeaderCollection * @return \MailSo\Mime\HeaderCollection
* *
* @throws \MailSo\Base\Exceptions\InvalidArgumentException * @throws \MailSo\Base\Exceptions\InvalidArgumentException
*/ */
public function AddByName($sName, $sValue, $bToTop = false) public function AddByName($sName, $sValue, $bToTop = false)
{ {
return $this->Add(Header::NewInstance($sName, $sValue), $bToTop); return $this->Add(Header::NewInstance($sName, $sValue), $bToTop);
} }
/** /**
* @param string $sName * @param string $sName
* @param string $sValue * @param string $sValue
* @param bool $bToTop = false * @param bool $bToTop = false
* *
* @return \MailSo\Mime\HeaderCollection * @return \MailSo\Mime\HeaderCollection
* *
* @throws \MailSo\Base\Exceptions\InvalidArgumentException * @throws \MailSo\Base\Exceptions\InvalidArgumentException
*/ */
public function SetByName($sName, $sValue, $bToTop = false) public function SetByName($sName, $sValue, $bToTop = false)
{ {
return $this->RemoveByName($sName)->Add(Header::NewInstance($sName, $sValue), $bToTop); return $this->RemoveByName($sName)->Add(Header::NewInstance($sName, $sValue), $bToTop);
} }
/** /**
* @return \MailSo\Mime\Header | null * @return \MailSo\Mime\Header | null
*/ */
public function &GetByIndex($iIndex) public function &GetByIndex($iIndex)
{ {
$mResult = null; $mResult = null;
$mResult =& parent::GetByIndex($iIndex); $mResult =& parent::GetByIndex($iIndex);
return $mResult; return $mResult;
} }
/** /**
* @param string $sHeaderName * @param string $sHeaderName
* @param bool $bCharsetAutoDetect = false * @param bool $bCharsetAutoDetect = false
* @return string * @return string
*/ */
public function ValueByName($sHeaderName, $bCharsetAutoDetect = false) public function ValueByName($sHeaderName, $bCharsetAutoDetect = false)
{ {
$oHeader = null; $oHeader = null;
$oHeader =& $this->GetByName($sHeaderName); $oHeader =& $this->GetByName($sHeaderName);
return (null !== $oHeader) ? ($bCharsetAutoDetect ? $oHeader->ValueWithCharsetAutoDetect() : $oHeader->Value()) : ''; return (null !== $oHeader) ? ($bCharsetAutoDetect ? $oHeader->ValueWithCharsetAutoDetect() : $oHeader->Value()) : '';
} }
/** /**
* @param string $sHeaderName * @param string $sHeaderName
* @param bool $bCharsetAutoDetect = false * @param bool $bCharsetAutoDetect = false
* @return array * @return array
*/ */
public function ValuesByName($sHeaderName, $bCharsetAutoDetect = false) public function ValuesByName($sHeaderName, $bCharsetAutoDetect = false)
{ {
$aResult = array(); $aResult = array();
$oHeader = null; $oHeader = null;
$sHeaderNameLower = \strtolower($sHeaderName); $sHeaderNameLower = \strtolower($sHeaderName);
$aHeaders =& $this->GetAsArray(); $aHeaders =& $this->GetAsArray();
foreach ($aHeaders as /* @var $oHeader \MailSo\Mime\Header */ &$oHeader) foreach ($aHeaders as /* @var $oHeader \MailSo\Mime\Header */ &$oHeader)
{ {
if ($sHeaderNameLower === \strtolower($oHeader->Name())) if ($sHeaderNameLower === \strtolower($oHeader->Name()))
{ {
$aResult[] = $bCharsetAutoDetect ? $oHeader->ValueWithCharsetAutoDetect() : $oHeader->Value(); $aResult[] = $bCharsetAutoDetect ? $oHeader->ValueWithCharsetAutoDetect() : $oHeader->Value();
} }
} }
return $aResult; return $aResult;
} }
/** /**
* @param string $sHeaderName * @param string $sHeaderName
* *
* @return \MailSo\Mime\HeaderCollection * @return \MailSo\Mime\HeaderCollection
*/ */
public function RemoveByName($sHeaderName) public function RemoveByName($sHeaderName)
{ {
$aResult = $this->FilterList(function ($oHeader) use ($sHeaderName) { $aResult = $this->FilterList(function ($oHeader) use ($sHeaderName) {
return $oHeader && \strtolower($oHeader->Name()) !== \strtolower($sHeaderName); return $oHeader && \strtolower($oHeader->Name()) !== \strtolower($sHeaderName);
}); });
return $this->SetAsArray($aResult); return $this->SetAsArray($aResult);
} }
/** /**
* @param string $sHeaderName * @param string $sHeaderName
* @param bool $bCharsetAutoDetect = false * @param bool $bCharsetAutoDetect = false
* *
* @return \MailSo\Mime\EmailCollection|null * @return \MailSo\Mime\EmailCollection|null
*/ */
public function GetAsEmailCollection($sHeaderName, $bCharsetAutoDetect = false) public function GetAsEmailCollection($sHeaderName, $bCharsetAutoDetect = false)
{ {
$oResult = null; $oResult = null;
$sValue = $this->ValueByName($sHeaderName, $bCharsetAutoDetect); $sValue = $this->ValueByName($sHeaderName, $bCharsetAutoDetect);
if (0 < \strlen($sValue)) if (0 < \strlen($sValue))
{ {
$oResult = \MailSo\Mime\EmailCollection::NewInstance($sValue); $oResult = \MailSo\Mime\EmailCollection::NewInstance($sValue);
} }
return $oResult && 0 < $oResult->Count() ? $oResult : null; return $oResult && 0 < $oResult->Count() ? $oResult : null;
} }
/** /**
* @param string $sHeaderName * @param string $sHeaderName
* @return \MailSo\Mime\ParameterCollection|null * @return \MailSo\Mime\ParameterCollection|null
*/ */
public function ParametersByName($sHeaderName) public function ParametersByName($sHeaderName)
{ {
$oParameters = $oHeader = null; $oParameters = $oHeader = null;
$oHeader =& $this->GetByName($sHeaderName); $oHeader =& $this->GetByName($sHeaderName);
if ($oHeader) if ($oHeader)
{ {
$oParameters = $oHeader->Parameters(); $oParameters = $oHeader->Parameters();
} }
return $oParameters; return $oParameters;
} }
/** /**
* @param string $sHeaderName * @param string $sHeaderName
* @param string $sParamName * @param string $sParamName
* @return string * @return string
*/ */
public function ParameterValue($sHeaderName, $sParamName) public function ParameterValue($sHeaderName, $sParamName)
{ {
$oParameters = $this->ParametersByName($sHeaderName); $oParameters = $this->ParametersByName($sHeaderName);
return (null !== $oParameters) ? $oParameters->ParameterValueByName($sParamName) : ''; return (null !== $oParameters) ? $oParameters->ParameterValueByName($sParamName) : '';
} }
/** /**
* @param string $sHeaderName * @param string $sHeaderName
* @return \MailSo\Mime\Header | false * @return \MailSo\Mime\Header | false
*/ */
public function &GetByName($sHeaderName) public function &GetByName($sHeaderName)
{ {
$oResult = $oHeader = null; $oResult = $oHeader = null;
$sHeaderNameLower = \strtolower($sHeaderName); $sHeaderNameLower = \strtolower($sHeaderName);
$aHeaders =& $this->GetAsArray(); $aHeaders =& $this->GetAsArray();
foreach ($aHeaders as /* @var $oHeader \MailSo\Mime\Header */ &$oHeader) foreach ($aHeaders as /* @var $oHeader \MailSo\Mime\Header */ &$oHeader)
{ {
if ($sHeaderNameLower === \strtolower($oHeader->Name())) if ($sHeaderNameLower === \strtolower($oHeader->Name()))
{ {
$oResult =& $oHeader; $oResult =& $oHeader;
break; break;
} }
} }
return $oResult; return $oResult;
} }
/** /**
* @param array $aList * @param array $aList
* @return \MailSo\Mime\HeaderCollection * @return \MailSo\Mime\HeaderCollection
* *
* @throws \MailSo\Base\Exceptions\InvalidArgumentException * @throws \MailSo\Base\Exceptions\InvalidArgumentException
*/ */
public function SetAsArray($aList) public function SetAsArray($aList)
{ {
parent::SetAsArray($aList); parent::SetAsArray($aList);
return $this; return $this;
} }
/** /**
* @param string $sParentCharset * @param string $sParentCharset
* @return \MailSo\Mime\HeaderCollection * @return \MailSo\Mime\HeaderCollection
*/ */
public function SetParentCharset($sParentCharset) public function SetParentCharset($sParentCharset)
{ {
if (0 < \strlen($sParentCharset)) if (0 < \strlen($sParentCharset))
{ {
if ($this->sParentCharset !== $sParentCharset) if ($this->sParentCharset !== $sParentCharset)
{ {
$oHeader = null; $oHeader = null;
$aHeaders =& $this->GetAsArray(); $aHeaders =& $this->GetAsArray();
foreach ($aHeaders as /* @var $oHeader \MailSo\Mime\Header */ &$oHeader) foreach ($aHeaders as /* @var $oHeader \MailSo\Mime\Header */ &$oHeader)
{ {
$oHeader->SetParentCharset($sParentCharset); $oHeader->SetParentCharset($sParentCharset);
} }
$this->sParentCharset = $sParentCharset; $this->sParentCharset = $sParentCharset;
} }
} }
return $this; return $this;
} }
/** /**
* @return void * @return void
*/ */
public function Clear() public function Clear()
{ {
parent::Clear(); parent::Clear();
$this->sRawHeaders = ''; $this->sRawHeaders = '';
} }
/** /**
* @param string $sRawHeaders * @param string $sRawHeaders
* @param bool $bStoreRawHeaders = false * @param bool $bStoreRawHeaders = false
* @param string $sParentCharset = '' * @param string $sParentCharset = ''
* *
* @return \MailSo\Mime\HeaderCollection * @return \MailSo\Mime\HeaderCollection
*/ */
public function Parse($sRawHeaders, $bStoreRawHeaders = false, $sParentCharset = '') public function Parse($sRawHeaders, $bStoreRawHeaders = false, $sParentCharset = '')
{ {
$this->Clear(); $this->Clear();
if ($bStoreRawHeaders) if ($bStoreRawHeaders)
{ {
$this->sRawHeaders = $sRawHeaders; $this->sRawHeaders = $sRawHeaders;
} }
if (0 === \strlen($this->sParentCharset)) if (0 === \strlen($this->sParentCharset))
{ {
$this->sParentCharset = $sParentCharset; $this->sParentCharset = $sParentCharset;
} }
$aHeaders = \explode("\n", \str_replace("\r", '', $sRawHeaders)); $aHeaders = \explode("\n", \str_replace("\r", '', $sRawHeaders));
$sName = null; $sName = null;
$sValue = null; $sValue = null;
foreach ($aHeaders as $sHeadersValue) foreach ($aHeaders as $sHeadersValue)
{ {
if (0 === strlen($sHeadersValue)) if (0 === strlen($sHeadersValue))
{ {
continue; continue;
} }
$sFirstChar = \substr($sHeadersValue, 0, 1); $sFirstChar = \substr($sHeadersValue, 0, 1);
if ($sFirstChar !== ' ' && $sFirstChar !== "\t" && false === \strpos($sHeadersValue, ':')) if ($sFirstChar !== ' ' && $sFirstChar !== "\t" && false === \strpos($sHeadersValue, ':'))
{ {
continue; continue;
} }
else if (null !== $sName && ($sFirstChar === ' ' || $sFirstChar === "\t")) else if (null !== $sName && ($sFirstChar === ' ' || $sFirstChar === "\t"))
{ {
$sValue = \is_null($sValue) ? '' : $sValue; $sValue = \is_null($sValue) ? '' : $sValue;
if ('?=' === \substr(\rtrim($sHeadersValue), -2)) if ('?=' === \substr(\rtrim($sHeadersValue), -2))
{ {
$sHeadersValue = \rtrim($sHeadersValue); $sHeadersValue = \rtrim($sHeadersValue);
} }
if ('=?' === \substr(\ltrim($sHeadersValue), 0, 2)) if ('=?' === \substr(\ltrim($sHeadersValue), 0, 2))
{ {
$sHeadersValue = \ltrim($sHeadersValue); $sHeadersValue = \ltrim($sHeadersValue);
} }
if ('=?' === \substr($sHeadersValue, 0, 2)) if ('=?' === \substr($sHeadersValue, 0, 2))
{ {
$sValue .= $sHeadersValue; $sValue .= $sHeadersValue;
} }
else else
{ {
$sValue .= "\n".$sHeadersValue; $sValue .= "\n".$sHeadersValue;
} }
} }
else else
{ {
if (null !== $sName) if (null !== $sName)
{ {
$oHeader = Header::NewInstanceFromEncodedString($sName.': '.$sValue, $this->sParentCharset); $oHeader = Header::NewInstanceFromEncodedString($sName.': '.$sValue, $this->sParentCharset);
if ($oHeader) if ($oHeader)
{ {
$this->Add($oHeader); $this->Add($oHeader);
} }
$sName = null; $sName = null;
$sValue = null; $sValue = null;
} }
$aHeaderParts = \explode(':', $sHeadersValue, 2); $aHeaderParts = \explode(':', $sHeadersValue, 2);
$sName = $aHeaderParts[0]; $sName = $aHeaderParts[0];
$sValue = isset($aHeaderParts[1]) ? $aHeaderParts[1] : ''; $sValue = isset($aHeaderParts[1]) ? $aHeaderParts[1] : '';
if ('?=' === \substr(\rtrim($sValue), -2)) if ('?=' === \substr(\rtrim($sValue), -2))
{ {
$sValue = \rtrim($sValue); $sValue = \rtrim($sValue);
} }
} }
} }
if (null !== $sName) if (null !== $sName)
{ {
$oHeader = Header::NewInstanceFromEncodedString($sName.': '.$sValue, $this->sParentCharset); $oHeader = Header::NewInstanceFromEncodedString($sName.': '.$sValue, $this->sParentCharset);
if ($oHeader) if ($oHeader)
{ {
$this->Add($oHeader); $this->Add($oHeader);
} }
} }
return $this; return $this;
} }
/** /**
* @return int * @return int
*/ */
public function DkimStatuses() public function DkimStatuses()
{ {
$aResult = array(); $aResult = array();
$aHeaders = $this->ValuesByName(\MailSo\Mime\Enumerations\Header::AUTHENTICATION_RESULTS); $aHeaders = $this->ValuesByName(\MailSo\Mime\Enumerations\Header::AUTHENTICATION_RESULTS);
if (\is_array($aHeaders) && 0 < \count($aHeaders)) if (\is_array($aHeaders) && 0 < \count($aHeaders))
{ {
foreach ($aHeaders as $sHeaderValue) foreach ($aHeaders as $sHeaderValue)
{ {
$sStatus = ''; $sStatus = '';
$sHeader = ''; $sHeader = '';
$sDkimLine = ''; $sDkimLine = '';
$aMatch = array(); $aMatch = array();
$sHeaderValue = \preg_replace('/[\r\n\t\s]+/', ' ', $sHeaderValue); $sHeaderValue = \preg_replace('/[\r\n\t\s]+/', ' ', $sHeaderValue);
if (\preg_match('/dkim=[^;]+/i', $sHeaderValue, $aMatch) && !empty($aMatch[0])) if (\preg_match('/dkim=.+/i', $sHeaderValue, $aMatch) && !empty($aMatch[0]))
{ {
$sDkimLine = $aMatch[0]; $sDkimLine = $aMatch[0];
$aMatch = array(); $aMatch = array();
if (\preg_match('/dkim=([a-zA-Z0-9]+)/i', $sDkimLine, $aMatch) && !empty($aMatch[1])) if (\preg_match('/dkim=([a-zA-Z0-9]+)/i', $sDkimLine, $aMatch) && !empty($aMatch[1]))
{ {
$sStatus = $aMatch[1]; $sStatus = $aMatch[1];
} }
$aMatch = array(); $aMatch = array();
if (\preg_match('/header\.(d|i|from)=([^\s;]+)/i', $sDkimLine, $aMatch) && !empty($aMatch[2])) if (\preg_match('/header\.(d|i|from)=([^\s;]+)/i', $sDkimLine, $aMatch) && !empty($aMatch[2]))
{ {
$sHeader = \trim($aMatch[2]); $sHeader = \trim($aMatch[2]);
} }
if (!empty($sStatus) && !empty($sHeader)) if (!empty($sStatus) && !empty($sHeader))
{ {
$aResult[] = array($sStatus, $sHeader, $sDkimLine); $aResult[] = array($sStatus, $sHeader, $sDkimLine);
} }
} }
} }
} }
else else
{ {
// X-DKIM-Authentication-Results: signer="hostinger.com" status="pass" // X-DKIM-Authentication-Results: signer="hostinger.com" status="pass"
$aHeaders = $this->ValuesByName(\MailSo\Mime\Enumerations\Header::X_DKIM_AUTHENTICATION_RESULTS); $aHeaders = $this->ValuesByName(\MailSo\Mime\Enumerations\Header::X_DKIM_AUTHENTICATION_RESULTS);
if (\is_array($aHeaders) && 0 < \count($aHeaders)) if (\is_array($aHeaders) && 0 < \count($aHeaders))
{ {
foreach ($aHeaders as $sHeaderValue) foreach ($aHeaders as $sHeaderValue)
{ {
$sStatus = ''; $sStatus = '';
$sHeader = ''; $sHeader = '';
$aMatch = array(); $aMatch = array();
$sHeaderValue = \preg_replace('/[\r\n\t\s]+/', ' ', $sHeaderValue); $sHeaderValue = \preg_replace('/[\r\n\t\s]+/', ' ', $sHeaderValue);
if (\preg_match('/status[\s]?=[\s]?"([a-zA-Z0-9]+)"/i', $sHeaderValue, $aMatch) && !empty($aMatch[1])) if (\preg_match('/status[\s]?=[\s]?"([a-zA-Z0-9]+)"/i', $sHeaderValue, $aMatch) && !empty($aMatch[1]))
{ {
$sStatus = $aMatch[1]; $sStatus = $aMatch[1];
} }
if (\preg_match('/signer[\s]?=[\s]?"([^";]+)"/i', $sHeaderValue, $aMatch) && !empty($aMatch[1])) if (\preg_match('/signer[\s]?=[\s]?"([^";]+)"/i', $sHeaderValue, $aMatch) && !empty($aMatch[1]))
{ {
$sHeader = \trim($aMatch[1]); $sHeader = \trim($aMatch[1]);
} }
if (!empty($sStatus) && !empty($sHeader)) if (!empty($sStatus) && !empty($sHeader))
{ {
$aResult[] = array($sStatus, $sHeader, $sHeaderValue); $aResult[] = array($sStatus, $sHeader, $sHeaderValue);
} }
} }
} }
} }
return $aResult; return $aResult;
} }
/** /**
* @return int * @return int
*/ */
public function PopulateEmailColectionByDkim($oEmails) public function PopulateEmailColectionByDkim($oEmails)
{ {
if ($oEmails && $oEmails instanceof \MailSo\Mime\EmailCollection) if ($oEmails && $oEmails instanceof \MailSo\Mime\EmailCollection)
{ {
$aDkimStatuses = $this->DkimStatuses(); $aDkimStatuses = $this->DkimStatuses();
if (\is_array($aDkimStatuses) && 0 < \count($aDkimStatuses)) if (\is_array($aDkimStatuses) && 0 < \count($aDkimStatuses))
{ {
$oEmails->ForeachList(function (/* @var $oItem \MailSo\Mime\Email */ $oItem) use ($aDkimStatuses) { $oEmails->ForeachList(function (/* @var $oItem \MailSo\Mime\Email */ $oItem) use ($aDkimStatuses) {
if ($oItem && $oItem instanceof \MailSo\Mime\Email) if ($oItem && $oItem instanceof \MailSo\Mime\Email)
{ {
$sEmail = $oItem->GetEmail(); $sEmail = $oItem->GetEmail();
foreach ($aDkimStatuses as $aDkimData) foreach ($aDkimStatuses as $aDkimData)
{ {
if (isset($aDkimData[0], $aDkimData[1]) && if (isset($aDkimData[0], $aDkimData[1]) &&
$aDkimData[1] === \strstr($sEmail, $aDkimData[1])) $aDkimData[1] === \strstr($sEmail, $aDkimData[1]))
{ {
$oItem->SetDkimStatusAndValue($aDkimData[0], empty($aDkimData[2]) ? '' : $aDkimData[2]); $oItem->SetDkimStatusAndValue($aDkimData[0], empty($aDkimData[2]) ? '' : $aDkimData[2]);
} }
} }
} }
}); });
} }
} }
} }
/** /**
* @return string * @return string
*/ */
public function ToEncodedString() public function ToEncodedString()
{ {
$aResult = array(); $aResult = array();
$aHeaders =& $this->GetAsArray(); $aHeaders =& $this->GetAsArray();
foreach ($aHeaders as /* @var $oHeader \MailSo\Mime\Header */ &$oHeader) foreach ($aHeaders as /* @var $oHeader \MailSo\Mime\Header */ &$oHeader)
{ {
$aResult[] = $oHeader->EncodedValue(); $aResult[] = $oHeader->EncodedValue();
} }
return \implode(\MailSo\Mime\Enumerations\Constants::CRLF, $aResult); return \implode(\MailSo\Mime\Enumerations\Constants::CRLF, $aResult);
} }
} }