[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Common String Functions 5 * 6 * PHP version 5 7 * 8 * @author Jim Wigginton <terrafrost@php.net> 9 * @copyright 2016 Jim Wigginton 10 * @license http://www.opensource.org/licenses/mit-license.html MIT License 11 * @link http://phpseclib.sourceforge.net 12 */ 13 14 namespace phpseclib3\Common\Functions; 15 16 use ParagonIE\ConstantTime\Base64; 17 use ParagonIE\ConstantTime\Base64UrlSafe; 18 use ParagonIE\ConstantTime\Hex; 19 use phpseclib3\Math\BigInteger; 20 use phpseclib3\Math\Common\FiniteField; 21 22 /** 23 * Common String Functions 24 * 25 * @author Jim Wigginton <terrafrost@php.net> 26 */ 27 abstract class Strings 28 { 29 /** 30 * String Shift 31 * 32 * Inspired by array_shift 33 * 34 * @param string $string 35 * @param int $index 36 * @return string 37 */ 38 public static function shift(&$string, $index = 1) 39 { 40 $substr = substr($string, 0, $index); 41 $string = substr($string, $index); 42 return $substr; 43 } 44 45 /** 46 * String Pop 47 * 48 * Inspired by array_pop 49 * 50 * @param string $string 51 * @param int $index 52 * @return string 53 */ 54 public static function pop(&$string, $index = 1) 55 { 56 $substr = substr($string, -$index); 57 $string = substr($string, 0, -$index); 58 return $substr; 59 } 60 61 /** 62 * Parse SSH2-style string 63 * 64 * Returns either an array or a boolean if $data is malformed. 65 * 66 * Valid characters for $format are as follows: 67 * 68 * C = byte 69 * b = boolean (true/false) 70 * N = uint32 71 * Q = uint64 72 * s = string 73 * i = mpint 74 * L = name-list 75 * 76 * uint64 is not supported. 77 * 78 * @param string $format 79 * @param string $data 80 * @return mixed 81 */ 82 public static function unpackSSH2($format, &$data) 83 { 84 $format = self::formatPack($format); 85 $result = []; 86 for ($i = 0; $i < strlen($format); $i++) { 87 switch ($format[$i]) { 88 case 'C': 89 case 'b': 90 if (!strlen($data)) { 91 throw new \LengthException('At least one byte needs to be present for successful C / b decodes'); 92 } 93 break; 94 case 'N': 95 case 'i': 96 case 's': 97 case 'L': 98 if (strlen($data) < 4) { 99 throw new \LengthException('At least four byte needs to be present for successful N / i / s / L decodes'); 100 } 101 break; 102 case 'Q': 103 if (strlen($data) < 8) { 104 throw new \LengthException('At least eight byte needs to be present for successful N / i / s / L decodes'); 105 } 106 break; 107 108 default: 109 throw new \InvalidArgumentException('$format contains an invalid character'); 110 } 111 switch ($format[$i]) { 112 case 'C': 113 $result[] = ord(self::shift($data)); 114 continue 2; 115 case 'b': 116 $result[] = ord(self::shift($data)) != 0; 117 continue 2; 118 case 'N': 119 list(, $temp) = unpack('N', self::shift($data, 4)); 120 $result[] = $temp; 121 continue 2; 122 case 'Q': 123 // pack() added support for Q in PHP 5.6.3 and PHP 5.6 is phpseclib 3's minimum version 124 // so in theory we could support this BUT, "64-bit format codes are not available for 125 // 32-bit versions" and phpseclib works on 32-bit installs. on 32-bit installs 126 // 64-bit floats can be used to get larger numbers then 32-bit signed ints would allow 127 // for. sure, you're not gonna get the full precision of 64-bit numbers but just because 128 // you need > 32-bit precision doesn't mean you need the full 64-bit precision 129 extract(unpack('Nupper/Nlower', self::shift($data, 8))); 130 $temp = $upper ? 4294967296 * $upper : 0; 131 $temp += $lower < 0 ? ($lower & 0x7FFFFFFFF) + 0x80000000 : $lower; 132 // $temp = hexdec(bin2hex(self::shift($data, 8))); 133 $result[] = $temp; 134 continue 2; 135 } 136 list(, $length) = unpack('N', self::shift($data, 4)); 137 if (strlen($data) < $length) { 138 throw new \LengthException("$length bytes needed; " . strlen($data) . ' bytes available'); 139 } 140 $temp = self::shift($data, $length); 141 switch ($format[$i]) { 142 case 'i': 143 $result[] = new BigInteger($temp, -256); 144 break; 145 case 's': 146 $result[] = $temp; 147 break; 148 case 'L': 149 $result[] = explode(',', $temp); 150 } 151 } 152 153 return $result; 154 } 155 156 /** 157 * Create SSH2-style string 158 * 159 * @param string $format 160 * @param string|int|float|array|bool ...$elements 161 * @return string 162 */ 163 public static function packSSH2($format, ...$elements) 164 { 165 $format = self::formatPack($format); 166 if (strlen($format) != count($elements)) { 167 throw new \InvalidArgumentException('There must be as many arguments as there are characters in the $format string'); 168 } 169 $result = ''; 170 for ($i = 0; $i < strlen($format); $i++) { 171 $element = $elements[$i]; 172 switch ($format[$i]) { 173 case 'C': 174 if (!is_int($element)) { 175 throw new \InvalidArgumentException('Bytes must be represented as an integer between 0 and 255, inclusive.'); 176 } 177 $result .= pack('C', $element); 178 break; 179 case 'b': 180 if (!is_bool($element)) { 181 throw new \InvalidArgumentException('A boolean parameter was expected.'); 182 } 183 $result .= $element ? "\1" : "\0"; 184 break; 185 case 'Q': 186 if (!is_int($element) && !is_float($element)) { 187 throw new \InvalidArgumentException('An integer was expected.'); 188 } 189 // 4294967296 == 1 << 32 190 $result .= pack('NN', $element / 4294967296, $element); 191 break; 192 case 'N': 193 if (is_float($element)) { 194 $element = (int) $element; 195 } 196 if (!is_int($element)) { 197 throw new \InvalidArgumentException('An integer was expected.'); 198 } 199 $result .= pack('N', $element); 200 break; 201 case 's': 202 if (!self::is_stringable($element)) { 203 throw new \InvalidArgumentException('A string was expected.'); 204 } 205 $result .= pack('Na*', strlen($element), $element); 206 break; 207 case 'i': 208 if (!$element instanceof BigInteger && !$element instanceof FiniteField\Integer) { 209 throw new \InvalidArgumentException('A phpseclib3\Math\BigInteger or phpseclib3\Math\Common\FiniteField\Integer object was expected.'); 210 } 211 $element = $element->toBytes(true); 212 $result .= pack('Na*', strlen($element), $element); 213 break; 214 case 'L': 215 if (!is_array($element)) { 216 throw new \InvalidArgumentException('An array was expected.'); 217 } 218 $element = implode(',', $element); 219 $result .= pack('Na*', strlen($element), $element); 220 break; 221 default: 222 throw new \InvalidArgumentException('$format contains an invalid character'); 223 } 224 } 225 return $result; 226 } 227 228 /** 229 * Expand a pack string 230 * 231 * Converts C5 to CCCCC, for example. 232 * 233 * @param string $format 234 * @return string 235 */ 236 private static function formatPack($format) 237 { 238 $parts = preg_split('#(\d+)#', $format, -1, PREG_SPLIT_DELIM_CAPTURE); 239 $format = ''; 240 for ($i = 1; $i < count($parts); $i += 2) { 241 $format .= substr($parts[$i - 1], 0, -1) . str_repeat(substr($parts[$i - 1], -1), $parts[$i]); 242 } 243 $format .= $parts[$i - 1]; 244 245 return $format; 246 } 247 248 /** 249 * Convert binary data into bits 250 * 251 * bin2hex / hex2bin refer to base-256 encoded data as binary, whilst 252 * decbin / bindec refer to base-2 encoded data as binary. For the purposes 253 * of this function, bin refers to base-256 encoded data whilst bits refers 254 * to base-2 encoded data 255 * 256 * @param string $x 257 * @return string 258 */ 259 public static function bits2bin($x) 260 { 261 /* 262 // the pure-PHP approach is faster than the GMP approach 263 if (function_exists('gmp_export')) { 264 return strlen($x) ? gmp_export(gmp_init($x, 2)) : gmp_init(0); 265 } 266 */ 267 268 if (preg_match('#[^01]#', $x)) { 269 throw new \RuntimeException('The only valid characters are 0 and 1'); 270 } 271 272 if (!defined('PHP_INT_MIN')) { 273 define('PHP_INT_MIN', ~PHP_INT_MAX); 274 } 275 276 $length = strlen($x); 277 if (!$length) { 278 return ''; 279 } 280 $block_size = PHP_INT_SIZE << 3; 281 $pad = $block_size - ($length % $block_size); 282 if ($pad != $block_size) { 283 $x = str_repeat('0', $pad) . $x; 284 } 285 286 $parts = str_split($x, $block_size); 287 $str = ''; 288 foreach ($parts as $part) { 289 $xor = $part[0] == '1' ? PHP_INT_MIN : 0; 290 $part[0] = '0'; 291 $str .= pack( 292 PHP_INT_SIZE == 4 ? 'N' : 'J', 293 $xor ^ eval('return 0b' . $part . ';') 294 ); 295 } 296 return ltrim($str, "\0"); 297 } 298 299 /** 300 * Convert bits to binary data 301 * 302 * @param string $x 303 * @return string 304 */ 305 public static function bin2bits($x, $trim = true) 306 { 307 /* 308 // the pure-PHP approach is slower than the GMP approach BUT 309 // i want to the pure-PHP version to be easily unit tested as well 310 if (function_exists('gmp_import')) { 311 return gmp_strval(gmp_import($x), 2); 312 } 313 */ 314 315 $len = strlen($x); 316 $mod = $len % PHP_INT_SIZE; 317 if ($mod) { 318 $x = str_pad($x, $len + PHP_INT_SIZE - $mod, "\0", STR_PAD_LEFT); 319 } 320 321 $bits = ''; 322 if (PHP_INT_SIZE == 4) { 323 $digits = unpack('N*', $x); 324 foreach ($digits as $digit) { 325 $bits .= sprintf('%032b', $digit); 326 } 327 } else { 328 $digits = unpack('J*', $x); 329 foreach ($digits as $digit) { 330 $bits .= sprintf('%064b', $digit); 331 } 332 } 333 334 return $trim ? ltrim($bits, '0') : $bits; 335 } 336 337 /** 338 * Switch Endianness Bit Order 339 * 340 * @param string $x 341 * @return string 342 */ 343 public static function switchEndianness($x) 344 { 345 $r = ''; 346 for ($i = strlen($x) - 1; $i >= 0; $i--) { 347 $b = ord($x[$i]); 348 if (PHP_INT_SIZE === 8) { 349 // 3 operations 350 // from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64BitsDiv 351 $r .= chr((($b * 0x0202020202) & 0x010884422010) % 1023); 352 } else { 353 // 7 operations 354 // from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith32Bits 355 $p1 = ($b * 0x0802) & 0x22110; 356 $p2 = ($b * 0x8020) & 0x88440; 357 $r .= chr( 358 (($p1 | $p2) * 0x10101) >> 16 359 ); 360 } 361 } 362 return $r; 363 } 364 365 /** 366 * Increment the current string 367 * 368 * @param string $var 369 * @return string 370 */ 371 public static function increment_str(&$var) 372 { 373 if (function_exists('sodium_increment')) { 374 $var = strrev($var); 375 sodium_increment($var); 376 $var = strrev($var); 377 return $var; 378 } 379 380 for ($i = 4; $i <= strlen($var); $i += 4) { 381 $temp = substr($var, -$i, 4); 382 switch ($temp) { 383 case "\xFF\xFF\xFF\xFF": 384 $var = substr_replace($var, "\x00\x00\x00\x00", -$i, 4); 385 break; 386 case "\x7F\xFF\xFF\xFF": 387 $var = substr_replace($var, "\x80\x00\x00\x00", -$i, 4); 388 return $var; 389 default: 390 $temp = unpack('Nnum', $temp); 391 $var = substr_replace($var, pack('N', $temp['num'] + 1), -$i, 4); 392 return $var; 393 } 394 } 395 396 $remainder = strlen($var) % 4; 397 398 if ($remainder == 0) { 399 return $var; 400 } 401 402 $temp = unpack('Nnum', str_pad(substr($var, 0, $remainder), 4, "\0", STR_PAD_LEFT)); 403 $temp = substr(pack('N', $temp['num'] + 1), -$remainder); 404 $var = substr_replace($var, $temp, 0, $remainder); 405 406 return $var; 407 } 408 409 /** 410 * Find whether the type of a variable is string (or could be converted to one) 411 * 412 * @param mixed $var 413 * @return bool 414 * @psalm-assert-if-true string|\Stringable $var 415 */ 416 public static function is_stringable($var) 417 { 418 return is_string($var) || (is_object($var) && method_exists($var, '__toString')); 419 } 420 421 /** 422 * Constant Time Base64-decoding 423 * 424 * ParagoneIE\ConstantTime doesn't use libsodium if it's available so we'll do so 425 * ourselves. see https://github.com/paragonie/constant_time_encoding/issues/39 426 * 427 * @param string $data 428 * @return string 429 */ 430 public static function base64_decode($data) 431 { 432 return function_exists('sodium_base642bin') ? 433 sodium_base642bin($data, SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING, '=') : 434 Base64::decode($data); 435 } 436 437 /** 438 * Constant Time Base64-decoding (URL safe) 439 * 440 * @param string $data 441 * @return string 442 */ 443 public static function base64url_decode($data) 444 { 445 // return self::base64_decode(str_replace(['-', '_'], ['+', '/'], $data)); 446 447 return function_exists('sodium_base642bin') ? 448 sodium_base642bin($data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, '=') : 449 Base64UrlSafe::decode($data); 450 } 451 452 /** 453 * Constant Time Base64-encoding 454 * 455 * @param string $data 456 * @return string 457 */ 458 public static function base64_encode($data) 459 { 460 return function_exists('sodium_bin2base64') ? 461 sodium_bin2base64($data, SODIUM_BASE64_VARIANT_ORIGINAL) : 462 Base64::encode($data); 463 } 464 465 /** 466 * Constant Time Base64-encoding (URL safe) 467 * 468 * @param string $data 469 * @return string 470 */ 471 public static function base64url_encode($data) 472 { 473 // return str_replace(['+', '/'], ['-', '_'], self::base64_encode($data)); 474 475 return function_exists('sodium_bin2base64') ? 476 sodium_bin2base64($data, SODIUM_BASE64_VARIANT_URLSAFE) : 477 Base64UrlSafe::encode($data); 478 } 479 480 /** 481 * Constant Time Hex Decoder 482 * 483 * @param string $data 484 * @return string 485 */ 486 public static function hex2bin($data) 487 { 488 return function_exists('sodium_hex2bin') ? 489 sodium_hex2bin($data) : 490 Hex::decode($data); 491 } 492 493 /** 494 * Constant Time Hex Encoder 495 * 496 * @param string $data 497 * @return string 498 */ 499 public static function bin2hex($data) 500 { 501 return function_exists('sodium_bin2hex') ? 502 sodium_bin2hex($data) : 503 Hex::encode($data); 504 } 505 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body