<?php /** * Created by PhpStorm. * User: Rhilip * Date: 2019/4/21 * Time: 21:12 */ class Bencode { /** * Decodes a BEncoded string to the following values: * - Dictionary (starts with d, ends with e) * - List (starts with l, ends with e * - Integer (starts with i, ends with e * - String (starts with number denoting number of characters followed by : and then the string) * * @see https://wiki.theory.org/index.php/BitTorrentSpecification * * @param string $data * @param int $pos * @return mixed */ public static function decode($data, &$pos = 0) { $start_decode = ($pos === 0); if ($data[$pos] === 'd') { $pos++; $return = []; while ($data[$pos] !== 'e') { $key = self::decode($data, $pos); $value = self::decode($data, $pos); if ($key === null || $value === null) { break; } if (!is_string($key)) { throw new RuntimeException('Invalid key type, must be string: ' . gettype($key)); } $return[$key] = $value; } ksort($return); $pos++; } elseif ($data[$pos] === 'l') { $pos++; $return = []; while ($data[$pos] !== 'e') { $value = self::decode($data, $pos); $return[] = $value; } $pos++; } elseif ($data[$pos] === 'i') { $pos++; $digits = strpos($data, 'e', $pos) - $pos; $return = substr($data, $pos, $digits); if ($return === '-0') { throw new RuntimeException('Cannot have integer value -0'); } $multiplier = 1; if ($return[0] === '-') { $multiplier = -1; $return = substr($return, 1); } if (!ctype_digit($return)) { throw new RuntimeException('Cannot have non-digit values in integer number: ' . $return); } $return = $multiplier * ((int)$return); $pos += $digits + 1; } else { $digits = strpos($data, ':', $pos) - $pos; $len = (int)substr($data, $pos, $digits); $pos += ($digits + 1); $return = substr($data, $pos, $len); $pos += $len; } if ($start_decode) { if ($pos !== strlen($data)) { throw new RuntimeException('Could not fully decode bencode string'); } } return $return; } /** * @param mixed $data * @return string */ public static function encode($data) { if (is_array($data)) { $return = ''; $check = -1; $list = true; foreach ($data as $key => $value) { if ($key !== ++$check) { $list = false; break; } } if ($list) { $return .= 'l'; foreach ($data as $value) { $return .= self::encode($value); } } else { $return .= 'd'; foreach ($data as $key => $value) { $return .= self::encode(strval($key)); $return .= self::encode($value); } } $return .= 'e'; } elseif (is_integer($data)) { $return = 'i' . $data . 'e'; } else { $return = strlen($data) . ':' . $data; } return $return; } /** * Given a path to a file, decode the contents of it * * @param string $path * @return mixed */ public static function load($path) { if (is_file($path)) { return self::decode(file_get_contents($path)); } return null; } /** * Given a path for a file, encode the contents of it * * @param string $path * @param $data * @return mixed */ public static function dump($path, $data) { return file_put_contents($path, self::encode($data)); } }