[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/phpseclib/phpseclib/phpseclib/Common/Functions/ -> Strings.php (source)

   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  }