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

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