diff --git a/dev/Model/Message.js b/dev/Model/Message.js index 8a85626f5..000635e34 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -14,6 +14,7 @@ import { SettingsUserStore } from 'Stores/User/Settings'; import { FileInfo, RFC822 } from 'Common/File'; import { AttachmentCollectionModel } from 'Model/AttachmentCollection'; import { EmailCollectionModel } from 'Model/EmailCollection'; +import { MimeHeaderCollectionModel } from 'Model/MimeHeaderCollection'; import { AbstractModel } from 'Knoin/AbstractModel'; import PreviewHTML from 'Html/PreviewMessage.html'; @@ -122,6 +123,7 @@ export class MessageModel extends AbstractModel { this.threadUnseen = ko.observableArray(); this.unsubsribeLinks = ko.observableArray(); this.flags = ko.observableArray(); + this.headers = ko.observableArray(new MimeHeaderCollectionModel); addComputablesTo(this, { attachmentIconClass: () => @@ -224,8 +226,44 @@ export class MessageModel extends AbstractModel { if (super.revivePropertiesFromJson(json)) { // this.foundCIDs = isArray(json.FoundCIDs) ? json.FoundCIDs : []; // this.attachments(AttachmentCollectionModel.reviveFromJson(json.attachments, this.foundCIDs)); +// this.headers(MimeHeaderCollectionModel.reviveFromJson(json.headers)); this.computeSenderEmail(); + + let value, headers = this.headers(); +/* // These could be by Envelope or MIME + this.messageId = headers.valueByName('Message-Id'); + this.subject(headers.valueByName('Subject')); + this.sender = EmailCollectionModel.fromString(headers.valueByName('Sender')); + this.from = EmailCollectionModel.fromString(headers.valueByName('From')); + this.replyTo = EmailCollectionModel.fromString(headers.valueByName('Reply-To')); + this.to = EmailCollectionModel.fromString(headers.valueByName('To')); + this.cc = EmailCollectionModel.fromString(headers.valueByName('Cc')); + this.bcc = EmailCollectionModel.fromString(headers.valueByName('Bcc')); + this.inReplyTo = headers.valueByName('In-Reply-To'); + + this.deliveredTo = EmailCollectionModel.fromString(headers.valueByName('Delivered-To')); +*/ + // Priority + value = headers.valueByName('X-MSMail-Priority') + || headers.valueByName('Importance') + || headers.valueByName('X-Priority'); + if (value) { + if (/[h12]/.test(value[0])) { + this.priority(1); + } else if (/[l45]/.test(value[0])) { + this.priority(5); + } + } + + // Unsubscribe links + value = headers.valueByName('List-Unsubscribe'); + if (value) { + this.unsubsribeLinks(value.split(',').map( + link => link.replace(/^[ <>]+|[ <>]+$/g, '') + )); + } + return true; } } diff --git a/dev/Model/MimeHeader.js b/dev/Model/MimeHeader.js new file mode 100644 index 000000000..5b4de7f7f --- /dev/null +++ b/dev/Model/MimeHeader.js @@ -0,0 +1,13 @@ +import ko from 'ko'; + +import { AbstractModel } from 'Knoin/AbstractModel'; + +export class MimeHeaderModel extends AbstractModel +{ + constructor() { + super(); + this.name = ''; + this.value = ''; + this.parameters = ko.observableArray(); + } +} diff --git a/dev/Model/MimeHeaderCollection.js b/dev/Model/MimeHeaderCollection.js new file mode 100644 index 000000000..6240e1285 --- /dev/null +++ b/dev/Model/MimeHeaderCollection.js @@ -0,0 +1,32 @@ +import { AbstractCollectionModel } from 'Model/AbstractCollection'; +import { MimeHeaderModel } from 'Model/MimeHeader'; + +'use strict'; + +export class MimeHeaderCollectionModel extends AbstractCollectionModel +{ + /** + * @param {?Array} json + * @returns {MimeHeaderCollectionModel} + */ + static reviveFromJson(items) { + return super.reviveFromJson(items, header => MimeHeaderModel.reviveFromJson(header)); + } + + /** + * @param {string} name + * @returns {?MimeHeader} + */ + getByName(name) + { + name = name.toLowerCase(); + return this.find(header => header.name.toLowerCase() === name); + } + + valueByName(name) + { + const header = this.getByName(name); + return header ? header.value : ''; + } + +} diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php index fc4f9f20f..a81a50a3a 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php @@ -47,9 +47,9 @@ class MailClient } return FetchType::BuildBodyCustomHeaderRequest(array( - MimeHeader::RETURN_PATH, - MimeHeader::RECEIVED, - MimeHeader::MIME_VERSION, +// MimeHeader::RETURN_PATH, +// MimeHeader::RECEIVED, +// MimeHeader::MIME_VERSION, MimeHeader::MESSAGE_ID, MimeHeader::CONTENT_TYPE, MimeHeader::FROM_, @@ -67,7 +67,7 @@ class MailClient MimeHeader::IMPORTANCE, MimeHeader::X_PRIORITY, MimeHeader::X_DRAFT_INFO, - MimeHeader::RETURN_RECEIPT_TO, +// MimeHeader::RETURN_RECEIPT_TO, MimeHeader::DISPOSITION_NOTIFICATION_TO, MimeHeader::X_CONFIRM_READING_TO, MimeHeader::AUTHENTICATION_RESULTS, diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Message.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Message.php index e3a950a5c..6e470f098 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Message.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mail/Message.php @@ -32,7 +32,6 @@ class Message implements \JsonSerializable $sPlain = '', $sHtml = '', $References = '', - $sDeliveryReceipt = '', $ReadReceipt = ''; private ?string @@ -53,8 +52,7 @@ class Message implements \JsonSerializable $iSize = 0, $SpamScore = 0, $iInternalTimeStampInUTC = 0, - $iHeaderTimeStampInUTC = 0, - $iPriority = \MailSo\Mime\Enumerations\MessagePriority::NORMAL; + $iHeaderTimeStampInUTC = 0; private bool $bIsSpam = false; @@ -73,7 +71,6 @@ class Message implements \JsonSerializable $DMARC = [], // $aFlags = [], $aFlagsLowerCase = [], - $UnsubsribeLinks = [], $aThreadUIDs = [], $aThreadUnseenUIDs = []; @@ -93,6 +90,9 @@ class Message implements \JsonSerializable private ?AttachmentCollection $Attachments = null; + private ?\MailSo\Mime\HeaderCollection + $Headers = null; + function __get($k) { return \property_exists($this, $k) ? $this->$k : null; @@ -164,6 +164,8 @@ class Message implements \JsonSerializable $sHeaders = $oFetchResponse->GetHeaderFieldsValue(); $oHeaders = \strlen($sHeaders) ? new \MailSo\Mime\HeaderCollection($sHeaders, false, $sCharset) : null; if ($oHeaders) { + $oMessage->Headers = $oHeaders; + $sContentTypeCharset = $oHeaders->ParameterValue( MimeHeader::CONTENT_TYPE, \MailSo\Mime\Enumerations\Parameter::CHARSET @@ -200,29 +202,8 @@ class Message implements \JsonSerializable $oHeaders->ValueByName(MimeHeader::DATE) ); - // Priority - $sPriority = $oHeaders->ValueByName(MimeHeader::X_MSMAIL_PRIORITY) - ?: $oHeaders->ValueByName(MimeHeader::IMPORTANCE) - ?: $oHeaders->ValueByName(MimeHeader::X_PRIORITY); - if (\strlen($sPriority)) { - switch (\substr(\trim($sPriority), 0, 1)) - { - case 'h': - case '1': - case '2': - $oMessage->iPriority = \MailSo\Mime\Enumerations\MessagePriority::HIGH; - break; - - case 'l': - case '4': - case '5': - $oMessage->iPriority = \MailSo\Mime\Enumerations\MessagePriority::LOW; - break; - } - } - // Delivery Receipt - $oMessage->sDeliveryReceipt = \trim($oHeaders->ValueByName(MimeHeader::RETURN_RECEIPT_TO)); +// $oMessage->sDeliveryReceipt = \trim($oHeaders->ValueByName(MimeHeader::RETURN_RECEIPT_TO)); // Read Receipt $oMessage->ReadReceipt = \trim($oHeaders->ValueByName(MimeHeader::DISPOSITION_NOTIFICATION_TO)); @@ -230,17 +211,6 @@ class Message implements \JsonSerializable $oMessage->ReadReceipt = \trim($oHeaders->ValueByName(MimeHeader::X_CONFIRM_READING_TO)); } - // Unsubscribe links - $UnsubsribeLinks = $oHeaders->ValueByName(MimeHeader::LIST_UNSUBSCRIBE); - if ($UnsubsribeLinks) { - $oMessage->UnsubsribeLinks = \array_map( - function ($link) { - return trim($link, ' <>'); - }, - \explode(',', $UnsubsribeLinks) - ); - } - if ($spam = $oHeaders->ValueByName(MimeHeader::X_SPAMD_RESULT)) { if (\preg_match('/\\[([\\d\\.-]+)\\s*\\/\\s*([\\d\\.]+)\\];/', $spam, $match)) { if ($threshold = \floatval($match[2])) { @@ -554,7 +524,6 @@ class Message implements \JsonSerializable 'sender' => $this->oSender, 'deliveredTo' => $this->oDeliveredTo, - 'priority' => $this->iPriority, 'readReceipt' => $sReadReceipt, 'autocrypt' => $aAutocrypt ?: null, @@ -576,15 +545,14 @@ class Message implements \JsonSerializable // 'keywords' => $keywords, 'size' => $this->iSize, - 'preview' => $this->sPreview + 'preview' => $this->sPreview, + + 'headers' => $this->Headers ); if ($this->DraftInfo) { $result['draftInfo'] = $this->DraftInfo; } - if ($this->UnsubsribeLinks) { - $result['unsubsribeLinks'] = $this->UnsubsribeLinks; - } if ($this->References) { $result['references'] = $this->References; } diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Header.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Header.php index 65f890dbc..eff1a3275 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Header.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Header.php @@ -15,7 +15,7 @@ namespace MailSo\Mime; * @category MailSo * @package Mime */ -class Header +class Header implements \JsonSerializable { private string $sName; @@ -41,7 +41,7 @@ class Header $this->sFullValue = \trim($sValue); $this->sEncodedValueForReparse = ''; - if (\strlen($sEncodedValueForReparse) && $this->IsReparsed()) { + if (\strlen($sEncodedValueForReparse) && ($this->IsEmail() || $this->IsSubject() || $this->IsParameterized())) { $this->sEncodedValueForReparse = \trim($sEncodedValueForReparse); } @@ -88,7 +88,7 @@ class Header public function NameWithDelimitrom() : string { - return $this->Name().': '; + return $this->sName . ': '; } public function Value() : string @@ -103,7 +103,7 @@ class Header public function SetParentCharset(string $sParentCharset) : Header { - if ($this->sParentCharset !== $sParentCharset && $this->IsReparsed() && \strlen($this->sEncodedValueForReparse)) { + if ($this->sParentCharset !== $sParentCharset && \strlen($this->sEncodedValueForReparse)) { $this->initInputData( $this->sName, \trim(\MailSo\Base\Utils::DecodeHeaderValue($this->sEncodedValueForReparse, $sParentCharset)), @@ -132,19 +132,17 @@ class Header if ($this->IsSubject()) { if (!\MailSo\Base\Utils::IsAscii($sResult) && \function_exists('iconv_mime_encode')) { - $aPreferences = array( + return \iconv_mime_encode($this->Name(), $sResult, array( // 'scheme' => \MailSo\Base\Enumerations\Encoding::QUOTED_PRINTABLE_SHORT, 'scheme' => \MailSo\Base\Enumerations\Encoding::BASE64_SHORT, 'input-charset' => \MailSo\Base\Enumerations\Charset::UTF_8, 'output-charset' => \MailSo\Base\Enumerations\Charset::UTF_8, 'line-length' => 74, 'line-break-chars' => "\r\n" - ); - - return \iconv_mime_encode($this->Name(), $sResult, $aPreferences); + )); } } - else if ($this->IsParameterized() && 0 < $this->oParameters->count()) + else if ($this->IsParameterized() && $this->oParameters->count()) { $sResult = $this->sValue.'; '.$this->oParameters->ToString(true); } @@ -157,16 +155,15 @@ class Header } // https://www.rfc-editor.org/rfc/rfc2822#section-2.1.1, avoid folding immediately after the header name - return $this->NameWithDelimitrom() . \wordwrap($sResult, 78 - \strlen($this->NameWithDelimitrom()) - 1, "\r\n "); - + return $this->NameWithDelimitrom() . \wordwrap($sResult, 78 - \strlen($this->NameWithDelimitrom()) - 1, "\r\n "); } - public function IsSubject() : bool + private function IsSubject() : bool { return \strtolower(Enumerations\Header::SUBJECT) === \strtolower($this->Name()); } - public function IsParameterized() : bool + private function IsParameterized() : bool { return \in_array(\strtolower($this->sName), array( \strtolower(Enumerations\Header::CONTENT_TYPE), @@ -174,7 +171,7 @@ class Header )); } - public function IsEmail() : bool + private function IsEmail() : bool { return \in_array(\strtolower($this->sName), array( \strtolower(Enumerations\Header::FROM_), @@ -182,7 +179,7 @@ class Header \strtolower(Enumerations\Header::CC), \strtolower(Enumerations\Header::BCC), \strtolower(Enumerations\Header::REPLY_TO), - \strtolower(Enumerations\Header::RETURN_PATH), +// \strtolower(Enumerations\Header::RETURN_PATH), \strtolower(Enumerations\Header::SENDER) )); } @@ -199,8 +196,22 @@ class Header return $this->Value(); } - public function IsReparsed() : bool + private function IsReparsed() : bool { return $this->IsEmail() || $this->IsSubject() || $this->IsParameterized(); } + + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + $aResult = array( + '@Object' => 'Object/MimeHeader', + 'name' => $this->sName, + 'value' => $this->sValue + ); + if ($this->oParameters->count()) { + $aResult['parameters'] = $this->oParameters; + } + return $aResult; + } } diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php index 4697cfd23..8d698dafd 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php @@ -81,15 +81,10 @@ class HeaderCollection extends \MailSo\Base\Collection ); } - public function ParametersByName(string $sHeaderName) : ?ParameterCollection - { - $oHeader = $this->GetByName($sHeaderName); - return $oHeader ? $oHeader->Parameters() : null; - } - public function ParameterValue(string $sHeaderName, string $sParamName) : string { - $oParameters = $this->ParametersByName($sHeaderName); + $oHeader = $this->GetByName($sHeaderName); + $oParameters = $oHeader ? $oHeader->Parameters() : null; return (null !== $oParameters) ? $oParameters->ParameterValueByName($sParamName) : ''; } @@ -244,4 +239,13 @@ class HeaderCollection extends \MailSo\Base\Collection { return \implode("\r\n", $this->getArrayCopy()); } + + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return array( + '@Object' => 'Collection/MimeHeaderCollection', + '@Collection' => $this->getArrayCopy() + ); + } }