HTTP.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. <?php
  2. /**
  3. * This class only contains static functions which can do the following:
  4. *
  5. * - HTTP Redirection
  6. * - E-Tag Cache comparison
  7. * - HTTP Header Parsing
  8. * - URL-Formatting
  9. *
  10. * @copyright (c) 2007-2016 Kohana Team
  11. * @copyright (c) since 2016 Koseven Team
  12. * @license https://koseven.dev/LICENSE
  13. * @since 3.1.0
  14. *
  15. * @package KO7\HTTP
  16. */
  17. abstract class KO7_HTTP {
  18. /**
  19. * The default protocol to use if it cannot be detected
  20. *
  21. * @var string
  22. */
  23. public static $protocol = 'HTTP/1.1';
  24. /**
  25. * Issues a HTTP redirect.
  26. *
  27. * @param string $uri URI to redirect to
  28. * @param int $code HTTP Status code to use for the redirect (RFC 7231)
  29. *
  30. * @throws HTTP_Exception
  31. */
  32. public static function redirect($uri = '', $code = 302)
  33. {
  34. // Check if Redirection code is valid according to RFC 7231
  35. if ($code < 300 || $code > 308)
  36. {
  37. throw new HTTP_Exception('Invalid redirect code \':code\'', [':code' => $code]);
  38. }
  39. $e = HTTP_Exception::factory($code);
  40. throw $e->location($uri);
  41. }
  42. /**
  43. * Checks the browser cache to see the response needs to be returned,
  44. * execution will halt and a 304 Not Modified will be sent if the
  45. * browser cache is up to date.
  46. *
  47. * @param Request $request Request
  48. * @param Response $response Response
  49. * @param string $etag Resource ETag
  50. *
  51. * @throws Request_Exception
  52. * @throws HTTP_Exception_304
  53. *
  54. * @return Response
  55. */
  56. public static function check_cache(Request $request, Response $response, $etag = NULL)
  57. {
  58. // Generate an etag if necessary
  59. if ($etag === NULL)
  60. {
  61. $etag = $response->generate_etag();
  62. }
  63. // Set the ETag header
  64. $response->headers('etag', $etag);
  65. // Add the Cache-Control header if it is not already set
  66. // This allows etags to be used with max-age, etc
  67. if ($response->headers('cache-control'))
  68. {
  69. $response->headers('cache-control', $response->headers('cache-control') . ', must-revalidate');
  70. }
  71. else
  72. {
  73. $response->headers('cache-control', 'must-revalidate');
  74. }
  75. // Check if we have a matching etag
  76. if ($request->headers('if-none-match') && (string)$request->headers('if-none-match') === $etag)
  77. {
  78. // No need to send data again
  79. throw HTTP_Exception::factory(304)->headers('etag', $etag);
  80. }
  81. return $response;
  82. }
  83. /**
  84. * Parses a HTTP header string into an associative array
  85. *
  86. * @param string $header_string Header string to parse
  87. *
  88. * @return HTTP_Header
  89. */
  90. public static function parse_header_string($header_string)
  91. {
  92. // If the PECL HTTP extension is loaded
  93. if (extension_loaded('http'))
  94. {
  95. // Use the fast method to parse header string
  96. // Ignore Code Coverage in case pecl_http is not loaded
  97. // @codeCoverageIgnoreStart
  98. $headers = (new http\Header)->parse($header_string);
  99. return new HTTP_Header($headers);
  100. // @codeCoverageIgnoreEnd
  101. }
  102. // Otherwise we use the slower PHP parsing
  103. $headers = [];
  104. // Match all HTTP headers
  105. if (preg_match_all('/(\w[^\s:]*):[ ]*([^\r\n]*(?:\r\n[ \t][^\r\n]*)*)/', $header_string, $matches))
  106. {
  107. // Parse each matched header
  108. foreach ($matches[0] as $key => $value)
  109. {
  110. // If the header has not already been set
  111. if (!isset($headers[$matches[1][$key]]))
  112. {
  113. // Apply the header directly
  114. $headers[$matches[1][$key]] = $matches[2][$key];
  115. } // Otherwise there is an existing entry
  116. else
  117. {
  118. // If the entry is an array
  119. if (is_array($headers[$matches[1][$key]]))
  120. {
  121. // Apply the new entry to the array
  122. $headers[$matches[1][$key]][] = $matches[2][$key];
  123. } // Otherwise create a new array with the entries
  124. else
  125. {
  126. $headers[$matches[1][$key]] = [$headers[$matches[1][$key]], $matches[2][$key],];
  127. }
  128. }
  129. }
  130. }
  131. // Return the headers
  132. return new HTTP_Header($headers);
  133. }
  134. /**
  135. * Parses the the HTTP request headers and returns an array containing
  136. * key value pairs. This method is slow, but provides an accurate
  137. * representation of the HTTP request.
  138. *
  139. * @return HTTP_Header
  140. */
  141. public static function request_headers()
  142. {
  143. // If running on apache server
  144. if (function_exists('apache_request_headers'))
  145. {
  146. // Return the much faster method
  147. // Ignore Code Coverage....apache_request_headers will *never* be present with PHPUnit
  148. return new HTTP_Header(apache_request_headers()); // @codeCoverageIgnore
  149. }
  150. // If `pecl_http` extension is installed and loaded
  151. if (extension_loaded('http'))
  152. {
  153. // Return the faster method
  154. // Ignore Code Coverage in case pecl_http is not loaded
  155. // @codeCoverageIgnoreStart
  156. $headers = (new http\Env)->getRequestHeader();
  157. return new HTTP_Header($headers);
  158. // @codeCoverageIgnoreEnd
  159. }
  160. // Setup the output
  161. $headers = [];
  162. // Parse the content type
  163. if (!empty($_SERVER['CONTENT_TYPE']))
  164. {
  165. $headers['content-type'] = $_SERVER['CONTENT_TYPE'];
  166. }
  167. // Parse the content length
  168. if (!empty($_SERVER['CONTENT_LENGTH']))
  169. {
  170. $headers['content-length'] = $_SERVER['CONTENT_LENGTH'];
  171. }
  172. foreach ($_SERVER as $key => $value)
  173. {
  174. // If there is no HTTP header here, skip
  175. if (strpos($key, 'HTTP_') !== 0)
  176. {
  177. continue;
  178. }
  179. // This is a dirty hack to ensure HTTP_X_FOO_BAR becomes X-FOO-BAR
  180. $headers[str_replace('_', '-', substr($key, 5))] = $value;
  181. }
  182. return new HTTP_Header($headers);
  183. }
  184. /**
  185. * Processes an array of key value pairs and encodes
  186. * the values to meet RFC 3986
  187. *
  188. * @param array $params Params
  189. *
  190. * @return string
  191. */
  192. public static function www_form_urlencode(array $params)
  193. {
  194. $encoded = [];
  195. foreach ($params as $key => $value)
  196. {
  197. $encoded[] = $key . '=' . rawurlencode($value);
  198. }
  199. return implode('&', $encoded);
  200. }
  201. }