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 #' . $message_number . ' 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 #' . $message_number . ': processed'); } else { $this->_displayLogMessage('message #' . $message_number . ': 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[^>]*>/is", '', $message_body); $message_body = preg_replace("/]*>[^<]*<\/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 } }