Skip to content

Commit

Permalink
Return all fetched messages headers in JSON.
Browse files Browse the repository at this point in the history
This is in preperation of BIMI-Selectors #1394
  • Loading branch information
the-djmaze committed Jan 21, 2024
1 parent 55a5fb4 commit 8292c47
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 69 deletions.
38 changes: 38 additions & 0 deletions dev/Model/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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: () =>
Expand Down Expand Up @@ -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;
}
}
Expand Down
13 changes: 13 additions & 0 deletions dev/Model/MimeHeader.js
Original file line number Diff line number Diff line change
@@ -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();
}
}
32 changes: 32 additions & 0 deletions dev/Model/MimeHeaderCollection.js
Original file line number Diff line number Diff line change
@@ -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 : '';
}

}
8 changes: 4 additions & 4 deletions snappymail/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ private function getEnvelopeOrHeadersRequestString() : string
}

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_,
Expand All @@ -67,7 +67,7 @@ private function getEnvelopeOrHeadersRequestString() : string
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,
Expand Down
52 changes: 10 additions & 42 deletions snappymail/v/0.0.0/app/libraries/MailSo/Mail/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ class Message implements \JsonSerializable
$sPlain = '',
$sHtml = '',
$References = '',
$sDeliveryReceipt = '',
$ReadReceipt = '';

private ?string
Expand All @@ -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;
Expand All @@ -73,7 +71,6 @@ class Message implements \JsonSerializable
$DMARC = [],
// $aFlags = [],
$aFlagsLowerCase = [],
$UnsubsribeLinks = [],
$aThreadUIDs = [],
$aThreadUnseenUIDs = [];

Expand All @@ -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;
Expand Down Expand Up @@ -164,6 +164,8 @@ public static function fromFetchResponse(string $sFolder, \MailSo\Imap\FetchResp
$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
Expand Down Expand Up @@ -200,47 +202,15 @@ public static function fromFetchResponse(string $sFolder, \MailSo\Imap\FetchResp
$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));
if (empty($oMessage->ReadReceipt)) {
$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])) {
Expand Down Expand Up @@ -554,7 +524,6 @@ public function jsonSerialize()
'sender' => $this->oSender,
'deliveredTo' => $this->oDeliveredTo,

'priority' => $this->iPriority,
'readReceipt' => $sReadReceipt,
'autocrypt' => $aAutocrypt ?: null,

Expand All @@ -576,15 +545,14 @@ public function jsonSerialize()
// '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;
}
Expand Down
43 changes: 27 additions & 16 deletions snappymail/v/0.0.0/app/libraries/MailSo/Mime/Header.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* @category MailSo
* @package Mime
*/
class Header
class Header implements \JsonSerializable
{
private string $sName;

Expand All @@ -41,7 +41,7 @@ private function initInputData(string $sName, string $sValue, string $sEncodedVa
$this->sFullValue = \trim($sValue);
$this->sEncodedValueForReparse = '';

if (\strlen($sEncodedValueForReparse) && $this->IsReparsed()) {
if (\strlen($sEncodedValueForReparse) && ($this->IsEmail() || $this->IsSubject() || $this->IsParameterized())) {
$this->sEncodedValueForReparse = \trim($sEncodedValueForReparse);
}

Expand Down Expand Up @@ -88,7 +88,7 @@ public function Name() : string

public function NameWithDelimitrom() : string
{
return $this->Name().': ';
return $this->sName . ': ';
}

public function Value() : string
Expand All @@ -103,7 +103,7 @@ public function FullValue() : string

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)),
Expand Down Expand Up @@ -132,19 +132,17 @@ public function __toString() : string

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);
}
Expand All @@ -157,32 +155,31 @@ public function __toString() : string
}

// 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),
\strtolower(Enumerations\Header::CONTENT_DISPOSITION)
));
}

public function IsEmail() : bool
private function IsEmail() : bool
{
return \in_array(\strtolower($this->sName), array(
\strtolower(Enumerations\Header::FROM_),
\strtolower(Enumerations\Header::TO_),
\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)
));
}
Expand All @@ -199,8 +196,22 @@ public function ValueWithCharsetAutoDetect() : string
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;
}
}
Loading

0 comments on commit 8292c47

Please sign in to comment.