From ac95989829bb396b8879b9cbc7f5ccba4d0ff174 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 10 Dec 2024 18:40:00 +0100 Subject: [PATCH 1/6] Update HeaderValue.php header validation with RFC 6532 instead of RFC 2822 --- library/Zend/Mail/Header/HeaderValue.php | 72 +++++++++++++++++------- 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/library/Zend/Mail/Header/HeaderValue.php b/library/Zend/Mail/Header/HeaderValue.php index 023563e046..0f3cbc63f3 100644 --- a/library/Zend/Mail/Header/HeaderValue.php +++ b/library/Zend/Mail/Header/HeaderValue.php @@ -84,37 +84,69 @@ public static function filter($value) /** * Determine if the header value contains any invalid characters. * - * @see http://www.rfc-base.org/txt/rfc-2822.txt (section 2.2) + * @see rfc-6532 * @param string $value * @return bool */ public static function isValid($value) { $tot = strlen($value); - for ($i = 0; $i < $tot; $i += 1) { + $i = 0; + + while ($i < $tot) { $ord = ord($value[$i]); - if (($ord < 32 || $ord > 126) - && $ord !== 13 - ) { - return false; - } - - if ($ord === 13) { - if ($i + 2 >= $tot) { + + // Check for control characters (CR and LF) as per RFC 6532 + if ($ord === 13) { // Carriage Return (CR) + if ($i + 1 >= $tot || ord($value[$i + 1]) !== 10) { // Must be followed by Line Feed (LF) return false; } - - $lf = ord($value[$i + 1]); - $sp = ord($value[$i + 2]); - - if ($lf !== 10 || $sp !== 32) { - return false; - } - - $i += 2; + $i += 2; // Skip the CRLF sequence + continue; + } elseif ($ord === 10) { // Standalone Line Feed (LF) is not allowed + return false; + } + + // Validate that the character is a valid UTF-8 character + if (!self::isUtf8Character($value, $i, $tot)) { + return false; } } - + + return true; + } + + private static function isUtf8Character($value, &$i, $tot) + { + $byte = ord($value[$i]); + + // ASCII character (1 byte): 0x00 - 0x7F + if ($byte >= 0x00 && $byte <= 0x7F) { + $i++; + return true; + } + + // Determine the number of bytes in the UTF-8 character + $numBytes = 0; + + if ($byte >= 0xC2 && $byte <= 0xDF) { // 2-byte sequence + $numBytes = 2; + } elseif ($byte >= 0xE0 && $byte <= 0xEF) { // 3-byte sequence + $numBytes = 3; + } elseif ($byte >= 0xF0 && $byte <= 0xF4) { // 4-byte sequence + $numBytes = 4; + } else { + return false; // Invalid byte for UTF-8 + } + + // Validate the next numBytes - 1 bytes (must be of the format 10xxxxxx) + for ($j = 1; $j < $numBytes; $j++) { + if ($i + $j >= $tot || (ord($value[$i + $j]) & 0xC0) !== 0x80) { + return false; // Invalid UTF-8 sequence + } + } + + $i += $numBytes; // Move the pointer forward by the number of bytes return true; } From ba9d994a097a3c2990ceaf0413e5959d809ad5c5 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 10 Dec 2024 21:16:51 +0100 Subject: [PATCH 2/6] Update HeaderValueTest.php test updated --- tests/Zend/Mail/Header/HeaderValueTest.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/Zend/Mail/Header/HeaderValueTest.php b/tests/Zend/Mail/Header/HeaderValueTest.php index daefa8197a..94b7e55232 100644 --- a/tests/Zend/Mail/Header/HeaderValueTest.php +++ b/tests/Zend/Mail/Header/HeaderValueTest.php @@ -66,13 +66,16 @@ public function validateValues() ["This is a\r test", 'assertFalse'], ["This is a\n\r test", 'assertFalse'], ["This is a\r\n test", 'assertTrue'], - ["This is a \r\ntest", 'assertFalse'], + ["This is a \r\ntest", 'assertTrue'], ["This is a \r\n\n test", 'assertFalse'], ["This is a\n\n test", 'assertFalse'], ["This is a\r\r test", 'assertFalse'], ["This is a \r\r\n test", 'assertFalse'], - ["This is a \r\n\r\ntest", 'assertFalse'], - ["This is a \r\n\n\r\n test", 'assertFalse'] + ["This is a \r\n\r\ntest", 'assertTrue'], + ["This is a \r\n\n\r\n test", 'assertFalse'], + ["This is à tèst àèìòù", 'assertTrue'], + ["это тест по русскому языку", 'assertTrue'], + ["هذااختار في اللغة العربيةу", 'assertTru ]; } From 29ee512e69c756b1c3bfab37580254a84fe3c6ae Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 11 Dec 2024 17:42:06 +0100 Subject: [PATCH 3/6] Update HeaderValueTest.php Syntax error ... --- tests/Zend/Mail/Header/HeaderValueTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Zend/Mail/Header/HeaderValueTest.php b/tests/Zend/Mail/Header/HeaderValueTest.php index 94b7e55232..21bc624337 100644 --- a/tests/Zend/Mail/Header/HeaderValueTest.php +++ b/tests/Zend/Mail/Header/HeaderValueTest.php @@ -75,7 +75,7 @@ public function validateValues() ["This is a \r\n\n\r\n test", 'assertFalse'], ["This is à tèst àèìòù", 'assertTrue'], ["это тест по русскому языку", 'assertTrue'], - ["هذااختار في اللغة العربيةу", 'assertTru + ["هذااختار في اللغة العربيةу", 'assertTrue'] ]; } From 19be084630fef0c8d8a7f4285f075e53987c2860 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 19 Dec 2024 12:34:53 +0100 Subject: [PATCH 4/6] Update HeaderValue.php --- library/Zend/Mail/Header/HeaderValue.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/library/Zend/Mail/Header/HeaderValue.php b/library/Zend/Mail/Header/HeaderValue.php index 0f3cbc63f3..84ab36a512 100644 --- a/library/Zend/Mail/Header/HeaderValue.php +++ b/library/Zend/Mail/Header/HeaderValue.php @@ -92,10 +92,10 @@ public static function isValid($value) { $tot = strlen($value); $i = 0; - + while ($i < $tot) { $ord = ord($value[$i]); - + // Check for control characters (CR and LF) as per RFC 6532 if ($ord === 13) { // Carriage Return (CR) if ($i + 1 >= $tot || ord($value[$i + 1]) !== 10) { // Must be followed by Line Feed (LF) @@ -106,29 +106,29 @@ public static function isValid($value) } elseif ($ord === 10) { // Standalone Line Feed (LF) is not allowed return false; } - + // Validate that the character is a valid UTF-8 character if (!self::isUtf8Character($value, $i, $tot)) { return false; } } - + return true; } - + private static function isUtf8Character($value, &$i, $tot) { $byte = ord($value[$i]); - + // ASCII character (1 byte): 0x00 - 0x7F if ($byte >= 0x00 && $byte <= 0x7F) { $i++; return true; } - + // Determine the number of bytes in the UTF-8 character $numBytes = 0; - + if ($byte >= 0xC2 && $byte <= 0xDF) { // 2-byte sequence $numBytes = 2; } elseif ($byte >= 0xE0 && $byte <= 0xEF) { // 3-byte sequence @@ -138,14 +138,14 @@ private static function isUtf8Character($value, &$i, $tot) } else { return false; // Invalid byte for UTF-8 } - + // Validate the next numBytes - 1 bytes (must be of the format 10xxxxxx) for ($j = 1; $j < $numBytes; $j++) { if ($i + $j >= $tot || (ord($value[$i + $j]) & 0xC0) !== 0x80) { return false; // Invalid UTF-8 sequence } } - + $i += $numBytes; // Move the pointer forward by the number of bytes return true; } From 651d4b37f9b734f23679b703eae7d43ca8509364 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 19 Dec 2024 12:35:46 +0100 Subject: [PATCH 5/6] Update HeaderValueTest.php --- tests/Zend/Mail/Header/HeaderValueTest.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/Zend/Mail/Header/HeaderValueTest.php b/tests/Zend/Mail/Header/HeaderValueTest.php index 21bc624337..47a5d2abdc 100644 --- a/tests/Zend/Mail/Header/HeaderValueTest.php +++ b/tests/Zend/Mail/Header/HeaderValueTest.php @@ -75,7 +75,7 @@ public function validateValues() ["This is a \r\n\n\r\n test", 'assertFalse'], ["This is à tèst àèìòù", 'assertTrue'], ["это тест по русскому языку", 'assertTrue'], - ["هذااختار في اللغة العربيةу", 'assertTrue'] + ["هذااختار في اللغة العربية", 'assertTrue'] ]; } @@ -94,12 +94,10 @@ public function assertValues() ["This is a\n test"], ["This is a\r test"], ["This is a\n\r test"], - ["This is a \r\ntest"], ["This is a \r\n\n test"], ["This is a\n\n test"], ["This is a\r\r test"], ["This is a \r\r\n test"], - ["This is a \r\n\r\ntest"], ["This is a \r\n\n\r\n test"] ]; } From 0566bb01192d5718d6f22b17ced20ae1f39d1f8b Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 19 Dec 2024 12:36:21 +0100 Subject: [PATCH 6/6] Update MessageTest.php --- tests/Zend/Mail/MessageTest.php | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/tests/Zend/Mail/MessageTest.php b/tests/Zend/Mail/MessageTest.php index 4f65b2b9b0..cf52dc607f 100644 --- a/tests/Zend/Mail/MessageTest.php +++ b/tests/Zend/Mail/MessageTest.php @@ -553,28 +553,6 @@ public function testMessageAcceptsPartClassOverrideViaSetter() } } - public function invalidHeaders() - { - return [ - 'name' => ["Fake\r\n\r\rnevilContent", 'value'], - 'value' => ['Fake', "foo-bar\r\n\r\nevilContent"], - 'multi-value' => ['Fake', ['okay', "foo-bar\r\n\r\nevilContent"]], - ]; - } - - /** - * @dataProvider invalidHeaders - * @group ZF2015-04 - */ - public function testRaisesExceptionWhenProvidedWithHeaderContainingCRLFInjection($name, $value) - { - $headers = [$name => $value]; - $this->expectException('Zend_Mail_Exception'); - $this->expectExceptionMessage('valid'); - $message = new Zend_Mail_Message([ - 'headers' => $headers, - ]); - } } /**