diff --git a/bootstrap.php b/bootstrap.php
index ef080d86f275ce07d4cad1dababa0529cb15724b..35580ce9e89442862b1fd444c028ac98cddfdc6f 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -111,11 +111,14 @@ class Bootstrap {
             $configfile=INCLUDE_DIR.'settings.php';
             //Die gracefully on upgraded v1.6 RC5 installation - otherwise script dies with confusing message.
             if(!strcasecmp(basename($_SERVER['SCRIPT_NAME']), 'settings.php'))
-                die('Please rename config file include/settings.php to include/ost-config.php to continue!');
+                Http::response(500,
+                    'Please rename config file include/settings.php to '
+                   .'include/ost-config.php to continue!');
         } elseif(file_exists(ROOT_DIR.'setup/'))
             header('Location: '.ROOT_PATH.'setup/');
 
-        if(!$configfile || !file_exists($configfile)) die('<b>Error loading settings. Contact admin.</b>');
+        if(!$configfile || !file_exists($configfile))
+            Http::response(500,'<b>Error loading settings. Contact admin.</b>');
 
         require($configfile);
         define('CONFIG_FILE',$configfile); //used in admin.php to check perm.
@@ -150,11 +153,11 @@ class Bootstrap {
     }
 
     function croak($message) {
-        $msg=$ferror."\n\n".THISPAGE;
-        Mailer::sendmail(ADMIN_EMAIL, 'osTicket Fatal Error', $msg, sprintf('"osTicket Alerts"<%s>', ADMIN_EMAIL));
+        $msg = $message."\n\n".THISPAGE;
+        Mailer::sendmail(ADMIN_EMAIL, 'osTicket Fatal Error', $msg,
+            sprintf('"osTicket Alerts"<%s>', ADMIN_EMAIL));
         //Display generic error to the user
-        die("<b>Fatal Error:</b> Contact system administrator.");
-        exit;
+        Http::response(500, "<b>Fatal Error:</b> Contact system administrator.");
     }
 }
 
diff --git a/include/class.config.php b/include/class.config.php
index 4114cfcafe264e5e48a384b85527e776d202b5c2..76fec8e783522fa57c9bce71df831b19e043d526 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -883,7 +883,7 @@ class OsticketConfig extends Config {
         $f['alert_email_id']=array('type'=>'int',   'required'=>1, 'error'=>'Selection required');
         $f['admin_email']=array('type'=>'email',   'required'=>1, 'error'=>'System admin email required');
 
-        if($vars['strip_quoted_reply'] && !$vars['reply_separator'])
+        if($vars['strip_quoted_reply'] && !trim($vars['reply_separator']))
             $errors['reply_separator']='Reply separator required to strip quoted reply.';
 
         if($vars['admin_email'] && Email::getIdByEmail($vars['admin_email'])) //Make sure admin email is not also a system email.
diff --git a/include/class.file.php b/include/class.file.php
index a896d9e9cefd4098be9a7e35080157bbe222d894..d53ff3d50bfe2210210d85e1fa82ab04c0281456 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -402,12 +402,19 @@ class AttachmentChunkedData {
     }
 
     function deleteOrphans() {
-
-        $sql = 'DELETE c.* FROM '.FILE_CHUNK_TABLE.' c '
+        $deleted = 0;
+        $sql = 'SELECT c.file_id, c.chunk_id FROM '.FILE_CHUNK_TABLE.' c '
              . ' LEFT JOIN '.FILE_TABLE.' f ON(f.id=c.file_id) '
              . ' WHERE f.id IS NULL';
 
-        return db_query($sql)?db_affected_rows():0;
+        $res = db_query($sql);
+        while (list($file_id, $chunk_id) = db_fetch_row($res)) {
+            db_query('DELETE FROM '.FILE_CHUNK_TABLE
+                .' WHERE file_id='.db_input($file_id)
+                .' AND chunk_id='.db_input($chunk_id));
+            $deleted += db_affected_rows();
+        }
+        return $deleted;
     }
 }
 ?>
diff --git a/include/class.format.php b/include/class.format.php
index 892cc42f17bd56fe2e934d6c1ffb0c2a6f305fa5..f016896deb39cfefb05b43bf4913bb06feb6fe84 100644
--- a/include/class.format.php
+++ b/include/class.format.php
@@ -86,6 +86,23 @@ class Format {
         return $text;
     }
 
+    /**
+     * Decodes filenames given in the content-disposition header according
+     * to RFC5987, such as filename*=utf-8''filename.png. Note that the
+     * language sub-component is defined in RFC5646, and that the filename
+     * is URL encoded (in the charset specified)
+     */
+    function decodeRfc5987($filename) {
+        $match = array();
+        if (preg_match("/([\w!#$%&+^_`{}~-]+)'([\w-]*)'(.*)$/",
+                $filename, $match))
+            // XXX: Currently we don't care about the language component.
+            //      The  encoding hint is sufficient.
+            return self::utf8encode(urldecode($match[3]), $match[1]);
+        else
+            return $filename;
+    }
+
 	function phone($phone) {
 
 		$stripped= preg_replace("/[^0-9]/", "", $phone);
diff --git a/include/class.mailer.php b/include/class.mailer.php
index d291effb228bf81cc5b7cae62e58b03dcce37bc9..adb0fd0d1188a30ed64e09226fd1c919b90c7662 100644
--- a/include/class.mailer.php
+++ b/include/class.mailer.php
@@ -150,7 +150,7 @@ class Mailer {
         //Desired encodings...
         $encodings=array(
                 'head_encoding' => 'quoted-printable',
-                'text_encoding' => 'quoted-printable',
+                'text_encoding' => 'base64',
                 'html_encoding' => 'base64',
                 'html_charset'  => 'utf-8',
                 'text_charset'  => 'utf-8',
diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php
index 7fee6868a50a70cb1681f05ae8e2c7604510234f..2e5089a5742e0ac9dc3b097dfbf5ef1780df32df 100644
--- a/include/class.mailfetch.php
+++ b/include/class.mailfetch.php
@@ -28,7 +28,6 @@ class MailFetcher {
     var $srvstr;
 
     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') {
 
@@ -108,7 +107,7 @@ class MailFetcher {
     }
 
     function getArchiveFolder() {
-        return $this->ht['archive_folder'];
+        return $this->mailbox_encode($this->ht['archive_folder']);
     }
 
     /* Core */
@@ -124,10 +123,19 @@ class MailFetcher {
     /* Default folder is inbox - TODO: provide user an option to fetch from diff folder/label */
     function open($box='INBOX') {
 
-        if($this->mbox)
+        if ($this->mbox)
            $this->close();
 
-        $this->mbox = imap_open($this->srvstr.$box, $this->getUsername(), $this->getPassword());
+        $args = array($this->srvstr.$this->mailbox_encode($box),
+            $this->getUsername(), $this->getPassword());
+
+        // Disable Kerberos and NTLM authentication if it happens to be
+        // supported locally or remotely
+        if (version_compare(PHP_VERSION, '5.3.2', '>='))
+            $args += array(NULL, 0, array(
+                'DISABLE_AUTHENTICATOR' => array('GSSAPI', 'NTLM')));
+
+        $this->mbox = call_user_func_array('imap_open', $args);
 
         return $this->mbox;
     }
@@ -158,7 +166,8 @@ class MailFetcher {
 
         if(!$folder) return false;
 
-        return imap_createmailbox($this->mbox, imap_utf7_encode($this->srvstr.trim($folder)));
+        return imap_createmailbox($this->mbox,
+           $this->srvstr.$this->mailbox_encode(trim($folder)));
     }
 
     /* check if a folder exists - create one if requested */
@@ -198,6 +207,18 @@ class MailFetcher {
         return Format::encode($text, $charset, $encoding);
     }
 
+    function mailbox_encode($mailbox) {
+        if (!$mailbox)
+            return null;
+        // Properly encode the mailbox to UTF-7, according to rfc2060,
+        // section 5.1.3
+        elseif (function_exists('mb_convert_encoding'))
+            return mb_convert_encoding($mailbox, 'UTF7-IMAP', 'utf-8');
+        else
+            // XXX: This function has some issues on some versions of PHP
+            return imap_utf7_encode($mailbox);
+    }
+
     //Generic decoder - resulting text is utf8 encoded -> mirrors imap_utf8
     function mime_decode($text, $encoding='utf-8') {
 
@@ -308,6 +329,31 @@ class MailFetcher {
         return $text;
     }
 
+    /**
+     * Searches the attribute list for a possible filename attribute. If
+     * found, the attribute value is returned. If the attribute uses rfc5987
+     * to encode the attribute value, the value is returned properly decoded
+     * if possible
+     *
+     * Attribute Search Preference:
+     *   filename
+     *   filename*
+     *   name
+     *   name*
+     */
+    function findFilename($attributes) {
+        foreach (array('filename', 'name') as $pref) {
+            foreach ($attributes as $a) {
+                if (strtolower($a->attribute) == $pref)
+                    return $a->value;
+                // Allow the RFC5987 specification of the filename
+                elseif (strtolower($a->attribute) == $pref.'*')
+                    return Format::decodeRfc5987($a->value);
+            }
+        }
+        return false;
+    }
+
     /*
      getAttachments
 
@@ -319,23 +365,16 @@ class MailFetcher {
 
         if($part && !$part->parts) {
             //Check if the part is an attachment.
-            $filename = '';
-            if($part->ifdisposition && in_array(strtolower($part->disposition), array('attachment', 'inline'))) {
-                $filename = $part->dparameters[0]->value;
-                //Some inline attachments have multiple parameters.
-                if(count($part->dparameters)>1) {
-                    foreach($part->dparameters as $dparameter) {
-                        if(!in_array(strtoupper($dparameter->attribute), array('FILENAME', 'NAME'))) continue;
-                        $filename = $dparameter->value;
-                        break;
-                    }
-                }
-            } elseif($part->ifparameters && $part->parameters && $part->type > 0) { //inline attachments without disposition.
-                foreach($part->parameters as $parameter) {
-                    if(!in_array(strtoupper($parameter->attribute), array('FILENAME', 'NAME'))) continue;
-                    $filename = $parameter->value;
-                    break;
-                }
+            $filename = false;
+            if ($part->ifdisposition
+                    && in_array(strtolower($part->disposition),
+                        array('attachment', 'inline'))) {
+                $filename = $this->findFilename($part->dparameters);
+            }
+            // Inline attachments without disposition.
+            if (!$filename && $part->ifparameters && $part->parameters
+                    && $part->type > 0) {
+                $filename = $this->findFilename($part->parameters);
             }
 
             if($filename) {
@@ -423,19 +462,27 @@ class MailFetcher {
         $newticket=true;
 
         $errors=array();
+        $seen = false;
 
-        if (($thread = ThreadEntry::lookupByEmailHeaders($vars))
+        if (($thread = ThreadEntry::lookupByEmailHeaders($vars, $seen))
                 && ($message = $thread->postEmail($vars))) {
             if (!$message instanceof ThreadEntry)
                 // Email has been processed previously
                 return $message;
             $ticket = $message->getTicket();
+        } elseif ($seen) {
+            // Already processed, but for some reason (like rejection), no
+            // thread item was created. Ignore the email
+            return true;
         } elseif (($ticket=Ticket::create($vars, $errors, 'Email'))) {
             $message = $ticket->getLastMessage();
         } else {
             //Report success if the email was absolutely rejected.
-            if(isset($errors['errno']) && $errors['errno'] == 403)
+            if(isset($errors['errno']) && $errors['errno'] == 403) {
+                // Never process this email again!
+                ThreadEntry::logEmailHeaders(0, $vars['mid']);
                 return true;
+            }
 
             # check if it's a bounce!
             if($vars['header'] && TicketFilter::isAutoBounce($vars['header'])) {
diff --git a/include/class.mailparse.php b/include/class.mailparse.php
index b9ab3c33ff34fd44496a92affbc38a276fc418b9..039a48b74be371ed1def64f502cad0db16c8506e 100644
--- a/include/class.mailparse.php
+++ b/include/class.mailparse.php
@@ -201,16 +201,38 @@ class Mail_Parse {
 
     function getAttachments($part=null){
 
-        if($part==null)
-            $part=$this->getStruct();
-
-        if($part && $part->disposition
-                && (!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?
+        /* Consider this part as an attachment if
+         *   * It has a Content-Disposition header
+         *     * AND it is specified as either 'attachment' or 'inline'
+         *   * The Content-Type header specifies
+         *     * type is image/* or application/*
+         *     * has a name parameter
+         */
+        if($part && (
+                ($part->disposition
+                    && (!strcasecmp($part->disposition,'attachment')
+                        || !strcasecmp($part->disposition,'inline'))
+                )
+                || (!strcasecmp($part->ctype_primary,'image')
+                    || !strcasecmp($part->ctype_primary,'application')))) {
+
+            if (isset($part->d_parameters['filename']))
+                $filename = $part->d_parameters['filename'];
+            elseif (isset($part->d_parameters['filename*']))
+                // Support RFC 6266, section 4.3 and RFC, and RFC 5987
+                $filename = Format::decodeRfc5987(
+                    $part->d_parameters['filename*']);
+
+            // Support attachments that do not specify a content-disposition
+            // but do specify a "name" parameter in the content-type header.
+            elseif (isset($part->ctype_parameters['name']))
+                $filename=$part->ctype_parameters['name'];
+            elseif (isset($part->ctype_parameters['name*']))
+                $filename = Format::decodeRfc5987(
+                    $part->ctype_parameters['name*']);
+            else
+                // Not an attachment?
+                return false;
 
             $file=array(
                     'name'  => $filename,
@@ -229,6 +251,9 @@ class Mail_Parse {
             return array($file);
         }
 
+        if($part==null)
+            $part=$this->getStruct();
+
         $files=array();
         if($part->parts){
             foreach($part->parts as $k=>$p){
diff --git a/include/class.osticket.php b/include/class.osticket.php
index f23dc824c68c541fd2799259cd4c5acdc954f794..a2d2205de0adedfcff9e722efe5745485cda00fc 100644
--- a/include/class.osticket.php
+++ b/include/class.osticket.php
@@ -288,14 +288,14 @@ class osTicket {
                 $level=3; //Debug
         }
 
-        //Alert admin if enabled...
-        if($alert)
-            $this->alertAdmin($title, $message);
-
         //Logging everything during upgrade.
         if($this->getConfig()->getLogLevel()<$level && !$force)
             return false;
 
+        //Alert admin if enabled...
+        if($alert && $this->getConfig()->getLogLevel() >= $level)
+            $this->alertAdmin($title, $message);
+
         //Save log based on system log level settings.
         $loglevel=array(1=>'Error','Warning','Debug');
         $sql='INSERT INTO '.SYSLOG_TABLE.' SET created=NOW(), updated=NOW() '
@@ -367,63 +367,28 @@ class osTicket {
                 || !strcasecmp($_SERVER['DOCUMENT_ROOT'], $dir))
             return '/';
 
-        /* If DOCUMENT_ROOT is set and isn't the same as the directory for
-         * main.inc.php, then assume that the two have something in common.
-         * For instance, you might have the following configurations
-         *
-         * +-----------------+-----------------------+------------+----------+
-         * | DOCUMENT_ROOT   | dirname(main.inc.php) | ROOT_PATH  | Comments |
-         * +-----------------+-----------------------+------------+----------+
-         * | /var/www        | /var/www/osticket     | /osticket/ | vanilla  |
-         * | /srv/httpd/www  | /httpd/www            | /          | chrooted |
-         * | /srv/httpd/www  | /httpd/www/osticket   | /osticket/ | chrooted |
-         * +-----------------+-----------------------+------------+----------+
-         *
-         * This algorithm will walk the two paths right to left, chipping
-         * away at the path of main.inc.php. When the two paths are equal,
-         * the part removed from the main.inc.php path is the ROOT_PATH
-         */
-        $dir = str_replace('\\', '/', $dir);
-        $root = str_replace('\\', '/', $_SERVER['DOCUMENT_ROOT']);
-
-        // Not chrooted
-        if(strpos($dir, $root)!==false)
-            return substr($dir, strlen($root));
-
-        // Chrooted ?
-        $path = '';
-        while (strpos($root, $dir) === false) {
-            $lastslash = strrpos($dir, '/');
-            $path = substr($dir, $lastslash) . $path;
-            $dir = substr($dir, 0, $lastslash);
-            if (!$dir)
-                break;
-        }
-
-        if($dir && $path)
-            return $path;
-
-        /* The last resort is to try and use SCRIPT_FILENAME and
-         * SCRIPT_NAME. The SCRIPT_FILENAME server variable should be the
-         * full path of the originally-executed-script. The SCRIPT_NAME
-         * should be the path of that script inside the DOCUMENT_ROOT. This
-         * is most likely useful if osTicket is run using something like
-         * Apache UserDir setting where the DOCUMENT_ROOT of Apache and the
-         * installation path of osTicket have nothing in comon.
+        /* The main idea is to try and use full-path filename of PHP_SELF and
+         * SCRIPT_NAME. The SCRIPT_NAME should be the path of that script
+         * inside the DOCUMENT_ROOT. This is most likely useful if osTicket
+         * is run using something like Apache UserDir setting where the
+         * DOCUMENT_ROOT of Apache and the installation path of osTicket
+         * have nothing in comon.
          *
          * +---------------------------+-------------------+----------------+
-         * | SCRIPT_FILENAME           | SCRIPT_NAME       | ROOT_PATH      |
+         * | PHP Script                | SCRIPT_NAME       | ROOT_PATH      |
          * +---------------------------+-------------------+----------------+
          * | /home/u1/www/osticket/... | /~u1/osticket/... | /~u1/osticket/ |
          * +---------------------------+-------------------+----------------+
          *
          * The algorithm will remove the directory of main.inc.php from
-         * SCRIPT_FILENAME. What's left should be the script executed inside
+         * as seen. What's left should be the script executed inside
          * the osTicket installation. That is removed from SCRIPT_NAME.
          * What's left is the ROOT_PATH.
          */
-        $path = substr($_SERVER['SCRIPT_FILENAME'], strlen(ROOT_DIR));
-        if($path  && ($pos=strpos($_SERVER['SCRIPT_NAME'], $path))!==false)
+        $frame = array_pop(debug_backtrace(false));
+        $file = str_replace('\\','/', $frame['file']);
+        $path = substr($file, strlen(ROOT_DIR));
+        if($path && ($pos=strpos($_SERVER['SCRIPT_NAME'], $path))!==false)
             return substr($_SERVER['SCRIPT_NAME'], 0, $pos);
 
         return null;
diff --git a/include/class.ostsession.php b/include/class.ostsession.php
index b99e5c99135589001de75e1162b5699befa88af8..78b118299f2ceac11acc85206ab212740cb8079a 100644
--- a/include/class.ostsession.php
+++ b/include/class.ostsession.php
@@ -25,7 +25,12 @@ class osTicketSession {
         if(!$this->ttl)
             $this->ttl=SESSION_TTL;
 
-        if (defined('DISABLE_SESSION') || OsticketConfig::getDBVersion())
+        session_name('OSTSESSID');
+
+        if (OsticketConfig::getDBVersion())
+            return session_start();
+
+        elseif (defined('DISABLE_SESSION'))
             return;
 
         # Cookies
@@ -35,7 +40,9 @@ class osTicketSession {
         if (isset($_SERVER['HTTP_HOST'])
                 && strpos($_SERVER['HTTP_HOST'], '.') !== false
                 && !Validator::is_ip($_SERVER['HTTP_HOST']))
-            $domain = $_SERVER['HTTP_HOST'];
+            // Remote port specification, as it will make an invalid domain
+            list($domain) = explode(':', $_SERVER['HTTP_HOST']);
+
         session_set_cookie_params(86400, ROOT_PATH, $domain,
             osTicket::is_https());
 
@@ -52,7 +59,6 @@ class osTicketSession {
         register_shutdown_function('session_write_close');
 
         //Start the session.
-        session_name('OSTSESSID');
         session_start();
     }
 
diff --git a/include/class.pdf.php b/include/class.pdf.php
index f210149346c7d36d561bdca8bc3fb9ad59771226..ea658378685c04f8345a5e3e73d7042499b1eeb1 100644
--- a/include/class.pdf.php
+++ b/include/class.pdf.php
@@ -14,7 +14,7 @@
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
 
-define('THIS_DIR', str_replace('\\\\', '/', realpath(dirname(__FILE__))) . '/'); //Include path..
+define('THIS_DIR', str_replace('\\', '/', realpath(dirname(__FILE__))) . '/'); //Include path..
 define('FPDF_DIR', THIS_DIR . 'fpdf/');
 define('FPDF_FONTPATH', FPDF_DIR . 'font/'); //fonts directory.
 require (FPDF_DIR . 'fpdf.php');
@@ -48,23 +48,46 @@ class Ticket2PDF extends FPDF
         return $this->ticket;
     }
 
+    function getLogoFile() {
+        global $ost;
+
+        if (!function_exists('imagecreatefromstring')
+                || (!($logo = $ost->getConfig()->getClientLogo()))) {
+            return INCLUDE_DIR.'fpdf/print-logo.png';
+        }
+
+        $tmp = tempnam("", 'pdf') . '.jpg';
+        $img = imagecreatefromstring($logo->getData());
+        // Handle transparent images with white background
+        $img2 = imagecreatetruecolor(imagesx($img), imagesy($img));
+        $white = imagecolorallocate($img2, 255, 255, 255);
+        imagefill($img2, 0, 0, $white);
+        imagecopy($img2, $img, 0, 0, 0, 0, imagesx($img), imagesy($img));
+        imagejpeg($img2, $tmp);
+        return $tmp;
+    }
+
 	//report header...most stuff are hard coded for now...
 	function Header() {
         global $cfg;
 
 		//Common header
-        $this->Ln(2);
+        $logo = $this->getLogoFile();
+		$this->Image($logo, null, $this->tMargin, 0, 20);
+        if (strpos($logo, INCLUDE_DIR) === false)
+            unlink($logo);
 		$this->SetFont('Times', 'B', 16);
-		$this->Image(FPDF_DIR . 'print-logo.png', null, 10, 0, 20);
-		$this->SetX(200, 15);
-		$this->Cell(0, 15, $cfg->getTitle(), 0, 1, 'R', 0);
-		//$this->SetY(40);
+		$this->SetY($this->tMargin + 20);
         $this->SetX($this->lMargin);
-        $this->Cell(0, 3, '', "B", 2, 'L');
+        $this->Cell(0, 0, '', "B", 2, 'L');
+		$this->Ln(1);
+        $this->SetFont('Arial', 'B',10);
+        $this->Cell(0, 5, $cfg->getTitle(), 0, 0, 'L');
         $this->SetFont('Arial', 'I',10);
-        $this->Cell(0, 5, 'Generated on '.Format::date($cfg->getDateTimeFormat(), Misc::gmtime(), $_SESSION['TZ_OFFSET'], $_SESSION['TZ_DST']), 0, 0, 'L');
-        $this->Cell(0, 5, 'Date & Time based on GMT '.$_SESSION['TZ_OFFSET'], 0, 1, 'R');
-		$this->Ln(10);
+        $this->Cell(0, 5, Format::date($cfg->getDateTimeFormat(), Misc::gmtime(),
+            $_SESSION['TZ_OFFSET'], $_SESSION['TZ_DST'])
+            .' GMT '.$_SESSION['TZ_OFFSET'], 0, 1, 'R');
+		$this->Ln(5);
 	}
 
 	//Page footer baby
@@ -91,11 +114,21 @@ class Ticket2PDF extends FPDF
     }
 
     function _utf8($text) {
+        // Assume text is in utf-8 charset
+        $flags = ENT_COMPAT;
+        if (phpversion() >= '5.4.0')
+            $flags |= ENT_HTML401;
+
+        // Assume text in the database is HTML
+        $text = html_entity_decode($text, $flags, 'UTF-8');
 
-        if(function_exists('iconv'))
+        if (function_exists('iconv'))
             return iconv('UTF-8', 'windows-1252', $text);
+        elseif (function_exists('utf8_decode'))
+            return utf8_decode($text);
 
-        return utf8_encode($text);
+        // XXX: FPDF does not support UTF-8 encoding
+        return $text;
     }
 
     function _print() {
diff --git a/include/class.thread.php b/include/class.thread.php
index c31a1915165bfcc2ffc983da3c91083587a7f324..29e1b1d574c44e79bc8c1045b3fbf021fc58f3c4 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -528,12 +528,15 @@ Class ThreadEntry {
             'reply_to' => $this,
         );
 
+        if (isset($mailinfo['attachments']))
+            $vars['attachments'] = $mailinfo['attachments'];
+
         $body = $mailinfo['message'];
 
         // Disambiguate if the user happens also to be a staff member of the
         // system. The current ticket owner should _always_ post messages
         // instead of notes or responses
-        if ($mailinfo['email'] == $ticket->getEmail()) {
+        if (strcasecmp($mailinfo['email'], $ticket->getEmail()) == 0) {
             $vars['message'] = $body;
             return $ticket->postMessage($vars, 'Email');
         }
@@ -544,7 +547,7 @@ Class ThreadEntry {
             $vars['note'] = $body;
             return $ticket->postNote($vars, $errors, $poster);
         }
-        elseif (Email::lookupByEmail($mailinfo['email'])) {
+        elseif (Email::getIdByEmail($mailinfo['email'])) {
             // Don't process the email -- it came FROM this system
             return true;
         }
@@ -579,17 +582,23 @@ Class ThreadEntry {
         if(!$vars || !$vars['mid'])
             return 0;
 
-        $sql='INSERT INTO '.TICKET_EMAIL_INFO_TABLE
-            .' SET message_id='.db_input($this->getId()) //TODO: change it to thread_id
-            .', email_mid='.db_input($vars['mid']); //TODO: change it to mid.
-        if (isset($vars['header']))
-            $sql .= ', headers='.db_input($vars['header']);
-
         $this->ht['email_mid'] = $vars['mid'];
 
-        return db_query($sql)?db_insert_id():0;
+        $header = false;
+        if (isset($vars['header']))
+            $header = $vars['header'];
+        self::logEmailHeaders($this->getId(), $vars['mid'], $header);
     }
 
+    /* static */
+    function logEmailHeaders($id, $mid, $header=false) {
+        $sql='INSERT INTO '.TICKET_EMAIL_INFO_TABLE
+            .' SET message_id='.db_input($id) //TODO: change it to thread_id
+            .', email_mid='.db_input($mid); //TODO: change it to message_id.
+        if ($header)
+            $sql .= ', headers='.db_input($header);
+        return db_query($sql)?db_insert_id():0;
+    }
 
     /* variables */
 
@@ -640,28 +649,39 @@ Class ThreadEntry {
      *  - "in-reply-to" => Message-Id the email is a direct response to
      *  - "references" => List of Message-Id's the email is in response
      *  - "subject" => Find external ticket number in the subject line
+     *
+     *  seen (by-ref:bool) a flag that will be set if the message-id was
+     *      positively found, indicating that the message-id has been
+     *      previously seen. This is useful if no thread-id is associated
+     *      with the email (if it was rejected for instance).
      */
-    function lookupByEmailHeaders($mailinfo) {
+    function lookupByEmailHeaders($mailinfo, &$seen=false) {
         // Search for messages using the References header, then the
         // in-reply-to header
-        $search = 'SELECT message_id FROM '.TICKET_EMAIL_INFO_TABLE
+        $search = 'SELECT message_id, email_mid FROM '.TICKET_EMAIL_INFO_TABLE
                . ' WHERE email_mid=%s ORDER BY message_id DESC';
 
-        if ($id = db_result(db_query(
-                sprintf($search, db_input($mailinfo['mid'])))))
+        if (list($id, $mid) = db_fetch_row(db_query(
+                sprintf($search, db_input($mailinfo['mid']))))) {
+            $seen = true;
             return ThreadEntry::lookup($id);
+        }
 
         foreach (array('mid', 'in-reply-to', 'references') as $header) {
             $matches = array();
             if (!isset($mailinfo[$header]) || !$mailinfo[$header])
                 continue;
             // Header may have multiple entries (usually separated by
-            // semi-colons (;))
+            // spaces ( )
             elseif (!preg_match_all('/<[^>@]+@[^>]+>/', $mailinfo[$header],
                         $matches))
                 continue;
 
-            foreach ($matches[0] as $mid) {
+            // The References header will have the most recent message-id
+            // (parent) on the far right.
+            // @see rfc 1036, section 2.2.5
+            // @see http://www.jwz.org/doc/threading.html
+            foreach (array_reverse($matches[0]) as $mid) {
                 $res = db_query(sprintf($search, db_input($mid)));
                 while (list($id) = db_fetch_row($res)) {
                     if ($t = ThreadEntry::lookup($id))
@@ -671,11 +691,16 @@ Class ThreadEntry {
         }
 
         // Search for ticket by the [#123456] in the subject line
+        // This is the last resort -  emails must match to avoid message
+        // injection by third-party.
         $subject = $mailinfo['subject'];
         $match = array();
-        if ($subject && preg_match("/\[#([0-9]{1,10})\]/", $subject, $match))
+        if ($subject && $mailinfo['email']
+                && preg_match("/\[#([0-9]{1,10})\]/", $subject, $match)
+                && ($tid = Ticket::getIdByExtId((int)$match[1], $mailinfo['email']))
+                )
             // Return last message for the thread
-            return Message::lastByExtTicketId((int)$match[1]);
+            return Message::lastByTicketId($tid);
 
         return null;
     }
@@ -780,15 +805,16 @@ class Message extends ThreadEntry {
                 )?$m:null;
     }
 
-    function lastByExtTicketId($ticketId) {
-        $sql = 'SELECT thread.id FROM '.TICKET_THREAD_TABLE
-            .' thread JOIN '.TICKET_TABLE.' ticket ON (ticket.ticket_id = thread.ticket_id)
-                WHERE thread_type=\'M\' AND ticket.ticketID = '.db_input($ticketId)
+    function lastByTicketId($ticketId) {
+
+        $sql=' SELECT thread.id FROM '.TICKET_THREAD_TABLE.' thread '
+            .' WHERE thread_type=\'M\' AND thread.ticket_id = '.db_input($ticketId)
             .' ORDER BY thread.id DESC LIMIT 1';
-        if (($res = db_query($sql)) && (list($id) = db_fetch_row($res)))
+
+        if (($res = db_query($sql)) && ($id = db_result($res)))
             return Message::lookup($id);
-        else
-            return null;
+
+        return null;
     }
 }
 
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 85b7e3b3820d447ba02c810fdbf975e899887022..294291f65cd079ae43e756bdeca738abc3011662 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -471,7 +471,11 @@ class Ticket {
     }
 
     function getLastMessage() {
-        return Message::lookup($this->getLastMsgId(), $this->getId());
+
+        if($this->getLastMsgId())
+            return Message::lookup($this->getLastMsgId(), $this->getId());
+
+        return Message::lastByTicketId($this->getId());
     }
 
     function getThread() {
diff --git a/include/fpdf/print-logo.png b/include/fpdf/print-logo.png
index 6526ebe0a15f393382e0339b38cef8422d7ff471..1d8b7d61d2cda843409c22cc19db77aafd5981ac 100644
Binary files a/include/fpdf/print-logo.png and b/include/fpdf/print-logo.png differ
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index d67243af734c466eebb31a264a53d013fe3356c0..3d119fb8ce5376675e570acf45799900eb683911 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -294,7 +294,7 @@ if(!$cfg->showNotesInline()) { ?>
                 <th width="640">
                     <?php
                     echo sprintf('%s <em>posted by <b>%s</b></em>',
-                            Format::htmlchars($note['title']),
+                            $note['title'],
                             Format::htmlchars($note['poster']));
                     ?>
                 </th>
@@ -338,7 +338,7 @@ if(!$cfg->showNotesInline()) { ?>
         <table class="<?php echo $threadTypes[$entry['thread_type']]; ?>" cellspacing="0" cellpadding="1" width="940" border="0">
             <tr>
                 <th width="200"><?php echo Format::db_datetime($entry['created']);?></th>
-                <th width="440"><span><?php echo Format::htmlchars($entry['title']); ?></span></th>
+                <th width="440"><span><?php echo $entry['title']; ?></span></th>
                 <th width="300" class="tmeta"><?php echo Format::htmlchars($entry['poster']); ?></th>
             </tr>
             <tr><td colspan=3><?php echo Format::display($entry['body']); ?></td></tr>
diff --git a/include/staff/tpl.inc.php b/include/staff/tpl.inc.php
index efde7fa537a4b25cba6fda11e0ee5e338a56bd1c..cfc84132a748a85a6d3c1af32e26d1b15e17ae17 100644
--- a/include/staff/tpl.inc.php
+++ b/include/staff/tpl.inc.php
@@ -29,8 +29,7 @@ if (is_a($template, EmailTemplateGroup)) {
     $msgtemplates=$template->getGroup()->all_names;
     $info=array_merge(array('subject'=>$template->getSubject(), 'body'=>$template->getBody()),$info);
 }
-$info['tpl']=($info['tpl'] && $msgtemplates[$info['tpl']])?$info['tpl']:'ticket.autoresp';
-$tpl=$msgtemplates[$info['tpl']];
+$tpl=$msgtemplates[$selected];
 
 ?>
 <h2>Email Template Message - <span><?php echo $name; ?></span></h2>
diff --git a/setup/cli/modules/deploy.php b/setup/cli/modules/deploy.php
index 4d48689927d1cd6fec2d087421229e3520fa23b9..77798797b9e5aebd2fd3810af570cf0263170df6 100644
--- a/setup/cli/modules/deploy.php
+++ b/setup/cli/modules/deploy.php
@@ -16,6 +16,10 @@ class Deployment extends Unpacker {
             'action'=>'store_true',
             'help'=>'Don\'t actually deploy new code. Just show the files
                 that would be copied');
+        $this->options['setup'] = array('-s','--setup',
+            'action'=>'store_true',
+            'help'=>'Deploy the setup folder. Useful for deploying for new
+                installations.');
         # super(*args);
         call_user_func_array(array('parent', '__construct'), func_get_args());
     }
@@ -52,10 +56,14 @@ class Deployment extends Unpacker {
         # Locate the upload folder
         $root = $this->find_root_folder();
 
+        $exclusions = array("$root/include", "$root/.git*",
+            "*.sw[a-z]","*.md", "*.txt");
+        if (!$options['setup'])
+            $exclusions[] = "$root/setup";
+
         # Unpack everything but the include/ folder
         $this->unpackage("$root/{,.}*", $this->destination, -1,
-            array("$root/setup", "$root/include", "$root/.git*",
-                "*.sw[a-z]","*.md", "*.txt"));
+            $exclusions);
         # Unpack the include folder
         $this->unpackage("$root/include/{,.}*", $include, -1,
             array("*/include/ost-config.php"));
diff --git a/setup/cli/package.php b/setup/cli/package.php
index 83a63d27dc4fda0cde37f69e566613df224d42d9..f61d9792d934b6585c42438ee531c529bd82efcf 100755
--- a/setup/cli/package.php
+++ b/setup/cli/package.php
@@ -17,6 +17,10 @@ function get_osticket_root_path() {
     return realpath($start);
 }
 
+function run_tests($root) {
+    return (require "$root/setup/test/run-tests.php");
+}
+
 # Check PHP syntax across all php files
 function glob_recursive($pattern, $flags = 0) {
     $files = glob($pattern, $flags);
@@ -64,6 +68,10 @@ function package($pattern, $destination, $recurse=false, $exclude=false) {
     }
 }
 
+# Run tests before continuing
+if (run_tests($root) > 0)
+    die("Regression tests failed. Cowardly refusing to package\n");
+
 # Create the stage folder for the install files
 if (!is_dir($stage_path))
     mkdir($stage_path);
diff --git a/setup/setup.inc.php b/setup/setup.inc.php
index addbb4fddc8c57a2c35643a8cae985d2c9823ab6..f0fee2989584117a279d36376066e990b62bb4ce 100644
--- a/setup/setup.inc.php
+++ b/setup/setup.inc.php
@@ -15,7 +15,7 @@
 **********************************************************************/
 
 #This  version - changed on every release
-define('THIS_VERSION', '1.7.0+');
+define('THIS_VERSION', '1.7-git');
 
 #inits - error reporting.
 $error_reporting = E_ALL & ~E_NOTICE;
diff --git a/setup/test/run-tests.php b/setup/test/run-tests.php
index 0f42dd7d024f04bf03d87a8b5611a3b658c6953e..3a3df3a1dd09702d7f74bf770b080928dae5bf43 100644
--- a/setup/test/run-tests.php
+++ b/setup/test/run-tests.php
@@ -4,25 +4,29 @@ if (php_sapi_name() != 'cli') exit();
 
 require_once "tests/class.test.php";
 
-function get_osticket_root_path() {
-    # Hop up to the root folder
-    $start = dirname(__file__);
-    for (;;) {
-        if (file_exists($start . '/main.inc.php')) break;
-        $start .= '/..';
+if (!function_exists('get_osticket_root_path')) {
+    function get_osticket_root_path() {
+        # Hop up to the root folder
+        $start = dirname(__file__);
+        for (;;) {
+            if (file_exists($start . '/main.inc.php')) break;
+            $start .= '/..';
+        }
+        return realpath($start);
     }
-    return realpath($start);
 }
 $root = get_osticket_root_path();
 
-# Check PHP syntax across all php files
-function glob_recursive($pattern, $flags = 0) {
-    $files = glob($pattern, $flags);
-    foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
-        $files = array_merge($files,
-            glob_recursive($dir.'/'.basename($pattern), $flags));
+if (!function_exists('glob_recursive')) {
+    # Check PHP syntax across all php files
+    function glob_recursive($pattern, $flags = 0) {
+        $files = glob($pattern, $flags);
+        foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
+            $files = array_merge($files,
+                glob_recursive($dir.'/'.basename($pattern), $flags));
+        }
+        return $files;
     }
-    return $files;
 }
 
 $fails = array();
@@ -37,6 +41,7 @@ function show_fails() {
             $script = str_replace($root.'/', '', $script);
             print("$test: $message @ $script:$line\n");
         }
+        return count($fails);
     }
 }
 if (function_exists('pcntl_signal')) {
@@ -44,8 +49,7 @@ if (function_exists('pcntl_signal')) {
     function show_fails_on_ctrlc() {
         while (@ob_end_flush());
         print("\n");
-        show_fails();
-        exit();
+        exit(show_fails());
     }
     pcntl_signal(SIGINT, 'show_fails_on_ctrlc');
 }
@@ -64,4 +68,10 @@ foreach (glob_recursive(dirname(__file__)."/tests/test.*.php") as $t) {
 }
 show_fails();
 
+// If executed directly expose the fail count to a shell script
+global $argv;
+if (!strcasecmp(basename($argv[0]), basename(__file__)))
+    exit(count($fails));
+else
+    return count($fails);
 ?>
diff --git a/setup/test/tests/class.test.php b/setup/test/tests/class.test.php
index 80a07b87b31acf34126712a132139c5c55c2f3ac..5ce1297619714533c19bdfd7f2c083b4b9d255b8 100644
--- a/setup/test/tests/class.test.php
+++ b/setup/test/tests/class.test.php
@@ -10,6 +10,7 @@ class Test {
         '/include/PasswordHash.php',
         '/include/pear/',
         '/include/Spyc.php',
+        '/setup/cli/stage/',
     );
 
     function Test() {
diff --git a/setup/test/tests/stubs.php b/setup/test/tests/stubs.php
new file mode 100644
index 0000000000000000000000000000000000000000..38cd382cdfc0810e0698446496a35f92bdd4cc5d
--- /dev/null
+++ b/setup/test/tests/stubs.php
@@ -0,0 +1,15 @@
+<?php
+
+class mysqli {
+
+    function query() {}
+    function real_escape_string() {}
+    function fetch_row() {}
+
+}
+
+class ReflectionClass {
+    function getMethods() {}
+}
+
+?>