Skip to content

Commit

Permalink
MIME conversion with Swiftmailer - exception swallowing when encounte…
Browse files Browse the repository at this point in the history
…ring apparent file corruption

* catching the exception when adding invalid/empty headers and body
* conditional exceptions
* Revert "conditional exceptions"

---------

Co-authored-by: Richard Toth <richard.toth@fortix.com.au>
  • Loading branch information
adrichel and Richard Toth authored Jun 12, 2023
1 parent 36f9676 commit c18c878
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 35 deletions.
17 changes: 14 additions & 3 deletions src/MAPI/Mime/Swiftmailer/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,19 @@

class Factory implements ConversionFactory
{

protected $muteConversionExceptions;

public function __construct(bool $muteConversionExceptions = false)
{
$this->muteConversionExceptions = $muteConversionExceptions;
}

public function parseMessage(Element $root)
{
return new \Hfig\MAPI\Mime\Swiftmailer\Message($root);
{
$message = new \Hfig\MAPI\Mime\Swiftmailer\Message($root);
$message->setMuteConversionExceptions($this->muteConversionExceptions);

return $message;
}
}
}
147 changes: 115 additions & 32 deletions src/MAPI/Mime/Swiftmailer/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

class Message extends BaseMessage implements MimeConvertible
{
protected $conversionExceptionsList = [];
protected $muteConversionExceptions = false;

public static function wrap(BaseMessage $message)
{
Expand All @@ -21,43 +23,92 @@ public static function wrap(BaseMessage $message)

return new self($message->obj, $message->parent);
}

public function toMime()
{
DependencySet::register();

$message = new \Swift_Message();
$message->setEncoder(new \Swift_Mime_ContentEncoder_RawContentEncoder());


// get headers
$headers = $this->translatePropertyHeaders();

// add them to the message
$add = [$message, 'setTo']; // function
$this->addRecipientHeaders('To', $headers, $add);
try {
$this->addRecipientHeaders('To', $headers, $add);
}
catch (\Swift_RfcComplianceException $e) {
if (!$this->muteConversionExceptions) {
throw $e;
}
$this->conversionExceptionsList[] = $e;
}
$headers->unset('To');

$add = [$message, 'setCc']; // function
$this->addRecipientHeaders('Cc', $headers, $add);
try {
$this->addRecipientHeaders('Cc', $headers, $add);
}
catch (\Swift_RfcComplianceException $e) {
if (!$this->muteConversionExceptions) {
throw $e;
}
$this->conversionExceptionsList[] = $e;
}
$headers->unset('Cc');

$add = [$message, 'setBcc']; // function
$this->addRecipientHeaders('Bcc', $headers, $add);
try {
$this->addRecipientHeaders('Bcc', $headers, $add);
}
catch (\Swift_RfcComplianceException $e) {
if (!$this->muteConversionExceptions) {
throw $e;
}
$this->conversionExceptionsList[] = $e;}
$headers->unset('Bcc');

$add = [$message, 'setFrom']; // function
$this->addRecipientHeaders('From', $headers, $add);
try {
$this->addRecipientHeaders('From', $headers, $add);
}
catch (\Swift_RfcComplianceException $e) {
if (!$this->muteConversionExceptions) {
throw $e;
}
$this->conversionExceptionsList[] = $e;
}
$headers->unset('From');


$message->setId(trim($headers->getValue('Message-ID'), '<>'));
$message->setDate(new \DateTime($headers->getValue('Date')));
try {
$message->setId(trim($headers->getValue('Message-ID'), '<>'));
}
catch (\Swift_RfcComplianceException $e) {
if (!$this->muteConversionExceptions) {
throw $e;
}
$this->conversionExceptionsList[] = $e;
}

try {
$message->setDate(new \DateTime($headers->getValue('Date')));
}
catch (\Exception $e) { // the \DateTime can throw \Exception
if (!$this->muteConversionExceptions) {
throw $e;
}
$this->conversionExceptionsList[] = $e;
}

if ($boundary = $this->getMimeBoundary($headers)) {
$message->setBoundary($boundary);
}


$headers->unset('Message-ID');
$headers->unset('Date');
$headers->unset('Mime-Version');
Expand All @@ -72,21 +123,32 @@ public function toMime()
$bodyBoundary = '';
if ($boundary) {
if (preg_match('~^_(\d\d\d)_([^_]+)_~', $boundary, $matches)) {
$bodyBoundary = sprintf('_%03d_%s_', (int)$matches[1]+1, $matches[2]);
$bodyBoundary = sprintf('_%03d_%s_', (int)$matches[1]+1, $matches[2]);
}
}
}
try {
$html = $this->getBodyHTML();
if ($html) {
if ($html) {
$hasHtml = true;
}
}
catch (\Exception $e) {
// ignore invalid HTML body
catch (\Exception $e) { // getBodyHTML() can throw \Exception
if (!$this->muteConversionExceptions) {
throw $e;
}
$this->conversionExceptionsList[] = $e;
}

if (!$hasHtml) {
$message->setBody($this->getBody(), 'text/plain');
try {
$message->setBody($this->getBody(), 'text/plain');
}
catch (\Exception $e) { // getBody() can throw \Exception
if (!$this->muteConversionExceptions) {
throw $e;
}
$this->conversionExceptionsList[] = $e;
}
}
else {
// build multi-part
Expand All @@ -97,7 +159,15 @@ public function toMime()
if ($bodyBoundary) {
$multipart->setBoundary($bodyBoundary);
}
$multipart->setBody($this->getBody(), 'text/plain');
try {
$multipart->setBody($this->getBody(), 'text/plain');
}
catch (\Exception $e) { // getBody() can throw \Exception
if (!$this->muteConversionExceptions) {
throw $e;
}
$this->conversionExceptionsList[] = $e;
}

$part = new \Swift_MimePart($html, 'text/html', null);
$part->setEncoder($message->getEncoder());
Expand All @@ -106,7 +176,7 @@ public function toMime()
$message->attach($multipart);
$multipart->setChildren(array_merge($multipart->getChildren(), [$part]));
}


// attachments
foreach ($this->getAttachments() as $a) {
Expand All @@ -121,7 +191,7 @@ public function toMime()

public function toMimeString(): string
{
return (string)$this->toMime();
return (string) $this->toMime();
}

public function copyMimeToStream($stream)
Expand All @@ -130,6 +200,11 @@ public function copyMimeToStream($stream)
fwrite($stream, $this->toMimeString());
}

public function setMuteConversionExceptions(bool $muteConversionExceptions)
{
$this->muteConversionExceptions = $muteConversionExceptions;
}

protected function addRecipientHeaders($field, HeaderCollection $headers, callable $add)
{
$recipient = $headers->getValue($field);
Expand All @@ -141,7 +216,7 @@ protected function addRecipientHeaders($field, HeaderCollection $headers, callab
if (!is_array($recipient)) {
$recipient = [$recipient];
}


$map = [];
foreach ($recipient as $r) {
Expand All @@ -165,13 +240,13 @@ protected function addPlainHeaders(HeaderCollection $headers, callable $add)
$header = $ivalue->rawkey;
$value = $ivalue->value;
$add($header, $value);
}
}
}
else {
$header = $value->rawkey;
$value = $value->value;
$add($header, $value);
}
}
}
}

Expand All @@ -197,13 +272,13 @@ protected function translatePropertyHeaders()
}

foreach ($transport as $header) {
$rawHeaders->add($header);
$rawHeaders->add($header);
}



// sender
$senderType = $this->properties['sender_addrtype'];
// sender
$senderType = $this->properties['sender_addrtype'];
if ($senderType == 'SMTP') {
$rawHeaders->set('From', $this->getSender());
}
Expand Down Expand Up @@ -239,22 +314,22 @@ protected function translatePropertyHeaders()
$map = [
['internet_message_id', 'Message-ID'],
['in_reply_to_id', 'In-Reply-To'],

['importance', 'Importance', function($val) { return ($val == '1') ? null : $val; }],
['priority', 'Priority', function($val) { return ($val == '1') ? null : $val; }],
['sensitivity', 'Sensitivity', function($val) { return ($val == '0') ? null : $val; }],

['conversation_topic', 'Thread-Topic'],

//# not sure of the distinction here
//# :originator_delivery_report_requested ??
['read_receipt_requested', 'Disposition-Notification-To', function($val) use ($rawHeaders) {
['read_receipt_requested', 'Disposition-Notification-To', function($val) use ($rawHeaders) {
$from = $rawHeaders->getValue('From');

if (preg_match('/^((?:"[^"]*")|.+) (<.+>)$/', $from, $matches)) {
$from = trim($matches[2], '<>');
}
return $from;
return $from;
}]
];
foreach ($map as $do) {
Expand All @@ -271,7 +346,7 @@ protected function translatePropertyHeaders()

}

protected function getMimeBoundary(HeaderCollection $headers)
protected function getMimeBoundary(HeaderCollection $headers)
{
// firstly - use the value in the headers
if ($type = $headers->getValue('Content-Type')) {
Expand All @@ -283,12 +358,20 @@ protected function getMimeBoundary(HeaderCollection $headers)
// if never sent via SMTP then it has to be synthesised
// this is done using the message id
if ($mid = $headers->getValue('Message-ID')) {
$recount = 0;
$recount = 0;
$mid = preg_replace('~[^a-zA-z0-9\'()+_,-.\/:=? ]~', '', $mid, -1, $recount);
$mid = substr($mid, 0, 55);
return sprintf('_%03d_%s_', $recount, $mid);
}
return '';
}

}
/**
* Returns the list of conversion exceptions.
*
* @return array
*/
public function getConversionExceptionsList() : array {
return $this->conversionExceptionsList;
}
}

0 comments on commit c18c878

Please sign in to comment.