PageRenderTime 53ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Molinos/HTTP/API.php

https://code.google.com/p/molinos-cms/
PHP | 382 lines | 251 code | 61 blank | 70 comment | 45 complexity | 5bd7efcc2dd8bed50b8633dc6b4aa9a3 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, GPL-2.0
  1. <?php
  2. /**
  3. * HTTP ??????.
  4. *
  5. * @package Molinos_CMS
  6. * @subpackage HTTP
  7. * @author Justin Forest <justin.forest@gmail.com>
  8. * @copyright 2006-2011 molinos.ru
  9. * @license http://www.gnu.org/copyleft/gpl.html GPL
  10. */
  11. class Molinos_HTTP_API extends Molinos_Core_API
  12. {
  13. const CONTENT = 1;
  14. const NO_CACHE = 2;
  15. const NO_ERROR = 4;
  16. const TEXT = 8;
  17. const EXTRA = 16;
  18. public function head($url, $loop = 10)
  19. {
  20. $this->log('HEAD ' . $url);
  21. if (false === ($result = get_headers($url, 1)))
  22. throw new RuntimeException('Bad URL: ' . $url);
  23. // ?????? ?????????? ?????? ????????, ??? ???????????
  24. // ??????????. ??????????? ?????????.
  25. if (is_array($result['Content-Type'])) {
  26. $result[0] = $result[count($result['Content-Type']) - 1];
  27. foreach ($result as $k => $v)
  28. if (is_array($v))
  29. list($result[$k]) = array_reverse($v);
  30. elseif (is_numeric($k) and $k !== 0)
  31. unset($result[$k]);
  32. }
  33. $parts = explode(' ', $result[0], 3);
  34. list($result['_protocol'], $result['_status'], $result['_message']) = $parts;
  35. if ($loop and $result['_status'] >= 300 and $result['_status'] < 400 and isset($result['Location']))
  36. return $this->head($result['Location'], $loop - 1);
  37. return $result;
  38. }
  39. /**
  40. * ?????????? ????? ?? ?????????.
  41. */
  42. public function fetch($url, $options = null, $outfile = null)
  43. {
  44. $this->log('FETCH ' . $url);
  45. if (null === $outfile)
  46. $this->ctx->addTempFile($outfile = tempnam(Molinos_Core_Utils::tmpdir(), 'fetch'));
  47. if (ini_get('allow_url_fopen')) {
  48. $result = $this->fetch_fopen($url, $options, $outfile);
  49. } elseif (function_exists('curl_init')) {
  50. $result = $this->fetch_curl($url, $options, $outfile);
  51. } elseif ($options & self::NO_ERROR) {
  52. $result = false;
  53. } else {
  54. throw new RuntimeException(t('All methods of file fetching are disabled.'));
  55. }
  56. if (!$result and !($options & self::NO_ERROR))
  57. throw new RuntimeException(t('Could not fetch %url. More information could be available in the log file.', array(
  58. '%url' => $url,
  59. )));
  60. if ($options & self::CONTENT) {
  61. $result = file_get_contents($outfile);
  62. unlink($outfile);
  63. } else {
  64. $result = $outfile;
  65. }
  66. return $result;
  67. }
  68. /**
  69. * ?????????? ?????????? ?????.
  70. */
  71. public function fetchContent($url)
  72. {
  73. return $this->fetch($url, self::CONTENT);
  74. }
  75. /**
  76. * ?????????? ?????, ?????????? ??????????? ????????.
  77. */
  78. public function fetchEx($url)
  79. {
  80. $in = fopen($url, 'rb');
  81. $out = fopen($this->ctx->addTempFile($fileName = tempnam(MCMS_TEMP_FOLDER, 'mcms-fetch.')), 'wb');
  82. $meta = $this->copyStream($in, $out);
  83. $ct = explode(';', $meta['content-type']);
  84. $result = array(
  85. 'name' => basename($fileName),
  86. 'tmp_name' => $fileName,
  87. 'type' => array_shift($ct),
  88. 'size' => filesize($fileName),
  89. 'remove' => true,
  90. 'url' => isset($meta['location'])
  91. ? $meta['location']
  92. : $url,
  93. );
  94. // ???????? ?????????? ?????????? ?????.
  95. if (isset($meta['content-length']) and $meta['content-length'] != $result['size'])
  96. throw new RuntimeException(sprintf('Incomplete file received (%u bytes instead of %u).', $result['size'], $meta['content-length']));
  97. // ???????????? ?????, ???? ?????.
  98. if (isset($meta['location']))
  99. $url = $meta['location'];
  100. // ?????????? ??? ?????.
  101. if (isset($meta['content-disposition'])) {
  102. // attachment; filename="thumbnailer-9.05.12.zip"
  103. if (preg_match('/filename="([^"]+)"/', $meta['content-disposition'], $m))
  104. $result['name'] = $m[1];
  105. } else {
  106. $u = parse_url($result['url']);
  107. $result['name'] = basename($u['path']);
  108. }
  109. return $result;
  110. }
  111. /**
  112. * ?????????? ?????????? ????? ????????????.
  113. */
  114. public function pass($fileName, $type = null)
  115. {
  116. if (null === $type)
  117. $type = Molinos_Core_Utils::getFileType($fileName, $fileName);
  118. throw new Molinos_Core_Exceptions_Response(Molinos_HTTP_Responses_File($fileName, $type, false));
  119. }
  120. /**
  121. * ?????? ????????? HTTP.
  122. */
  123. public function parseHeaders($headers)
  124. {
  125. $result = array();
  126. foreach (preg_split('/[\r\n]+/', $headers, -1, PREG_SPLIT_NO_EMPTY) as $h) {
  127. if (0 === strpos($h, 'HTTP/'))
  128. list($result['_protocol'], $result['_status'], $result['_message']) = explode(' ', $h, 3);
  129. else {
  130. list($k, $v) = explode(':', $h, 2);
  131. $result[strtolower($k)] = trim($v);
  132. }
  133. }
  134. return $result;
  135. }
  136. /**
  137. * ?????????? ????? ?????????? CURL.
  138. */
  139. protected function fetch_curl($url, $options, $outfile)
  140. {
  141. $ch = curl_init($url);
  142. $fp = fopen($outfile, "wb+");
  143. curl_setopt($ch, CURLOPT_FILE, $fp);
  144. curl_setopt($ch, CURLOPT_HEADER, 0);
  145. if (null !== ($time = ini_get('max_execution_time')))
  146. curl_setopt($ch, CURLOPT_TIMEOUT, $time);
  147. curl_setopt($ch, CURLOPT_USERAGENT, 'Molinos.CMS/' . MCMS_VERSION . ' at http://' . $ctx->request->host);
  148. if (!ini_get('safe_mode'))
  149. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
  150. curl_exec($ch);
  151. $status = curl_getinfo($ch);
  152. curl_close($ch);
  153. // ??? ?????? ?????? ??????????? filesize($outfile) ???????? ????????
  154. // ??????, ??? ???????? ? ?????? ?????????? ?? ??????? ??? ??????????.
  155. fflush($fp);
  156. fclose($fp);
  157. if (200 != $status['http_code']) {
  158. $this->log($url . ': error ' . $status['http_code']);
  159. return false;
  160. }
  161. if ($status['size_download'] != $status['download_content_length']) {
  162. $this->log("{$url}: incomplete transfer, got {$status['size_download']} bytes instead of {$status['download_content_length']}");
  163. return false;
  164. }
  165. if ($options & self::TEXT)
  166. return $this->transcode($outfile, $status['content_type']);
  167. return true;
  168. }
  169. /**
  170. * ?????????? ????? ?????????? fopen().
  171. */
  172. protected function fetch_fopen($url, $options, $outfile)
  173. {
  174. if (!($f = @fopen($url, 'rb'))) {
  175. $this->log($url . ': could not fopen(rb)');
  176. return false;
  177. }
  178. if (!($out = fopen($outfile, 'w'))) {
  179. fclose($f);
  180. $this->log($outfile . ': could not fopen(w)');
  181. return false;
  182. }
  183. while (!feof($f))
  184. fwrite($out, fread($f, 1024));
  185. $meta = stream_get_meta_data($f);
  186. $head = $this->parseHeaders(implode("\n", $meta['wrapper_data']));
  187. fclose($f);
  188. fclose($out);
  189. if ($options & self::TEXT)
  190. return $this->transcode($outfile, $head['content-type']);
  191. if (!empty($head['content-length']) and $head['content-length'] != ($got = filesize($outfile))) {
  192. $this->log($url . ": incomplete file, {$got} bytes instead of {$url}");
  193. return false;
  194. }
  195. return true;
  196. }
  197. /**
  198. * ????? ????????? ? ???.
  199. */
  200. protected function log($message)
  201. {
  202. Molinos_Core_Logger::getInstance()->debug($message, 'http');
  203. }
  204. /**
  205. * ??????????? ????????? ??????, ???? ??? ??????? ?? utf-8. ????????
  206. * ????????? ??????? ? ????????? Content-Type ($content_type).
  207. */
  208. protected function transcode($filename, $content_type)
  209. {
  210. if (substr($content_type, 0, 5) != 'text/')
  211. return false;
  212. if ('utf-8' != ($charset = $this->get_charset($content_type))) {
  213. $data = mb_convert_encoding(file_get_contents($filename), 'utf-8', $charset);
  214. if (mb_check_encoding($data, 'utf-8')) {
  215. file_put_contents($filename, $data);
  216. return true;
  217. }
  218. } elseif (mb_check_encoding(file_get_contents($filename), 'utf-8')) {
  219. return true;
  220. }
  221. return false;
  222. }
  223. /**
  224. * ?????????? ?????????.
  225. */
  226. protected function get_charset($content_type)
  227. {
  228. $cpos = strpos($content_type, 'charset=');
  229. return (false === $cpos)
  230. ? 'utf-8'
  231. : strtolower(substr($content_type, $cpos + 8));
  232. }
  233. /**
  234. * ???????? ???? ????? ? ??????, ?????????? ??????????.
  235. */
  236. protected function copyStream($from, $to)
  237. {
  238. while (!feof($from))
  239. fwrite($to, fread($from, 1024));
  240. fflush($to);
  241. fclose($to);
  242. $meta = stream_get_meta_data($from);
  243. if (!isset($meta['wrapper_data']))
  244. throw new RuntimeException('Fetch error.');
  245. $meta = $this->parseHeaders(implode("\n", $meta['wrapper_data']));
  246. fclose($from);
  247. return $meta;
  248. }
  249. /**
  250. * ???????? ?????? ??????? POST.
  251. *
  252. * @param string $url ?????, ???? ??????? ????????? ?????.
  253. * @param array $data ???????????? ??????.
  254. * @return string ?????????? ?????????.
  255. */
  256. public function post($url, array $data)
  257. {
  258. $c = stream_context_create(array(
  259. 'http' => array(
  260. 'method' => 'POST',
  261. 'header' => 'Content-Type: application/x-www-form-urlencoded' . PHP_EOL,
  262. 'content' => Molinos_Core_API::getInstance('http')->build_query($data),
  263. ),
  264. ));
  265. return file_get_contents($url, false, $c);
  266. }
  267. /**
  268. * ?????????? ????????? ??? ?????? ?????.
  269. *
  270. * @todo ???????? ????????? If-Modified-Since ? If-None-Match, ??.
  271. * http://drupal.org/files/issues/147310-29.patch ?
  272. * http://drupal.org/node/147310.
  273. *
  274. * @param string $fileName ??? ??????????? ?????.
  275. * @param string $mimeType ??? ???????????.
  276. * @param bool $inline True, ???? ???? ????? ??????? ????? ? ???????.
  277. * @return array ????????????? ?????? ??????????.
  278. */
  279. public function getFileServeHeaders($fileName, $mimeType = null, $inline = false)
  280. {
  281. $result = array();
  282. $cacheLifeTime = intval(Molinos_Core_API::getInstance('settings')->get('cache.ttl', 900));
  283. if (null === $mimeType)
  284. $mimeType = Molinos_Core_Utils::getFileType($fileName);
  285. $result['Content-Type'] = $mimeType;
  286. $result['Content-Length'] = filesize($fileName);
  287. if ($inline)
  288. $result['Content-Disposition'] = 'inline';
  289. else
  290. $result['Content-Disposition'] = sprintf('attachment; filename="%s"', urlencode(basename($fileName)));
  291. $result['Expires'] = gmdate("D, d M Y H:i:s", time() + $cacheLifeTime) . 'GMT';
  292. $result['Cache-Control'] = 'public, max-age=' . $cacheLifeTime;
  293. $result['Last-Modified'] = gmdate('D, d M Y H:i:s', filemtime($fileName)) . 'GMT';
  294. $result['ETag'] = '"' . md5($result['Last-Modified'] .';'. $fileName) . '"';
  295. return $result;
  296. }
  297. public function build_query(array $args)
  298. {
  299. $parts = array();
  300. foreach ($args as $k => $v) {
  301. if (is_numeric($k))
  302. $parts[] = urlencode($v);
  303. elseif (is_scalar($v))
  304. $parts[] = urlencode($k) . '=' . urlencode($v);
  305. }
  306. return implode(ini_get('arg_separator.output'), $parts);
  307. }
  308. public function build_cookie(array $cookie)
  309. {
  310. $result = array();
  311. foreach ($cookie['cookies'] as $k => $v)
  312. $result[] = urlencode($k) . '=' . urlencode($v);
  313. if (isset($cookie['path']))
  314. $result[] = 'path=' . $cookie['path'];
  315. if (isset($cookie['domain']))
  316. $result[] = 'domain=' . $cookie['domain'];
  317. if (isset($cookie['expires']))
  318. $result[] = 'expires=' . date('r', $cookie['expires']);
  319. return implode('; ', $result);
  320. }
  321. }