BioTorrents.de’s version of Gazelle
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

bencodedecode.class.php 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. <?php
  2. /**
  3. * The decode class is simple and straightforward. The only thing to
  4. * note is that empty dictionaries are represented by boolean trues
  5. */
  6. class BencodeDecode extends Bencode
  7. {
  8. private $Data;
  9. private $Length;
  10. private $Pos = 0;
  11. public $Dec = [];
  12. public $ExitOnError = true;
  13. const SnipLength = 40;
  14. /**
  15. * Decode prepararations
  16. *
  17. * @param string $Arg bencoded string or path to bencoded file to decode
  18. * @param bool $IsPath needs to be true if $Arg is a path
  19. * @return decoded data with a suitable structure
  20. */
  21. public function __construct($Arg = false, $IsPath = false, $Strict = true)
  22. {
  23. if (!$Strict) {
  24. $this->ExitOnError = false;
  25. }
  26. if ($Arg === false) {
  27. if (empty($this->Enc)) {
  28. return false;
  29. }
  30. } else {
  31. if ($IsPath === true) {
  32. return $this->bdec_file($Arg);
  33. }
  34. $this->Data = $Arg;
  35. }
  36. return $this->decode();
  37. }
  38. /**
  39. * Decodes a bencoded file
  40. *
  41. * @param $Path path to bencoded file to decode
  42. * @return decoded data with a suitable structure
  43. */
  44. public function bdec_file($Path = false)
  45. {
  46. if (empty($Path)) {
  47. return false;
  48. }
  49. if (!$this->Data = @file_get_contents($Path, FILE_BINARY)) {
  50. return $this->error("Error: file '$Path' could not be opened.\n");
  51. }
  52. return $this->decode();
  53. }
  54. /**
  55. * Decodes a string with bencoded data
  56. *
  57. * @param mixed $Arg bencoded data or false to decode the content of $this->Data
  58. * @return decoded data with a suitable structure
  59. */
  60. public function decode($Arg = false)
  61. {
  62. if ($Arg !== false) {
  63. $this->Data = $Arg;
  64. } elseif (!$this->Data) {
  65. $this->Data = $this->Enc;
  66. }
  67. if (!$this->Data) {
  68. return false;
  69. }
  70. $this->Length = strlen($this->Data);
  71. $this->Pos = 0;
  72. $this->Dec = $this->_bdec();
  73. if ($this->Pos < $this->Length) {
  74. // Not really necessary, but if the torrent is invalid, it's better to warn than to silently truncate it
  75. return $this->error();
  76. }
  77. return $this->Dec;
  78. }
  79. /**
  80. * Internal decoding function that does the actual job
  81. *
  82. * @return decoded data with a suitable structure
  83. */
  84. private function _bdec()
  85. {
  86. switch ($this->Data[$this->Pos]) {
  87. case 'i':
  88. $this->Pos++;
  89. $Value = substr($this->Data, $this->Pos, strpos($this->Data, 'e', $this->Pos) - $this->Pos);
  90. if (!ctype_digit($Value) && !($Value[0] == '-' && ctype_digit(substr($Value, 1)))) {
  91. return $this->error();
  92. }
  93. $this->Pos += strlen($Value) + 1;
  94. return Int64::make($Value);
  95. case 'l':
  96. $Value = [];
  97. $this->Pos++;
  98. while ($this->Data[$this->Pos] != 'e') {
  99. if ($this->Pos >= $this->Length) {
  100. return $this->error();
  101. }
  102. $Value[] = $this->_bdec();
  103. }
  104. $this->Pos++;
  105. return $Value;
  106. case 'd':
  107. $Value = [];
  108. $this->Pos++;
  109. while ($this->Data[$this->Pos] != 'e') {
  110. $Length = substr($this->Data, $this->Pos, strpos($this->Data, ':', $this->Pos) - $this->Pos);
  111. if (!ctype_digit($Length)) {
  112. return $this->error();
  113. }
  114. $this->Pos += strlen($Length) + $Length + 1;
  115. $Key = substr($this->Data, $this->Pos - $Length, $Length);
  116. if ($this->Pos >= $this->Length) {
  117. return $this->error();
  118. }
  119. $Value[$Key] = $this->_bdec();
  120. }
  121. $this->Pos++;
  122. // Use boolean true to keep track of empty dictionaries
  123. return empty($Value) ? true : $Value;
  124. default:
  125. $Length = substr($this->Data, $this->Pos, strpos($this->Data, ':', $this->Pos) - $this->Pos);
  126. if (!ctype_digit($Length)) {
  127. return $this->error(); // Even if the string is likely to be decoded correctly without this check, it's malformed
  128. }
  129. $this->Pos += strlen($Length) + $Length + 1;
  130. return substr($this->Data, $this->Pos - $Length, $Length);
  131. }
  132. }
  133. /**
  134. * Convert everything to the correct data types and optionally escape strings
  135. *
  136. * @param bool $Escape whether to escape the textual data
  137. * @param mixed $Data decoded data or false to use the $Dec property
  138. * @return decoded data with more useful data types
  139. */
  140. public function dump($Escape = true, $Data = false)
  141. {
  142. if ($Data === false) {
  143. $Data = $this->Dec;
  144. }
  145. if (Int64::is_int($Data)) {
  146. return Int64::get($Data);
  147. }
  148. if (is_bool($Data)) {
  149. return [];
  150. }
  151. if (is_array($Data)) {
  152. $Output = [];
  153. foreach ($Data as $Key => $Val) {
  154. $Output[$Key] = $this->dump($Escape, $Val);
  155. }
  156. return $Output;
  157. }
  158. return $Escape ? htmlentities($Data) : $Data;
  159. }
  160. /**
  161. * Display an error and halt the operation unless the $ExitOnError property is false
  162. *
  163. * @param string $ErrMsg the error message to display
  164. */
  165. private function error($ErrMsg = false)
  166. {
  167. static $ErrorPos;
  168. if ($this->Pos === $ErrorPos) {
  169. // The recursive nature of the class requires this to avoid duplicate error messages
  170. return false;
  171. }
  172. if ($this->ExitOnError) {
  173. if ($ErrMsg === false) {
  174. printf(
  175. "Malformed string. Invalid character at pos 0x%X: %s\n",
  176. $this->Pos,
  177. str_replace(array("\r","\n"), array('',' '), htmlentities(substr($this->Data, $this->Pos, self::SnipLength)))
  178. );
  179. } else {
  180. echo $ErrMsg;
  181. }
  182. exit();
  183. }
  184. $ErrorPos = $this->Pos;
  185. return false;
  186. }
  187. }