URL.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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. if (is_null($uri))
  141. {
  142. $uri = '';
  143. }
  144. // Chop off possible scheme, host, port, user and pass parts
  145. $path = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/'));
  146. if ( ! UTF8::is_ascii($path))
  147. {
  148. // Encode all non-ASCII characters, as per RFC 1738
  149. $path = preg_replace_callback('~([^/#]+)~', 'URL::_rawurlencode_callback', $path);
  150. }
  151. // Concat the URL
  152. return URL::base($protocol, $index, $subdomain).$path;
  153. }
  154. /**
  155. * Callback used for encoding all non-ASCII characters, as per RFC 1738
  156. * Used by URL::site()
  157. *
  158. * @param array $matches Array of matches from preg_replace_callback()
  159. * @return string Encoded string
  160. */
  161. protected static function _rawurlencode_callback($matches)
  162. {
  163. return rawurlencode($matches[0]);
  164. }
  165. /**
  166. * Merges the current GET parameters with an array of new or overloaded
  167. * parameters and returns the resulting query string.
  168. *
  169. * // Returns "?sort=title&limit=10" combined with any existing GET values
  170. * $query = URL::query(array('sort' => 'title', 'limit' => 10));
  171. *
  172. * Typically you would use this when you are sorting query results,
  173. * or something similar.
  174. *
  175. * [!!] Parameters with a NULL value are left out.
  176. *
  177. * @param array $params Array of GET parameters
  178. * @param boolean $use_get Include current request GET parameters
  179. * @return string
  180. */
  181. public static function query(array $params = NULL, $use_get = TRUE)
  182. {
  183. if ($use_get)
  184. {
  185. if ($params === NULL)
  186. {
  187. // Use only the current parameters
  188. $params = $_GET;
  189. }
  190. else
  191. {
  192. // Merge the current and new parameters
  193. $params = Arr::merge($_GET, $params);
  194. }
  195. }
  196. if (empty($params))
  197. {
  198. // No query parameters
  199. return '';
  200. }
  201. // Note: http_build_query returns an empty string for a params array with only NULL values
  202. $query = http_build_query($params, '', '&');
  203. // Don't prepend '?' to an empty string
  204. return ($query === '') ? '' : ('?'.$query);
  205. }
  206. /**
  207. * Convert a phrase to a URL-safe title.
  208. *
  209. * echo URL::title('My Blog Post'); // "my-blog-post"
  210. *
  211. * @param string $title Phrase to convert
  212. * @param string $separator Word separator (any single character)
  213. * @param boolean $ascii_only Transliterate to ASCII?
  214. * @return string
  215. * @uses UTF8::transliterate_to_ascii
  216. */
  217. public static function title($title, $separator = '-', $ascii_only = FALSE)
  218. {
  219. if ($ascii_only)
  220. {
  221. // Transliterate non-ASCII characters
  222. if (extension_loaded('intl'))
  223. {
  224. $title = transliterator_transliterate('Any-Latin;Latin-ASCII', $title);
  225. }
  226. else
  227. {
  228. $title = UTF8::transliterate_to_ascii($title);
  229. }
  230. // Remove all characters that are not the separator, a-z, 0-9, or whitespace
  231. $title = preg_replace('![^'.preg_quote($separator).'a-z0-9\s]+!', '', strtolower($title));
  232. }
  233. else
  234. {
  235. // Remove all characters that are not the separator, letters, numbers, or whitespace
  236. $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', UTF8::strtolower($title));
  237. }
  238. // Replace all separator characters and whitespace by a single separator
  239. $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
  240. // Trim separators from the beginning and end
  241. return trim($title, $separator);
  242. }
  243. /**
  244. * Test if given $host should be trusted.
  245. *
  246. * Tests against given $trusted_hosts
  247. * or looks for key `trusted_hosts` in `url` config
  248. *
  249. * @param string $host
  250. * @param array $trusted_hosts
  251. * @return boolean TRUE if $host is trustworthy
  252. */
  253. public static function is_trusted_host($host, array $trusted_hosts = NULL)
  254. {
  255. // If list of trusted hosts is not directly provided read from config
  256. if (empty($trusted_hosts))
  257. {
  258. $trusted_hosts = (array) Kohana::$config->load('url')->get('trusted_hosts');
  259. }
  260. // loop through the $trusted_hosts array for a match
  261. foreach ($trusted_hosts as $trusted_host)
  262. {
  263. // make sure we fully match the trusted hosts
  264. $pattern = '#^'.$trusted_host.'$#uD';
  265. // return TRUE if there is match
  266. if (preg_match($pattern, $host)) {
  267. return TRUE;
  268. }
  269. }
  270. // return FALSE as nothing is matched
  271. return FALSE;
  272. }
  273. }