From 6b53486b9e054b9bc853f314ed946c9726a8423c Mon Sep 17 00:00:00 2001
From: Peter Rotich <peter@osticket.com>
Date: Mon, 4 Mar 2013 23:09:14 -0500
Subject: [PATCH] Balance html tags & neutralize unsafe tags on emailed html'ed
 emails.

---
 include/class.mailfetch.php | 59 ++++++++++++++++++-------------------
 include/class.mailparse.php | 52 ++++++++++++++++----------------
 2 files changed, 55 insertions(+), 56 deletions(-)

diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php
index c7612ccbc..1bcf6d690 100644
--- a/include/class.mailfetch.php
+++ b/include/class.mailfetch.php
@@ -29,20 +29,20 @@ class MailFetcher {
 
     var $charset = 'UTF-8';
     var $encodings =array('UTF-8','WINDOWS-1251', 'ISO-8859-5', 'ISO-8859-1','KOI8-R');
-    
+
     function MailFetcher($email, $charset='UTF-8') {
 
-        
+
         if($email && is_numeric($email)) //email_id
             $email=Email::lookup($email);
 
         if(is_object($email))
             $this->ht = $email->getMailAccountInfo();
         elseif(is_array($email) && $email['host']) //hashtable of mail account info
-            $this->ht = $email; 
+            $this->ht = $email;
         else
             $this->ht = null;
-           
+
         $this->charset = $charset;
 
         if($this->ht) {
@@ -59,12 +59,12 @@ class MailFetcher {
             $this->srvstr=sprintf('{%s:%d/%s', $this->getHost(), $this->getPort(), $this->getProtocol());
             if(!strcasecmp($this->getEncryption(), 'SSL'))
                 $this->srvstr.='/ssl';
-        
+
             $this->srvstr.='/novalidate-cert}';
 
         }
 
-        //Set timeouts 
+        //Set timeouts
         if(function_exists('imap_timeout')) imap_timeout(1,20);
 
     }
@@ -92,7 +92,7 @@ class MailFetcher {
     function getUsername() {
         return $this->ht['username'];
     }
-    
+
     function getPassword() {
         return $this->ht['password'];
     }
@@ -112,7 +112,7 @@ class MailFetcher {
     }
 
     /* Core */
-    
+
     function connect() {
         return ($this->mbox && $this->ping())?$this->mbox:$this->open();
     }
@@ -123,7 +123,7 @@ class MailFetcher {
 
     /* Default folder is inbox - TODO: provide user an option to fetch from diff folder/label */
     function open($box='INBOX') {
-      
+
         if($this->mbox)
            $this->close();
 
@@ -157,7 +157,7 @@ class MailFetcher {
     function createMailbox($folder) {
 
         if(!$folder) return false;
-            
+
         return imap_createmailbox($this->mbox, imap_utf7_encode($this->srvstr.trim($folder)));
     }
 
@@ -187,23 +187,23 @@ class MailFetcher {
             $text=imap_qprint($text);
             break;
         }
-        
+
         return $text;
     }
 
     //Convert text to desired encoding..defaults to utf8
-    function mime_encode($text, $charset=null, $encoding='utf-8') { //Thank in part to afterburner 
+    function mime_encode($text, $charset=null, $encoding='utf-8') { //Thank in part to afterburner
         return Format::encode($text, $charset, $encoding);
     }
-    
+
     //Generic decoder - resulting text is utf8 encoded -> mirrors imap_utf8
     function mime_decode($text, $encoding='utf-8') {
-        
+
         $str = '';
         $parts = imap_mime_header_decode($text);
         foreach ($parts as $part)
             $str.= $this->mime_encode($part->text, $part->charset, $encoding);
-        
+
         return $str?$str:imap_utf8($text);
     }
 
@@ -215,12 +215,12 @@ class MailFetcher {
         $mimeType = array('TEXT', 'MULTIPART', 'MESSAGE', 'APPLICATION', 'AUDIO', 'IMAGE', 'VIDEO', 'OTHER');
         if(!$struct || !$struct->subtype)
             return 'TEXT/PLAIN';
-        
+
         return $mimeType[(int) $struct->type].'/'.$struct->subtype;
     }
 
     function getHeaderInfo($mid) {
-        
+
         if(!($headerinfo=imap_headerinfo($this->mbox, $mid)) || !$headerinfo->from)
             return null;
 
@@ -237,7 +237,7 @@ class MailFetcher {
 
     //search for specific mime type parts....encoding is the desired encoding.
     function getPart($mid, $mimeType, $encoding=false, $struct=null, $partNumber=false) {
-          
+
         if(!$struct && $mid)
             $struct=@imap_fetchstructure($this->mbox, $mid);
 
@@ -264,7 +264,7 @@ class MailFetcher {
         $text='';
         if($struct && $struct->parts) {
             while(list($i, $substruct) = each($struct->parts)) {
-                if($partNumber) 
+                if($partNumber)
                     $prefix = $partNumber . '.';
                 if(($result=$this->getPart($mid, $mimeType, $encoding, $substruct, $prefix.($i+1))))
                     $text.=$result;
@@ -332,29 +332,28 @@ class MailFetcher {
         return imap_fetchheader($this->mbox, $mid,FT_PREFETCHTEXT);
     }
 
-    
+
     function getPriority($mid) {
         return Mail_Parse::parsePriority($this->getHeader($mid));
     }
 
     function getBody($mid) {
-        
+
         $body ='';
         if(!($body = $this->getPart($mid,'TEXT/PLAIN', $this->charset))) {
             if(($body = $this->getPart($mid,'TEXT/HTML', $this->charset))) {
                 //Convert tags of interest before we striptags
                 $body=str_replace("</DIV><DIV>", "\n", $body);
                 $body=str_replace(array("<br>", "<br />", "<BR>", "<BR />"), "\n", $body);
-                $body=Format::html($body); //Balance html tags before stripping.
-                $body=Format::striptags($body); //Strip tags??
+                $body=Format::safe_html($body); //Balance html tags & neutralize unsafe tags.
             }
         }
 
         return $body;
     }
 
-    //email to ticket 
-    function createTicket($mid) { 
+    //email to ticket
+    function createTicket($mid) {
         global $ost;
 
         if(!($mailinfo = $this->getHeaderInfo($mid)))
@@ -387,7 +386,7 @@ class MailFetcher {
 
         if($ost->getConfig()->useEmailPriority())
             $vars['priorityId']=$this->getPriority($mid);
-       
+
         $ticket=null;
         $newticket=true;
         //Check the subject line for possible ID.
@@ -397,7 +396,7 @@ class MailFetcher {
             if(!($ticket=Ticket::lookupByExtId($tid, $vars['email'])))
                 $ticket=null;
         }
-        
+
         $errors=array();
         if($ticket) {
             if(!($message=$ticket->postMessage($vars, 'Email')))
@@ -494,11 +493,11 @@ class MailFetcher {
     /*
        MailFetcher::run()
 
-       Static function called to initiate email polling 
+       Static function called to initiate email polling
      */
     function run() {
         global $ost;
-      
+
         if(!$ost->getConfig()->isEmailPollingEnabled())
             return;
 
@@ -510,7 +509,7 @@ class MailFetcher {
             return;
         }
 
-        //Hardcoded error control... 
+        //Hardcoded error control...
         $MAXERRORS = 5; //Max errors before we start delayed fetch attempts
         $TIMEOUT = 10; //Timeout in minutes after max errors is reached.
 
diff --git a/include/class.mailparse.php b/include/class.mailparse.php
index 342fc5298..23f5d64d8 100644
--- a/include/class.mailparse.php
+++ b/include/class.mailparse.php
@@ -19,16 +19,16 @@ require_once(PEAR_DIR.'Mail/mimeDecode.php');
 require_once(PEAR_DIR.'Mail/RFC822.php');
 
 class Mail_Parse {
-    
+
     var $mime_message;
     var $include_bodies;
     var $decode_headers;
     var $decode_bodies;
-    
+
     var $struct;
-    
+
     function Mail_parse($mimeMessage,$includeBodies=true,$decodeHeaders=TRUE,$decodeBodies=TRUE){
-        
+
         $this->mime_message=$mimeMessage;
         $this->include_bodies=$includeBodies;
         $this->decode_headers=$decodeHeaders;
@@ -42,9 +42,9 @@ class Mail_Parse {
                         'include_bodies'=> $this->include_bodies,
                         'decode_headers'=> $this->decode_headers,
                         'decode_bodies' => $this->decode_bodies);
-        $this->splitBodyHeader();    
+        $this->splitBodyHeader();
         $this->struct=Mail_mimeDecode::decode($params);
-        
+
         return (PEAR::isError($this->struct) || !(count($this->struct->headers)>1))?FALSE:TRUE;
     }
 
@@ -94,7 +94,7 @@ class Mail_Parse {
         }
         return $array;
     }
-    
+
 
     function getStruct(){
         return $this->struct;
@@ -109,8 +109,8 @@ class Mail_Parse {
     function getError(){
         return PEAR::isError($this->struct)?$this->struct->getMessage():'';
     }
-   
-    
+
+
     function getFromAddressList(){
         return Mail_Parse::parseAddressList($this->struct->headers['from']);
     }
@@ -119,7 +119,7 @@ class Mail_Parse {
         //Delivered-to incase it was a BBC mail.
        return Mail_Parse::parseAddressList($this->struct->headers['to']?$this->struct->headers['to']:$this->struct->headers['delivered-to']);
     }
-        
+
     function getCcAddressList(){
         return $this->struct->headers['cc']?Mail_Parse::parseAddressList($this->struct->headers['cc']):null;
     }
@@ -127,27 +127,27 @@ class Mail_Parse {
     function getMessageId(){
         return $this->struct->headers['message-id'];
     }
- 
+
     function getSubject(){
         return $this->struct->headers['subject'];
     }
-    
+
     function getBody(){
-        
+
         $body='';
         if(!($body=$this->getPart($this->struct,'text/plain'))) {
             if(($body=$this->getPart($this->struct,'text/html'))) {
                 //Cleanup the html.
-                $body=str_replace("</DIV><DIV>", "\n", $body);                        
+                $body=str_replace("</DIV><DIV>", "\n", $body);
                 $body=str_replace(array("<br>", "<br />", "<BR>", "<BR />"), "\n", $body);
-                $body=Format::striptags(Format::html($body));
+                $body=Format::safe_html($body); //Balance html tags & neutralize unsafe tags.
             }
         }
         return $body;
     }
-    
+
     function getPart($struct,$ctypepart) {
-        
+
         if($struct && !$struct->parts) {
             $ctype = @strtolower($struct->ctype_primary.'/'.$struct->ctype_secondary);
             if($ctype && strcasecmp($ctype,$ctypepart)==0)
@@ -164,7 +164,7 @@ class Mail_Parse {
         return $data;
     }
 
-     
+
     function mime_encode($text, $charset=null, $encoding='utf-8') {
         return Format::encode($text, $charset, $encoding);
     }
@@ -175,15 +175,15 @@ class Mail_Parse {
             $part=$this->getStruct();
 
         if($part && $part->disposition
-                && (!strcasecmp($part->disposition,'attachment') 
-                    || !strcasecmp($part->disposition,'inline') 
+                && (!strcasecmp($part->disposition,'attachment')
+                    || !strcasecmp($part->disposition,'inline')
                     || !strcasecmp($part->ctype_primary,'image'))){
-            
+
             if(!($filename=$part->d_parameters['filename']) && $part->d_parameters['filename*'])
                 $filename=$part->d_parameters['filename*']; //Do we need to decode?
-           
+
             $file=array(
-                    'name'  => $filename, 
+                    'name'  => $filename,
                     'type'  => strtolower($part->ctype_primary.'/'.$part->ctype_secondary),
                     'data'  => $this->mime_encode($part->body, $part->ctype_parameters['charset'])
                     );
@@ -245,7 +245,7 @@ class EmailDataParser {
     function EmailDataParser($stream=null) {
         $this->stream = $stream;
     }
-    
+
     function parse($stream) {
 
         $contents ='';
@@ -260,7 +260,7 @@ class EmailDataParser {
         $parser= new Mail_Parse($contents);
         if(!$parser->decode()) //Decode...returns false on decoding errors
             return $this->err('Email parse failed ['.$parser->getError().']');
-        
+
         $data =array();
         //FROM address: who sent the email.
         if(($fromlist = $parser->getFromAddressList()) && !PEAR::isError($fromlist)) {
@@ -293,7 +293,7 @@ class EmailDataParser {
                     break;
             }
         }
-            
+
         $data['subject'] = Format::utf8encode($parser->getSubject());
         $data['message'] = Format::utf8encode(Format::stripEmptyLines($parser->getBody()));
         $data['header'] = $parser->getHeader();
-- 
GitLab