1
0

SimpleHTTP.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971
  1. <?php
  2. /* $Id: SimpleHTTP.php 3307 2007-12-16 22:32:03Z warion $ */
  3. /*******************************************************************************
  4. LICENSE
  5. This program is free software; you can redistribute it and/or
  6. modify it under the terms of the GNU General Public License (GPL)
  7. as published by the Free Software Foundation; either version 2
  8. of the License, or (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. To read the license please visit http://www.gnu.org/copyleft/gpl.html
  14. *******************************************************************************/
  15. // states
  16. define('SIMPLEHTTP_STATE_NULL', 0); // null
  17. define('SIMPLEHTTP_STATE_OK', 1); // ok
  18. define('SIMPLEHTTP_STATE_ERROR', -1); // error
  19. /**
  20. * SimpleHTTP
  21. */
  22. class SimpleHTTP
  23. {
  24. // public fields
  25. // timeout
  26. var $timeout = 20;
  27. /**
  28. * Temporarily use HTTP/1.0 until chunked encoding is sorted out
  29. * Valid values are '1.0' or '1.1'
  30. * @param string $httpVersion
  31. */
  32. var $httpVersion = "1.0";
  33. /**
  34. * Cookie string used in raw HTTP request
  35. * @param string $cookie
  36. */
  37. var $cookie = "";
  38. /**
  39. * URI/path used in GET request:
  40. * @param string $getcmd
  41. */
  42. var $getcmd = "";
  43. /**
  44. * the raw HTTP request to send to the remote webserver
  45. * @param string $request
  46. */
  47. var $request = "";
  48. /**
  49. * the raw HTTP response received from the remote webserver
  50. * @param string $responseBody
  51. */
  52. var $responseBody = "";
  53. /**
  54. * Array of HTTP response headers
  55. * @param array $responseHeaders
  56. */
  57. var $responseHeaders = array();
  58. /**
  59. * Indicates if we got the response line or not from webserver
  60. * 'HTTP/1.1 200 OK
  61. * etc
  62. * @param bool $gotResponseLine
  63. */
  64. var $gotResponseLine = false;
  65. /**
  66. * Status code of webserver response
  67. * @param string $status
  68. */
  69. var $status = "";
  70. /**
  71. * socket
  72. */
  73. var $socket = 0;
  74. /**
  75. * Error string
  76. * @param string $errstr
  77. */
  78. var $errstr = "";
  79. /**
  80. * Error number
  81. * @param int $errno
  82. */
  83. var $errno = 0;
  84. // user-agent
  85. var $userAgent = "";
  86. // filename
  87. var $filename = "";
  88. // url
  89. var $url = "";
  90. // referer
  91. var $referer = "";
  92. // messages
  93. var $messages = array();
  94. // state
  95. var $state = SIMPLEHTTP_STATE_NULL;
  96. // Number of redirects we've followed so far:
  97. var $redirectCount = 0;
  98. // Maximum number of redirects to follow:
  99. var $redirectMax = 5;
  100. // The redirect URL specified in the location header for a 30x status code:
  101. var $redirectUrl = "";
  102. // Can PHP do TLS?
  103. var $canTLS = null;
  104. // =========================================================================
  105. // public static methods
  106. // =========================================================================
  107. /**
  108. * accessor for singleton
  109. *
  110. * @return SimpleHTTP
  111. */
  112. function getInstance() {
  113. global $instanceSimpleHTTP;
  114. // initialize if needed
  115. if (!isset($instanceSimpleHTTP))
  116. SimpleHTTP::initialize();
  117. return $instanceSimpleHTTP;
  118. }
  119. /**
  120. * initialize SimpleHTTP.
  121. */
  122. function initialize() {
  123. global $instanceSimpleHTTP;
  124. // create instance
  125. if (!isset($instanceSimpleHTTP))
  126. $instanceSimpleHTTP = new SimpleHTTP();
  127. }
  128. /**
  129. * getState
  130. *
  131. * @return state
  132. */
  133. function getState() {
  134. global $instanceSimpleHTTP;
  135. return (isset($instanceSimpleHTTP))
  136. ? $instanceSimpleHTTP->state
  137. : SIMPLEHTTP_STATE_NULL;
  138. }
  139. /**
  140. * getMessages
  141. *
  142. * @return array
  143. */
  144. function getMessages() {
  145. global $instanceSimpleHTTP;
  146. return (isset($instanceSimpleHTTP))
  147. ? $instanceSimpleHTTP->messages
  148. : array();
  149. }
  150. /**
  151. * getMessages
  152. *
  153. * @return string
  154. */
  155. function getFilename() {
  156. global $instanceSimpleHTTP;
  157. return (isset($instanceSimpleHTTP))
  158. ? $instanceSimpleHTTP->filename
  159. : "";
  160. }
  161. /**
  162. * method to get data from URL -- uses timeout and user agent
  163. *
  164. * @param $get_url
  165. * @param $get_referer
  166. * @return string
  167. */
  168. function getData($get_url, $get_referer = "") {
  169. global $instanceSimpleHTTP;
  170. // initialize if needed
  171. if (!isset($instanceSimpleHTTP))
  172. SimpleHTTP::initialize();
  173. // call instance-method
  174. return $instanceSimpleHTTP->instance_getData($get_url, $get_referer);
  175. }
  176. /**
  177. * get torrent from URL. Has support for specific sites
  178. *
  179. * @param $durl
  180. * @return string
  181. */
  182. function getTorrent($durl) {
  183. global $instanceSimpleHTTP;
  184. // initialize if needed
  185. if (!isset($instanceSimpleHTTP))
  186. SimpleHTTP::initialize();
  187. // call instance-method
  188. return $instanceSimpleHTTP->instance_getTorrent($durl);
  189. }
  190. /**
  191. * get nzb from URL.
  192. *
  193. * @param $durl
  194. * @return string
  195. */
  196. function getNzb($durl) {
  197. global $instanceSimpleHTTP;
  198. // initialize if needed
  199. if (!isset($instanceSimpleHTTP))
  200. SimpleHTTP::initialize();
  201. // call instance-method
  202. return $instanceSimpleHTTP->instance_getNzb($durl);
  203. }
  204. /**
  205. * get size from URL.
  206. *
  207. * @param $durl
  208. * @return int
  209. */
  210. function getRemoteSize($durl) {
  211. global $instanceSimpleHTTP;
  212. // initialize if needed
  213. if (!isset($instanceSimpleHTTP))
  214. SimpleHTTP::initialize();
  215. // call instance-method
  216. return $instanceSimpleHTTP->instance_getRemoteSize($durl);
  217. }
  218. // =========================================================================
  219. // ctor
  220. // =========================================================================
  221. /**
  222. * do not use direct, use the factory-method !
  223. *
  224. * @return SimpleHTTP
  225. */
  226. function SimpleHTTP() {
  227. global $cfg;
  228. // user-agent
  229. $this->userAgent = $cfg['user_agent'];
  230. // ini-settings
  231. @ini_set("allow_url_fopen", "1");
  232. @ini_set("user_agent", $this->userAgent);
  233. }
  234. // =========================================================================
  235. // public methods
  236. // =========================================================================
  237. /**
  238. * method to get data from URL -- uses timeout and user agent
  239. *
  240. * @param $get_url
  241. * @param $get_referer
  242. * @return string
  243. */
  244. function instance_getData($get_url, $get_referer = "") {
  245. global $cfg, $db;
  246. // set fields
  247. $this->url = $get_url;
  248. $this->referer = $get_referer;
  249. // (re)set state
  250. $this->state = SIMPLEHTTP_STATE_NULL;
  251. // (re-)set some vars
  252. $this->cookie = "";
  253. $this->request = "";
  254. $this->responseBody = "";
  255. $this->responseHeaders = array();
  256. $this->gotResponseLine = false;
  257. $this->status = "";
  258. $this->errstr = "";
  259. $this->errno = 0;
  260. $this->socket = 0;
  261. /**
  262. * array of URL component parts for use in raw HTTP request
  263. * @param array $domain
  264. */
  265. $domain = parse_url($this->url);
  266. if (
  267. empty($domain) || // Check URL is a well-formed HTTP/HTTPS URL.
  268. empty($domain['scheme']) || ($domain['scheme'] != 'http' && $domain['scheme'] != 'https') ||
  269. empty($domain['host'])
  270. ) {
  271. $this->state = SIMPLEHTTP_STATE_ERROR;
  272. $msg = "Error fetching " . $this->url .". This is not a valid HTTP/HTTPS URL.";
  273. array_push($this->messages, $msg);
  274. AuditAction($cfg["constants"]["error"], $msg);
  275. return($data="");
  276. }
  277. $secure = $domain['scheme'] == 'https';
  278. if ($secure && !$this->_canTLS()) {
  279. $this->state = SIMPLEHTTP_STATE_ERROR;
  280. $msg = "Error fetching " . $this->url .". PHP does not have module OpenSSL, which is needed for HTTPS.";
  281. array_push($this->messages, $msg);
  282. AuditAction($cfg["constants"]["error"], $msg);
  283. return($data="");
  284. }
  285. // get-command
  286. if (!array_key_exists("path", $domain))
  287. $domain["path"] = "/";
  288. $this->getcmd = $domain["path"];
  289. if (!array_key_exists("query", $domain))
  290. $domain["query"] = "";
  291. // append the query string if included:
  292. $this->getcmd .= (!empty($domain["query"])) ? "?" . $domain["query"] : "";
  293. // Check to see if cookie required for this domain:
  294. $sql = "SELECT c.data AS data FROM tf_cookies AS c LEFT JOIN tf_users AS u ON ( u.uid = c.uid ) WHERE u.user_id = ".$db->qstr($cfg["user"])." AND c.host = ".$db->qstr($domain['host']);
  295. $this->cookie = $db->GetOne($sql);
  296. if ($db->ErrorNo() != 0) dbError($sql);
  297. if (!array_key_exists("port", $domain))
  298. $domain["port"] = $secure ? 443 : 80;
  299. // Fetch the data using fsockopen():
  300. $this->socket = @fsockopen( // connect to server, let PHP handle TLS layer for an HTTPS connection
  301. ($secure ? 'tls://' : '') . $domain["host"], $domain["port"],
  302. $this->errno, $this->errstr, $this->timeout
  303. );
  304. if (!empty($this->socket)) {
  305. // Write the outgoing HTTP request using cookie info
  306. // Standard HTTP/1.1 request looks like:
  307. //
  308. // GET /url/path/example.php HTTP/1.1
  309. // Host: example.com
  310. // Accept: */*
  311. // Accept-Language: en-us
  312. // User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.8.1) Gecko/20061010 Firefox/2.0
  313. // Connection: Close
  314. // Cookie: uid=12345;pass=asdfasdf;
  315. //
  316. //$this->request = "GET " . ($this->httpVersion=="1.1" ? $this->getcmd : $this->url ). " HTTP/" . $this->httpVersion ."\r\n";
  317. $this->request = "GET ".$this->_fullURLEncode($this->getcmd)." HTTP/".$this->httpVersion."\r\n";
  318. $this->request .= (!empty($this->referer)) ? "Referer: " . $this->referer . "\r\n" : "";
  319. $this->request .= "Accept: */*\r\n";
  320. $this->request .= "Accept-Language: en-us\r\n";
  321. $this->request .= "User-Agent: ".$this->userAgent."\r\n";
  322. $this->request .= "Host: " . $domain["host"] . "\r\n";
  323. if($this->httpVersion=="1.1"){
  324. $this->request .= "Connection: Close\r\n";
  325. }
  326. if(!empty($this->cookie)){
  327. $this->request .= "Cookie: " . $this->cookie . "\r\n";
  328. }
  329. $this->request .= "\r\n";
  330. // Send header packet information to server
  331. fputs($this->socket, $this->request);
  332. // socket-options
  333. stream_set_timeout($this->socket, $this->timeout);
  334. // meta-data
  335. $info = stream_get_meta_data($this->socket);
  336. // Get response headers:
  337. while ((!$info['timed_out']) && ($line = @fgets($this->socket, 500000))) {
  338. // First empty line/\r\n indicates end of response headers:
  339. if($line == "\r\n"){
  340. break;
  341. }
  342. if (!$this->gotResponseLine) {
  343. preg_match("@HTTP/[^ ]+ (\d\d\d)@", $line, $matches);
  344. // TODO: Use this to see if we redirected (30x) and follow the redirect:
  345. $this->status = $matches[1];
  346. $this->gotResponseLine = true;
  347. continue;
  348. }
  349. // Get response headers:
  350. preg_match("/^([^:]+):\s*(.*)/", trim($line), $matches);
  351. $this->responseHeaders[strtolower($matches[1])] = $matches[2];
  352. // meta-data
  353. $info = stream_get_meta_data($this->socket);
  354. }
  355. if(
  356. $this->httpVersion=="1.1"
  357. && isset($this->responseHeaders["transfer-encoding"])
  358. && !empty($this->responseHeaders["transfer-encoding"])
  359. ) {
  360. /*
  361. // NOT CURRENTLY WORKING, USE HTTP/1.0 ONLY UNTIL THIS IS FIXED!
  362. */
  363. // Get body of HTTP response:
  364. // Handle chunked encoding:
  365. /*
  366. length := 0
  367. read chunk-size, chunk-extension (if any) and CRLF
  368. while (chunk-size > 0) {
  369. read chunk-data and CRLF
  370. append chunk-data to entity-body
  371. length := length + chunk-size
  372. read chunk-size and CRLF
  373. }
  374. */
  375. // Used to count total of all chunk lengths, the content-length:
  376. $chunkLength=0;
  377. // Get first chunk size:
  378. $chunkSize = hexdec(trim(fgets($this->socket)));
  379. // 0 size chunk indicates end of content:
  380. while ((!$info['timed_out']) && ($chunkSize > 0)) {
  381. // Read in up to $chunkSize chars:
  382. $line = @fgets($this->socket, $chunkSize);
  383. // Discard crlf after current chunk:
  384. fgets($this->socket);
  385. // Append chunk to response body:
  386. $this->responseBody .= $line;
  387. // Keep track of total chunk/content length:
  388. $chunkLength += $chunkSize;
  389. // Read next chunk size:
  390. $chunkSize = hexdec(trim(fgets($this->socket)));
  391. // meta-data
  392. $info = stream_get_meta_data($this->socket);
  393. }
  394. $this->responseHeaders["content-length"] = $chunkLength;
  395. } else {
  396. while ((!$info['timed_out']) && ($line = @fread($this->socket, 500000))) {
  397. $this->responseBody .= $line;
  398. // meta-data
  399. $info = stream_get_meta_data($this->socket);
  400. }
  401. }
  402. @fclose($this->socket); // Close our connection
  403. } else {
  404. return "Error fetching ".$this->url.". PHP Error No=".$this->errno." . PHP Error String=".$this->errstr;
  405. }
  406. /*
  407. Check if we need to follow a redirect:
  408. http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
  409. Each of these HTTP response status codes indicates a redirect and the
  410. content should be included in the Location field/header:
  411. 300 Multiple Locations
  412. 301 Moved Permanently
  413. 302 Found (has a temp location somewhere else on server)
  414. 303 See Other (should be fetched using GET, probably not relevant but won't hurt to include it)
  415. 307 Temporary Redirect
  416. */
  417. if( preg_match("/^30[0-37]$/D", $this->status) > 0 ){
  418. // Check we're not already over the max redirects limit:
  419. if ( $this->redirectCount > $this->redirectMax ) {
  420. $this->state = SIMPLEHTTP_STATE_ERROR;
  421. $msg = "Error fetching " . $this->url .". The maximum number of allowed redirects ";
  422. $msg .="(" .$this->redirectMax. ") was exceeded. Last followed URL was: " .$this->redirectUrl;
  423. array_push($this->messages , $msg);
  424. AuditAction($cfg["constants"]["error"], $msg);
  425. return($data="");
  426. } else {
  427. $this->redirectCount++;
  428. // Check we have a location to get redirected content:
  429. if( isset($this->responseHeaders["location"]) && !empty($this->responseHeaders["location"]) ){
  430. // 3 different cases for location header:
  431. // - full URL (scheme://.../foobar) -- just go to that URL,
  432. // - absolute URL (/foobar) -- keep everything up to host/port,
  433. // and replace end of request,
  434. // - relative URL (foobar) -- keep everything up to last component of path,
  435. // and replace end of request.
  436. $redirectLocation = $this->responseHeaders["location"];
  437. if (preg_match('#^(ht|f)tp(s)?://#', $redirectLocation) > 0) {
  438. // Case 1: full URL. Just use it.
  439. $this->redirectUrl = $redirectLocation;
  440. } else {
  441. // Cases 2 or 3: partial URL.
  442. // Keep scheme/user/pass/host/port of current request.
  443. $redirectUrlBase =
  444. $domain['scheme'].'://'.
  445. ((isset($domain['user']) || isset($domain['pass'])) ?
  446. ((isset($domain['user']) ? $domain['user'] : '').
  447. (isset($domain['pass']) ? ':'.$domain['pass'] : '').'@') : '').
  448. $domain['host'].
  449. (isset($domain['port']) ? ':'.$domain['port'] : '');
  450. if ($redirectLocation[0] == '/') {
  451. // Case 2: absolute URL.
  452. // Append it to current request's base.
  453. $this->redirectUrl = $redirectUrlBase . $redirectLocation;
  454. } else {
  455. // Case 3: relative URL.
  456. // Append it to current request's base + path stripped of its last component.
  457. $domainPathAry = explode('/', $domain['path']);
  458. array_splice($domainPathAry, -1, 1, $redirectLocation);
  459. $domainPathNew = implode('/', $domainPathAry);
  460. $this->redirectUrl =
  461. $redirectUrlBase .
  462. ((isset($domainPathNew) &&
  463. strlen($domainPathNew) > 0 &&
  464. $domainPathNew[0] == '/') ? '' : '/') .
  465. $domainPathNew;
  466. }
  467. }
  468. } else {
  469. $msg = "Error fetching " . $this->url .". A redirect status code (" . $this->status . ")";
  470. $msg .= " was sent from the remote webserver, but no location header was set to obtain the redirected content from.";
  471. AuditAction($cfg["constants"]["error"], $msg);
  472. array_push($this->messages , $msg);
  473. return($data="");
  474. }
  475. $this->instance_getData($this->redirectUrl);
  476. }
  477. }
  478. // Trim any extraneous linefeed chars:
  479. $this->responseBody = trim($this->responseBody, "\r\n");
  480. // If a filename is associated with this content, assign it to $filename
  481. if (isset($this->responseHeaders["content-disposition"]) && !empty($this->responseHeaders["content-disposition"])) {
  482. // Content-disposition: attachment; filename="nameoffile":
  483. // Don't think single quotes can be used to escape filename here, but just in case check for ' and ":
  484. if (preg_match("/filename=(['\"])([^\\1]+)\\1/", $this->responseHeaders["content-disposition"], $matches)) {
  485. if(isset($matches[2]) && !empty($matches[2])){
  486. $file_name = $matches[2];
  487. // Only accept filenames, not paths:
  488. if (!preg_match("@/@", $file_name))
  489. $this->filename = $file_name;
  490. }
  491. }
  492. }
  493. // state
  494. $this->state = SIMPLEHTTP_STATE_OK;
  495. // return content
  496. return $this->responseBody;
  497. }
  498. /**
  499. * get torrent from URL. Has support for specific sites
  500. *
  501. * @param $durl
  502. * @return string
  503. */
  504. function instance_getTorrent($durl) {
  505. global $cfg;
  506. // (re)set state
  507. $this->state = SIMPLEHTTP_STATE_NULL;
  508. // (re)set redir-count
  509. $this->redirectCount = 0;
  510. // Initialize file name:
  511. $this->filename = "";
  512. $domain = parse_url($durl);
  513. // Check we have a remote URL:
  514. if (!isset($domain["host"])) {
  515. // Not a remote URL:
  516. $msg = "The torrent requested for download (".$durl.") is not a remote torrent. Please enter a valid remote torrent URL such as http://example.com/example.torrent\n";
  517. AuditAction($cfg["constants"]["error"], $msg);
  518. array_push($this->messages , $msg);
  519. // state
  520. $this->state = SIMPLEHTTP_STATE_ERROR;
  521. // return empty data:
  522. return ($data="");
  523. }
  524. if (strtolower(substr($domain["path"], -8)) != ".torrent") {
  525. /*
  526. In these cases below, we check for torrent URLs that have to be manipulated in some
  527. way to obtain the torrent content. These are sites that perhaps use redirection or
  528. URL rewriting in some way.
  529. */
  530. // Check known domain types
  531. // mininova
  532. if (strpos(strtolower($domain["host"]), "mininova") !== false) {
  533. // Sample (http://www.mininova.org/rss.xml):
  534. // http://www.mininova.org/tor/2254847
  535. // <a href="/get/2281554">FreeLinux.ISO.iso.torrent</a>
  536. // If received a /tor/ get the required information
  537. if (strpos($durl, "/tor/") !== false) {
  538. // Get the contents of the /tor/ to find the real torrent name
  539. $data = $this->instance_getData($durl);
  540. // Check for the tag used on mininova.org
  541. if (preg_match("/<a href=\"\/get\/[0-9].[^\"]+\">(.[^<]+)<\/a>/i", $data, $data_preg_match)) {
  542. // This is the real torrent filename
  543. $this->filename = $data_preg_match[1];
  544. }
  545. // Change to GET torrent url
  546. $durl = str_replace("/tor/", "/get/", $durl);
  547. }
  548. // Now fetch the torrent file
  549. $data = $this->instance_getData($durl);
  550. // demonoid
  551. } elseif (strpos(strtolower($domain["host"]), "demonoid") !== false) {
  552. // Sample (http://www.demonoid.com/rss/0.xml):
  553. // http://www.demonoid.com/files/details/241739/6976998/
  554. // <a href="/files/download/HTTP/241739/6976998">...</a>
  555. // If received a /details/ page url, change it to the download url
  556. if (strpos($durl, "/details/") !== false) {
  557. // Need to make it grab the torrent
  558. $durl = str_replace("/details/", "/download/HTTP/", $durl);
  559. }
  560. // Now fetch the torrent file
  561. $data = $this->instance_getData($durl);
  562. // isohunt
  563. } elseif (strpos(strtolower($domain["host"]), "isohunt") !== false) {
  564. // Sample (http://isohunt.com/js/rss.php):
  565. $treferer = "http://" . $domain["host"] . "/btDetails.php?id=";
  566. // http://isohunt.com/torrent_details/7591035/
  567. // http://isohunt.com/download/7591035/
  568. // If the url points to the details page, change it to the download url
  569. if (strpos($durl, "/torrent_details/") !== false) {
  570. // Need to make it grab the torrent
  571. $durl = str_replace("/torrent_details/", "/download/", $durl);
  572. }
  573. // old one, but still works:
  574. // http://isohunt.com/btDetails.php?ihq=&id=8464972
  575. // http://isohunt.com/download.php?mode=bt&id=8837938
  576. // If the url points to the details page, change it to the download url
  577. if (strpos(strtolower($durl), "/btdetails.php?") !== false) {
  578. // Need to make it grab the torrent
  579. $durl = str_replace("/btDetails.php?", "/download.php?", $durl) . "&mode=bt";
  580. }
  581. // Now fetch the torrent file
  582. $data = $this->instance_getData($durl, $treferer);
  583. // details.php
  584. } elseif (strpos(strtolower($durl), "details.php?") !== false) {
  585. // Sample (http://www.bitmetv.org/rss.php?passkey=123456):
  586. // http://www.bitmetv.org/details.php?id=18435&hit=1
  587. // Strip final &hit=1 if present, since it only ever returns a 302
  588. // redirect to the same URL without the &hit=1.
  589. $durl2 = preg_replace('/&hit=1$/', '', $durl);
  590. $treferer = "http://" . $domain["host"] . "/details.php?id=";
  591. $data = $this->instance_getData($durl2, $treferer);
  592. // Sample (http://www.bitmetv.org/details.php?id=18435)
  593. // download.php/18435/SpiderMan%20Season%204.torrent
  594. if (preg_match("/(download.php.[^\"]+)/i", $data, $data_preg_match)) {
  595. $torrent = substr($data_preg_match[0], 0, -1);
  596. $turl2 = "http://" . $domain["host"] . "/" . $torrent;
  597. $data = $this->instance_getData($turl2);
  598. } else {
  599. $msg = "Error: could not find link to torrent file in $durl";
  600. AuditAction($cfg["constants"]["error"], $msg);
  601. array_push($this->messages , $msg);
  602. // state
  603. $this->state = SIMPLEHTTP_STATE_ERROR;
  604. // return empty data:
  605. return($data="");
  606. }
  607. // torrentspy
  608. } elseif (strpos(strtolower($domain["host"]), "torrentspy") !== false) {
  609. // Sample (http://torrentspy.com/rss.asp):
  610. // http://www.torrentspy.com/torrent/1166188/gentoo_livedvd_i686_installer_2007_0
  611. $treferer = "http://" . $domain["host"] . "/download.asp?id=";
  612. $data = $this->instance_getData($durl, $treferer);
  613. // If received a /download.asp?, a /directory.asp?mode=torrentdetails
  614. // or a /torrent/, extract real torrent link
  615. if (
  616. strpos($durl, "/download.asp?") !== false ||
  617. strpos($durl, "/directory.asp?mode=torrentdetails") !== false ||
  618. strpos($durl, "/torrent/") !== false
  619. ) {
  620. // Check for the tag used in download details page
  621. if (preg_match("#<a\s+id=\"downloadlink0\"[^>]*?\s+href=\"(http[^\"]+)\"#i", $data, $data_preg_match)) {
  622. // This is the real torrent download link
  623. $durl = $data_preg_match[1];
  624. // Follow it
  625. $data = $this->instance_getData($durl);
  626. }
  627. }
  628. // download.asp
  629. } elseif (strpos(strtolower($durl), "download.asp?") !== false) {
  630. $treferer = "http://" . $domain["host"] . "/download.asp?id=";
  631. $data = $this->instance_getData($durl, $treferer);
  632. // default
  633. } else {
  634. // Fallback case for any URL not ending in .torrent and not matching the above cases:
  635. $data = $this->instance_getData($durl);
  636. }
  637. } else {
  638. $data = $this->instance_getData($durl);
  639. }
  640. // Make sure we have a torrent file
  641. if (strpos($data, "d8:") === false) {
  642. // We don't have a Torrent File... it is something else. Let the user know about it:
  643. $msg = "Content returned from $durl does not appear to be a valid torrent.";
  644. AuditAction($cfg["constants"]["error"], $msg);
  645. array_push($this->messages , $msg);
  646. // Display the first part of $data if debuglevel higher than 1:
  647. if ($cfg["debuglevel"] > 1){
  648. if (strlen($data) > 0){
  649. array_push($this->messages , "Displaying first 1024 chars of output: ");
  650. array_push($this->messages , htmlentities(substr($data, 0, 1023)), ENT_QUOTES);
  651. } else {
  652. array_push($this->messages , "Output from $durl was empty.");
  653. }
  654. } else {
  655. array_push($this->messages , "Set debuglevel > 2 in 'Admin, Webapps' to see the content returned from $durl.");
  656. }
  657. $data = "";
  658. // state
  659. $this->state = SIMPLEHTTP_STATE_ERROR;
  660. } else {
  661. // If the torrent file name isn't set already, do it now:
  662. if ((!isset($this->filename)) || (strlen($this->filename) == 0)) {
  663. // Get the name of the torrent, and make it the filename
  664. if (preg_match("/name([0-9][^:]):(.[^:]+)/i", $data, $data_preg_match)) {
  665. $filelength = $data_preg_match[1];
  666. $file_name = $data_preg_match[2];
  667. $this->filename = substr($file_name, 0, $filelength).".torrent";
  668. } else {
  669. require_once('inc/classes/BDecode.php');
  670. $btmeta = @BDecode($data);
  671. $this->filename = ((is_array($btmeta)) && (!empty($btmeta['info'])) && (!empty($btmeta['info']['name'])))
  672. ? trim($btmeta['info']['name']).".torrent"
  673. : "";
  674. }
  675. }
  676. // state
  677. $this->state = SIMPLEHTTP_STATE_OK;
  678. }
  679. return $data;
  680. }
  681. /**
  682. * get nzb from URL
  683. *
  684. * @param $durl
  685. * @return string
  686. */
  687. function instance_getNzb($durl) {
  688. global $cfg;
  689. // (re)set state
  690. $this->state = SIMPLEHTTP_STATE_NULL;
  691. // (re)set redir-count
  692. $this->redirectCount = 0;
  693. // Initialize file name:
  694. $this->filename = "";
  695. $domain = parse_url($durl);
  696. // Check we have a remote URL:
  697. if (!isset($domain["host"])) {
  698. // Not a remote URL:
  699. $msg = "The nzb requested for download (".$durl.") is not a remote nzb. Please enter a valid remote nzb URL such as http://example.com/example.nzb\n";
  700. AuditAction($cfg["constants"]["error"], $msg);
  701. array_push($this->messages , $msg);
  702. // state
  703. $this->state = SIMPLEHTTP_STATE_ERROR;
  704. // return empty data:
  705. return ($data="");
  706. }
  707. if (strtolower(substr($domain["path"], -4)) != ".nzb") {
  708. /*
  709. In these cases below, we check for URLs that have to be manipulated in some
  710. way to obtain the content. These are sites that perhaps use redirection or
  711. URL rewriting in some way.
  712. */
  713. // details.php
  714. if (strpos(strtolower($durl), "details.php?") !== false) {
  715. // Sample (http://www.bitmetv.org/rss.php?passkey=123456):
  716. // http://www.bitmetv.org/details.php?id=18435&hit=1
  717. // Strip final &hit=1 if present, since it only ever returns a 302
  718. // redirect to the same URL without the &hit=1.
  719. $durl2 = preg_replace('/&hit=1$/', '', $durl);
  720. $treferer = "http://" . $domain["host"] . "/details.php?id=";
  721. $data = $this->instance_getData($durl, $treferer);
  722. // Sample (http://www.bitmetv.org/details.php?id=18435)
  723. // download.php/18435/SpiderMan%20Season%204.torrent
  724. if (preg_match("/(download.php.[^\"]+)/i", $data, $data_preg_match)) {
  725. $tr = substr($data_preg_match[0], 0, -1);
  726. $turl2 = "http://" . $domain["host"] . "/" . $tr;
  727. $data = $this->instance_getData($turl2);
  728. } else {
  729. $msg = "Error: could not find link to nzb file in $durl";
  730. AuditAction($cfg["constants"]["error"], $msg);
  731. array_push($this->messages , $msg);
  732. // state
  733. $this->state = SIMPLEHTTP_STATE_ERROR;
  734. // return empty data:
  735. return($data="");
  736. }
  737. // download.asp
  738. } elseif (strpos(strtolower($durl), "download.asp?") !== false) {
  739. // Sample (TF's TorrentSpy Search):
  740. // http://www.torrentspy.com/download.asp?id=519793
  741. $treferer = "http://" . $domain["host"] . "/download.asp?id=";
  742. $data = $this->instance_getData($durl, $treferer);
  743. // default
  744. } else {
  745. // Fallback case for any URL not ending in .nzb and not matching the above cases:
  746. $data = $this->instance_getData($durl);
  747. }
  748. } else {
  749. $data = $this->instance_getData($durl);
  750. }
  751. // Make sure we have a nzb file
  752. if (strpos($data, "nzb") === false) {
  753. // We don't have a nzb File... it is something else. Let the user know about it:
  754. $msg = "Content returned from $durl does not appear to be a valid nzb.";
  755. AuditAction($cfg["constants"]["error"], $msg);
  756. array_push($this->messages , $msg);
  757. // Display the first part of $data if debuglevel higher than 1:
  758. if ($cfg["debuglevel"] > 1){
  759. if (strlen($data) > 0){
  760. array_push($this->messages , "Displaying first 1024 chars of output: ");
  761. array_push($this->messages , htmlentities(substr($data, 0, 1023)), ENT_QUOTES);
  762. } else {
  763. array_push($this->messages , "Output from $durl was empty.");
  764. }
  765. } else {
  766. array_push($this->messages , "Set debuglevel > 2 in 'Admin, Webapps' to see the content returned from $durl.");
  767. }
  768. $data = "";
  769. // state
  770. $this->state = SIMPLEHTTP_STATE_ERROR;
  771. } else {
  772. // state
  773. $this->state = SIMPLEHTTP_STATE_OK;
  774. }
  775. return $data;
  776. }
  777. /**
  778. * get size from URL.
  779. *
  780. * @param $durl
  781. * @return string
  782. */
  783. function instance_getRemoteSize($durl) {
  784. // set fields
  785. $this->url = $durl;
  786. $this->timeout = 8;
  787. $this->status = "";
  788. $this->errstr = "";
  789. $this->errno = 0;
  790. // domain
  791. $domain = parse_url($this->url);
  792. if (!isset($domain["port"]))
  793. $domain["port"] = 80;
  794. // check we have a remote URL:
  795. if (!isset($domain["host"]))
  796. return 0;
  797. // check we have a remote path:
  798. if (!isset($domain["path"]))
  799. return 0;
  800. // open socket
  801. $this->socket = @fsockopen($domain["host"], $domain["port"], $this->errno, $this->errstr, $this->timeout);
  802. if (!$this->socket)
  803. return 0;
  804. // send HEAD request
  805. $this->request = "HEAD ".$domain["path"]." HTTP/1.0\r\nConnection: Close\r\n\r\n";
  806. @fwrite($this->socket, $this->request);
  807. // socket options
  808. stream_set_timeout($this->socket, $this->timeout);
  809. // meta data
  810. $info = stream_get_meta_data($this->socket);
  811. // read the response
  812. $this->responseBody = "";
  813. $ctr = 0;
  814. while ((!$info['timed_out']) && ($ctr < 25)) {
  815. $s = @fgets($this->socket, 4096);
  816. $this->responseBody .= $s;
  817. if (strcmp($s, "\r\n") == 0 || strcmp($s, "\n") == 0)
  818. break;
  819. // meta data
  820. $info = stream_get_meta_data($this->socket);
  821. // increment counter
  822. $ctr++;
  823. }
  824. // close socket
  825. @fclose($this->socket);
  826. // try to get Content-Length in response-body
  827. preg_match('/Content-Length:\s([0-9].+?)\s/', $this->responseBody, $matches);
  828. return (isset($matches[1]))
  829. ? $matches[1]
  830. : 0;
  831. }
  832. /**
  833. * Check whether PHP can do TLS.
  834. *
  835. * @return bool
  836. */
  837. function _canTLS() {
  838. // Just check whether openssl extension is available.
  839. if (!isset($this->canTLS))
  840. $this->canTLS = extension_loaded('openssl');
  841. return $this->canTLS;
  842. }
  843. /**
  844. * URL-encode all fragments of an URL, not re-encoding what is already encoded.
  845. *
  846. * @param $url
  847. * @return string
  848. */
  849. function _fullURLEncode($url) {
  850. # Split URL into fragments, delimiters are: '+', ':', '@', '/', '?', '=', '&' and /%[[:xdigit:]]{2}/.
  851. $fragments = preg_split('#[+:@/?=&]+|%[[:xdigit:]]{2}#', $url, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
  852. for ($i = count($fragments) - 1; $i >= 0; $i--) {
  853. $fragment = $fragments[$i]; # $fragment[0] is the fragment, $fragment[1] is its starting position.
  854. $url = substr_replace($url, rawurlencode($fragment[0]), $fragment[1], strlen($fragment[0]));
  855. }
  856. return $url;
  857. }
  858. }
  859. ?>