Oppaitime'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 5.4KB

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