URL.php 8.4 KB

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