You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
4.2 KiB
141 lines
4.2 KiB
<?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));
|
|
}
|
|
}
|
|
|