+ return $host;
+ }
+}
+
+// Port normalization: Suppress the (redundant) default port
+// HTTP://example.org:80/ => http://example.org/
+// HTTP://example.org:8080/ => http://example.org:8080/
+// HTTPS://example.org:443/ => https://example.org/
+function port_normalize($port, $scheme, $scheme_normalize = FALSE)
+{
+ // Schemes that users _maybe_ want to add protocol-handlers
+ // to their web browsers. (and attackers _maybe_ want to use ...)
+ // Reference: http://www.iana.org/assignments/port-numbers
+ static $array = array(
+ // scheme => default port
+ 'ftp' => 21,
+ 'ssh' => 22,
+ 'telnet' => 23,
+ 'smtp' => 25,
+ 'tftp' => 69,
+ 'gopher' => 70,
+ 'finger' => 79,
+ 'http' => 80,
+ 'pop3' => 110,
+ 'sftp' => 115,
+ 'nntp' => 119,
+ 'imap' => 143,
+ 'irc' => 194,
+ 'wais' => 210,
+ 'https' => 443,
+ 'nntps' => 563,
+ 'rsync' => 873,
+ 'ftps' => 990,
+ 'telnets' => 992,
+ 'imaps' => 993,
+ 'ircs' => 994,
+ 'pop3s' => 995,
+ 'mysql' => 3306,
+ );
+
+ // intval() converts '0-1' to '0', so preg_match() rejects these invalid ones
+ if (! is_numeric($port) || $port < 0 || preg_match('/[^0-9]/i', $port))
+ return '';
+
+ $port = intval($port);
+ if ($scheme_normalize) $scheme = scheme_normalize($scheme);
+ if (isset($array[$scheme]) && $port == $array[$scheme])
+ $port = ''; // Ignore the defaults
+
+ return $port;
+}
+
+// Path normalization
+// http://example.org => http://example.org/
+// http://example.org#hoge => http://example.org/#hoge
+// http://example.org/path/a/b/./c////./d => http://example.org/path/a/b/c/d
+// http://example.org/path/../../a/../back => http://example.org/back
+function path_normalize($path = '', $divider = '/', $add_root = TRUE)
+{
+ if (! is_string($divider)) return is_string($path) ? $path : '';
+
+ if ($add_root) {
+ $first_div = & $divider;
+ } else {
+ $first_div = '';
+ }
+ if (! is_string($path) || $path == '') return $first_div;
+
+ if (strpos($path, $divider, strlen($path) - strlen($divider)) === FALSE) {
+ $last_div = '';
+ } else {
+ $last_div = & $divider;
+ }
+
+ $array = explode($divider, $path);
+
+ // Remove paddings ('//' and '/./')
+ foreach(array_keys($array) as $key) {
+ if ($array[$key] == '' || $array[$key] == '.') {
+ unset($array[$key]);
+ }
+ }
+
+ // Remove back-tracks ('/../')
+ $tmp = array();
+ foreach($array as $value) {
+ if ($value == '..') {
+ array_pop($tmp);
+ } else {
+ array_push($tmp, $value);
+ }
+ }
+ $array = & $tmp;
+
+ if (empty($array)) {
+ return $first_div;
+ } else {
+ return $first_div . implode($divider, $array) . $last_div;
+ }
+}
+
+// DirectoryIndex normalize (Destructive and rough)
+// TODO: sample.en.ja.html.gz => sample.html
+function file_normalize($file = 'index.html.en')
+{
+ static $simple_defaults = array(
+ 'default.htm' => TRUE,
+ 'default.html' => TRUE,
+ 'default.asp' => TRUE,
+ 'default.aspx' => TRUE,
+ 'index' => TRUE, // Some system can omit the suffix
+ );
+
+ static $content_suffix = array(
+ // index.xxx, sample.xxx
+ 'htm' => TRUE,
+ 'html' => TRUE,
+ 'shtml' => TRUE,
+ 'jsp' => TRUE,
+ 'php' => TRUE,
+ 'php3' => TRUE,
+ 'php4' => TRUE,
+ 'pl' => TRUE,
+ 'py' => TRUE,
+ 'rb' => TRUE,
+ 'cgi' => TRUE,
+ 'xml' => TRUE,
+ );
+
+ static $language_suffix = array(
+ // Reference: Apache 2.0.59 'AddLanguage' default
+ 'ca' => TRUE,
+ 'cs' => TRUE, // cs
+ 'cz' => TRUE, // cs
+ 'de' => TRUE,
+ 'dk' => TRUE, // da
+ 'el' => TRUE,
+ 'en' => TRUE,
+ 'eo' => TRUE,
+ 'es' => TRUE,
+ 'et' => TRUE,
+ 'fr' => TRUE,
+ 'he' => TRUE,
+ 'hr' => TRUE,
+ 'it' => TRUE,
+ 'ja' => TRUE,
+ 'ko' => TRUE,
+ 'ltz' => TRUE,
+ 'nl' => TRUE,
+ 'nn' => TRUE,
+ 'no' => TRUE,
+ 'po' => TRUE,
+ 'pt' => TRUE,
+ 'pt-br' => TRUE,
+ 'ru' => TRUE,
+ 'sv' => TRUE,
+ 'zh-cn' => TRUE,
+ 'zh-tw' => TRUE,
+
+ // Reference: Apache 2.0.59 default 'index.html' variants
+ 'ee' => TRUE,
+ 'lb' => TRUE,
+ 'var' => TRUE,
+ );
+
+ static $charset_suffix = array(
+ // Reference: Apache 2.0.59 'AddCharset' default
+ 'iso8859-1' => TRUE, // ISO-8859-1
+ 'latin1' => TRUE, // ISO-8859-1
+ 'iso8859-2' => TRUE, // ISO-8859-2
+ 'latin2' => TRUE, // ISO-8859-2
+ 'cen' => TRUE, // ISO-8859-2
+ 'iso8859-3' => TRUE, // ISO-8859-3
+ 'latin3' => TRUE, // ISO-8859-3
+ 'iso8859-4' => TRUE, // ISO-8859-4
+ 'latin4' => TRUE, // ISO-8859-4
+ 'iso8859-5' => TRUE, // ISO-8859-5
+ 'latin5' => TRUE, // ISO-8859-5
+ 'cyr' => TRUE, // ISO-8859-5
+ 'iso-ru' => TRUE, // ISO-8859-5
+ 'iso8859-6' => TRUE, // ISO-8859-6
+ 'latin6' => TRUE, // ISO-8859-6
+ 'arb' => TRUE, // ISO-8859-6
+ 'iso8859-7' => TRUE, // ISO-8859-7
+ 'latin7' => TRUE, // ISO-8859-7
+ 'grk' => TRUE, // ISO-8859-7
+ 'iso8859-8' => TRUE, // ISO-8859-8
+ 'latin8' => TRUE, // ISO-8859-8
+ 'heb' => TRUE, // ISO-8859-8
+ 'iso8859-9' => TRUE, // ISO-8859-9
+ 'latin9' => TRUE, // ISO-8859-9
+ 'trk' => TRUE, // ISO-8859-9
+ 'iso2022-jp'=> TRUE, // ISO-2022-JP
+ 'jis' => TRUE, // ISO-2022-JP
+ 'iso2022-kr'=> TRUE, // ISO-2022-KR
+ 'kis' => TRUE, // ISO-2022-KR
+ 'iso2022-cn'=> TRUE, // ISO-2022-CN
+ 'cis' => TRUE, // ISO-2022-CN
+ 'big5' => TRUE,
+ 'cp-1251' => TRUE, // ru, WINDOWS-1251
+ 'win-1251' => TRUE, // ru, WINDOWS-1251
+ 'cp866' => TRUE, // ru
+ 'koi8-r' => TRUE, // ru, KOI8-r
+ 'koi8-ru' => TRUE, // ru, KOI8-r
+ 'koi8-uk' => TRUE, // ru, KOI8-ru
+ 'ua' => TRUE, // ru, KOI8-ru
+ 'ucs2' => TRUE, // ru, ISO-10646-UCS-2
+ 'ucs4' => TRUE, // ru, ISO-10646-UCS-4
+ 'utf8' => TRUE,
+
+ // Reference: Apache 2.0.59 default 'index.html' variants
+ 'euc-kr' => TRUE,
+ 'gb2312' => TRUE,
+ );
+
+ // May uncompress by web browsers on the fly
+ // Must be at the last of the filename
+ // Reference: Apache 2.0.59 'AddEncoding'
+ static $encoding_suffix = array(
+ 'z' => TRUE,
+ 'gz' => TRUE,
+ );
+
+ if (! is_string($file)) return '';
+ $_file = strtolower($file);
+ if (isset($simple_defaults[$_file])) return '';
+
+ // Roughly removing language/character-set/encoding suffixes
+ // References:
+ // * Apache 2 document about 'Content-negotiaton', 'mod_mime' and 'mod_negotiation'
+ // http://httpd.apache.org/docs/2.0/content-negotiation.html
+ // http://httpd.apache.org/docs/2.0/mod/mod_mime.html
+ // http://httpd.apache.org/docs/2.0/mod/mod_negotiation.html
+ // * http://www.iana.org/assignments/character-sets
+ // * RFC3066: Tags for the Identification of Languages
+ // http://www.ietf.org/rfc/rfc3066.txt
+ // * ISO 639: codes of 'language names'
+ $suffixes = explode('.', $_file);
+ $body = array_shift($suffixes);
+ if ($suffixes) {
+ // Remove the last .gz/.z
+ $last_key = end(array_keys($suffixes));
+ if (isset($encoding_suffix[$suffixes[$last_key]])) {
+ unset($suffixes[$last_key]);
+ }
+ }
+ // Cut language and charset suffixes
+ foreach($suffixes as $key => $value){
+ if (isset($language_suffix[$value]) || isset($charset_suffix[$value])) {
+ unset($suffixes[$key]);
+ }