File_Bittorrent
[ class tree: File_Bittorrent ] [ index: File_Bittorrent ] [ all elements ]

Source for file Decode.php

Documentation is available at Decode.php

  1. <?php
  2.  
  3. // +----------------------------------------------------------------------+
  4. // | Decode and Encode data in Bittorrent format                          |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (C) 2004-2006 Markus Tacker <m@tacker.org>                 |
  7. // +----------------------------------------------------------------------+
  8. // | This library is free software; you can redistribute it and/or        |
  9. // | modify it under the terms of the GNU Lesser General Public           |
  10. // | License as published by the Free Software Foundation; either         |
  11. // | version 2.1 of the License, or (at your option) any later version.   |
  12. // |                                                                      |
  13. // | This library is distributed in the hope that it will be useful,      |
  14. // | but WITHOUT ANY WARRANTY; without even the implied warranty of       |
  15. // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |
  16. // | Lesser General Public License for more details.                      |
  17. // |                                                                      |
  18. // | You should have received a copy of the GNU Lesser General Public     |
  19. // | License along with this library; if not, write to the                |
  20. // | Free Software Foundation, Inc.                                       |
  21. // | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA               |
  22. // +----------------------------------------------------------------------+
  23.  
  24.  
  25.  
  26. /**
  27. * Encode data in Bittorrent format
  28. *
  29. * Based on
  30. *   Original Python implementation by Petru Paler <petru@paler.net>
  31. *   PHP translation by Gerard Krijgsman <webmaster@animesuki.com>
  32. *   Gerard's regular expressions removed by Carl Ritson <critson@perlfu.co.uk>
  33. * Info on the .torrent file format
  34. * BEncoding is a simple, easy to implement method of associating
  35. * data types with information in a file. The values in a torrent
  36. * file are bEncoded.
  37. * There are 4 different data types that can be bEncoded:
  38. * Integers, Strings, Lists and Dictionaries.
  39. * [http://www.monduna.com/bt/faq.html]
  40. *
  41. @package File_Bittorrent
  42. @category File
  43. @author Markus Tacker <m@tacker.org>
  44. @author Robin H. Johnson <robbat2@gentoo.org>
  45. @version $Id: Decode.php 65 2006-10-23 10:46:20Z m $
  46. */
  47.  
  48. /**
  49. * Include required classes
  50. */
  51. require_once 'PEAR.php';
  52. require_once 'PHP/Compat.php';
  53. require_once 'File/Bittorrent/Encode.php';
  54.  
  55. /**
  56. * Load replacement functions
  57. */
  58. PHP_Compat::loadFunction('file_get_contents');
  59.  
  60. /**
  61. * Encode data in Bittorrent format
  62. *
  63. * Based on
  64. *   Original Python implementation by Petru Paler <petru@paler.net>
  65. *   PHP translation by Gerard Krijgsman <webmaster@animesuki.com>
  66. *   Gerard's regular expressions removed by Carl Ritson <critson@perlfu.co.uk>
  67. * Info on the .torrent file format
  68. * BEncoding is a simple, easy to implement method of associating
  69. * data types with information in a file. The values in a torrent
  70. * file are bEncoded.
  71. * There are 4 different data types that can be bEncoded:
  72. * Integers, Strings, Lists and Dictionaries.
  73. * [http://www.monduna.com/bt/faq.html]
  74. *
  75. @package File_Bittorrent
  76. @category File
  77. @author Markus Tacker <m@tacker.org>
  78. @author Robin H. Johnson <robbat2@gentoo.org>
  79. */
  80. {
  81.     /**
  82.     * @var string   Name of the torrent
  83.     */
  84.     var $name = '';
  85.  
  86.     /**
  87.     * @var string   Filename of the torrent
  88.     */
  89.     var $filename = '';
  90.  
  91.     /**
  92.     * @var string   Comment
  93.     */
  94.     var $comment = '';
  95.  
  96.     /**
  97.     * @var int   Creation date as unix timestamp
  98.     */
  99.     var $date = 0;
  100.  
  101.     /**
  102.     * @var array    Files in the torrent
  103.     */
  104.     var $files = array();
  105.  
  106.     /**
  107.     * @var int      Size of of the full torrent (after download)
  108.     */
  109.     var $size = 0;
  110.  
  111.     /**
  112.     * @var string   Signature of the software which created the torrent
  113.     */
  114.     var $created_by = '';
  115.  
  116.     /**
  117.     * @var string    tracker (the tracker the torrent has been received from)
  118.     */
  119.     var $announce = '';
  120.  
  121.     /**
  122.     * @var array     List of known trackers for the torrent
  123.     */
  124.     var $announce_list = array();
  125.  
  126.     /**
  127.     * @var string   Source string
  128.     * @access private
  129.     */
  130.     var $_source '';
  131.  
  132.     /**
  133.     * @var int      Source length
  134.     * @access private
  135.     */
  136.     var $_source_length = 0;
  137.  
  138.     /**
  139.     * @var int      Current position of the string
  140.     * @access private
  141.     */
  142.     var $_position = 0;
  143.  
  144.     /**
  145.     * @var string   Info hash
  146.     */
  147.     var $info_hash;
  148.  
  149.     /**
  150.     * @var mixed    The last error object or null if no error has occurred.
  151.     */
  152.     var $last_error;
  153.  
  154.     /**
  155.     * @var array    Decoded data from File_Bittorrent_Decode::decodeFile()
  156.     */
  157.     var $decoded = array();
  158.  
  159.     /**
  160.     * Decode a Bencoded string
  161.     *
  162.     * @param string 
  163.     * @return mixed 
  164.     */
  165.     function decode($str)
  166.     {
  167.         $this->_source $str;
  168.         $this->_position  = 0;
  169.         $this->_source_length strlen($this->_source);
  170.         $result $this->_bdecode();
  171.         if ($this->_position $this->_source_length{
  172.             $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::decode() - Trailing garbage in file.');
  173.             return false;
  174.         }
  175.         return $result;
  176.     }
  177.  
  178.     /**
  179.     * Decode .torrent file and accumulate information
  180.     *
  181.     * @param string    Filename
  182.     * @return mixed    Returns an arrayon success or false on error
  183.     */
  184.     function decodeFile($file)
  185.     {
  186.         // Check file
  187.         if (!is_file($file)) {
  188.             $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::decode() - Not a file.'nullnull"Given filename '$file' is not a valid file.");
  189.             return false;
  190.         }
  191.  
  192.         // Reset public attributes
  193.         $this->name          = '';
  194.         $this->filename      = '';
  195.         $this->comment       = '';
  196.         $this->date          = 0;
  197.         $this->files         = array();
  198.         $this->size          = 0;
  199.         $this->created_by    = '';
  200.         $this->announce      = '';
  201.         $this->announce_list = array();
  202.         $this->_position     = 0;
  203.         $this->info_hash     = '';
  204.  
  205.         // Decode .torrent
  206.         $this->_source file_get_contents($file);
  207.         $this->_source_length strlen($this->_source);
  208.         $this->decoded = $this->_bdecode();
  209.         if (!is_array($this->decoded)) {
  210.             $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::decode() - Corrupted bencoded data.'nullnull"Failed to decode data from file '$file'.");
  211.             return false;
  212.         }
  213.  
  214.         // Compute info_hash
  215.         $Encoder = new File_Bittorrent_Encode;
  216.         $this->info_hash = sha1($Encoder->encode($this->decoded['info']));
  217.  
  218.         // Pull information form decoded data
  219.         $this->filename = basename($file);
  220.         // Name of the torrent - statet by the torrent's author
  221.         $this->name     = $this->decoded['info']['name'];
  222.         // Authors may add comments to a torrent
  223.         if (isset($this->decoded['comment'])) {
  224.             $this->comment = $this->decoded['comment'];
  225.         }
  226.         // Creation date of the torrent as unix timestamp
  227.         if (isset($this->decoded['creation date'])) {
  228.             $this->date = $this->decoded['creation date'];
  229.         }
  230.         // This contains the signature of the application used to create the torrent
  231.         if (isset($this->decoded['created by'])) {
  232.             $this->created_by = $this->decoded['created by'];
  233.         }
  234.         // Get the directory separator
  235.         $sep (PHP_OS == 'Linux''/' '\\';
  236.         // There is sometimes an array listing all files
  237.         // in the torrent with their individual filesize
  238.         if (isset($this->decoded['info']['files']and is_array($this->decoded['info']['files'])) {
  239.             foreach ($this->decoded['info']['files'as $file{
  240.                 $path join($sep$file['path']);
  241.                 // We are computing the total size of the download heres
  242.                 $this->size += $file['length'];
  243.                 $this->files[= array(
  244.                     'filename' => $path,
  245.                     'size'     => $file['length'],
  246.                 );
  247.             }
  248.         // In case the torrent contains only on file
  249.         elseif (isset($this->decoded['info']['name']))  {
  250.                 $this->files[= array(
  251.                    'filename' => $this->decoded['info']['name'],
  252.                    'size'     => $this->decoded['info']['length'],
  253.                 );
  254.         }
  255.         // If the the info->length field is present we are dealing with
  256.         // a single file torrent.
  257.         if (isset($this->decoded['info']['length']and $this->size == 0{
  258.             $this->size = $this->decoded['info']['length'];
  259.         }
  260.  
  261.         // This contains the tracker the torrent has been received from
  262.         if (isset($this->decoded['announce'])) {
  263.             $this->announce = $this->decoded['announce'];
  264.         }
  265.  
  266.         // This contains a list of all known trackers for this torrent
  267.         if (isset($this->decoded['announce-list']and is_array($this->decoded['announce-list'])) {
  268.             $this->announce_list = $this->decoded['announce-list'];
  269.         }
  270.  
  271.         // Currently, I'm not sure how to determine an error
  272.         // Just try to fetch the info from the decoded data
  273.         // and return it
  274.         return array(
  275.             'name'          => $this->name,
  276.             'filename'      => $this->filename,
  277.             'comment'       => $this->comment,
  278.             'date'          => $this->date,
  279.             'created_by'    => $this->created_by,
  280.             'files'         => $this->files,
  281.             'size'          => $this->size,
  282.             'announce'      => $this->announce,
  283.             'announce_list' => $this->announce_list,
  284.         );
  285.     }
  286.  
  287.     /**
  288.     * Decode a BEncoded String
  289.     *
  290.     * @access private
  291.     * @return mixed    Returns the representation of the data in the BEncoded string or false on error
  292.     */
  293.     function _bdecode()
  294.     {
  295.         switch ($this->_getChar()) {
  296.         case 'i':
  297.             $this->_position++;
  298.             return $this->_decode_int();
  299.             break;
  300.         case 'l':
  301.             $this->_position++;
  302.             return $this->_decode_list();
  303.             break;
  304.         case 'd':
  305.             $this->_position++;
  306.             return $this->_decode_dict();
  307.             break;
  308.         default:
  309.             return $this->_decode_string();
  310.         }
  311.     }
  312.  
  313.     /**
  314.     * Decode a BEncoded dictionary
  315.     *
  316.     * Dictionaries are prefixed with a d and terminated by an e. They
  317.     * are similar to list, except that items are in key value pairs. The
  318.     * dictionary {"key":"value", "Monduna":"com", "bit":"Torrents", "number":7}
  319.     * would bEncode to d3:key5:value7:Monduna3:com3:bit:8:Torrents6:numberi7ee
  320.     *
  321.     * @access private
  322.     * @return array 
  323.     */
  324.     function _decode_dict()
  325.     {
  326.         $return = array();
  327.         $ended = false;
  328.         $lastkey = NULL;
  329.         while ($char $this->_getChar()) {
  330.             if ($char == 'e'{
  331.                 $ended = true;
  332.                 break;
  333.             }
  334.             if (!ctype_digit($char)) {
  335.                 $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_dict() - Invalid dictionary key.');
  336.                 $return = false;
  337.                 break;
  338.             }
  339.             $key $this->_decode_string();
  340.             if (isset($return[$key])) {
  341.                 $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_dict() - Duplicate dictionary key.');
  342.                 $return = false;
  343.                 break;
  344.             }
  345.             if ($key $lastkey{
  346.                 $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_dict() - Missorted dictionary key.');
  347.                 $return = false;
  348.                 break;
  349.             }
  350.             $val $this->_bdecode();
  351.             if ($val === false{
  352.                 $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_dict() - Invalid value.');
  353.                 $return = false;
  354.                 break;
  355.             }
  356.             $return[$key$val;
  357.             $lastkey $key;
  358.         }
  359.         if (!$ended{
  360.             $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_dict() - Unterminated dictionary.');
  361.             $return = false;
  362.         }
  363.         $this->_position++;
  364.         return $return;
  365.     }
  366.  
  367.     /**
  368.     * Decode a BEncoded string
  369.     *
  370.     * Strings are prefixed with their length followed by a colon.
  371.     * For example, "Monduna" would bEncode to 7:Monduna and "BitTorrents"
  372.     * would bEncode to 11:BitTorrents.
  373.     *
  374.     * @access private
  375.     * @return string|false
  376.     */
  377.     function _decode_string()
  378.     {
  379.         // Check for bad leading zero
  380.         if (substr($this->_source$this->_position1== '0' and
  381.         substr($this->_source$this->_position + 11!= ':'{
  382.             $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_string() - Leading zero in string length.');
  383.             return false;
  384.         }
  385.         // Find position of colon
  386.         // Supress error message if colon is not found which may be caused by a corrupted or wrong encoded string
  387.         if (!$pos_colon @strpos($this->_source':'$this->_position)) {
  388.             $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_string() - Colon not found.');
  389.             return false;
  390.         }
  391.         // Get length of string
  392.         $str_length intval(substr($this->_source$this->_position$pos_colon));
  393.         if ($str_length $pos_colon + 1 > $this->_source_length{
  394.             $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_string() - Input too short for string length.');
  395.             return false;
  396.         }
  397.         // Get string
  398.         if ($str_length === 0{
  399.             $return '';
  400.         else {
  401.             $return substr($this->_source$pos_colon + 1$str_length);
  402.         }
  403.         // Move Pointer after string
  404.         $this->_position $pos_colon $str_length + 1;
  405.         return $return;
  406.     }
  407.  
  408.     /**
  409.     * Decode a BEncoded integer
  410.     *
  411.     * Integers are prefixed with an i and terminated by an e. For
  412.     * example, 123 would bEcode to i123e, -3272002 would bEncode to
  413.     * i-3272002e.
  414.     *
  415.     * @access private
  416.     * @return int 
  417.     */
  418.     function _decode_int()
  419.     {
  420.         $pos_e  strpos($this->_source'e'$this->_position);
  421.         $p $this->_position;
  422.         if ($p === $pos_e{
  423.             $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_int() - Empty integer.');
  424.             return false;
  425.         }
  426.         if (substr($this->_source$this->_position1== '-'$p++;
  427.         if (substr($this->_source$p1== '0' and
  428.         ($p != $this->_position or $pos_e $p+1)) {
  429.             $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_int() - Leading zero in integer.');
  430.             return false;
  431.         }
  432.         for ($i $p$i $pos_e-1; $i++{
  433.             if (!ctype_digit(substr($this->_source$i1))) {
  434.                 $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_int() - Non-digit characters in integer.');
  435.                 return false;
  436.             }
  437.         }
  438.         // The return value showld be automatically casted to float if the intval would
  439.         // overflow. The "+ 0" accomplishes exactly that, using the internal casting
  440.         // logic of PHP
  441.         $return substr($this->_source$this->_position$pos_e $this->_position+ 0;
  442.         $this->_position $pos_e + 1;
  443.         return $return;
  444.     }
  445.  
  446.     /**
  447.     * Decode a BEncoded list
  448.     *
  449.     * Lists are prefixed with a l and terminated by an e. The list
  450.     * should contain a series of bEncoded elements. For example, the
  451.     * list of strings ["Monduna", "Bit", "Torrents"] would bEncode to
  452.     * l7:Monduna3:Bit8:Torrentse. The list [1, "Monduna", 3, ["Sub", "List"]]
  453.     * would bEncode to li1e7:Mondunai3el3:Sub4:Listee
  454.     *
  455.     * @access private
  456.     * @return array 
  457.     */
  458.     function _decode_list()
  459.     {
  460.         $return = array();
  461.         $char $this->_getChar();
  462.         $p1 $p2 = 0;
  463.         if ($char === false{
  464.             $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_list() - Unterminated list.');
  465.             return false;
  466.         }
  467.         while ($char !== false && substr($this->_source$this->_position1!= 'e'{
  468.             $p1 $this->_position;
  469.             $val $this->_bdecode();
  470.             $p2 $this->_position;
  471.             // Empty does not work here
  472.             if($p1 == $p2)  {
  473.                 $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_list() - Unterminated list.');
  474.                 return false;
  475.             }
  476.             $return[$val;
  477.         }
  478.         $this->_position++;
  479.         return $return;
  480.     }
  481.  
  482.     /**
  483.     * Get the char at the current position
  484.     *
  485.     * @access private
  486.     * @return string|false
  487.     */
  488.     function _getChar()
  489.     {
  490.         if (empty($this->_source)) return false;
  491.         if ($this->_position >= $this->_source_lengthreturn false;
  492.         return substr($this->_source$this->_position1);
  493.     }
  494.  
  495.     /**
  496.     * Returns the online stats for the torrent
  497.     *
  498.     * @return array|false
  499.     */
  500.     function getStats()
  501.     {
  502.         // Check if we can access remote data
  503.         if (!ini_get('allow_url_fopen')) {
  504.             $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::getStats() - "allow_url_fopen" must be enabled.');
  505.             return false;
  506.         }
  507.         // Query the scrape page
  508.         $packed_hash pack('H*'$this->info_hash);
  509.         $scrape_url preg_replace('/\/announce$/''/scrape'$this->announce'?info_hash=' urlencode($packed_hash);
  510.         $scrape_data file_get_contents($scrape_url);
  511.         $stats $this->decode($scrape_data);
  512.         if (!isset($stats['files'][$packed_hash])) {
  513.             $this->last_error = PEAR::raiseError('File_Bittorrent_Decode::getStats() - Invalid scrape data: "' $scrape_data '"');
  514.             return false;
  515.         }
  516.         return $stats['files'][$packed_hash];
  517.     }
  518. }
  519.  
  520. ?>

Documentation generated on Tue, 13 Mar 2007 10:00:11 -0500 by phpDocumentor 1.3.0. PEAR Logo Copyright © PHP Group 2004.