diff --git a/config.php.in b/config.php.in index cae1079..1aa24fb 100644 --- a/config.php.in +++ b/config.php.in @@ -277,6 +277,7 @@ $config['DECIMAL_SEPARATOR'] = "."; // See https://www.php.net/manual/en/function.number-format $config['THOUSANDS_SEPARATOR'] = ","; // for the format options +$config['ENABLE_GB2312_FIX'] = 1; $config['FROM_LENGTH_TO_SHOW'] = 28; @@ -434,7 +435,7 @@ define('TABLE_DELETED', 'deleted'); define('VIEW_MESSAGES', 'v_messages'); -define('EOL', "\n"); +define('EOL', "\r\n"); define('DIR_SYSTEM', DIR_BASE . 'system/'); define('DIR_MODEL', DIR_BASE . 'model/'); diff --git a/webui/model/mail/mail.php b/webui/model/mail/mail.php index c9bdb74..f589342 100644 --- a/webui/model/mail/mail.php +++ b/webui/model/mail/mail.php @@ -8,6 +8,9 @@ require_once 'Zend/Mail/Protocol/Smtp.php'; require_once 'Zend/Mail/Protocol/Smtp/Auth/Login.php'; + // Workaround for the zend framework + $msg = str_replace("\r", "", $msg); + $ok = 0; if($to == "" || strlen($msg) < 30){ return $ok; } @@ -118,7 +121,7 @@ $s = strstr($hdr, "Subject:"); if($s) { $l1 = strlen($s); - $l2 = strlen(strstr($s, "\n")); + $l2 = strlen(strstr($s, EOL)); if($l1 > $l2 + 10) { $subject = substr($s, 0, $l1 - $l2) . EOL; } diff --git a/webui/model/search/message.php b/webui/model/search/message.php index 395fbfd..4d132ea 100644 --- a/webui/model/search/message.php +++ b/webui/model/search/message.php @@ -198,7 +198,7 @@ 'cc' => $cc, 'subject' => $this->highlight_search_terms($subject, $terms), 'date' => $date, - 'message' => $this->message['text/html'] ? $this->message['text/html'] : $this->message['text/plain'], + 'message' => $this->message['text/html'] ? $this->highlight_search_terms($this->message['text/html']) : $this->highlight_search_terms($this->message['text/plain']), 'has_journal' => $has_journal, 'verification' => $this->verification ); @@ -220,6 +220,10 @@ } } + if($html == 0) { + $s = preg_replace("/THE_BREAK_HTML_TAG/", "
", $s); + } + if(count($terms) <= 0) { return $s; } if($html == 0) { diff --git a/webui/system/helper/mime.php b/webui/system/helper/mime.php index 9e5d585..cf00571 100644 --- a/webui/system/helper/mime.php +++ b/webui/system/helper/mime.php @@ -5,6 +5,12 @@ const HEADER_FIELDS = ['from', 'to', 'cc', 'subject', 'date']; + public static function normalize_message($message) { + $a = preg_split("/\r?\n/", $message); + return implode(EOL, $a); + } + + public static function parseMessage($message, &$result) { self::splitMessage($message, $headers, $body); @@ -18,10 +24,10 @@ self::parseMessage($body, $result); } else { - $result[] = array( + $result[] = [ 'headers' => $headers, 'body' => $body - ); + ]; } return; @@ -39,7 +45,7 @@ } else { if(in_array($headers['content-type']['type'], ["text/plain", "text/html"])) { - $result[] = array('headers' => $headers, 'body' => $body); + $result[] = ['headers' => $headers, 'body' => $body]; } else if($headers['content-type']['type'] == "message/rfc822") { self::parseMessage($body, $result); @@ -51,23 +57,21 @@ public static function splitMime($body, $boundary) { $start = 0; - $res = array(); - - $body = self::remove_LF($body); + $res = []; // Extract the mime parts excluding the boundary itself - $p = strpos($body, '--' . $boundary . "\n", $start); + $p = strpos($body, '--' . $boundary . EOL, $start); if($p === false) { // no parts found! - return array(); + return []; } // Position after first boundary line $start = $p + 3 + strlen($boundary); - while(($p = strpos($body, '--' . $boundary . "\n", $start)) !== false) { + while(($p = strpos($body, '--' . $boundary . EOL, $start)) !== false) { $res[] = substr($body, $start, $p-$start); $start = $p + 3 + strlen($boundary); } @@ -76,7 +80,7 @@ $p = strpos($body, '--' . $boundary . '--', $start); if($p === false) { - return array(); + return []; } // The remaining part also needs to be parsed: @@ -86,22 +90,22 @@ } - public static function splitMessage($message, &$headers, &$body, $EOL = "\n") { + public static function splitMessage($message, &$headers, &$body) { self::splitMessageRaw($message, $headers, $journal, $body); $headers = self::splitHeaders($headers); } - public static function splitMessageRaw($message, &$headers, &$journal, &$body, $EOL = "\n") { + public static function splitMessageRaw($message, &$headers, &$journal, &$body) { $headers = []; $body = ''; - $message = self::remove_LF($message); + $message = self::normalize_message($message); // Find an empty line between headers and body, otherwise we got a header-only message - if(strpos($message, $EOL . $EOL)) { - list($headers, $body) = explode($EOL . $EOL, $message, 2); + if(strpos($message, EOL . EOL)) { + list($headers, $body) = explode(EOL . EOL, $message, 2); // Check if the header is actually a journal header $headers_array = self::splitHeaders($headers); @@ -113,7 +117,7 @@ if(count($parts) >= 2) { self::splitMessageRaw($parts[0], $s, $j, $journal); - $i = strpos($parts[1], $EOL . $EOL); + $i = strpos($parts[1], EOL . EOL); $msg = substr($parts[1], $i); $i = 0; @@ -141,26 +145,16 @@ } - public static function removeJournal(&$message, $EOL = "\n") { + public static function removeJournal(&$message) { $has_journal = 0; - $crlfs = substr_count($message, "\r\n"); - self::splitMessageRaw($message, $headers, $journal, $body); if($journal) { $has_journal = 1; } - // If the message has >10 CRLF sequences, then we assume - // that we need to restore the removed LF characters - if($crlfs > 10) { - $headers = str_replace("\n", "\r\n", $headers); - $body = str_replace("\n", "\r\n", $body); - $EOL = "\r\n"; - } - - $message = $headers . $EOL . $EOL . $body; + $message = $headers . EOL . EOL . $body; return $has_journal; } @@ -188,7 +182,7 @@ continue; } - $headers[$lower] = array($headers[$lower], $header); + $headers[$lower] = [$headers[$lower], $header]; } // Add some default values, if they are missing @@ -204,9 +198,21 @@ for($i=0; $i $v) { - - if(strchr($v, "\n")) { - $result[$k] = explode("\n", $v); + if(strchr($v, EOL)) { + $result[$k] = explode(EOL, $v); } } @@ -277,7 +282,7 @@ public static function splitContentType($field = '') { - $split = array(); + $split = []; $what = 'type'; $field = $what . '=' . $field; @@ -285,7 +290,7 @@ return $split; } - $split = array(); + $split = []; foreach ($matches[1] as $key => $name) { $name = strtolower($name); if($matches[2][$key][0] == '"') { @@ -299,13 +304,7 @@ } - public static function remove_LF($message = '') { - return str_replace("\r", "", $message); - //return preg_replace("/\r/", "", $message); - } - - - public static function getBoundary($headers = array()) { + public static function getBoundary($headers = []) { if(isset($headers['content-type']['boundary'])) { return $headers['content-type']['boundary']; } @@ -314,7 +313,7 @@ } - public static function fixMimeBodyPart($headers = array(), $body = '') { + public static function fixMimeBodyPart($headers = [], $body = '') { if(isset($headers['content-transfer-encoding'])) { if(strtolower($headers['content-transfer-encoding']) == 'quoted-printable') { @@ -327,16 +326,17 @@ } if(isset($headers['content-type']['charset'])) { - if(strtolower($headers['content-type']['charset']) == 'gb2312') { + if(ENABLE_GB2312_FIX && strtolower($headers['content-type']['charset']) == 'gb2312') { $headers['content-type']['charset'] = 'GBK'; } + $body = iconv($headers['content-type']['charset'], 'utf-8' . '//IGNORE', $body); } if(strtolower($headers['content-type']['type']) == 'text/plain') { $body = self::escape_lt_gt_symbols($body); - $body = preg_replace("/\n/", "
\n", $body); - $body = "\n" . self::printNicely($body); + $body = preg_replace("/\n/", "THE_BREAK_HTML_TAG\n", $body); + $body = EOL . self::printNicely($body); } return $body; @@ -361,9 +361,9 @@ $nice .= $x[$i] . " "; $k += strlen($x[$i]); - if(strstr($x[$i], "\n")){ $k = 0; } + if(strstr($x[$i], EOL)){ $k = 0; } - if($k > 70){ $nice .= "\n"; $k = 0; } + if($k > 70){ $nice .= EOL; $k = 0; } } return $nice; diff --git a/webui/tests/ParseMessageTest.php b/webui/tests/ParseMessageTest.php index fbf67cd..80469b6 100644 --- a/webui/tests/ParseMessageTest.php +++ b/webui/tests/ParseMessageTest.php @@ -11,17 +11,17 @@ public function providerTestParseMessage() { return [ - ["1.eml", 1, ["Liebe Gueste,\n\ndie Einarbeitung der Rechen- und Summenfunktionen ins RK-Formular"]], - ["2.eml", 1, ["Hallo!\nDie seltsamen Zeilenumbr=C3=BCche treten tats=C3=A4chlich auf."]], - ["3.eml", 1, ["\n\nCan we discuss? Send Reply For more information, THANKS."]], + ["1.eml", 1, ["Liebe Gueste,\r\n\r\ndie Einarbeitung der Rechen- und Summenfunktionen ins RK-Formular"]], + ["2.eml", 1, ["Hallo!\r\nDie seltsamen Zeilenumbr=C3=BCche treten tats=C3=A4chlich auf."]], + ["3.eml", 1, ["\r\n\r\nCan we discuss? Send Reply For more information, THANKS."]], ["4.eml", 2, ["=0D=0A=0D=0A=0D=0A=0D=0A", "=0D=0A"]], - ["8.eml", 2, ["Hello,\n\nYou have received a newsletter from Chemol Travel.", ""]], + ["7.eml", 2, ["Mai ajánlat: \r\n \r\n Exkluzív!", ""]], + ["8.eml", 2, ["Hello,\r\n\r\nYou have received a newsletter from Chemol Travel.", ""]], ]; - } + } /** diff --git a/webui/tests/SplitMessageTest.php b/webui/tests/SplitMessageTest.php index 8947de6..8cc9e88 100644 --- a/webui/tests/SplitMessageTest.php +++ b/webui/tests/SplitMessageTest.php @@ -1,70 +1,65 @@ 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', SUBJECT => 'test', CONTENT_TYPE => array('type' => TEXT_PLAIN)), - THIS_IS_A_TEST], + ["From: aaa\r\nTo:bbb\r\nSubject: test\r\n\r\nThis is a test", + array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', 'subject' => 'test', 'content-type' => array('type' => 'text/plain')), + "This is a test"], - ["From: aaa\r\nTo:bbb\r\nCC ccc\r\nSubject: test\r\n\r\n" . THIS_IS_A_TEST, - array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', SUBJECT => 'test', CONTENT_TYPE => array('type' => TEXT_PLAIN)), - THIS_IS_A_TEST], + ["From: aaa\r\nSender: alala@aaa\r\nTo:bbb\r\nCC ccc\r\nSubject: test\r\n\r\nThis is a test", + array('sender' => 'alala@aaa', 'from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', 'subject' => 'test', 'content-type' => array('type' => 'text/plain')), + "This is a test"], - ["From: aaa\nTo:bbb\nSubject: test\n\n" . THIS_IS_A_TEST, - array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', SUBJECT => 'test', CONTENT_TYPE => array('type' => TEXT_PLAIN)), - THIS_IS_A_TEST], + ["From: aaa\nTo:bbb\nSubject: test\n\nThis is a test", + array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', 'subject' => 'test', 'content-type' => array('type' => 'text/plain')), + "This is a test"], - ["From: aaa\r\nTo:bbb\r\nSubject: test\r\n\r\n\r\n\r\n" . THIS_IS_A_TEST . "\nAaa\n", - array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', SUBJECT => 'test', CONTENT_TYPE => array('type' => TEXT_PLAIN)), - "\n\n" . THIS_IS_A_TEST . "\nAaa\n"], + ["From: aaa\r\nTo:bbb\r\nSubject: test\r\n\r\n\r\n\r\nThis is a test\nAaa\n", + array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', 'subject' => 'test', 'content-type' => array('type' => 'text/plain')), + "\r\n\r\nThis is a test\r\nAaa\r\n"], - ["From: aaa\r\nTo:bbb\r\nSubject: test\r\nContent-type: text/html\r\n\r\n\r\n" . THIS_IS_A_TEST . "\nAaa\n", - array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', SUBJECT => 'test', CONTENT_TYPE => array('type' => 'text/html')), - "\n" . THIS_IS_A_TEST . "\nAaa\n"], + ["From: aaa\r\nTo:bbb\r\nSubject: test\r\nContent-type: text/html\r\n\r\n\r\nThis is a test\nAaa\n", + array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', 'subject' => 'test', 'content-type' => array('type' => 'text/html')), + "\r\nThis is a test\r\nAaa\r\n"], - ["From: aaa\nTo:bbb\nSubject: test\nContent-Type: text/plain\n\n" . THIS_IS_A_TEST, - array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', SUBJECT => 'test', CONTENT_TYPE => array('type' => TEXT_PLAIN)), - THIS_IS_A_TEST], + ["From: aaa\nTo:bbb\nSubject: test\nContent-Type: text/plain\n\nThis is a test", + array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', 'subject' => 'test', 'content-type' => array('type' => 'text/plain')), + "This is a test"], - ["From: aaa\nTo:bbb\nSubject: test\nDate: Sun, 17 Apr 2016 22:40:03 +0800\nDKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=chemoltravel.hu; s=ml;\n\tt=1471888357; bh=A/l/HLQe3HM4Xc4jFxAmhaWVCMU=;\n\th=Date:To:From:Subject:Sender:From:To:Subject:Date;\n\tb=JlEqXiAKBOoT/YyXKTMsXnEphh2J6sXxgNmbKbGybjo3cU1rgQEL0m1h26gl5AaBP\nContent-Type: " . TEXT_PLAIN . "\n\n" . THIS_IS_A_TEST, - array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', SUBJECT => 'test', 'date' => 'Sun, 17 Apr 2016 22:40:03 +0800', 'dkim-signature' => 'v=1; a=rsa-sha1; c=relaxed/relaxed; d=chemoltravel.hu; s=ml; t=1471888357; bh=A/l/HLQe3HM4Xc4jFxAmhaWVCMU=; h=Date:To:From:Subject:Sender:From:To:Subject:Date; b=JlEqXiAKBOoT/YyXKTMsXnEphh2J6sXxgNmbKbGybjo3cU1rgQEL0m1h26gl5AaBP', CONTENT_TYPE => array('type' => TEXT_PLAIN)), - THIS_IS_A_TEST], + ["From: aaa\nTo:bbb\nSubject: test\nDate: Sun, 17 Apr 2016 22:40:03 +0800\nDKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=chemoltravel.hu; s=ml;\n\tt=1471888357; bh=A/l/HLQe3HM4Xc4jFxAmhaWVCMU=;\n\th=Date:To:From:Subject:Sender:From:To:Subject:Date;\n\tb=JlEqXiAKBOoT/YyXKTMsXnEphh2J6sXxgNmbKbGybjo3cU1rgQEL0m1h26gl5AaBP\nContent-Type: text/plain\n\nThis is a test", + array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'subject' => 'test', 'date' => 'Sun, 17 Apr 2016 22:40:03 +0800', 'dkim-signature' => 'v=1; a=rsa-sha1; c=relaxed/relaxed; d=chemoltravel.hu; s=ml; t=1471888357; bh=A/l/HLQe3HM4Xc4jFxAmhaWVCMU=; h=Date:To:From:Subject:Sender:From:To:Subject:Date; b=JlEqXiAKBOoT/YyXKTMsXnEphh2J6sXxgNmbKbGybjo3cU1rgQEL0m1h26gl5AaBP', 'content-type' => array('type' => 'text/plain')), + "This is a test"], - ["From: aaa\nTo:bbb\nSubject: test\nContent-Type: text/PLAIN\n\n" . THIS_IS_A_TEST, - array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', SUBJECT => 'test', CONTENT_TYPE => array('type' => TEXT_PLAIN)), - THIS_IS_A_TEST], + ["From: aaa\nTo:bbb\nSubject: test\nContent-Type: text/PLAIN\n\nThis is a test", + array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', 'subject' => 'test', 'content-type' => array('type' => 'text/plain')), + "This is a test"], - ["From: aaa\nTo:bbb\nSubject: test\nContent-Type: text/plain; charset=\"ISO-8859-1\"\n\n" . THIS_IS_A_TEST, - array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', SUBJECT => 'test', CONTENT_TYPE => array('type' => TEXT_PLAIN, 'charset' => 'ISO-8859-1')), - THIS_IS_A_TEST], + ["From: aaa\nTo:bbb\nSubject: test\nContent-Type: text/plain; charset=\"ISO-8859-1\"\n\nThis is a test", + array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', 'subject' => 'test', 'content-type' => array('type' => 'text/plain', 'charset' => 'ISO-8859-1')), + "This is a test"], - ["From: aaa\nTo:bbb\nSubject: test\nMIME-Version: 1.0\nContent-Type: multipart/alternative; boundary=\"_=_SWIFT_v4_1460476188_145aa333fc0127705a7e904aab6d1957_=_\"\n\n" . THIS_IS_A_TEST, - array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', SUBJECT => 'test', 'mime-version' => '1.0', CONTENT_TYPE => array('type' => 'multipart/alternative', 'boundary' => '_=_SWIFT_v4_1460476188_145aa333fc0127705a7e904aab6d1957_=_')), - THIS_IS_A_TEST], + ["From: aaa\nTo:bbb\nSubject: test\nMIME-Version: 1.0\nContent-Type: multipart/alternative; boundary=\"_=_SWIFT_v4_1460476188_145aa333fc0127705a7e904aab6d1957_=_\"\n\nThis is a test", + array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', 'subject' => 'test', 'mime-version' => '1.0', 'content-type' => array('type' => 'multipart/alternative', 'boundary' => '_=_SWIFT_v4_1460476188_145aa333fc0127705a7e904aab6d1957_=_')), + "This is a test"], - ["From: aaa\nTo:bbb\nSubject: test\nMIME-Version: 1.0\nContent-Type: multipart/alternative;\n boundary=\"_=_SWIFT_v4_1460476188_145aa333fc0127705a7e904aab6d1957_=_\"\n\n" . THIS_IS_A_TEST, - array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', SUBJECT => 'test', 'mime-version' => '1.0', CONTENT_TYPE => array('type' => 'multipart/alternative', 'boundary' => '_=_SWIFT_v4_1460476188_145aa333fc0127705a7e904aab6d1957_=_')), - THIS_IS_A_TEST], + ["From: aaa\nTo:bbb\nSubject: test\nMIME-Version: 1.0\nContent-Type: multipart/alternative;\n boundary=\"_=_SWIFT_v4_1460476188_145aa333fc0127705a7e904aab6d1957_=_\"\n\nThis is a test", + array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', 'subject' => 'test', 'mime-version' => '1.0', 'content-type' => array('type' => 'multipart/alternative', 'boundary' => '_=_SWIFT_v4_1460476188_145aa333fc0127705a7e904aab6d1957_=_')), + "This is a test"], - ["From: aaa\nTo:bbb\nSubject: test\nMIME-Version: 1.0\nContent-Type: multipart/related;\n\ttype=\"multipart/alternative\";\n\tboundary=\"----=_NextPart_000_0006_01D195BC.69E26510\"\n\n" . THIS_IS_A_TEST, - array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', SUBJECT => 'test', 'mime-version' => '1.0', CONTENT_TYPE => array('type' => 'multipart/alternative', 'boundary' => '----=_NextPart_000_0006_01D195BC.69E26510')), - THIS_IS_A_TEST], + ["From: aaa\nTo:bbb\nSubject: test\nMIME-Version: 1.0\nContent-Type: multipart/related;\n\ttype=\"multipart/alternative\";\n\tboundary=\"----=_NextPart_000_0006_01D195BC.69E26510\"\n\nThis is a test", + array('from' => 'aaa', 'to' => 'bbb', 'cc' => '', 'date' => '', 'subject' => 'test', 'mime-version' => '1.0', 'content-type' => array('type' => 'multipart/alternative', 'boundary' => '----=_NextPart_000_0006_01D195BC.69E26510')), + "This is a test"], - ]; - } + ]; + } /**