%PDF- %PDF-
| Direktori : /data/www_bck/varak.cloud_bck/tracker.varak.cloud/vendor/rych/bencode/Rych/Bencode/ |
| Current File : //data/www_bck/varak.cloud_bck/tracker.varak.cloud/vendor/rych/bencode/Rych/Bencode/Decoder.php |
<?php
/**
* Rych Bencode Component
*
* @package Rych\Bencode
* @author Ryan Chouinard <rchouinard@gmail.com>
* @copyright Copyright (c) 2014, Ryan Chouinard
* @license MIT License - http://www.opensource.org/licenses/mit-license.php
*/
namespace Rych\Bencode;
use Rych\Bencode\Exception\RuntimeException;
/**
* Bencode decoder
*
* @package Rych\Bencode
* @author Ryan Chouinard <rchouinard@gmail.com>
* @copyright Copyright (c) 2014, Ryan Chouinard
* @license MIT License - http://www.opensource.org/licenses/mit-license.php
*/
class Decoder
{
/**
* @var string
*/
private $_source;
/**
* @var string
*/
private $_decodeType;
/**
* @var integer
*/
private $_sourceLength;
/**
* @var integer
*/
private $_offset = 0;
/**
* Class constructor
*
* @param string $source The bencode string to be decoded.
* @param string $decodeType currently unused.
* @return void
*/
private function __construct($source, $decodeType)
{
$this->_source = $source;
$this->_sourceLength = strlen($this->_source);
if ($decodeType != Bencode::TYPE_ARRAY && $decodeType != Bencode::TYPE_OBJECT) {
$decodeType = Bencode::TYPE_ARRAY;
}
$this->_decodeType = $decodeType;
}
/**
* Decode a bencode entity into a value
*
* @param string $source The bencode string to be decoded.
* @param string $decodeType currently unused.
* @return mixed Returns the decoded value.
* @throws Rych\Bencode\Exception\RuntimeException
*/
static public function decode($source, $decodeType = Bencode::TYPE_ARRAY)
{
if (!is_string($source)) {
throw new RuntimeException('Argument expected to be a string; Got ' . gettype($source));
}
$decoder = new self($source, $decodeType);
$decoded = $decoder->_decode();
if ($decoder->_offset != $decoder->_sourceLength) {
throw new RuntimeException(
'Found multiple entities outside list or dict definitions'
);
}
return $decoded;
}
/**
* Decode a bencode entity into a value
*
* @return mixed Returns the decoded value.
* @throws Rych\Bencode\Exception\RuntimeException
*/
private function _decode()
{
switch ($this->_getChar()) {
case 'i':
++$this->_offset;
return $this->_decodeInteger();
break;
case 'l':
++$this->_offset;
return $this->_decodeList();
break;
case 'd':
++$this->_offset;
return $this->_decodeDict();
break;
default:
if (ctype_digit($this->_getChar())) {
return $this->_decodeString();
}
}
throw new RuntimeException('Unknown entity found at offset ' . $this->_offset);
}
/**
* Decode a bencode integer into an integer
*
* @return integer Returns the decoded integer.
* @throws Rych\Bencode\Exception\RuntimeException
*/
private function _decodeInteger()
{
$offsetOfE = strpos($this->_source, 'e', $this->_offset);
if (false === $offsetOfE) {
throw new RuntimeException('Unterminated integer entity at offset ' . $this->_offset);
}
$currentOffset = $this->_offset;
if ('-' == $this->_getChar($currentOffset)) {
++$currentOffset;
}
if ($offsetOfE === $currentOffset) {
throw new RuntimeException('Empty integer entity at offset ' . $this->_offset);
}
while ($currentOffset < $offsetOfE) {
if (!ctype_digit($this->_getChar($currentOffset))) {
throw new RuntimeException('Non-numeric character found in integer entity at offset ' . $this->_offset);
}
++$currentOffset;
}
$value = substr($this->_source, $this->_offset, $offsetOfE - $this->_offset);
// One last check to make sure zero-padded integers don't slip by, as
// they're not allowed per bencode specification.
$absoluteValue = (string) abs($value);
if (1 < strlen($absoluteValue) && '0' == $value[0]) {
// TODO: Could probably just trigger a warning here
throw new RuntimeException('Illegal zero-padding found in integer entity at offset ' . $this->_offset);
}
$this->_offset = $offsetOfE + 1;
// The +0 auto-casts the chunk to either an integer or a float(in cases
// where an integer would overrun the max limits of integer types)
return $value + 0;
}
/**
* Decode a bencode string into a string
*
* @return string Returns the decoded string.
* @throws Rych\Bencode\Exception\RuntimeException
*/
private function _decodeString()
{
if ('0' === $this->_getChar() && ':' != $this->_getChar($this->_offset + 1)) {
// TODO: Trigger a warning instead?
throw new RuntimeException('Illegal zero-padding in string entity length declaration at offset ' . $this->_offset);
}
$offsetOfColon = strpos($this->_source, ':', $this->_offset);
if (false === $offsetOfColon) {
throw new RuntimeException('Unterminated string entity at offset ' . $this->_offset);
}
$contentLength = (int) substr($this->_source, $this->_offset, $offsetOfColon);
if (($contentLength + $offsetOfColon + 1) > $this->_sourceLength) {
throw new RuntimeException('Unexpected end of string entity at offset ' . $this->_offset);
}
$value = substr($this->_source, $offsetOfColon + 1, $contentLength);
$this->_offset = $offsetOfColon + $contentLength + 1;
return $value;
}
/**
* Decode a bencode list into a numeric array
*
* @return array Returns the decoded array.
* @throws Rych\Bencode\Exception\RuntimeException
*/
private function _decodeList()
{
$list = array ();
$terminated = false;
$listOffset = $this->_offset;
while (false !== $this->_getChar()) {
if ('e' == $this->_getChar()) {
$terminated = true;
break;
}
$list[] = $this->_decode();
}
if (!$terminated && false === $this->_getChar()) {
throw new RuntimeException('Unterminated list definition at offset ' . $listOffset);
}
++$this->_offset;
return $list;
}
/**
* Decode a bencode dictionary into an associative array
*
* @return array Returns the decoded array.
* @throws Rych\Bencode\Exception\RuntimeException
*/
private function _decodeDict()
{
$dict = array ();
$terminated = false;
$dictOffset = $this->_offset;
while (false !== $this->_getChar()) {
if ('e' == $this->_getChar()) {
$terminated = true;
break;
}
$keyOffset = $this->_offset;
if (!ctype_digit($this->_getChar())) {
throw new RuntimeException('Invalid dictionary key at offset ' . $keyOffset);
}
$key = $this->_decodeString();
if (isset ($dict[$key])) {
// TODO: This could probably just trigger a warning...
throw new RuntimeException('Duplicate dictionary key at offset ' . $keyOffset);
}
$dict[$key] = $this->_decode();
}
if (!$terminated && false === $this->_getChar()) {
throw new RuntimeException('Unterminated dictionary definition at offset ' . $dictOffset);
}
++$this->_offset;
return $dict;
}
/**
* Fetch the character at the specified source offset
*
* If not offset is provided, the current offset is used.
*
* @param integer $offset the offset to retrieve from the source string.
* @return string Returns the character found at the specified offset. If
* the specified offset is out of range, false is returned.
*/
private function _getChar($offset = null)
{
if (null === $offset) {
$offset = $this->_offset;
}
if (empty ($this->_source) || $this->_offset >= $this->_sourceLength) {
return false;
}
return $this->_source[$offset];
}
}