PageRenderTime 44ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/PEAR/HTTP/HTTP.php

https://bitbucket.org/claudiu_marginean/magento-hg-mirror
PHP | 548 lines | 272 code | 42 blank | 234 comment | 73 complexity | b6408ededd0ec0d585b84951b7054810 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL
  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3. /**
  4. * HTTP
  5. *
  6. * PHP versions 4 and 5
  7. *
  8. * @category HTTP
  9. * @package HTTP
  10. * @author Stig Bakken <ssb@fast.no>
  11. * @author Sterling Hughes <sterling@php.net>
  12. * @author Tomas V.V.Cox <cox@idecnet.com>
  13. * @author Richard Heyes <richard@php.net>
  14. * @author Philippe Jausions <jausions@php.net>
  15. * @author Michael Wallner <mike@php.net>
  16. * @copyright 2002-2008 The Authors
  17. * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
  18. * @version CVS: $Id: HTTP.php,v 1.56 2008/08/31 20:15:43 jausions Exp $
  19. * @link http://pear.php.net/package/HTTP
  20. */
  21. /**
  22. * Miscellaneous HTTP Utilities
  23. *
  24. * PEAR::HTTP provides static shorthand methods for generating HTTP dates,
  25. * issueing HTTP HEAD requests, building absolute URIs, firing redirects and
  26. * negotiating user preferred language.
  27. *
  28. * @category HTTP
  29. * @package HTTP
  30. * @author Stig Bakken <ssb@fast.no>
  31. * @author Sterling Hughes <sterling@php.net>
  32. * @author Tomas V.V.Cox <cox@idecnet.com>
  33. * @author Richard Heyes <richard@php.net>
  34. * @author Philippe Jausions <jausions@php.net>
  35. * @author Michael Wallner <mike@php.net>
  36. * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
  37. * @abstract
  38. * @version Release: $Revision: 1.56 $
  39. * @link http://pear.php.net/package/HTTP
  40. */
  41. class HTTP
  42. {
  43. /**
  44. * Formats a RFC compliant GMT date HTTP header. This function honors the
  45. * "y2k_compliance" php.ini directive and formats the GMT date corresponding
  46. * to either RFC850 or RFC822.
  47. *
  48. * @param mixed $time unix timestamp or date (default = current time)
  49. *
  50. * @return mixed GMT date string, or false for an invalid $time parameter
  51. * @access public
  52. * @static
  53. */
  54. function Date($time = null)
  55. {
  56. if (!isset($time)) {
  57. $time = time();
  58. } elseif (!is_numeric($time) && (-1 === $time = strtotime($time))) {
  59. return false;
  60. }
  61. // RFC822 or RFC850
  62. $format = ini_get('y2k_compliance') ? 'D, d M Y' : 'l, d-M-y';
  63. return gmdate($format .' H:i:s \G\M\T', $time);
  64. }
  65. /**
  66. * Negotiates language with the user's browser through the Accept-Language
  67. * HTTP header or the user's host address. Language codes are generally in
  68. * the form "ll" for a language spoken in only one country, or "ll-CC" for a
  69. * language spoken in a particular country. For example, U.S. English is
  70. * "en-US", while British English is "en-UK". Portugese as spoken in
  71. * Portugal is "pt-PT", while Brazilian Portugese is "pt-BR".
  72. *
  73. * Quality factors in the Accept-Language: header are supported, e.g.:
  74. * Accept-Language: en-UK;q=0.7, en-US;q=0.6, no, dk;q=0.8
  75. *
  76. * <code>
  77. * require_once 'HTTP.php';
  78. * $langs = array(
  79. * 'en' => 'locales/en',
  80. * 'en-US' => 'locales/en',
  81. * 'en-UK' => 'locales/en',
  82. * 'de' => 'locales/de',
  83. * 'de-DE' => 'locales/de',
  84. * 'de-AT' => 'locales/de',
  85. * );
  86. * $neg = HTTP::negotiateLanguage($langs);
  87. * $dir = $langs[$neg];
  88. * </code>
  89. *
  90. * @param array $supported An associative array of supported languages,
  91. * whose values must evaluate to true.
  92. * @param string $default The default language to use if none is found.
  93. *
  94. * @return string The negotiated language result or the supplied default.
  95. * @static
  96. * @access public
  97. */
  98. function negotiateLanguage($supported, $default = 'en-US')
  99. {
  100. $supp = array();
  101. foreach ($supported as $lang => $isSupported) {
  102. if ($isSupported) {
  103. $supp[strtolower($lang)] = $lang;
  104. }
  105. }
  106. if (!count($supp)) {
  107. return $default;
  108. }
  109. if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
  110. $match = HTTP::_matchAccept($_SERVER['HTTP_ACCEPT_LANGUAGE'],
  111. $supp);
  112. if (!is_null($match)) {
  113. return $match;
  114. }
  115. }
  116. if (isset($_SERVER['REMOTE_HOST'])) {
  117. $lang = strtolower(end($h = explode('.', $_SERVER['REMOTE_HOST'])));
  118. if (isset($supp[$lang])) {
  119. return $supp[$lang];
  120. }
  121. }
  122. return $default;
  123. }
  124. /**
  125. * Negotiates charset with the user's browser through the Accept-Charset
  126. * HTTP header.
  127. *
  128. * Quality factors in the Accept-Charset: header are supported, e.g.:
  129. * Accept-Language: en-UK;q=0.7, en-US;q=0.6, no, dk;q=0.8
  130. *
  131. * <code>
  132. * require_once 'HTTP.php';
  133. * $charsets = array(
  134. * 'UTF-8',
  135. * 'ISO-8859-1',
  136. * );
  137. * $charset = HTTP::negotiateCharset($charsets);
  138. * </code>
  139. *
  140. * @param array $supported An array of supported charsets
  141. * @param string $default The default charset to use if none is found.
  142. *
  143. * @return string The negotiated language result or the supplied default.
  144. * @static
  145. * @author Philippe Jausions <jausions@php.net>
  146. * @access public
  147. * @since 1.4.1
  148. */
  149. function negotiateCharset($supported, $default = 'ISO-8859-1')
  150. {
  151. $supp = array();
  152. foreach ($supported as $charset) {
  153. $supp[strtolower($charset)] = $charset;
  154. }
  155. if (!count($supp)) {
  156. return $default;
  157. }
  158. if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
  159. $match = HTTP::_matchAccept($_SERVER['HTTP_ACCEPT_CHARSET'],
  160. $supp);
  161. if (!is_null($match)) {
  162. return $match;
  163. }
  164. }
  165. return $default;
  166. }
  167. /**
  168. * Negotiates content type with the user's browser through the Accept
  169. * HTTP header.
  170. *
  171. * Quality factors in the Accept: header are supported, e.g.:
  172. * Accept: application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8
  173. *
  174. * <code>
  175. * require_once 'HTTP.php';
  176. * $contentType = array(
  177. * 'application/xhtml+xml',
  178. * 'application/xml',
  179. * 'text/html',
  180. * 'text/plain',
  181. * );
  182. * $mime = HTTP::negotiateContentType($contentType);
  183. * </code>
  184. *
  185. * @param array $supported An associative array of supported MIME types.
  186. * @param string $default The default type to use if none match.
  187. *
  188. * @return string The negotiated MIME type result or the supplied default.
  189. * @static
  190. * @author Philippe Jausions <jausions@php.net>
  191. * @access public
  192. * @since 1.4.1
  193. */
  194. function negotiateMimeType($supported, $default)
  195. {
  196. $supp = array();
  197. foreach ($supported as $type) {
  198. $supp[strtolower($type)] = $type;
  199. }
  200. if (!count($supp)) {
  201. return $default;
  202. }
  203. if (isset($_SERVER['HTTP_ACCEPT'])) {
  204. $accepts = HTTP::_sortAccept($_SERVER['HTTP_ACCEPT']);
  205. foreach ($accepts as $type => $q) {
  206. if (substr($type, -2) != '/*') {
  207. if (isset($supp[$type])) {
  208. return $supp[$type];
  209. }
  210. continue;
  211. }
  212. if ($type == '*/*') {
  213. return array_shift($supp);
  214. }
  215. list($general, $specific) = explode('/', $type);
  216. $general .= '/';
  217. $len = strlen($general);
  218. foreach ($supp as $mime => $t) {
  219. if (strncasecmp($general, $mime, $len) == 0) {
  220. return $t;
  221. }
  222. }
  223. }
  224. }
  225. return $default;
  226. }
  227. /**
  228. * Parses a weighed "Accept" HTTP header and matches it against a list
  229. * of supported options
  230. *
  231. * @param string $header The HTTP "Accept" header to parse
  232. * @param array $supported A list of supported values
  233. *
  234. * @return string|NULL a matched option, or NULL if no match
  235. * @access private
  236. * @static
  237. */
  238. function _matchAccept($header, $supported)
  239. {
  240. $matches = HTTP::_sortAccept($header);
  241. foreach ($matches as $key => $q) {
  242. if (isset($supported[$key])) {
  243. return $supported[$key];
  244. }
  245. }
  246. // If any (i.e. "*") is acceptable, return the first supported format
  247. if (isset($matches['*'])) {
  248. return array_shift($supported);
  249. }
  250. return null;
  251. }
  252. /**
  253. * Parses and sorts a weighed "Accept" HTTP header
  254. *
  255. * @param string $header The HTTP "Accept" header to parse
  256. *
  257. * @return array a sorted list of "accept" options
  258. * @access private
  259. * @static
  260. */
  261. function _sortAccept($header)
  262. {
  263. $matches = array();
  264. foreach (explode(',', $header) as $option) {
  265. $option = array_map('trim', explode(';', $option));
  266. $l = strtolower($option[0]);
  267. if (isset($option[1])) {
  268. $q = (float) str_replace('q=', '', $option[1]);
  269. } else {
  270. $q = null;
  271. // Assign default low weight for generic values
  272. if ($l == '*/*') {
  273. $q = 0.01;
  274. } elseif (substr($l, -1) == '*') {
  275. $q = 0.02;
  276. }
  277. }
  278. // Unweighted values, get high weight by their position in the
  279. // list
  280. $matches[$l] = isset($q) ? $q : 1000 - count($matches);
  281. }
  282. arsort($matches, SORT_NUMERIC);
  283. return $matches;
  284. }
  285. /**
  286. * Sends a "HEAD" HTTP command to a server and returns the headers
  287. * as an associative array.
  288. *
  289. * Example output could be:
  290. * <code>
  291. * Array
  292. * (
  293. * [response_code] => 200 // The HTTP response code
  294. * [response] => HTTP/1.1 200 OK // The full HTTP response string
  295. * [Date] => Fri, 11 Jan 2002 01:41:44 GMT
  296. * [Server] => Apache/1.3.20 (Unix) PHP/4.1.1
  297. * [X-Powered-By] => PHP/4.1.1
  298. * [Connection] => close
  299. * [Content-Type] => text/html
  300. * )
  301. * </code>
  302. *
  303. * @param string $url A valid URL, e.g.: http://pear.php.net/credits.php
  304. * @param integer $timeout Timeout in seconds (default = 10)
  305. *
  306. * @return array Returns associative array of response headers on success
  307. * or PEAR error on failure.
  308. * @static
  309. * @access public
  310. * @see HTTP_Client::head()
  311. * @see HTTP_Request
  312. */
  313. function head($url, $timeout = 10)
  314. {
  315. $p = parse_url($url);
  316. if (!isset($p['scheme'])) {
  317. $p = parse_url(HTTP::absoluteURI($url));
  318. } elseif ($p['scheme'] != 'http') {
  319. return HTTP::raiseError('Unsupported protocol: '. $p['scheme']);
  320. }
  321. $port = isset($p['port']) ? $p['port'] : 80;
  322. if (!$fp = @fsockopen($p['host'], $port, $eno, $estr, $timeout)) {
  323. return HTTP::raiseError("Connection error: $estr ($eno)");
  324. }
  325. $path = !empty($p['path']) ? $p['path'] : '/';
  326. $path .= !empty($p['query']) ? '?' . $p['query'] : '';
  327. fputs($fp, "HEAD $path HTTP/1.0\r\n");
  328. fputs($fp, 'Host: ' . $p['host'] . ':' . $port . "\r\n");
  329. fputs($fp, "Connection: close\r\n\r\n");
  330. $response = rtrim(fgets($fp, 4096));
  331. if (preg_match("|^HTTP/[^\s]*\s(.*?)\s|", $response, $status)) {
  332. $headers['response_code'] = $status[1];
  333. }
  334. $headers['response'] = $response;
  335. while ($line = fgets($fp, 4096)) {
  336. if (!trim($line)) {
  337. break;
  338. }
  339. if (($pos = strpos($line, ':')) !== false) {
  340. $header = substr($line, 0, $pos);
  341. $value = trim(substr($line, $pos + 1));
  342. $headers[$header] = $value;
  343. }
  344. }
  345. fclose($fp);
  346. return $headers;
  347. }
  348. /**
  349. * This function redirects the client. This is done by issuing
  350. * a "Location" header and exiting if wanted. If you set $rfc2616 to true
  351. * HTTP will output a hypertext note with the location of the redirect.
  352. *
  353. * @param string $url URL where the redirect should go to.
  354. * @param bool $exit Whether to exit immediately after redirection.
  355. * @param bool $rfc2616 Wheter to output a hypertext note where we're
  356. * redirecting to (Redirecting to
  357. * <a href="...">...</a>.)
  358. *
  359. * @return boolean Returns TRUE on succes (or exits) or FALSE if headers
  360. * have already been sent.
  361. * @static
  362. * @access public
  363. */
  364. function redirect($url, $exit = true, $rfc2616 = false)
  365. {
  366. if (headers_sent()) {
  367. return false;
  368. }
  369. $url = HTTP::absoluteURI($url);
  370. header('Location: '. $url);
  371. if ($rfc2616 && isset($_SERVER['REQUEST_METHOD'])
  372. && $_SERVER['REQUEST_METHOD'] != 'HEAD') {
  373. echo '
  374. <p>Redirecting to: <a href="'.str_replace('"', '%22', $url).'">'
  375. .htmlspecialchars($url).'</a>.</p>
  376. <script type="text/javascript">
  377. //<![CDATA[
  378. if (location.replace == null) {
  379. location.replace = location.assign;
  380. }
  381. location.replace("'.str_replace('"', '\\"', $url).'");
  382. // ]]>
  383. </script>';
  384. }
  385. if ($exit) {
  386. exit;
  387. }
  388. return true;
  389. }
  390. /**
  391. * This function returns the absolute URI for the partial URL passed.
  392. * The current scheme (HTTP/HTTPS), host server, port, current script
  393. * location are used if necessary to resolve any relative URLs.
  394. *
  395. * Offsets potentially created by PATH_INFO are taken care of to resolve
  396. * relative URLs to the current script.
  397. *
  398. * You can choose a new protocol while resolving the URI. This is
  399. * particularly useful when redirecting a web browser using relative URIs
  400. * and to switch from HTTP to HTTPS, or vice-versa, at the same time.
  401. *
  402. * @param string $url Absolute or relative URI the redirect should
  403. * go to.
  404. * @param string $protocol Protocol to use when redirecting URIs.
  405. * @param integer $port A new port number.
  406. *
  407. * @return string The absolute URI.
  408. * @author Philippe Jausions <Philippe.Jausions@11abacus.com>
  409. * @static
  410. * @access public
  411. */
  412. function absoluteURI($url = null, $protocol = null, $port = null)
  413. {
  414. // filter CR/LF
  415. $url = str_replace(array("\r", "\n"), ' ', $url);
  416. // Mess around protocol and port with already absolute URIs
  417. if (preg_match('!^([a-z0-9]+)://!i', $url)) {
  418. if (empty($protocol) && empty($port)) {
  419. return $url;
  420. }
  421. if (!empty($protocol)) {
  422. $url = $protocol .':'. end($array = explode(':', $url, 2));
  423. }
  424. if (!empty($port)) {
  425. $url = preg_replace('!^(([a-z0-9]+)://[^/:]+)(:[\d]+)?!i',
  426. '\1:'. $port, $url);
  427. }
  428. return $url;
  429. }
  430. $host = 'localhost';
  431. if (!empty($_SERVER['HTTP_HOST'])) {
  432. list($host) = explode(':', $_SERVER['HTTP_HOST']);
  433. } elseif (!empty($_SERVER['SERVER_NAME'])) {
  434. list($host) = explode(':', $_SERVER['SERVER_NAME']);
  435. }
  436. if (empty($protocol)) {
  437. if (isset($_SERVER['HTTPS']) && !strcasecmp($_SERVER['HTTPS'], 'on')) {
  438. $protocol = 'https';
  439. } else {
  440. $protocol = 'http';
  441. }
  442. if (!isset($port) || $port != intval($port)) {
  443. $port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
  444. }
  445. }
  446. if ($protocol == 'http' && $port == 80) {
  447. unset($port);
  448. }
  449. if ($protocol == 'https' && $port == 443) {
  450. unset($port);
  451. }
  452. $server = $protocol.'://'.$host.(isset($port) ? ':'.$port : '');
  453. $uriAll = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI']
  454. : $_SERVER['PHP_SELF'];
  455. if (false !== ($q = strpos($uriAll, '?'))) {
  456. $uriBase = substr($uriAll, 0, $q);
  457. } else {
  458. $uriBase = $uriAll;
  459. }
  460. if (!strlen($url) || $url{0} == '#') {
  461. $url = $uriAll.$url;
  462. } elseif ($url{0} == '?') {
  463. $url = $uriBase.$url;
  464. }
  465. if ($url{0} == '/') {
  466. return $server . $url;
  467. }
  468. // Adjust for PATH_INFO if needed
  469. if (isset($_SERVER['PATH_INFO']) && strlen($_SERVER['PATH_INFO'])) {
  470. $path = dirname(substr($uriBase, 0,
  471. -strlen($_SERVER['PATH_INFO'])));
  472. } else {
  473. /**
  474. * Fixes bug #12672 PHP_SELF ending on / causes incorrect redirects
  475. *
  476. * @link http://pear.php.net/bugs/12672
  477. */
  478. $path = dirname($uriBase.'-');
  479. }
  480. if (substr($path = strtr($path, '\\', '/'), -1) != '/') {
  481. $path .= '/';
  482. }
  483. return $server . $path . $url;
  484. }
  485. /**
  486. * Raise Error
  487. *
  488. * Lazy raising of PEAR_Errors.
  489. *
  490. * @param mixed $error Error
  491. * @param integer $code Error code
  492. *
  493. * @return object PEAR_Error
  494. * @static
  495. * @access protected
  496. */
  497. function raiseError($error = null, $code = null)
  498. {
  499. include_once 'PEAR.php';
  500. return PEAR::raiseError($error, $code);
  501. }
  502. }
  503. ?>