123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- <?php
- /**
- * Contains a pair of recursive functions that implement the bencode data
- * encoding format.
- *
- * @author Michael J. I. Jackson <mjijackson@gmail.com>
- */
- class Bencode
- {
- /**
- * The version of the library.
- *
- * @var string
- */
- const VERSION = "1.0";
- /**
- * Bencodes the given data structure.
- *
- * @param mixed
- * @return string
- * @throws Exception
- */
- public static function encode($value)
- {
- if (is_null($value)) {
- return "0:";
- }
- if (is_int($value)) {
- return "i" . $value . "e";
- }
- if (is_string($value)) {
- return strlen($value) . ":" . $value;
- }
- if (is_array($value)) {
- if (self::isAssoc($value)) {
- ksort($value, SORT_STRING);
- $buffer = "d";
- foreach ($value as $key => $v) {
- $buffer .= self::encode(strval($key));
- $buffer .= self::encode($v);
- }
- $buffer .= "e";
- } else {
- ksort($value, SORT_NUMERIC);
- $buffer = "l";
- foreach ($value as $v) {
- $buffer .= self::encode($v);
- }
- $buffer .= "e";
- }
- return $buffer;
- }
- throw new Exception("Unable to encode data type: " . gettype($value));
- }
- /**
- * Decodes the given string. The second parameter is only used in recursion.
- *
- * @param string
- * @param int
- * @return mixed
- * @throws Exception
- */
- public static function decode($tokens, &$i=0)
- {
- if (is_string($tokens)) {
- $tokens = str_split($tokens);
- }
- switch ($tokens[$i]) {
- case "d":
- $dict = array();
- while (isset($tokens[++$i])) {
- if ($tokens[$i] == "e") {
- return $dict;
- } else {
- $key = self::decode($tokens, $i);
- if (isset($tokens[++$i])) {
- $dict[$key] = self::decode($tokens, $i);
- } else {
- throw new Exception("Dictionary key ($key) without a value at index $i");
- }
- }
- }
- throw new Exception("Unterminated dictionary at index $i");
- case "l":
- $list = array();
- while (isset($tokens[++$i])) {
- if ($tokens[$i] == "e") {
- return $list;
- } else {
- $list[] = self::decode($tokens, $i);
- }
- }
- throw new Exception("Unterminated list at index $i");
- case "i":
- $buffer = '';
- while (isset($tokens[++$i])) {
- if ($tokens[$i] == "e") {
- return intval($buffer);
- } elseif (ctype_digit($tokens[$i])) {
- $buffer .= $tokens[$i];
- } else {
- throw new Exception("Unexpected token while parsing integer at index $i: {$tokens[$i]}");
- }
- }
- throw new Exception("Unterminated integer at index $i");
- case ctype_digit($tokens[$i]):
- $length = $tokens[$i];
- while (isset($tokens[++$i])) {
- if ($tokens[$i] == ":") {
- break;
- } elseif (ctype_digit($tokens[$i])) {
- $length .= $tokens[$i];
- } else {
- throw new Exception("Unexpected token while parsing string length at index $i: {$tokens[$i]}");
- }
- }
- $end = $i + intval($length);
- $buffer = '';
- while (isset($tokens[++$i])) {
- if ($i <= $end) {
- $buffer .= $tokens[$i];
- if ($i == $end) {
- return $buffer;
- }
- }
- }
- throw new Exception("Unterminated string at index $i");
- }
- throw new Exception("Unexpected token at index $i: {$tokens[$i]}");
- }
- /**
- * Tells whether an array is associative or not. In order to be non-associative,
- * each of the array's key numbers must correspond exactly to it's position
- * in the array.
- *
- * @param array
- * @return bool
- */
- public static function isAssoc($array)
- {
- return count($array) !== array_reduce(array_keys($array), array("Bencode", "isAssocCallback"), 0);
- }
- /**
- * A callback function used by {@link isAssoc()}.
- *
- * @return int
- */
- protected static function isAssocCallback($a, $b)
- {
- return $a === $b ? $a + 1 : 0;
- }
- }
- /**
- * Shorthand for {@link Bencode::encode()}.
- *
- * @param mixed
- * @return string
- * @throws Exception
- * @see Bencode::encode()
- */
- function bencode($value)
- {
- return Bencode::encode($value);
- }
- /**
- * Shorthand for {@link Bencode::decode()}.
- *
- * @param string
- * @return mixed
- * @throws Exception
- * @see Bencode::decode()
- */
- function bdecode($value)
- {
- return Bencode::decode($value);
- }
|