diff --git a/core.php b/core.php index 7a0ef18..807ae72 100644 --- a/core.php +++ b/core.php @@ -1,88 +1,138 @@ <?php namespace Keruald; /** * Keruald, core libraries for Pluton and Xen engines. * * Global functions */ +/// +/// Strings +/// + +/** + * Pads a multibytes string to a certain length with another string + * + * @param string $str the input string + * @param int $pad_length the target string size + * @param string $pad_string the padding characters (optional, default is space) + * @param int $pad_type STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH (optional, default is STR_PAD_RIGHT) + * @param string the character encoding (optional) + * + * @return string the padded string + * + */ +function mb_str_pad($input, $pad_length, $pad_string = ' ', $pad_type = STR_PAD_RIGHT, $encoding = null) { + // Inspired by Ronald Ulysses Swanson method + // http://stackoverflow.com/a/27194169/1930997 + // who followed the str_pad PHP implementation. + + if ($encoding === null) { + $encoding = mb_internal_encoding(); + } + + $padBefore = $pad_type === STR_PAD_BOTH || $pad_type === STR_PAD_LEFT; + $padAfter = $pad_type === STR_PAD_BOTH || $pad_type === STR_PAD_RIGHT; + + $pad_length -= mb_strlen($input, $encoding); + if ($padBefore && $padAfter) { + $targetLength = $pad_length / 2; + } else { + $targetLength = $pad_length; + } + $strToRepeatLength = mb_strlen($pad_string, $encoding); + $repeatTimes = ceil($targetLength / $strToRepeatLength); + $repeatedString = str_repeat($pad_string, max(0, $repeatTimes)); // safe if used with valid Unicode sequences (any charset) + + $paddedString = ''; + if ($padBefore) { + $paddedString = mb_substr($repeatedString, 0, floor($targetLength), $encoding); + } + $paddedString .= $input; + if ($padAfter) { + $paddedString .= mb_substr($repeatedString, 0, ceil($targetLength), $encoding); + } + + return $paddedString; +} + /// /// Error and debug /// /** * Prints human-readable information about a variable, wrapped in a <pre> block * * @param mixed $variable the variable to dump */ function dprint_r ($variable) { echo '<pre>'; print_r($variable); echo '</pre>'; } /** * Prints human-readable information about a variable, wrapped in a <pre> block * then dies * * @param mixed $variable the variable to dump */ function dieprint_r ($variable) { dprint_r($variable); die; }; /// /// Client information /// /** * Returns the full header or the IP part of it * * @param string $value The header value * @return string the IP part */ function extract_client_ip_from_header ($value) { if (strpos($value, ',') !== false) { //Header contains 'clientIP, proxyIP, anotherProxyIP' //The first value is so the one to return. //See draft-ietf-appsawg-http-forwarded-10. $ips = explode(',', $value, 2); return trim($ips[0]); } return $value; } /** * Gets remote IP address. * * This is intended as a drop-in replacement for $_SERVER['REMOTE_ADDR'], * which takes in consideration proxy values. */ function get_remote_addr () { $candidates = [ //Standard header provided by draft-ietf-appsawg-http-forwarded-10 'HTTP_X_FORWARDED_FOR', //Legacy headers 'HTTP_CLIENT_IP', 'HTTP_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_X_FORWARDED', //Default header if no proxy information could be detected 'REMOTE_ADDR', ]; foreach ($candidates as $candidate) { if (array_key_exists($candidate, $_SERVER)) { return extract_client_ip_from_header($_SERVER[$candidate]); } } return ''; } diff --git a/tests/CoreTest.php b/tests/CoreTest.php index 30bc018..dcef8b6 100644 --- a/tests/CoreTest.php +++ b/tests/CoreTest.php @@ -1,45 +1,81 @@ <?php namespace Keruald; class CoreTest extends \PHPUnit_Framework_Testcase { + /// + /// Strings + /// + + function test_mb_str_pad () { + // Tests from http://3v4l.org/UnXTF + // http://web.archive.org/web/20150711100913/http://3v4l.org/UnXTF + + $this->assertEquals('àèòàFOOàèòà', mb_str_pad("FOO", 11, "àèò", STR_PAD_BOTH, "UTF-8")); + $this->assertEquals('àèòFOOàèòà', mb_str_pad("FOO", 10, "àèò", STR_PAD_BOTH, "UTF-8")); + $this->assertEquals('àèòBAAZàèòà', mb_str_pad("BAAZ", 11, "àèò", STR_PAD_BOTH, "UTF-8")); + $this->assertEquals('àèòBAAZàèò', mb_str_pad("BAAZ", 10, "àèò", STR_PAD_BOTH, "UTF-8")); + $this->assertEquals('FOOBAR', mb_str_pad("FOOBAR", 6, "àèò", STR_PAD_BOTH, "UTF-8")); + $this->assertEquals('FOOBAR', mb_str_pad("FOOBAR", 1, "àèò", STR_PAD_BOTH, "UTF-8")); + $this->assertEquals('FOOBAR', mb_str_pad("FOOBAR", 0, "àèò", STR_PAD_BOTH, "UTF-8")); + $this->assertEquals('FOOBAR', mb_str_pad("FOOBAR", -10, "àèò", STR_PAD_BOTH, "UTF-8")); + + $this->assertEquals('àèòàèòàèFOO', mb_str_pad("FOO", 11, "àèò", STR_PAD_LEFT, "UTF-8")); + $this->assertEquals('àèòàèòàFOO', mb_str_pad("FOO", 10, "àèò", STR_PAD_LEFT, "UTF-8")); + $this->assertEquals('àèòàèòàBAAZ', mb_str_pad("BAAZ", 11, "àèò", STR_PAD_LEFT, "UTF-8")); + $this->assertEquals('àèòàèòBAAZ', mb_str_pad("BAAZ", 10, "àèò", STR_PAD_LEFT, "UTF-8")); + $this->assertEquals('FOOBAR', mb_str_pad("FOOBAR", 6, "àèò", STR_PAD_LEFT, "UTF-8")); + $this->assertEquals('FOOBAR', mb_str_pad("FOOBAR", 1, "àèò", STR_PAD_LEFT, "UTF-8")); + $this->assertEquals('FOOBAR', mb_str_pad("FOOBAR", 0, "àèò", STR_PAD_LEFT, "UTF-8")); + $this->assertEquals('FOOBAR', mb_str_pad("FOOBAR", -10, "àèò", STR_PAD_LEFT, "UTF-8")); + + $this->assertEquals('FOOàèòàèòàè', mb_str_pad("FOO", 11, "àèò", STR_PAD_RIGHT, "UTF-8")); + $this->assertEquals('FOOàèòàèòà', mb_str_pad("FOO", 10, "àèò", STR_PAD_RIGHT, "UTF-8")); + $this->assertEquals('BAAZàèòàèòà', mb_str_pad("BAAZ", 11, "àèò", STR_PAD_RIGHT, "UTF-8")); + $this->assertEquals('BAAZàèòàèò', mb_str_pad("BAAZ", 10, "àèò", STR_PAD_RIGHT, "UTF-8")); + $this->assertEquals('FOOBAR', mb_str_pad("FOOBAR", 6, "àèò", STR_PAD_RIGHT, "UTF-8")); + $this->assertEquals('FOOBAR', mb_str_pad("FOOBAR", 1, "àèò", STR_PAD_RIGHT, "UTF-8")); + $this->assertEquals('FOOBAR', mb_str_pad("FOOBAR", 0, "àèò", STR_PAD_RIGHT, "UTF-8")); + $this->assertEquals('FOOBAR', mb_str_pad("FOOBAR", -10, "àèò", STR_PAD_RIGHT, "UTF-8")); + } + /// /// Client information /// function test_extract_client_ip_from_header () { $values = [ //Each value should return 10.0.0.3 '10.0.0.3', '10.0.0.3,10.0.0.4', '10.0.0.3, 10.0.0.4', '10.0.0.3, 10.0.0.4, lorem ipsum dolor', ]; foreach ($values as $value) { $this->assertEquals( '10.0.0.3', extract_client_ip_from_header($value) ); } $this->assertEmpty( extract_client_ip_from_header('') ); } function test_get_remote_addr () { $this->assertEmpty(get_remote_addr()); $_SERVER = [ 'REMOTE_ADDR' => '10.0.0.2' ]; $this->assertEquals('10.0.0.2', get_remote_addr()); $_SERVER += [ 'HTTP_X_FORWARDED_FOR' => '10.0.0.3', 'HTTP_CLIENT_IP' => '10.0.0.4', ]; $this->assertEquals('10.0.0.3', get_remote_addr(), "HTTP_X_FORWARDED_FOR must be prioritized."); } }