sContentType = $sContentType; $this->sCharset = $sCharset; $this->aBodyParams = $aBodyParams; $this->sContentID = $sContentID; $this->sDescription = $sDescription; $this->sMailEncodingName = $sMailEncodingName; $this->sDisposition = $sDisposition; $this->aDispositionParams = $aDispositionParams; $this->sFileName = $sFileName; $this->sLanguage = $sLanguage; $this->sLocation = $sLocation; $this->iSize = $iSize; $this->iTextLineCount = $iTextLineCount; $this->sPartID = $sPartID; $this->aSubParts = $aSubParts; } /** * return string */ public function MailEncodingName() { return $this->sMailEncodingName; } /** * return string */ public function PartID() { return (string) $this->sPartID; } /** * return string */ public function FileName() { return $this->sFileName; } /** * return string */ public function ContentType() { return $this->sContentType; } /** * return int */ public function Size() { return (int) $this->iSize; } /** * return int */ public function EstimatedSize() { $fCoefficient = 1; switch (strtolower($this->MailEncodingName())) { case 'base64': $fCoefficient = 0.75; break; case 'quoted-printable': $fCoefficient = 0.44; break; } return (int) ($this->Size() * $fCoefficient); } /** * return string */ public function Charset() { return $this->sCharset; } /** * return string */ public function ContentID() { return (null === $this->sContentID) ? '' : $this->sContentID; } /** * return bool */ public function IsInline() { return (null === $this->sDisposition) ? (0 < strlen($this->ContentID())) : ('inline' === strtolower($this->sDisposition)); } /** * @return array|null */ public function SearchPlainParts() { $aReturn = array(); $aParts = $this->SearchByContentType('text/plain'); foreach ($aParts as $oPart) { if (!$oPart->isAttachBodyPart()) { $aReturn[] = $oPart; } } return $aReturn; } /** * @return array|null */ public function SearchHtmlParts() { $aReturn = array(); $aParts = $this->SearchByContentType('text/html'); foreach ($aParts as $oPart) { if (!$oPart->isAttachBodyPart()) { $aReturn[] = $oPart; } } return $aReturn; } /** * @return array|null */ public function SearchHtmlOrPlainParts() { $mResult = $this->SearchHtmlParts(); if (null === $mResult || (is_array($mResult) && 0 === count($mResult))) { $mResult = $this->SearchPlainParts(); } return $mResult; } /** * @return string */ public function SearchCharset() { $sResult = ''; $mHtmlParts = $this->SearchHtmlParts(); $mPlainParts = $this->SearchPlainParts(); $mParts = array(); if (is_array($mHtmlParts) && 0 < count($mHtmlParts)) { $mParts = array_merge($mParts, $mHtmlParts); } if (is_array($mPlainParts) && 0 < count($mPlainParts)) { $mParts = array_merge($mParts, $mPlainParts); } foreach ($mParts as $oPart) { $sResult = $oPart ? $oPart->Charset() : ''; if (!empty($sResult)) { break; } } if (0 === strlen($sResult)) { $aParts = $this->SearchAttachmentsParts(); foreach ($aParts as $oPart) { if (0 === strlen($sResult)) { $sResult = $oPart ? $oPart->Charset() : ''; } else { break; } } } return $sResult; } /** * @return bool */ protected function isAttachBodyPart() { $bResult = ( (null !== $this->sDisposition && 'attachment' === strtolower($this->sDisposition)) ); if (!$bResult && null !== $this->sContentType) { $sContentType = strtolower($this->sContentType); $bResult = false === strpos($sContentType, 'multipart/') && 'text/html' !== $sContentType && 'text/plain' !== $sContentType; } return $bResult; } /** * @return array */ public function SearchAttachmentsParts() { $aReturn = array(); if ($this->isAttachBodyPart()) { $aReturn[] = $this; } if (is_array($this->aSubParts) && 0 < count($this->aSubParts)) { foreach ($this->aSubParts as /* @var $oSubPart \MailSo\Imap\BodyStructure */ &$oSubPart) { $aReturn = array_merge($aReturn, $oSubPart->SearchAttachmentsParts()); unset($oSubPart); } } return $aReturn; } /** * @param string $sContentType * * @return array */ public function SearchByContentType($sContentType) { $aReturn = array(); if (strtolower($sContentType) === $this->sContentType) { $aReturn[] = $this; } if (is_array($this->aSubParts) && 0 < count($this->aSubParts)) { foreach ($this->aSubParts as /* @var $oSubPart \MailSo\Imap\BodyStructure */ &$oSubPart) { $aReturn = array_merge($aReturn, $oSubPart->SearchByContentType($sContentType)); } } return $aReturn; } /** * @param string $sMimeIndex * * @return \MailSo\Imap\BodyStructure */ public function GetPartByMimeIndex($sMimeIndex) { $oPart = null; if (0 < strlen($sMimeIndex)) { if ($sMimeIndex === $this->sPartID) { $oPart = $this; } if (null === $oPart && is_array($this->aSubParts) && 0 < count($this->aSubParts)) { foreach ($this->aSubParts as /* @var $oSubPart \MailSo\Imap\BodyStructure */ &$oSubPart) { $oPart = $oSubPart->GetPartByMimeIndex($sMimeIndex); if (null !== $oPart) { break; } } } } return $oPart; } /** * @param array $aParams * @param string $sParamName * @param string $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8 * * @return string */ private static function decodeAttrParamenter($aParams, $sParamName, $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8) { $sResult = ''; if (isset($aParams[$sParamName])) { $sResult = \MailSo\Base\Utils::DecodeHeaderValue($aParams[$sParamName], $sCharset); } else if (isset($aParams[$sParamName.'*'])) { $aValueParts = explode('\'\'', $aParams[$sParamName.'*'], 2); if (is_array($aValueParts) && 2 === count($aValueParts)) { $sCharset = isset($aValueParts[0]) ? $aValueParts[0] : \MailSo\Base\Enumerations\Charset::UTF_8; $sResult = \MailSo\Base\Utils::ConvertEncoding( urldecode($aValueParts[1]), $sCharset, \MailSo\Base\Enumerations\Charset::UTF_8); } else { $sResult = urldecode($aParams[$sParamName.'*']); } } else if (isset($aParams[$sParamName.'*0*'])) { $sCharset = ''; $aFileNames = array(); foreach ($aParams as $sName => $sValue) { $aMatches = array(); if ($sParamName.'*0*' === $sName) { if (0 === strlen($sCharset)) { $aValueParts = explode('\'\'', $sValue, 2); if (is_array($aValueParts) && 2 === count($aValueParts) && 0 < strlen($aValueParts[0])) { $sCharset = $aValueParts[0]; $sValue = $aValueParts[1]; } } $aFileNames[0] = $sValue; } else if ($sParamName.'*0*' !== $sName && preg_match('/^'.preg_quote($sParamName, '/').'\*([0-9]+)\*$/i', $sName, $aMatches) && 0 < strlen($aMatches[1])) { $aFileNames[(int) $aMatches[1]] = $sValue; } } if (0 < count($aFileNames)) { ksort($aFileNames, SORT_NUMERIC); $sResult = implode(array_values($aFileNames)); $sResult = urldecode($sResult); if (0 < strlen($sCharset)) { $sResult = \MailSo\Base\Utils::ConvertEncoding($sResult, $sCharset, \MailSo\Base\Enumerations\Charset::UTF_8); } } } return $sResult; } /** * @param array $aBodyStructure * @param string $sPartID = '' * * @return \MailSo\Imap\BodyStructure */ public static function NewInstance(array $aBodyStructure, $sPartID = '') { if (!is_array($aBodyStructure) || 2 > count($aBodyStructure)) { return null; } else { $sBodyMainType = null; if (is_string($aBodyStructure[0]) && 'NIL' !== $aBodyStructure[0]) { $sBodyMainType = $aBodyStructure[0]; } $sBodySubType = null; $sContentType = ''; $aSubParts = null; $aBodyParams = array(); $sName = null; $sCharset = null; $sContentID = null; $sDescription = null; $sMailEncodingName = null; $iSize = 0; $iTextLineCount = 0; // valid for rfc822/message and text parts $iExtraItemPos = 0; // list index of items which have no well-established position (such as 0, 1, 5, etc). if (null === $sBodyMainType) { // Process multipart body structure if (!is_array($aBodyStructure[0])) { return null; } else { $sBodyMainType = 'multipart'; $sSubPartIDPrefix = ''; if (0 === strlen($sPartID) || '.' === $sPartID[strlen($sPartID) - 1]) { // This multi-part is root part of message. $sSubPartIDPrefix = $sPartID; $sPartID .= 'TEXT'; } else if (0 < strlen($sPartID)) { // This multi-part is a part of another multi-part. $sSubPartIDPrefix = $sPartID.'.'; } $aSubParts = array(); $iIndex = 1; while ($iExtraItemPos < count($aBodyStructure) && is_array($aBodyStructure[$iExtraItemPos])) { $oPart = self::NewInstance($aBodyStructure[$iExtraItemPos], $sSubPartIDPrefix.$iIndex); if (null === $oPart) { return null; } else { // For multipart, we have no charset info in the part itself. Thus, // obtain charset from nested parts. if ($sCharset == null) { $sCharset = $oPart->Charset(); } $aSubParts[] = $oPart; $iExtraItemPos++; $iIndex++; } } } if ($iExtraItemPos < count($aBodyStructure)) { if (!is_string($aBodyStructure[$iExtraItemPos]) || 'NIL' === $aBodyStructure[$iExtraItemPos]) { return null; } $sBodySubType = strtolower($aBodyStructure[$iExtraItemPos]); $iExtraItemPos++; } if ($iExtraItemPos < count($aBodyStructure)) { $sBodyParamList = $aBodyStructure[$iExtraItemPos]; if (is_array($sBodyParamList)) { $aBodyParams = self::getKeyValueListFromArrayList($sBodyParamList); } } $iExtraItemPos++; } else { // Process simple (singlepart) body structure if (7 > count($aBodyStructure)) { return null; } $sBodyMainType = strtolower($sBodyMainType); if (!is_string($aBodyStructure[1]) || 'NIL' === $aBodyStructure[1]) { return null; } $sBodySubType = strtolower($aBodyStructure[1]); $aBodyParamList = $aBodyStructure[2]; if (is_array($aBodyParamList)) { $aBodyParams = self::getKeyValueListFromArrayList($aBodyParamList); if (isset($aBodyParams['charset'])) { $sCharset = $aBodyParams['charset']; } if (is_array($aBodyParams)) { $sName = self::decodeAttrParamenter($aBodyParams, 'name', $sContentType); } } if (null !== $aBodyStructure[3] && 'NIL' !== $aBodyStructure[3]) { if (!is_string($aBodyStructure[3])) { return null; } $sContentID = $aBodyStructure[3]; } if (null !== $aBodyStructure[4] && 'NIL' !== $aBodyStructure[4]) { if (!is_string($aBodyStructure[4])) { return null; } $sDescription = $aBodyStructure[4]; } if (null !== $aBodyStructure[5] && 'NIL' !== $aBodyStructure[5]) { if (!is_string($aBodyStructure[5])) { return null; } $sMailEncodingName = $aBodyStructure[5]; } if (is_numeric($aBodyStructure[6])) { $iSize = (int) $aBodyStructure[6]; } else { $iSize = -1; } if (0 === strlen($sPartID) || '.' === $sPartID[strlen($sPartID) - 1]) { // This is the only sub-part of the message (otherwise, it would be // one of sub-parts of a multi-part, and partID would already be fully set up). $sPartID .= '1'; } $iExtraItemPos = 7; if ('text' === $sBodyMainType) { if ($iExtraItemPos < count($aBodyStructure)) { if (is_numeric($aBodyStructure[$iExtraItemPos])) { $iTextLineCount = (int) $aBodyStructure[$iExtraItemPos]; } else { $iTextLineCount = -1; } } else { $iTextLineCount = -1; } $iExtraItemPos++; } else if ('message' === $sBodyMainType && 'rfc822' === $sBodySubType) { if ($iExtraItemPos + 2 < count($aBodyStructure)) { if (is_numeric($aBodyStructure[$iExtraItemPos + 2])) { $iTextLineCount = (int) $aBodyStructure[$iExtraItemPos + 2]; } else { $iTextLineCount = -1; } } else { $iTextLineCount = -1; } $iExtraItemPos += 3; } $iExtraItemPos++; // skip MD5 digest of the body because most mail servers leave it NIL anyway } $sContentType = $sBodyMainType.'/'.$sBodySubType; $sDisposition = null; $aDispositionParams = null; $sFileName = null; if ($iExtraItemPos < count($aBodyStructure)) { $aDispList = $aBodyStructure[$iExtraItemPos]; if (is_array($aDispList) && 1 < count($aDispList)) { if (null !== $aDispList[0]) { if (is_string($aDispList[0]) && 'NIL' !== $aDispList[0]) { $sDisposition = $aDispList[0]; } else { return null; } } } $aDispParamList = $aDispList[1]; if (is_array($aDispParamList)) { $aDispositionParams = self::getKeyValueListFromArrayList($aDispParamList); if (is_array($aDispositionParams)) { $sFileName = self::decodeAttrParamenter($aDispositionParams, 'filename', $sCharset); } } } $iExtraItemPos++; $sLanguage = null; if ($iExtraItemPos < count($aBodyStructure)) { if (null !== $aBodyStructure[$iExtraItemPos] && 'NIL' !== $aBodyStructure[$iExtraItemPos]) { if (is_array($aBodyStructure[$iExtraItemPos])) { $sLanguage = implode(',', $aBodyStructure[$iExtraItemPos]); } else if (is_string($aBodyStructure[$iExtraItemPos])) { $sLanguage = $aBodyStructure[$iExtraItemPos]; } } $iExtraItemPos++; } $sLocation = null; if ($iExtraItemPos < count($aBodyStructure)) { if (null !== $aBodyStructure[$iExtraItemPos] && 'NIL' !== $aBodyStructure[$iExtraItemPos]) { if (is_string($aBodyStructure[$iExtraItemPos])) { $sLocation = $aBodyStructure[$iExtraItemPos]; } } $iExtraItemPos++; } return new self( $sContentType, $sCharset, $aBodyParams, $sContentID, $sDescription, $sMailEncodingName, $sDisposition, $aDispositionParams, \MailSo\Base\Utils::Utf8Clear( null === $sFileName || 0 === strlen($sFileName) ? $sName : $sFileName), $sLanguage, $sLocation, $iSize, $iTextLineCount, $sPartID, $aSubParts ); } } /** * @param array $aBodyStructure * @param string $sPartID * * @return \MailSo\Imap\BodyStructure|null */ public static function NewInstanceFromRfc822SubPart(array $aBodyStructure, $sSubPartID) { $oBody = null; $aBodySubStructure = self::findPartByIndexInArray($aBodyStructure, $sSubPartID); if ($aBodySubStructure && is_array($aBodySubStructure) && isset($aBodySubStructure[8])) { $oBody = self::NewInstance($aBodySubStructure[8], $sSubPartID); } return $oBody; } /** * @param array $aList * @param string $sPartID * * @return array|null */ private static function findPartByIndexInArray(array $aList, $sPartID) { $bFind = false; $aPath = explode('.', ''.$sPartID); $aCurrentPart = $aList; foreach ($aPath as $iPos => $iNum) { $iIndex = intval($iNum) - 1; if (0 <= $iIndex && 0 < $iPos ? isset($aCurrentPart[8][$iIndex]) : isset($aCurrentPart[$iIndex])) { $aCurrentPart = 0 < $iPos ? $aCurrentPart[8][$iIndex] : $aCurrentPart[$iIndex]; $bFind = true; } } return $bFind ? $aCurrentPart : null; } /** * Returns dict with key="charset" and value="US-ASCII" for array ("CHARSET" "US-ASCII"). * Keys are lowercased (StringDictionary itself does this), values are not altered. * * @param array $aList * * @return array */ private static function getKeyValueListFromArrayList(array $aList) { $aDict = null; if (0 === count($aList) % 2) { $aDict = array(); for ($iIndex = 0, $iLen = count($aList); $iIndex < $iLen; $iIndex += 2) { if (is_string($aList[$iIndex]) && isset($aList[$iIndex + 1]) && is_string($aList[$iIndex + 1])) { $aDict[strtolower($aList[$iIndex])] = $aList[$iIndex + 1]; } } } return $aDict; } }