1
0

HTTPSocket.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. <?php
  2. namespace App\Utils;
  3. /**
  4. * Socket communication class.
  5. *
  6. * Originally designed for use with DirectAdmin's API, this class will fill any HTTP socket need.
  7. *
  8. * Very, very basic usage:
  9. * $Socket = new HTTPSocket;
  10. * echo $Socket->get('http://user:[email protected]/somedir/some.file?query=string&this=that');
  11. *
  12. * @author Phi1 'l0rdphi1' Stier <[email protected]>
  13. * @package HTTPSocket
  14. * @version 2.7.2
  15. * 2.7.2
  16. * added x-use-https header check
  17. * added max number of location redirects
  18. * added custom settable message if x-use-https is found, so users can be told where to set their scripts
  19. * if a redirect host is https, add ssl:// to remote_host
  20. * 2.7.1
  21. * added isset to headers['location'], line 306
  22. */
  23. class HTTPSocket
  24. {
  25. public $version = '2.7.2';
  26. /* all vars are private except $error, $query_cache, and $doFollowLocationHeader */
  27. public $method = 'GET';
  28. public $remote_host;
  29. public $remote_port;
  30. public $remote_uname;
  31. public $remote_passwd;
  32. public $result;
  33. public $result_header;
  34. public $result_body;
  35. public $result_status_code;
  36. public $lastTransferSpeed;
  37. public $bind_host;
  38. public $error = array();
  39. public $warn = array();
  40. public $query_cache = array();
  41. public $doFollowLocationHeader = true;
  42. public $redirectURL;
  43. public $max_redirects = 5;
  44. public $ssl_setting_message = 'DirectAdmin appears to be using SSL. Change your script to connect to ssl://';
  45. public $extra_headers = array();
  46. /**
  47. * Create server "connection".
  48. *
  49. */
  50. public function connect($host, $port = '')
  51. {
  52. if (!is_numeric($port)) {
  53. $port = 80;
  54. }
  55. $this->remote_host = $host;
  56. $this->remote_port = $port;
  57. }
  58. public function bind($ip = '')
  59. {
  60. if ($ip == '') {
  61. $ip = $_SERVER['SERVER_ADDR'];
  62. }
  63. $this->bind_host = $ip;
  64. }
  65. /**
  66. * Change the method being used to communicate.
  67. *
  68. * @param string|null request method. supports GET, POST, and HEAD. default is GET
  69. */
  70. public function set_method($method = 'GET')
  71. {
  72. $this->method = strtoupper($method);
  73. }
  74. /**
  75. * Specify a username and password.
  76. *
  77. * @param string|null username. defualt is null
  78. * @param string|null password. defualt is null
  79. */
  80. public function set_login($uname = '', $passwd = '')
  81. {
  82. if (strlen($uname) > 0) {
  83. $this->remote_uname = $uname;
  84. }
  85. if (strlen($passwd) > 0) {
  86. $this->remote_passwd = $passwd;
  87. }
  88. }
  89. /**
  90. * Query the server
  91. *
  92. * @param string containing properly formatted server API. See DA API docs and examples. Http:// URLs O.K. too.
  93. * @param string|array query to pass to url
  94. * @param int if connection KB/s drops below value here, will drop connection
  95. */
  96. public function query($request, $content = '', $doSpeedCheck = 0)
  97. {
  98. $this->error = $this->warn = array();
  99. $this->result_status_code = null;
  100. // is our request a http:// ... ?
  101. if (preg_match('!^http://!i', $request) || preg_match('!^https://!i', $request)) {
  102. $location = parse_url($request);
  103. if (preg_match('!^https://!i', $request)) {
  104. $this->connect('ssl://'.$location['host'], $location['port']);
  105. } else {
  106. $this->connect($location['host'], $location['port']);
  107. }
  108. $this->set_login($location['user'], $location['pass']);
  109. $request = $location['path'];
  110. $content = $location['query'];
  111. if (strlen($request) < 1) {
  112. $request = '/';
  113. }
  114. }
  115. $array_headers = array(
  116. 'User-Agent' => "HTTPSocket/$this->version",
  117. 'Host' => ($this->remote_port == 80 ? $this->remote_host : "$this->remote_host:$this->remote_port"),
  118. 'Accept' => '*/*',
  119. 'Connection' => 'Close' );
  120. foreach ($this->extra_headers as $key => $value) {
  121. $array_headers[$key] = $value;
  122. }
  123. $this->result = $this->result_header = $this->result_body = '';
  124. // was content sent as an array? if so, turn it into a string
  125. if (is_array($content)) {
  126. $pairs = array();
  127. foreach ($content as $key => $value) {
  128. $pairs[] = "$key=".urlencode($value);
  129. }
  130. $content = join('&', $pairs);
  131. unset($pairs);
  132. }
  133. $OK = true;
  134. // instance connection
  135. if ($this->bind_host) {
  136. $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
  137. socket_bind($socket, $this->bind_host);
  138. if (!@socket_connect($socket, $this->remote_host, $this->remote_port)) {
  139. $OK = false;
  140. }
  141. } else {
  142. $socket = @fsockopen($this->remote_host, $this->remote_port, $sock_errno, $sock_errstr, 10);
  143. }
  144. if (!$socket || !$OK) {
  145. $this->error[] = "Can't create socket connection to $this->remote_host:$this->remote_port.";
  146. return 0;
  147. }
  148. // if we have a username and password, add the header
  149. if (isset($this->remote_uname) && isset($this->remote_passwd)) {
  150. $array_headers['Authorization'] = 'Basic '.base64_encode("$this->remote_uname:$this->remote_passwd");
  151. }
  152. // for DA skins: if $this->remote_passwd is NULL, try to use the login key system
  153. if (isset($this->remote_uname) && $this->remote_passwd == null) {
  154. $array_headers['Cookie'] = "session={$_SERVER['SESSION_ID']}; key={$_SERVER['SESSION_KEY']}";
  155. }
  156. // if method is POST, add content length & type headers
  157. if ($this->method == 'POST') {
  158. $array_headers['Content-type'] = 'application/x-www-form-urlencoded';
  159. $array_headers['Content-length'] = strlen($content);
  160. }
  161. // else method is GET or HEAD. we don't support anything else right now.
  162. else {
  163. if ($content) {
  164. $request .= "?$content";
  165. }
  166. }
  167. // prepare query
  168. $query = "$this->method $request HTTP/1.0\r\n";
  169. foreach ($array_headers as $key => $value) {
  170. $query .= "$key: $value\r\n";
  171. }
  172. $query .= "\r\n";
  173. // if POST we need to append our content
  174. if ($this->method == 'POST' && $content) {
  175. $query .= "$content\r\n\r\n";
  176. }
  177. // query connection
  178. if ($this->bind_host) {
  179. socket_write($socket, $query);
  180. // now load results
  181. while ($out = socket_read($socket, 2048)) {
  182. $this->result .= $out;
  183. }
  184. } else {
  185. fwrite($socket, $query, strlen($query));
  186. // now load results
  187. $this->lastTransferSpeed = 0;
  188. $status = socket_get_status($socket);
  189. $startTime = time();
  190. $length = 0;
  191. $prevSecond = 0;
  192. while (!feof($socket) && !$status['timed_out']) {
  193. $chunk = fgets($socket, 1024);
  194. $length += strlen($chunk);
  195. $this->result .= $chunk;
  196. $elapsedTime = time() - $startTime;
  197. if ($elapsedTime > 0) {
  198. $this->lastTransferSpeed = ($length/1024)/$elapsedTime;
  199. }
  200. if ($doSpeedCheck > 0 && $elapsedTime > 5 && $this->lastTransferSpeed < $doSpeedCheck) {
  201. $this->warn[] = "kB/s for last 5 seconds is below 50 kB/s (~".(($length/1024)/$elapsedTime)."), dropping connection...";
  202. $this->result_status_code = 503;
  203. break;
  204. }
  205. }
  206. if ($this->lastTransferSpeed == 0) {
  207. $this->lastTransferSpeed = $length/1024;
  208. }
  209. }
  210. list($this->result_header, $this->result_body) = preg_split("/\r\n\r\n/", $this->result, 2);
  211. if ($this->bind_host) {
  212. socket_close($socket);
  213. } else {
  214. fclose($socket);
  215. }
  216. $this->query_cache[] = $query;
  217. $headers = $this->fetch_header();
  218. // what return status did we get?
  219. if (!$this->result_status_code) {
  220. preg_match("#HTTP/1\.. (\d+)#", $headers[0], $matches);
  221. $this->result_status_code = $matches[1];
  222. }
  223. // did we get the full file?
  224. if (!empty($headers['content-length']) && $headers['content-length'] != strlen($this->result_body)) {
  225. $this->result_status_code = 206;
  226. }
  227. // now, if we're being passed a location header, should we follow it?
  228. if ($this->doFollowLocationHeader) {
  229. //dont bother if we didn't even setup the script correctly
  230. if (isset($headers['x-use-https']) && $headers['x-use-https']=='yes') {
  231. die($this->ssl_setting_message);
  232. }
  233. if (isset($headers['location'])) {
  234. if ($this->max_redirects <= 0) {
  235. die("Too many redirects on: ".$headers['location']);
  236. }
  237. $this->max_redirects--;
  238. $this->redirectURL = $headers['location'];
  239. $this->query($headers['location']);
  240. }
  241. }
  242. }
  243. public function getTransferSpeed()
  244. {
  245. return $this->lastTransferSpeed;
  246. }
  247. /**
  248. * The quick way to get a URL's content :)
  249. *
  250. * @param string URL
  251. * @param boolean return as array? (like PHP's file() command)
  252. * @return string result body
  253. */
  254. public function get($location, $asArray = false)
  255. {
  256. $this->query($location);
  257. if ($this->get_status_code() == 200) {
  258. if ($asArray) {
  259. return preg_split("/\n/", $this->fetch_body());
  260. }
  261. return $this->fetch_body();
  262. }
  263. return false;
  264. }
  265. /**
  266. * Returns the last status code.
  267. * 200 = OK;
  268. * 403 = FORBIDDEN;
  269. * etc.
  270. *
  271. * @return int status code
  272. */
  273. public function get_status_code()
  274. {
  275. return $this->result_status_code;
  276. }
  277. /**
  278. * Adds a header, sent with the next query.
  279. *
  280. * @param string header name
  281. * @param string header value
  282. */
  283. public function add_header($key, $value)
  284. {
  285. $this->extra_headers[$key] = $value;
  286. }
  287. /**
  288. * Clears any extra headers.
  289. *
  290. */
  291. public function clear_headers()
  292. {
  293. $this->extra_headers = array();
  294. }
  295. /**
  296. * Return the result of a query.
  297. *
  298. * @return string result
  299. */
  300. public function fetch_result()
  301. {
  302. return $this->result;
  303. }
  304. /**
  305. * Return the header of result (stuff before body).
  306. *
  307. * @param string (optional) header to return
  308. * @return array result header
  309. */
  310. public function fetch_header($header = '')
  311. {
  312. $array_headers = preg_split("/\r\n/", $this->result_header);
  313. $array_return = array( 0 => $array_headers[0] );
  314. unset($array_headers[0]);
  315. foreach ($array_headers as $pair) {
  316. list($key, $value) = preg_split("/: /", $pair, 2);
  317. $array_return[strtolower($key)] = $value;
  318. }
  319. if ($header != '') {
  320. return $array_return[strtolower($header)];
  321. }
  322. return $array_return;
  323. }
  324. /**
  325. * Return the body of result (stuff after header).
  326. *
  327. * @return string result body
  328. */
  329. public function fetch_body()
  330. {
  331. return $this->result_body;
  332. }
  333. /**
  334. * Return parsed body in array format.
  335. *
  336. * @return array result parsed
  337. */
  338. public function fetch_parsed_body()
  339. {
  340. parse_str($this->result_body, $x);
  341. return $x;
  342. }
  343. /**
  344. * Set a specifc message on how to change the SSL setting, in the event that it's not set correctly.
  345. */
  346. public function set_ssl_setting_message($str)
  347. {
  348. $this->ssl_setting_message = $str;
  349. }
  350. }