/inc/libs/clearbricks/mail.mime/class.mime.message.php
PHP | 467 lines | 396 code | 45 blank | 26 comment | 37 complexity | c3a41bf94dc11a1387a8f2d606111e64 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, Apache-2.0
- <?php
- # ***** BEGIN LICENSE BLOCK *****
- # This file is part of Clearbricks.
- # Copyright (c) 2003-2011 Olivier Meunier & Association Dotclear
- # All rights reserved.
- #
- # Clearbricks is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # Clearbricks is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with Clearbricks; if not, write to the Free Software
- # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- #
- # ***** END LICENSE BLOCK *****
- class mimeMessage
- {
- protected $from_email = null;
- protected $from_name = null;
- protected $ctype_primary = null;
- protected $ctype_secondary = null;
- protected $ctype_parameters = array();
- protected $d_parameters = array();
- protected $disposition = null;
- protected $headers = array();
- protected $body = null;
- protected $parts = array();
-
- public function __construct($message)
- {
- list($header,$body) = $this->splitBodyHeader($message);
- $this->decode($header,$body);
- }
-
- public function getMainBody()
- {
- # Look for the main body part (text/plain or text/html)
- if ($this->body && $this->ctype_primary == 'text' && $this->ctype_secondary == 'plain') {
- return $this->body;
- }
-
- foreach ($this->parts as $part)
- {
- if (($body = $part->getBody()) !== null) {
- return $body;
- }
- }
-
- return null;
- }
-
- public function getBody()
- {
- return $this->body;
- }
-
- public function getHeaders()
- {
- return $this->headers;
- }
-
- public function getHeader($hdr,$only_last=false)
- {
- $hdr = strtolower($hdr);
-
- if (!isset($this->headers[$hdr])) {
- return null;
- }
-
- if ($only_last && is_array($this->headers[$hdr])) {
- $r = $this->headers[$hdr];
- return array_pop($r);
- }
-
- return $this->headers[$hdr];
- }
-
- public function getFrom()
- {
- return array($this->from_email,$this->from_name);
- }
-
- public function getAllFiles()
- {
- $parts = array();
- foreach ($this->parts as $part)
- {
- $body = $part->getBody();
- $filename = $part->getFileName();
- if ($body && $filename)
- {
- $parts[] = array(
- 'filename' => $filename,
- 'content' => $part->getBody()
- );
- }
- else
- {
- $parts = array_merge($parts,$part->getAllFiles());
- }
- }
-
- return $parts;
- }
-
- public function getFileName()
- {
- if (isset($this->ctype_parameters['name'])) {
- return $this->ctype_parameters['name'];
- }
-
- if (isset($this->d_parameters['filename'])) {
- return $this->d_parameters['filename'];
- }
-
- return null;
- }
-
- protected function decode($headers,$body,$default_ctype='text/plain')
- {
- $headers = $this->parseHeaders($headers);
-
- foreach ($headers as $v)
- {
- if (isset($this->headers[strtolower($v['name'])]) &&
- !is_array($this->headers[strtolower($v['name'])]))
- {
- $this->headers[strtolower($v['name'])] = array($this->headers[strtolower($v['name'])]);
- $this->headers[strtolower($v['name'])][] = $v['value'];
- }
- elseif (isset($this->headers[strtolower($v['name'])]))
- {
- $this->headers[strtolower($v['name'])][] = $v['value'];
- }
- else
- {
- $this->headers[strtolower($v['name'])] = $v['value'];
- }
- }
-
- foreach ($headers as $k => $v)
- {
- $headers[$k]['name'] = strtolower($headers[$k]['name']);
- switch ($headers[$k]['name'])
- {
- case 'from':
- list($this->from_name,$this->from_email) = $this->decodeSender($headers[$k]['value']);
- break;
-
- case 'content-type':
- $content_type = $this->parseHeaderValue($headers[$k]['value']);
-
- if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
- $this->ctype_primary = strtolower($regs[1]);
- $this->ctype_secondary = strtolower($regs[2]);
- }
-
- if (isset($content_type['other']))
- {
- while (list($p_name, $p_value) = each($content_type['other'])) {
- $this->ctype_parameters[$p_name] = $p_value;
- }
- }
- break;
-
- case 'content-disposition':
- $content_disposition = $this->parseHeaderValue($headers[$k]['value']);
- $this->disposition = $content_disposition['value'];
- if (isset($content_disposition['other'])) {
- while (list($p_name, $p_value) = each($content_disposition['other'])) {
- $this->d_parameters[$p_name] = $p_value;
- }
- }
- break;
-
- case 'content-transfer-encoding':
- $content_transfer_encoding = $this->parseHeaderValue($headers[$k]['value']);
- break;
- }
- }
-
- if (isset($content_type))
- {
- switch (strtolower($content_type['value']))
- {
- case 'text/plain':
- case 'text/html':
- $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
- $charset = isset($this->ctype_parameters['charset']) ? $this->ctype_parameters['charset'] : null;
- $this->body = $this->decodeBody($body,$encoding);
- $this->body = text::cleanUTF8(@text::toUTF8($this->body,$charset));
- break;
-
- case 'multipart/parallel':
- case 'multipart/appledouble': // Appledouble mail
- case 'multipart/report': // RFC1892
- case 'multipart/signed': // PGP
- case 'multipart/digest':
- case 'multipart/alternative':
- case 'multipart/related':
- case 'multipart/mixed':
- if (!isset($content_type['other']['boundary'])) {
- throw new Exception('No boundary found');
- }
-
- $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';
-
- $parts = $this->boundarySplit($body,$content_type['other']['boundary']);
- for ($i = 0; $i < count($parts); $i++)
- {
- $this->parts[] = new self($parts[$i]);
- }
- break;
-
- case 'message/rfc822':
- $this->parts[] = new self($body);
- break;
-
- default:
- if(!isset($content_transfer_encoding['value'])) {
- $content_transfer_encoding['value'] = '7bit';
- }
- $this->body = $this->decodeBody($body, $content_transfer_encoding['value']);
- break;
- }
- }
- else
- {
- $ctype = explode('/', $default_ctype);
- $this->ctype_primary = $ctype[0];
- $this->ctype_secondary = $ctype[1];
- $this->body = $this->decodeBody($body,'7bit');
- $this->body = text::cleanUTF8(@text::toUTF8($this->body));
- }
- }
-
- protected function splitBodyHeader($input)
- {
- if (preg_match('/^(.*?)\r?\n\r?\n(.*)/s', $input, $match)) {
- return array($match[1], $match[2]);
- } else { # No body found
- return array($input,'');
- }
- }
-
- protected function parseHeaders($input)
- {
- if (!$input) {
- return array();
- }
-
- # Unfold the input
- $input = preg_replace("/\r?\n/", "\r\n", $input);
- $input = preg_replace("/\r\n(\t| )+/", ' ', $input);
- $headers = explode("\r\n", trim($input));
-
- $res = array();
-
- # Remove first From line if exists
- if (strpos($headers[0],'From ') === 0) {
- array_shift($headers);
- }
-
- foreach ($headers as $value)
- {
- $hdr_name = substr($value, 0, $pos = strpos($value, ':'));
-
- $hdr_value = substr($value, $pos+1);
-
- if($hdr_value[0] == ' ') {
- $hdr_value = substr($hdr_value, 1);
- }
-
- $res[] = array(
- 'name' => $hdr_name,
- 'value' => $this->decodeHeader($hdr_value)
- );
- }
-
- return $res;
- }
-
- protected function parseHeaderValue($input)
- {
- if (($pos = strpos($input, ';')) !== false)
- {
- $return['value'] = trim(substr($input, 0, $pos));
- $input = trim(substr($input, $pos+1));
-
- if (strlen($input) > 0)
- {
- # This splits on a semi-colon, if there's no preceeding backslash
- # Now works with quoted values; had to glue the \; breaks in PHP
- # the regex is already bordering on incomprehensible
- $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/';
- preg_match_all($splitRegex, $input, $matches);
- $parameters = array();
- for ($i=0; $i<count($matches[0]); $i++)
- {
- $param = $matches[0][$i];
- while (substr($param, -2) == '\;') {
- $param .= $matches[0][++$i];
- }
- $parameters[] = $param;
- }
-
- for ($i = 0; $i < count($parameters); $i++)
- {
- $param_name = trim(substr($parameters[$i], 0, $pos = strpos($parameters[$i], '=')), "'\";\t\\ ");
- $param_value = trim(str_replace('\;', ';', substr($parameters[$i], $pos + 1)), "'\";\t\\ ");
- if ($param_value[0] == '"') {
- $param_value = substr($param_value, 1, -1);
- }
-
- if (preg_match('/\*$/',$param_name)) {
- $return['other'][strtolower(substr($param_name,0,-1))] = $this->parserHeaderSpecialValue($param_value);
- } else {
- $return['other'][strtolower($param_name)] = $param_value;
- }
- }
- }
- }
- else
- {
- $return['value'] = trim($input);
- }
-
- return $return;
- }
-
- protected function parserHeaderSpecialValue($value)
- {
- if (strpos($value,"''") === false) {
- return $value;
- }
-
- list($charset,$value) = explode("''",$value);
- return @text::toUTF8(rawurldecode($value),$charset);
- }
-
- protected function boundarySplit($input, $boundary)
- {
- $parts = array();
-
- $bs_possible = substr($boundary, 2, -2);
- $bs_check = '\"' . $bs_possible . '\"';
-
- if ($boundary == $bs_check) {
- $boundary = $bs_possible;
- }
-
- $tmp = explode('--' . $boundary, $input);
-
- for ($i = 1; $i < count($tmp) - 1; $i++) {
- $parts[] = $tmp[$i];
- }
-
- return $parts;
- }
-
- protected function decodeHeader($input)
- {
- # Remove white space between encoded-words
- $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
-
- # Non encoded
- if (!preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i',$input)) {
- return @text::toUTF8($input);
- }
-
- # For each encoded-word...
- while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches))
- {
- $encoded = $matches[1];
- $charset = $matches[2];
- $encoding = $matches[3];
- $text = $matches[4];
-
- switch (strtolower($encoding))
- {
- case 'b':
- $text = base64_decode($text);
- break;
-
- case 'q':
- $text = str_replace('_', ' ', $text);
- preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
- foreach($matches[1] as $value) {
- $text = str_replace('='.$value, chr(hexdec($value)), $text);
- }
- break;
- }
- $text = @text::toUTF8($text,$charset);
- $input = str_replace($encoded, $text, $input);
- }
-
- return text::cleanUTF8($input);
- }
-
- protected function decodeSender($sender)
- {
- if (preg_match('/([\'|"])?(.*)(?(1)[\'|"])\s+<([\w\-=!#$%^*\'+\\.={}|?~]+@[\w\-=!#$%^*\'+\\.={}|?~]+[\w\-=!#$%^*\'+\\={}|?~])>/', $sender, $matches)) {
- # Match address in the form: Name <email@host>
- $result[0] = $matches[2];
- $result[1] = $matches[sizeof($matches) - 1];
- } elseif (preg_match('/([\w\-=!#$%^*\'+\\.={}|?~]+@[\w\-=!#$%^*\'+\\.={}|?~]+[\w\-=!#$%^*\'+\\={}|?~])\s+\((.*)\)/', $sender, $matches)) {
- # Match address in the form: email@host (Name)
- $result[0] = $matches[1];
- $result[1] = $matches[2];
- } else {
- # Only the email address present
- $result[0] = $sender;
- $result[1] = $sender;
- }
-
- $result[0] = str_replace("\"", "", $result[0]);
- $result[0] = str_replace("'", "", $result[0]);
- return $result;
- }
-
- protected function decodeBody($input, $encoding = '7bit')
- {
- switch (strtolower($encoding))
- {
- case '7bit':
- return $input;
- break;
-
- case 'quoted-printable':
- return $this->quotedPrintableDecode($input);
- break;
-
- case 'base64':
- return base64_decode($input);
- break;
-
- default:
- return $input;
- }
- }
-
- protected function quotedPrintableDecode($input)
- {
- // Remove soft line breaks
- $input = preg_replace("/=\r?\n/", '', $input);
-
- // Replace encoded characters
- $input = preg_replace_callback('/=([a-f0-9]{2})/i',array($this,'quotedPrintableDecodeHandler'),$input);
-
- return $input;
- }
-
- protected function quotedPrintableDecodeHandler($m)
- {
- return chr(hexdec($m[1]));
- }
- }
- ?>