Jump to content

Bencode Decoding Torrent file, i have included the source.


Recommended Posts

<?php

// +----------------------------------------------------------------------+
// | Decode and Encode data in Bittorrent format                          |
// +----------------------------------------------------------------------+
// | Copyright (C) 2004-2005 Markus Tacker <m@tacker.org>                |
// +----------------------------------------------------------------------+
// | This library is free software; you can redistribute it and/or      |
// | modify it under the terms of the GNU Lesser General Public        |
// | License as published by the Free Software Foundation; either        |
// | version 2.1 of the License, or (at your option) any later version.   |
// |                                                                      |
// | This library is distributed in the hope that it will be useful,      |
// | but WITHOUT ANY WARRANTY; without even the implied warranty of    |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  |
// | Lesser General Public License for more details.                      |
// |                                                                      |
// | You should have received a copy of the GNU Lesser General Public    |
// | License along with this library; if not, write to the              |
// | Free Software Foundation, Inc.                                    |
// | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA            |
// +----------------------------------------------------------------------+

/**
* Encode data in Bittorrent format
*
* Based on
*   Original Python implementation by Petru Paler <petru@paler.net>
*   PHP translation by Gerard Krijgsman <webmaster@animesuki.com>
*   Gerard's regular exp[b][/b]ressions removed by Carl Ritson <critson@perlfu.co.uk>
* Info on the .torrent file format
* BEncoding is a simple, easy to implement method of associating
* data types with information in a file. The values in a torrent
* file are bEncoded.
* There are 4 different data types that can be bEncoded:
* Integers, Strings, Lists and Dictionaries.
* [http://www.monduna.com/bt/faq.html]
*
* @package File_Bittorrent
* @category File
* @author Markus Tacker <m@tacker.org>
* @version $Id: Decode.php 49 2005-10-19 17:06:23Z m $
*/

/**
* Include required classes
*/
require_once 'PEAR.php';
require_once 'PHP/Compat.php';
require_once 'File/Bittorrent/Encode.php';

/**
* Load replacement functions
*/
PHP_Compat::loadFunction('file_get_contents');

/**
* Encode data in Bittorrent format
*
* Based on
*   Original Python implementation by Petru Paler <petru@paler.net>
*   PHP translation by Gerard Krijgsman <webmaster@animesuki.com>
*   Gerard's regular exp[b][/b]ressions removed by Carl Ritson <critson@perlfu.co.uk>
* Info on the .torrent file format
* BEncoding is a simple, easy to implement method of associating
* data types with information in a file. The values in a torrent
* file are bEncoded.
* There are 4 different data types that can be bEncoded:
* Integers, Strings, Lists and Dictionaries.
* [http://www.monduna.com/bt/faq.html]
*
* @package File_Bittorrent
* @category File
* @author Markus Tacker <m@tacker.org>
*/
class File_Bittorrent_Decode
{
    /**
    * @var string   Name of the torrent
    */
    var $name = '';

    /**
    * @var string   Filename of the torrent
    */
    var $filename = '';

    /**
    * @var string   Comment
    */
    var $comment = '';

    /**
    * @var int   Creation date as unix timestamp
    */
    var $date = 0;

    /**
    * @var array    Files in the torrent
    */
    var $files = array();

    /**
    * @var int    Size of of the full torrent (after download)
    */
    var $size = 0;

    /**
    * @var string   Signature of the software which created the torrent
    */
    var $created_by = '';

    /**
    * @var string   tracker (the tracker the torrent has been received from)
    */
    var $announce = '';

    /**
    * @var array     List of known trackers for the torrent
    */
    var $announce_list = array();

    /**
    * @var string   Source string
    * @access private
    */
    var $_source = '';

    /**
    * @var int    Source length
    * @access private
    */
    var $_source_length = 0;

    /**
    * @var int    Current position of the string
    * @access private
    */
    var $_position = 0;

    /**
    * @var string   Info hash
    */
    var $info_hash;

    /**
    * @var mixed    The last error object or null if no error has occurred.
    */
    var $last_error;

    /**
    * Decode a Bencoded string
    *
    * @param string
    * @return mixed
    */
    function decode($str)
    {
        $this->_source = $str;
        $this->_position  = 0;
        $this->_source_length = strlen($this->_source);
        return $this->_bdecode();
    }

    /**
    * Decode .torrent file and accumulate information
    *
    * @param string Filename
    * @return mixed Returns an arrayon success or false on error
    */
    function decodeFile($file)
    {
        // Check file
        if (!is_file($file)) {
            $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::decode() - Not a file.', null, null, "Given filename '$file' is not a valid file.");
            return false;
        }

        // Reset public attributes
        $this->name       = '';
        $this->filename   = '';
        $this->comment     = '';
        $this->date       = 0;
        $this->files         = array();
        $this->size       = 0;
        $this->created_by   = '';
        $this->announce   = '';
        $this->announce_list = array();
        $this->_position     = 0;
        $this->info_hash     = '';

        // Decode .torrent
        $this->_source = file_get_contents($file);
        $this->_source_length = strlen($this->_source);
        $decoded = $this->_bdecode();
        if (!is_array($decoded)) {
            $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::decode() - Corrupted bencoded data.', null, null, "Failed to decode data from file '$file'.");
            return false;
        }

        // Compute info_hash
        $Encoder = new File_Bittorrent_Encode;
        $this->info_hash = sha1($Encoder->encode($decoded['info']));

        // Pull information form decoded data
        $this->filename = basename($file);
        // Name of the torrent - statet by the torrent's author
        $this->name  = $decoded['info']['name'];
        // Authors may add comments to a torrent
        if (isset($decoded['comment'])) {
            $this->comment = $decoded['comment'];
        }
        // Creation date of the torrent as unix timestamp
        if (isset($decoded['creation date'])) {
            $this->date = $decoded['creation date'];
        }
        // This contains the signature of the application used to create the torrent
        if (isset($decoded['created by'])) {
            $this->created_by = $decoded['created by'];
        }
        // Get the directory separator
        $sep = (PHP_OS == 'Linux') ? '/' : '\\';
        // There is sometimes an array listing all files
        // in the torrent with their individual filesize
        if (isset($decoded['info']['files']) and is_array($decoded['info']['files'])) {
            foreach ($decoded['info']['files'] as $file) {
                $path = join($sep, $file['path']);
                // We are computing the total size of the download heres
                $this->size += $file['length'];
                $this->files[] = array(
                    'filename' => $path,
                    'size'   => $file['length'],
                );
            }
        // In case the torrent contains only on file
        } elseif(isset($decoded['info']['name']))  {
                $this->files[] = array(
                   'filename' => $decoded['info']['name'],
                   'size'    => $decoded['info']['length'],
                );
        }
        // If the the info->length field is present we are dealing with
        // a single file torrent.
        if (isset($decoded['info']['length']) and $this->size == 0) {
            $this->size = $decoded['info']['length'];
        }

        // This contains the tracker the torrent has been received from
        if (isset($decoded['announce'])) {
            $this->announce = $decoded['announce'];
        }

        // This contains a list of all known trackers for this torrent
        if (isset($decoded['announce-list']) and is_array($decoded['announce-list'])) {
            foreach($decoded['announce-list'] as $item) {
                if (!isset($item[0])) continue;
                $this->announce_list[] = $item[0];
            }
        }

        // Currently, I'm not sure how to determine an error
        // Just try to fetch the info from the decoded data
        // and return it
        return array(
            'name'        => $this->name,
            'filename'    => $this->filename,
            'comment'      => $this->comment,
            'date'        => $this->date,
            'created_by'    => $this->created_by,
            'files'      => $this->files,
            'size'        => $this->size,
            'announce'    => $this->announce,
            'announce_list' => $this->announce_list,
        );
    }

    /**
    * Decode a BEncoded String
    *
    * @access private
    * @return mixed Returns the representation of the data in the BEncoded string or false on error
    */
    function _bdecode()
    {
        switch ($this->_getChar()) {
        case 'i':
            $this->_position++;
            return $this->_decode_int();
            break;
        case 'l':
            $this->_position++;
            return $this->_decode_list();
            break;
        case 'd':
            $this->_position++;
            return $this->_decode_dict();
            break;
        default:
            return $this->_decode_string();
        }
    }

    /**
    * Decode a BEncoded dictionary
    *
    * Dictionaries are prefixed with a d and terminated by an e. They
    * are similar to list, except that items are in key value pairs. The
    * dictionary {"key":"value", "Monduna":"com", "bit":"Torrents", "number":7}
    * would bEncode to d3:key5:value7:Monduna3:com3:bit:8:Torrents6:numberi7ee
    *
    * @access private
    * @return array
    */
    function _decode_dict()
    {
        while ($char = $this->_getChar()) {
            if ($char == 'e') break;
            $key = $this->_decode_string();
            $val = $this->_bdecode();
            $return[$key] = $val;
        }
        $this->_position++;
        return $return;
    }

    /**
    * Decode a BEncoded string
    *
    * Strings are prefixed with their length followed by a colon.
    * For example, "Monduna" would bEncode to 7:Monduna and "BitTorrents"
    * would bEncode to 11:BitTorrents.
    *
    * @access private
    * @return string|false
    */
    function _decode_string()
    {
        // Find position of colon
        // Supress error message if colon is not found which may be caused by a corrupted or wrong encoded string
        if(!$pos_colon = @strpos($this->_source, ':', $this->_position)) {
            return false;
        }
        // Get length of string
        $str_length = intval(substr($this->_source, $this->_position, $pos_colon));
        // Get string
        $return = substr($this->_source, $pos_colon + 1, $str_length);
        // Move Pointer after string
        $this->_position = $pos_colon + $str_length + 1;
        return $return;
    }

    /**
    * Decode a BEncoded integer
    *
    * Integers are prefixed with an i and terminated by an e. For
    * example, 123 would bEcode to i123e, -3272002 would bEncode to
    * i-3272002e.
    *
    * @access private
    * @return int
    */
    function _decode_int()
    {
        $pos_e  = strpos($this->_source, 'e', $this->_position);
        $return = intval(substr($this->_source, $this->_position, $pos_e - $this->_position));
        $this->_position = $pos_e + 1;
        return $return;
    }

    /**
    * Decode a BEncoded list
    *
    * Lists are prefixed with a l and terminated by an e. The list
    * should contain a series of bEncoded elements. For example, the
    * list of strings ["Monduna", "Bit", "Torrents"] would bEncode to
    * l7:Monduna3:Bit8:Torrentse. The list [1, "Monduna", 3, ["Sub", "List"]]
    * would bEncode to li1e7:Mondunai3el3:Sub4:Listee
    *
    * @access private
    * @return array
    */
    function _decode_list()
    {
        $return = array();
        $char = $this->_getChar();
        while ($this->_source{$this->_position} != 'e') {
            $val = $this->_bdecode();
            $return[] = $val;
        }
        $this->_position++;
        return $return;
    }

    /**
    * Get the char at the current position
    *
    * @access private
    * @return string|false
    */
    function _getChar()
    {
        if (empty($this->_source)) return false;
        if ($this->_position >= $this->_source_length) return false;
        return $this->_source{$this->_position};
    }

    /**
    * Returns the online stats for the torrent
    *
    * @return array|false
    */
    function getStats()
    {
        // Check if we can access remote data
        if (!ini_get('allow_url_fopen')) {
            $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::getStats() - "allow_url_fopen" must be enabled.');
            return false;
        }
        // Query the scrape page
        $packed_hash = pack('H*', $this->info_hash);
        $scrape_url = preg_replace('/\/announce$/', '/scrape', $this->announce) . '?info_hash=' . urlencode($packed_hash);
        $scrape_data = file_get_contents($scrape_url);
        $stats = $this->decode($scrape_data);
        if (!isset($stats['files'][$packed_hash])) {
            $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::getStats() - Invalid scrape data: "' . $scrape_data . '"');
            return false;
        }
        return $stats['files'][$packed_hash];
    }
}

?>

That is the PHP version of decoding a torrent, however i dont understand what $this-> does... therefore have no clue how to code it using autoit, i am using an external SHA1 program to hash using solid snakes HASHING UDF.

All i want to do is get all the verables into Strings, so put info_hash data into $INFO_hash then RUN SHA1 on $INFO_hash and thats it.. all done.

how to decode the torrent file: http://www.monduna.com/bt/faq.html#TERM_12

However i cant code it :P

Below is what is found inside an average torrent file. however many things are missing as its binary i couldnt copy and paste everything.

d8:announce44:http://zerowing.idsoftware.com:6969/announce13:creation datei1135684016e4:infod6:lengthi11001339e4:name32:doom3-linux-1.3.1302-sdk.x86.run12:piece lengthi65536e6:pieces3360:¯Âµ¦.¨z
z«hY.<^3_8Ê)­|%Ï=ß]=ë¬-Ы¤Úåñ'Ã¥´^8kb{3Sؤè);¿]ötË7R^ý­üöõ#¬â]­Fé`yt©ÊüÐv>Ü¡z9)\
ª}amF&í
£òW¹ç® bJÊ7«×¨§E ¹ÐÖÝ&z"Ì3w G®  fnÇ<¢¤Ð@

Dim $name = ""

Dim $filename = ""

Dim $comment = ""

Dim $date = 0

Dim $files ; array

Dim $size = 0

Dim $created_by = ""

dim $announce = ""

dim $announce_list ;array

dim $_source = ""

Dim $_source_length = 0

Dim $_position = 0

Dim $info_hash

Dim $last_error

Above is all the verables i need to extract from the Bencode torrent file.

I have attached an example torrent, i named it demo.txt because demo.torrent is blocked from geting uploaded.

#Include <FileHashSHA1.au3>

$file = FileOpen("demo.torrent", 0)

dim $counter = 1

while 1
$test = FileRead($file, 1)
if @error = -1 exitloop
$counter = $counter + 1
wend

$torrentdata = FileRead($file, $test) 

; due to verable torrent file size.. this way i get the entire torrent data

FileClose($file)

; NOW once you have $torrentdata, i want to sperate out all the stuff into string
; such as "info" to $info_hash then SHA1 hashing it.

$decoded_info_hash =_FileHashSHA1($info_hash)

;now i have info hash, decoded

As you can see i can do rest of the code, only part i am stuck on is extracting all the required data.

demo.txt

Edited by pingpong24
Link to comment
Share on other sites

  • 6 months later...

i think this is a bit better code

<?

/*

Basic knowledge of how bencoding works is assumed. Details can be found
at <http://bitconjurer.org/BitTorrent/protocol.html>.



How to use these functions:

An "object" is defined to be an associative array with at least the keys
"type" and "value" present. The "type" key contains a string which is
one of "string", "integer", "list" or "dictionary". The "value" key
contains the appropriate thing, either a string, an integer, a list which
is just a flat array, or a dictionary, which is an associative array. In
the case of "list" and "dictionary", the values of the contained array
are agaib "objects".



Description of the functions:



string benc($obj);

Takes an object as argument and returns the bencoded form of it as string.
Returns the undefined/unset value on failure.

Examples:

benc(array(type => "string", value => "spam"))              returns "4:spam".
benc(array(type => "integer", value => 3))              returns "i3e".
benc(array(type => "list", value => array(
        array(type => "string", value => "spam"),
        array(type => "string", value => "eggs")
)))
                                                returns "l4:spam4:eggse"

benc(array(type => "dictionary", value => array(
        cow => array(type => "string", value => "moo"),
        spam => array(type => "string", value => "eggs"),
)))
                                        returns "d3:cow3:moo4:spam4:eggse"




object bdec($str);

Returns the object that results from bdecoding the given string. Note
that those aren't real php objects, but merely "objects" as described
above. The returned objects have two additional keys: "string" and
"strlen". They represent the bencoded form of the returned objects, as
it was given in the original bencoded string. Use this to extract
certain portions of a bencoded string without having to re-encode it
(and avoiding possible re-ordering of dictionary keys). $x["strlen"]
is always equivalent to strlen($x["string"]). The "string" attribute
of the top-level returned object will be the same as the original
bencoded string, unless there's trailing garbage at the end of the
string.

This function returns the undefined/unset value on failure.

Example:

bdec("d4:spaml11:spiced pork3:hamee")
        returns this monster:

Array
(
    [type] => dictionary
    [value] => Array
        (
            [spam] => Array
                (
                    [type] => list
                    [value] => Array
                        (
                            [0] => Array
                                (
                                    [type] => string
                                    [value] => spiced pork
                                    [strlen] => 14
                                    [string] => 11:spiced pork
                                )

                            [1] => Array
                                (
                                    [type] => string
                                    [value] => ham
                                    [strlen] => 5
                                    [string] => 3:ham
                                )

                        )

                    [strlen] => 21
                    [string] => l11:spiced pork3:hame
                )

        )

    [strlen] => 29
    [string] => d4:spaml11:spiced pork3:hamee
)





object bdec_file($filename, $maxsize);

Opens the specified file, reads its contents (up to the specified length),
and returns whatever bdec() returns for those contents. This is a simple
convenience function.

*/

function benc($obj) {
        if (!is_array($obj) || !isset($obj["type"]) || !isset($obj["value"]))
                return;
        $c = $obj["value"];
        switch ($obj["type"]) {
                case "string":
                        return benc_str($c);
                case "integer":
                        return benc_int($c);
                case "list":
                        return benc_list($c);
                case "dictionary":
                        return benc_dict($c);
                default:
                        return;
        }
}

function benc_str($s) {
        return strlen($s) . ":$s";
}

function benc_int($i) {
        return "i" . $i . "e";
}

function benc_list($a) {
        $s = "l";
        foreach ($a as $e) {
                $s .= benc($e);
        }
        $s .= "e";
        return $s;
}

function benc_dict($d) {
        $s = "d";
        $keys = array_keys($d);
        sort($keys);
        foreach ($keys as $k) {
                $v = $d[$k];
                $s .= benc_str($k);
                $s .= benc($v);
        }
        $s .= "e";
        return $s;
}

function bdec_file($f, $ms) {
        $fp = fopen($f, "rb");
        if (!$fp)
                return;
        $e = fread($fp, $ms);
        fclose($fp);
        return bdec($e);
}

function bdec($s) {
        if (preg_match('/^(\d+):/', $s, $m)) {
                $l = $m[1];
                $pl = strlen($l) + 1;
                $v = substr($s, $pl, $l);
                $ss = substr($s, 0, $pl + $l);
                if (strlen($v) != $l)
                        return;
                return array(type => "string", value => $v, strlen => strlen($ss), string => $ss);
        }
        if (preg_match('/^i(\d+)e/', $s, $m)) {
                $v = $m[1];
                $ss = "i" . $v . "e";
                if ($v === "-0")
                        return;
                if ($v[0] == "0" && strlen($v) != 1)
                        return;
                return array(type => "integer", value => $v, strlen => strlen($ss), string => $ss);
        }
        switch ($s[0]) {
                case "l":
                        return bdec_list($s);
                case "d":
                        return bdec_dict($s);
                default:
                        return;
        }
}

function bdec_list($s) {
        if ($s[0] != "l")
                return;
        $sl = strlen($s);
        $i = 1;
        $v = array();
        $ss = "l";
        for (;;) {
                if ($i >= $sl)
                        return;
                if ($s[$i] == "e")
                        break;
                $ret = bdec(substr($s, $i));
                if (!isset($ret) || !is_array($ret))
                        return;
                $v[] = $ret;
                $i += $ret["strlen"];
                $ss .= $ret["string"];
        }
        $ss .= "e";
        return array(type => "list", value => $v, strlen => strlen($ss), string => $ss);
}

function bdec_dict($s) {
        if ($s[0] != "d")
                return;
        $sl = strlen($s);
        $i = 1;
        $v = array();
        $ss = "d";
        for (;;) {
                if ($i >= $sl)
                        return;
                if ($s[$i] == "e")
                        break;
                $ret = bdec(substr($s, $i));
                if (!isset($ret) || !is_array($ret) || $ret["type"] != "string")
                        return;
                $k = $ret["value"];
                $i += $ret["strlen"];
                $ss .= $ret["string"];
                if ($i >= $sl)
                        return;
                $ret = bdec(substr($s, $i));
                if (!isset($ret) || !is_array($ret))
                        return;
                $v[$k] = $ret;
                $i += $ret["strlen"];
                $ss .= $ret["string"];
        }
        $ss .= "e";
        return array(type => "dictionary", value => $v, strlen => strlen($ss), string => $ss);
}

?>
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...