Page MenuHomeIn-Portal Phabricator

mailbox_helper.php
No OneTemporary

File Metadata

Created
Mon, Aug 18, 7:35 AM

mailbox_helper.php

<?php
/**
* @version $Id: mailbox_helper.php 16513 2017-01-20 14:10:53Z alex $
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class MailboxHelper extends kHelper {
var $headers = Array ();
var $parsedMessage = Array ();
/**
* Maximal megabytes of data to process
*
* @var int
*/
var $maxMegabytes = 2;
/**
* Maximal message count to process
*
* @var int
*/
var $maxMessages = 50;
/**
* Reads mailbox and gives messages to processing callback
*
* @param Array $connection_info
* @param Array $verify_callback
* @param Array $process_callback
* @param Array $callback_params
* @param bool $include_attachment_contents
* @return string
*/
function process($connection_info, $verify_callback, $process_callback, $callback_params = Array (), $include_attachment_contents = true)
{
/** @var POP3Helper $pop3_helper */
$pop3_helper = $this->Application->makeClass('POP3Helper', Array ($connection_info));
$connection_status = $pop3_helper->initMailbox();
if (is_string($connection_status)) {
return $connection_status;
}
if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) {
$this->Application->Debugger->appendHTML('Reading MAILBOX: ' . $connection_info['username']);
}
// Figure out if all messages are huge
$only_big_messages = true;
$max_message_size = $this->maxMegabytes * (1024 * 1024);
foreach ($pop3_helper->messageSizes as $message_size) {
if (($message_size <= $max_message_size) && ($max_message_size > 0)) {
$only_big_messages = false;
break;
}
}
$count = $total_size = 0;
foreach ($pop3_helper->messageSizes as $message_number => $message_size) {
// Too many messages?
if (($count++ > $this->maxMessages) && ($this->maxMessages > 0)) {
break;
}
// Message too big?
if (!$only_big_messages && ($message_size > $max_message_size) && ($max_message_size > 0)) {
$this->_displayLogMessage('message <strong>#' . $message_number . '</strong> too big, skipped');
continue;
}
// Processed enough for today?
if (($total_size > $max_message_size) && ($max_message_size > 0)) {
break;
}
$total_size += $message_size;
$pop3_helper->getEmail($message_number, $message_source);
$processed = $this->normalize($message_source, $verify_callback, $process_callback, $callback_params, $include_attachment_contents);
if ($processed) {
// delete message from server immediatly after retrieving & processing
$pop3_helper->deleteEmail($message_number);
$this->_displayLogMessage('message <strong>#' . $message_number . '</strong>: processed');
}
else {
$this->_displayLogMessage('message <strong>#' . $message_number . '</strong>: skipped');
}
}
$pop3_helper->close();
return 'success';
}
/**
* Displays log message
*
* @param string $text
*/
function _displayLogMessage($text)
{
if (defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode()) {
$this->Application->Debugger->appendHTML($text);
}
}
/**
* Takes an RFC822 formatted date, returns a unix timestamp (allowing for zone)
*
* @param string $rfcdate
* @return int
*/
function rfcToTime($rfcdate)
{
$date = strtotime($rfcdate);
if ($date == -1) {
return false;
}
return $date;
}
/**
* Gets recipients from all possible headers
*
* @return string
*/
function getRecipients()
{
$ret = '';
// headers that could contain recipients
$recipient_headers = Array (
'to', 'cc', 'envelope-to', 'resent-to', 'delivered-to',
'apparently-to', 'envelope-to', 'x-envelope-to', 'received',
);
foreach ($recipient_headers as $recipient_header) {
if (!array_key_exists($recipient_header, $this->headers)) {
continue;
}
if (!is_array($this->headers["$recipient_header"])) {
$ret .= ' ' . $this->headers["$recipient_header"];
} else {
$ret .= ' ' . implode(' ', $this->headers["$recipient_header"]);
}
}
return $ret;
}
/**
* "Flattens" the multi-demensinal headers array into a single dimension one
*
* @param Array $input
* @param string $add
* @return Array
*/
function flattenHeadersArray($input, $add = '')
{
$output = Array ();
foreach ($input as $key => $value) {
if (!empty($add)) {
$newkey = ucfirst( strtolower($add) );
} elseif (is_numeric($key)) {
$newkey = '';
} else {
$newkey = ucfirst( strtolower($key) );
}
if (is_array($value)) {
$output = array_merge($output, $this->flattenHeadersArray($value, $newkey));
} else {
$output[] = (!empty($newkey) ? $newkey . ': ' : '') . $value;
}
}
return $output;
}
/**
* Processes given message using given callbacks
*
* @param string $message
* @param Array $verify_callback
* @param Array $process_callback
* @param Array $callback_params
* @param bool $include_attachment_contents
* @return bool
* @access protected
*/
protected function normalize($message, $verify_callback, $process_callback, $callback_params, $include_attachment_contents = true)
{
// Decode message
$this->decodeMime($message, $include_attachment_contents);
// Init vars; $good will hold all the correct infomation from now on
$good = Array ();
// trim() some stuff now instead of later
$this->headers['from'] = trim($this->headers['from']);
$this->headers['to'] = trim($this->headers['to']);
$this->headers['cc'] = array_key_exists('cc', $this->headers) ? trim($this->headers['cc']) : '';
$this->headers['x-forward-to'] = array_key_exists('x-forward-to', $this->headers) ? $this->headers['x-forward-to'] : '';
$this->headers['subject'] = trim($this->headers['subject']);
$this->headers['received'] = is_array($this->headers['received']) ? $this->headers['received'] : Array ($this->headers['received']);
if (array_key_exists('return-path', $this->headers) && is_array($this->headers['return-path'])) {
$this->headers['return-path'] = implode(' ', $this->flattenHeadersArray($this->headers['return-path']));
}
// Create our own message-ID if it's missing
$message_id = array_key_exists('message-id', $this->headers) ? trim($this->headers['message-id']) : '';
$good['emailid'] = $message_id ? $message_id : md5($message) . "@in-portal";
// Stops us looping in stupid conversations with other mail software
if (isset($this->headers['x-loop-detect']) && $this->headers['x-loop-detect'] > 2) {
return false;
}
/** @var kEmailSendingHelper $esender */
$esender = $this->Application->recallObject('EmailSender');
// Get the return address
$return_path = '';
if (array_key_exists('return-path', $this->headers)) {
$return_path = $esender->ExtractRecipientEmail($this->headers['return-path']);
}
if (!$return_path) {
if (array_key_exists('reply-to', $this->headers)) {
$return_path = $esender->ExtractRecipientEmail( $this->headers['reply-to'] );
}
else {
$return_path = $esender->ExtractRecipientEmail( $this->headers['from'] );
}
}
// Get the sender's name & email
$good['fromemail'] = $esender->ExtractRecipientEmail($this->headers['from']);
$good['fromname'] = $esender->ExtractRecipientName($this->headers['from'], $good['fromemail']);
// Get the list of recipients.
if ( !call_user_func($verify_callback, $callback_params) ) {
// Error: mail is probably spam.
return false;
}
// Handle the subject
$good['subject'] = $this->headers['subject'];
// Priorities rock
$good['priority'] = array_key_exists('x-priority', $this->headers) ? (int)$this->headers['x-priority'] : 0;
switch ($good['priority']) {
case 1: case 5: break;
default:
$good['priority'] = 3;
}
// If we have attachments it's about time we tell the user about it
if (array_key_exists('attachments', $this->parsedMessage) && is_array($this->parsedMessage['attachments'])) {
$good['attach'] = count( $this->parsedMessage['attachments'] );
} else {
$good['attach'] = 0;
}
// prepare message text (for replies, etc)
if (isset($this->parsedMessage['text'][0]) && trim($this->parsedMessage['text'][0]['body']) != '') {
$message_body = trim($this->parsedMessage['text'][0]['body']);
$message_type = 'text';
} elseif (isset($this->parsedMessage['html']) && trim($this->parsedMessage['html'][0]['body']) != '') {
$message_body = trim($this->parsedMessage['html'][0]['body']);
$message_type = 'html';
} else {
$message_body = '[no message]';
$message_type = 'text';
}
// remove scripts
$message_body = preg_replace("/<script[^>]*>[^<]+<\/script[^>]*>/is", '', $message_body);
$message_body = preg_replace("/<iframe[^>]*>[^<]*<\/iframe[^>]*>/is", '', $message_body);
if ($message_type == 'html') {
$message_body = $esender->ConvertToText($message_body);
}
/** @var MimeDecodeHelper $mime_decode_helper */
$mime_decode_helper = $this->Application->recallObject('MimeDecodeHelper');
// convert to site encoding
$message_charset = $this->parsedMessage[$message_type][0]['charset'];
if ($message_charset) {
$good['message'] = $mime_decode_helper->convertEncoding($message_charset, $message_body);
}
if (array_key_exists('delivery-date', $this->headers)) {
// We found the Delivery-Date header (and it's not too far in the future)
$dateline = $this->rfcToTime($this->headers['delivery-date']);
if ($dateline > TIMENOW + 86400) {
unset($dateline);
}
}
// We found the latest date from the received headers
$received_timestamp = $this->headers['received'][0];
$dateline = $this->rfcToTime(trim( substr($received_timestamp, strrpos($received_timestamp, ';') + 1) ));
if ($dateline == $this->rfcToTime(0)) {
unset($dateline);
}
if (!isset($dateline)) {
$dateline = TIMENOW;
}
// save collected data to database
$fields_hash = Array (
'DeliveryDate' => $dateline, // date, when SMTP server received the message
'ReceivedDate' => TIMENOW, // date, when message was retrieved from POP3 server
'CreatedOn' => $this->rfcToTime($this->headers['date']), // date, when created on sender's computer
'ReturnPath' => $return_path,
'FromEmail' => $good['fromemail'],
'FromName' => $good['fromname'],
'To' => $this->headers['to'],
'Subject' => $good['subject'],
'Message' => $good['message'],
'MessageType' => $message_type,
'AttachmentCount' => $good['attach'],
'MessageId' => $good['emailid'],
'Source' => $message,
'Priority' => $good['priority'],
'Size' => strlen($message),
);
return call_user_func($process_callback, $callback_params, $fields_hash);
}
/**
* Function that decodes the MIME message and creates the $this->headers and $this->parsedMessage data arrays
*
* @param string $message
* @param bool $include_attachments
*
*/
function decodeMime($message, $include_attachments = true)
{
$message = preg_replace("/\r?\n/", "\r\n", trim($message));
/** @var MimeDecodeHelper $mime_decode_helper */
$mime_decode_helper = $this->Application->recallObject('MimeDecodeHelper');
// 1. separate headers from bodies
$mime_decode_helper->InitHelper($message);
$decoded_message = $mime_decode_helper->decode(true, true, true);
// 2. extract attachments
$this->parsedMessage = Array (); // ! reset value
$this->parseOutput($decoded_message, $this->parsedMessage, $include_attachments);
// 3. add "other" attachments (text part, that is not maked as attachment)
if (array_key_exists('text', $this->parsedMessage) && count($this->parsedMessage['text']) > 1) {
for ($attach = 1; $attach < count($this->parsedMessage['text']); $attach++) {
$this->parsedMessage['attachments'][] = Array (
'data' => $this->parsedMessage['text']["$attach"]['body'],
);
}
}
$this->headers = $this->parsedMessage['headers']; // ! reset value
if (empty($decoded_message->ctype_parameters['boundary'])) {
// when no boundary, then assume all message is it's text
$this->parsedMessage['text'][0]['body'] = $decoded_message->body;
}
}
/**
* Returns content-id's from inline attachments in message
*
* @return Array
*/
function getContentIds()
{
$cids = Array();
if (array_key_exists('attachments', $this->parsedMessage) && is_array($this->parsedMessage['attachments'])) {
foreach ($this->parsedMessage['attachments'] as $attachnum => $attachment) {
if (!isset($attachment['headers']['content-id'])) {
continue;
}
$cid = $attachment['headers']['content-id'];
if (substr($cid, 0, 1) == '<' && substr($cid, -1) == '>') {
$cid = substr($cid, 1, -1);
}
$cids["$attachnum"] = $cid;
}
}
return $cids;
}
/**
* Get more detailed information about attachments
*
* @param stdClass $decoded parsed headers & body as object
* @param Array $parts parsed parts
* @param bool $include_attachments
*/
function parseOutput(&$decoded, &$parts, $include_attachments = true)
{
$ctype = strtolower($decoded->ctype_primary . '/' . $decoded->ctype_secondary);
// don't parse attached messages recursevely
if (!empty($decoded->parts) && $ctype != 'message/rfc822') {
for ($i = 0; $i < count($decoded->parts); $i++) {
$this->parseOutput($decoded->parts["$i"], $parts, $include_attachments);
}
} else/*if (!empty($decoded->disposition) && $decoded->disposition != 'inline' or 1)*/ {
switch ($ctype) {
case 'text/plain':
case 'text/html':
if (!empty($decoded->disposition) && ($decoded->disposition == 'attachment')) {
$parts['attachments'][] = Array (
'data' => $include_attachments ? $decoded->body : '',
'filename' => array_key_exists('filename', $decoded->d_parameters) ? $decoded->d_parameters['filename'] : '', // from content-disposition
'filename2' => $decoded->ctype_parameters['name'], // from content-type
'type' => $decoded->ctype_primary, // "text"
'encoding' => $decoded->headers['content-transfer-encoding']
);
} else {
$body_type = $decoded->ctype_secondary == 'plain' ? 'text' : 'html';
$parts[$body_type][] = Array (
'content-type' => $ctype,
'charset' => array_key_exists('charset', $decoded->ctype_parameters) ? $decoded->ctype_parameters['charset'] : 'ISO-8859-1',
'body' => $decoded->body
);
}
break;
case 'message/rfc822':
// another e-mail as attachment
$parts['attachments'][] = Array (
'data' => $include_attachments ? $decoded->body : '',
'filename' => array_key_exists('filename', $decoded->d_parameters) ? $decoded->d_parameters['filename'] : '',
'filename2' => array_key_exists('name', $decoded->ctype_parameters) ? $decoded->ctype_parameters['name'] : $decoded->parts[0]->headers['subject'],
'type' => $decoded->ctype_primary, // "message"
'headers' => $decoded->headers // individual copy of headers with each attachment
);
break;
default:
if (!stristr($decoded->headers['content-type'], 'signature')) {
$parts['attachments'][] = Array (
'data' => $include_attachments ? $decoded->body : '',
'filename' => array_key_exists('filename', $decoded->d_parameters) ? $decoded->d_parameters['filename'] : '', // from content-disposition
'filename2' => $decoded->ctype_parameters['name'], // from content-type
'type' => $decoded->ctype_primary,
'headers' => $decoded->headers // individual copy of headers with each attachment
);
}
}
}
$parts['headers'] = $decoded->headers; // headers of next parts overwrite previous part headers
}
}

Event Timeline