147 lines
4.2 KiB
PHP
147 lines
4.2 KiB
PHP
<?php
|
|
interface BEncodeSerializable {
|
|
function bencodeSerialize(): mixed;
|
|
}
|
|
|
|
// todo: if $depth < 1 return unencoded, or just get mad i dunno yet
|
|
function bdecode(mixed $input, bool $dictAsObject = false, int $depth = 512): mixed {
|
|
if(is_string($input)) {
|
|
$file = fopen('php://temp', 'rb+');
|
|
fwrite($file, $input);
|
|
fseek($file, 0, SEEK_SET);
|
|
$output = bdecode($file, $dictAsObject, $depth);
|
|
fclose($file);
|
|
return $output;
|
|
} elseif(!is_resource($input))
|
|
return null;
|
|
|
|
$char = fgetc($input);
|
|
if($char === false)
|
|
die('bdecode: unexpected eof');
|
|
|
|
switch($char) {
|
|
case 'i':
|
|
$number = '';
|
|
|
|
for(;;) {
|
|
$char = fgetc($input);
|
|
if($char === false)
|
|
die('bdecode: integer unexpected eof');
|
|
if($char === 'e')
|
|
break;
|
|
if($char === '-' && $number !== '')
|
|
die('bdecode: integer unexpected minus');
|
|
if($char === '0' && $number === '-')
|
|
die('bdecode: integer negative zero');
|
|
if($char === '0' && $number === '0')
|
|
die('bdecode: integer double zero');
|
|
if(!ctype_digit($char))
|
|
die('bdecode: integer unexpected char');
|
|
$number .= $char;
|
|
}
|
|
|
|
return intval($number);
|
|
|
|
case 'l':
|
|
$list = [];
|
|
|
|
for(;;) {
|
|
$char = fgetc($input);
|
|
if($char === false)
|
|
die('bdecode: list unexpected eof');
|
|
if($char === 'e')
|
|
break;
|
|
fseek($input, -1, SEEK_CUR);
|
|
$list[] = bdecode($input, $dictAsObject, $depth - 1);
|
|
}
|
|
|
|
return $list;
|
|
|
|
case 'd':
|
|
$dict = [];
|
|
|
|
for(;;) {
|
|
$char = fgetc($input);
|
|
if($char === false)
|
|
die('bdecode: dict unexpected eof');
|
|
if($char === 'e')
|
|
break;
|
|
if(!ctype_digit($char))
|
|
die('bdecode: dict expecting string');
|
|
fseek($input, -1, SEEK_CUR);
|
|
$dict[bdecode($input)] = bdecode($input, $dictAsObject, $depth - 1);
|
|
}
|
|
|
|
if($dictAsObject)
|
|
$dict = (object)$dict;
|
|
|
|
return $dict;
|
|
|
|
default:
|
|
if(!ctype_digit($char))
|
|
die('bdecode: unexpected char');
|
|
|
|
$length = $char;
|
|
|
|
for(;;) {
|
|
$char = fgetc($input);
|
|
if($char === false)
|
|
die('bdecode: string unexpected eof');
|
|
if($char === ':')
|
|
break;
|
|
if($char === '0' && $length === '0')
|
|
die('bdecode: string double zero');
|
|
if(!ctype_digit($char))
|
|
die('bdecode: string unexpected char');
|
|
|
|
$length .= $char;
|
|
}
|
|
|
|
return fread($input, intval($length));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function bencode(mixed $input): string {
|
|
switch(gettype($input)) {
|
|
case 'string':
|
|
return sprintf('%d:%s', strlen($input), $input);
|
|
|
|
case 'integer':
|
|
return sprintf('i%de', $input);
|
|
|
|
case 'array':
|
|
if(array_is_list($input)) {
|
|
$output = 'l';
|
|
foreach($input as $item)
|
|
$output .= bencode($item);
|
|
} else {
|
|
$output = 'd';
|
|
foreach($input as $key => $value) {
|
|
$output .= bencode(strval($key));
|
|
$output .= bencode($value);
|
|
}
|
|
}
|
|
|
|
return $output . 'e';
|
|
|
|
case 'object':
|
|
if($input instanceof BEncodeSerializable)
|
|
return bencode($input->bencodeSerialize());
|
|
|
|
$input = get_object_vars($input);
|
|
$output = 'd';
|
|
|
|
foreach($input as $key => $value) {
|
|
$output .= bencode(strval($key));
|
|
$output .= bencode($value);
|
|
}
|
|
|
|
return $output . 'e';
|
|
|
|
default:
|
|
return '';
|
|
}
|
|
}
|