secure()) { // Use the current protocol list($protocol) = explode('/', strtolower($protocol->protocol())); } else { $protocol = 'https'; } } if ( ! $protocol) { // Use the configured default protocol $protocol = parse_url($base_url, PHP_URL_SCHEME); } if ($index === TRUE AND ! empty(KO7::$index_file)) { // Add the index file to the URL $base_url .= KO7::$index_file.'/'; } if (is_string($protocol)) { if ($port = parse_url($base_url, PHP_URL_PORT)) { // Found a port, make it usable for the URL $port = ':'.$port; } if ($host = parse_url($base_url, PHP_URL_HOST)) { // Remove everything but the path from the URL $base_url = parse_url($base_url, PHP_URL_PATH); } else { // Attempt to use HTTP_HOST and fallback to SERVER_NAME $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME']; } // If subdomain passed, then prepend to host or replace existing subdomain if (NULL !== $subdomain) { if (FALSE === strstr($host, '.')) { $host = $subdomain.'.'.$host; } else { // Get the domain part of host eg. example.com, then prepend subdomain $host = $subdomain.'.'.implode('.', array_slice(explode('.', $host), -2)); } } // make $host lowercase $host = strtolower($host); // check that host does not contain forbidden characters (see RFC 952 and RFC 2181) // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) { throw new KO7_Exception( 'Invalid host :host', [':host' => $host] ); } // Validate $host, see if it matches trusted hosts if ( ! static::is_trusted_host($host)) { throw new KO7_Exception( 'Untrusted host :host. If you trust :host, add it to the trusted hosts in the `url` config file.', [':host' => $host] ); } // Add the protocol and domain to the base URL $base_url = $protocol.'://'.$host.$port.$base_url; } return $base_url; } /** * Fetches an absolute site URL based on a URI segment. * * echo URL::site('foo/bar'); * * @param string $uri Site URI to convert * @param mixed $protocol Protocol string or [Request] class to use protocol from * @param boolean $index Include the index_page in the URL * @param string $subdomain Subdomain string * @return string * @uses URL::base */ public static function site($uri = '', $protocol = NULL, $index = TRUE, $subdomain = NULL) { // Chop off possible scheme, host, port, user and pass parts $path = preg_replace('~^[-a-z0-9+.]++://[^/]++/?~', '', trim($uri, '/')); if ( ! UTF8::is_ascii($path)) { // Encode all non-ASCII characters, as per RFC 1738 $path = preg_replace_callback('~([^/#]+)~', 'URL::_rawurlencode_callback', $path); } // Concat the URL return URL::base($protocol, $index, $subdomain).$path; } /** * Callback used for encoding all non-ASCII characters, as per RFC 1738 * Used by URL::site() * * @param array $matches Array of matches from preg_replace_callback() * @return string Encoded string */ protected static function _rawurlencode_callback($matches) { return rawurlencode($matches[0]); } /** * Merges the current GET parameters with an array of new or overloaded * parameters and returns the resulting query string. * * // Returns "?sort=title&limit=10" combined with any existing GET values * $query = URL::query(array('sort' => 'title', 'limit' => 10)); * * Typically you would use this when you are sorting query results, * or something similar. * * [!!] Parameters with a NULL value are left out. * * @param array $params Array of GET parameters * @param boolean $use_get Include current request GET parameters * @return string */ public static function query(array $params = NULL, $use_get = TRUE) { if ($use_get) { if ($params === NULL) { // Use only the current parameters $params = $_GET; } else { // Merge the current and new parameters $params = Arr::merge($_GET, $params); } } if (empty($params)) { // No query parameters return ''; } // Note: http_build_query returns an empty string for a params array with only NULL values $query = http_build_query($params, '', '&'); // Don't prepend '?' to an empty string return ($query === '') ? '' : ('?'.$query); } /** * Convert a phrase to a URL-safe title. * * echo URL::title('My Blog Post'); // "my-blog-post" * * @param string $title Phrase to convert * @param string $separator Word separator (any single character) * @param boolean $ascii_only Transliterate to ASCII? * @return string * @uses UTF8::transliterate_to_ascii */ public static function title($title, $separator = '-', $ascii_only = FALSE) { if ($ascii_only) { // Transliterate non-ASCII characters if (extension_loaded('intl')) { $title = transliterator_transliterate('Any-Latin;Latin-ASCII', $title); } else { $title = UTF8::transliterate_to_ascii($title); } // Remove all characters that are not the separator, a-z, 0-9, or whitespace $title = preg_replace('![^'.preg_quote($separator).'a-z0-9\s]+!', '', strtolower($title)); } else { // Remove all characters that are not the separator, letters, numbers, or whitespace $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', UTF8::strtolower($title)); } // Replace all separator characters and whitespace by a single separator $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title); // Trim separators from the beginning and end return trim($title, $separator); } /** * Test if given $host should be trusted. * * Tests against given $trusted_hosts * or looks for key `trusted_hosts` in `url` config * * @param string $host * @param array $trusted_hosts * @return boolean TRUE if $host is trustworthy */ public static function is_trusted_host($host, array $trusted_hosts = NULL) { // If list of trusted hosts is not directly provided read from config if (empty($trusted_hosts)) { $trusted_hosts = (array) KO7::$config->load('url')->get('trusted_hosts'); } // loop through the $trusted_hosts array for a match foreach ($trusted_hosts as $trusted_host) { // make sure we fully match the trusted hosts $pattern = '#^'.$trusted_host.'$#uD'; // return TRUE if there is match if (preg_match($pattern, $host)) { return TRUE; } } // return FALSE as nothing is matched return FALSE; } }