<?php
/*
* @(#) $Header: /var/cvsroot/viewmsg/class.viewmsg.php,v 1.28 2010/05/19 03:36:21 cvs Exp $
*/
/*
A simple class to display a mail message from message buffer
Copyright (C) 2009- Giuseppe Lucarelli <hide@address.com>
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* WARNING! Be careful with messages! You can easily reach max size for a
* php script (normally 10M) or (for example) max_allowed_packet for mysql.
* enable row below (and adjust memory value) to enlarge 'php.ini' field value
*/
// ini_set('memory_limit','32M');
// error_reporting(E_ALL & ~E_NOTICE);
class ViewMsg
{
/* public */
var $message='';
var $subject='';
var $from='';
var $to='';
var $body_result='';
var $attachment='';
var $content_type='';
var $content_transfer_encoding='';
/* private */
var $body_mime_parts='';
var $header='';
var $start_body='';
var $debug=false;
function GetPartData($start,$end) {
$body_part=substr($this->message,$start,$end);
if(!$body_part || strlen($body_part) < 32)
return;
$body_part = ltrim($body_part,"\n");
$pos = strpos($body_part,"\n\n");
$sub_header = substr($body_part,0,$pos);
$body_part=substr($body_part,$pos+2);
if(preg_match('/Content-Transfer-Encoding:.*\n/i',$sub_header,$matches)) {
$content_transfer_encoding=
trim(substr($matches[0],strlen('Content-Transfer-Encoding:'))," ;.\"'\n");
}
if(preg_match('/boundary=.*($|\n)/i',$sub_header,$matches)) {
$boundary=
trim(substr($matches[0],strlen('boundary='))," ;.\"'\n");
}
if(preg_match('/Content-Disposition:.*($|\n)/i',$sub_header,$matches)) {
$content_disposition=
trim(substr($matches[0],strlen('Content-Disposition:'))," ;.\"'\n");
}
//if (eregi("content-disposition: attachment;",$sub_header)
//|| eregi("(content-|^)type: (message/rfc822|application/)",$sub_header)
//|| eregi("(content-|^)type: image/.*;",$sub_header)) {
if (preg_match("/content-disposition: attachment;/im",$sub_header)
|| preg_match("/(content-|^)type: (message\/rfc822|application\/)/im",$sub_header)
|| preg_match("/(content-|^)type: image\/.*;/im",$sub_header)) {
if(!@$this->body_mime_parts['attach'] || !is_array($this->body_mime_parts['attach'])) {
$i=0;
} else {
$i = sizeof($this->body_mime_parts['attach']);
}
$this->body_mime_parts['attach'][$i] = array();
$pos = strpos($sub_header,'name=');
if($pos) {
$len = strpos(substr($sub_header,$pos+5),"\n");
if(!$len) $len = strlen($sub_header) - $pos;
$name = trim(substr($sub_header,$pos+5,$len),"\"\n");
} else {
$name = sprintf("message%d.eml",$i);
}
$this->body_mime_parts['attach'][$i]['header'] = $sub_header;
$this->body_mime_parts['attach'][$i]['filename'] = $name;
$this->body_mime_parts['attach'][$i]['buffer'] = $body_part;
if(preg_match('/(content-|^)type:.*\n/i',$sub_header,$matches)) {
$this->body_mime_parts['attach'][$i]['Content-Type'] =
trim(substr($matches[0],ViewMsg::Stripos($matches[0],':')+2)," <>;.\"'\n");
}
if(preg_match('/\nContent-ID:.*($|\n)/i',$sub_header,$matches)) {
$this->body_mime_parts['attach'][$i]['Content-ID'] =
str_replace("$",'\$',trim(substr($matches[0],strlen(' Content-ID:'))," <>;.\"'\n"));
}
if(@$content_disposition) {
$this->body_mime_parts['attach'][$i]['Content-Disposition'] = $content_disposition;
}
if(@$content_transfer_encoding) {
$this->body_mime_parts['attach'][$i]['Content-Transfer-Encoding'] = $content_transfer_encoding;
}
//} else if(eregi("(content-|^)type: text/html",$sub_header)) {
} else if(preg_match("/(content-|^)type: text\/html/im",$sub_header)) {
$this->body_mime_parts['html'] = array();
$this->body_mime_parts['html']['header'] = $sub_header;
$this->body_mime_parts['html']['buffer'] = $body_part;
if(@$content_transfer_encoding) {
$this->body_mime_parts['html']['Content-Transfer-Encoding'] = $content_transfer_encoding;
}
//} else if(eregi("(content-|^)type: text/",$sub_header)) {
} else if(preg_match("/(content-|^)type: text\//im",$sub_header)) {
$this->body_mime_parts['text'] = array();
$this->body_mime_parts['text']['header'] = $sub_header;
$this->body_mime_parts['text']['buffer'] = $body_part;
if(@$content_transfer_encoding) {
$this->body_mime_parts['text']['Content-Transfer-Encoding'] = $content_transfer_encoding;
}
/*
//} else if(eregi("(content-|^)type: image/.*;",$sub_header)) {
} else if(preg_match("/(content-|^)type: image\/.*;/im",$sub_header)) {
if(!$this->body_mime_parts['image'] || !is_array($this->body_mime_parts['image'])) {
$i=0;
} else {
$i = sizeof($this->body_mime_parts['image']);
}
$this->body_mime_parts['image'][$i] = array();
$pos = strpos($sub_header,'name=');
$len = strpos(substr($sub_header,$pos+5),"\n");
if(!$len) $len = strlen($sub_header) - $pos;
$name = trim(substr($sub_header,$pos+5,$len),"\"\n");
$this->body_mime_parts['image'][$i]['header'] = $sub_header;
$this->body_mime_parts['image'][$i]['name'] = $name;
$this->body_mime_parts['image'][$i]['buffer'] = $body_part;
if(preg_match('/(content-|^)type:.*\n/i',$sub_header,$matches)) {
$this->body_mime_parts['image'][$i]['Content-Type'] =
trim(substr($matches[0],ViewMsg::Stripos($matches[0],'image'))," <>;.\"'\n");
}
if(preg_match('/\nContent-ID:.*($|\n)/i',$sub_header,$matches)) {
$this->body_mime_parts['image'][$i]['Content-ID'] =
str_replace("$",'\$',trim(substr($matches[0],strlen(' Content-ID:'))," <>;.\"'\n"));
}
if(@$content_transfer_encoding) {
$this->body_mime_parts['image'][$i]['Content-Transfer-Encoding'] = $content_transfer_encoding;
}
if(@$content_disposition) {
$this->body_mime_parts['image'][$i]['Content-Disposition'] = $content_disposition;
}
*/
}
}
function InsertInlineImages() {
if(!@$this->body_mime_parts['attach'] || sizeof($this->body_mime_parts['attach']) <= 0) {
return;
}
//foreach($this->body_mime_parts['image'] as $img) {
for($i=0; $i < sizeof($this->body_mime_parts['attach']); $i++) {
//if(!eregi("image/",$this->body_mime_parts['attach'][$i]['Content-Type'])) {
if(!preg_match("/image\//i",$this->body_mime_parts['attach'][$i]['Content-Type'])) {
continue;
}
$img = & $this->body_mime_parts['attach'][$i];
if(preg_match("/(\"|')(cid:\{.*\}\/|cid:.*|)".@$img['Content-ID']."[\"']/im",
$this->body_result,$matches,PREG_OFFSET_CAPTURE)) {
$end = strpos($img['buffer'],"\n\n");
if(!$end) $end = strlen($img['buffer']);
$this->body_result = substr($this->body_result,0,$matches[0][1]).
'"data:'.$img['Content-Type'].';'.$img['Content-Transfer-Encoding'].','.
substr($img['buffer'],0,$end).'"'.
substr($this->body_result,$matches[0][1]+strlen($matches[0][0]));
//unset($this->body_mime_parts['image'][$i]['buffer']);
} else if(preg_match("/=([[:space:]]+|)(\"|'|)".@$img['filename']."/i",
$this->body_result,$matches,PREG_OFFSET_CAPTURE)) {
$end = strpos($img['buffer'],"\n\n");
if(!$end) $end = strlen($img['buffer']);
$this->body_result = substr($this->body_result,0,$matches[0][1]).
'="data:'.$img['Content-Type'].';'.$img['Content-Transfer-Encoding'].','.
substr($img['buffer'],0,$end).'"'.
substr($this->body_result,$matches[0][1]+strlen($matches[0][0]));
//unset($this->body_mime_parts['image'][$i]['buffer']);
}
}
}
// TODO: for messages with content-type: text/plain and attachment insert <BR> for simple CR
function & QuotedPrintableDecode(&$header,&$buffer) {
//if(eregi("quoted-printable",$this->content_transfer_encoding)
//|| eregi("Content-Transfer-Encoding: quoted-printable",$header)) {
if(preg_match("/quoted-printable/i",@$this->content_transfer_encoding)
|| preg_match("/Content-Transfer-Encoding: quoted-printable/i",$header)) {
// remove =\n first
//$buffer=str_replace("=\n",'',$buffer);
// remove =80 second (euro symbol)
//$buffer=str_replace("=80","€",$buffer);
$buffer=preg_replace(
array(
"/=\n/",
"/=80/", // euro symbol
"/=92/"), // ’
array(
'', '€', '’'),
$buffer);
// decode others characters
$buffer=quoted_printable_decode($buffer);
} else if(eregi("Content-Transfer-Encoding: base64",$header)) {
$buffer=base64_decode($buffer);
}
return $buffer;
}
function BuildBodyMessage() {
// no content-type or text/plain only
//if(!$this->content_type || eregi("text/plain",$this->content_type)) {
if(!@$this->content_type || preg_match("/text\/plain/im",$this->content_type)) {
$this->body_result = '<html><body><pre>'.
preg_replace('/(\n|\r)/',"<br>",$this->QuotedPrintableDecode($this->header,
preg_replace(array('/</','/>/'),array('<','>'),
substr($this->message,$this->start_body)))).
'</pre></body></html>';
// html only
//} else if(eregi("text/html",$this->content_type)) {
} else if(preg_match("/text\/html/i",$this->content_type)) {
$this->body_result = $this->QuotedPrintableDecode($this->header,substr($this->message,$this->start_body));
// search for 'text/html' first, 'text/plain' after
} else if(@$this->body_mime_parts['html']) {
$this->body_result = $this->QuotedPrintableDecode($this->body_mime_parts['html']['header'],$this->body_mime_parts['html']['buffer']);
} else if(@$this->body_mime_parts['text']) {
$this->body_mime_parts['text']['buffer']=
preg_replace(array('/</','/>/',"/(\n|\r)/"),array('<','>','<br>'),$this->body_mime_parts['text']['buffer']);
$this->body_result = $this->QuotedPrintableDecode($this->body_mime_parts['text']['header'],
$this->body_mime_parts['text']['buffer']);
}
//---------------------------------------------------------------------
// DISABLED! converts urls and email address to clickable links
/**
$this->body_result=preg_replace(
array(
'/([^\'"])\b((http(s|)|ftp|file):\/\/[-A-Z0-9+&@#i\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])\b/i',
'/\b(?:mailto:)?([A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4})\b/i'),
array(
'\1<a href="\2">\2</a>',
'<a href="mailto:\1">\1</a>'),
$this->body_result);
*/
//---------------------------------------------------------------------
$this->InsertInlineImages();
}
function BuildAttachList($type) {
if(!@$this->body_mime_parts[$type] || !is_array($this->body_mime_parts[$type])) {
return;
}
$this->attachment=array();
for($i=0; $i < sizeof($this->body_mime_parts[$type]); $i++) {
$this->attachment[$i] = & $this->body_mime_parts[$type][$i];
}
}
function IsoDecode($data) {
$retval='';
if(!preg_match('/=\?.*\?([qb])\?.*\?=/im',$data)) {
return $data;
}
$token = preg_split('/=\?/i',$data);
foreach($token as $tok) {
if(strlen($tok) <= 0) {
continue;
}
if(!preg_match('/.*\?([qb])\?(.*)\?=(.*)/im',$tok,$matches,PREG_OFFSET_CAPTURE)) {
$retval.=$tok;
}
if(!strcasecmp($matches[1][0],'b')) {
$retval.=base64_decode($matches[2][0]).$matches[3][0];
} else if(!strcasecmp($matches[1][0],'q')) {
$retval.=str_replace('_',' ',quoted_printable_decode($matches[2][0])).$matches[3][0];
}
}
return $retval;
}
function ParseHeader() {
$pos=strpos($this->message,"\n\n");
if($pos) {
$this->start_body=$pos+1; // "2"
} else {
return false;
}
$this->header = substr($this->message,0,$pos);
if(preg_match('/(\n|^)Content-Type:.*($|\n)/i',$this->header,$matches,PREG_OFFSET_CAPTURE)) {
$this->content_type = substr($matches[0][0],strlen(' content_type:'));
$this->content_type = trim(strtok($this->content_type,";\n")," ;,\"'");
$pos = ViewMsg::Stripos(substr($this->header,$matches[0][1]),'boundary=');
if($pos) {
$this->boundary = substr(substr($this->header,$matches[0][1]),$pos+strlen('boundary='));
$this->boundary = trim(preg_replace("/\n|\"|'/",'',strtok($this->boundary,"\n")));
}
}
if(preg_match('/(\n|^)Content-Transfer-Encoding:.*(\n|$)/i',$this->header,$matches)) {
$this->content_transfer_encoding = substr($matches[0],strlen(' Content-Transfer-Encoding:'));
$this->content_transfer_encoding = trim(strtok($this->content_transfer_encoding,";\n")," ;,\"'");
}
if(preg_match('/(\n|^)subject:.*(\n|$)/i',$this->header,$matches)) {
$this->subject = substr($matches[0],strlen(' subject:'));
$this->subject = $this->IsoDecode(trim(strtok($this->subject,"\n")," ;,\"'"));
}
if(preg_match('/(\n|^)from:.*(\n|$)/i',$this->header,$matches)) {
$this->from = substr($matches[0],strlen(' from:'));
$this->from = $this->IsoDecode(trim(preg_replace("/\n/",'',strtok($this->from,"\n"))," ;,"));
}
if(preg_match('/(\n|^)to:.*(\n|$)/i',$this->header,$matches)) {
$this->to = substr($matches[0],strlen(' to:'));
$this->to = $this->IsoDecode(trim(preg_replace("/\n/",'',strtok($this->to,"\n"))," ;,"));
}
}
function Stripos (& $haystack, $needle, $offset = 0) {
if(function_exists('stripos')) {
return stripos($haystack,$needle,$offset);
}
// PHP 4 doesn't define this function
preg_match('/'.str_replace('/','\/',$needle).'/i',$haystack,$matches,PREG_OFFSET_CAPTURE,1);
if(!$matches || !$matches[0][1]) {
return false;
}
return $matches[0][1];
}
function strRightPos(&$buffer,&$needle) {
if(version_compare(PHP_VERSION,'5','>=')) {
$retval = strrpos($buffer,$needle);
if(!$retval)
$retval = strlen($buffer) - strlen($needle);
return $retval;
}
// TODO: complete for PHP4
// get last occurrence of needle
// while
}
function TokenizeParts($start_part,$boundary) {
$start=$start_part;
$end=strpos($this->message,$boundary.'--',$start);
if(!$end) {
$end=$this->strRightPos($this->message,$boundary);
}
while($start=strpos($this->message,"\n--".$boundary."\n",$start)) {
$start+=strlen($boundary)+3;
if($pos=ViewMsg::Stripos(substr($this->message,$start,$end),"Content-Type:")) {
$cnt=$start+$pos;
//$start+=$pos;
$pos = ViewMsg::Stripos(substr($this->message,$cnt,$end),'boundary=');
if($pos) {
//$start += $pos+strlen('boundary=');
$start = $cnt+$pos+strlen('boundary=');
$crpos=strpos(substr($this->message,$start,$end),"\n");
$subboundary = substr($this->message,$start,$crpos);
//strpos(substr($this->message,$start,$end),"\n")));
$subboundary = trim($subboundary,"\"';\n\r ");
$start=strpos($this->message,"\n--".$subboundary,$start);
$this->TokenizeParts($start,$subboundary);
} else {
$this->GetPartData($start,strpos(substr($this->message,$start,$end),'--'.$boundary));
}
} else {
$this->GetPartData($start,strpos(substr($this->message,$start,$end),'--'.$boundary));
}
}
}
function Run() {
if(!@$this->message || strlen($this->message) <= 0) {
return -1;
}
$this->message=str_replace("\r",'',$this->message);
$this->ParseHeader();
$this->body_mime_parts=array();
if(@$this->boundary && !strstr($this->content_type,'text/plain')) {
$this->TokenizeParts($this->start_body,$this->boundary);
} else {
$this->GetPartData(0,strlen($this->message));
}
$this->BuildBodyMessage();
if($this->debug) {
var_dump(str_replace("<br>","\n",$this->body_result));
}
$this->BuildAttachList('attach');
}
};