diff --git a/lib/Horde/String.php b/lib/Horde/String.php index d2797bfd..94828c75 100644 --- a/lib/Horde/String.php +++ b/lib/Horde/String.php @@ -93,6 +93,10 @@ public static function convertCharset($input, $from, $to, $force = false) return $input; } + if(strlen($input) === 0) { + return $input; + } + return self::_convertCharset($input, $from, $to); } @@ -145,12 +149,7 @@ protected static function _convertCharset($input, $from, $to) /* Try iconv with transliteration. */ if (Horde_Util::extensionExists('iconv')) { - unset($php_errormsg); - ini_set('track_errors', 1); - $out = @iconv($from, $to . '//TRANSLIT', $input); - $errmsg = isset($php_errormsg); - ini_restore('track_errors'); - if (!$errmsg && $out !== false) { + if (($out = self::_convertCharsetIconv($input, $from, $to)) !== false) { return $out; } } @@ -170,6 +169,25 @@ protected static function _convertCharset($input, $from, $to) return $input; } + /** + * Internal function used to do charset transliteration with iconv. + * + * @param string $input See self::convertCharset(). + * @param string $from See self::convertCharset(). + * @param string $to See self::convertCharset(). + * + * @return mixed The converted string, or false on error. + */ + protected static function _convertCharsetIconv(string $input, string $from, string $to): string + { + error_clear_last(); + $out = @iconv($from, $to . '//TRANSLIT', $input); + if (is_null(error_get_last()) && $out !== false) { + return $out; + } + return false; + } + /** * Makes a string lowercase. * @@ -488,37 +506,77 @@ protected static function _pos( ) { if (Horde_Util::extensionExists('mbstring')) { - unset($php_errormsg); - $track_errors = ini_set('track_errors', 1); - $ret = @call_user_func('mb_' . $func, $haystack, $needle, $offset, self::_mbstringCharset($charset)); - ini_set('track_errors', $track_errors); - if (!isset($php_errormsg)) { - return $ret; + if (($out = @self::_posMbstring($haystack, $needle, $offset, $charset, $func)) !== false) { + return $out; } } if (Horde_Util::extensionExists('intl')) { - unset($php_errormsg); - $track_errors = ini_set('track_errors', 1); - $ret = self::convertCharset( - @call_user_func( - 'grapheme_' . $func, - self::convertCharset($haystack, $charset, 'UTF-8'), - self::convertCharset($needle, $charset, 'UTF-8'), - $offset - ), - 'UTF-8', - $charset - ); - ini_set('track_errors', $track_errors); - if (!isset($php_errormsg)) { - return $ret; + if(($out = @self::_posIntl($haystack, $needle, $offset, $charset, $func)) !== false) { + return $out; } } return $func($haystack, $needle, $offset); } + /** + * Internal function to perform string position searches using mbstring. + * + * @param string $haystack See self::_pos + * @param string $needle See self::_pos + * @param integer $offset See self::_pos + * @param string $charset See self::_pos + * @param string $func See self::_pos + * + * @return mixed The position of occurrence, or false on error. + */ + protected static function _posMbstring( + $haystack, $needle, $offset, $charset, $func + ) + { + error_clear_last(); + $ret = @call_user_func('mb_' . $func, $haystack, $needle, $offset, self::_mbstringCharset($charset)); + if (is_null(error_get_last())) { + return $ret; + } + + return false; + } + + /** + * Internal function to perform string position searches using intl. + * + * @param string $haystack See self::_pos + * @param string $needle See self::_pos + * @param integer $offset See self::_pos + * @param string $charset See self::_pos + * @param string $func See self::_pos + * + * @return mixed The position of occurrence, or false on error. + */ + protected static function _posIntl( + $haystack, $needle, $offset, $charset, $func + ) + { + error_clear_last(); + $ret = self::convertCharset( + @call_user_func( + 'grapheme_' . $func, + self::convertCharset($haystack, $charset, 'UTF-8'), + self::convertCharset($needle, $charset, 'UTF-8'), + $offset + ), + 'UTF-8', + $charset + ); + if (is_null(error_get_last())) { + return $ret; + } + + return false; + } + /** * Returns a string padded to a certain length with another string. * This method behaves exactly like str_pad() but is multibyte safe. diff --git a/test/Horde/Util/Mock/String.php b/test/Horde/Util/Mock/String.php new file mode 100644 index 00000000..dd70f475 --- /dev/null +++ b/test/Horde/Util/Mock/String.php @@ -0,0 +1,26 @@ + + * @category Horde + * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 + * @package Util + */ +class Horde_Util_Mock_String extends Horde_String +{ + public static function testConvertCharsetIconv(string $input, string $from, string $to) + { + return self::_convertCharsetIconv($input, $from, $to); + } + + public static function testPosMbstring(string $haystack, string $needle, int $offset, string $charset, string $func) + { + return self::_posMbstring($haystack, $needle, $offset, $charset, $func); + } + + public static function testPosIntl(string $haystack, string $needle, int $offset, string $charset, string $func) + { + return self::_posIntl($haystack, $needle, $offset, $charset, $func); + } +} diff --git a/test/Horde/Util/StringTest.php b/test/Horde/Util/StringTest.php index 039b801d..eb71d1d8 100644 --- a/test/Horde/Util/StringTest.php +++ b/test/Horde/Util/StringTest.php @@ -731,4 +731,61 @@ public function invalidUtf8Provider() ); } + /** + * @dataProvider ConvertCharsetIconvProvider + */ + public function testConvertCharsetIconv(string $input, string $from, string $to, $expected): void + { + $this->assertEquals( + $expected, + Horde_Util_Mock_String::testConvertCharsetIconv($input, $from, $to) + ); + } + + public function ConvertCharsetIconvProvider() + { + return [ + 'valid character sequence' => ['This will work.', 'UTF-8', 'ISO-8859-1', 'This will work.'], + 'illegal character in input string' => ["This is the Euro symbol '€'.", 'UTF-8', 'ISO-8859-1', false] + ]; + } + + /** + * @dataProvider posMbstringProvider + */ + public function testPosMbstring(string $haystack, string $needle, int $offset, string $charset, string $func, $expected): void + { + $this->assertEquals( + $expected, + Horde_Util_Mock_String::testPosMbstring($haystack, $needle, $offset, $charset, $func) + ); + } + + public function posMbstringProvider() + { + return [ + 'valid character sequence' => ['Some random string.', 'Some', 0, 'UTF-8', 'strpos', 0], + 'invalid search offset' => ['Some random string', 'Some', 50, 'UTF-8', 'strpos', false] + ]; + } + + /** + * @dataProvider posIntlProvider + */ + public function testPosIntl(string $haystack, string $needle, int $offset, string $charset, string $func, $expected): void + { + $this->assertEquals( + $expected, + Horde_Util_Mock_String::testPosIntl($haystack, $needle, $offset, $charset, $func) + ); + } + + public function posIntlProvider() + { + return [ + 'valid character sequence' => ['Some random string.', 'Some', 0, 'UTF-8', 'strpos', 0], + 'invalid search offset' => ['Some random string', 'Some', 50, 'UTF-8', 'strpos', false] + ]; + } + }