Subversion Repositories oidplus

Rev

Rev 846 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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