Current File : //home/strato/chroot/opt/RZphp80/includes/File/Bittorrent/Decode.php |
<?php
// +----------------------------------------------------------------------+
// | Decode and Encode data in Bittorrent format |
// +----------------------------------------------------------------------+
// | Copyright (C) 2004-2006 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 expressions 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>
* @author Robin H. Johnson <robbat2@gentoo.org>
* @version $Id: Decode.php 65 2006-10-23 10:46:20Z 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 expressions 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>
* @author Robin H. Johnson <robbat2@gentoo.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;
/**
* @var array Decoded data from File_Bittorrent_Decode::decodeFile()
*/
var $decoded = array();
/**
* Decode a Bencoded string
*
* @param string
* @return mixed
*/
function decode($str)
{
$this->_source = $str;
$this->_position = 0;
$this->_source_length = strlen($this->_source);
$result = $this->_bdecode();
if ($this->_position < $this->_source_length) {
$this->last_error = PEAR::raiseError('File_Bittorrent_Decode::decode() - Trailing garbage in file.');
return false;
}
return $result;
}
/**
* 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);
$this->decoded = $this->_bdecode();
if (!is_array($this->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($this->decoded['info']));
// Pull information form decoded data
$this->filename = basename($file);
// Name of the torrent - statet by the torrent's author
$this->name = $this->decoded['info']['name'];
// Authors may add comments to a torrent
if (isset($this->decoded['comment'])) {
$this->comment = $this->decoded['comment'];
}
// Creation date of the torrent as unix timestamp
if (isset($this->decoded['creation date'])) {
$this->date = $this->decoded['creation date'];
}
// This contains the signature of the application used to create the torrent
if (isset($this->decoded['created by'])) {
$this->created_by = $this->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($this->decoded['info']['files']) and is_array($this->decoded['info']['files'])) {
foreach ($this->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($this->decoded['info']['name'])) {
$this->files[] = array(
'filename' => $this->decoded['info']['name'],
'size' => $this->decoded['info']['length'],
);
}
// If the the info->length field is present we are dealing with
// a single file torrent.
if (isset($this->decoded['info']['length']) and $this->size == 0) {
$this->size = $this->decoded['info']['length'];
}
// This contains the tracker the torrent has been received from
if (isset($this->decoded['announce'])) {
$this->announce = $this->decoded['announce'];
}
// This contains a list of all known trackers for this torrent
if (isset($this->decoded['announce-list']) and is_array($this->decoded['announce-list'])) {
$this->announce_list = $this->decoded['announce-list'];
}
// 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()
{
$return = array();
$ended = false;
$lastkey = NULL;
while ($char = $this->_getChar()) {
if ($char == 'e') {
$ended = true;
break;
}
if (!ctype_digit($char)) {
$this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_dict() - Invalid dictionary key.');
$return = false;
break;
}
$key = $this->_decode_string();
if (isset($return[$key])) {
$this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_dict() - Duplicate dictionary key.');
$return = false;
break;
}
if ($key < $lastkey) {
$this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_dict() - Missorted dictionary key.');
$return = false;
break;
}
$val = $this->_bdecode();
if ($val === false) {
$this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_dict() - Invalid value.');
$return = false;
break;
}
$return[$key] = $val;
$lastkey = $key;
}
if (!$ended) {
$this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_dict() - Unterminated dictionary.');
$return = false;
}
$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()
{
// Check for bad leading zero
if (substr($this->_source, $this->_position, 1) == '0' and
substr($this->_source, $this->_position + 1, 1) != ':') {
$this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_string() - Leading zero in string length.');
return false;
}
// 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)) {
$this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_string() - Colon not found.');
return false;
}
// Get length of string
$str_length = intval(substr($this->_source, $this->_position, $pos_colon));
if ($str_length + $pos_colon + 1 > $this->_source_length) {
$this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_string() - Input too short for string length.');
return false;
}
// Get string
if ($str_length === 0) {
$return = '';
} else {
$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);
$p = $this->_position;
if ($p === $pos_e) {
$this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_int() - Empty integer.');
return false;
}
if (substr($this->_source, $this->_position, 1) == '-') $p++;
if (substr($this->_source, $p, 1) == '0' and
($p != $this->_position or $pos_e > $p+1)) {
$this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_int() - Leading zero in integer.');
return false;
}
for ($i = $p; $i < $pos_e-1; $i++) {
if (!ctype_digit(substr($this->_source, $i, 1))) {
$this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_int() - Non-digit characters in integer.');
return false;
}
}
// The return value showld be automatically casted to float if the intval would
// overflow. The "+ 0" accomplishes exactly that, using the internal casting
// logic of PHP
$return = substr($this->_source, $this->_position, $pos_e - $this->_position) + 0;
$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();
$p1 = $p2 = 0;
if ($char === false) {
$this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_list() - Unterminated list.');
return false;
}
while ($char !== false && substr($this->_source, $this->_position, 1) != 'e') {
$p1 = $this->_position;
$val = $this->_bdecode();
$p2 = $this->_position;
// Empty does not work here
if($p1 == $p2) {
$this->last_error = PEAR::raiseError('File_Bittorrent_Decode::_decode_list() - Unterminated list.');
return false;
}
$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 substr($this->_source, $this->_position, 1);
}
/**
* 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];
}
}
?>