PageRenderTime 180ms CodeModel.GetById 42ms RepoModel.GetById 0ms app.codeStats 0ms

/system/application/stats/core/Http.php

https://github.com/isS/Microweber
PHP | 396 lines | 261 code | 45 blank | 90 comment | 67 complexity | a2c029a2d67e8e6d015bfb385a24befc MD5 | raw file
  1. <?php
  2. /**
  3. * Piwik - Open source web analytics
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. * @version $Id: Http.php 3812 2011-01-26 15:53:24Z vipsoft $
  8. *
  9. * @category Piwik
  10. * @package Piwik
  11. */
  12. /**
  13. * Server-side http client to retrieve content from remote servers, and optionally save to a local file.
  14. * Used to check for the latest Piwik version and download updates.
  15. *
  16. * @package Piwik
  17. */
  18. class Piwik_Http
  19. {
  20. /**
  21. * Get "best" available transport method for sendHttpRequest() calls.
  22. *
  23. * @return string
  24. */
  25. static public function getTransportMethod()
  26. {
  27. $method = 'curl';
  28. if(!extension_loaded('curl'))
  29. {
  30. $method = 'fopen';
  31. if(@ini_get('allow_url_fopen') != '1')
  32. {
  33. $method = 'socket';
  34. if(!function_exists('fsockopen'))
  35. {
  36. return null;
  37. }
  38. }
  39. }
  40. return $method;
  41. }
  42. /**
  43. * Sends http request ensuring the request will fail before $timeout seconds
  44. *
  45. * If no $destinationPath is specified, the trimmed response (without header) is returned as a string.
  46. * If a $destinationPath is specified, the response (without header) is saved to a file.
  47. *
  48. * @param string $aUrl
  49. * @param int $timeout
  50. * @param string $userAgent
  51. * @param string $destinationPath
  52. * @param int $followDepth
  53. * @return bool true (or string) on success; false on HTTP response error code (1xx or 4xx)
  54. * @throws Exception for all other errors
  55. */
  56. static public function sendHttpRequest($aUrl, $timeout, $userAgent = null, $destinationPath = null, $followDepth = 0)
  57. {
  58. // create output file
  59. $file = null;
  60. if($destinationPath)
  61. {
  62. // Ensure destination directory exists
  63. Piwik_Common::mkdir(dirname($destinationPath));
  64. if (($file = @fopen($destinationPath, 'wb')) === false || !is_resource($file))
  65. {
  66. throw new Exception('Error while creating the file: ' . $destinationPath);
  67. }
  68. }
  69. return self::sendHttpRequestBy(self::getTransportMethod(), $aUrl, $timeout, $userAgent, $destinationPath, $file, $followDepth);
  70. }
  71. /**
  72. * Sends http request using the specified transport method
  73. *
  74. * @param string $method
  75. * @param string $aUrl
  76. * @param int $timeout
  77. * @param string $userAgent
  78. * @param string $destinationPath
  79. * @param resource $file
  80. * @param int $followDepth
  81. * @return bool true (or string) on success; false on HTTP response error code (1xx or 4xx)
  82. * @throws Exception for all other errors
  83. */
  84. static public function sendHttpRequestBy($method = 'socket', $aUrl, $timeout, $userAgent = null, $destinationPath = null, $file = null, $followDepth = 0)
  85. {
  86. if ($followDepth > 5)
  87. {
  88. throw new Exception('Too many redirects ('.$followDepth.')');
  89. }
  90. $contentLength = 0;
  91. $fileLength = 0;
  92. if($method == 'socket')
  93. {
  94. // initialization
  95. $url = @parse_url($aUrl);
  96. if($url === false || !isset($url['scheme']))
  97. {
  98. throw new Exception('Malformed URL: '.$aUrl);
  99. }
  100. if($url['scheme'] != 'http')
  101. {
  102. throw new Exception('Invalid protocol/scheme: '.$url['scheme']);
  103. }
  104. $host = $url['host'];
  105. $port = isset($url['port)']) ? $url['port'] : 80;
  106. $path = isset($url['path']) ? $url['path'] : '/';
  107. if(isset($url['query']))
  108. {
  109. $path .= '?'.$url['query'];
  110. }
  111. $errno = null;
  112. $errstr = null;
  113. // connection attempt
  114. if (($fsock = @fsockopen($host, $port, $errno, $errstr, $timeout)) === false || !is_resource($fsock))
  115. {
  116. if(is_resource($file)) { @fclose($file); }
  117. throw new Exception("Error while connecting to: $host. Please try again later. $errstr");
  118. }
  119. // send HTTP request header
  120. fwrite($fsock,
  121. "GET $path HTTP/1.0\r\n"
  122. ."Host: $host".($port != 80 ? ':'.$port : '')."\r\n"
  123. ."User-Agent: Piwik/".Piwik_Version::VERSION.($userAgent ? " $userAgent" : '')."\r\n"
  124. .'Referer: http://'.Piwik_Common::getIpString()."/\r\n"
  125. ."Connection: close\r\n"
  126. ."\r\n"
  127. );
  128. $streamMetaData = array('timed_out' => false);
  129. @stream_set_blocking($fsock, true);
  130. @stream_set_timeout($fsock, $timeout);
  131. // process header
  132. $status = null;
  133. $expectRedirect = false;
  134. while(!feof($fsock))
  135. {
  136. $line = fgets($fsock, 4096);
  137. $streamMetaData = @stream_get_meta_data($fsock);
  138. if($streamMetaData['timed_out'])
  139. {
  140. if(is_resource($file)) { @fclose($file); }
  141. @fclose($fsock);
  142. throw new Exception('Timed out waiting for server response');
  143. }
  144. // a blank line marks the end of the server response header
  145. if(rtrim($line, "\r\n") == '')
  146. {
  147. break;
  148. }
  149. // parse first line of server response header
  150. if(!$status)
  151. {
  152. // expect first line to be HTTP response status line, e.g., HTTP/1.1 200 OK
  153. if(!preg_match('~^HTTP/(\d\.\d)\s+(\d+)(\s*.*)?~', $line, $m))
  154. {
  155. if(is_resource($file)) { @fclose($file); }
  156. @fclose($fsock);
  157. throw new Exception('Expected server response code. Got '.rtrim($line, "\r\n"));
  158. }
  159. $status = (integer) $m[2];
  160. // Informational 1xx or Client Error 4xx
  161. if ($status < 200 || $status >= 400)
  162. {
  163. if(is_resource($file)) { @fclose($file); }
  164. @fclose($fsock);
  165. return false;
  166. }
  167. continue;
  168. }
  169. // handle redirect
  170. if(preg_match('/^Location:\s*(.+)/', rtrim($line, "\r\n"), $m))
  171. {
  172. if(is_resource($file)) { @fclose($file); }
  173. @fclose($fsock);
  174. // Successful 2xx vs Redirect 3xx
  175. if($status < 300)
  176. {
  177. throw new Exception('Unexpected redirect to Location: '.rtrim($line).' for status code '.$status);
  178. }
  179. return self::sendHttpRequestBy($method, trim($m[1]), $timeout, $userAgent, $destinationPath, $file, $followDepth+1);
  180. }
  181. // save expected content length for later verification
  182. if(preg_match('/^Content-Length:\s*(\d+)/', $line, $m))
  183. {
  184. $contentLength = (integer) $m[1];
  185. }
  186. }
  187. if(feof($fsock))
  188. {
  189. throw new Exception('Unexpected end of transmission');
  190. }
  191. // process content/body
  192. $response = '';
  193. while(!feof($fsock))
  194. {
  195. $line = fread($fsock, 8192);
  196. $streamMetaData = @stream_get_meta_data($fsock);
  197. if($streamMetaData['timed_out'])
  198. {
  199. if(is_resource($file)) { @fclose($file); }
  200. @fclose($fsock);
  201. throw new Exception('Timed out waiting for server response');
  202. }
  203. $fileLength += strlen($line);
  204. if(is_resource($file))
  205. {
  206. // save to file
  207. fwrite($file, $line);
  208. }
  209. else
  210. {
  211. // concatenate to response string
  212. $response .= $line;
  213. }
  214. }
  215. // determine success or failure
  216. @fclose(@$fsock);
  217. }
  218. else if($method == 'fopen')
  219. {
  220. $response = false;
  221. // we make sure the request takes less than a few seconds to fail
  222. // we create a stream_context (works in php >= 5.2.1)
  223. // we also set the socket_timeout (for php < 5.2.1)
  224. $default_socket_timeout = @ini_get('default_socket_timeout');
  225. @ini_set('default_socket_timeout', $timeout);
  226. $ctx = null;
  227. if(function_exists('stream_context_create')) {
  228. $stream_options = array(
  229. 'http' => array(
  230. 'header' => 'User-Agent: Piwik/'.Piwik_Version::VERSION.($userAgent ? " $userAgent" : '')."\r\n"
  231. .'Referer: http://'.Piwik_Common::getIpString()."/\r\n",
  232. 'max_redirects' => 5, // PHP 5.1.0
  233. 'timeout' => $timeout, // PHP 5.2.1
  234. )
  235. );
  236. $ctx = stream_context_create($stream_options);
  237. }
  238. $response = @file_get_contents($aUrl, 0, $ctx);
  239. $fileLength = strlen($response);
  240. if(is_resource($file))
  241. {
  242. // save to file
  243. fwrite($file, $response);
  244. }
  245. // restore the socket_timeout value
  246. if(!empty($default_socket_timeout))
  247. {
  248. @ini_set('default_socket_timeout', $default_socket_timeout);
  249. }
  250. }
  251. else if($method == 'curl')
  252. {
  253. $ch = @curl_init();
  254. $curl_options = array(
  255. // internal to ext/curl
  256. CURLOPT_BINARYTRANSFER => is_resource($file),
  257. // curl options (sorted oldest to newest)
  258. CURLOPT_URL => $aUrl,
  259. CURLOPT_REFERER => 'http://'.Piwik_Common::getIpString(),
  260. CURLOPT_USERAGENT => 'Piwik/'.Piwik_Version::VERSION.($userAgent ? " $userAgent" : ''),
  261. CURLOPT_HEADER => false,
  262. CURLOPT_CONNECTTIMEOUT => $timeout,
  263. );
  264. @curl_setopt_array($ch, $curl_options);
  265. /*
  266. * use local list of Certificate Authorities, if available
  267. */
  268. if(file_exists(PIWIK_INCLUDE_PATH . '/core/DataFiles/cacert.pem'))
  269. {
  270. @curl_setopt($ch, CURLOPT_CAINFO, PIWIK_INCLUDE_PATH . '/core/DataFiles/cacert.pem');
  271. }
  272. /*
  273. * as of php 5.2.0, CURLOPT_FOLLOWLOCATION can't be set if
  274. * in safe_mode or open_basedir is set
  275. */
  276. if((string)ini_get('safe_mode') == '' && ini_get('open_basedir') == '')
  277. {
  278. $curl_options = array(
  279. // curl options (sorted oldest to newest)
  280. CURLOPT_FOLLOWLOCATION => true,
  281. CURLOPT_MAXREDIRS => 5,
  282. );
  283. @curl_setopt_array($ch, $curl_options);
  284. }
  285. if(is_resource($file))
  286. {
  287. // write output directly to file
  288. @curl_setopt($ch, CURLOPT_FILE, $file);
  289. }
  290. else
  291. {
  292. // internal to ext/curl
  293. @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  294. }
  295. ob_start();
  296. $response = @curl_exec($ch);
  297. ob_end_clean();
  298. if($response === true)
  299. {
  300. $response = '';
  301. }
  302. else if($response === false)
  303. {
  304. $errstr = curl_error($ch);
  305. if($errstr != '')
  306. {
  307. throw new Exception('curl_exec: '.$errstr);
  308. }
  309. $response = '';
  310. }
  311. $contentLength = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
  312. $fileLength = is_resource($file) ? curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD) : strlen($response);
  313. @curl_close($ch);
  314. unset($ch);
  315. }
  316. else
  317. {
  318. throw new Exception('Invalid request method: '.$method);
  319. }
  320. if(is_resource($file))
  321. {
  322. fflush($file);
  323. @fclose($file);
  324. $fileSize = filesize($destinationPath);
  325. if((($contentLength > 0) && ($fileLength != $contentLength)) || ($fileSize != $fileLength))
  326. {
  327. throw new Exception('File size error: '.$destinationPath.'; expected '.$contentLength.' bytes; received '.$fileLength.' bytes; saved '.$fileSize.' bytes to file');
  328. }
  329. return true;
  330. }
  331. if(($contentLength > 0) && ($fileLength != $contentLength))
  332. {
  333. throw new Exception('Content length error: expected '.$contentLength.' bytes; received '.$fileLength.' bytes');
  334. }
  335. return trim($response);
  336. }
  337. /**
  338. * Fetch the file at $url in the destination $destinationPath
  339. *
  340. * @param string $url
  341. * @param string $destinationPath
  342. * @param int $tries
  343. * @return true on success, throws Exception on failure
  344. */
  345. static public function fetchRemoteFile($url, $destinationPath = null, $tries = 0)
  346. {
  347. @ignore_user_abort(true);
  348. Piwik::setMaxExecutionTime(0);
  349. return self::sendHttpRequest($url, 10, 'Update', $destinationPath, $tries);
  350. }
  351. }