diff --git a/lib/Compose.php b/lib/Compose.php index 9572d33a7..bafa85307 100644 --- a/lib/Compose.php +++ b/lib/Compose.php @@ -2630,20 +2630,30 @@ public function sendRedirectMessage($to, $log = true) if ($log && ($tmp = $headers['Message-ID'])) { $msg_id = reset($tmp->getIdentificationOb()->ids); - /* Store history information. */ - $injector->getInstance('IMP_Maillog')->log( - new IMP_Maillog_Message($msg_id), - new IMP_Maillog_Log_Redirect([ - 'msgid' => reset($resent_headers->getIdentificationOb()->ids), - 'recipients' => $recipients, - ]) - ); + /* Some inbound messages ship a Message-ID header + * whose value cannot be parsed into an id (missing + * angle brackets, empty header, mailer-daemon + * bounces, malformed Notes/SharePoint output). + * reset() on the resulting empty ids array returns + * false; skip the maillog + sentmail write rather + * than construct log entries that will fault + * downstream. See imp#96. */ + if ($msg_id) { + /* Store history information. */ + $injector->get('IMP_Maillog')->log( + new IMP_Maillog_Message($msg_id), + new IMP_Maillog_Log_Redirect([ + 'msgid' => reset($resent_headers->getIdentificationOb()->ids), + 'recipients' => $recipients, + ]) + ); - $injector->getInstance('IMP_Sentmail')->log( - IMP_Sentmail::REDIRECT, - $msg_id, - $recipients - ); + $injector->get('IMP_Sentmail')->log( + IMP_Sentmail::REDIRECT, + $msg_id, + $recipients + ); + } } $tmp = new stdClass(); diff --git a/lib/Maillog/Message.php b/lib/Maillog/Message.php index 54782fd1c..63f172457 100644 --- a/lib/Maillog/Message.php +++ b/lib/Maillog/Message.php @@ -51,15 +51,28 @@ public function __construct($data) } /** + * Populate the message payload. * + * Accepts either an {@see IMP_Indices} (msgid resolved lazily on + * first read via IMAP fetch) or an already-known Message-ID + * string. Empty/false input is discarded so a getter that finds + * neither payload can return an empty string cleanly rather than + * fault on a null indices reference. See imp#96 — + * Compose::sendRedirectMessage constructs one of these from a + * `reset()` on a possibly-empty ids array, which yields `false`. */ public function add($data) { if ($data instanceof IMP_Indices) { $this->_indices = $data; - } else { - $this->_msgid = strval($data); + } elseif (($stringified = strval($data)) !== '') { + $this->_msgid = $stringified; } + /* else: caller handed us an empty/false value. Neither + * $_indices nor $_msgid is set; the msgid getter returns '' + * and downstream Storage/History::_getUniqueHistoryId's + * empty-string guard turns it into a clean + * RuntimeException. */ } /** @@ -79,7 +92,23 @@ public function __get($name) case 'msgid': if (!$this->_msgid) { - [$mbox, $uid] = $this->indices->getSingle(); + /* Constructed without an IMP_Indices AND without a + * usable Message-ID. Callers that build a message + * with a false/empty/malformed id (see imp#96 — + * Compose::sendRedirectMessage on a message whose + * Message-ID header had no parseable id) would + * otherwise fault here with "Call to a member + * function getSingle() on null". Return an empty + * string; downstream storage layers (notably + * IMP_Maillog_Storage_History::_getUniqueHistoryId) + * already throw a clean RuntimeException on + * empty msgids, which is the intended failure + * mode. */ + if ($this->_indices === null) { + return ''; + } + + [$mbox, $uid] = $this->_indices->getSingle(); $query = new Horde_Imap_Client_Fetch_Query(); $query->envelope();