removed unused variables
[mono.git] / mcs / class / System / System / Uri.cs
index 223a4ed3a2374097ac263c7de9b79918a495fea1..1a0d51ec54f882ae40d1b486c578193dcfc4508b 100755 (executable)
 // (C) 2003 Novell inc.
 //
 
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+using System.IO;
 using System.Net;
 using System.Runtime.Serialization;
 using System.Text;
 using System.Collections;
+using System.Globalization;
 
 // See RFC 2396 for more info on URI's.
 
@@ -32,15 +54,15 @@ namespace System
                // o  scheme excludes the scheme delimiter
                // o  port is -1 to indicate no port is defined
                // o  path is empty or starts with / when scheme delimiter == "://"
-               // o  query is empty or starts with ? char
-               // o  fragment is empty or starts with # char
+               // o  query is empty or starts with ? char, escaped.
+               // o  fragment is empty or starts with # char, unescaped.
                // o  all class variables are in escaped format when they are escapable,
                //    except cachedToString.
                // o  UNC is supported, as starts with "\\" for windows,
                //    or "//" with unix.
 
-               private bool isWindowsFilePath = false;
                private bool isUnixFilePath = false;
+               private string source;
                private string scheme = String.Empty;
                private string host = String.Empty;
                private int port = -1;
@@ -48,8 +70,6 @@ namespace System
                private string query = String.Empty;
                private string fragment = String.Empty;
                private string userinfo = String.Empty;
-               private bool is_root_path = false;
-               private bool is_wins_dir = true;
                private bool isUnc = false;
                private bool isOpaquePart = false;
 
@@ -58,6 +78,7 @@ namespace System
                private bool userEscaped = false;
                private string cachedAbsoluteUri = null;
                private string cachedToString = null;
+               private string cachedLocalPath = null;
                private int cachedHashCode = 0;
                
                private static readonly string hexUpperChars = "0123456789ABCDEF";
@@ -87,15 +108,10 @@ namespace System
                }
 
                public Uri (string uriString, bool dontEscape) 
-               {                       
+               {
                        userEscaped = dontEscape;
-                       Parse (uriString);
-                       
-                       if (userEscaped) 
-                               return;
-
-                       host = EscapeString (host, false, true, false);
-                       path = EscapeString (path);
+                       source = uriString;
+                       Parse ();
                }
 
                public Uri (Uri baseUri, string relativeUri) 
@@ -112,47 +128,53 @@ namespace System
 
                        userEscaped = dontEscape;
 
-                       this.scheme = baseUri.scheme;
-                       this.host = baseUri.host;
-                       this.port = baseUri.port;
-                       this.userinfo = baseUri.userinfo;
-                       this.isUnc = baseUri.isUnc;
-                       this.isWindowsFilePath = baseUri.isWindowsFilePath;
-                       this.isUnixFilePath = baseUri.isUnixFilePath;
-                       this.isOpaquePart = baseUri.isOpaquePart;
-
                        if (relativeUri == null)
                                throw new NullReferenceException ("relativeUri");
 
-                       if (relativeUri == String.Empty) {
-                               this.path = baseUri.path;
-                               this.query = baseUri.query;
-                               this.fragment = baseUri.fragment;
+                       // Check Windows UNC (for // it is scheme/host separator)
+                       if (relativeUri.StartsWith ("\\\\")) {
+                               source = relativeUri;
+                               Parse ();
                                return;
                        }
 
                        int pos = relativeUri.IndexOf (':');
                        if (pos != -1) {
 
-                               int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\'});
+                               int pos2 = relativeUri.IndexOfAny (new char [] {'/', '\\', '?'});
 
-                               if (pos2 > pos) {
+                               // pos2 < 0 ... e.g. mailto
+                               // pos2 > pos ... to block ':' in query part
+                               if (pos2 > pos || pos2 < 0) {
                                        // equivalent to new Uri (relativeUri, dontEscape)
-                                       Parse (relativeUri);
+                                       source = relativeUri;
+                                       Parse ();
 
-                                       if (userEscaped) 
-                                               return;
-
-                                       host = EscapeString (host, false, true, false);
-                                       path = EscapeString (path);
                                        return;
                                }
                        }
 
+                       this.scheme = baseUri.scheme;
+                       this.host = baseUri.host;
+                       this.port = baseUri.port;
+                       this.userinfo = baseUri.userinfo;
+                       this.isUnc = baseUri.isUnc;
+                       this.isUnixFilePath = baseUri.isUnixFilePath;
+                       this.isOpaquePart = baseUri.isOpaquePart;
+
+                       if (relativeUri == String.Empty) {
+                               this.path = baseUri.path;
+                               this.query = baseUri.query;
+                               this.fragment = baseUri.fragment;
+                               return;
+                       }
+                       
                        // 8 fragment
+                       // Note that in relative constructor, file URI cannot handle '#' as a filename character, but just regarded as a fragment identifier.
                        pos = relativeUri.IndexOf ('#');
                        if (pos != -1) {
                                fragment = relativeUri.Substring (pos);
+                               // fragment is not escaped.
                                relativeUri = relativeUri.Substring (0, pos);
                        }
 
@@ -166,10 +188,16 @@ namespace System
                        }
 
                        if (relativeUri.Length > 0 && relativeUri [0] == '/') {
-                               path = relativeUri;
-                               if (!userEscaped)
-                                       path = EscapeString (path);
-                               return;
+                               if (relativeUri.Length > 1 && relativeUri [1] == '/') {
+                                       source = scheme + ':' + relativeUri;
+                                       Parse ();
+                                       return;
+                               } else {
+                                       path = relativeUri;
+                                       if (!userEscaped)
+                                               path = EscapeString (path);
+                                       return;
+                               }
                        }
                        
                        // par 5.2 step 6 a)
@@ -247,8 +275,9 @@ namespace System
 
                public string AbsoluteUri { 
                        get { 
-                               if (cachedAbsoluteUri == null)
+                               if (cachedAbsoluteUri == null) {
                                        cachedAbsoluteUri = GetLeftPart (UriPartial.Path) + query + fragment;
+                               }
                                return cachedAbsoluteUri;
                        } 
                }
@@ -296,7 +325,8 @@ namespace System
                                        return true;
                                        
                                try {
-                                       return IPAddress.IsLoopback (IPAddress.Parse (host));
+                                       if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
+                                               return true;
                                } catch (FormatException) {}
 
                                try {
@@ -316,24 +346,37 @@ namespace System
 
                public string LocalPath { 
                        get {
+                               if (cachedLocalPath != null)
+                                       return cachedLocalPath;
                                if (!IsFile)
-                                       return path;
+                                       return AbsolutePath;
+
+                               bool windows = (path.Length > 3 && path [1] == ':' &&
+                                               (path [2] == '\\' || path [2] == '/'));
+
                                if (!IsUnc) {
                                        string p = Unescape (path);
-                                       if (System.IO.Path.DirectorySeparatorChar == '\\')
-                                               return p.Replace ('/', '\\');
+                                       if (System.IO.Path.DirectorySeparatorChar == '\\' || windows)
+                                               cachedLocalPath = p.Replace ('/', '\\');
                                        else
-                                               return p;
+                                               cachedLocalPath = p;
+                               } else {
+                                       // support *nix and W32 styles
+                                       if (path.Length > 1 && path [1] == ':')
+                                               cachedLocalPath = Unescape (path.Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar));
+
+                                       // LAMESPEC: ok, now we cannot determine
+                                       // if such URI like "file://foo/bar" is
+                                       // Windows UNC or unix file path, so
+                                       // they should be handled differently.
+                                       else if (System.IO.Path.DirectorySeparatorChar == '\\')
+                                               cachedLocalPath = "\\\\" + Unescape (host + path.Replace ('/', '\\'));
+                                       else
+                                               cachedLocalPath = Unescape (path);
                                }
-
-                               // support *nix and W32 styles
-                               if (path.Length > 1 && path [1] == ':')
-                                       return Unescape (path.Replace ('/', '\\'));
-
-                               if (System.IO.Path.DirectorySeparatorChar == '\\')
-                                       return "\\\\" + Unescape (host + path.Replace ('/', '\\'));
-                               else 
-                                       return (is_root_path? "/": "") + (is_wins_dir? "/": "") + Unescape (host + path);
+                               if (cachedLocalPath == String.Empty)
+                                       cachedLocalPath = Path.DirectorySeparatorChar.ToString ();
+                               return cachedLocalPath;
                        } 
                }
 
@@ -429,7 +472,7 @@ namespace System
                                return false;
                        for (int i = 0; i < 4; i++) {
                                try {
-                                       int d = Int32.Parse (captures [i]);
+                                       int d = Int32.Parse (captures [i], CultureInfo.InvariantCulture);
                                        if (d < 0 || d > 255)
                                                return false;
                                } catch (Exception) {
@@ -504,13 +547,14 @@ namespace System
                                        return false;
                                uri = new Uri (s);
                        }
-                       
-                       return ((this.scheme == uri.scheme) &&
-                               (this.userinfo == uri.userinfo) &&
-                               (this.host == uri.host) &&
-                               (this.port == uri.port) &&
-                               (this.path == uri.path) &&
-                               (this.query == uri.query));
+
+                       CultureInfo inv = CultureInfo.InvariantCulture;
+                       return ((this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)) &&
+                               (this.userinfo.ToLower (inv) == uri.userinfo.ToLower (inv)) &&
+                               (this.host.ToLower (inv) == uri.host.ToLower (inv)) &&
+                               (this.port == uri.port) &&
+                               (this.path == uri.path) &&
+                               (this.query.ToLower (inv) == uri.query.ToLower (inv)));
                }               
                
                public override int GetHashCode () 
@@ -598,18 +642,54 @@ namespace System
                                throw new ArgumentException ("pattern");
                                
                        if (index < 0 || index >= pattern.Length)
-                               throw new ArgumentOutOfRangeException ("index");        
-                               
-                       if (((index + 3) > pattern.Length) ||
-                           (pattern [index] != '%') || 
-                           !IsHexDigit (pattern [index + 1]) || 
-                           !IsHexDigit (pattern [index + 2]))
-                       {
-                               return pattern[index++];
-                       }
+                               throw new ArgumentOutOfRangeException ("index");
+
+                       int stage = 0;
+                       int c = 0;
+                       do {
+                               if (((index + 3) > pattern.Length) ||
+                                   (pattern [index] != '%') || 
+                                   !IsHexDigit (pattern [index + 1]) || 
+                                   !IsHexDigit (pattern [index + 2]))
+                               {
+                                       if (stage == 0)
+                                               return pattern [index++];
+                                       break;
+                               }
+
+                               index++;
+                               int msb = FromHex (pattern [index++]);
+                               int lsb = FromHex (pattern [index++]);
+                               int b = (msb << 4) + lsb;
+
+                               if (stage == 0) {
+                                       if (b < 0xc0)
+                                               return (char) b;
+                                       else if (b < 0xE0) {
+                                               c = b - 0xc0;
+                                               stage = 2;
+                                       } else if (b < 0xF0) {
+                                               c = b - 0xe0;
+                                               stage = 3;
+                                       } else if (b < 0xF8) {
+                                               c = b - 0xf0;
+                                               stage = 4;
+                                       } else if (b < 0xFB) {
+                                               c = b - 0xf8;
+                                               stage = 5;
+                                       } else if (b < 0xFE) {
+                                               c = b - 0xfc;
+                                               stage = 6;
+                                       }
+                                       c <<= (stage - 1) * 6;
+                               }
+                               else
+                                       c += (b - 0x80) << ((stage - 1) * 6);
+//Console.WriteLine ("stage {0}: {5:X04} <-- {1:X02}|{2:X01},{3:X01} {4}", new object [] {stage, b, msb, lsb, pattern.Substring (index), c});
+                               stage--;
+                       } while (stage > 0);
                        
-                       index++;
-                       return (char) ((FromHex (pattern [index++]) << 4) + FromHex (pattern [index++]));
+                       return (char) c;
                }
 
                public static bool IsHexDigit (char digit) 
@@ -660,11 +740,8 @@ namespace System
                {
                        if (cachedToString != null) 
                                return cachedToString;
-                       if (IsFile && !IsUnc)
-                               cachedToString = Unescape (AbsoluteUri);
-                       else
-                               cachedToString = AbsoluteUri;
-
+                       string q = query.StartsWith ("?") ? '?' + Unescape (query.Substring (1)) : Unescape (query);
+                       cachedToString = Unescape (GetLeftPart (UriPartial.Path), true) + q + fragment;
                        return cachedToString;
                }
 
@@ -722,7 +799,6 @@ namespace System
                                    (escapeHex && (c == '#')) ||
                                    (escapeBrackets && (c == '[' || c == ']')) ||
                                    (escapeReserved && (";/?:@&=+$,".IndexOf (c) != -1))) {
-                                       // FIXME: EscapeString should allow wide characters (> 255). HexEscape rejects it.
                                        s.Append (HexEscape (c));
                                        continue;
                                }
@@ -734,12 +810,26 @@ namespace System
                        return s.ToString ();
                }
 
-               [MonoTODO ("Find out what this should do!")]
+               // This method is called from .ctor(). When overriden, we can
+               // avoid the "absolute uri" constraints of the .ctor() by
+               // overriding with custom code.
                protected virtual void Parse ()
                {
-               }       
+                       Parse (source);
+
+                       if (userEscaped) 
+                               return;
+
+                       host = EscapeString (host, false, true, false);
+                       path = EscapeString (path);
+               }
+               
+               protected virtual string Unescape (string str)
+               {
+                       return Unescape (str, false);
+               }
                
-               protected virtual string Unescape (string str
+               private string Unescape (string str, bool excludeSharp
                {
                        if (str == null)
                                return String.Empty;
@@ -748,10 +838,14 @@ namespace System
                        for (int i = 0; i < len; i++) {
                                char c = str [i];
                                if (c == '%') {
-                                       s.Append (HexUnescape (str, ref i));
+                                       char x = HexUnescape (str, ref i);
+                                       if (excludeSharp && x == '#')
+                                               s.Append ("%23");
+                                       else
+                                               s.Append (x);
                                        i--;
                                } else
-                                       s.Append (c);                                   
+                                       s.Append (c);
                        }
                        return s.ToString ();
                }
@@ -759,6 +853,68 @@ namespace System
                
                // Private Methods
                
+               private void ParseAsWindowsUNC (string uriString)
+               {
+                       scheme = UriSchemeFile;
+                       port = -1;
+                       fragment = String.Empty;
+                       query = String.Empty;
+                       isUnc = true;
+
+                       uriString = uriString.TrimStart (new char [] {'\\'});
+                       int pos = uriString.IndexOf ('\\');
+                       if (pos > 0) {
+                               path = uriString.Substring (pos);
+                               host = uriString.Substring (0, pos);
+                       } else { // "\\\\server"
+                               host = uriString;
+                               path = String.Empty;
+                       }
+                       path = path.Replace ("\\", "/");
+               }
+
+               private void ParseAsWindowsAbsoluteFilePath (string uriString)
+               {
+                       if (uriString.Length > 2 && uriString [2] != '\\'
+                                       && uriString [2] != '/')
+                               throw new UriFormatException ("Relative file path is not allowed.");
+                       scheme = UriSchemeFile;
+                       host = String.Empty;
+                       port = -1;
+                       path = uriString.Replace ("\\", "/");
+                       fragment = String.Empty;
+                       query = String.Empty;
+               }
+
+               private void ParseAsUnixAbsoluteFilePath (string uriString)
+               {
+                       isUnixFilePath = true;
+                       scheme = UriSchemeFile;
+                       port = -1;
+                       fragment = String.Empty;
+                       query = String.Empty;
+                       host = String.Empty;
+                       path = null;
+
+                       if (uriString.StartsWith ("//")) {
+                               uriString = uriString.TrimStart (new char [] {'/'});
+                               // Now we don't regard //foo/bar as "foo" host.
+                               /* 
+                               int pos = uriString.IndexOf ('/');
+                               if (pos > 0) {
+                                       path = '/' + uriString.Substring (pos + 1);
+                                       host = uriString.Substring (0, pos);
+                               } else { // "///server"
+                                       host = uriString;
+                                       path = String.Empty;
+                               }
+                               */
+                               path = '/' + uriString;
+                       }
+                       if (path == null)
+                               path = uriString;
+               }
+
                // this parse method is as relaxed as possible about the format
                // it will hardly ever throw a UriFormatException
                private void Parse (string uriString)
@@ -777,76 +933,47 @@ namespace System
                        if (len <= 1) 
                                throw new UriFormatException ();
 
-                       // 1
-                       char c = 'x';
                        int pos = 0;
-                       for (; pos < len; pos++) {
-                               c = uriString [pos];
-                               if ((c == ':') || (c == '/') || (c == '\\') || (c == '?') || (c == '#')) 
-                                       break;
-                       }
-
-                       if (pos == len)
-                               throw new UriFormatException ("The format of the URI could not be determined.");
 
-                       if (c == '/') {
-                               is_root_path = true;
+                       // 1, 2
+                       // Identify Windows path, unix path, or standard URI.
+                       pos = uriString.IndexOf (':');
+                       if (pos < 0) {
+                               // It must be Unix file path or Windows UNC
+                               if (uriString [0] == '/')
+                                       ParseAsUnixAbsoluteFilePath (uriString);
+                               else if (uriString.StartsWith ("\\\\"))
+                                       ParseAsWindowsUNC (uriString);
+                               else
+                                       throw new UriFormatException ("URI scheme was not recognized, nor input string is not recognized as an absolute file path.");
+                               return;
                        }
-
-                       if ((uriString.Length >= 2) &&
-                                       ((uriString [0] == '/') && (uriString [1] == '/')) ||
-                                       ((uriString [0] == '\\') || (uriString [1] == '\\'))) {
-                               is_wins_dir = true;
-                               is_root_path = true;
+                       else if (pos == 1) {
+                               if (!Char.IsLetter (uriString [0]))
+                                       throw new UriFormatException ("URI scheme must start with alphabet character.");
+                               // This means 'a:' == windows full path.
+                               ParseAsWindowsAbsoluteFilePath (uriString);
+                               return;
                        }
 
-                       // 2 scheme
-                       if (c == ':') {
-                               if (pos == 1) {
-                                       // a windows filepath
-                                       if (uriString.Length < 3 || (uriString [2] != '\\' && uriString [2] != '/'))
-                                               throw new UriFormatException ("Invalid URI: The format of the URI could not be determined.");
-                                       isWindowsFilePath = true;
-                                       scheme = Uri.UriSchemeFile;
-                                       path = uriString.Replace ('\\', '/');
-                                       return;
-                               }
-
-                               scheme = uriString.Substring (0, pos).ToLower ();
-                               uriString = uriString.Remove (0, pos + 1);
-                       } else if ((c == '/') && (pos == 0)) {
-                               scheme = Uri.UriSchemeFile;
-                               if (uriString.Length > 1 && uriString [1] != '/')
-                                       // unix bare filepath
-                                       isUnixFilePath = true;
-                               else
-                                       // unix UNC (kind of)
-                                       isUnc = true;
-                       } else {
-                               if (uriString [0] != '\\' && uriString [0] != '/' && !uriString.StartsWith ("file://"))
-                                       throw new UriFormatException ("Invalid URI: The format of the URI could not be determined.");
-                               scheme = Uri.UriSchemeFile;
-                               if (uriString.StartsWith ("\\\\")) {
-                                       isUnc = true;
-                                       isWindowsFilePath = true;
+                       // scheme
+                       scheme = uriString.Substring (0, pos).ToLower (CultureInfo.InvariantCulture);
+                       // Check scheme name characters as specified in RFC2396.
+                       if (!Char.IsLetter (scheme [0]))
+                                       throw new UriFormatException ("URI scheme must start with alphabet character.");
+                       for (int i = 1; i < scheme.Length; i++) {
+                               if (!Char.IsLetterOrDigit (scheme, i)) {
+                                       switch (scheme [i]) {
+                                       case '+':
+                                       case '-':
+                                       case '.':
+                                               break;
+                                       default:
+                                               throw new UriFormatException ("URI scheme must consist of one of alphabet, digits, '+', '-' or '.' character.");
+                                       }
                                }
-                               if (uriString.Length > 8 && uriString [8] != '/')
-                                       isUnc = true;
-                       }
-
-                       // 3
-                       if ((uriString.Length >= 2) && 
-                           ((uriString [0] == '/') && (uriString [1] == '/')) ||
-                           ((uriString [0] == '\\') || (uriString [1] == '\\')))  {
-                               if (scheme == Uri.UriSchemeFile)
-                                       uriString = uriString.TrimStart (new char [] {'/', '\\'});
-                               else
-                                       uriString = uriString.Remove (0, 2);
-                       } else if (!IsPredefinedScheme (scheme)) {
-                               path = uriString;
-                               isOpaquePart = true;
-                               return;
                        }
+                       uriString = uriString.Substring (pos + 1);
 
                        // 8 fragment
                        pos = uriString.IndexOf ('#');
@@ -860,17 +987,35 @@ namespace System
                        if (pos != -1) {
                                query = uriString.Substring (pos);
                                uriString = uriString.Substring (0, pos);
+                               if (!userEscaped)
+                                       query = EscapeString (query);
+                       }
+
+                       // 3
+                       bool unixAbsPath = scheme == UriSchemeFile && uriString.StartsWith ("///");
+                       if (uriString.StartsWith ("//")) {
+                               if (uriString.StartsWith ("////"))
+                                       unixAbsPath = false;
+                               uriString = uriString.TrimStart (new char [] {'/'});
+                               if (uriString.Length > 1 && uriString [1] == ':')
+                                       unixAbsPath = false;
+                       } else if (!IsPredefinedScheme (scheme)) {
+                               path = uriString;
+                               isOpaquePart = true;
+                               return;
                        }
                        
                        // 5 path
-                       pos = uriString.IndexOfAny (new char[] {'/', '\\'});
+                       pos = uriString.IndexOfAny (new char[] {'/'});
+                       if (unixAbsPath)
+                               pos = -1;
                        if (pos == -1) {
                                if ((scheme != Uri.UriSchemeMailto) &&
                                    (scheme != Uri.UriSchemeNews) &&
                                        (scheme != Uri.UriSchemeFile))
                                        path = "/";
                        } else {
-                               path = uriString.Substring (pos).Replace ('\\', '/');
+                               path = uriString.Substring (pos);
                                uriString = uriString.Substring (0, pos);
                        }
 
@@ -884,11 +1029,13 @@ namespace System
                        // 4.b port
                        port = -1;
                        pos = uriString.LastIndexOf (":");
+                       if (unixAbsPath)
+                               pos = -1;
                        if (pos != -1 && pos != (uriString.Length - 1)) {
                                string portStr = uriString.Remove (0, pos + 1);
                                if (portStr.Length > 1 && portStr [portStr.Length - 1] != ']') {
                                        try {
-                                               port = (int) UInt32.Parse (portStr);
+                                               port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
                                                uriString = uriString.Substring (0, pos);
                                        } catch (Exception) {
                                                throw new UriFormatException ("Invalid URI: invalid port number");
@@ -909,7 +1056,10 @@ namespace System
                                }
                        }
 
-                       if (host.Length == 2 && host [1] == ':') {
+                       if (unixAbsPath) {
+                               path = '/' + uriString;
+                               host = String.Empty;
+                       } else if (host.Length == 2 && host [1] == ':') {
                                // windows filepath
                                path = host + path;
                                host = String.Empty;
@@ -928,7 +1078,7 @@ namespace System
                        path = Reduce (path);
                }
 
-               public static string Reduce (string path)
+               private static string Reduce (string path)
                {
                        path = path.Replace ('\\','/');
                        string [] parts = path.Split ('/');
@@ -958,7 +1108,12 @@ namespace System
                                return "/";
 
                        result.Insert (0, "");
-                       return String.Join ("/", (string []) result.ToArray (typeof (string)));
+
+                       string res = String.Join ("/", (string []) result.ToArray (typeof (string)));
+                       if (path.EndsWith ("/"))
+                               res += '/';
+                               
+                       return res;
                }
                                
                private struct UriScheme