All pastes #422798 Raw Edit

Anonymous

public text v1 · immutable
·published 2007-04-03 18:31 UTC
rendered paste body
<?php
/*
Public methods:  BEncode::encode($data)   -> bencodes $data
                            BEncode::decode($data)   -> bdecodes $data
*/

/*
Error codes:
0 => no error
1 => BEncode::encode() wrong parameter type  [errpos is -1]
2 => Invalid bencoded string ('i' has no corresponding 'e') [errpos is the position of 'i']
3 => Invalid bencoded string (in 'i*e', * is supposed to be a number) [errpos is the position of 'i']
4 => Invalid bencoded string (there's garbage in it)    [errpos is the position of first garbage byte]
5 => Invalid bencoded string (the specified length for the string should be a number) [errpos is the position that represents the first character of the string's length]
6 => Invalid bencoded string (the length of the string is longer than the actual string) [see (5)]
7 => Invalid bencoded string (the keys for dictionary are supposed to be strings) [errpos points to the first byte of the key]
8 => Invalid bencoded string (couldn't find end mark for a list/dictionary) [errpos is -1]
*/
class BEncode {

    var $errno=0, $encoded, $decoded;
    /**
             *
             *  Bencodes a string/array/int. This function fails only if you supply the wrong parameter type.
             *
             *  @param mixed    Can be string, int or array
             *  @return mixed  Bencoded string on success, boolean false on failure.
           */
    function encode ($data)
    {
        // string
        if (is_string($data))     $this->encoded = strlen($data).':'.$data;
        // int
        else if (is_int($data))   $this->encoded = 'i'.$data.'e';
        // list or dictionary
        else if (is_array($data)) $this->encoded = $this->_encode_arr($data);
        else {
            $this->errno = 1;
            $this->errpos=-1;
            return false;
        } // none of those, return error
        return true;
    }
    
    /**
            *
            *  bencodes an array into a list or dictionary
            *
            *  @param Array     the array that has to be converted to list/dictionary
            *  @return String   a bencoded list/dictionary
            *  @access private
            */
    function _encode_arr ($arr) {
        $is_a_list = true;
        foreach ($arr as $key=>$value)
            if (!is_int($key)) { // we found a non-integer key, meaning we have a dictionary, not a list
                $is_a_list = false;
                break;
            }
        if ($is_a_list)
        {
            // we got a list
            $ret = 'l'; // lists start with a l
            ksort($arr, SORT_NUMERIC); // we sort the array numerically, by keys
            foreach ($arr as $key) // we take each element of the array, adding it to the list
                $ret .= $this->encode($key); 
            $ret .= 'e'; // lists end with an e
            return $ret; // we return the bencoded list
        } else {
            // we got a dictionary
            // the order doesn't seem to matter for dictionary, therefore we do not sort
            $ret = 'd'; // dictionary starts with a d
            foreach ($arr as $key=>$value) {
                // the key of a dictionary in bencode MUST be a string, so we cast $key to string
                $ret .= $this->encode(string($key)) . $this->encode($value);
            }
            $ret .= 'e'; // dictionary ends with an e
            return $ret; // we return the bencoded dictionary
        }
    }
    
    /**
            *
            * Decode a bencoded string
            * 
            * @param string     The bencoded string
            * @return mixed     On success, a string/array/int is returned. On failure, boolean false is returned.
            */
    function decode ($str)
    {
        $endpos=0;
        $ret = $this->_decode($str, 0, $endpos);
        if ($ret === false) return false; // invalid bencoded string
        /*else if ($endpos != strlen($str)-1) {
            // garbage at the end ?
            $this->errno  = 4;
            $this->errpos = $endpos;
            return false; 
        }*/
        else return $ret;
    }
    
    /**
             *
             * Decode a bencoded string starting at a specified position
             *
             * @param string    The bencoded string
             * @param integer   Position where it starts bdecoding at
             * @param &integer   After return, it is filled with the position where it stopped bdecoding
             * @return mixed
             * @access private
          */
    function _decode ($str, $startpos, &$endpos)
    {
        $ret = '';
        switch ($str[$startpos]) {
        case 'i': // integer
            $ending_position = strpos($str, 'e', $startpos+1); // first found 'e' after position $startpos
            if ($ending_position === false) {
                $this->errno  = 2;
                $this->errpos = $startpos;
                return false; // we could not find 'e', invalid bencoded string
            }
            $ret = substr($str, $startpos+1, $ending_position-$startpos-1); // everything after 'i' and before'e'
            if (!ctype_digit($ret)) {
                $this->errno  = 3;
                $this->errpos = $startpos;
                return false; // this is not an int, invalid bencoded string
            }
            $endpos = $ending_position;
            return (int)$ret;
            break;
        case 'l': // list
            $endposition=0;
            $ret = $this->_decode_arr($str, $startpos+1, $endposition, false);
            if ($ret === false) return false; // error occured
            $endpos=$endposition; return $ret; // success
            break;
        case 'd': // dictionary
            $endposition=0;
            $ret = $this->_decode_arr($str, $startpos+1, $endposition, true );
            if ($ret === false) return false; // error occured
            $endpos=$endposition; return $ret; // success
            break;
        default: // string or error
            $colon_position = strpos($str, ':', $startpos+1);
            if ($colon_position === false) {
                // we couldn't find a colon, so this is not a string => error
                $this->errno  = 4;
                $this->errpos = $startpos;
                return false;
            }
            $before_colon   = substr($str, $startpos, $colon_position-$startpos);
            $after_colon    = substr($str, $colon_position + 1);
            if (!ctype_digit($before_colon)) {
                // the size of the string is not an integer as it should be
                $this->errno  = 5;
                $this->errpos = $startpos;
                return false;
            }
            $string_size = &$before_colon;
            $string = substr($after_colon, 0, $string_size);
            if (strlen($string) != $string_size) {
                // not the whole string is represented in the bencoded string
                $this->errno  = 6;
                $this->errpos = $startpos;
                return false;
            }
            $endpos = $colon_position+$string_size;
            return $string;
        }
    }
    
    /**
            *
            *  Transforms a list or dictionary into an array
            *
            *   @param string   The bencoded string we have to parse
            *   @param integer Position to start parsing at (the position is after the position of 'l' or 'd')
            *   @param &integer  Position it stopped parsing at (the position is the position of the 'e' character)
            *   @param boolean  true if what we have to parse is a dictionary, false otherwise
            *   @access private
            *   @return mixed   array on success, boolean false on failure
            */
    function _decode_arr ($str, $startpos, &$endpos, $isdictionary = false)
    {
        $ret = array(); // what we're going to return on success
        $element = 0;
        
        while (1)
        {
            if ($str[$startpos] == 'e') break;
            if ($startpos > strlen($str) -1)
            {
                // startpos got out of the string without finding an 'e' character
                $this->errno  = 8;
                $this->errpos = -1;
                return false;
            }
            $endposition=0;
            $listvalue = $this->_decode($str, $startpos, $endposition);
            if ($listvalue === false) return false;
            if ($isdictionary) {
                // we got a dictionary
                if ($element % 2 == 0) {
                    $lastkey = $listvalue; // we got a key for array
                    if (!is_string($lastkey)) {
                        // the keys of a dictionary MUST be strings
                        $this->errno  = 7;
                        $this->errpos = $startpos;
                    }
                }
                // we got a value for array, we add it into the array with the previously found key
                else $ret[$lastkey] = $listvalue;
            }
            else $ret[] = $listvalue; // we got a list, we just push into the array
            $startpos = $endposition+1;
            $element++;
        }
        $endpos = $startpos; // $startpos is the position where we found the 'e'
        return $ret;
    }
}
$benc = new BEncode;
$a = $benc->decode(file_get_contents('a.torrent'));
//$a = $benc->decode('li1111915968ee');
if ($a===false)echo "error ".$benc->errno." ".$benc->errpos;
else echo print_r($a);
?>