Return all fetched messages headers in JSON.

This is in preperation of BIMI-Selectors #1394
This commit is contained in:
the-djmaze 2024-01-21 23:35:32 +01:00
parent 55a5fb491a
commit 8292c47f7c
7 changed files with 135 additions and 69 deletions

View file

@ -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;
}
}

13
dev/Model/MimeHeader.js Normal file
View file

@ -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();
}
}

View file

@ -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 : '';
}
}

View file

@ -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,

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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()
);
}
}