diff --git a/include/class.api.php b/include/class.api.php
index b3e4ea7b349805b8d0489c23dabf0b22b1af7f77..65f0a90e89abaf42ecd5fa980d0b09299b738f3a 100644
--- a/include/class.api.php
+++ b/include/class.api.php
@@ -337,20 +337,12 @@ class ApiXmlDataParser extends XmlDataParser {
                 }
                 if (isset($value['encoding']))
                     $value['body'] = Format::utf8encode($value['body'], $value['encoding']);
-                // HTML-ize text if html is enabled
-                if ($cfg->isHtmlThreadEnabled()
-                        && (!isset($value['type'])
-                            || strcasecmp($value['type'], 'text/html')))
-                    $value = sprintf('<pre>%s</pre>',
-                        Format::htmlchars($value['body']));
-                // Text-ify html if html is disabled
-                elseif (!$cfg->isHtmlThreadEnabled()
-                        && !strcasecmp($value['type'], 'text/html'))
-                    $value = Format::html2text(Format::safe_html(
-                        $value['body']), 100, false);
-                // Noop if they content-type matches the html setting
+
+                if (!strcasecmp($value['type'], 'text/html'))
+                    $value = new HtmlThreadBody($value['body']);
                 else
-                    $value = $value['body'];
+                    $value = new TextThreadBody($value['body']);
+
             } else if ($key == "attachments") {
                 if(!isset($value['file'][':text']))
                     $value = $value['file'];
@@ -390,11 +382,12 @@ class ApiJsonDataParser extends JsonDataParser {
             } elseif ($key == "message") {
                 // Allow message specified in RFC 2397 format
                 $data = Format::parseRfc2397($value, 'utf-8');
-                if (!isset($data['type']) || $data['type'] != 'text/html')
-                    $value = sprintf('<pre>%s</pre>',
-                        Format::htmlchars($data['data']));
+
+                if (isset($data['type']) && $data['type'] == 'text/html')
+                    $value = new HtmlThreadBody($data['data']);
                 else
-                    $value = $data['data'];
+                    $value = new TextThreadBody($data['data']);
+
             } else if ($key == "attachments") {
                 foreach ($value as &$info) {
                     $data = reset($info);
@@ -408,10 +401,8 @@ class ApiJsonDataParser extends JsonDataParser {
                 }
                 unset($info);
             }
-            if (is_array($value)) {
-                $value = $this->fixup($value);
-            }
         }
+        unset($value);
 
         return $current;
     }
@@ -432,9 +423,6 @@ class ApiEmailDataParser extends EmailDataParser {
 
         $data['source'] = 'Email';
 
-        if(!$data['message'])
-            $data['message'] = '--';
-
         if(!$data['subject'])
             $data['subject'] = '[No Subject]';
 
diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php
index f1ce7fdc75bf590efa9c46eaff30045e78e6795a..9c4afe9b4005e4a3844c6b536473ff9718a5eb69 100644
--- a/include/class.mailfetch.php
+++ b/include/class.mailfetch.php
@@ -472,10 +472,8 @@ class MailFetcher {
             foreach ($struct->parameters as $p) {
                 if (strtolower($p->attribute) == 'report-type'
                         && $p->value == 'delivery-status') {
-                    return sprintf('<pre>%s</pre>',
-                        Format::htmlchars(
-                            $this->getPart($mid, 'text/plain', $this->charset, $struct, false, 1)
-                        ));
+                    return new TextThreadBody( $this->getPart(
+                                $mid, 'text/plain', $this->charset, $struct, false, 1));
                 }
             }
         }
@@ -501,27 +499,23 @@ class MailFetcher {
         global $cfg;
 
         if ($cfg->isHtmlThreadEnabled()) {
-            if ($body=$this->getPart($mid, 'text/html', $this->charset)) {
-                //Cleanup the html.
-                $body = (trim($body, " <>br/\t\n\r"))
-                    ? Format::safe_html($body)
-                    : '--';
-            }
-            elseif ($body=$this->getPart($mid, 'text/plain', $this->charset)) {
-                $body = trim($body)
-                    ? sprintf('<pre>%s</pre>',
-                        Format::htmlchars($body))
-                    : '--';
-            }
-        }
-        else {
-            if (!($body=$this->getPart($mid, 'text/plain', $this->charset))) {
-                if ($body=$this->getPart($mid, 'text/html', $this->charset)) {
-                    $body = Format::html2text(Format::safe_html($body), 100, false);
-                }
-            }
-            $body = trim($body) ? $body : '--';
+            if ($html=$this->getPart($mid, 'text/html', $this->charset))
+                $body = new HtmlThreadBody($html);
+            elseif ($text=$this->getPart($mid, 'text/plain', $this->charset))
+                $body = new TextThreadBody($text);
         }
+        elseif ($text=$this->getPart($mid, 'text/plain', $this->charset))
+            $body = new TextThreadBody($text);
+        elseif ($html=$this->getPart($mid, 'text/html', $this->charset))
+            $body = new TextThreadBody(
+                    Format::html2text(Format::safe_html($html),
+                        100, false));
+        else
+            $body = new TextThreadBody('');
+
+        if ($cfg->stripQuotedReply())
+            $body->stripQuotedReply($cfg->getReplySeparator());
+
         return $body;
     }
 
@@ -572,7 +566,7 @@ class MailFetcher {
             $vars['thread-type'] = 'N';
         }
         else {
-            $vars['message'] = Format::stripEmptyLines($this->getBody($mid));
+            $vars['message'] = $this->getBody($mid);
         }
 
 
diff --git a/include/class.mailparse.php b/include/class.mailparse.php
index eacd62d2a24b44db7a6f66c38411ca9bf5a885f5..eca76a0b753ebba8167e71b78f5f6411d65e0e7c 100644
--- a/include/class.mailparse.php
+++ b/include/class.mailparse.php
@@ -229,27 +229,23 @@ class Mail_Parse {
         global $cfg;
 
         if ($cfg->isHtmlThreadEnabled()) {
-            if ($body=$this->getPart($this->struct,'text/html')) {
-                // Cleanup the html -- Balance html tags & neutralize unsafe tags.
-                $body = (trim($body, " <>br/\t\n\r"))
-                    ? Format::safe_html($body)
-                    : '--';
-            }
-            elseif ($body=$this->getPart($this->struct,'text/plain')) {
-                $body = trim($body)
-                    ? sprintf('<pre>%s</pre>',
-                        Format::htmlchars($body))
-                    : '--';
-            }
-        }
-        else {
-            if (!($body=$this->getPart($this->struct,'text/plain'))) {
-                if ($body=$this->getPart($this->struct,'text/html')) {
-                    $body = Format::html2text(Format::safe_html($body), 100, false);
-                }
-            }
-            $body = trim($body) ? $body : '--';
+            if ($html=$this->getPart($this->struct,'text/html'))
+                $body = new HtmlThreadBody($html);
+            elseif ($text=$this->getPart($this->struct,'text/plain'))
+                $body = new TextThreadBody($text);
         }
+        elseif ($text=$this->getPart($this->struct,'text/plain'))
+            $body = new TextThreadBody($text);
+        elseif ($html=$this->getPart($this->struct,'text/html'))
+            $body = new TextThreadBody(
+                    Format::html2text(Format::safe_html($html),
+                        100, false));
+        else
+            $body = new TextThreadBody('');
+
+        if ($cfg->stripQuotedReply())
+            $body->stripQuotedReply($cfg->getReplySeparator());
+
         return $body;
     }
 
@@ -481,7 +477,7 @@ class EmailDataParser {
         }
         else {
             // Typical email
-            $data['message'] = Format::stripEmptyLines($parser->getBody());
+            $data['message'] = $parser->getBody();
             $data['in-reply-to'] = $parser->struct->headers['in-reply-to'];
             $data['references'] = $parser->struct->headers['references'];
         }
diff --git a/include/class.thread.php b/include/class.thread.php
index b0748f69e7522f3ac4113591efab38e0bbea56a9..fec7c52c40d9284a18ef3f4a6fca9191db3a0713 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -865,23 +865,17 @@ Class ThreadEntry {
         if(!$vars['ticketId'] || !$vars['type'] || !in_array($vars['type'], array('M','R','N')))
             return false;
 
-        //Strip quoted reply...on emailed  messages
-        if($vars['origin']
-                && !strcasecmp($vars['origin'], 'Email')
-                && $cfg->stripQuotedReply()
-                && ($tag=$cfg->getReplySeparator())
-                && strpos($vars['body'], $tag))
-            if((list($msg) = explode($tag, $vars['body'], 2)) && trim($msg))
-                $vars['body'] = $msg;
-
-        if (!$cfg->isHtmlThreadEnabled()) {
-            // Data in the database is assumed to be HTML, change special
-            // plain text XML characters
-            $vars['title'] = Format::htmlchars($vars['title']);
-            $vars['body'] = sprintf('<pre>%s</pre>',
-                Format::htmlchars($vars['body']));
+
+        if (!$vars['body'] instanceof ThreadBody) {
+            if ($cfg->isHtmlThreadEnabled())
+                $vars['body'] = new HtmlThreadBody($vars['body']);
+            else
+                $vars['body'] = new TextThreadBody($vars['body']);
         }
-        $vars['body'] = Format::sanitize($vars['body']);
+
+        if (!($body = Format::sanitize(
+                        (string) $vars['body']->convertTo('html'))))
+            $body = '-'; //Special tag used to signify empty message as stored.
 
         $poster = $vars['poster'];
         if ($poster && is_object($poster))
@@ -899,7 +893,7 @@ Class ThreadEntry {
         if (!isset($vars['attachments']) || !$vars['attachments'])
             // Otherwise, body will be configured in a block below (after
             // inline attachments are saved and updated in the database)
-            $sql.=' ,body='.db_input($vars['body']);
+            $sql.=' ,body='.db_input($body);
 
         if(isset($vars['pid']))
             $sql.=' ,pid='.db_input($vars['pid']);
@@ -936,12 +930,12 @@ Class ThreadEntry {
                 // content-id will be discarded, only the unique hash-code
                 // will be available to retrieve the image later
                 if ($a['cid'] && $a['key']) {
-                    $vars['body'] = str_replace('src="cid:'.$a['cid'].'"',
-                        'src="cid:'.$a['key'].'"', $vars['body']);
+                    $body = str_replace('src="cid:'.$a['cid'].'"',
+                        'src="cid:'.$a['key'].'"', $body);
                 }
             }
             unset($a);
-            $sql = 'UPDATE '.TICKET_THREAD_TABLE.' SET body='.db_input($vars['body'])
+            $sql = 'UPDATE '.TICKET_THREAD_TABLE.' SET body='.db_input($body)
                 .' WHERE `id`='.db_input($entry->getId());
             if (!db_query($sql) || !db_affected_rows())
                 return false;
@@ -954,7 +948,7 @@ Class ThreadEntry {
         $entry->saveEmailInfo($vars);
 
         // Inline images (attached to the draft)
-        $entry->saveAttachments(Draft::getAttachmentIds($vars['body']));
+        $entry->saveAttachments(Draft::getAttachmentIds($body));
 
         return $entry;
     }
@@ -1123,4 +1117,61 @@ class Note extends ThreadEntry {
                 )?$n:null;
     }
 }
+
+class ThreadBody /* extends SplString */ {
+
+    static $types = array('text', 'html');
+
+    var $body;
+    var $type;
+
+    function __construct($body, $type='text') {
+        $type = strtolower($type);
+        if (!in_array($type, static::$types))
+            throw new Exception($type.': Unsupported ThreadBody type');
+        $this->body = (string) $body;
+        $this->type = $type;
+    }
+
+    function convertTo($type) {
+        if ($type === $this->type)
+            return $this;
+
+        $conv = $this->type . ':' . strtolower($type);
+        switch ($conv) {
+        case 'text:html':
+            return new ThreadBody(sprintf('<pre>%s</pre>',
+                Format::htmlchars($this->body)), $type);
+        case 'html:text':
+            return new ThreadBody(Format::html2text((string) $this), $type);
+        }
+    }
+
+    function stripQuotedReply($tag) {
+
+        //Strip quoted reply...on emailed  messages
+        if (!$tag || strpos($this->body, $tag) === false)
+            return;
+
+        if ((list($msg) = explode($tag, $this->body, 2)) && trim($msg))
+            $this->body = $msg;
+
+    }
+
+    function __toString() {
+        return $this->body;
+    }
+}
+
+class TextThreadBody extends ThreadBody {
+    function __construct($body) {
+        parent::__construct(Format::stripEmptyLines($body), 'text');
+    }
+}
+class HtmlThreadBody extends ThreadBody {
+    function __construct($body) {
+        $body = trim($body, " <>br/\t\n\r") ? Format::safe_html($body) : '';
+        parent::__construct($body, 'html');
+    }
+}
 ?>
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 7bfcdc7a662c7227878da332c9c6659e1122403a..de0befdff87b7ed5de59a8f27fc2e790fbde0e61 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -1752,11 +1752,19 @@ class Ticket {
     function logNote($title, $note, $poster='SYSTEM', $alert=true) {
 
         $errors = array();
+        //Unless specified otherwise, assume HTML
+        if ($note && is_string($note))
+            $note = new HtmlThreadBody($note);
+
         return $this->postNote(
-                array('title' => $title, 'note' => $note),
+                array(
+                    'title' => $title,
+                    'note' => $note,
+                ),
                 $errors,
                 $poster,
-                $alert);
+                $alert
+        );
     }
 
     function postNote($vars, &$errors, $poster, $alert=true) {
@@ -2164,7 +2172,7 @@ class Ticket {
 
         $id=0;
         $fields=array();
-        $fields['message']  = array('type'=>'text',     'required'=>1, 'error'=>'Message required');
+        $fields['message']  = array('type'=>'*',     'required'=>1, 'error'=>'Message required');
         switch (strtolower($origin)) {
             case 'web':
                 $fields['topicId']  = array('type'=>'int',  'required'=>1, 'error'=>'Select help topic');
diff --git a/include/class.validator.php b/include/class.validator.php
index f01bcb38ca85f799c9a7d8a187624907228d8941..c41058e057f51731a994387517d0835de40f137d 100644
--- a/include/class.validator.php
+++ b/include/class.validator.php
@@ -61,6 +61,10 @@ class Validator {
                 $this->errors[$k]=$field['error'];
                 continue;
             }
+
+            //We don't care about the type.
+            if ($field['type'] == '*') continue;
+
             //Do the actual validation based on the type.
             switch(strtolower($field['type'])):
             case 'integer':
@@ -73,7 +77,7 @@ class Validator {
             case 'double':
                 if(!is_numeric($this->input[$k]))
                     $this->errors[$k]=$field['error'];
-            break;
+                break;
             case 'text':
             case 'string':
                 if(!is_string($this->input[$k]))
@@ -84,9 +88,9 @@ class Validator {
                     $this->errors[$k]=$field['error'];
                 break;
             case 'radio':
-            if(!isset($this->input[$k]))
-               $this->errors[$k]=$field['error'];
-            break;
+                if(!isset($this->input[$k]))
+                    $this->errors[$k]=$field['error'];
+                break;
             case 'date': //TODO...make sure it is really in GNU date format..
                 if(strtotime($this->input[$k])===false)
                     $this->errors[$k]=$field['error'];