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.

torrent_32bit.class.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. <?php
  2. /*******************************************************************************
  3. |~~~~ Gazelle bencode parser ~~~~|
  4. --------------------------------------------------------------------------------
  5. Welcome to the Gazelle bencode parser. bencoding is the way of encoding data
  6. that bittorrent uses in torrent files. When we read the torrent files, we get
  7. one long string that must be parsed into a format we can easily edit - that's
  8. where this file comes into play.
  9. There are 4 data types in bencode:
  10. * String
  11. * Int
  12. * List - array without keys
  13. - like array('value', 'value 2', 'value 3', 'etc')
  14. * Dictionary - array with string keys
  15. - like array['key 1'] = 'value 1'; array['key 2'] = 'value 2';
  16. Before you go any further, we recommend reading the sections on bencoding and
  17. metainfo file structure here: http://wiki.theory.org/BitTorrentSpecification
  18. //----- How we store the data -----//
  19. * Strings
  20. - Stored as php strings. Not difficult to remember.
  21. * Integers
  22. - Stored as php strings with an [*INT*] marker
  23. - Can be stored an an int on 64 bit boxes for uber speed (we do this)
  24. - If stored as an int on 32 bit boxes, it won't allow for any size over 2 gigs
  25. * Lists
  26. - Stored as a BENCODE_LIST object.
  27. - The actual list is in BENCODE_LIST::$Val, as an array with incrementing integer indices
  28. - The list in BENCODE_LIST::$Val is populated by the BENCODE_LIST::dec() function
  29. * Dictionaries
  30. - Stored as a BENCODE_DICT object.
  31. - The actual list is in BENCODE_DICT::$Val, as an array with incrementing integer indices
  32. - The list in BENCODE_DICT::$Val is populated by the BENCODE_DICT::dec() function
  33. //----- BENCODE_* Objects -----//
  34. Lists and dictionaries are stored as objects. They each have the following
  35. functions:
  36. * decode(Type, $Key)
  37. - Decodes ANY bencoded element, given the type and the key
  38. - Gets the position and string from $this
  39. * encode($Val)
  40. - Encodes ANY non-bencoded element, given the value
  41. * dec()
  42. - Decodes either a dictionary or a list, depending on where it's called from
  43. - Uses the decode() function quite a bit
  44. * enc()
  45. - Encodes either a dictionary or a list, depending on where it's called from
  46. - Relies mostly on the encode() function
  47. Finally, as all torrents are just large dictionaries, the TORRENT class extends
  48. the BENCODE_DICT class.
  49. **Note** The version we run doesn't store ints as strings marked with [*INT*]
  50. We store them as php integers. You can do this too for added speed and reduced
  51. hackery, if you're running a 64 bit box, or if you're running a 32 bit box and
  52. don't care about files larger than 2 gigs. The system with the [*INT*]s was
  53. coded up in around 4 minutes for STC when we discovered this problem, then
  54. discovered that floats aren't accurate enough to use. :(
  55. *******************************************************************************/
  56. class BENCODE2
  57. {
  58. public $Val; // Decoded array
  59. public $Pos = 1; // Pointer that indicates our position in the string
  60. public $Str = ''; // Torrent string
  61. public function __construct($Val, $IsParsed = false)
  62. {
  63. if (!$IsParsed) {
  64. $this->Str = $Val;
  65. $this->dec();
  66. } else {
  67. $this->Val = $Val;
  68. }
  69. }
  70. // Decode an element based on the type
  71. public function decode($Type, $Key)
  72. {
  73. if (ctype_digit($Type)) { // Element is a string
  74. // Get length of string
  75. $StrLen = $Type;
  76. while ($this->Str[$this->Pos + 1] != ':') {
  77. $this->Pos++;
  78. $StrLen.=$this->Str[$this->Pos];
  79. }
  80. $this->Val[$Key] = substr($this->Str, $this->Pos + 2, $StrLen);
  81. $this->Pos += $StrLen;
  82. $this->Pos += 2;
  83. } elseif ($Type == 'i') { // Element is an int
  84. $this->Pos++;
  85. // Find end of integer (first occurance of 'e' after position)
  86. $End = strpos($this->Str, 'e', $this->Pos);
  87. // Get the integer, and mark it as an int (on our version 64 bit box, we cast it to an int)
  88. $this->Val[$Key] = '[*INT*]'.substr($this->Str, $this->Pos, $End-$this->Pos);
  89. $this->Pos = $End + 1;
  90. } elseif ($Type == 'l') { // Element is a list
  91. $this->Val[$Key] = new BENCODE_LIST(substr($this->Str, $this->Pos));
  92. $this->Pos += $this->Val[$Key]->Pos;
  93. } elseif ($Type == 'd') { // Element is a dictionary
  94. $this->Val[$Key] = new BENCODE_DICT(substr($this->Str, $this->Pos));
  95. $this->Pos += $this->Val[$Key]->Pos;
  96. // Sort by key to respect spec
  97. ksort($this->Val[$Key]->Val);
  98. } else {
  99. die('Invalid torrent file');
  100. }
  101. }
  102. public function encode($Val)
  103. {
  104. if (is_string($Val)) {
  105. if (substr($Val, 0, 7) == '[*INT*]') {
  106. return 'i'.substr($Val, 7).'e';
  107. } else {
  108. return strlen($Val).':'.$Val;
  109. }
  110. } elseif (is_object($Val)) {
  111. return $Val->enc();
  112. } else {
  113. return 'fail';
  114. }
  115. }
  116. }
  117. class BENCODE_LIST extends BENCODE2
  118. {
  119. public function enc()
  120. {
  121. $Str = 'l';
  122. foreach ($this->Val as $Value) {
  123. $Str.=$this->encode($Value);
  124. }
  125. return $Str.'e';
  126. }
  127. // Decode a list
  128. public function dec()
  129. {
  130. $Key = 0; // Array index
  131. $Length = strlen($this->Str);
  132. while ($this->Pos<$Length) {
  133. $Type = $this->Str[$this->Pos];
  134. // $Type now indicates what type of element we're dealing with
  135. // It's either an integer (string), 'i' (an integer), 'l' (a list), 'd' (a dictionary), or 'e' (end of dictionary/list)
  136. if ($Type == 'e') { // End of list
  137. $this->Pos += 1;
  138. unset($this->Str); // Since we're finished parsing the string, we don't need to store it anymore. Benchmarked - this makes the parser run way faster.
  139. return;
  140. }
  141. // Decode the bencoded element.
  142. // This function changes $this->Pos and $this->Val, so you don't have to.
  143. $this->decode($Type, $Key);
  144. ++ $Key;
  145. }
  146. return true;
  147. }
  148. }
  149. class BENCODE_DICT extends BENCODE2
  150. {
  151. public function enc()
  152. {
  153. $Str = 'd';
  154. foreach ($this->Val as $Key => $Value) {
  155. $Str.=strlen($Key).':'.$Key.$this->encode($Value);
  156. }
  157. return $Str.'e';
  158. }
  159. // Decode a dictionary
  160. public function dec()
  161. {
  162. $Length = strlen($this->Str);
  163. while ($this->Pos < $Length) {
  164. if ($this->Str[$this->Pos] == 'e') { // End of dictionary
  165. $this->Pos += 1;
  166. unset($this->Str); // Since we're finished parsing the string, we don't need to store it anymore. Benchmarked - this makes the parser run way faster.
  167. return;
  168. }
  169. // Get the dictionary key
  170. // Length of the key, in bytes
  171. $KeyLen = $this->Str[$this->Pos];
  172. // Allow for multi-digit lengths
  173. while ($this->Str[$this->Pos + 1] != ':' && $this->Pos + 1 < $Length) {
  174. $this->Pos++;
  175. $KeyLen.=$this->Str[$this->Pos];
  176. }
  177. // $this->Pos is now on the last letter of the key length
  178. // Adding 2 brings it past that character and the ':' to the beginning of the string
  179. $this->Pos+=2;
  180. // Get the name of the key
  181. $Key = substr($this->Str, $this->Pos, $KeyLen);
  182. // Move the position past the key to the beginning of the element
  183. $this->Pos += $KeyLen;
  184. $Type = $this->Str[$this->Pos];
  185. // $Type now indicates what type of element we're dealing with
  186. // It's either an integer (string), 'i' (an integer), 'l' (a list), 'd' (a dictionary), or 'e' (end of dictionary/list)
  187. // Decode the bencoded element.
  188. // This function changes $this->Pos and $this->Val, so you don't have to.
  189. $this->decode($Type, $Key);
  190. }
  191. return true;
  192. }
  193. }
  194. class TORRENT extends BENCODE_DICT
  195. {
  196. public function dump()
  197. {
  198. // Convenience function used for testing and figuring out how we store the data
  199. print_r($this->Val);
  200. }
  201. public function dump_data()
  202. {
  203. // Function which serializes $this->Val for storage
  204. return base64_encode(serialize($this->Val));
  205. }
  206. public function set_announce_url($Announce)
  207. {
  208. $this->Val['announce'] = $Announce;
  209. ksort($this->Val);
  210. }
  211. // Returns an array of:
  212. // * the files in the torrent
  213. // * the total size of files described therein
  214. public function file_list()
  215. {
  216. $FileList = [];
  217. if (!isset($this->Val['info']->Val['files'])) { // Single file mode
  218. $TotalSize = substr($this->Val['info']->Val['length'], 7);
  219. $FileList[] = array($TotalSize, $this->get_name());
  220. } else { // Multiple file mode
  221. $FileNames = [];
  222. $FileSizes = [];
  223. $TotalSize = 0;
  224. $Files = $this->Val['info']->Val['files']->Val;
  225. if (isset($Files[0]->Val['path.utf-8'])) {
  226. $PathKey = 'path.utf-8';
  227. } else {
  228. $PathKey = 'path';
  229. }
  230. foreach ($Files as $File) {
  231. $FileSize = substr($File->Val['length'], 7);
  232. $TotalSize += $FileSize;
  233. $FileName = ltrim(implode('/', $File->Val[$PathKey]->Val), '/');
  234. $FileSizes[] = $FileSize;
  235. $FileNames[] = $FileName;
  236. }
  237. natcasesort($FileNames);
  238. foreach ($FileNames as $Index => $FileName) {
  239. $FileList[] = array($FileSizes[$Index], $FileName);
  240. }
  241. }
  242. return array($TotalSize, $FileList);
  243. }
  244. public function get_name()
  245. {
  246. if (isset($this->Val['info']->Val['name.utf-8'])) {
  247. return $this->Val['info']->Val['name.utf-8'];
  248. } else {
  249. return $this->Val['info']->Val['name'];
  250. }
  251. }
  252. public function make_private()
  253. {
  254. //----- The following properties do not affect the infohash:
  255. // anounce-list is an unofficial extension to the protocol
  256. // that allows for multiple trackers per torrent
  257. unset($this->Val['announce-list']);
  258. // Bitcomet & Azureus cache peers in here
  259. unset($this->Val['nodes']);
  260. // Azureus stores the dht_backup_enable flag here
  261. unset($this->Val['azureus_properties']);
  262. // Remove web-seeds
  263. unset($this->Val['url-list']);
  264. // Remove libtorrent resume info
  265. unset($this->Val['libtorrent_resume']);
  266. //----- End properties that do not affect the infohash
  267. if (!empty($this->Val['info']->Val['private']) && $this->Val['info']->Val['private'] == '[*INT*]1') {
  268. return true;
  269. } else {
  270. // Torrent is not private!
  271. // add private tracker flag and sort info dictionary
  272. $this->Val['info']->Val['private'] = '[*INT*]1';
  273. ksort($this->Val['info']->Val);
  274. return false;
  275. }
  276. }
  277. }