URL.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. <?php
  2. /**
  3. * URL helper class.
  4. *
  5. * [!!] You need to setup the list of trusted hosts in the `url.php` config file, before starting using this helper class.
  6. *
  7. * @package Kohana
  8. * @category Helpers
  9. * @author Kohana Team
  10. * @copyright (c) Kohana Team
  11. * @license https://koseven.ga/LICENSE.md
  12. */
  13. class Kohana_URL {
  14. /**
  15. * Gets the base URL to the application.
  16. * To specify a protocol, provide the protocol as a string or request object.
  17. * If a protocol is used, a complete URL will be generated using the
  18. * `$_SERVER['HTTP_HOST']` variable, which will be validated against RFC 952
  19. * and RFC 2181, as well as against the list of trusted hosts you have set
  20. * in the `url.php` config file.
  21. *
  22. * // Absolute URL path with no host or protocol
  23. * echo URL::base();
  24. *
  25. * // Absolute URL path with host, https protocol and index.php if set
  26. * echo URL::base('https', TRUE);
  27. *
  28. * // Absolute URL path with host, https protocol and subdomain part
  29. * // prepended or replaced with given value
  30. * echo URL::base('https', FALSE, 'subdomain');
  31. *
  32. * // Absolute URL path with host and protocol from $request
  33. * echo URL::base($request);
  34. *
  35. * @param mixed $protocol Protocol string, [Request], or boolean
  36. * @param boolean $index Add index file to URL?
  37. * @param string $subdomain Subdomain string
  38. * @return string
  39. * @uses Kohana::$index_file
  40. * @uses Request::protocol()
  41. */
  42. public static function base($protocol = NULL, $index = FALSE, $subdomain = NULL)
  43. {
  44. // Start with the configured base URL
  45. $base_url = Kohana::$base_url;
  46. if ($protocol === TRUE)
  47. {
  48. // Use the initial request to get the protocol
  49. $protocol = Request::$initial;
  50. }
  51. if ($protocol instanceof Request)
  52. {
  53. if ( ! $protocol->secure())
  54. {
  55. // Use the current protocol
  56. list($protocol) = explode('/', strtolower($protocol->protocol()));
  57. }
  58. else
  59. {
  60. $protocol = 'https';
  61. }
  62. }
  63. if ( ! $protocol)
  64. {
  65. // Use the configured default protocol
  66. $protocol = parse_url($base_url, PHP_URL_SCHEME);
  67. }
  68. if ($index === TRUE AND ! empty(Kohana::$index_file))
  69. {
  70. // Add the index file to the URL
  71. $base_url .= Kohana::$index_file.'/';
  72. }
  73. if (is_string($protocol))
  74. {
  75. if ($port = parse_url($base_url, PHP_URL_PORT))
  76. {
  77. // Found a port, make it usable for the URL
  78. $port = ':'.$port;
  79. }
  80. if ($host = parse_url($base_url, PHP_URL_HOST))
  81. {
  82. // Remove everything but the path from the URL
  83. $base_url = parse_url($base_url, PHP_URL_PATH);
  84. }
  85. else
  86. {
  87. // Attempt to use HTTP_HOST and fallback to SERVER_NAME
  88. $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'];
  89. }
  90. // If subdomain passed, then prepend to host or replace existing subdomain
  91. if (NULL !== $subdomain)
  92. {
  93. if (FALSE === strstr($host, '.'))
  94. {
  95. $host = $subdomain.'.'.$host;
  96. }
  97. else
  98. {
  99. // Get the domain part of host eg. example.com, then prepend subdomain
  100. $host = $subdomain.'.'.implode('.', array_slice(explode('.', $host), -2));
  101. }
  102. }
  103. // make $host lowercase
  104. $host = strtolower($host);
  105. // check that host does not contain forbidden characters (see RFC 952 and RFC 2181)
  106. // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
  107. if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
  108. throw new Kohana_Exception(
  109. 'Invalid host :host',
  110. [':host' => $host]
  111. );
  112. }
  113. // Validate $host, see if it matches trusted hosts
  114. if ( ! static::is_trusted_host($host))
  115. {
  116. throw new Kohana_Exception(
  117. 'Untrusted host :host. If you trust :host, add it to the trusted hosts in the `url` config file.',
  118. [':host' => $host]
  119. );
  120. }
  121. // Add the protocol and domain to the base URL
  122. $base_url = $protocol.'://'.$host.$port.$base_url;
  123. }
  124. return $base_url;
  125. }
  126. /**
  127. * Fetches an absolute site URL based on a URI segment.
  128. *
  129. * echo URL::site('foo/bar');
  130. *
  131. * @param string $uri Site URI to convert
  132. * @param mixed $protocol Protocol string or [Request] class to use protocol from
  133. * @param boolean $index Include the index_page in the URL
  134. * @param string $subdomain Subdomain string
  135. * @return string
  136. * @uses URL::base
  137. */
  138. public static function site($uri = '', $protocol = NULL, $index = TRUE, $subdomain = NULL)
  139. {
  140. // Chop off possible scheme, host, port, user and pass parts
  141. $path = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/'));
  142. if ( ! UTF8::is_ascii($path))
  143. {
  144. // Encode all non-ASCII characters, as per RFC 1738
  145. $path = preg_replace_callback('~([^/#]+)~', 'URL::_rawurlencode_callback', $path);
  146. }
  147. // Concat the URL
  148. return URL::base($protocol, $index, $subdomain).$path;
  149. }
  150. /**
  151. * Callback used for encoding all non-ASCII characters, as per RFC 1738
  152. * Used by URL::site()
  153. *
  154. * @param array $matches Array of matches from preg_replace_callback()
  155. * @return string Encoded string
  156. */
  157. protected static function _rawurlencode_callback($matches)
  158. {
  159. return rawurlencode($matches[0]);
  160. }
  161. /**
  162. * Merges the current GET parameters with an array of new or overloaded
  163. * parameters and returns the resulting query string.
  164. *
  165. * // Returns "?sort=title&limit=10" combined with any existing GET values
  166. * $query = URL::query(array('sort' => 'title', 'limit' => 10));
  167. *
  168. * Typically you would use this when you are sorting query results,
  169. * or something similar.
  170. *
  171. * [!!] Parameters with a NULL value are left out.
  172. *
  173. * @param array $params Array of GET parameters
  174. * @param boolean $use_get Include current request GET parameters
  175. * @return string
  176. */
  177. public static function query(array $params = NULL, $use_get = TRUE)
  178. {
  179. if ($use_get)
  180. {
  181. if ($params === NULL)
  182. {
  183. // Use only the current parameters
  184. $params = $_GET;
  185. }
  186. else
  187. {
  188. // Merge the current and new parameters
  189. $params = Arr::merge($_GET, $params);
  190. }
  191. }
  192. if (empty($params))
  193. {
  194. // No query parameters
  195. return '';
  196. }
  197. // Note: http_build_query returns an empty string for a params array with only NULL values
  198. $query = http_build_query($params, '', '&');
  199. // Don't prepend '?' to an empty string
  200. return ($query === '') ? '' : ('?'.$query);
  201. }
  202. /**
  203. * Convert a phrase to a URL-safe title.
  204. *
  205. * echo URL::title('My Blog Post'); // "my-blog-post"
  206. *
  207. * @param string $title Phrase to convert
  208. * @param string $separator Word separator (any single character)
  209. * @param boolean $ascii_only Transliterate to ASCII?
  210. * @return string
  211. * @uses UTF8::transliterate_to_ascii
  212. */
  213. public static function title($title, $separator = '-', $ascii_only = FALSE)
  214. {
  215. if ($ascii_only)
  216. {
  217. // Transliterate non-ASCII characters
  218. if (extension_loaded('intl'))
  219. {
  220. $title = transliterator_transliterate('Any-Latin;Latin-ASCII', $title);
  221. }
  222. else
  223. {
  224. $title = UTF8::transliterate_to_ascii($title);
  225. }
  226. // Remove all characters that are not the separator, a-z, 0-9, or whitespace
  227. $title = preg_replace('![^'.preg_quote($separator).'a-z0-9\s]+!', '', strtolower($title));
  228. }
  229. else
  230. {
  231. // Remove all characters that are not the separator, letters, numbers, or whitespace
  232. $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', UTF8::strtolower($title));
  233. }
  234. // Replace all separator characters and whitespace by a single separator
  235. $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
  236. // Trim separators from the beginning and end
  237. return trim($title, $separator);
  238. }
  239. /**
  240. * Test if given $host should be trusted.
  241. *
  242. * Tests against given $trusted_hosts
  243. * or looks for key `trusted_hosts` in `url` config
  244. *
  245. * @param string $host
  246. * @param array $trusted_hosts
  247. * @return boolean TRUE if $host is trustworthy
  248. */
  249. public static function is_trusted_host($host, array $trusted_hosts = NULL)
  250. {
  251. // If list of trusted hosts is not directly provided read from config
  252. if (empty($trusted_hosts))
  253. {
  254. $trusted_hosts = (array) Kohana::$config->load('url')->get('trusted_hosts');
  255. }
  256. // loop through the $trusted_hosts array for a match
  257. foreach ($trusted_hosts as $trusted_host)
  258. {
  259. // make sure we fully match the trusted hosts
  260. $pattern = '#^'.$trusted_host.'$#uD';
  261. // return TRUE if there is match
  262. if (preg_match($pattern, $host)) {
  263. return TRUE;
  264. }
  265. }
  266. // return FALSE as nothing is matched
  267. return FALSE;
  268. }
  269. }