iPriority = \MailSo\Mime\Enumerations\MessagePriority::NORMAL; } public function Plain() : string { return $this->sPlain; } public function Html() : string { return $this->sHtml; } public function PgpSigned() : ?array { return $this->aPgpSigned; } public function PgpEncrypted() : ?array { return $this->aPgpEncrypted; } public function Folder() : string { return $this->sFolder; } public function Uid() : int { return $this->iUid; } public function MessageId() : string { return $this->sMessageId; } public function Subject() : string { return $this->sSubject; } public function ContentType() : string { return $this->sContentType; } public function Size() : int { return $this->iSize; } public function SpamScore() : int { return $this->iSpamScore; } private function setSpamScore($value) : void { $this->iSpamScore = \intval(\max(0, \min(100, $value))); } public function SpamResult() : string { return $this->sSpamResult; } public function IsSpam() : bool { return $this->bIsSpam; } /** * null = not scanned * true = scanned and infected * false = scanned and no infection found */ public function HasVirus() : ?bool { return $this->bHasVirus; } public function InternalTimeStampInUTC() : int { return $this->iInternalTimeStampInUTC; } public function HeaderTimeStampInUTC() : int { return $this->iHeaderTimeStampInUTC; } public function HeaderDate() : string { return $this->sHeaderDate; } public function FlagsLowerCase() : array { return $this->aFlagsLowerCase; } public function From() : ?\MailSo\Mime\EmailCollection { return $this->oFrom; } public function Priority() : int { return $this->iPriority; } public function Sender() : ?\MailSo\Mime\EmailCollection { return $this->oSender; } public function ReplyTo() : ?\MailSo\Mime\EmailCollection { return $this->oReplyTo; } public function DeliveredTo() : ?\MailSo\Mime\EmailCollection { return $this->oDeliveredTo; } public function To() : ?\MailSo\Mime\EmailCollection { return $this->oTo; } public function Cc() : ?\MailSo\Mime\EmailCollection { return $this->oCc; } public function Bcc() : ?\MailSo\Mime\EmailCollection { return $this->oBcc; } public function Attachments() : ?AttachmentCollection { return $this->oAttachments; } public function InReplyTo() : string { return $this->sInReplyTo; } public function References() : string { return $this->sReferences; } public function DeliveryReceipt() : string { return $this->sDeliveryReceipt; } public function ReadReceipt() : string { return $this->sReadReceipt; } public function UnsubsribeLinks() : array { return $this->aUnsubsribeLinks; } public function ReadingConfirmation() : string { return $this->ReadReceipt(); } public function DraftInfo() : ?array { return $this->aDraftInfo; } public function Threads() : array { return $this->aThreads; } public function SetThreads(array $aThreads) { $this->aThreads = $aThreads; } public static function NewFetchResponseInstance(string $sFolder, \MailSo\Imap\FetchResponse $oFetchResponse, ?\MailSo\Imap\BodyStructure $oBodyStructure = null) : self { $oMessage = new self; if (!$oBodyStructure) { $oBodyStructure = $oFetchResponse->GetFetchBodyStructure(); } $sInternalDate = $oFetchResponse->GetFetchValue(FetchType::INTERNALDATE); $aFlags = $oFetchResponse->GetFetchValue(FetchType::FLAGS); $oMessage->sFolder = $sFolder; $oMessage->iUid = (int) $oFetchResponse->GetFetchValue(FetchType::UID); $oMessage->iSize = (int) $oFetchResponse->GetFetchValue(FetchType::RFC822_SIZE); $oMessage->aFlagsLowerCase = \array_map('mb_strtolower', \array_map('\\MailSo\\Base\\Utils::Utf7ModifiedToUtf8', $aFlags ?: [])); $oMessage->iInternalTimeStampInUTC = \MailSo\Base\DateTimeHelper::ParseInternalDateString($sInternalDate); $sCharset = $oBodyStructure ? Utils::NormalizeCharset($oBodyStructure->SearchCharset()) : ''; $sHeaders = $oFetchResponse->GetHeaderFieldsValue(); if (\strlen($sHeaders)) { $oHeaders = new \MailSo\Mime\HeaderCollection($sHeaders, false, $sCharset); $sContentTypeCharset = $oHeaders->ParameterValue( \MailSo\Mime\Enumerations\Header::CONTENT_TYPE, \MailSo\Mime\Enumerations\Parameter::CHARSET ); if (\strlen($sContentTypeCharset)) { $sCharset = Utils::NormalizeCharset($sContentTypeCharset); } if (\strlen($sCharset)) { $oHeaders->SetParentCharset($sCharset); } $bCharsetAutoDetect = !\strlen($sCharset); $oMessage->sSubject = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::SUBJECT, $bCharsetAutoDetect); $oMessage->sMessageId = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::MESSAGE_ID); $oMessage->sContentType = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE); $oMessage->oFrom = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::FROM_, $bCharsetAutoDetect); $oMessage->oTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::TO_, $bCharsetAutoDetect); $oMessage->oCc = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::CC, $bCharsetAutoDetect); $oMessage->oBcc = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::BCC, $bCharsetAutoDetect); if ($oMessage->oFrom) { $oHeaders->PopulateEmailColectionByDkim($oMessage->oFrom); } $oMessage->oSender = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::SENDER, $bCharsetAutoDetect); $oMessage->oReplyTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::REPLY_TO, $bCharsetAutoDetect); $oMessage->oDeliveredTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::DELIVERED_TO, $bCharsetAutoDetect); $oMessage->sInReplyTo = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::IN_REPLY_TO); $oMessage->sReferences = Utils::StripSpaces( $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::REFERENCES)); $sHeaderDate = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::DATE); $oMessage->sHeaderDate = $sHeaderDate; $oMessage->iHeaderTimeStampInUTC = \MailSo\Base\DateTimeHelper::ParseRFC2822DateString($sHeaderDate); // Priority $oMessage->iPriority = \MailSo\Mime\Enumerations\MessagePriority::NORMAL; $sPriority = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_MSMAIL_PRIORITY); if (!\strlen($sPriority)) { $sPriority = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::IMPORTANCE); } if (!\strlen($sPriority)) { $sPriority = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_PRIORITY); } if (\strlen($sPriority)) { switch (\str_replace(' ', '', \strtolower($sPriority))) { case 'high': case '1(highest)': case '2(high)': case '1': case '2': $oMessage->iPriority = \MailSo\Mime\Enumerations\MessagePriority::HIGH; break; case 'low': case '4(low)': case '5(lowest)': case '4': case '5': $oMessage->iPriority = \MailSo\Mime\Enumerations\MessagePriority::LOW; break; } } // Delivery Receipt $oMessage->sDeliveryReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::RETURN_RECEIPT_TO)); // Read Receipt $oMessage->sReadReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::DISPOSITION_NOTIFICATION_TO)); if (empty($oMessage->sReadReceipt)) { $oMessage->sReadReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_CONFIRM_READING_TO)); } // Unsubscribe links $oMessage->aUnsubsribeLinks = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::LIST_UNSUBSCRIBE); if (empty($oMessage->aUnsubsribeLinks)) { $oMessage->aUnsubsribeLinks = array(); } else { $oMessage->aUnsubsribeLinks = explode(',', $oMessage->aUnsubsribeLinks); $oMessage->aUnsubsribeLinks = array_map( function ($link) { return trim($link, ' <>'); }, $oMessage->aUnsubsribeLinks ); } if ($spam = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_SPAMD_RESULT)) { if (\preg_match('/\\[([\\d\\.-]+)\\s*\\/\\s*([\\d\\.]+)\\];/', $spam, $match)) { if ($threshold = \floatval($match[2])) { $oMessage->setSpamScore(100 * \floatval($match[1]) / $threshold); $oMessage->sSpamResult = "{$match[1]} / {$match[2]}"; } } $oMessage->bIsSpam = false !== \stripos($oMessage->sSubject, '*** SPAM ***'); } else if ($spam = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_BOGOSITY)) { $oMessage->sSpamResult = $spam; $oMessage->bIsSpam = !!\preg_match('/yes|spam/', $spam); if (\preg_match('/spamicity=([\\d\\.]+)/', $spam, $spamicity)) { $oMessage->setSpamScore(\floatval($spamicity[1])); } } else if ($spam = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_SPAM_STATUS)) { $oMessage->sSpamResult = $spam; if (\preg_match('/(?:hits|score)=([\\d\\.-]+)/', $spam, $value) && \preg_match('/required=([\\d\\.-]+)/', $spam, $required)) { if ($threshold = \floatval($required[1])) { $oMessage->setSpamScore(100 * \floatval($value[1]) / $threshold); $oMessage->sSpamResult = "{$value[1]} / {$required[1]}"; } } $oMessage->bIsSpam = 'Yes' === \substr($spam, 0, 3); // $spam = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_SPAM_FLAG); // $oMessage->bIsSpam = false !== \stripos($spam, 'YES'); } if ($virus = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_VIRUS)) { $oMessage->bHasVirus = true; } if ($virus = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_VIRUS_STATUS)) { if (false !== \stripos($spam, 'infected')) { $oMessage->bHasVirus = true; } else if (false !== \stripos($spam, 'clean')) { $oMessage->bHasVirus = false; } } if ($virus = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_VIRUS_SCANNED)) { $oMessage->sVirusScanned = $virus; } $sDraftInfo = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_DRAFT_INFO); if (\strlen($sDraftInfo)) { $sType = ''; $sFolder = ''; $iUid = 0; $oParameters = new \MailSo\Mime\ParameterCollection($sDraftInfo); foreach ($oParameters as $oParameter) { switch (\strtolower($oParameter->Name())) { case 'type': $sType = $oParameter->Value(); break; case 'uid': $iUid = (int) $oParameter->Value(); break; case 'folder': $sFolder = \base64_decode($oParameter->Value()); break; } } if (\strlen($sType) && \strlen($sFolder) && $iUid) { $oMessage->aDraftInfo = array($sType, $iUid, $sFolder); } } $oMessage->sAutocrypt = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::AUTOCRYPT); } else if ($oFetchResponse->GetEnvelope()) { $sCharset = $sCharset ?: \MailSo\Base\Enumerations\Charset::ISO_8859_1; // date, subject, from, sender, reply-to, to, cc, bcc, in-reply-to, message-id $oMessage->sMessageId = $oFetchResponse->GetFetchEnvelopeValue(9, ''); $oMessage->sSubject = Utils::DecodeHeaderValue($oFetchResponse->GetFetchEnvelopeValue(1, ''), $sCharset); $oMessage->oFrom = $oFetchResponse->GetFetchEnvelopeEmailCollection(2, $sCharset); $oMessage->oSender = $oFetchResponse->GetFetchEnvelopeEmailCollection(3, $sCharset); $oMessage->oReplyTo = $oFetchResponse->GetFetchEnvelopeEmailCollection(4, $sCharset); $oMessage->oTo = $oFetchResponse->GetFetchEnvelopeEmailCollection(5, $sCharset); $oMessage->oCc = $oFetchResponse->GetFetchEnvelopeEmailCollection(6, $sCharset); $oMessage->oBcc = $oFetchResponse->GetFetchEnvelopeEmailCollection(7, $sCharset); $oMessage->sInReplyTo = $oFetchResponse->GetFetchEnvelopeValue(8, ''); } if ($oBodyStructure) { $gEncryptedParts = $oBodyStructure->SearchByContentType('multipart/encrypted'); foreach ($gEncryptedParts as $oPart) { if ($oPart->IsPgpEncrypted()) { $oMessage->aPgpEncrypted = [ 'PartId' => $oPart->SubParts()[1]->PartID() ]; } } $gSignatureParts = $oBodyStructure->SearchByContentType('multipart/signed'); foreach ($gSignatureParts as $oPart) { if (!$oPart->IsPgpSigned()) { continue; } $oPgpSignaturePart = $oPart->SubParts()[1]; $oMessage->aPgpSigned = [ // /?/Raw/&q[]=/0/Download/&q[]=/... // /?/Raw/&q[]=/0/View/&q[]=/... 'BodyPartId' => $oPart->SubParts()[0]->PartID(), 'SigPartId' => $oPgpSignaturePart->PartID(), 'MicAlg' => (string) $oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'micalg') ]; /* // An empty section specification refers to the entire message, including the header. // But Dovecot does not return it with BODY.PEEK[1], so we also use BODY.PEEK[1.MIME]. $sPgpText = \trim( \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->aPgpSigned['BodyPartId'].'.MIME]')) . "\r\n\r\n" . \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->aPgpSigned['BodyPartId'].']')) ); if ($sPgpText) { $oMessage->aPgpSigned['Body'] = $sPgpText; } $sPgpSignatureText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->aPgpSigned['SigPartId'].']'); if ($sPgpSignatureText && 0 < \strpos($sPgpSignatureText, 'BEGIN PGP SIGNATURE')) { $oMessage->aPgpSigned['Signature'] = $oPart->SubParts()[0]->PartID(); } */ break; } $aTextParts = $oBodyStructure->GetHtmlAndPlainParts(); if ($aTextParts) { $sCharset = $sCharset ?: \MailSo\Base\Enumerations\Charset::UTF_8; $aHtmlParts = array(); $aPlainParts = array(); foreach ($aTextParts as $oPart) { $sText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oPart->PartID().']'); if (null === $sText) { // TextPartIsTrimmed ? $sText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oPart->PartID().']<0>'); } if (\is_string($sText) && \strlen($sText)) { $sText = Utils::DecodeEncodingValue($sText, $oPart->MailEncodingName()); $sText = Utils::ConvertEncoding($sText, Utils::NormalizeCharset($oPart->Charset() ?: $sCharset, true), \MailSo\Base\Enumerations\Charset::UTF_8 ); $sText = Utils::Utf8Clear($sText); // https://datatracker.ietf.org/doc/html/rfc4880#section-7 // Cleartext Signature if (!$oMessage->aPgpSigned && \str_contains($sText, '-----BEGIN PGP SIGNED MESSAGE-----')) { $oMessage->aPgpSigned = [ 'BodyPartId' => $oPart->PartID() ]; } if (\str_contains($sText, '-----BEGIN PGP MESSAGE-----')) { $keyIds = []; if (\SnappyMail\PGP\GPG::isSupported()) { $GPG = new \SnappyMail\PGP\GPG(''); $keyIds = $GPG->getEncryptedMessageKeys($sText); } $oMessage->aPgpEncrypted = [ 'PartId' => $oPart->PartID(), 'KeyIds' => $keyIds ]; } if ('text/html' === $oPart->ContentType()) { $aHtmlParts[] = $sText; } else { if ($oPart->IsFlowedFormat()) { $sText = Utils::DecodeFlowedFormat($sText); } $aPlainParts[] = $sText; } } } $oMessage->sHtml = \implode('
', $aHtmlParts); $oMessage->sPlain = \trim(\implode("\n", $aPlainParts)); unset($aHtmlParts, $aPlainParts); } $gAttachmentsParts = $oBodyStructure->SearchAttachmentsParts(); if ($gAttachmentsParts->valid()) { $oMessage->oAttachments = new AttachmentCollection; foreach ($gAttachmentsParts as /* @var $oAttachmentItem \MailSo\Imap\BodyStructure */ $oAttachmentItem) { // if ('application/pgp-keys' === $oAttachmentItem->ContentType()) import ??? $oMessage->oAttachments->append( Attachment::NewBodyStructureInstance($oMessage->sFolder, $oMessage->iUid, $oAttachmentItem) ); } } } return $oMessage; } #[\ReturnTypeWillChange] public function jsonSerialize() { return array( '@Object' => 'Object/Message', 'Folder' => $this->sFolder, 'Uid' => $this->iUid, 'subject' => \trim(Utils::Utf8Clear($this->sSubject)), 'MessageId' => $this->sMessageId, 'SpamScore' => $this->bIsSpam ? 100 : $this->iSpamScore, 'SpamResult' => $this->sSpamResult, 'IsSpam' => $this->bIsSpam, 'HasVirus' => $this->bHasVirus, 'DateTimeStampInUTC' => $this->iInternalTimeStampInUTC, // \MailSo\Mime\EmailCollection 'From' => $this->oFrom, 'ReplyTo' => $this->oReplyTo, 'To' => $this->oTo, 'Cc' => $this->oCc, 'Bcc' => $this->oBcc, 'Sender' => $this->oSender, 'DeliveredTo' => $this->oDeliveredTo, 'Priority' => $this->iPriority, 'Threads' => $this->aThreads, 'UnsubsribeLinks' => $this->aUnsubsribeLinks, 'ReadReceipt' => '', 'Autocrypt' => $this->sAutocrypt, 'Attachments' => $this->oAttachments ? $this->oAttachments->SpecData() : null, 'Flags' => $this->aFlagsLowerCase, // https://datatracker.ietf.org/doc/html/rfc8621#section-4.1.1 'size' => $this->iSize, 'receivedAt' => \gmdate('Y-m-d\\TH:i:s\\Z', $this->iInternalTimeStampInUTC) // 'keywords' => \array_fill_keys($this->aFlagsLowerCase, true) ); } }