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); ?>